https://github.com/akkartik/mu/blob/master/073scheduler.cc
1
2
3
4 :(scenario scheduler)
5 def f1 [
6 start-running f2
7
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
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 :(after "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);
30 return Current_routine->state == RUNNING
31 && Current_routine->instructions_run_this_scheduling_slice < Scheduling_interval;
32 }
33 :(after "stop_running_current_routine:")
34
35 Current_routine->instructions_run_this_scheduling_slice = 0;
36
37
38
39 :(before "struct routine")
40 enum routine_state {
41 RUNNING,
42 COMPLETED,
43
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 Reset")
54 Scheduling_interval = 500;
55 for (int i = 0; i < SIZE(Routines); ++i)
56 delete Routines.at(i);
57 Routines.clear();
58 Current_routine = NULL;
59 :(replace{} "void run(const recipe_ordinal r)")
60 void run(const recipe_ordinal r) {
61 run(new routine(r));
62 }
63
64 :(code)
65 void run(routine* rr) {
66 Routines.push_back(rr);
67 Current_routine_index = 0, Current_routine = Routines.at(0);
68 while (!all_routines_done()) {
69 skip_to_next_routine();
70 assert(Current_routine);
71 assert(Current_routine->state == RUNNING);
72 trace(9990, "schedule") << current_routine_label() << end();
73 run_current_routine();
74
75 if (Current_routine->completed())
76 Current_routine->state = COMPLETED;
77
78
79
80
81 }
82
83 }
84
85 bool all_routines_done() {
86 for (int i = 0; i < SIZE(Routines); ++i) {
87 if (Routines.at(i)->state == RUNNING)
88 return false;
89 }
90 return true;
91 }
92
93
94 void skip_to_next_routine() {
95 assert(!Routines.empty());
96 assert(Current_routine_index < SIZE(Routines));
97 for (int i = (Current_routine_index+1)%SIZE(Routines); i != Current_routine_index; i = (i+1)%SIZE(Routines)) {
98 if (Routines.at(i)->state == RUNNING) {
99 Current_routine_index = i;
100 Current_routine = Routines.at(i);
101 return;
102 }
103 }
104 }
105
106 string current_routine_label() {
107 return routine_label(Current_routine);
108 }
109
110 string routine_label(routine* r) {
111 ostringstream result;
112 const call_stack& calls = r->calls;
113 for (call_stack::const_iterator p = calls.begin(); p != calls.end(); ++p) {
114 if (p != calls.begin()) result << '/';
115 result << get(Recipe, p->running_recipe).name;
116 }
117 return result.str();
118 }
119
120
121 :(replace{} "void run_main(int argc, char* argv[])")
122 void run_main(int argc, char* argv[]) {
123 recipe_ordinal r = get(Recipe_ordinal, "main");
124 assert(r);
125 routine* main_routine = new routine(r);
126
127
128 Current_routine = main_routine;
129 for (int i = 1; i < argc; ++i) {
130 vector<double> arg;
131 arg.push_back(new_mu_text(argv[i]));
132 assert(get(Memory, arg.back()) == 0);
133 current_call().ingredient_atoms.push_back(arg);
134 }
135 run(main_routine);
136 }
137
138
139
140
141
142 :(before "End routine Fields")
143 int id;
144 :(before "End Globals")
145 int Next_routine_id = 1;
146 :(before "End Reset")
147 Next_routine_id = 1;
148 :(before "End routine Constructor")
149 id = Next_routine_id;
150 ++Next_routine_id;
151
152
153 :(before "End routine Fields")
154
155 int parent_index;
156 :(before "End routine Constructor")
157 parent_index = -1;
158
159 :(before "End Primitive Recipe Declarations")
160 START_RUNNING,
161 :(before "End Primitive Recipe Numbers")
162 put(Recipe_ordinal, "start-running", START_RUNNING);
163 :(before "End Primitive Recipe Checks")
164 case START_RUNNING: {
165 if (inst.ingredients.empty()) {
166 raise << maybe(get(Recipe, r).name) << "'start-running' requires at least one ingredient: the recipe to start running\n" << end();
167 break;
168 }
169 if (!is_mu_recipe(inst.ingredients.at(0))) {
170 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();
171 break;
172 }
173 break;
174 }
175 :(before "End Primitive Recipe Implementations")
176 case START_RUNNING: {
177 routine* new_routine = new routine(ingredients.at(0).at(0));
178 new_routine->parent_index = Current_routine_index;
179
180 for (int i = 1; i < SIZE(current_instruction().ingredients); ++i) {
181 new_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i));
182 reagent ingredient = current_instruction().ingredients.at(i);
183 new_routine->calls.front().ingredients.push_back(ingredient);
184
185 }
186 Routines.push_back(new_routine);
187 products.resize(1);
188 products.at(0).push_back(new_routine->id);
189 break;
190 }
191
192 :(scenario scheduler_runs_single_routine)
193 % Scheduling_interval = 1;
194 def f1 [
195 1:num <- copy 0
196 2:num <- copy 0
197 ]
198 +schedule: f1
199 +run: {1: "number"} <- copy {0: "literal"}
200 +schedule: f1
201 +run: {2: "number"} <- copy {0: "literal"}
202
203 :(scenario scheduler_interleaves_routines)
204 % Scheduling_interval = 1;
205 def f1 [
206 start-running f2
207 1:num <- copy 0
208 2:num <- copy 0
209 ]
210 def f2 [
211 3:num <- copy 0
212 4:num <- copy 0
213 ]
214 +schedule: f1
215 +run: start-running {f2: "recipe-literal"}
216 +schedule: f2
217 +run: {3: "number"} <- copy {0: "literal"}
218 +schedule: f1
219 +run: {1: "number"} <- copy {0: "literal"}
220 +schedule: f2
221 +run: {4: "number"} <- copy {0: "literal"}
222 +schedule: f1
223 +run: {2: "number"} <- copy {0: "literal"}
224
225 :(scenario start_running_takes_ingredients)
226 def f1 [
227 start-running f2, 3
228
229 {
230 jump-unless 1:num, -1
231 }
232 ]
233 def f2 [
234 1:num <- next-ingredient
235 2:num <- add 1:num, 1
236 ]
237 +mem: storing 4 in location 2
238
239
240
241 :(scenario start_running_checks_types)
242 % Hide_errors = true;
243 def f1 [
244 start-running f2, 3
245 ]
246 def f2 n:&:num [
247 ]
248 +error: f1: ingredient 0 has the wrong type at 'start-running f2, 3'
249
250
251 :(before "End is_indirect_call_with_ingredients Special-cases")
252 if (r == START_RUNNING) return true;
253
254
255
256 :(scenario start_running_returns_routine_id)
257 def f1 [
258 1:num <- start-running f2
259 ]
260 def f2 [
261 12:num <- copy 44
262 ]
263 +mem: storing 2 in location 1
264
265
266
267 :(scenario scheduler_skips_completed_routines)
268 % recipe_ordinal f1 = load("recipe f1 [\n1:num <- copy 0\n]\n").front();
269 % recipe_ordinal f2 = load("recipe f2 [\n2:num <- copy 0\n]\n").front();
270 % Routines.push_back(new routine(f1)); // f1 meant to run
271 % Routines.push_back(new routine(f2));
272 % Routines.back()->state = COMPLETED; // f2 not meant to run
273
274 def f3 [
275 3:num <- copy 0
276 ]
277
278 +schedule: f1
279 +mem: storing 0 in location 1
280 -schedule: f2
281 -mem: storing 0 in location 2
282 +schedule: f3
283 +mem: storing 0 in location 3
284
285 :(scenario scheduler_starts_at_middle_of_routines)
286 % Routines.push_back(new routine(COPY));
287 % Routines.back()->state = COMPLETED;
288 def f1 [
289 1:num <- copy 0
290 2:num <- copy 0
291 ]
292 +schedule: f1
293 -run: idle
294
295
296
297 :(scenario scheduler_terminates_routines_after_errors)
298 % Hide_errors = true;
299 % Scheduling_interval = 2;
300 def f1 [
301 start-running f2
302 1:num <- copy 0
303 2:num <- copy 0
304 ]
305 def f2 [
306
307 3:num <- divide-with-remainder 4, 0
308 4:num <- divide-with-remainder 4, 0
309 ]
310
311 +error: f2: divide by zero in '3:num <- divide-with-remainder 4, 0'
312 -error: f2: divide by zero in '4:num <- divide-with-remainder 4, 0'
313
314 :(after "operator<<(ostream& os, end /*unused*/)")
315 if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) {
316 Current_routine->state = COMPLETED;
317 }
318
319
320
321 :(scenario scheduler_kills_orphans)
322 def main [
323 start-running f1
324
325 ]
326 def f1 [
327 1:num <- copy 0
328 ]
329 -schedule: f1
330
331 :(before "End Scheduler Cleanup")
332 for (int i = 0; i < SIZE(Routines); ++i) {
333 if (Routines.at(i)->state == COMPLETED) continue;
334 if (Routines.at(i)->parent_index < 0) continue;
335
336 if (has_completed_parent(i)) {
337 Routines.at(i)->state = COMPLETED;
338 }
339 }
340
341 :(code)
342 bool has_completed_parent(int routine_index) {
343 for (int j = routine_index; j >= 0; j = Routines.at(j)->parent_index) {
344 if (Routines.at(j)->state == COMPLETED)
345 return true;
346 }
347 return false;
348 }
349
350
351
352 :(scenario routine_state_test)
353 % Scheduling_interval = 2;
354 def f1 [
355 1:num/child-id <- start-running f2
356 12:num <- copy 0
357
358 2:num/state <- routine-state 1:num/child-id
359 ]
360 def f2 [
361 12:num <- copy 0
362
363 ]
364
365 +mem: storing 1 in location 2
366
367 :(before "End Primitive Recipe Declarations")
368 ROUTINE_STATE,
369 :(before "End Primitive Recipe Numbers")
370 put(Recipe_ordinal, "routine-state", ROUTINE_STATE);
371 :(before "End Primitive Recipe Checks")
372 case ROUTINE_STATE: {
373 if (SIZE(inst.ingredients) != 1) {
374 raise << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
375 break;
376 }
377 if (!is_mu_number(inst.ingredients.at(0))) {
378 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();
379 break;
380 }
381 break;
382 }
383 :(before "End Primitive Recipe Implementations")
384 case ROUTINE_STATE: {
385 int id = ingredients.at(0).at(0);
386 int result = -1;
387 for (int i = 0; i < SIZE(Routines); ++i) {
388 if (Routines.at(i)->id == id) {
389 result = Routines.at(i)->state;
390 break;
391 }
392 }
393 products.resize(1);
394 products.at(0).push_back(result);
395 break;
396 }
397
398
399
400 :(before "End Primitive Recipe Declarations")
401 STOP,
402 :(before "End Primitive Recipe Numbers")
403 put(Recipe_ordinal, "stop", STOP);
404 :(before "End Primitive Recipe Checks")
405 case STOP: {
406 if (SIZE(inst.ingredients) != 1) {
407 raise << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
408 break;
409 }
410 if (!is_mu_number(inst.ingredients.at(0))) {
411 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();
412 break;
413 }
414 break;
415 }
416 :(before "End Primitive Recipe Implementations")
417 case STOP: {
418 int id = ingredients.at(0).at(0);
419 for (int i = 0; i < SIZE(Routines); ++i) {
420 if (Routines.at(i)->id == id) {
421 Routines.at(i)->state = COMPLETED;
422 break;
423 }
424 }
425 break;
426 }
427
428 :(before "End Primitive Recipe Declarations")
429 _DUMP_ROUTINES,
430 :(before "End Primitive Recipe Numbers")
431 put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES);
432 :(before "End Primitive Recipe Checks")
433 case _DUMP_ROUTINES: {
434 break;
435 }
436 :(before "End Primitive Recipe Implementations")
437 case _DUMP_ROUTINES: {
438 for (int i = 0; i < SIZE(Routines); ++i) {
439 cerr << i << ": " << Routines.at(i)->id << ' ' << Routines.at(i)->state << ' ' << Routines.at(i)->parent_index << '\n';
440 }
441 break;
442 }
443
444
445
446 :(scenario routine_discontinues_past_limit)
447 % Scheduling_interval = 2;
448 def f1 [
449 1:num/child-id <- start-running f2
450 limit-time 1:num/child-id, 10
451
452 2:num <- copy 20
453 2:num <- subtract 2:num, 1
454 jump-if 2:num, -2:offset
455 ]
456 def f2 [
457 jump -1:offset
458 $print [should never get here], 10/newline
459 ]
460
461 +schedule: discontinuing routine 2
462
463 :(before "End routine States")
464 DISCONTINUED,
465 :(before "End Scheduler State Transitions")
466 if (Current_routine->limit >= 0) {
467 if (Current_routine->limit <= Scheduling_interval) {
468 trace("schedule") << "discontinuing routine " << Current_routine->id << end();
469 Current_routine->state = DISCONTINUED;
470 Current_routine->limit = 0;
471 }
472 else {
473 Current_routine->limit -= Scheduling_interval;
474 }
475 }
476
477 :(before "End Test Teardown")
478 if (Passed && any_routines_with_error())
479 raise << "some routines died with errors\n" << end();
480 :(before "End Mu Test Teardown")
481 if (Passed && any_routines_with_error())
482 raise << Current_scenario->name << ": some routines died with errors\n" << end();
483
484 :(code)
485 bool any_routines_with_error() {
486 for (int i = 0; i < SIZE(Routines); ++i) {
487 if (Routines.at(i)->state == DISCONTINUED)
488 return true;
489 }
490 return false;
491 }
492
493 :(before "End routine Fields")
494 int limit;
495 :(before "End routine Constructor")
496 limit = -1;
497
498 :(before "End Primitive Recipe Declarations")
499 LIMIT_TIME,
500 :(before "End Primitive Recipe Numbers")
501 put(Recipe_ordinal, "limit-time", LIMIT_TIME);
502 :(before "End Primitive Recipe Checks")
503 case LIMIT_TIME: {
504 if (SIZE(inst.ingredients) != 2) {
505 raise << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end();
506 break;
507 }
508 if (!is_mu_number(inst.ingredients.at(0))) {
509 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();
510 break;
511 }
512 if (!is_mu_number(inst.ingredients.at(1))) {
513 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();
514 break;
515 }
516 break;
517 }
518 :(before "End Primitive Recipe Implementations")
519 case LIMIT_TIME: {
520 int id = ingredients.at(0).at(0);
521 for (int i = 0; i < SIZE(Routines); ++i) {
522 if (Routines.at(i)->id == id) {
523 Routines.at(i)->limit = ingredients.at(1).at(0);
524 break;
525 }
526 }
527 break;
528 }
529
530 :(before "End routine Fields")
531 int instructions_run;
532 :(before "End routine Constructor")
533 instructions_run = 0;
534 :(before "Reset instructions_run_this_scheduling_slice")
535 Current_routine->instructions_run += Current_routine->instructions_run_this_scheduling_slice;
536 :(before "End Primitive Recipe Declarations")
537 NUMBER_OF_INSTRUCTIONS,
538 :(before "End Primitive Recipe Numbers")
539 put(Recipe_ordinal, "number-of-instructions", NUMBER_OF_INSTRUCTIONS);
540 :(before "End Primitive Recipe Checks")
541 case NUMBER_OF_INSTRUCTIONS: {
542 if (SIZE(inst.ingredients) != 1) {
543 raise << maybe(get(Recipe, r).name) << "'number-of-instructions' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
544 break;
545 }
546 if (!is_mu_number(inst.ingredients.at(0))) {
547 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();
548 break;
549 }
550 break;
551 }
552 :(before "End Primitive Recipe Implementations")
553 case NUMBER_OF_INSTRUCTIONS: {
554 int id = ingredients.at(0).at(0);
555 int result = -1;
556 for (int i = 0; i < SIZE(Routines); ++i) {
557 if (Routines.at(i)->id == id) {
558 result = Routines.at(i)->instructions_run;
559 break;
560 }
561 }
562 products.resize(1);
563 products.at(0).push_back(result);
564 break;
565 }
566
567 :(scenario number_of_instructions)
568 def f1 [
569 10:num/child-id <- start-running f2
570 {
571 loop-unless 20:num
572 }
573 11:num <- number-of-instructions 10:num
574 ]
575 def f2 [
576
577 1:num <- copy 34
578 20:num <- copy 1
579 ]
580
581
582 +mem: storing 3 in location 11
583
584 :(scenario number_of_instructions_across_multiple_scheduling_intervals)
585 % Scheduling_interval = 1;
586 def f1 [
587 10:num/child-id <- start-running f2
588 {
589 loop-unless 20:num
590 }
591 11:num <- number-of-instructions 10:num
592 ]
593 def f2 [
594
595 1:num <- copy 34
596 2:num <- copy 1
597 2:num <- copy 3
598 20:num <- copy 1
599 ]
600
601
602 +mem: storing 5 in location 11
603
604
605
606 :(scenario new_concurrent)
607 def f1 [
608 start-running f2
609 1:&:num/raw <- new number:type
610
611 {
612 loop-unless 4:num/raw
613 }
614 ]
615 def f2 [
616 2:&:num/raw <- new number:type
617
618 3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw
619
620 4:num/raw <- copy 1
621 ]
622 +mem: storing 0 in location 3