https://github.com/akkartik/mu/blob/master/020run.cc

      1 //: Phase 3: Start running a loaded and transformed recipe.
      2 //:
      3 //:   The process of running Mu code:
      4 //:     load -> transform -> run
      5 //:
      6 //: So far we've seen recipes as lists of instructions, and instructions point
      7 //: at other recipes. To kick things off Mu needs to know how to run certain
      8 //: 'primitive' recipes. That will then give the ability to run recipes
      9 //: containing these primitives.
     10 //:
     11 //: This layer defines a skeleton with just two primitive recipes: IDLE which
     12 //: does nothing, and COPY, which can copy numbers from one memory location to
     13 //: another. Later layers will add more primitives.
     14
     15 :(scenario copy_literal)
     16 def main [
     17   1:num <- copy 23
     18 ]
     19 +run: {1: "number"} <- copy {23: "literal"}
     20 +mem: storing 23 in location 1
     21
     22 :(scenario copy)
     23 def main [
     24   1:num <- copy 23
     25   2:num <- copy 1:num
     26 ]
     27 +run: {2: "number"} <- copy {1: "number"}
     28 +mem: location 1 is 23
     29 +mem: storing 23 in location 2
     30
     31 :(scenario copy_multiple)
     32 def main [
     33   1:num, 2:num <- copy 23, 24
     34 ]
     35 +mem: storing 23 in location 1
     36 +mem: storing 24 in location 2
     37
     38 :(before "End Types")
     39 // Book-keeping while running a recipe.
     40 //: Later layers will replace this to support running multiple routines at once.
     41 struct routine {
     42   recipe_ordinal running_recipe;
     43   int running_step_index;
     44   routine(recipe_ordinal r) :running_recipe(r), running_step_index(0) {}
     45   bool completed() const;
     46   const vector<instruction>& steps() const;
     47 };
     48
     49 :(before "End Globals")
     50 routine* Current_routine = NULL;
     51 :(before "End Reset")
     52 Current_routine = NULL;
     53
     54 :(code)
     55 void run(const recipe_ordinal r) {
     56   routine rr(r);
     57   Current_routine = &rr;
     58   run_current_routine();
     59   Current_routine = NULL;
     60 }
     61
     62 void run_current_routine() {
     63   while (should_continue_running(Current_routine)) {  // beware: may modify Current_routine
     64     // Running One Instruction
     65     if (current_instruction().is_label) { ++current_step_index();  continue; }
     66     trace(Initial_callstack_depth + Trace_stream->callstack_depth, "run") << to_string(current_instruction()) << end();
     67 //?     if (Foo) cerr << "run: " << to_string(current_instruction()) << '\n';
     68     if (get_or_insert(Memory, 0) != 0) {
     69       raise << "something wrote to location 0; this should never happen\n" << end();
     70       put(Memory, 0, 0);
     71     }
     72     // read all ingredients from memory, each potentially spanning multiple locations
     73     vector<vector<double> > ingredients;
     74     if (should_copy_ingredients()) {
     75       for (int i = 0;  i < SIZE(current_instruction().ingredients);  ++i)
     76         ingredients.push_back(read_memory(current_instruction().ingredients.at(i)));
     77     }
     78     // instructions below will write to 'products'
     79     vector<vector<double> > products;
     80     //: This will be a large switch that later layers will often insert cases
     81     //: into. Never call 'continue' within it. Instead, we'll explicitly
     82     //: control which of the following stages after the switch we run for each
     83     //: instruction.
     84     bool write_products = true;
     85     bool fall_through_to_next_instruction = true;
     86     switch (current_instruction().operation) {
     87       // Primitive Recipe Implementations
     88       case COPY: {
     89         copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
     90         break;
     91       }
     92       // End Primitive Recipe Implementations
     93       default: {
     94         raise << "not a primitive op: " << current_instruction().operation << '\n' << end();
     95       }
     96     }
     97     //: used by a later layer
     98     if (write_products) {
     99       if (SIZE(products) < SIZE(current_instruction().products)) {
    100         raise << SIZE(products) << " vs " << SIZE(current_instruction().products) << ": failed to write to all products in '" << to_original_string(current_instruction()) << "'\n" << end();
    101       }
    102       else {
    103         for (int i = 0;  i < SIZE(current_instruction().products);  ++i) {
    104           // Writing Instruction Product(i)
    105           write_memory(current_instruction().products.at(i), products.at(i));
    106         }
    107       }
    108     }
    109     // End Running One Instruction
    110     if (fall_through_to_next_instruction)
    111       ++current_step_index();
    112   }
    113   stop_running_current_routine:;
    114 }
    115
    116 :(code)
    117 //: hook replaced in a later layer
    118 bool should_continue_running(const routine* current_routine) {
    119   assert(current_routine == Current_routine);  // argument passed in just to make caller readable above
    120   return !Current_routine->completed();
    121 }
    122
    123 bool should_copy_ingredients() {
    124   // End should_copy_ingredients Special-cases
    125   return true;
    126 }
    127
    128 bool is_mu_scalar(reagent/*copy*/ r) {
    129   return is_mu_scalar(r.type);
    130 }
    131 bool is_mu_scalar(const type_tree* type) {
    132   if (!type) return false;
    133   if (is_mu_address(type)) return false;
    134   if (!type->atom) return false;
    135   if (is_literal(type))
    136     return type->name != "literal-string";
    137   return size_of(type) == 1;
    138 }
    139
    140 bool is_mu_address(reagent/*copy*/ r) {
    141   // End Preprocess is_mu_address(reagent r)
    142   return is_mu_address(r.type);
    143 }
    144 bool is_mu_address(const type_tree* type) {
    145   if (!type) return false;
    146   if (is_literal(type)) return false;
    147   if (type->atom) return false;
    148   if (!type->left->atom) {
    149     raise << "invalid type " << to_string(type) << '\n' << end();
    150     return false;
    151   }
    152   return type->left->value == Address_type_ordinal;
    153 }
    154
    155 //: Some helpers.
    156 //: Important that they return references into the current routine.
    157
    158 //: hook replaced in a later layer
    159 int& current_step_index() {
    160   return Current_routine->running_step_index;
    161 }
    162
    163 //: hook replaced in a later layer
    164 recipe_ordinal currently_running_recipe() {
    165   return Current_routine->running_recipe;
    166 }
    167
    168 //: hook replaced in a later layer
    169 const string& current_recipe_name() {
    170   return get(Recipe, Current_routine->running_recipe).name;
    171 }
    172
    173 //: hook replaced in a later layer
    174 const recipe& current_recipe() {
    175   return get(Recipe, Current_routine->running_recipe);
    176 }
    177
    178 //: hook replaced in a later layer
    179 const instruction& current_instruction() {
    180   return get(Recipe, Current_routine->running_recipe).steps.at(Current_routine->running_step_index);
    181 }
    182
    183 //: hook replaced in a later layer
    184 bool routine::completed() const {
    185   return running_step_index >= SIZE(get(Recipe, running_recipe).steps);
    186 }
    187
    188 //: hook replaced in a later layer
    189 const vector<instruction>& routine::steps() const {
    190   return get(Recipe, running_recipe).steps;
    191 }
    192
    193 //:: Startup flow
    194
    195 :(before "End Mu Prelude")
    196 load_file_or_directory("core.mu");
    197 //? DUMP("");
    198 //? exit(0);
    199
    200 //: Step 2: load any .mu files provided at the commandline
    201 :(before "End Commandline Parsing")
    202 // Check For .mu Files
    203 if (argc > 1) {
    204   // skip argv[0]
    205   ++argv;
    206   --argc;
    207   while (argc > 0) {
    208     // ignore argv past '--'; that's commandline args for 'main'
    209     if (string(*argv) == "--") break;
    210     if (starts_with(*argv, "--"))
    211       cerr << "treating " << *argv << " as a file rather than an option\n";
    212     load_file_or_directory(*argv);
    213     --argc;
    214     ++argv;
    215   }
    216   if (Run_tests) Recipe.erase(get(Recipe_ordinal, "main"));
    217 }
    218 transform_all();
    219 //? cerr << to_original_string(get(Type_ordinal, "editor")) << '\n';
    220 //? cerr << to_original_string(get(Recipe, get(Recipe_ordinal, "event-loop"))) << '\n';
    221 //? DUMP("");
    222 //? exit(0);
    223 if (trace_contains_errors()) return 1;
    224 if (Trace_stream && Run_tests) {
    225   // We'll want a trace per test. Clear the trace.
    226   delete Trace_stream;
    227   Trace_stream = NULL;
    228 }
    229 save_snapshots();
    230
    231 //: Step 3: if we aren't running tests, locate a recipe called 'main' and
    232 //: start running it.
    233 :(before "End Main")
    234 if (!Run_tests && contains_key(Recipe_ordinal, "main") && contains_key(Recipe, get(Recipe_ordinal, "main"))) {
    235   // Running Main
    236   reset();
    237   trace(2, "run") << "=== Starting to run" << end();
    238   assert(Num_calls_to_transform_all == 1);
    239   run_main(argc, argv);
    240 }
    241 :(code)
    242 void run_main(int argc, char* argv[]) {
    243   recipe_ordinal r = get(Recipe_ordinal, "main");
    244   if (r) run(r);
    245 }
    246
    247 :(code)
    248 void load_file_or_directory(string filename) {
    249   if (is_directory(filename)) {
    250     load_all(filename);
    251     return;
    252   }
    253   ifstream fin(filename.c_str());
    254   if (!fin) {
    255     cerr << "no such file '" << filename << "'\n" << end();  // don't raise, just warn. just in case it's just a name for a scenario to run.
    256     return;
    257   }
    258   trace(9990, "load") << "=== " << filename << end();
    259   load(fin);
    260   fin.close();
    261 }
    262
    263 bool is_directory(string path) {
    264   struct stat info;
    265   if (stat(path.c_str(), &info)) return false;  // error
    266   return info.st_mode & S_IFDIR;
    267 }
    268
    269 void load_all(string dir) {
    270   dirent** files;
    271   int num_files = scandir(dir.c_str(), &files, NULL, alphasort);
    272   for (int i = 0;  i < num_files;  ++i) {
    273     string curr_file = files[i]->d_name;
    274     if (isdigit(curr_file.at(0)) && ends_with(curr_file, ".mu"))
    275       load_file_or_directory(dir+'/'+curr_file);
    276     free(files[i]);
    277     files[i] = NULL;
    278   }
    279   free(files);
    280 }
    281
    282 bool ends_with(const string& s, const string& pat) {
    283   for (string::const_reverse_iterator p = s.rbegin(), q = pat.rbegin();  q != pat.rend();  ++p, ++q) {
    284     if (p == s.rend()) return false;  // pat too long
    285     if (*p != *q) return false;
    286   }
    287   return true;
    288 }
    289
    290 :(before "End Includes")
    291 #include <dirent.h>
    292 #include <sys/stat.h>
    293
    294 //:: Reading from memory, writing to memory.
    295
    296 :(code)
    297 vector<double> read_memory(reagent/*copy*/ x) {
    298   // Begin Preprocess read_memory(x)
    299   vector<double> result;
    300   if (x.name == "null") result.push_back(/*alloc id*/0);
    301   if (is_literal(x)) {
    302     result.push_back(x.value);
    303     return result;
    304   }
    305   // End Preprocess read_memory(x)
    306   int size = size_of(x);
    307   for (int offset = 0;  offset < size;  ++offset) {
    308     double val = get_or_insert(Memory, x.value+offset);
    309     trace("mem") << "location " << x.value+offset << " is " << no_scientific(val) << end();
    310     result.push_back(val);
    311   }
    312   return result;
    313 }
    314
    315 void write_memory(reagent/*copy*/ x, const vector<double>& data) {
    316   assert(Current_routine);  // run-time only
    317   // Begin Preprocess write_memory(x, data)
    318   if (!x.type) {
    319     raise << "can't write to '" << to_string(x) << "'; no type\n" << end();
    320     return;
    321   }
    322   if (is_dummy(x)) return;
    323   if (is_literal(x)) return;
    324   // End Preprocess write_memory(x, data)
    325   if (x.value == 0) {
    326     raise << "can't write to location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
    327     return;
    328   }
    329   if (size_mismatch(x, data)) {
    330     raise << maybe(current_recipe_name()) << "size mismatch in storing to '" << x.original_string << "' (" << size_of(x) << " vs " << SIZE(data) << ") at '" << to_original_string(current_instruction()) << "'\n" << end();
    331     return;
    332   }
    333   // End write_memory(x) Special-cases
    334   for (int offset = 0;  offset < SIZE(data);  ++offset) {
    335     assert(x.value+offset > 0);
    336     trace("mem") << "storing " << no_scientific(data.at(offset)) << " in location " << x.value+offset << end();
    337 //?     if (Foo) cerr << "mem: storing " << no_scientific(data.at(offset)) << " in location " << x.value+offset << '\n';
    338     put(Memory, x.value+offset, data.at(offset));
    339   }
    340 }
    341
    342 :(code)
    343 int size_of(const reagent& r) {
    344   if (!r.type) return 0;
    345   // End size_of(reagent r) Special-cases
    346   return size_of(r.type);
    347 }
    348 int size_of(const type_tree* type) {
    349   if (!type) return 0;
    350   if (type->atom) {
    351     if (type->value == -1) return 1;  // error value, but we'll raise it elsewhere
    352     if (type->value == 0) return 1;
    353     // End size_of(type) Atom Special-cases
    354   }
    355   else {
    356     if (!type->left->atom) {
    357       raise << "invalid type " << to_string(type) << '\n' << end();
    358       return 0;
    359     }
    360     if (type->left->value == Address_type_ordinal) return 2;  // address and alloc id
    361     // End size_of(type) Non-atom Special-cases
    362   }
    363   // End size_of(type) Special-cases
    364   return 1;
    365 }
    366
    367 bool size_mismatch(const reagent& x, const vector<double>& data) {
    368   if (!x.type) return true;
    369   // End size_mismatch(x) Special-cases
    370 //?   if (size_of(x) != SIZE(data)) cerr << size_of(x) << " vs " << SIZE(data) << '\n';
    371   return size_of(x) != SIZE(data);
    372 }
    373
    374 bool is_literal(const reagent& r) {
    375   return is_literal(r.type);
    376 }
    377 bool is_literal(const type_tree* type) {
    378   if (!type) return false;
    379   if (!type->atom) return false;
    380   return type->value == 0;
    381 }
    382
    383 bool scalar(const vector<int>& x) {
    384   return SIZE(x) == 1;
    385 }
    386 bool scalar(const vector<double>& x) {
    387   return SIZE(x) == 1;
    388 }
    389
    390 // helper for tests
    391 void run(const string& form) {
    392   vector<recipe_ordinal> tmp = load(form);
    393   transform_all();
    394   if (tmp.empty()) return;
    395   if (trace_contains_errors()) {
    396     if (Save_trace && Trace_stream) Trace_stream->save();
    397     return;
    398   }
    399   // if a test defines main, it probably wants to start there regardless of
    400   // definition order
    401   if (contains_key(Recipe, get(Recipe_ordinal, "main")))
    402     run(get(Recipe_ordinal, "main"));
    403   else
    404     run(tmp.front());
    405 }
    406
    407 :(scenario run_label)
    408 def main [
    409   +foo
    410   1:num <- copy 23
    411   2:num <- copy 1:num
    412 ]
    413 +run: {1: "number"} <- copy {23: "literal"}
    414 +run: {2: "number"} <- copy {1: "number"}
    415 -run: +foo
    416
    417 :(scenario run_dummy)
    418 def main [
    419   _ <- copy 0
    420 ]
    421 +run: _ <- copy {0: "literal"}
    422
    423 :(scenario run_null)
    424 def main [
    425   1:&:num <- copy null
    426 ]
    427
    428 :(scenario write_to_0_disallowed)
    429 % Hide_errors = true;
    430 def main [
    431   0:num <- copy 34
    432 ]
    433 -mem: storing 34 in location 0
    434
    435 //: Mu is robust to various combinations of commas and spaces. You just have
    436 //: to put spaces around the '<-'.
    437
    438 :(scenario comma_without_space)
    439 def main [
    440   1:num, 2:num <- copy 2,2
    441 ]
    442 +mem: storing 2 in location 1
    443
    444 :(scenario space_without_comma)
    445 def main [
    446   1:num, 2:num <- copy 2 2
    447 ]
    448 +mem: storing 2 in location 1
    449
    450 :(scenario comma_before_space)
    451 def main [
    452   1:num, 2:num <- copy 2, 2
    453 ]
    454 +mem: storing 2 in location 1
    455
    456 :(scenario comma_after_space)
    457 def main [
    458   1:num, 2:num <- copy 2 ,2
    459 ]
    460 +mem: storing 2 in location 1
    461
    462 //:: Counters for trying to understand where Mu programs are spending their
    463 //:: time.
    464
    465 :(before "End Globals")
    466 bool Run_profiler = false;
    467 // We'll key profile information by recipe_ordinal rather than name because
    468 // it's more efficient, and because later layers will show more than just the
    469 // name of a recipe.
    470 //
    471 // One drawback: if you're clearing recipes your profile will be inaccurate.
    472 // So far that happens in tests, and in 'run-sandboxed' in a later layer.
    473 map<recipe_ordinal, int> Instructions_running;
    474 :(before "End Commandline Options(*arg)")
    475 else if (is_equal(*arg, "--profile")) {
    476   Run_profiler = true;
    477 }
    478 :(after "Running One Instruction")
    479 if (Run_profiler) Instructions_running[currently_running_recipe()]++;
    480 :(before "End One-time Setup")
    481 atexit(dump_profile);
    482 :(code)
    483 void dump_profile() {
    484   if (!Run_profiler) return;
    485   if (Run_tests) {
    486     cerr << "It's not a good idea to profile a run with tests, since tests can create conflicting recipes and mislead you. To try it anyway, comment out this check in the code.\n";
    487     return;
    488   }
    489   ofstream fout;
    490   fout.open("profile.instructions");
    491   if (fout) {
    492     for (map<recipe_ordinal, int>::iterator p = Instructions_running.begin();  p != Instructions_running.end();  ++p) {
    493       fout << std::setw(9) << p->second << ' ' << header_label(p->first) << '\n';
    494     }
    495   }
    496   fout.close();
    497   // End dump_profile
    498 }
    499
    500 // overridden in a later layer
    501 string header_label(const recipe_ordinal r) {
    502   return get(Recipe, r).name;
    503 }