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 = 1;  i < SIZE(current_instruction().ingredients);  ++i) {
182   ¦ new_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i));
183   ¦ reagent/*copy*/ ingredient = current_instruction().ingredients.at(i);
184   ¦ canonize_type(ingredient);
185   ¦ new_routine->calls.front().ingredients.push_back(ingredient);
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 //: more complex: 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 wasn't reclaimed when create-new-routine returned
280 +mem: storing 34 in location 1
281 
282 //: to support the previous scenario we'll increment refcounts for all call
283 //: ingredients right at call time, and stop incrementing refcounts inside
284 //: next-ingredient
285 :(before "End Populate Call Ingredient")
286 increment_any_refcounts(ingredient, ingredients.at(i));
287 :(before "End Populate start-running Ingredient")
288 increment_any_refcounts(ingredient, ingredients.at(i));
289 :(after "should_update_refcounts() Special-cases When Writing Products Of Primitive Instructions")
290 if (inst.operation == NEXT_INGREDIENT || inst.operation == NEXT_INGREDIENT_WITHOUT_TYPECHECKING) {
291   if (space_index(inst.products.at(0)) > 0) return true;
292   if (has_property(inst.products.at(0), "raw")) return true;
293   return false;
294 }
295 
296 // ensure this works with indirect calls using 'call' as well
297 :(scenario start_running_immediately_updates_refcounts_of_ingredients_of_indirect_calls)
298 % Scheduling_interval = 1;
299 def main [
300   local-scope
301   n:&:num <- new number:type
302   *n <- copy 34
303   call f1, n
304   1:num/raw <- copy *n
305 ]
306 def f1 n:&:num [
307   local-scope
308   load-ingredients
309 ]
310 # check that n wasn't reclaimed when f1 returned
311 +mem: storing 34 in location 1
312 
313 :(scenario next_ingredient_never_leaks_refcounts)
314 def create-space n:&:num -> default-space:space [
315   default-space <- new location:type, 2
316   load-ingredients
317 ]
318 def use-space [
319   local-scope
320   0:space/names:create-space <- next-ingredient
321   n:&:num/space:1 <- next-ingredient  # should decrement refcount
322   *n/space:1 <- copy 34
323   n2:num <- add *n/space:1, 1
324   return n2
325 ]
326 def main [
327   local-scope
328   n:&:num <- copy 12000/unsafe  # pretend allocation with a known address
329   *n <- copy 23
330   space:space <- create-space n
331   n2:&:num <- copy 13000/unsafe
332   n3:num <- use-space space, n2
333 ]
334 +run: {n: ("address" "number"), "space": "1"} <- next-ingredient
335 +mem: decrementing refcount of 12000: 2 -> 1
336 +run: {n: ("address" "number"), "space": "1", "lookup": ()} <- copy {34: "literal"}
337 
338 //: back to testing 'start-running'
339 
340 :(scenario start_running_returns_routine_id)
341 def f1 [
342   1:num <- start-running f2
343 ]
344 def f2 [
345   12:num <- copy 44
346 ]
347 +mem: storing 2 in location 1
348 
349 //: this scenario will require some careful setup in escaped C++
350 //: (straining our tangle capabilities to near-breaking point)
351 :(scenario scheduler_skips_completed_routines)
352 % recipe_ordinal f1 = load("recipe f1 [\n1:num <- copy 0\n]\n").front();
353 % recipe_ordinal f2 = load("recipe f2 [\n2:num <- copy 0\n]\n").front();
354 % Routines.push_back(new routine(f1));  // f1 meant to run
355 % Routines.push_back(new routine(f2));
356 % Routines.back()->state = COMPLETED;  // f2 not meant to run
357 # must have at least one routine without escaping
358 def f3 [
359   3:num <- copy 0
360 ]
361 # by interleaving '+' lines with '-' lines, we allow f1 and f3 to run in any order
362 +schedule: f1
363 +mem: storing 0 in location 1
364 -schedule: f2
365 -mem: storing 0 in location 2
366 +schedule: f3
367 +mem: storing 0 in location 3
368 
369 :(scenario scheduler_starts_at_middle_of_routines)
370 % Routines.push_back(new routine(COPY));
371 % Routines.back()->state = COMPLETED;
372 def f1 [
373   1:num <- copy 0
374   2:num <- copy 0
375 ]
376 +schedule: f1
377 -run: idle
378 
379 //:: Errors in a routine cause it to terminate.
380 
381 :(scenario scheduler_terminates_routines_after_errors)
382 % Hide_errors = true;
383 % Scheduling_interval = 2;
384 def f1 [
385   start-running f2
386   1:num <- copy 0
387   2:num <- copy 0
388 ]
389 def f2 [
390   # divide by 0 twice
391   3:num <- divide-with-remainder 4, 0
392   4:num <- divide-with-remainder 4, 0
393 ]
394 # f2 should stop after first divide by 0
395 +error: f2: divide by zero in '3:num <- divide-with-remainder 4, 0'
396 -error: f2: divide by zero in '4:num <- divide-with-remainder 4, 0'
397 
398 :(after "operator<<(ostream& os, unused end)")
399   if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) {
400   ¦ Current_routine->state = COMPLETED;
401   }
402 
403 //:: Routines are marked completed when their parent completes.
404 
405 :(scenario scheduler_kills_orphans)
406 def main [
407   start-running f1
408   # f1 never actually runs because its parent completes without waiting for it
409 ]
410 def f1 [
411   1:num <- copy 0
412 ]
413 -schedule: f1
414 
415 :(before "End Scheduler Cleanup")
416 for (int i = 0;  i < SIZE(Routines);  ++i) {
417   if (Routines.at(i)->state == COMPLETED) continue;
418   if (Routines.at(i)->parent_index < 0) continue;  // root thread
419   // structured concurrency: http://250bpm.com/blog:71
420   if (has_completed_parent(i)) {
421   ¦ Routines.at(i)->state = COMPLETED;
422   }
423 }
424 
425 :(code)
426 bool has_completed_parent(int routine_index) {
427   for (int j = routine_index;  j >= 0;  j = Routines.at(j)->parent_index) {
428   ¦ if (Routines.at(j)->state == COMPLETED)
429   ¦ ¦ return true;
430   }
431   return false;
432 }
433 
434 //:: 'routine-state' can tell if a given routine id is running
435 
436 :(scenario routine_state_test)
437 % Scheduling_interval = 2;
438 def f1 [
439   1:num/child-id <- start-running f2
440   12:num <- copy 0  # race condition since we don't care about location 12
441   # thanks to Scheduling_interval, f2's one instruction runs in between here and completes
442   2:num/state <- routine-state 1:num/child-id
443 ]
444 def f2 [
445   12:num <- copy 0
446   # trying to run a second instruction marks routine as completed
447 ]
448 # recipe f2 should be in state COMPLETED
449 +mem: storing 1 in location 2
450 
451 :(before "End Primitive Recipe Declarations")
452 ROUTINE_STATE,
453 :(before "End Primitive Recipe Numbers")
454 put(Recipe_ordinal, "routine-state", ROUTINE_STATE);
455 :(before "End Primitive Recipe Checks")
456 case ROUTINE_STATE: {
457   if (SIZE(inst.ingredients) != 1) {
458   ¦ raise << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
459   ¦ break;
460   }
461   if (!is_mu_number(inst.ingredients.at(0))) {
462   ¦ 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();
463   ¦ break;
464   }
465   break;
466 }
467 :(before "End Primitive Recipe Implementations")
468 case ROUTINE_STATE: {
469   int id = ingredients.at(0).at(0);
470   int result = -1;
471   for (int i = 0;  i < SIZE(Routines);  ++i) {
472   ¦ if (Routines.at(i)->id == id) {
473   ¦ ¦ result = Routines.at(i)->state;
474   ¦ ¦ break;
475   ¦ }
476   }
477   products.resize(1);
478   products.at(0).push_back(result);
479   break;
480 }
481 
482 //:: miscellaneous helpers
483 
484 :(before "End Primitive Recipe Declarations")
485 STOP,
486 :(before "End Primitive Recipe Numbers")
487 put(Recipe_ordinal, "stop", STOP);
488 :(before "End Primitive Recipe Checks")
489 case STOP: {
490   if (SIZE(inst.ingredients) != 1) {
491   ¦ raise << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
492   ¦ break;
493   }
494   if (!is_mu_number(inst.ingredients.at(0))) {
495   ¦ 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();
496   ¦ break;
497   }
498   break;
499 }
500 :(before "End Primitive Recipe Implementations")
501 case STOP: {
502   int id = ingredients.at(0).at(0);
503   for (int i = 0;  i < SIZE(Routines);  ++i) {
504   ¦ if (Routines.at(i)->id == id) {
505   ¦ ¦ Routines.at(i)->state = COMPLETED;
506   ¦ ¦ break;
507   ¦ }
508   }
509   break;
510 }
511 
512 :(before "End Primitive Recipe Declarations")
513 _DUMP_ROUTINES,
514 :(before "End Primitive Recipe Numbers")
515 put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES);
516 :(before "End Primitive Recipe Checks")
517 case _DUMP_ROUTINES: {
518   break;
519 }
520 :(before "End Primitive Recipe Implementations")
521 case _DUMP_ROUTINES: {
522   for (int i = 0;  i < SIZE(Routines);  ++i) {
523   ¦ cerr << i << ": " << Routines.at(i)->id << ' ' << Routines.at(i)->state << ' ' << Routines.at(i)->parent_index << '\n';
524   }
525   break;
526 }
527 
528 //: support for stopping routines after some number of cycles
529 
530 :(scenario routine_discontinues_past_limit)
531 % Scheduling_interval = 2;
532 def f1 [
533   1:num/child-id <- start-running f2
534   limit-time 1:num/child-id, 10
535   # padding loop just to make sure f2 has time to completed
536   2:num <- copy 20
537   2:num <- subtract 2:num, 1
538   jump-if 2:num, -2:offset
539 ]
540 def f2 [
541   jump -1:offset  # run forever
542   $print [should never get here], 10/newline
543 ]
544 # f2 terminates
545 +schedule: discontinuing routine 2
546 
547 :(before "End routine States")
548 DISCONTINUED,
549 :(before "End Scheduler State Transitions")
550 if (Current_routine->limit >= 0) {
551   if (Current_routine->limit <= Scheduling_interval) {
552   ¦ trace(9999, "schedule") << "discontinuing routine " << Current_routine->id << end();
553   ¦ Current_routine->state = DISCONTINUED;
554   ¦ Current_routine->limit = 0;
555   }
556   else {
557   ¦ Current_routine->limit -= Scheduling_interval;
558   }
559 }
560 
561 :(before "End Test Teardown")
562 if (Passed && any_routines_with_error())
563   raise << "some routines died with errors\n" << end();
564 :(before "End Mu Test Teardown")
565 if (Passed && any_routines_with_error())
566   raise << Current_scenario->name << ": some routines died with errors\n" << end();
567 
568 :(code)
569 bool any_routines_with_error() {
570   for (int i = 0;  i < SIZE(Routines);  ++i) {
571   ¦ if (Routines.at(i)->state == DISCONTINUED)
572   ¦ ¦ return true;
573   }
574   return false;
575 }
576 
577 :(before "End routine Fields")
578 int limit;
579 :(before "End routine Constructor")
580 limit = -1;  /* no limit */
581 
582 :(before "End Primitive Recipe Declarations")
583 LIMIT_TIME,
584 :(before "End Primitive Recipe Numbers")
585 put(Recipe_ordinal, "limit-time", LIMIT_TIME);
586 :(before "End Primitive Recipe Checks")
587 case LIMIT_TIME: {
588   if (SIZE(inst.ingredients) != 2) {
589   ¦ raise << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end();
590   ¦ break;
591   }
592   if (!is_mu_number(inst.ingredients.at(0))) {
593   ¦ 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();
594   ¦ break;
595   }
596   if (!is_mu_number(inst.ingredients.at(1))) {
597   ¦ 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();
598   ¦ break;
599   }
600   break;
601 }
602 :(before "End Primitive Recipe Implementations")
603 case LIMIT_TIME: {
604   int id = ingredients.at(0).at(0);
605   for (int i = 0;  i < SIZE(Routines);  ++i) {
606   ¦ if (Routines.at(i)->id == id) {
607   ¦ ¦ Routines.at(i)->limit = ingredients.at(1).at(0);
608   ¦ ¦ break;
609   ¦ }
610   }
611   break;
612 }
613 
614 :(before "End routine Fields")
615 int instructions_run;
616 :(before "End routine Constructor")
617 instructions_run = 0;
618 :(before "Reset instructions_run_this_scheduling_slice")
619 Current_routine->instructions_run += Current_routine->instructions_run_this_scheduling_slice;
620 :(before "End Primitive Recipe Declarations")
621 NUMBER_OF_INSTRUCTIONS,
622 :(before "End Primitive Recipe Numbers")
623 put(Recipe_ordinal, "number-of-instructions", NUMBER_OF_INSTRUCTIONS);
624 :(before "End Primitive Recipe Checks")
625 case NUMBER_OF_INSTRUCTIONS: {
626   if (SIZE(inst.ingredients) != 1) {
627   ¦ raise << maybe(get(Recipe, r).name) << "'number-of-instructions' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
628   ¦ break;
629   }
630   if (!is_mu_number(inst.ingredients.at(0))) {
631   ¦ 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();
632   ¦ break;
633   }
634   break;
635 }
636 :(before "End Primitive Recipe Implementations")
637 case NUMBER_OF_INSTRUCTIONS: {
638   int id = ingredients.at(0).at(0);
639   int result = -1;
640   for (int i = 0;  i < SIZE(Routines);  ++i) {
641   ¦ if (Routines.at(i)->id == id) {
642   ¦ ¦ result = Routines.at(i)->instructions_run;
643   ¦ ¦ break;
644   ¦ }
645   }
646   products.resize(1);
647   products.at(0).push_back(result);
648   break;
649 }
650 
651 :(scenario number_of_instructions)
652 def f1 [
653   10:num/child-id <- start-running f2
654   {
655   ¦ loop-unless 20:num
656   }
657   11:num <- number-of-instructions 10:num
658 ]
659 def f2 [
660   # 2 instructions worth of work
661   1:num <- copy 34
662   20:num <- copy 1
663 ]
664 # f2 runs an extra instruction for the implicit return added by the
665 # fill_in_return_ingredients transform
666 +mem: storing 3 in location 11
667 
668 :(scenario number_of_instructions_across_multiple_scheduling_intervals)
669 % Scheduling_interval = 1;
670 def f1 [
671   10:num/child-id <- start-running f2
672   {
673   ¦ loop-unless 20:num
674   }
675   11:num <- number-of-instructions 10:num
676 ]
677 def f2 [
678   # 4 instructions worth of work
679   1:num <- copy 34
680   2:num <- copy 1
681   2:num <- copy 3
682   20:num <- copy 1
683 ]
684 # f2 runs an extra instruction for the implicit return added by the
685 # fill_in_return_ingredients transform
686 +mem: storing 5 in location 11
687 
688 //:: make sure that each routine gets a different alloc to start
689 
690 :(scenario new_concurrent)
691 def f1 [
692   start-running f2
693   1:&:num/raw <- new number:type
694   # wait for f2 to complete
695   {
696   ¦ loop-unless 4:num/raw
697   }
698 ]
699 def f2 [
700   2:&:num/raw <- new number:type
701   # hack: assumes scheduler implementation
702   3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw
703   # signal f2 complete
704   4:num/raw <- copy 1
705 ]
706 +mem: storing 0 in location 3