1 //: Run a second routine concurrently using 'start-running', without any
  2 //: guarantees on how the operations in each are interleaved with each other.
  3 
  4 :(scenario scheduler)
  5 def f1 [
  6   start-running f2
  7   # wait for f2 to run
  8   {
  9   ¦ jump-unless 1:num, -1
 10   }
 11 ]
 12 def f2 [
 13   1:num <- copy 1
 14 ]
 15 +schedule: f1
 16 +schedule: f2
 17 
 18 //: first, add a deadline to run(routine)
 19 :(before "End Globals")
 20 int Scheduling_interval = 500;
 21 :(before "End routine Fields")
 22 int instructions_run_this_scheduling_slice;
 23 :(before "End routine Constructor")
 24 instructions_run_this_scheduling_slice = 0;
 25 :(after "Running One Instruction")
 26  ++Current_routine->instructions_run_this_scheduling_slice;
 27 :(replace{} "bool should_continue_running(const routine* current_routine)")
 28 bool should_continue_running(const routine* current_routine) {
 29   assert(current_routine == Current_routine);  // argument passed in just to make caller readable above
 30   return Current_routine->state == RUNNING
 31   ¦ ¦ && Current_routine->instructions_run_this_scheduling_slice < Scheduling_interval;
 32 }
 33 :(after "stop_running_current_routine:")
 34 // Reset instructions_run_this_scheduling_slice
 35 Current_routine->instructions_run_this_scheduling_slice = 0;
 36 
 37 //: now the rest of the scheduler is clean
 38 
 39 :(before "struct routine")
 40 enum routine_state {
 41   RUNNING,
 42   COMPLETED,
 43   // End routine States
 44 };
 45 :(before "End routine Fields")
 46 enum routine_state state;
 47 :(before "End routine Constructor")
 48 state = RUNNING;
 49 
 50 :(before "End Globals")
 51 vector<routine*> Routines;
 52 int Current_routine_index = 0;
 53 :(before "End Reset")
 54 Scheduling_interval = 500;
 55 for (int i = 0;  i < SIZE(Routines);  ++i)
 56   delete Routines.at(i);
 57 Routines.clear();
 58 Current_routine = NULL;
 59 :(replace{} "void run(const recipe_ordinal r)")
 60 void run(const recipe_ordinal r) {
 61   run(new routine(r));
 62 }
 63 
 64 :(code)
 65 void run(routine* rr) {
 66   Routines.push_back(rr);
 67   Current_routine_index = 0, Current_routine = Routines.at(0);
 68   while (!all_routines_done()) {
 69   ¦ skip_to_next_routine();
 70   ¦ assert(Current_routine);
 71   ¦ assert(Current_routine->state == RUNNING);
 72   ¦ trace(9990, "schedule") << current_routine_label() << end();
 73   ¦ run_current_routine();
 74   ¦ // Scheduler State Transitions
 75   ¦ if (Current_routine->completed())
 76   ¦ ¦ Current_routine->state = COMPLETED;
 77   ¦ // End Scheduler State Transitions
 78 
 79   ¦ // Scheduler Cleanup
 80   ¦ // End Scheduler Cleanup
 81   }
 82   // End Run Routine
 83 }
 84 
 85 bool all_routines_done() {
 86   for (int i = 0;  i < SIZE(Routines);  ++i) {
 87   ¦ if (Routines.at(i)->state == RUNNING)
 88   ¦ ¦ return false;
 89   }
 90   return true;
 91 }
 92 
 93 // skip Current_routine_index past non-RUNNING routines
 94 void skip_to_next_routine() {
 95   assert(!Routines.empty());
 96   assert(Current_routine_index < SIZE(Routines));
 97   for (int i = (Current_routine_index+1)%SIZE(Routines);  i != Current_routine_index;  i = (i+1)%SIZE(Routines)) {
 98   ¦ if (Routines.at(i)->state == RUNNING) {
 99   ¦ ¦ Current_routine_index = i;
100   ¦ ¦ Current_routine = Routines.at(i);
101   ¦ ¦ return;
102   ¦ }
103   }
104 }
105 
106 string current_routine_label() {
107   return routine_label(Current_routine);
108 }
109 
110 string routine_label(routine* r) {
111   ostringstream result;
112   const call_stack& calls = r->calls;
113   for (call_stack::const_iterator p = calls.begin();  p != calls.end();  ++p) {
114   ¦ if (p != calls.begin()) result << '/';
115   ¦ result << get(Recipe, p->running_recipe).name;
116   }
117   return result.str();
118 }
119 
120 //: special case for the very first routine
121 :(replace{} "void run_main(int argc, char* argv[])")
122 void run_main(int argc, char* argv[]) {
123   recipe_ordinal r = get(Recipe_ordinal, "main");
124   assert(r);
125   routine* main_routine = new routine(r);
126   // pass in commandline args as ingredients to main
127   // todo: test this
128   Current_routine = main_routine;
129   for (int i = 1;  i < argc;  ++i) {
130   ¦ vector<double> arg;
131   ¦ arg.push_back(new_mu_text(argv[i]));
132   ¦ assert(get(Memory, arg.back()) == 0);
133   ¦ put(Memory, arg.back(), 1);  // update refcount
134   ¦ current_call().ingredient_atoms.push_back(arg);
135   }
136   run(main_routine);
137 }
138 
139 //:: To schedule new routines to run, call 'start-running'.
140 
141 //: 'start-running' will return a unique id for the routine that was created.
142 //: routine id is a number, but don't do any arithmetic on it
143 :(before "End routine Fields")
144 int id;
145 :(before "End Globals")
146 int Next_routine_id = 1;
147 :(before "End Reset")
148 Next_routine_id = 1;
149 :(before "End routine Constructor")
150 id = Next_routine_id;
151 ++Next_routine_id;
152 
153 //: routines save the routine that spawned them
154 :(before "End routine Fields")
155 // todo: really should be routine_id, but that's less efficient.
156 int parent_index;  // only < 0 if there's no parent_index
157 :(before "End routine Constructor")
158 parent_index = -1;
159 
160 :(before "End Primitive Recipe Declarations")
161 START_RUNNING,
162 :(before "End Primitive Recipe Numbers")
163 put(Recipe_ordinal, "start-running", START_RUNNING);
164 :(before "End Primitive Recipe Checks")
165 case START_RUNNING: {
166   if (inst.ingredients.empty()) {
167   ¦ raise << maybe(get(Recipe, r).name) << "'start-running' requires at least one ingredient: the recipe to start running\n" << end();
168   ¦ break;
169   }
170   if (!is_mu_recipe(inst.ingredients.at(0))) {
171   ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'start-running' should be a recipe, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
172   ¦ break;
173   }
174   break;
175 }
176 :(before "End Primitive Recipe Implementations")
177 case START_RUNNING: {
178   routine* new_routine = new routine(ingredients.at(0).at(0));
179   new_routine->parent_index = Current_routine_index;
180   // populate ingredients
181   for (int i = /*skip callee*/1;  i < SIZE(current_instruction().ingredients);  ++i) {
182   ¦ reagent/*copy*/ ingredient = current_instruction().ingredients.at(i);
183   ¦ new_routine->calls.front().ingredients.push_back(ingredient);
184   ¦ vector<double> new_ingredient_atoms = deep_copy(ingredient);
185   ¦ new_routine->calls.front().ingredient_atoms.push_back(new_ingredient_atoms);
186   ¦ // End Populate start-running Ingredient
187   }
188   Routines.push_back(new_routine);
189   products.resize(1);
190   products.at(0).push_back(new_routine->id);
191   break;
192 }
193 
194 :(scenario scheduler_runs_single_routine)
195 % Scheduling_interval = 1;
196 def f1 [
197   1:num <- copy 0
198   2:num <- copy 0
199 ]
200 +schedule: f1
201 +run: {1: "number"} <- copy {0: "literal"}
202 +schedule: f1
203 +run: {2: "number"} <- copy {0: "literal"}
204 
205 :(scenario scheduler_interleaves_routines)
206 % Scheduling_interval = 1;
207 def f1 [
208   start-running f2
209   1:num <- copy 0
210   2:num <- copy 0
211 ]
212 def f2 [
213   3:num <- copy 0
214   4:num <- copy 0
215 ]
216 +schedule: f1
217 +run: start-running {f2: "recipe-literal"}
218 +schedule: f2
219 +run: {3: "number"} <- copy {0: "literal"}
220 +schedule: f1
221 +run: {1: "number"} <- copy {0: "literal"}
222 +schedule: f2
223 +run: {4: "number"} <- copy {0: "literal"}
224 +schedule: f1
225 +run: {2: "number"} <- copy {0: "literal"}
226 
227 :(scenario start_running_takes_ingredients)
228 def f1 [
229   start-running f2, 3
230   # wait for f2 to run
231   {
232   ¦ jump-unless 1:num, -1
233   }
234 ]
235 def f2 [
236   1:num <- next-ingredient
237   2:num <- add 1:num, 1
238 ]
239 +mem: storing 4 in location 2
240 
241 //: type-checking for 'start-running'
242 
243 :(scenario start_running_checks_types)
244 % Hide_errors = true;
245 def f1 [
246   start-running f2, 3
247 ]
248 def f2 n:&:num [
249 ]
250 +error: f1: ingredient 0 has the wrong type at 'start-running f2, 3'
251 
252 // 'start-running' only uses the ingredients of the callee, not its products
253 :(before "End is_indirect_call_with_ingredients Special-cases")
254 if (r == START_RUNNING) return true;
255 
256 //: refcounting management when starting up new routines
257 
258 :(scenario start_running_immediately_updates_refcounts_of_ingredients)
259 % Scheduling_interval = 1;
260 def main [
261   local-scope
262   create-new-routine
263   # padding to make sure we run new-routine before returning
264   dummy:num <- copy 0
265   dummy:num <- copy 0
266 ]
267 def create-new-routine [
268   local-scope
269   n:&:num <- new number:type
270   *n <- copy 34
271   start-running new-routine, n
272   # refcount of n decremented
273 ]
274 def new-routine n:&:num [
275   local-scope
276   load-ingredients
277   1:num/raw <- copy *n
278 ]
279 # check that n was successfully passed into new-routine before being reclaimed
280 +mem: storing 34 in location 1
281 
282 //: ensure this works with indirect calls using 'call' as well
283 :(scenario start_running_immediately_updates_refcounts_of_ingredients_of_indirect_calls)
284 % Scheduling_interval = 1;
285 def main [
286   local-scope
287   n:&:num <- new number:type
288   *n <- copy 34
289   call f1, n
290   1:num/raw <- copy *n
291 ]
292 def f1 n:&:num [
293   local-scope
294   load-ingredients
295 ]
296 # check that n was successfully passed into new-routine before being reclaimed
297 +mem: storing 34 in location 1
298 
299 :(scenario next_ingredient_never_leaks_refcounts)
300 def create-space n:&:num -> default-space:space [
301   default-space <- new location:type, 2
302   load-ingredients
303 ]
304 def use-space [
305   local-scope
306   0:space/names:create-space <- next-ingredient
307   n:&:num/space:1 <- next-ingredient  # should decrement refcount
308   *n/space:1 <- copy 34
309   n2:num <- add *n/space:1, 1
310   return n2
311 ]
312 def main [
313   local-scope
314   n:&:num <- copy 12000/unsafe  # pretend allocation with a known address
315   *n <- copy 23
316   space:space/names:create-space <- create-space n
317   n2:&:num <- copy 13000/unsafe
318   n3:num <- use-space space, n2
319 ]
320 +run: {n: ("address" "number"), "space": "1"} <- next-ingredient
321 +mem: decrementing refcount of 12000: 2 -> 1
322 +run: {n: ("address" "number"), "space": "1", "lookup": ()} <- copy {34: "literal"}
323 
324 //: back to testing 'start-running'
325 
326 :(scenario start_running_returns_routine_id)
327 def f1 [
328   1:num <- start-running f2
329 ]
330 def f2 [
331   12:num <- copy 44
332 ]
333 +mem: storing 2 in location 1
334 
335 //: this scenario will require some careful setup in escaped C++
336 //: (straining our tangle capabilities to near-breaking point)
337 :(scenario scheduler_skips_completed_routines)
338 % recipe_ordinal f1 = load("recipe f1 [\n1:num <- copy 0\n]\n").front();
339 % recipe_ordinal f2 = load("recipe f2 [\n2:num <- copy 0\n]\n").front();
340 % Routines.push_back(new routine(f1));  // f1 meant to run
341 % Routines.push_back(new routine(f2));
342 % Routines.back()->state = COMPLETED;  // f2 not meant to run
343 # must have at least one routine without escaping
344 def f3 [
345   3:num <- copy 0
346 ]
347 # by interleaving '+' lines with '-' lines, we allow f1 and f3 to run in any order
348 +schedule: f1
349 +mem: storing 0 in location 1
350 -schedule: f2
351 -mem: storing 0 in location 2
352 +schedule: f3
353 +mem: storing 0 in location 3
354 
355 :(scenario scheduler_starts_at_middle_of_routines)
356 % Routines.push_back(new routine(COPY));
357 % Routines.back()->state = COMPLETED;
358 def f1 [
359   1:num <- copy 0
360   2:num <- copy 0
361 ]
362 +schedule: f1
363 -run: idle
364 
365 //:: Errors in a routine cause it to terminate.
366 
367 :(scenario scheduler_terminates_routines_after_errors)
368 % Hide_errors = true;
369 % Scheduling_interval = 2;
370 def f1 [
371   start-running f2
372   1:num <- copy 0
373   2:num <- copy 0
374 ]
375 def f2 [
376   # divide by 0 twice
377   3:num <- divide-with-remainder 4, 0
378   4:num <- divide-with-remainder 4, 0
379 ]
380 # f2 should stop after first divide by 0
381 +error: f2: divide by zero in '3:num <- divide-with-remainder 4, 0'
382 -error: f2: divide by zero in '4:num <- divide-with-remainder 4, 0'
383 
384 :(after "operator<<(ostream& os, unused end)")
385   if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) {
386   ¦ Current_routine->state = COMPLETED;
387   }
388 
389 //:: Routines are marked completed when their parent completes.
390 
391 :(scenario scheduler_kills_orphans)
392 def main [
393   start-running f1
394   # f1 never actually runs because its parent completes without waiting for it
395 ]
396 def f1 [
397   1:num <- copy 0
398 ]
399 -schedule: f1
400 
401 :(before "End Scheduler Cleanup")
402 for (int i = 0;  i < SIZE(Routines);  ++i) {
403   if (Routines.at(i)->state == COMPLETED) continue;
404   if (Routines.at(i)->parent_index < 0) continue;  // root thread
405   // structured concurrency: http://250bpm.com/blog:71
406   if (has_completed_parent(i)) {
407   ¦ Routines.at(i)->state = COMPLETED;
408   }
409 }
410 
411 :(code)
412 bool has_completed_parent(int routine_index) {
413   for (int j = routine_index;  j >= 0;  j = Routines.at(j)->parent_index) {
414   ¦ if (Routines.at(j)->state == COMPLETED)
415   ¦ ¦ return true;
416   }
417   return false;
418 }
419 
420 //:: 'routine-state' can tell if a given routine id is running
421 
422 :(scenario routine_state_test)
423 % Scheduling_interval = 2;
424 def f1 [
425   1:num/child-id <- start-running f2
426   12:num <- copy 0  # race condition since we don't care about location 12
427   # thanks to Scheduling_interval, f2's one instruction runs in between here and completes
428   2:num/state <- routine-state 1:num/child-id
429 ]
430 def f2 [
431   12:num <- copy 0
432   # trying to run a second instruction marks routine as completed
433 ]
434 # recipe f2 should be in state COMPLETED
435 +mem: storing 1 in location 2
436 
437 :(before "End Primitive Recipe Declarations")
438 ROUTINE_STATE,
439 :(before "End Primitive Recipe Numbers")
440 put(Recipe_ordinal, "routine-state", ROUTINE_STATE);
441 :(before "End Primitive Recipe Checks")
442 case ROUTINE_STATE: {
443   if (SIZE(inst.ingredients) != 1) {
444   ¦ raise << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
445   ¦ break;
446   }
447   if (!is_mu_number(inst.ingredients.at(0))) {
448   ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'routine-state' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
449   ¦ break;
450   }
451   break;
452 }
453 :(before "End Primitive Recipe Implementations")
454 case ROUTINE_STATE: {
455   int id = ingredients.at(0).at(0);
456   int result = -1;
457   for (int i = 0;  i < SIZE(Routines);  ++i) {
458   ¦ if (Routines.at(i)->id == id) {
459   ¦ ¦ result = Routines.at(i)->state;
460   ¦ ¦ break;
461   ¦ }
462   }
463   products.resize(1);
464   products.at(0).push_back(result);
465   break;
466 }
467 
468 //:: miscellaneous helpers
469 
470 :(before "End Primitive Recipe Declarations")
471 STOP,
472 :(before "End Primitive Recipe Numbers")
473 put(Recipe_ordinal, "stop", STOP);
474 :(before "End Primitive Recipe Checks")
475 case STOP: {
476   if (SIZE(inst.ingredients) != 1) {
477   ¦ raise << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
478   ¦ break;
479   }
480   if (!is_mu_number(inst.ingredients.at(0))) {
481   ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'stop' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
482   ¦ break;
483   }
484   break;
485 }
486 :(before "End Primitive Recipe Implementations")
487 case STOP: {
488   int id = ingredients.at(0).at(0);
489   for (int i = 0;  i < SIZE(Routines);  ++i) {
490   ¦ if (Routines.at(i)->id == id) {
491   ¦ ¦ Routines.at(i)->state = COMPLETED;
492   ¦ ¦ break;
493   ¦ }
494   }
495   break;
496 }
497 
498 :(before "End Primitive Recipe Declarations")
499 _DUMP_ROUTINES,
500 :(before "End Primitive Recipe Numbers")
501 put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES);
502 :(before "End Primitive Recipe Checks")
503 case _DUMP_ROUTINES: {
504   break;
505 }
506 :(before "End Primitive Recipe Implementations")
507 case _DUMP_ROUTINES: {
508   for (int i = 0;  i < SIZE(Routines);  ++i) {
509   ¦ cerr << i << ": " << Routines.at(i)->id << ' ' << Routines.at(i)->state << ' ' << Routines.at(i)->parent_index << '\n';
510   }
511   break;
512 }
513 
514 //: support for stopping routines after some number of cycles
515 
516 :(scenario routine_discontinues_past_limit)
517 % Scheduling_interval = 2;
518 def f1 [
519   1:num/child-id <- start-running f2
520   limit-time 1:num/child-id, 10
521   # padding loop just to make sure f2 has time to completed
522   2:num <- copy 20
523   2:num <- subtract 2:num, 1
524   jump-if 2:num, -2:offset
525 ]
526 def f2 [
527   jump -1:offset  # run forever
528   $print [should never get here], 10/newline
529 ]
530 # f2 terminates
531 +schedule: discontinuing routine 2
532 
533 :(before "End routine States")
534 DISCONTINUED,
535 :(before "End Scheduler State Transitions")
536 if (Current_routine->limit >= 0) {
537   if (Current_routine->limit <= Scheduling_interval) {
538   ¦ trace(9999, "schedule") << "discontinuing routine " << Current_routine->id << end();
539   ¦ Current_routine->state = DISCONTINUED;
540   ¦ Current_routine->limit = 0;
541   }
542   else {
543   ¦ Current_routine->limit -= Scheduling_interval;
544   }
545 }
546 
547 :(before "End Test Teardown")
548 if (Passed && any_routines_with_error())
549   raise << "some routines died with errors\n" << end();
550 :(before "End Mu Test Teardown")
551 if (Passed && any_routines_with_error())
552   raise << Current_scenario->name << ": some routines died with errors\n" << end();
553 
554 :(code)
555 bool any_routines_with_error() {
556   for (int i = 0;  i < SIZE(Routines);  ++i) {
557   ¦ if (Routines.at(i)->state == DISCONTINUED)
558   ¦ ¦ return true;
559   }
560   return false;
561 }
562 
563 :(before "End routine Fields")
564 int limit;
565 :(before "End routine Constructor")
566 limit = -1;  /* no limit */
567 
568 :(before "End Primitive Recipe Declarations")
569 LIMIT_TIME,
570 :(before "End Primitive Recipe Numbers")
571 put(Recipe_ordinal, "limit-time", LIMIT_TIME);
572 :(before "End Primitive Recipe Checks")
573 case LIMIT_TIME: {
574   if (SIZE(inst.ingredients) != 2) {
575   ¦ raise << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end();
576   ¦ break;
577   }
578   if (!is_mu_number(inst.ingredients.at(0))) {
579   ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'limit-time' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
580   ¦ break;
581   }
582   if (!is_mu_number(inst.ingredients.at(1))) {
583   ¦ raise << maybe(get(Recipe, r).name) << "second ingredient of 'limit-time' should be a number (of instructions to run for), but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
584   ¦ break;
585   }
586   break;
587 }
588 :(before "End Primitive Recipe Implementations")
589 case LIMIT_TIME: {
590   int id = ingredients.at(0).at(0);
591   for (int i = 0;  i < SIZE(Routines);  ++i) {
592   ¦ if (Routines.at(i)->id == id) {
593   ¦ ¦ Routines.at(i)->limit = ingredients.at(1).at(0);
594   ¦ ¦ break;
595   ¦ }
596   }
597   break;
598 }
599 
600 :(before "End routine Fields")
601 int instructions_run;
602 :(before "End routine Constructor")
603 instructions_run = 0;
604 :(before "Reset instructions_run_this_scheduling_slice")
605 Current_routine->instructions_run += Current_routine->instructions_run_this_scheduling_slice;
606 :(before "End Primitive Recipe Declarations")
607 NUMBER_OF_INSTRUCTIONS,
608 :(before "End Primitive Recipe Numbers")
609 put(Recipe_ordinal, "number-of-instructions", NUMBER_OF_INSTRUCTIONS);
610 :(before "End Primitive Recipe Checks")
611 case NUMBER_OF_INSTRUCTIONS: {
612   if (SIZE(inst.ingredients) != 1) {
613   ¦ raise << maybe(get(Recipe, r).name) << "'number-of-instructions' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
614   ¦ break;
615   }
616   if (!is_mu_number(inst.ingredients.at(0))) {
617   ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'number-of-instructions' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
618   ¦ break;
619   }
620   break;
621 }
622 :(before "End Primitive Recipe Implementations")
623 case NUMBER_OF_INSTRUCTIONS: {
624   int id = ingredients.at(0).at(0);
625   int result = -1;
626   for (int i = 0;  i < SIZE(Routines);  ++i) {
627   ¦ if (Routines.at(i)->id == id) {
628   ¦ ¦ result = Routines.at(i)->instructions_run;
629   ¦ ¦ break;
630   ¦ }
631   }
632   products.resize(1);
633   products.at(0).push_back(result);
634   break;
635 }
636 
637 :(scenario number_of_instructions)
638 def f1 [
639   10:num/child-id <- start-running f2
640   {
641   ¦ loop-unless 20:num
642   }
643   11:num <- number-of-instructions 10:num
644 ]
645 def f2 [
646   # 2 instructions worth of work
647   1:num <- copy 34
648   20:num <- copy 1
649 ]
650 # f2 runs an extra instruction for the implicit return added by the
651 # fill_in_return_ingredients transform
652 +mem: storing 3 in location 11
653 
654 :(scenario number_of_instructions_across_multiple_scheduling_intervals)
655 % Scheduling_interval = 1;
656 def f1 [
657   10:num/child-id <- start-running f2
658   {
659   ¦ loop-unless 20:num
660   }
661   11:num <- number-of-instructions 10:num
662 ]
663 def f2 [
664   # 4 instructions worth of work
665   1:num <- copy 34
666   2:num <- copy 1
667   2:num <- copy 3
668   20:num <- copy 1
669 ]
670 # f2 runs an extra instruction for the implicit return added by the
671 # fill_in_return_ingredients transform
672 +mem: storing 5 in location 11
673 
674 //:: make sure that each routine gets a different alloc to start
675 
676 :(scenario new_concurrent)
677 def f1 [
678   start-running f2
679   1:&:num/raw <- new number:type
680   # wait for f2 to complete
681   {
682   ¦ loop-unless 4:num/raw
683   }
684 ]
685 def f2 [
686   2:&:num/raw <- new number:type
687   # hack: assumes scheduler implementation
688   3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw
689   # signal f2 complete
690   4:num/raw <- copy 1
691 ]
692 +mem: storing 0 in location 3