From e5c11a5137d538b7713dd8708ca767c208824c06 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Mon, 26 Dec 2016 01:17:01 -0800 Subject: 3709 - line numbers in html Each line number also gets an anchor name, but I'm not hyperlinking them for now because I don't want to encourage bookmarking these links just yet. They aren't permalinks because every revision may change what's at any given line number. --- html/072scheduler.cc.html | 1450 +++++++++++++++++++++++---------------------- 1 file changed, 737 insertions(+), 713 deletions(-) (limited to 'html/072scheduler.cc.html') diff --git a/html/072scheduler.cc.html b/html/072scheduler.cc.html index 5681858c..3af700ed 100644 --- a/html/072scheduler.cc.html +++ b/html/072scheduler.cc.html @@ -6,7 +6,7 @@ - + - +
-//: Run a second routine concurrently using 'start-running', without any
-//: guarantees on how the operations in each are interleaved with each other.
-
-:(scenario scheduler)
-def f1 [
-  start-running f2
-  # wait for f2 to run
-  {
-    jump-unless 1:num, -1
-  }
-]
-def f2 [
-  1:num <- copy 1
-]
-+schedule: f1
-+schedule: f2
-
-//: first, add a deadline to run(routine)
-:(before "End Globals")
-int Scheduling_interval = 500;
-:(before "End routine Fields")
-int instructions_run_this_scheduling_slice;
-:(before "End routine Constructor")
-instructions_run_this_scheduling_slice = 0;
-:(before "Running One Instruction")
- ++Current_routine->instructions_run_this_scheduling_slice;
-:(replace{} "bool should_continue_running(const routine* current_routine)")
-bool should_continue_running(const routine* current_routine) {
-  assert(current_routine == Current_routine);  // argument passed in just to make caller readable above
-  return Current_routine->state == RUNNING
-      && Current_routine->instructions_run_this_scheduling_slice < Scheduling_interval;
-}
-:(after "stop_running_current_routine:")
-// Reset instructions_run_this_scheduling_slice
-Current_routine->instructions_run_this_scheduling_slice = 0;
-
-//: now the rest of the scheduler is clean
-
-:(before "struct routine")
-enum routine_state {
-  RUNNING,
-  COMPLETED,
-  // End routine States
-};
-:(before "End routine Fields")
-enum routine_state state;
-:(before "End routine Constructor")
-state = RUNNING;
-
-:(before "End Globals")
-vector<routine*> Routines;
-int Current_routine_index = 0;
-:(before "End Setup")
-Scheduling_interval = 500;
-Routines.clear();
-:(replace{} "void run(const recipe_ordinal r)")
-void run(const recipe_ordinal r) {
-  run(new routine(r));
-}
-
-:(code)
-void run(routine* rr) {
-  Routines.push_back(rr);
-  Current_routine_index = 0, Current_routine = Routines.at(0);
-  while (!all_routines_done()) {
-    skip_to_next_routine();
-    assert(Current_routine);
-    assert(Current_routine->state == RUNNING);
-    trace(9990, "schedule") << current_routine_label() << end();
-    run_current_routine();
-    // Scheduler State Transitions
-    if (Current_routine->completed())
-      Current_routine->state = COMPLETED;
-    // End Scheduler State Transitions
-
-    // Scheduler Cleanup
-    // End Scheduler Cleanup
-  }
-  // End Run Routine
-}
-
-bool all_routines_done() {
-  for (int i = 0;  i < SIZE(Routines);  ++i) {
-    if (Routines.at(i)->state == RUNNING)
-      return false;
-  }
-  return true;
-}
-
-// skip Current_routine_index past non-RUNNING routines
-void skip_to_next_routine() {
-  assert(!Routines.empty());
-  assert(Current_routine_index < SIZE(Routines));
-  for (int i = (Current_routine_index+1)%SIZE(Routines);  i != Current_routine_index;  i = (i+1)%SIZE(Routines)) {
-    if (Routines.at(i)->state == RUNNING) {
-      Current_routine_index = i;
-      Current_routine = Routines.at(i);
-      return;
-    }
-  }
-}
-
-string current_routine_label() {
-  return routine_label(Current_routine);
-}
-
-string routine_label(routine* r) {
-  ostringstream result;
-  const call_stack& calls = r->calls;
-  for (call_stack::const_iterator p = calls.begin();  p != calls.end();  ++p) {
-    if (p != calls.begin()) result << '/';
-    result << get(Recipe, p->running_recipe).name;
-  }
-  return result.str();
-}
-
-:(before "End Teardown")
-for (int i = 0;  i < SIZE(Routines);  ++i)
-  delete Routines.at(i);
-Routines.clear();
-Current_routine = NULL;
-
-//: special case for the very first routine
-:(replace{} "void run_main(int argc, char* argv[])")
-void run_main(int argc, char* argv[]) {
-  recipe_ordinal r = get(Recipe_ordinal, "main");
-  assert(r);
-  routine* main_routine = new routine(r);
-  // pass in commandline args as ingredients to main
-  // todo: test this
-  Current_routine = main_routine;
-  for (int i = 1;  i < argc;  ++i) {
-    vector<double> arg;
-    arg.push_back(new_mu_text(argv[i]));
-    assert(get(Memory, arg.back()) == 0);
-    put(Memory, arg.back(), 1);  // update refcount
-    current_call().ingredient_atoms.push_back(arg);
-  }
-  run(main_routine);
-}
-
-//:: To schedule new routines to run, call 'start-running'.
-
-//: 'start-running' will return a unique id for the routine that was created.
-//: routine id is a number, but don't do any arithmetic on it
-:(before "End routine Fields")
-int id;
-:(before "End Globals")
-int Next_routine_id = 1;
-:(before "End Setup")
-Next_routine_id = 1;
-:(before "End routine Constructor")
-id = Next_routine_id;
-++Next_routine_id;
-
-//: routines save the routine that spawned them
-:(before "End routine Fields")
-// todo: really should be routine_id, but that's less efficient.
-int parent_index;  // only < 0 if there's no parent_index
-:(before "End routine Constructor")
-parent_index = -1;
-
-:(before "End Primitive Recipe Declarations")
-START_RUNNING,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "start-running", START_RUNNING);
-:(before "End Primitive Recipe Checks")
-case START_RUNNING: {
-  if (inst.ingredients.empty()) {
-    raise << maybe(get(Recipe, r).name) << "'start-running' requires at least one ingredient: the recipe to start running\n" << end();
-    break;
-  }
-  if (!is_mu_recipe(inst.ingredients.at(0))) {
-    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();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case START_RUNNING: {
-  routine* new_routine = new routine(ingredients.at(0).at(0));
-  new_routine->parent_index = Current_routine_index;
-  // populate ingredients
-  for (int i = 1;  i < SIZE(current_instruction().ingredients);  ++i) {
-    new_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i));
-    reagent/*copy*/ ingredient = current_instruction().ingredients.at(i);
-    canonize_type(ingredient);
-    new_routine->calls.front().ingredients.push_back(ingredient);
-    // End Populate start-running Ingredient
-  }
-  Routines.push_back(new_routine);
-  products.resize(1);
-  products.at(0).push_back(new_routine->id);
-  break;
-}
-
-:(scenario scheduler_runs_single_routine)
-% Scheduling_interval = 1;
-def f1 [
-  1:num <- copy 0
-  2:num <- copy 0
-]
-+schedule: f1
-+run: {1: "number"} <- copy {0: "literal"}
-+schedule: f1
-+run: {2: "number"} <- copy {0: "literal"}
-
-:(scenario scheduler_interleaves_routines)
-% Scheduling_interval = 1;
-def f1 [
-  start-running f2
-  1:num <- copy 0
-  2:num <- copy 0
-]
-def f2 [
-  3:num <- copy 0
-  4:num <- copy 0
-]
-+schedule: f1
-+run: start-running {f2: "recipe-literal"}
-+schedule: f2
-+run: {3: "number"} <- copy {0: "literal"}
-+schedule: f1
-+run: {1: "number"} <- copy {0: "literal"}
-+schedule: f2
-+run: {4: "number"} <- copy {0: "literal"}
-+schedule: f1
-+run: {2: "number"} <- copy {0: "literal"}
-
-:(scenario start_running_takes_ingredients)
-def f1 [
-  start-running f2, 3
-  # wait for f2 to run
-  {
-    jump-unless 1:num, -1
-  }
-]
-def f2 [
-  1:num <- next-ingredient
-  2:num <- add 1:num, 1
-]
-+mem: storing 4 in location 2
-
-//: type-checking for 'start-running'
-
-:(scenario start_running_checks_types)
-% Hide_errors = true;
-def f1 [
-  start-running f2, 3
-]
-def f2 n:&:num [
-]
-+error: f1: ingredient 0 has the wrong type at 'start-running f2, 3'
-
-// 'start-running' only uses the ingredients of the callee, not its products
-:(before "End is_indirect_call_with_ingredients Special-cases")
-if (r == START_RUNNING) return true;
-
-//: more complex: refcounting management when starting up new routines
-
-:(scenario start_running_immediately_updates_refcounts_of_ingredients)
-% Scheduling_interval = 1;
-def main [
-  local-scope
-  create-new-routine
-  # padding to make sure we run new-routine before returning
-  dummy:num <- copy 0
-  dummy:num <- copy 0
-]
-def create-new-routine [
-  local-scope
-  n:&:num <- new number:type
-  *n <- copy 34
-  start-running new-routine, n
-  # refcount of n decremented
-]
-def new-routine n:&:num [
-  local-scope
-  load-ingredients
-  1:num/raw <- copy *n
-]
-# check that n wasn't reclaimed when create-new-routine returned
-+mem: storing 34 in location 1
-
-//: to support the previous scenario we'll increment refcounts for all call
-//: ingredients right at call time, and stop incrementing refcounts inside
-//: next-ingredient
-:(before "End Populate Call Ingredient")
-increment_any_refcounts(ingredient, ingredients.at(i));
-:(before "End Populate start-running Ingredient")
-increment_any_refcounts(ingredient, ingredients.at(i));
-:(before "End should_update_refcounts_in_write_memory Special-cases For Primitives")
-if (inst.operation == NEXT_INGREDIENT || inst.operation == NEXT_INGREDIENT_WITHOUT_TYPECHECKING) {
-  if (space_index(inst.products.at(0)) > 0) return true;
-  if (has_property(inst.products.at(0), "raw")) return true;
-  return false;
-}
-
-// ensure this works with indirect calls using 'call' as well
-:(scenario start_running_immediately_updates_refcounts_of_ingredients_of_indirect_calls)
-% Scheduling_interval = 1;
-def main [
-  local-scope
-  n:&:num <- new number:type
-  *n <- copy 34
-  call f1, n
-  1:num/raw <- copy *n
-]
-def f1 n:&:num [
-  local-scope
-  load-ingredients
-]
-# check that n wasn't reclaimed when f1 returned
-+mem: storing 34 in location 1
-
-:(scenario next_ingredient_never_leaks_refcounts)
-def create-space n:&:num -> default-space:space [
-  default-space <- new location:type, 2
-  load-ingredients
-]
-def use-space [
-  local-scope
-  0:space/names:create-space <- next-ingredient
-  n:&:num/space:1 <- next-ingredient  # should decrement refcount
-  *n/space:1 <- copy 34
-  n2:num <- add *n/space:1, 1
-  return n2
-]
-def main [
-  local-scope
-  n:&:num <- copy 12000/unsafe  # pretend allocation with a known address
-  *n <- copy 23
-  space:space <- create-space n
-  n2:&:num <- copy 13000/unsafe
-  n3:num <- use-space space, n2
-]
-+run: {n: ("address" "number"), "space": "1"} <- next-ingredient
-+mem: decrementing refcount of 12000: 2 -> 1
-+run: {n: ("address" "number"), "space": "1", "lookup": ()} <- copy {34: "literal"}
-
-//: back to testing 'start-running'
-
-:(scenario start_running_returns_routine_id)
-def f1 [
-  1:num <- start-running f2
-]
-def f2 [
-  12:num <- copy 44
-]
-+mem: storing 2 in location 1
-
-//: this scenario will require some careful setup in escaped C++
-//: (straining our tangle capabilities to near-breaking point)
-:(scenario scheduler_skips_completed_routines)
-% recipe_ordinal f1 = load("recipe f1 [\n1:num <- copy 0\n]\n").front();
-% recipe_ordinal f2 = load("recipe f2 [\n2:num <- copy 0\n]\n").front();
-% Routines.push_back(new routine(f1));  // f1 meant to run
-% Routines.push_back(new routine(f2));
-% Routines.back()->state = COMPLETED;  // f2 not meant to run
-# must have at least one routine without escaping
-def f3 [
-  3:num <- copy 0
-]
-# by interleaving '+' lines with '-' lines, we allow f1 and f3 to run in any order
-+schedule: f1
-+mem: storing 0 in location 1
--schedule: f2
--mem: storing 0 in location 2
-+schedule: f3
-+mem: storing 0 in location 3
-
-:(scenario scheduler_starts_at_middle_of_routines)
-% Routines.push_back(new routine(COPY));
-% Routines.back()->state = COMPLETED;
-def f1 [
-  1:num <- copy 0
-  2:num <- copy 0
-]
-+schedule: f1
--run: idle
-
-//:: Errors in a routine cause it to terminate.
-
-:(scenario scheduler_terminates_routines_after_errors)
-% Hide_errors = true;
-% Scheduling_interval = 2;
-def f1 [
-  start-running f2
-  1:num <- copy 0
-  2:num <- copy 0
-]
-def f2 [
-  # divide by 0 twice
-  3:num <- divide-with-remainder 4, 0
-  4:num <- divide-with-remainder 4, 0
-]
-# f2 should stop after first divide by 0
-+error: f2: divide by zero in '3:num <- divide-with-remainder 4, 0'
--error: f2: divide by zero in '4:num <- divide-with-remainder 4, 0'
-
-:(after "operator<<(ostream& os, unused end)")
-  if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) {
-    Current_routine->state = COMPLETED;
-  }
-
-//:: Routines are marked completed when their parent completes.
-
-:(scenario scheduler_kills_orphans)
-def main [
-  start-running f1
-  # f1 never actually runs because its parent completes without waiting for it
-]
-def f1 [
-  1:num <- copy 0
-]
--schedule: f1
-
-:(before "End Scheduler Cleanup")
-for (int i = 0;  i < SIZE(Routines);  ++i) {
-  if (Routines.at(i)->state == COMPLETED) continue;
-  if (Routines.at(i)->parent_index < 0) continue;  // root thread
-  // structured concurrency: http://250bpm.com/blog:71
-  if (has_completed_parent(i)) {
-    Routines.at(i)->state = COMPLETED;
-  }
-}
-
-:(code)
-bool has_completed_parent(int routine_index) {
-  for (int j = routine_index;  j >= 0;  j = Routines.at(j)->parent_index) {
-    if (Routines.at(j)->state == COMPLETED)
-      return true;
-  }
-  return false;
-}
-
-//:: 'routine-state' can tell if a given routine id is running
-
-:(scenario routine_state_test)
-% Scheduling_interval = 2;
-def f1 [
-  1:num/child-id <- start-running f2
-  12:num <- copy 0  # race condition since we don't care about location 12
-  # thanks to Scheduling_interval, f2's one instruction runs in between here and completes
-  2:num/state <- routine-state 1:num/child-id
-]
-def f2 [
-  12:num <- copy 0
-  # trying to run a second instruction marks routine as completed
-]
-# recipe f2 should be in state COMPLETED
-+mem: storing 1 in location 2
-
-:(before "End Primitive Recipe Declarations")
-ROUTINE_STATE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "routine-state", ROUTINE_STATE);
-:(before "End Primitive Recipe Checks")
-case ROUTINE_STATE: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(0))) {
-    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();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case ROUTINE_STATE: {
-  int id = ingredients.at(0).at(0);
-  int result = -1;
-  for (int i = 0;  i < SIZE(Routines);  ++i) {
-    if (Routines.at(i)->id == id) {
-      result = Routines.at(i)->state;
-      break;
-    }
-  }
-  products.resize(1);
-  products.at(0).push_back(result);
-  break;
-}
-
-//:: miscellaneous helpers
-
-:(before "End Primitive Recipe Declarations")
-STOP,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "stop", STOP);
-:(before "End Primitive Recipe Checks")
-case STOP: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(0))) {
-    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();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case STOP: {
-  int id = ingredients.at(0).at(0);
-  for (int i = 0;  i < SIZE(Routines);  ++i) {
-    if (Routines.at(i)->id == id) {
-      Routines.at(i)->state = COMPLETED;
-      break;
-    }
-  }
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-_DUMP_ROUTINES,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES);
-:(before "End Primitive Recipe Checks")
-case _DUMP_ROUTINES: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _DUMP_ROUTINES: {
-  for (int i = 0;  i < SIZE(Routines);  ++i) {
-    cerr << i << ": " << Routines.at(i)->id << ' ' << Routines.at(i)->state << ' ' << Routines.at(i)->parent_index << '\n';
-  }
-  break;
-}
-
-//: support for stopping routines after some number of cycles
-
-:(scenario routine_discontinues_past_limit)
-% Scheduling_interval = 2;
-def f1 [
-  1:num/child-id <- start-running f2
-  limit-time 1:num/child-id, 10
-  # padding loop just to make sure f2 has time to completed
-  2:num <- copy 20
-  2:num <- subtract 2:num, 1
-  jump-if 2:num, -2:offset
-]
-def f2 [
-  jump -1:offset  # run forever
-  $print [should never get here], 10/newline
-]
-# f2 terminates
-+schedule: discontinuing routine 2
-
-:(before "End routine States")
-DISCONTINUED,
-:(before "End Scheduler State Transitions")
-if (Current_routine->limit >= 0) {
-  if (Current_routine->limit <= Scheduling_interval) {
-    trace(9999, "schedule") << "discontinuing routine " << Current_routine->id << end();
-    Current_routine->state = DISCONTINUED;
-    Current_routine->limit = 0;
-  }
-  else {
-    Current_routine->limit -= Scheduling_interval;
-  }
-}
-
-:(before "End Test Teardown")
-if (Passed && any_routines_with_error())
-  raise << "some routines died with errors\n" << end();
-:(before "End Mu Test Teardown")
-if (Passed && any_routines_with_error())
-  raise << Current_scenario->name << ": some routines died with errors\n" << end();
-
-:(code)
-bool any_routines_with_error() {
-  for (int i = 0;  i < SIZE(Routines);  ++i) {
-    if (Routines.at(i)->state == DISCONTINUED)
-      return true;
-  }
-  return false;
-}
-
-:(before "End routine Fields")
-int limit;
-:(before "End routine Constructor")
-limit = -1;  /* no limit */
-
-:(before "End Primitive Recipe Declarations")
-LIMIT_TIME,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "limit-time", LIMIT_TIME);
-:(before "End Primitive Recipe Checks")
-case LIMIT_TIME: {
-  if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got '" << inst.original_string << "'\n" << end();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(0))) {
-    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();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(1))) {
-    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();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case LIMIT_TIME: {
-  int id = ingredients.at(0).at(0);
-  for (int i = 0;  i < SIZE(Routines);  ++i) {
-    if (Routines.at(i)->id == id) {
-      Routines.at(i)->limit = ingredients.at(1).at(0);
-      break;
-    }
-  }
-  break;
-}
-
-:(before "End routine Fields")
-int instructions_run;
-:(before "End routine Constructor")
-instructions_run = 0;
-:(before "Reset instructions_run_this_scheduling_slice")
-Current_routine->instructions_run += Current_routine->instructions_run_this_scheduling_slice;
-:(before "End Primitive Recipe Declarations")
-NUMBER_OF_INSTRUCTIONS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "number-of-instructions", NUMBER_OF_INSTRUCTIONS);
-:(before "End Primitive Recipe Checks")
-case NUMBER_OF_INSTRUCTIONS: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(get(Recipe, r).name) << "'number-of-instructions' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(0))) {
-    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();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case NUMBER_OF_INSTRUCTIONS: {
-  int id = ingredients.at(0).at(0);
-  int result = -1;
-  for (int i = 0;  i < SIZE(Routines);  ++i) {
-    if (Routines.at(i)->id == id) {
-      result = Routines.at(i)->instructions_run;
-      break;
-    }
-  }
-  products.resize(1);
-  products.at(0).push_back(result);
-  break;
-}
-
-:(scenario number_of_instructions)
-def f1 [
-  10:num/child-id <- start-running f2
-  {
-    loop-unless 20:num
-  }
-  11:num <- number-of-instructions 10:num
-]
-def f2 [
-  # 2 instructions worth of work
-  1:num <- copy 34
-  20:num <- copy 1
-]
-# f2 runs an extra instruction for the implicit return added by the
-# fill_in_return_ingredients transform
-+mem: storing 3 in location 11
-
-:(scenario number_of_instructions_across_multiple_scheduling_intervals)
-% Scheduling_interval = 1;
-def f1 [
-  10:num/child-id <- start-running f2
-  {
-    loop-unless 20:num
-  }
-  11:num <- number-of-instructions 10:num
-]
-def f2 [
-  # 4 instructions worth of work
-  1:num <- copy 34
-  2:num <- copy 1
-  2:num <- copy 3
-  20:num <- copy 1
-]
-# f2 runs an extra instruction for the implicit return added by the
-# fill_in_return_ingredients transform
-+mem: storing 5 in location 11
-
-//:: make sure that each routine gets a different alloc to start
-
-:(scenario new_concurrent)
-def f1 [
-  start-running f2
-  1:&:num/raw <- new number:type
-  # wait for f2 to complete
-  {
-    loop-unless 4:num/raw
-  }
-]
-def f2 [
-  2:&:num/raw <- new number:type
-  # hack: assumes scheduler implementation
-  3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw
-  # signal f2 complete
-  4:num/raw <- copy 1
-]
-+mem: storing 0 in location 3
+  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
 
-- cgit 1.4.1-2-gfad0