fix other mandelbrot variants
[mu.git] / archive / 1.vm / 073scheduler.cc
blob5f97220bbf4f6f904bb9bb72da09ec324478a14b
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() {
5 run(
6 "def f1 [\n"
7 " start-running f2\n"
8 // wait for f2 to run
9 " {\n"
10 " jump-unless 1:num, -1\n"
11 " }\n"
12 "]\n"
13 "def f2 [\n"
14 " 1:num <- copy 1\n"
15 "]\n"
17 CHECK_TRACE_CONTENTS(
18 "schedule: f1\n"
19 "schedule: f2\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")
45 enum routine_state {
46 RUNNING,
47 COMPLETED,
48 // End routine States
50 :(before "End routine Fields")
51 enum routine_state state;
52 :(before "End routine Constructor")
53 state = RUNNING;
55 :(before "End Globals")
56 vector<routine*> Routines;
57 int Current_routine_index = 0;
58 :(before "End Reset")
59 Scheduling_interval = 500;
60 for (int i = 0; i < SIZE(Routines); ++i)
61 delete Routines.at(i);
62 Routines.clear();
63 Current_routine = NULL;
64 :(replace{} "void run(const recipe_ordinal r)")
65 void run(const recipe_ordinal r) {
66 run(new routine(r));
69 :(code)
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
84 // Scheduler Cleanup
85 // End Scheduler Cleanup
87 // End Run Routine
90 bool all_routines_done() {
91 for (int i = 0; i < SIZE(Routines); ++i) {
92 if (Routines.at(i)->state == RUNNING)
93 return false;
95 return true;
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);
106 return;
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;
122 return result.str();
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");
129 assert(r);
130 routine* main_routine = new routine(r);
131 // pass in commandline args as ingredients to main
132 // todo: test this
133 Current_routine = main_routine;
134 for (int i = 1; i < argc; ++i) {
135 vector<double> arg;
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);
141 run(main_routine);
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")
149 int id;
150 :(before "End Globals")
151 int Next_routine_id = 1;
152 :(before "End Reset")
153 Next_routine_id = 1;
154 :(before "End routine Constructor")
155 id = Next_routine_id;
156 ++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")
163 parent_index = -1;
165 :(before "End Primitive Recipe Declarations")
166 START_RUNNING,
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();
173 break;
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();
177 break;
179 break;
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);
193 products.resize(1);
194 products.at(0).push_back(new_routine->id);
195 break;
198 :(code)
199 void test_scheduler_runs_single_routine() {
200 Scheduling_interval = 1;
201 run(
202 "def f1 [\n"
203 " 1:num <- copy 0\n"
204 " 2:num <- copy 0\n"
205 "]\n"
207 CHECK_TRACE_CONTENTS(
208 "schedule: f1\n"
209 "run: {1: \"number\"} <- copy {0: \"literal\"}\n"
210 "schedule: f1\n"
211 "run: {2: \"number\"} <- copy {0: \"literal\"}\n"
215 void test_scheduler_interleaves_routines() {
216 Scheduling_interval = 1;
217 run(
218 "def f1 [\n"
219 " start-running f2\n"
220 " 1:num <- copy 0\n"
221 " 2:num <- copy 0\n"
222 "]\n"
223 "def f2 [\n"
224 " 3:num <- copy 0\n"
225 " 4:num <- copy 0\n"
226 "]\n"
228 CHECK_TRACE_CONTENTS(
229 "schedule: f1\n"
230 "run: start-running {f2: \"recipe-literal\"}\n"
231 "schedule: f2\n"
232 "run: {3: \"number\"} <- copy {0: \"literal\"}\n"
233 "schedule: f1\n"
234 "run: {1: \"number\"} <- copy {0: \"literal\"}\n"
235 "schedule: f2\n"
236 "run: {4: \"number\"} <- copy {0: \"literal\"}\n"
237 "schedule: f1\n"
238 "run: {2: \"number\"} <- copy {0: \"literal\"}\n"
242 void test_start_running_takes_ingredients() {
243 run(
244 "def f1 [\n"
245 " start-running f2, 3\n"
246 // wait for f2 to run
247 " {\n"
248 " jump-unless 1:num, -1\n"
249 " }\n"
250 "]\n"
251 "def f2 [\n"
252 " 1:num <- next-ingredient\n"
253 " 2:num <- add 1:num, 1\n"
254 "]\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() {
264 Hide_errors = true;
265 run(
266 "def f1 [\n"
267 " start-running f2, 3\n"
268 "]\n"
269 "def f2 n:&:num [\n"
270 "]\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'
283 :(code)
284 void test_start_running_returns_routine_id() {
285 run(
286 "def f1 [\n"
287 " 1:num <- start-running f2\n"
288 "]\n"
289 "def f2 [\n"
290 " 12:num <- copy 44\n"
291 "]\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(
301 "recipe f1 [\n"
302 " 1:num <- copy 0\n"
303 "]\n").front();
304 recipe_ordinal f2 = load(
305 "recipe f2 [\n"
306 " 2:num <- copy 0\n"
307 "]\n").front();
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
311 run(
312 "def f3 [\n"
313 " 3:num <- copy 0\n"
314 "]\n"
316 // f1 and f3 can run in any order
317 CHECK_TRACE_CONTENTS(
318 "schedule: f1\n"
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(
324 "schedule: f3\n"
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;
332 run(
333 "def f1 [\n"
334 " 1:num <- copy 0\n"
335 " 2:num <- copy 0\n"
336 "]\n"
338 CHECK_TRACE_CONTENTS(
339 "schedule: f1\n"
341 CHECK_TRACE_DOESNT_CONTAIN("run: idle");
344 //:: Errors in a routine cause it to terminate.
346 void test_scheduler_terminates_routines_after_errors() {
347 Hide_errors = true;
348 Scheduling_interval = 2;
349 run(
350 "def f1 [\n"
351 " start-running f2\n"
352 " 1:num <- copy 0\n"
353 " 2:num <- copy 0\n"
354 "]\n"
355 "def f2 [\n"
356 // divide by 0 twice
357 " 3:num <- divide-with-remainder 4, 0\n"
358 " 4:num <- divide-with-remainder 4, 0\n"
359 "]\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.
375 :(code)
376 void test_scheduler_kills_orphans() {
377 run(
378 "def main [\n"
379 " start-running f1\n"
380 // f1 never actually runs because its parent completes without
381 // waiting for it
382 "]\n"
383 "def f1 [\n"
384 " 1:num <- copy 0\n"
385 "]\n"
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;
400 :(code)
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)
404 return true;
406 return false;
409 //:: 'routine-state' can tell if a given routine id is running
411 void test_routine_state_test() {
412 Scheduling_interval = 2;
413 run(
414 "def f1 [\n"
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"
420 "]\n"
421 "def f2 [\n"
422 " 12:num <- copy 0\n"
423 // trying to run a second instruction marks routine as completed
424 "]\n"
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")
433 ROUTINE_STATE,
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();
440 break;
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();
444 break;
446 break;
448 :(before "End Primitive Recipe Implementations")
449 case ROUTINE_STATE: {
450 int id = ingredients.at(0).at(0);
451 int result = -1;
452 for (int i = 0; i < SIZE(Routines); ++i) {
453 if (Routines.at(i)->id == id) {
454 result = Routines.at(i)->state;
455 break;
458 products.resize(1);
459 products.at(0).push_back(result);
460 break;
463 //:: miscellaneous helpers
465 :(before "End Primitive Recipe Declarations")
466 STOP,
467 :(before "End Primitive Recipe Numbers")
468 put(Recipe_ordinal, "stop", STOP);
469 :(before "End Primitive Recipe Checks")
470 case STOP: {
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();
473 break;
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();
477 break;
479 break;
481 :(before "End Primitive Recipe Implementations")
482 case STOP: {
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;
487 break;
490 break;
493 :(before "End Primitive Recipe Declarations")
494 _DUMP_ROUTINES,
495 :(before "End Primitive Recipe Numbers")
496 put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES);
497 :(before "End Primitive Recipe Checks")
498 case _DUMP_ROUTINES: {
499 break;
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';
506 break;
509 //: support for stopping routines after some number of cycles
511 :(code)
512 void test_routine_discontinues_past_limit() {
513 Scheduling_interval = 2;
514 run(
515 "def f1 [\n"
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"
522 "]\n"
523 "def f2 [\n"
524 " jump -1:offset\n" // run forever
525 " $print [should never get here], 10/newline\n"
526 "]\n"
528 // f2 terminates
529 CHECK_TRACE_CONTENTS(
530 "schedule: discontinuing routine 2\n"
534 :(before "End routine States")
535 DISCONTINUED,
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;
543 else {
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();
555 :(code)
556 bool any_routines_with_error() {
557 for (int i = 0; i < SIZE(Routines); ++i) {
558 if (Routines.at(i)->state == DISCONTINUED)
559 return true;
561 return false;
564 :(before "End routine Fields")
565 int limit;
566 :(before "End routine Constructor")
567 limit = -1; /* no limit */
569 :(before "End Primitive Recipe Declarations")
570 LIMIT_TIME,
571 :(before "End Primitive Recipe Numbers")
572 put(Recipe_ordinal, "limit-time", LIMIT_TIME);
573 :(before "End Primitive Recipe Checks")
574 case LIMIT_TIME: {
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();
577 break;
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();
581 break;
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();
585 break;
587 break;
589 :(before "End Primitive Recipe Implementations")
590 case LIMIT_TIME: {
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);
595 break;
598 break;
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();
615 break;
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();
619 break;
621 break;
623 :(before "End Primitive Recipe Implementations")
624 case NUMBER_OF_INSTRUCTIONS: {
625 int id = ingredients.at(0).at(0);
626 int result = -1;
627 for (int i = 0; i < SIZE(Routines); ++i) {
628 if (Routines.at(i)->id == id) {
629 result = Routines.at(i)->instructions_run;
630 break;
633 products.resize(1);
634 products.at(0).push_back(result);
635 break;
638 :(code)
639 void test_number_of_instructions() {
640 run(
641 "def f1 [\n"
642 " 10:num/child-id <- start-running f2\n"
643 " {\n"
644 " loop-unless 20:num\n"
645 " }\n"
646 " 11:num <- number-of-instructions 10:num\n"
647 "]\n"
648 "def f2 [\n"
649 // 2 instructions worth of work
650 " 1:num <- copy 34\n"
651 " 20:num <- copy 1\n"
652 "]\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;
663 run(
664 "def f1 [\n"
665 " 10:num/child-id <- start-running f2\n"
666 " {\n"
667 " loop-unless 20:num\n"
668 " }\n"
669 " 11:num <- number-of-instructions 10:num\n"
670 "]\n"
671 "def f2 [\n"
672 // 4 instructions worth of work
673 " 1:num <- copy 34\n"
674 " 2:num <- copy 1\n"
675 " 2:num <- copy 3\n"
676 " 20:num <- copy 1\n"
677 "]\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() {
689 run(
690 "def f1 [\n"
691 " start-running f2\n"
692 " 1:&:num/raw <- new number:type\n"
693 // wait for f2 to complete
694 " {\n"
695 " loop-unless 4:num/raw\n"
696 " }\n"
697 "]\n"
698 "def f2 [\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"
704 "]\n"
706 CHECK_TRACE_CONTENTS(
707 "mem: storing 0 in location 3\n"