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