1 //: Run a second routine concurrently using 'start-running', without any
2 //: guarantees on how the operations in each are interleaved with each other.
4 void test_scheduler() {
10 " jump-unless 1:num, -1\n"
23 //: first, add a deadline to run(routine)
24 :(before
"End Globals")
25 int Scheduling_interval
= 500;
26 :(before
"End routine Fields")
27 int instructions_run_this_scheduling_slice
;
28 :(before
"End routine Constructor")
29 instructions_run_this_scheduling_slice
= 0;
30 :(after
"Running One Instruction")
31 ++Current_routine
->instructions_run_this_scheduling_slice
;
32 :(replace
{} "bool should_continue_running(const routine* current_routine)")
33 bool should_continue_running(const routine
* current_routine
) {
34 assert(current_routine
== Current_routine
); // argument passed in just to make caller readable above
35 return Current_routine
->state
== RUNNING
36 && Current_routine
->instructions_run_this_scheduling_slice
< Scheduling_interval
;
38 :(after
"stop_running_current_routine:")
39 // Reset instructions_run_this_scheduling_slice
40 Current_routine
->instructions_run_this_scheduling_slice
= 0;
42 //: now the rest of the scheduler is clean
44 :(before
"struct routine")
50 :(before
"End routine Fields")
51 enum routine_state state
;
52 :(before
"End routine Constructor")
55 :(before
"End Globals")
56 vector
<routine
*> Routines
;
57 int Current_routine_index
= 0;
59 Scheduling_interval
= 500;
60 for (int i
= 0; i
< SIZE(Routines
); ++i
)
61 delete Routines
.at(i
);
63 Current_routine
= NULL
;
64 :(replace
{} "void run(const recipe_ordinal r)")
65 void run(const recipe_ordinal r
) {
70 void run(routine
* rr
) {
71 Routines
.push_back(rr
);
72 Current_routine_index
= 0, Current_routine
= Routines
.at(0);
73 while (!all_routines_done()) {
74 skip_to_next_routine();
75 assert(Current_routine
);
76 assert(Current_routine
->state
== RUNNING
);
77 trace(100, "schedule") << current_routine_label() << end();
78 run_current_routine();
79 // Scheduler State Transitions
80 if (Current_routine
->completed())
81 Current_routine
->state
= COMPLETED
;
82 // End Scheduler State Transitions
85 // End Scheduler Cleanup
90 bool all_routines_done() {
91 for (int i
= 0; i
< SIZE(Routines
); ++i
) {
92 if (Routines
.at(i
)->state
== RUNNING
)
98 // skip Current_routine_index past non-RUNNING routines
99 void skip_to_next_routine() {
100 assert(!Routines
.empty());
101 assert(Current_routine_index
< SIZE(Routines
));
102 for (int i
= (Current_routine_index
+1)%SIZE(Routines
); i
!= Current_routine_index
; i
= (i
+1)%SIZE(Routines
)) {
103 if (Routines
.at(i
)->state
== RUNNING
) {
104 Current_routine_index
= i
;
105 Current_routine
= Routines
.at(i
);
111 string
current_routine_label() {
112 return routine_label(Current_routine
);
115 string
routine_label(routine
* r
) {
116 ostringstream result
;
117 const call_stack
& calls
= r
->calls
;
118 for (call_stack::const_iterator p
= calls
.begin(); p
!= calls
.end(); ++p
) {
119 if (p
!= calls
.begin()) result
<< '/';
120 result
<< get(Recipe
, p
->running_recipe
).name
;
125 //: special case for the very first routine
126 :(replace
{} "void run_main(int argc, char* argv[])")
127 void run_main(int argc
, char* argv
[]) {
128 recipe_ordinal r
= get(Recipe_ordinal
, "main");
130 routine
* main_routine
= new routine(r
);
131 // pass in commandline args as ingredients to main
133 Current_routine
= main_routine
;
134 for (int i
= 1; i
< argc
; ++i
) {
136 arg
.push_back(/*alloc id*/0);
137 arg
.push_back(new_mu_text(argv
[i
]));
138 assert(get(Memory
, arg
.back()) == 0);
139 current_call().ingredient_atoms
.push_back(arg
);
144 //:: To schedule new routines to run, call 'start-running'.
146 //: 'start-running' will return a unique id for the routine that was created.
147 //: routine id is a number, but don't do any arithmetic on it
148 :(before
"End routine Fields")
150 :(before
"End Globals")
151 int Next_routine_id
= 1;
152 :(before
"End Reset")
154 :(before
"End routine Constructor")
155 id
= Next_routine_id
;
158 //: routines save the routine that spawned them
159 :(before
"End routine Fields")
160 // todo: really should be routine_id, but that's less efficient.
161 int parent_index
; // only < 0 if there's no parent_index
162 :(before
"End routine Constructor")
165 :(before
"End Primitive Recipe Declarations")
167 :(before
"End Primitive Recipe Numbers")
168 put(Recipe_ordinal
, "start-running", START_RUNNING
);
169 :(before
"End Primitive Recipe Checks")
170 case START_RUNNING
: {
171 if (inst
.ingredients
.empty()) {
172 raise
<< maybe(get(Recipe
, r
).name
) << "'start-running' requires at least one ingredient: the recipe to start running\n" << end();
175 if (!is_mu_recipe(inst
.ingredients
.at(0))) {
176 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();
181 :(before
"End Primitive Recipe Implementations")
182 case START_RUNNING
: {
183 routine
* new_routine
= new routine(ingredients
.at(0).at(0));
184 new_routine
->parent_index
= Current_routine_index
;
185 // populate ingredients
186 for (int i
= /*skip callee*/1; i
< SIZE(current_instruction().ingredients
); ++i
) {
187 new_routine
->calls
.front().ingredient_atoms
.push_back(ingredients
.at(i
));
188 reagent
/*copy*/ ingredient
= current_instruction().ingredients
.at(i
);
189 new_routine
->calls
.front().ingredients
.push_back(ingredient
);
190 // End Populate start-running Ingredient
192 Routines
.push_back(new_routine
);
194 products
.at(0).push_back(new_routine
->id
);
199 void test_scheduler_runs_single_routine() {
200 Scheduling_interval
= 1;
207 CHECK_TRACE_CONTENTS(
209 "run: {1: \"number\"} <- copy {0: \"literal\"}\n"
211 "run: {2: \"number\"} <- copy {0: \"literal\"}\n"
215 void test_scheduler_interleaves_routines() {
216 Scheduling_interval
= 1;
219 " start-running f2\n"
228 CHECK_TRACE_CONTENTS(
230 "run: start-running {f2: \"recipe-literal\"}\n"
232 "run: {3: \"number\"} <- copy {0: \"literal\"}\n"
234 "run: {1: \"number\"} <- copy {0: \"literal\"}\n"
236 "run: {4: \"number\"} <- copy {0: \"literal\"}\n"
238 "run: {2: \"number\"} <- copy {0: \"literal\"}\n"
242 void test_start_running_takes_ingredients() {
245 " start-running f2, 3\n"
246 // wait for f2 to run
248 " jump-unless 1:num, -1\n"
252 " 1:num <- next-ingredient\n"
253 " 2:num <- add 1:num, 1\n"
256 CHECK_TRACE_CONTENTS(
257 "mem: storing 4 in location 2\n"
261 //: type-checking for 'start-running'
263 void test_start_running_checks_types() {
267 " start-running f2, 3\n"
272 CHECK_TRACE_CONTENTS(
273 "error: f1: ingredient 0 has the wrong type at 'start-running f2, 3'\n"
277 // 'start-running' only uses the ingredients of the callee, not its products
278 :(before
"End is_indirect_call_with_ingredients Special-cases")
279 if (r
== START_RUNNING
) return true;
281 //: back to testing 'start-running'
284 void test_start_running_returns_routine_id() {
287 " 1:num <- start-running f2\n"
290 " 12:num <- copy 44\n"
293 CHECK_TRACE_CONTENTS(
294 "mem: storing 2 in location 1\n"
298 //: this scenario requires some careful setup
299 void test_scheduler_skips_completed_routines() {
300 recipe_ordinal f1
= load(
304 recipe_ordinal f2
= load(
308 Routines
.push_back(new routine(f1
)); // f1 meant to run
309 Routines
.push_back(new routine(f2
));
310 Routines
.back()->state
= COMPLETED
; // f2 not meant to run
316 // f1 and f3 can run in any order
317 CHECK_TRACE_CONTENTS(
319 "mem: storing 0 in location 1\n"
321 CHECK_TRACE_DOESNT_CONTAIN("schedule: f2");
322 CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 2");
323 CHECK_TRACE_CONTENTS(
325 "mem: storing 0 in location 3\n"
329 void test_scheduler_starts_at_middle_of_routines() {
330 Routines
.push_back(new routine(COPY
));
331 Routines
.back()->state
= COMPLETED
;
338 CHECK_TRACE_CONTENTS(
341 CHECK_TRACE_DOESNT_CONTAIN("run: idle");
344 //:: Errors in a routine cause it to terminate.
346 void test_scheduler_terminates_routines_after_errors() {
348 Scheduling_interval
= 2;
351 " start-running f2\n"
357 " 3:num <- divide-with-remainder 4, 0\n"
358 " 4:num <- divide-with-remainder 4, 0\n"
361 // f2 should stop after first divide by 0
362 CHECK_TRACE_CONTENTS(
363 "error: f2: divide by zero in '3:num <- divide-with-remainder 4, 0'\n"
365 CHECK_TRACE_DOESNT_CONTAIN("error: f2: divide by zero in '4:num <- divide-with-remainder 4, 0'");
368 :(after
"operator<<(ostream& os, end /*unused*/)")
369 if (Trace_stream
&& Trace_stream
->curr_label
== "error" && Current_routine
) {
370 Current_routine
->state
= COMPLETED
;
373 //:: Routines are marked completed when their parent completes.
376 void test_scheduler_kills_orphans() {
379 " start-running f1\n"
380 // f1 never actually runs because its parent completes without
387 CHECK_TRACE_DOESNT_CONTAIN("schedule: f1");
390 :(before
"End Scheduler Cleanup")
391 for (int i
= 0; i
< SIZE(Routines
); ++i
) {
392 if (Routines
.at(i
)->state
== COMPLETED
) continue;
393 if (Routines
.at(i
)->parent_index
< 0) continue; // root thread
394 // structured concurrency: http://250bpm.com/blog:71
395 if (has_completed_parent(i
)) {
396 Routines
.at(i
)->state
= COMPLETED
;
401 bool has_completed_parent(int routine_index
) {
402 for (int j
= routine_index
; j
>= 0; j
= Routines
.at(j
)->parent_index
) {
403 if (Routines
.at(j
)->state
== COMPLETED
)
409 //:: 'routine-state' can tell if a given routine id is running
411 void test_routine_state_test() {
412 Scheduling_interval
= 2;
415 " 1:num/child-id <- start-running f2\n"
416 " 12:num <- copy 0\n" // race condition since we don't care about location 12
417 // thanks to Scheduling_interval, f2's one instruction runs in
418 // between here and completes
419 " 2:num/state <- routine-state 1:num/child-id\n"
422 " 12:num <- copy 0\n"
423 // trying to run a second instruction marks routine as completed
426 // routine f2 should be in state COMPLETED
427 CHECK_TRACE_CONTENTS(
428 "mem: storing 1 in location 2\n"
432 :(before
"End Primitive Recipe Declarations")
434 :(before
"End Primitive Recipe Numbers")
435 put(Recipe_ordinal
, "routine-state", ROUTINE_STATE
);
436 :(before
"End Primitive Recipe Checks")
437 case ROUTINE_STATE
: {
438 if (SIZE(inst
.ingredients
) != 1) {
439 raise
<< maybe(get(Recipe
, r
).name
) << "'routine-state' requires exactly one ingredient, but got '" << to_original_string(inst
) << "'\n" << end();
442 if (!is_mu_number(inst
.ingredients
.at(0))) {
443 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();
448 :(before
"End Primitive Recipe Implementations")
449 case ROUTINE_STATE
: {
450 int id
= ingredients
.at(0).at(0);
452 for (int i
= 0; i
< SIZE(Routines
); ++i
) {
453 if (Routines
.at(i
)->id
== id
) {
454 result
= Routines
.at(i
)->state
;
459 products
.at(0).push_back(result
);
463 //:: miscellaneous helpers
465 :(before
"End Primitive Recipe Declarations")
467 :(before
"End Primitive Recipe Numbers")
468 put(Recipe_ordinal
, "stop", STOP
);
469 :(before
"End Primitive Recipe Checks")
471 if (SIZE(inst
.ingredients
) != 1) {
472 raise
<< maybe(get(Recipe
, r
).name
) << "'stop' requires exactly one ingredient, but got '" << to_original_string(inst
) << "'\n" << end();
475 if (!is_mu_number(inst
.ingredients
.at(0))) {
476 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();
481 :(before
"End Primitive Recipe Implementations")
483 int id
= ingredients
.at(0).at(0);
484 for (int i
= 0; i
< SIZE(Routines
); ++i
) {
485 if (Routines
.at(i
)->id
== id
) {
486 Routines
.at(i
)->state
= COMPLETED
;
493 :(before
"End Primitive Recipe Declarations")
495 :(before
"End Primitive Recipe Numbers")
496 put(Recipe_ordinal
, "$dump-routines", _DUMP_ROUTINES
);
497 :(before
"End Primitive Recipe Checks")
498 case _DUMP_ROUTINES
: {
501 :(before
"End Primitive Recipe Implementations")
502 case _DUMP_ROUTINES
: {
503 for (int i
= 0; i
< SIZE(Routines
); ++i
) {
504 cerr
<< i
<< ": " << Routines
.at(i
)->id
<< ' ' << Routines
.at(i
)->state
<< ' ' << Routines
.at(i
)->parent_index
<< '\n';
509 //: support for stopping routines after some number of cycles
512 void test_routine_discontinues_past_limit() {
513 Scheduling_interval
= 2;
516 " 1:num/child-id <- start-running f2\n"
517 " limit-time 1:num/child-id, 10\n"
518 // padding loop just to make sure f2 has time to complete
519 " 2:num <- copy 20\n"
520 " 2:num <- subtract 2:num, 1\n"
521 " jump-if 2:num, -2:offset\n"
524 " jump -1:offset\n" // run forever
525 " $print [should never get here], 10/newline\n"
529 CHECK_TRACE_CONTENTS(
530 "schedule: discontinuing routine 2\n"
534 :(before
"End routine States")
536 :(before
"End Scheduler State Transitions")
537 if (Current_routine
->limit
>= 0) {
538 if (Current_routine
->limit
<= Scheduling_interval
) {
539 trace(100, "schedule") << "discontinuing routine " << Current_routine
->id
<< end();
540 Current_routine
->state
= DISCONTINUED
;
541 Current_routine
->limit
= 0;
544 Current_routine
->limit
-= Scheduling_interval
;
548 :(before
"End Test Teardown")
549 if (Passed
&& any_routines_with_error())
550 raise
<< "some routines died with errors\n" << end();
551 :(before
"End Mu Test Teardown")
552 if (Passed
&& any_routines_with_error())
553 raise
<< Current_scenario
->name
<< ": some routines died with errors\n" << end();
556 bool any_routines_with_error() {
557 for (int i
= 0; i
< SIZE(Routines
); ++i
) {
558 if (Routines
.at(i
)->state
== DISCONTINUED
)
564 :(before
"End routine Fields")
566 :(before
"End routine Constructor")
567 limit
= -1; /* no limit */
569 :(before
"End Primitive Recipe Declarations")
571 :(before
"End Primitive Recipe Numbers")
572 put(Recipe_ordinal
, "limit-time", LIMIT_TIME
);
573 :(before
"End Primitive Recipe Checks")
575 if (SIZE(inst
.ingredients
) != 2) {
576 raise
<< maybe(get(Recipe
, r
).name
) << "'limit-time' requires exactly two ingredient, but got '" << to_original_string(inst
) << "'\n" << end();
579 if (!is_mu_number(inst
.ingredients
.at(0))) {
580 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();
583 if (!is_mu_number(inst
.ingredients
.at(1))) {
584 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();
589 :(before
"End Primitive Recipe Implementations")
591 int id
= ingredients
.at(0).at(0);
592 for (int i
= 0; i
< SIZE(Routines
); ++i
) {
593 if (Routines
.at(i
)->id
== id
) {
594 Routines
.at(i
)->limit
= ingredients
.at(1).at(0);
601 :(before
"End routine Fields")
602 int instructions_run
;
603 :(before
"End routine Constructor")
604 instructions_run
= 0;
605 :(before
"Reset instructions_run_this_scheduling_slice")
606 Current_routine
->instructions_run
+= Current_routine
->instructions_run_this_scheduling_slice
;
607 :(before
"End Primitive Recipe Declarations")
608 NUMBER_OF_INSTRUCTIONS
,
609 :(before
"End Primitive Recipe Numbers")
610 put(Recipe_ordinal
, "number-of-instructions", NUMBER_OF_INSTRUCTIONS
);
611 :(before
"End Primitive Recipe Checks")
612 case NUMBER_OF_INSTRUCTIONS
: {
613 if (SIZE(inst
.ingredients
) != 1) {
614 raise
<< maybe(get(Recipe
, r
).name
) << "'number-of-instructions' requires exactly one ingredient, but got '" << to_original_string(inst
) << "'\n" << end();
617 if (!is_mu_number(inst
.ingredients
.at(0))) {
618 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();
623 :(before
"End Primitive Recipe Implementations")
624 case NUMBER_OF_INSTRUCTIONS
: {
625 int id
= ingredients
.at(0).at(0);
627 for (int i
= 0; i
< SIZE(Routines
); ++i
) {
628 if (Routines
.at(i
)->id
== id
) {
629 result
= Routines
.at(i
)->instructions_run
;
634 products
.at(0).push_back(result
);
639 void test_number_of_instructions() {
642 " 10:num/child-id <- start-running f2\n"
644 " loop-unless 20:num\n"
646 " 11:num <- number-of-instructions 10:num\n"
649 // 2 instructions worth of work
650 " 1:num <- copy 34\n"
651 " 20:num <- copy 1\n"
654 // f2 runs an extra instruction for the implicit 'return' added by the
655 // fill_in_return_ingredients transform
656 CHECK_TRACE_CONTENTS(
657 "mem: storing 3 in location 11\n"
661 void test_number_of_instructions_across_multiple_scheduling_intervals() {
662 Scheduling_interval
= 1;
665 " 10:num/child-id <- start-running f2\n"
667 " loop-unless 20:num\n"
669 " 11:num <- number-of-instructions 10:num\n"
672 // 4 instructions worth of work
673 " 1:num <- copy 34\n"
676 " 20:num <- copy 1\n"
679 // f2 runs an extra instruction for the implicit 'return' added by the
680 // fill_in_return_ingredients transform
681 CHECK_TRACE_CONTENTS(
682 "mem: storing 5 in location 11\n"
686 //:: make sure that each routine gets a different alloc to start
688 void test_new_concurrent() {
691 " start-running f2\n"
692 " 1:&:num/raw <- new number:type\n"
693 // wait for f2 to complete
695 " loop-unless 4:num/raw\n"
699 " 2:&:num/raw <- new number:type\n"
700 // hack: assumes scheduler implementation
701 " 3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw\n"
702 // signal f2 complete
703 " 4:num/raw <- copy 1\n"
706 CHECK_TRACE_CONTENTS(
707 "mem: storing 0 in location 3\n"