From 94fed2020040cf469bd47c8890ed4e609e3ed561 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sun, 17 Jan 2016 21:20:05 -0800 Subject: 2561 Reorganize layers in preparation for a better, more type-safe implementation of first-class and higher-order recipes. --- 037recipe.cc | 25 --- 038scheduler.cc | 506 ----------------------------------------------- 039wait.cc | 167 ---------------- 043new.cc | 30 --- 050scenario.cc | 1 - 057static_dispatch.cc | 5 + 061recipe.cc | 25 +++ 062scheduler.cc | 533 ++++++++++++++++++++++++++++++++++++++++++++++++++ 063wait.cc | 167 ++++++++++++++++ 9 files changed, 730 insertions(+), 729 deletions(-) delete mode 100644 037recipe.cc delete mode 100644 038scheduler.cc delete mode 100644 039wait.cc create mode 100644 061recipe.cc create mode 100644 062scheduler.cc create mode 100644 063wait.cc diff --git a/037recipe.cc b/037recipe.cc deleted file mode 100644 index 42cc2279..00000000 --- a/037recipe.cc +++ /dev/null @@ -1,25 +0,0 @@ -//: So far we've been calling a fixed recipe in each instruction, but we'd -//: also like to make the recipe a variable, pass recipes to "higher-order" -//: recipes, return recipes from recipes and so on. - -:(before "End Mu Types Initialization") -// 'recipe' is a literal -put(Type_ordinal, "recipe", 0); -// 'recipe-ordinal' is the literal that can store recipe literals -type_ordinal recipe_ordinal = put(Type_ordinal, "recipe-ordinal", Next_type_ordinal++); -get_or_insert(Type, recipe_ordinal).name = "recipe-ordinal"; - -:(before "End Reagent-parsing Exceptions") -if (r.properties.at(0).second && r.properties.at(0).second->value == "recipe") { - r.set_value(get(Recipe_ordinal, r.name)); - return; -} - -:(code) -bool is_mu_recipe(reagent r) { - if (!r.type) return false; - if (r.type->value == get(Type_ordinal, "recipe")) return true; - if (r.type->value == get(Type_ordinal, "recipe-ordinal")) return true; - // End is_mu_recipe Cases - return false; -} diff --git a/038scheduler.cc b/038scheduler.cc deleted file mode 100644 index 7d8127a1..00000000 --- a/038scheduler.cc +++ /dev/null @@ -1,506 +0,0 @@ -//: Run a second routine concurrently using 'start-running', without any -//: guarantees on how the operations in each are interleaved with each other. - -:(scenario scheduler) -recipe f1 [ - start-running f2:recipe - # wait for f2 to run - { - jump-unless 1:number, -1 - } -] -recipe f2 [ - 1:number <- copy 1 -] -+schedule: f1 -+schedule: f2 - -//: first, add a deadline to run(routine) -//: these changes are ugly and brittle; just close your nose and get through the next few lines -:(replace "void run_current_routine()") -void run_current_routine(long long int time_slice) -:(replace "while (!Current_routine->completed())" following "void run_current_routine(long long int time_slice)") -long long int ninstrs = 0; -while (Current_routine->state == RUNNING && ninstrs < time_slice) -:(after "Running One Instruction") -ninstrs++; - -//: 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 Routines; -long long int Current_routine_index = 0; -long long int Scheduling_interval = 500; -:(before "End Setup") -Scheduling_interval = 500; -Routines.clear(); -:(replace{} "void run(recipe_ordinal r)") -void run(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(Scheduling_interval); - // Scheduler State Transitions - if (Current_routine->completed()) - Current_routine->state = COMPLETED; - // End Scheduler State Transitions - - // Scheduler Cleanup - // End Scheduler Cleanup - } -} - -bool all_routines_done() { - for (long long 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 (long long 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() { - ostringstream result; - const call_stack& calls = Current_routine->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 (long long 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"); - if (r) { - routine* main_routine = new routine(r); - // Update main_routine - 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") -long long int id; -:(before "End Globals") -long long 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. -long long 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_error << 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_error << maybe(get(Recipe, r).name) << "first ingredient of 'start-running' should be a recipe, but got " << inst.ingredients.at(0).original_string << '\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 (long long int i = 1; i < SIZE(current_instruction().ingredients); ++i) { - new_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i)); - reagent ingredient = current_instruction().ingredients.at(i); - canonize_type(ingredient); - new_routine->calls.front().ingredients.push_back(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; -recipe f1 [ - 1:number <- copy 0 - 2:number <- copy 0 -] -+schedule: f1 -+run: 1:number <- copy 0 -+schedule: f1 -+run: 2:number <- copy 0 - -:(scenario scheduler_interleaves_routines) -% Scheduling_interval = 1; -recipe f1 [ - start-running f2:recipe - 1:number <- copy 0 - 2:number <- copy 0 -] -recipe f2 [ - 3:number <- copy 0 - 4:number <- copy 0 -] -+schedule: f1 -+run: start-running f2:recipe -+schedule: f2 -+run: 3:number <- copy 0 -+schedule: f1 -+run: 1:number <- copy 0 -+schedule: f2 -+run: 4:number <- copy 0 -+schedule: f1 -+run: 2:number <- copy 0 - -:(scenario start_running_takes_ingredients) -recipe f1 [ - start-running f2:recipe, 3 - # wait for f2 to run - { - jump-unless 1:number, -1 - } -] -recipe f2 [ - 1:number <- next-ingredient - 2:number <- add 1:number, 1 -] -+mem: storing 4 in location 2 - -:(scenario start_running_returns_routine_id) -recipe f1 [ - 1:number <- start-running f2:recipe -] -recipe f2 [ - 12:number <- 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:number <- copy 0\n]\n").front(); -% recipe_ordinal f2 = load("recipe f2 [\n2:number <- 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 -recipe f3 [ - 3:number <- 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; -recipe f1 [ - 1:number <- copy 0 - 2:number <- 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; -recipe f1 [ - start-running f2:recipe - 1:number <- copy 0 - 2:number <- copy 0 -] -recipe f2 [ - # divide by 0 twice - 3:number <- divide-with-remainder 4, 0 - 4:number <- divide-with-remainder 4, 0 -] -# f2 should stop after first divide by 0 -+error: f2: divide by zero in '3:number <- divide-with-remainder 4, 0' --error: f2: divide by zero in '4:number <- 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) -recipe main [ - start-running f1:recipe - # f1 never actually runs because its parent completes without waiting for it -] -recipe f1 [ - 1:number <- copy 0 -] --schedule: f1 - -:(before "End Scheduler Cleanup") -for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->state == COMPLETED) continue; - if (Routines.at(i)->parent_index < 0) continue; // root thread - if (has_completed_parent(i)) { - Routines.at(i)->state = COMPLETED; - } -} - -:(code) -bool has_completed_parent(long long int routine_index) { - for (long long 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; -recipe f1 [ - 1:number/child-id <- start-running f2:recipe - 12:number <- 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:number/state <- routine-state 1:number/child-id -] -recipe f2 [ - 12:number <- 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_error << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << 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: { - long long int id = ingredients.at(0).at(0); - long long int result = -1; - for (long long 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") -RESTART, -:(before "End Primitive Recipe Numbers") -put(Recipe_ordinal, "restart", RESTART); -:(before "End Primitive Recipe Checks") -case RESTART: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'restart' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'restart' 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 RESTART: { - long long int id = ingredients.at(0).at(0); - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->id == id) { - Routines.at(i)->state = RUNNING; - break; - } - } - break; -} - -:(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_error << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << 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: { - long long int id = ingredients.at(0).at(0); - for (long long 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 (long long 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; -recipe f1 [ - 1:number/child-id <- start-running f2:recipe - limit-time 1:number/child-id, 10 - # padding loop just to make sure f2 has time to completed - 2:number <- copy 20 - 2:number <- subtract 2:number, 1 - jump-if 2:number, -2:offset -] -recipe 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 routine Fields") -long long 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_error << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got " << inst.to_string() << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << 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_error << 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: { - long long int id = ingredients.at(0).at(0); - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->id == id) { - Routines.at(i)->limit = ingredients.at(1).at(0); - break; - } - } - break; -} diff --git a/039wait.cc b/039wait.cc deleted file mode 100644 index ef3d30e5..00000000 --- a/039wait.cc +++ /dev/null @@ -1,167 +0,0 @@ -//: Routines can be put in a 'waiting' state, from which it will be ready to -//: run again when a specific memory location changes its value. This is mu's -//: basic technique for orchestrating the order in which different routines -//: operate. - -:(scenario wait_for_location) -recipe f1 [ - 1:number <- copy 0 - start-running f2:recipe - wait-for-location 1:number - # now wait for f2 to run and modify location 1 before using its value - 2:number <- copy 1:number -] -recipe f2 [ - 1:number <- copy 34 -] -# if we got the synchronization wrong we'd be storing 0 in location 2 -+mem: storing 34 in location 2 - -//: define the new state that all routines can be in - -:(before "End routine States") -WAITING, -:(before "End routine Fields") -// only if state == WAITING -long long int waiting_on_location; -int old_value_of_waiting_location; -:(before "End routine Constructor") -waiting_on_location = old_value_of_waiting_location = 0; - -//: primitive recipe to put routines in that state - -:(before "End Primitive Recipe Declarations") -WAIT_FOR_LOCATION, -:(before "End Primitive Recipe Numbers") -put(Recipe_ordinal, "wait-for-location", WAIT_FOR_LOCATION); -:(before "End Primitive Recipe Checks") -case WAIT_FOR_LOCATION: { - break; -} -:(before "End Primitive Recipe Implementations") -case WAIT_FOR_LOCATION: { - reagent loc = current_instruction().ingredients.at(0); - canonize(loc); - Current_routine->state = WAITING; - Current_routine->waiting_on_location = loc.value; - Current_routine->old_value_of_waiting_location = get_or_insert(Memory, loc.value); - trace(9998, "run") << "waiting for location " << loc.value << " to change from " << no_scientific(get_or_insert(Memory, loc.value)) << end(); - break; -} - -//: scheduler tweak to get routines out of that state - -:(before "End Scheduler State Transitions") -for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->state != WAITING) continue; - if (Routines.at(i)->waiting_on_location && - get_or_insert(Memory, Routines.at(i)->waiting_on_location) != Routines.at(i)->old_value_of_waiting_location) { - trace(9999, "schedule") << "waking up routine\n" << end(); - Routines.at(i)->state = RUNNING; - Routines.at(i)->waiting_on_location = Routines.at(i)->old_value_of_waiting_location = 0; - } -} - -//: also allow waiting on a routine to stop running - -:(scenario wait_for_routine) -recipe f1 [ - 1:number <- copy 0 - 12:number/routine <- start-running f2:recipe - wait-for-routine 12:number/routine - # now wait for f2 to run and modify location 1 before using its value - 3:number <- copy 1:number -] -recipe f2 [ - 1:number <- copy 34 -] -+schedule: f1 -+run: waiting for routine 2 -+schedule: f2 -+schedule: waking up routine 1 -+schedule: f1 -# if we got the synchronization wrong we'd be storing 0 in location 3 -+mem: storing 34 in location 3 - -:(before "End routine Fields") -// only if state == WAITING -long long int waiting_on_routine; -:(before "End routine Constructor") -waiting_on_routine = 0; - -:(before "End Primitive Recipe Declarations") -WAIT_FOR_ROUTINE, -:(before "End Primitive Recipe Numbers") -put(Recipe_ordinal, "wait-for-routine", WAIT_FOR_ROUTINE); -:(before "End Primitive Recipe Checks") -case WAIT_FOR_ROUTINE: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'wait-for-routine' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end(); - break; - } - if (!is_mu_number(inst.ingredients.at(0))) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'wait-for-routine' 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 WAIT_FOR_ROUTINE: { - if (ingredients.at(0).at(0) == Current_routine->id) { - raise_error << maybe(current_recipe_name()) << "routine can't wait for itself! " << current_instruction().to_string() << '\n' << end(); - break; - } - Current_routine->state = WAITING; - Current_routine->waiting_on_routine = ingredients.at(0).at(0); - trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << end(); - break; -} - -:(before "End Scheduler State Transitions") -// Wake up any routines waiting for other routines to go to sleep. -// Important: this must come after the scheduler loop above giving routines -// waiting for locations to change a chance to wake up. -for (long long int i = 0; i < SIZE(Routines); ++i) { - if (Routines.at(i)->state != WAITING) continue; - if (!Routines.at(i)->waiting_on_routine) continue; - long long int id = Routines.at(i)->waiting_on_routine; - assert(id != Routines.at(i)->id); // routine can't wait on itself - for (long long int j = 0; j < SIZE(Routines); ++j) { - if (Routines.at(j)->id == id && Routines.at(j)->state != RUNNING) { - trace(9999, "schedule") << "waking up routine " << Routines.at(i)->id << end(); - Routines.at(i)->state = RUNNING; - Routines.at(i)->waiting_on_routine = 0; - } - } -} - -:(before "End Primitive Recipe Declarations") -SWITCH, -:(before "End Primitive Recipe Numbers") -put(Recipe_ordinal, "switch", SWITCH); -:(before "End Primitive Recipe Checks") -case SWITCH: { - break; -} -:(before "End Primitive Recipe Implementations") -case SWITCH: { - long long int id = some_other_running_routine(); - if (id) { - assert(id != Current_routine->id); - Current_routine->state = WAITING; - Current_routine->waiting_on_routine = id; - } - break; -} - -:(code) -long long int some_other_running_routine() { - for (long long int i = 0; i < SIZE(Routines); ++i) { - if (i == Current_routine_index) continue; - assert(Routines.at(i) != Current_routine); - assert(Routines.at(i)->id != Current_routine->id); - if (Routines.at(i)->state == RUNNING) - return Routines.at(i)->id; - } - return 0; -} diff --git a/043new.cc b/043new.cc index e57c6a4d..b0942397 100644 --- a/043new.cc +++ b/043new.cc @@ -182,25 +182,6 @@ recipe main [ +mem: array size is 0 +mem: storing 1 in location 3 -//: Make sure that each routine gets a different alloc to start. -:(scenario new_concurrent) -recipe f1 [ - start-running f2:recipe - 1:address:number/raw <- new number:type - # wait for f2 to complete - { - loop-unless 4:number/raw - } -] -recipe f2 [ - 2:address:number/raw <- new number:type - # hack: assumes scheduler implementation - 3:boolean/raw <- equal 1:address:number/raw, 2:address:number/raw - # signal f2 complete - 4:number/raw <- copy 1 -] -+mem: storing 0 in location 3 - //: If a routine runs out of its initial allocation, it should allocate more. :(scenario new_overflow) % Initial_memory_per_routine = 2; @@ -367,17 +348,6 @@ long long int new_mu_string(const string& contents) { return result; } -//: pass in commandline args as ingredients to main -//: todo: test this - -:(after "Update main_routine") -Current_routine = main_routine; -for (long long int i = 1; i < argc; ++i) { - vector arg; - arg.push_back(new_mu_string(argv[i])); - current_call().ingredient_atoms.push_back(arg); -} - //: stash recognizes strings :(scenario stash_string) diff --git a/050scenario.cc b/050scenario.cc index 10c65e21..5753be73 100644 --- a/050scenario.cc +++ b/050scenario.cc @@ -137,7 +137,6 @@ void run_mu_scenario(const scenario& s) { Trace_stream = new trace_stream; setup(); } - assert(Routines.empty()); vector tmp = load("recipe scenario-"+s.name+" [ "+s.to_run+" ]"); bind_special_scenario_names(tmp.at(0)); transform_all(); diff --git a/057static_dispatch.cc b/057static_dispatch.cc index bc699bcb..dc2e4cb5 100644 --- a/057static_dispatch.cc +++ b/057static_dispatch.cc @@ -457,3 +457,8 @@ recipe foo a:boolean -> b:number [ ] +error: main: missing type for x in 'y:number <- foo x' +error: main: failed to find a matching call for 'y:number <- foo x' + +:(before "End Includes") +using std::min; +using std::max; +using std::abs; diff --git a/061recipe.cc b/061recipe.cc new file mode 100644 index 00000000..42cc2279 --- /dev/null +++ b/061recipe.cc @@ -0,0 +1,25 @@ +//: So far we've been calling a fixed recipe in each instruction, but we'd +//: also like to make the recipe a variable, pass recipes to "higher-order" +//: recipes, return recipes from recipes and so on. + +:(before "End Mu Types Initialization") +// 'recipe' is a literal +put(Type_ordinal, "recipe", 0); +// 'recipe-ordinal' is the literal that can store recipe literals +type_ordinal recipe_ordinal = put(Type_ordinal, "recipe-ordinal", Next_type_ordinal++); +get_or_insert(Type, recipe_ordinal).name = "recipe-ordinal"; + +:(before "End Reagent-parsing Exceptions") +if (r.properties.at(0).second && r.properties.at(0).second->value == "recipe") { + r.set_value(get(Recipe_ordinal, r.name)); + return; +} + +:(code) +bool is_mu_recipe(reagent r) { + if (!r.type) return false; + if (r.type->value == get(Type_ordinal, "recipe")) return true; + if (r.type->value == get(Type_ordinal, "recipe-ordinal")) return true; + // End is_mu_recipe Cases + return false; +} diff --git a/062scheduler.cc b/062scheduler.cc new file mode 100644 index 00000000..39568072 --- /dev/null +++ b/062scheduler.cc @@ -0,0 +1,533 @@ +//: Run a second routine concurrently using 'start-running', without any +//: guarantees on how the operations in each are interleaved with each other. + +:(scenario scheduler) +recipe f1 [ + start-running f2:recipe + # wait for f2 to run + { + jump-unless 1:number, -1 + } +] +recipe f2 [ + 1:number <- copy 1 +] ++schedule: f1 ++schedule: f2 + +//: first, add a deadline to run(routine) +//: these changes are ugly and brittle; just close your nose and get through the next few lines +:(replace "void run_current_routine()") +void run_current_routine(long long int time_slice) +:(replace "while (!Current_routine->completed())" following "void run_current_routine(long long int time_slice)") +long long int ninstrs = 0; +while (Current_routine->state == RUNNING && ninstrs < time_slice) +:(after "Running One Instruction") +ninstrs++; + +//: 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 Routines; +long long int Current_routine_index = 0; +long long int Scheduling_interval = 500; +:(before "End Setup") +Scheduling_interval = 500; +Routines.clear(); +:(replace{} "void run(recipe_ordinal r)") +void run(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(Scheduling_interval); + // Scheduler State Transitions + if (Current_routine->completed()) + Current_routine->state = COMPLETED; + // End Scheduler State Transitions + + // Scheduler Cleanup + // End Scheduler Cleanup + } +} + +bool all_routines_done() { + for (long long 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 (long long 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() { + ostringstream result; + const call_stack& calls = Current_routine->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 (long long 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"); + if (r) { + routine* main_routine = new routine(r); + // pass in commandline args as ingredients to main + // todo: test this + Current_routine = main_routine; + for (long long int i = 1; i < argc; ++i) { + vector arg; + arg.push_back(new_mu_string(argv[i])); + 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") +long long int id; +:(before "End Globals") +long long 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. +long long 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_error << 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_error << maybe(get(Recipe, r).name) << "first ingredient of 'start-running' should be a recipe, but got " << inst.ingredients.at(0).original_string << '\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 (long long int i = 1; i < SIZE(current_instruction().ingredients); ++i) { + new_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i)); + reagent ingredient = current_instruction().ingredients.at(i); + canonize_type(ingredient); + new_routine->calls.front().ingredients.push_back(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; +recipe f1 [ + 1:number <- copy 0 + 2:number <- copy 0 +] ++schedule: f1 ++run: 1:number <- copy 0 ++schedule: f1 ++run: 2:number <- copy 0 + +:(scenario scheduler_interleaves_routines) +% Scheduling_interval = 1; +recipe f1 [ + start-running f2:recipe + 1:number <- copy 0 + 2:number <- copy 0 +] +recipe f2 [ + 3:number <- copy 0 + 4:number <- copy 0 +] ++schedule: f1 ++run: start-running f2:recipe ++schedule: f2 ++run: 3:number <- copy 0 ++schedule: f1 ++run: 1:number <- copy 0 ++schedule: f2 ++run: 4:number <- copy 0 ++schedule: f1 ++run: 2:number <- copy 0 + +:(scenario start_running_takes_ingredients) +recipe f1 [ + start-running f2:recipe, 3 + # wait for f2 to run + { + jump-unless 1:number, -1 + } +] +recipe f2 [ + 1:number <- next-ingredient + 2:number <- add 1:number, 1 +] ++mem: storing 4 in location 2 + +:(scenario start_running_returns_routine_id) +recipe f1 [ + 1:number <- start-running f2:recipe +] +recipe f2 [ + 12:number <- 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:number <- copy 0\n]\n").front(); +% recipe_ordinal f2 = load("recipe f2 [\n2:number <- 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 +recipe f3 [ + 3:number <- 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; +recipe f1 [ + 1:number <- copy 0 + 2:number <- 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; +recipe f1 [ + start-running f2:recipe + 1:number <- copy 0 + 2:number <- copy 0 +] +recipe f2 [ + # divide by 0 twice + 3:number <- divide-with-remainder 4, 0 + 4:number <- divide-with-remainder 4, 0 +] +# f2 should stop after first divide by 0 ++error: f2: divide by zero in '3:number <- divide-with-remainder 4, 0' +-error: f2: divide by zero in '4:number <- 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) +recipe main [ + start-running f1:recipe + # f1 never actually runs because its parent completes without waiting for it +] +recipe f1 [ + 1:number <- copy 0 +] +-schedule: f1 + +:(before "End Scheduler Cleanup") +for (long long int i = 0; i < SIZE(Routines); ++i) { + if (Routines.at(i)->state == COMPLETED) continue; + if (Routines.at(i)->parent_index < 0) continue; // root thread + if (has_completed_parent(i)) { + Routines.at(i)->state = COMPLETED; + } +} + +:(code) +bool has_completed_parent(long long int routine_index) { + for (long long 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; +recipe f1 [ + 1:number/child-id <- start-running f2:recipe + 12:number <- 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:number/state <- routine-state 1:number/child-id +] +recipe f2 [ + 12:number <- 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_error << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end(); + break; + } + if (!is_mu_number(inst.ingredients.at(0))) { + raise_error << 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: { + long long int id = ingredients.at(0).at(0); + long long int result = -1; + for (long long 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") +RESTART, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "restart", RESTART); +:(before "End Primitive Recipe Checks") +case RESTART: { + if (SIZE(inst.ingredients) != 1) { + raise_error << maybe(get(Recipe, r).name) << "'restart' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end(); + break; + } + if (!is_mu_number(inst.ingredients.at(0))) { + raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'restart' 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 RESTART: { + long long int id = ingredients.at(0).at(0); + for (long long int i = 0; i < SIZE(Routines); ++i) { + if (Routines.at(i)->id == id) { + Routines.at(i)->state = RUNNING; + break; + } + } + break; +} + +:(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_error << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end(); + break; + } + if (!is_mu_number(inst.ingredients.at(0))) { + raise_error << 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: { + long long int id = ingredients.at(0).at(0); + for (long long 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 (long long 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; +recipe f1 [ + 1:number/child-id <- start-running f2:recipe + limit-time 1:number/child-id, 10 + # padding loop just to make sure f2 has time to completed + 2:number <- copy 20 + 2:number <- subtract 2:number, 1 + jump-if 2:number, -2:offset +] +recipe 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 routine Fields") +long long 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_error << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got " << inst.to_string() << '\n' << end(); + break; + } + if (!is_mu_number(inst.ingredients.at(0))) { + raise_error << 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_error << 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: { + long long int id = ingredients.at(0).at(0); + for (long long int i = 0; i < SIZE(Routines); ++i) { + if (Routines.at(i)->id == id) { + Routines.at(i)->limit = ingredients.at(1).at(0); + break; + } + } + break; +} + +//:: make sure that each routine gets a different alloc to start + +:(scenario new_concurrent) +recipe f1 [ + start-running f2:recipe + 1:address:number/raw <- new number:type + # wait for f2 to complete + { + loop-unless 4:number/raw + } +] +recipe f2 [ + 2:address:number/raw <- new number:type + # hack: assumes scheduler implementation + 3:boolean/raw <- equal 1:address:number/raw, 2:address:number/raw + # signal f2 complete + 4:number/raw <- copy 1 +] ++mem: storing 0 in location 3 diff --git a/063wait.cc b/063wait.cc new file mode 100644 index 00000000..ef3d30e5 --- /dev/null +++ b/063wait.cc @@ -0,0 +1,167 @@ +//: Routines can be put in a 'waiting' state, from which it will be ready to +//: run again when a specific memory location changes its value. This is mu's +//: basic technique for orchestrating the order in which different routines +//: operate. + +:(scenario wait_for_location) +recipe f1 [ + 1:number <- copy 0 + start-running f2:recipe + wait-for-location 1:number + # now wait for f2 to run and modify location 1 before using its value + 2:number <- copy 1:number +] +recipe f2 [ + 1:number <- copy 34 +] +# if we got the synchronization wrong we'd be storing 0 in location 2 ++mem: storing 34 in location 2 + +//: define the new state that all routines can be in + +:(before "End routine States") +WAITING, +:(before "End routine Fields") +// only if state == WAITING +long long int waiting_on_location; +int old_value_of_waiting_location; +:(before "End routine Constructor") +waiting_on_location = old_value_of_waiting_location = 0; + +//: primitive recipe to put routines in that state + +:(before "End Primitive Recipe Declarations") +WAIT_FOR_LOCATION, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "wait-for-location", WAIT_FOR_LOCATION); +:(before "End Primitive Recipe Checks") +case WAIT_FOR_LOCATION: { + break; +} +:(before "End Primitive Recipe Implementations") +case WAIT_FOR_LOCATION: { + reagent loc = current_instruction().ingredients.at(0); + canonize(loc); + Current_routine->state = WAITING; + Current_routine->waiting_on_location = loc.value; + Current_routine->old_value_of_waiting_location = get_or_insert(Memory, loc.value); + trace(9998, "run") << "waiting for location " << loc.value << " to change from " << no_scientific(get_or_insert(Memory, loc.value)) << end(); + break; +} + +//: scheduler tweak to get routines out of that state + +:(before "End Scheduler State Transitions") +for (long long int i = 0; i < SIZE(Routines); ++i) { + if (Routines.at(i)->state != WAITING) continue; + if (Routines.at(i)->waiting_on_location && + get_or_insert(Memory, Routines.at(i)->waiting_on_location) != Routines.at(i)->old_value_of_waiting_location) { + trace(9999, "schedule") << "waking up routine\n" << end(); + Routines.at(i)->state = RUNNING; + Routines.at(i)->waiting_on_location = Routines.at(i)->old_value_of_waiting_location = 0; + } +} + +//: also allow waiting on a routine to stop running + +:(scenario wait_for_routine) +recipe f1 [ + 1:number <- copy 0 + 12:number/routine <- start-running f2:recipe + wait-for-routine 12:number/routine + # now wait for f2 to run and modify location 1 before using its value + 3:number <- copy 1:number +] +recipe f2 [ + 1:number <- copy 34 +] ++schedule: f1 ++run: waiting for routine 2 ++schedule: f2 ++schedule: waking up routine 1 ++schedule: f1 +# if we got the synchronization wrong we'd be storing 0 in location 3 ++mem: storing 34 in location 3 + +:(before "End routine Fields") +// only if state == WAITING +long long int waiting_on_routine; +:(before "End routine Constructor") +waiting_on_routine = 0; + +:(before "End Primitive Recipe Declarations") +WAIT_FOR_ROUTINE, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "wait-for-routine", WAIT_FOR_ROUTINE); +:(before "End Primitive Recipe Checks") +case WAIT_FOR_ROUTINE: { + if (SIZE(inst.ingredients) != 1) { + raise_error << maybe(get(Recipe, r).name) << "'wait-for-routine' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end(); + break; + } + if (!is_mu_number(inst.ingredients.at(0))) { + raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'wait-for-routine' 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 WAIT_FOR_ROUTINE: { + if (ingredients.at(0).at(0) == Current_routine->id) { + raise_error << maybe(current_recipe_name()) << "routine can't wait for itself! " << current_instruction().to_string() << '\n' << end(); + break; + } + Current_routine->state = WAITING; + Current_routine->waiting_on_routine = ingredients.at(0).at(0); + trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << end(); + break; +} + +:(before "End Scheduler State Transitions") +// Wake up any routines waiting for other routines to go to sleep. +// Important: this must come after the scheduler loop above giving routines +// waiting for locations to change a chance to wake up. +for (long long int i = 0; i < SIZE(Routines); ++i) { + if (Routines.at(i)->state != WAITING) continue; + if (!Routines.at(i)->waiting_on_routine) continue; + long long int id = Routines.at(i)->waiting_on_routine; + assert(id != Routines.at(i)->id); // routine can't wait on itself + for (long long int j = 0; j < SIZE(Routines); ++j) { + if (Routines.at(j)->id == id && Routines.at(j)->state != RUNNING) { + trace(9999, "schedule") << "waking up routine " << Routines.at(i)->id << end(); + Routines.at(i)->state = RUNNING; + Routines.at(i)->waiting_on_routine = 0; + } + } +} + +:(before "End Primitive Recipe Declarations") +SWITCH, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "switch", SWITCH); +:(before "End Primitive Recipe Checks") +case SWITCH: { + break; +} +:(before "End Primitive Recipe Implementations") +case SWITCH: { + long long int id = some_other_running_routine(); + if (id) { + assert(id != Current_routine->id); + Current_routine->state = WAITING; + Current_routine->waiting_on_routine = id; + } + break; +} + +:(code) +long long int some_other_running_routine() { + for (long long int i = 0; i < SIZE(Routines); ++i) { + if (i == Current_routine_index) continue; + assert(Routines.at(i) != Current_routine); + assert(Routines.at(i)->id != Current_routine->id); + if (Routines.at(i)->state == RUNNING) + return Routines.at(i)->id; + } + return 0; +} -- cgit 1.4.1-2-gfad0