ssm  0.0.2
Runtime Library for the Sparse Synchronous Model
ssm-scheduler.c
Go to the documentation of this file.
1 /** @file ssm-scheduler.c
2  * @brief Implementation of the SSM runtime scheduler.
3  *
4  * Contains a priority queue implemented using a binary heap.
5  *
6  * When `SSM_DEBUG` is defined, relevant symbols are exported for whitebox
7  * testing (see #SSM_STATIC and #SSM_STATIC_INLINE).
8  *
9  * @author Stephen Edwards (sedwards-lab)
10  * @author John Hui (j-hui)
11  */
12 #include <ssm-internal.h>
13 
14 /** @def SSM_STATIC
15  * @brief A symbol is static unless `SSM_DEBUG` is defined.
16  *
17  * @def SSM_STATIC_INLINE
18  * @brief A symbol is static inline unless `SSM_DEBUG` is defined.
19  */
20 #ifdef SSM_DEBUG
21 #define SSM_STATIC
22 #define SSM_STATIC_INLINE
23 #else
24 #define SSM_STATIC static
25 #define SSM_STATIC_INLINE static inline
26 #endif
27 
28 #ifndef SSM_EVENT_QUEUE_SIZE
29 /** @brief Size of the event queue; override as necessary. */
30 #define SSM_EVENT_QUEUE_SIZE 2048
31 #endif
32 
33 #ifndef SSM_ACT_QUEUE_SIZE
34 /** @brief Size of the activation record queue; override as necessary. */
35 #define SSM_ACT_QUEUE_SIZE 1024
36 #endif
37 
38 /** @brief Extra entries at the start of a binary heap. */
39 #define SSM_QUEUE_HEAD 1
40 
41 /** @brief Queue index type; index of 1 is the first element. */
42 typedef uint16_t q_idx_t;
43 
44 /** @brief Event queue, used to track and schedule events between instants.
45  *
46  * Managed as a binary heap, sorted by @a later_time.
47  *
48  * Since the first element (#SSM_QUEUE_HEAD) is unusued,
49  * event_queue[event_queue_len] is the last element in the queue provided
50  * event_queue_len is non-zero.
51  */
53 
54 /** @brief The number of elements in #event_queue. */
56 
57 /** @brief Activation record queue, for scheduling at each instant.
58  *
59  * Managed as a binary heap sorted by @a priority.
60  */
62 
63 /** @brief the number of elements in #act_queue. */
65 
66 /** @brief The current model time.
67  *
68  * Read with ssm_now(); user programs should not manipuate this directly.
69  *
70  * This starts out initialized to 0; can be reset by ssm_reset().
71  *
72  * ssm_set_now() (and its caller ssm_tick()) advances #now monotonically.
73  */
75 
76 /** @brief Percolate act queue hole up, to find where to put a new entry.
77  *
78  * Starting at the hole, walk up toward the root of the tree, copying parent to
79  * child until we find where we can put the new activation record.
80  *
81  * @param hole index of the hole that needs to be filled.
82  * @param act activation record that needs to be placed in the heap.
83  */
86  // GCOV_EXCL_START
87  SSM_ASSERT(hole >= SSM_QUEUE_HEAD && hole <= act_queue_len);
88  // GCOV_EXCL_STOP
89  ssm_priority_t priority = act->priority;
90  for (; hole > SSM_QUEUE_HEAD && priority < act_queue[hole >> 1]->priority;
91  hole >>= 1)
92  act_queue[hole] = act_queue[hole >> 1];
93  act_queue[hole] = act;
94  act->scheduled = true;
95 }
96 
97 /** @brief Percolate act queue hole down, to find where to put a new entry.
98  *
99  * Starting at the hole, walk down towards the leaves of the tree, moving the
100  * earlier child to the parent and repeating the process on that child. This
101  * makes the parent earlier than both children. Stop when the activation record
102  * we're trying to place has a lower priority than either children.
103  *
104  * @param hole where to place the given activation record.
105  * @param act activation record to be placed in the queue.
106  */
109  // GCOV_EXCL_START
110  SSM_ASSERT(hole >= SSM_QUEUE_HEAD && hole <= act_queue_len);
111  // GCOV_EXCL_STOP
112  ssm_priority_t priority = act->priority;
113  for (;;) {
114  // Find the earlier of the two children
115  q_idx_t child = hole << 1; // Left child
116  if (child > act_queue_len)
117  break; // The parent was a leaf
118  if (child + 1 <= act_queue_len &&
119  act_queue[child + 1]->priority < act_queue[child]->priority)
120  child++; // Right child is earlier than the left
121 
122  if (priority < act_queue[child]->priority)
123  break; // Earlier child is later than what we're inserting
124  act_queue[hole] = act_queue[child];
125  hole = child;
126  }
127  act_queue[hole] = act;
128 }
129 
130 /** @brief Percolate event queue hole up, to find where to put a new entry.
131  *
132  * Starting at the hole, walk up toward the root of the tree, copying parent to
133  * child until we find where we can put the new event.
134  *
135  * The @a later_time field of @a var should already be set to the value it is
136  * sorted on.
137  *
138  * @param hole index of the hole, from 1 to #event_queue_len, inclusive.
139  * @param var event to place in the queue.
140  */
143  // GCOV_EXCL_START
144  SSM_ASSERT(hole >= SSM_QUEUE_HEAD && hole <= event_queue_len);
145  // GCOV_EXCL_STOP
146  ssm_time_t later = var->later_time;
147  for (; hole > SSM_QUEUE_HEAD && later < event_queue[hole >> 1]->later_time;
148  hole >>= 1)
149  event_queue[hole] = event_queue[hole >> 1];
150  event_queue[hole] = var;
151 }
152 
153 /** @brief Percolate event queue hole down, to find where to put a new entry.
154  *
155  * Starting at the hole, walk down towards the leaves of the tree, moving the
156  * earlier child to the parent and repeating the process on that child. This
157  * makes the parent earlier than both children. Stop when the event we're
158  * trying to place is earlier than both of the children.
159  *
160  * @param hole where to place @a event.
161  * @param event event to place in the queue.
162  */
165  // GCOV_EXCL_START
166  SSM_ASSERT(hole >= SSM_QUEUE_HEAD && hole <= event_queue_len);
167  // GCOV_EXCL_STOP
168 
169  ssm_time_t later = event->later_time;
170  for (;;) {
171  // Find the earlier of the two children
172  q_idx_t child = hole << 1; // Left child
173  if (child > event_queue_len)
174  break; // The parent was a leaf
175  if (child + 1 <= event_queue_len &&
176  event_queue[child + 1]->later_time < event_queue[child]->later_time)
177  child++; // Right child is earlier than the left
178 
179  if (later < event_queue[child]->later_time)
180  break; // Earlier child is later than what we're inserting
181  event_queue[hole] = event_queue[child];
182  hole = child;
183  }
184  event_queue[hole] = event;
185 }
186 
187 /** @brief Determine the index of an already scheduled event.
188  *
189  * This is an inefficient linear search, so it's not meant to be used often.
190  * This is in preparation for either removing or rescheduling an event in the
191  * queue. The function should only be called on a variable that is in the
192  * queue.
193  *
194  * @param var the variable being searched for.
195  */
198  // Should be in the queue
199  // GCOV_EXCL_START (control flow here is too predictable to warrant coverage)
201  for (q_idx_t i = SSM_QUEUE_HEAD; i <= event_queue_len; i++)
202  if (event_queue[i] == var)
203  return i;
204  SSM_ASSERT(0); // Should have found the variable if it was marked as queued
205  return 0;
206  // GCOV_EXCL_STOP
207 }
208 
209 #ifdef SSM_DEBUG
210 
211 // GCOV_EXCL_START
212 /** @brief SSM_ASSERT the event queue is well-formed. */
213 void event_queue_consistency_check(void) {
214  if (event_queue_len == 0)
215  return;
216 
218 
219  for (q_idx_t i = SSM_QUEUE_HEAD; i <= event_queue_len; i++) {
220  // Events should be valid
222  // Queue events should have valid time
223  SSM_ASSERT(event_queue[i]->later_time != SSM_NEVER);
224  q_idx_t child = i << 1;
225  if (child <= event_queue_len) {
226  SSM_ASSERT(event_queue[child]);
227  SSM_ASSERT(event_queue[child]->later_time >= event_queue[i]->later_time);
228  if (++child <= event_queue_len) {
229  SSM_ASSERT(event_queue[child]);
230  SSM_ASSERT(event_queue[child]->later_time >=
231  event_queue[i]->later_time);
232  }
233  }
234  }
235 }
236 
237 /** @brief SSM_ASSERT the activation record queue is well-formed. */
238 void act_queue_consistency_check(void) {
239  if (act_queue_len == 0)
240  return;
241 
242  // No overflow
244 
245  for (q_idx_t i = SSM_QUEUE_HEAD; i <= act_queue_len; i++) {
246  // Acts should be valid
247  SSM_ASSERT(act_queue[i]);
248  // If it's in the queue, it should say so
249  SSM_ASSERT(act_queue[i]->scheduled);
250  q_idx_t child = i << 1;
251  if (child <= act_queue_len) {
252  SSM_ASSERT(act_queue[child]);
253  SSM_ASSERT(act_queue[child]->priority >= act_queue[i]->priority);
254  if (++child <= act_queue_len) {
255  SSM_ASSERT(act_queue[child]);
256  SSM_ASSERT(act_queue[child]->priority >= act_queue[i]->priority);
257  }
258  }
259  }
260 }
261 // GCOV_EXCL_STOP
262 #endif
263 
264 /** @brief The program terminates when there are no more processes. */
265 static size_t num_processes = 0;
266 
267 ssm_time_t ssm_now(void) { return now; }
268 
269 // FIXME: Doesn't work well with handlers!
270 // bool ssm_active(void) { return num_processes > 0; }
271 bool ssm_active(void) { return act_queue_len > 0; }
272 
273 ssm_act_t *ssm_enter_int(size_t size, ssm_stepf_t step, ssm_act_t *parent,
274  ssm_priority_t priority, ssm_depth_t depth) {
275  ssm_act_t *act = ssm_mem_alloc(size);
276  *act = (ssm_act_t){
277  .step = step,
278  .caller = parent,
279  .pc = 0,
280  .children = 0,
281  .priority = priority,
282  .depth = depth,
283  .scheduled = false,
284  };
285  ++parent->children;
286  num_processes++;
287  return act;
288 }
289 
290 void ssm_leave(ssm_act_t *act, size_t size) {
291  num_processes--;
292  ssm_act_t *parent = act->caller;
293  ssm_mem_free(act, size);
294  if (--parent->children == 0)
295  parent->step(parent);
296 }
297 
299  if (act->scheduled)
300  return; // Don't activate an already activated routine
301 
302  q_idx_t hole = ++act_queue_len;
303 
306 
307  act_queue_percolate_up(hole, act);
308 }
309 
311  var->value = val;
312  var->last_updated = now;
313  for (ssm_trigger_t *trig = var->triggers; trig; trig = trig->next)
314  if (trig->act->priority > prio)
315  ssm_activate(trig->act);
316 }
317 
319  if (when < now)
320  // later must be in the future
322 
323  if (var->later_time == SSM_NEVER) {
324  // Variable does not have a pending event
325  var->later_time = when;
326 
327  // Add it to the queue
328  q_idx_t hole = ++event_queue_len;
331  event_queue_percolate_up(hole, var);
332 
333  } else {
334  // Variable has a pending event
335  var->later_time = when;
336 
337  // Drop the old pending value
338  ssm_drop(var->later_value);
339 
340  // Reposition the event in the queue as appropriate
341  q_idx_t hole = find_queued_event(var);
342  if (hole == SSM_QUEUE_HEAD || event_queue[hole >> 1]->later_time < when)
343  event_queue_percolate_down(hole, var);
344  else
345  event_queue_percolate_up(hole, var);
346  }
347  var->later_value = val;
348 }
349 
351  // Point us to the first element
352  trig->next = var->triggers;
353 
354  if (var->triggers)
355  // Make first element point to us
356  var->triggers->prev_ptr = &trig->next;
357 
358  // Insert us at the beginning
359  var->triggers = trig;
360 
361  // Our previous is the variable
362  trig->prev_ptr = &var->triggers;
363 }
364 
366  // Tell predecessor to skip us
367  *trig->prev_ptr = trig->next;
368 
369  if (trig->next)
370  // Tell successor its predecessor is our predecessor
371  trig->next->prev_ptr = trig->prev_ptr;
372 }
373 
375  if (var->later_time != SSM_NEVER) {
376  q_idx_t hole = find_queued_event(var);
377  var->later_time = SSM_NEVER;
378  ssm_sv_t *moved_var = event_queue[event_queue_len--];
379  if (hole < SSM_QUEUE_HEAD + event_queue_len) {
380  // Percolate only if removal led to a hole in the queue; no need to do
381  // this if we happened to remove the last element of the queue.
382  if (hole == SSM_QUEUE_HEAD ||
383  event_queue[hole >> 1]->later_time < moved_var->later_time)
384  event_queue_percolate_down(hole, moved_var);
385  else
386  event_queue_percolate_up(hole, moved_var);
387  }
388  }
389 }
390 
393 }
394 
395 void ssm_reset(void) {
396  now = 0L;
397  event_queue_len = 0;
398  act_queue_len = 0;
399 }
400 
402  if (next <= now)
403  // No time-traveling!
405 
406  if (event_queue_len != 0 && next > event_queue[SSM_QUEUE_HEAD]->later_time)
407  // No skipping scheduled events!
409 
410  now = next;
411 }
412 
413 void ssm_update(ssm_sv_t *sv) {
414  if (now != sv->later_time)
416 
417  ssm_drop(sv->value);
418  sv->value = sv->later_value;
419  sv->last_updated = now;
420  sv->later_time = SSM_NEVER;
421  for (ssm_trigger_t *trigger = sv->triggers; trigger; trigger = trigger->next)
422  ssm_activate(trigger->act);
423 }
424 
425 void ssm_tick(void) {
426  if (act_queue_len != 0 && ssm_next_event_time() != SSM_NEVER &&
428  // There are somehow events in the queue that have a timestamp earlier than
429  // the time at which we are trying to tick. This is usually a sign of queue
430  // mismanagement by platform code.
432 
433  if (act_queue_len == 0 && event_queue_len > 0)
435 
436  // Update every variable in the event queue at the current time.
437  while (event_queue_len > 0 &&
438  event_queue[SSM_QUEUE_HEAD]->later_time == now) {
440  ssm_update(sv);
441 
442  // Remove the top event from the queue by inserting the last element in the
443  // queue at the front and percolating it toward the leaves.
444  ssm_sv_t *to_insert = event_queue[event_queue_len--];
445 
446  if (event_queue_len) /* Only percolate if there are other events left */
448  }
449 
450  while (act_queue_len > 0) {
452  to_run->scheduled = false;
453 
454  // Remove the top activation record from the queue by inserting the last
455  // element in the queue at the front and percolating it down.
456  ssm_act_t *to_insert = act_queue[act_queue_len--];
457 
458  if (act_queue_len)
460 
461  to_run->step(to_run);
462  }
463 }
uint8_t ssm_depth_t
Index of least significant bit in a group of priorities.
Definition: ssm.h:111
void ssm_leave(ssm_act_t *act, size_t size)
Destroy the activation record of a routine before leaving.
ssm_act_t * ssm_enter_int(size_t size, ssm_stepf_t step, ssm_act_t *parent, ssm_priority_t priority, ssm_depth_t depth)
Allocate and initialize a routine activation record.
struct ssm_act ssm_act_t
Activation record for an SSM routine.
void ssm_tick(void)
Run the system for the next scheduled instant.
bool ssm_active(void)
Whether there are still active processes in the activation queue.
void ssm_stepf_t(struct ssm_act *)
The function that does an instant's work for a routine.
Definition: ssm.h:117
uint32_t ssm_priority_t
Thread priority.
Definition: ssm.h:102
void ssm_activate(ssm_act_t *act)
Schedule a routine to run in the current instant.
#define SSM_ASSERT(cond)
Throw an internal error.
Definition: ssm-internal.h:23
#define SSM_THROW(reason)
Terminate due to a non-recoverable error, with a specified reason.
Definition: ssm.h:69
@ SSM_EXHAUSTED_ACT_QUEUE
Tried to insert into full activation record queue.
Definition: ssm.h:40
@ SSM_EXHAUSTED_EVENT_QUEUE
Tried to insert into full event queue.
Definition: ssm.h:42
@ SSM_NOT_READY
Not yet ready to perform the requested action.
Definition: ssm.h:48
@ SSM_INVALID_TIME
Specified invalid time, e.g., scheduled assignment at an earlier time.
Definition: ssm.h:50
void ssm_mem_free(void *m, size_t size)
Deallocate memory allocated by ssm_mem_alloc().
Definition: ssm-mem.c:233
#define ssm_drop(v)
Drop a reference to a possible heap item, and free it if necessary.
Definition: ssm.h:338
void * ssm_mem_alloc(size_t size)
Allocate a contiguous range of memory.
Definition: ssm-mem.c:183
void ssm_sv_desensitize(ssm_trigger_t *trig)
Desensitize a variable from a trigger.
void ssm_unschedule(ssm_sv_t *var)
Unschedule any pending events on a variable.
void ssm_update(ssm_sv_t *sv)
Perform a (delayed) update on a variable.
void ssm_sv_later_unsafe(ssm_sv_t *var, ssm_time_t when, ssm_value_t val)
ssm_later() without automatic reference counting.
void ssm_sv_sensitize(ssm_sv_t *var, ssm_trigger_t *trig)
Sensitize a variable to a trigger.
void ssm_sv_assign_unsafe(ssm_sv_t *var, ssm_priority_t prio, ssm_value_t val)
ssm_assign() without automatic reference counting.
#define SSM_NEVER
Time indicating something will never happen.
Definition: ssm.h:417
void ssm_reset(void)
Reset the scheduler.
void ssm_set_now(ssm_time_t next)
Advance the current model time.
ssm_time_t ssm_now(void)
The current model time.
ssm_time_t ssm_next_event_time(void)
The time of the next event in the event queue.
uint64_t ssm_time_t
Absolute time; never to overflow.
Definition: ssm.h:399
The internal interface of the SSM runtime.
SSM_STATIC_INLINE void act_queue_percolate_up(q_idx_t hole, ssm_act_t *act)
Percolate act queue hole up, to find where to put a new entry.
Definition: ssm-scheduler.c:85
#define SSM_EVENT_QUEUE_SIZE
Size of the event queue; override as necessary.
Definition: ssm-scheduler.c:30
#define SSM_ACT_QUEUE_SIZE
Size of the activation record queue; override as necessary.
Definition: ssm-scheduler.c:35
SSM_STATIC ssm_time_t now
The current model time.
Definition: ssm-scheduler.c:74
SSM_STATIC_INLINE q_idx_t find_queued_event(ssm_sv_t *var)
Determine the index of an already scheduled event.
SSM_STATIC q_idx_t act_queue_len
the number of elements in act_queue.
Definition: ssm-scheduler.c:64
#define SSM_STATIC
A symbol is static unless SSM_DEBUG is defined.
Definition: ssm-scheduler.c:24
SSM_STATIC ssm_act_t * act_queue[SSM_ACT_QUEUE_SIZE+SSM_QUEUE_HEAD]
Activation record queue, for scheduling at each instant.
Definition: ssm-scheduler.c:61
static size_t num_processes
The program terminates when there are no more processes.
SSM_STATIC_INLINE void act_queue_percolate_down(q_idx_t hole, ssm_act_t *act)
Percolate act queue hole down, to find where to put a new entry.
SSM_STATIC_INLINE void event_queue_percolate_up(q_idx_t hole, ssm_sv_t *var)
Percolate event queue hole up, to find where to put a new entry.
SSM_STATIC q_idx_t event_queue_len
The number of elements in event_queue.
Definition: ssm-scheduler.c:55
#define SSM_STATIC_INLINE
A symbol is static inline unless SSM_DEBUG is defined.
Definition: ssm-scheduler.c:25
SSM_STATIC_INLINE void event_queue_percolate_down(q_idx_t hole, ssm_sv_t *event)
Percolate event queue hole down, to find where to put a new entry.
SSM_STATIC ssm_sv_t * event_queue[SSM_EVENT_QUEUE_SIZE+SSM_QUEUE_HEAD]
Event queue, used to track and schedule events between instants.
Definition: ssm-scheduler.c:52
#define SSM_QUEUE_HEAD
Extra entries at the start of a binary heap.
Definition: ssm-scheduler.c:39
uint16_t q_idx_t
Queue index type; index of 1 is the first element.
Definition: ssm-scheduler.c:42
Activation record for an SSM routine.
Definition: ssm.h:125
ssm_priority_t priority
Execution priority; lower goes first.
Definition: ssm.h:130
uint16_t children
Number of running child threads.
Definition: ssm.h:129
struct ssm_act * caller
Activation record of caller.
Definition: ssm.h:127
ssm_stepf_t * step
C function for running this continuation.
Definition: ssm.h:126
uint16_t pc
Stored "program counter" for the function.
Definition: ssm.h:128
bool scheduled
True when in the schedule queue.
Definition: ssm.h:132
A scheduled variable that supports scheduled updates with triggers.
Definition: ssm.h:588
ssm_time_t later_time
When the variable should be next updated.
Definition: ssm.h:590
ssm_time_t last_updated
When the variable was last updated.
Definition: ssm.h:591
ssm_value_t value
Current value.
Definition: ssm.h:593
ssm_trigger_t * triggers
List of sensitive continuations.
Definition: ssm.h:592
ssm_value_t later_value
Buffered value for delayed assignment.
Definition: ssm.h:594
Indicates a routine should run when a scheduled variable is written.
Definition: ssm.h:141
struct ssm_trigger ** prev_ptr
Pointer to self in previous element.
Definition: ssm.h:143
struct ssm_trigger * next
Next sensitive trigger, if any.
Definition: ssm.h:142
SSM values are either "packed" values or heap-allocated.
Definition: ssm.h:232