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 m
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Mu - 312copy.subx</title>
<meta name="Generator" content="Vim/8.1">
<meta name="plugin-version" content="vim8.1_v1">
<meta name="syntax" content="none">
<meta name="settings" content="number_lines,use_css,pre_wrap,no_foldcolumn,expand_tabs,line_ids,prevent_copy=">
<meta name="colorscheme" content="minimal-dark">
<style type="text/css">
<!--
pre { white-space: pre-wrap; font-family: monospace; color: #000000; background-color: #a8a8a8; }
body { font-size:12pt; font-family: monospace; color: #000000; background-color: #a8a8a8; }
a { color:inherit; }
* { font-size:12pt; font-size: 1em; }
.SpecialChar { color: #d70000; }
.subxComment { color: #005faf; }
.subxS1Comment { color: #0000af; }
.LineNr { }
.subxFunction { color: #af5f00; text-decoration: underline; }
.Constant { color: #008787; }
-->
</style>

<script type='text/javascript'>
<!--

/* function to open any folds containing a jumped-to line before jumping to it */
function JumpToLine()
{
  var lineNum;
  lineNum = window.location.hash;
  lineNum = lineNum.substr(1); /* strip off '#' */

  if (lineNum.indexOf('L') == -1) {
    lineNum = 'L'+lineNum;
  }
  var lineElem = document.getElementById(lineNum);
  /* Always jump to new location even if the line was hidden inside a fold, or
   * we corrected the raw number to a line ID.
   */
  if (lineElem) {
    lineElem.scrollIntoView(true);
  }
  return true;
}
if ('onhashchange' in window) {
  window.onhashchange = JumpToLine;
}

-->
</script>
</head>
<body onload='JumpToLine();'>
<a href='https://github.com/akkartik/mu/blob/main/312copy.subx'>https://github.com/akkartik/mu/blob/main/312copy.subx</a>
<pre id='vimCodeElement'>
<span id="L1" class="LineNr"> 1 </span>== code
<span id="L2" class="LineNr"> 2 </span>
<span id="L3" class="LineNr"> 3 </span><span class="subxFunction">copy-array-object</span>:  <span class="subxComment"># src: (addr array T), dest-ah: (addr handle array T)</span>
<span id="L4" class="LineNr"> 4 </span>    <span class="subxS1Comment"># . prologue</span>
<span id="L5" class="LineNr"> 5 </span>    55/push-ebp
<span id="L6" class="LineNr"> 6 </span>    89/&lt;- %ebp 4/r32/esp
<span id="L7" class="LineNr"> 7 </span>    <span class="subxComment">#</span>
<span id="L8" class="LineNr"> 8 </span>    (<a href='120allocate.subx.html#L669'>copy-array</a> <span class="SpecialChar"><a href='120allocate.subx.html#L27'>Heap</a></span> *(ebp+8) *(ebp+0xc))
<span id="L9" class="LineNr"> 9 </span><span class="Constant">$copy-array-object:end</span>:
<span id="L10" class="LineNr">10 </span>    <span class="subxS1Comment"># . epilogue</span>
<span id="L11" class="LineNr">11 </span>    89/&lt;- %esp 5/r32/ebp
<span id="L12" class="LineNr">12 </span>    5d/pop-to-ebp
<span id="L13" class="LineNr">13 </span>    c3/return
</pre>
</body>
</html>
<!-- vim: set foldmethod=manual : -->
"Delimiter">} 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 }