From e5c11a5137d538b7713dd8708ca767c208824c06 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Mon, 26 Dec 2016 01:17:01 -0800 Subject: 3709 - line numbers in html Each line number also gets an anchor name, but I'm not hyperlinking them for now because I don't want to encourage bookmarking these links just yet. They aren't permalinks because every revision may change what's at any given line number. --- html/043space.cc.html | 912 ++++++++++++++++++++++++++------------------------ 1 file changed, 468 insertions(+), 444 deletions(-) (limited to 'html/043space.cc.html') diff --git a/html/043space.cc.html b/html/043space.cc.html index 617bd873..aa08efa9 100644 --- a/html/043space.cc.html +++ b/html/043space.cc.html @@ -6,24 +6,25 @@ - + @@ -31,445 +32,468 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color - +
-//: Spaces help isolate recipes from each other. You can create them at will,
-//: and all addresses in arguments are implicitly based on the 'default-space'
-//: (unless they have the /raw property)
-
-//: A space is just an array of any scalar location.
-:(before "End Mu Types Initialization")
-put(Type_abbreviations, "space", new_type_tree("address:array:location"));
-//: Spaces are often called 'scopes' in other languages.
-put(Type_abbreviations, "scope", new_type_tree("address:array:location"));
-
-:(scenario set_default_space)
-# if default-space is 10, and if an array of 5 locals lies from location 12 to 16 (inclusive),
-# then local 0 is really location 12, local 1 is really location 13, and so on.
-def main [
-  # pretend address:array:location; in practice we'll use new
-  10:num <- copy 0  # refcount
-  11:num <- copy 5  # length
-  default-space:space <- copy 10/unsafe
-  1:num <- copy 23
-]
-+mem: storing 23 in location 13
-
-:(scenario lookup_sidesteps_default_space)
-def main [
-  # pretend pointer from outside (2000 reserved for refcount)
-  2001:num <- copy 34
-  # pretend address:array:location; in practice we'll use new
-  1000:num <- copy 0  # refcount
-  1001:num <- copy 5  # length
-  # actual start of this recipe
-  default-space:space <- copy 1000/unsafe
-  1:&:num <- copy 2000/unsafe  # even local variables always contain raw addresses
-  8:num/raw <- copy *1:&:num
-]
-+mem: storing 34 in location 8
-
-//:: first disable name conversion for 'default-space'
-:(scenario convert_names_passes_default_space)
-% Hide_errors = true;
-def main [
-  default-space:num, x:num <- copy 0, 1
-]
-+name: assign x 1
--name: assign default-space 1
-
-:(before "End is_disqualified Special-cases")
-if (x.name == "default-space")
-  x.initialized = true;
-:(before "End is_special_name Special-cases")
-if (s == "default-space") return true;
-
-//:: now implement space support
-:(before "End call Fields")
-int default_space;
-:(before "End call Constructor")
-default_space = 0;
-
-:(before "End canonize(x) Special-cases")
-absolutize(x);
-:(code)
-void absolutize(reagent& x) {
-  if (is_raw(x) || is_dummy(x)) return;
-  if (x.name == "default-space") return;
-  if (!x.initialized)
-    raise << to_original_string(current_instruction()) << ": reagent not initialized: '" << x.original_string << "'\n" << end();
-  x.set_value(address(x.value, space_base(x)));
-  x.properties.push_back(pair<string, string_tree*>("raw", NULL));
-  assert(is_raw(x));
-}
-
-//: hook replaced in a later layer
-int space_base(const reagent& x) {
-  return current_call().default_space ? (current_call().default_space+/*skip refcount*/1) : 0;
-}
-
-int address(int offset, int base) {
-  assert(offset >= 0);
-  if (base == 0) return offset;  // raw
-  int size = get_or_insert(Memory, base);
-  if (offset >= size) {
-    // todo: test
-    raise << "location " << offset << " is out of bounds " << size << " at " << base << '\n' << end();
-    return 0;
-  }
-  return base + /*skip length*/1 + offset;
-}
-
-//:: reads and writes to the 'default-space' variable have special behavior
-
-:(after "Begin Preprocess write_memory(x, data)")
-if (x.name == "default-space") {
-  if (!scalar(data) || !is_space(x))
-    raise << maybe(current_recipe_name()) << "'default-space' should be of type address:array:location, but is " << to_string(x.type) << '\n' << end();
-  current_call().default_space = data.at(0);
-  return;
-}
-:(code)
-bool is_space(const reagent& r) {
-  return is_address_of_array_of_numbers(r);
-}
-
-:(scenario get_default_space)
-def main [
-  default-space:space <- copy 10/unsafe
-  1:space/raw <- copy default-space:space
-]
-+mem: storing 10 in location 1
-
-:(after "Begin Preprocess read_memory(x)")
-if (x.name == "default-space") {
-  vector<double> result;
-  result.push_back(current_call().default_space);
-  return result;
-}
-
-//:: fix 'get'
-
-:(scenario lookup_sidesteps_default_space_in_get)
-def main [
-  # pretend pointer to container from outside (2000 reserved for refcount)
-  2001:num <- copy 34
-  2002:num <- copy 35
-  # pretend address:array:location; in practice we'll use new
-  1000:num <- copy 0  # refcount
-  1001:num <- copy 5  # length
-  # actual start of this recipe
-  default-space:space <- copy 1000/unsafe
-  1:&:point <- copy 2000/unsafe
-  9:num/raw <- get *1:&:point, 1:offset
-]
-+mem: storing 35 in location 9
-
-:(before "Read element" following "case GET:")
-element.properties.push_back(pair<string, string_tree*>("raw", NULL));
-
-//:: fix 'index'
-
-:(scenario lookup_sidesteps_default_space_in_index)
-def main [
-  # pretend pointer to array from outside (2000 reserved for refcount)
-  2001:num <- copy 2  # length
-  2002:num <- copy 34
-  2003:num <- copy 35
-  # pretend address:array:location; in practice we'll use new
-  1000:num <- copy 0  # refcount
-  1001:num <- copy 5  # length
-  # actual start of this recipe
-  default-space:space <- copy 1000/unsafe
-  1:&:@:num <- copy 2000/unsafe
-  9:num/raw <- index *1:&:@:num, 1
-]
-+mem: storing 35 in location 9
-
-:(before "Read element" following "case INDEX:")
-element.properties.push_back(pair<string, string_tree*>("raw", NULL));
-
-//:: convenience operation to automatically deduce the amount of space to
-//:: allocate in a default space with names
-
-:(scenario new_default_space)
-def main [
-  new-default-space
-  x:num <- copy 0
-  y:num <- copy 3
-]
-# allocate space for x and y, as well as the chaining slot at 0
-+mem: array length is 3
-
-:(before "End is_disqualified Special-cases")
-if (x.name == "number-of-locals")
-  x.initialized = true;
-:(before "End is_special_name Special-cases")
-if (s == "number-of-locals") return true;
-
-:(before "End Rewrite Instruction(curr, recipe result)")
-// rewrite `new-default-space` to
-//   `default-space:space <- new location:type, number-of-locals:literal`
-// where N is Name[recipe][""]
-if (curr.name == "new-default-space") {
-  rewrite_default_space_instruction(curr);
-}
-:(after "Begin Preprocess read_memory(x)")
-if (x.name == "number-of-locals") {
-  vector<double> result;
-  result.push_back(Name[get(Recipe_ordinal, current_recipe_name())][""]);
-  if (result.back() == 0)
-    raise << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names?\n" << end();
-  return result;
-}
-:(after "Begin Preprocess write_memory(x, data)")
-if (x.name == "number-of-locals") {
-  raise << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end();
-  return;
-}
-
-//:: 'local-scope' is like 'new-default-space' except that we'll reclaim the
-//:: default-space when the routine exits
-
-:(scenario local_scope)
-def main [
-  1:&:@:location <- foo
-  2:&:@:location <- foo
-  3:bool <- equal 1:&, 2:&
-]
-def foo [
-  local-scope
-  x:num <- copy 34
-  return default-space:space
-]
-# both calls to foo should have received the same default-space
-+mem: storing 1 in location 3
-
-:(scenario local_scope_frees_up_addresses)
-def main [
-  local-scope
-  x:text <- new [abc]
-]
-+mem: clearing x:text
-
-:(before "End Rewrite Instruction(curr, recipe result)")
-if (curr.name == "local-scope") {
-  rewrite_default_space_instruction(curr);
-}
-
-//: todo: do this in a transform, rather than magically in the 'return' instruction
-:(after "Falling Through End Of Recipe")
-try_reclaim_locals();
-:(after "Starting Reply")
-try_reclaim_locals();
-
-:(code)
-void try_reclaim_locals() {
-  // only reclaim routines starting with 'local-scope'
-  const recipe_ordinal r = get(Recipe_ordinal, current_recipe_name());
-  const recipe& exiting_recipe = get(Recipe, r);
-  if (exiting_recipe.steps.empty()) return;
-  const instruction& inst = exiting_recipe.steps.at(0);
-  if (inst.old_name != "local-scope") return;
-  // reclaim any local variables unless they're being returned
-  vector<double> zeros;
-  for (int i = /*leave default space for last*/1;  i < SIZE(exiting_recipe.steps);  ++i) {
-    const instruction& inst = exiting_recipe.steps.at(i);
-    for (int i = 0;  i < SIZE(inst.products);  ++i) {
-      const reagent& product = inst.products.at(i);
-      // local variables only
-      if (has_property(product, "lookup")) continue;
-      if (has_property(product, "raw")) continue;  // tests often want to check such locations after they run
-      if (escaping(product)) continue;
-      // End Checks For Reclaiming Locals
-      trace(9999, "mem") << "clearing " << product.original_string << end();
-      zeros.resize(size_of(product));
-      write_memory(product, zeros);
-    }
-  }
-  trace(9999, "mem") << "automatically abandoning " << current_call().default_space << end();
-  abandon(current_call().default_space,
-          inst.products.at(0).type->right,
-          /*refcount*/1 + /*array length*/1 + /*number-of-locals*/Name[r][""]);
-}
-
-:(code)
-// is this reagent one of the values returned by the current (return) instruction?
-// is the corresponding ingredient saved in the caller?
-bool escaping(const reagent& r) {
-  assert(Current_routine);  // run-time only
-  // nothing escapes when you fall through past end of recipe
-  if (current_step_index() >= SIZE(Current_routine->steps())) return false;
-  for (long long i = 0;  i < SIZE(current_instruction().ingredients);  ++i) {
-    if (r == current_instruction().ingredients.at(i)) {
-      if (caller_uses_product(i))
-        return true;
-    }
-  }
-  return false;
-}
-
-//: since we don't decrement refcounts for escaping values above, make sure we
-//: don't increment them when the caller saves them either
-
-:(after "Write Products of Instruction")
-Update_refcounts_in_write_memory = should_update_refcounts_in_write_memory();
-:(before "End Write Products of Instruction")
-Update_refcounts_in_write_memory = true;
-:(code)
-bool should_update_refcounts_in_write_memory() {
-  const instruction& inst = current_instruction();
-  // End should_update_refcounts_in_write_memory Special-cases For Primitives
-  if (inst.operation < MAX_PRIMITIVE_RECIPES) return true;
-  if (!contains_key(Recipe, inst.operation)) return true;
-  const recipe& callee = get(Recipe, inst.operation);
-  if (callee.steps.empty()) return true;
-  return callee.steps.at(0).old_name != "local-scope";  // callees that call local-scope are already dealt with before return
-}
-
-bool caller_uses_product(int product_index) {
-  assert(Current_routine);  // run-time only
-  assert(!Current_routine->calls.empty());
-  if (Current_routine->calls.size() == 1) return false;
-  const call& caller = *++Current_routine->calls.begin();
-  const instruction& caller_inst = to_instruction(caller);
-  if (product_index >= SIZE(caller_inst.products)) return false;
-  return !is_dummy(caller_inst.products.at(product_index));
-}
-
-void rewrite_default_space_instruction(instruction& curr) {
-  if (!curr.ingredients.empty())
-    raise << to_original_string(curr) << " can't take any ingredients\n" << end();
-  curr.name = "new";
-  curr.ingredients.push_back(reagent("location:type"));
-  curr.ingredients.push_back(reagent("number-of-locals:literal"));
-  if (!curr.products.empty())
-    raise << "new-default-space can't take any results\n" << end();
-  curr.products.push_back(reagent("default-space:space"));
-}
-
-:(scenario local_scope_frees_up_addresses_inside_containers)
-container foo [
-  x:num
-  y:&:num
-]
-def main [
-  local-scope
-  x:&:num <- new number:type
-  y:foo <- merge 34, x:&:num
-  # x and y are both cleared when main returns
-]
-+mem: clearing x:&:num
-+mem: decrementing refcount of 1006: 2 -> 1
-+mem: clearing y:foo
-+mem: decrementing refcount of 1006: 1 -> 0
-+mem: automatically abandoning 1006
-
-:(scenario local_scope_returns_addresses_inside_containers)
-container foo [
-  x:num
-  y:&:num
-]
-def f [
-  local-scope
-  x:&:num <- new number:type
-  *x:&:num <- copy 12
-  y:foo <- merge 34, x:&:num
-  # since y is 'escaping' f, it should not be cleared
-  return y:foo
-]
-def main [
-  1:foo <- f
-  3:num <- get 1:foo, x:offset
-  4:&:num <- get 1:foo, y:offset
-  5:num <- copy *4:&:num
-  1:foo <- put 1:foo, y:offset, 0
-  4:&:num <- copy 0
-]
-+mem: storing 34 in location 1
-+mem: storing 1006 in location 2
-+mem: storing 34 in location 3
-# refcount of 1:foo shouldn't include any stray ones from f
-+run: {4: ("address" "number")} <- get {1: "foo"}, {y: "offset"}
-+mem: incrementing refcount of 1006: 1 -> 2
-# 1:foo wasn't abandoned/cleared
-+run: {5: "number"} <- copy {4: ("address" "number"), "lookup": ()}
-+mem: storing 12 in location 5
-+run: {1: "foo"} <- put {1: "foo"}, {y: "offset"}, {0: "literal"}
-+mem: decrementing refcount of 1006: 2 -> 1
-+run: {4: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1006: 1 -> 0
-+mem: automatically abandoning 1006
-
-:(scenario local_scope_claims_return_values_when_not_saved)
-def f [
-  local-scope
-  x:&:num <- new number:type
-  return x:&:num
-]
-def main [
-  f  # doesn't save result
-]
-# x reclaimed
-+mem: automatically abandoning 1004
-# f's local scope reclaimed
-+mem: automatically abandoning 1000
-
-//:: all recipes must set default-space one way or another
-
-:(before "End Globals")
-bool Hide_missing_default_space_errors = true;
-:(before "End Checks")
-Transform.push_back(check_default_space);  // idempotent
-:(code)
-void check_default_space(const recipe_ordinal r) {
-  if (Hide_missing_default_space_errors) return;  // skip previous core tests; this is only for Mu code
-  const recipe& caller = get(Recipe, r);
-  // End check_default_space Special-cases
-  // assume recipes with only numeric addresses know what they're doing (usually tests)
-  if (!contains_non_special_name(r)) return;
-  trace(9991, "transform") << "--- check that recipe " << caller.name << " sets default-space" << end();
-  if (caller.steps.empty()) return;
-  if (caller.steps.at(0).products.empty()
-      || caller.steps.at(0).products.at(0).name != "default-space") {
-    raise << caller.name << " does not seem to start with default-space or local-scope\n" << end();
-  }
-}
-:(after "Load Mu Prelude")
-Hide_missing_default_space_errors = false;
-:(after "Test Runs")
-Hide_missing_default_space_errors = true;
-:(after "Running Main")
-Hide_missing_default_space_errors = false;
-
-:(code)
-bool contains_non_special_name(const recipe_ordinal r) {
-  for (map<string, int>::iterator p = Name[r].begin();  p != Name[r].end();  ++p) {
-    if (p->first.empty()) continue;
-    if (p->first.find("stash_") == 0) continue;  // generated by rewrite_stashes_to_text (cross-layer)
-    if (!is_special_name(p->first))
-      return true;
-  }
-  return false;
-}
-
-// reagent comparison -- only between reagents in a single recipe
-bool operator==(const reagent& a, const reagent& b) {
-  if (a.name != b.name) return false;
-  if (property(a, "space") != property(b, "space")) return false;
-  return true;
-}
-
-bool operator<(const reagent& a, const reagent& b) {
-  int aspace = 0, bspace = 0;
-  if (has_property(a, "space")) aspace = to_integer(property(a, "space")->value);
-  if (has_property(b, "space")) bspace = to_integer(property(b, "space")->value);
-  if (aspace != bspace) return aspace < bspace;
-  return a.name < b.name;
-}
+  1 //: Spaces help isolate recipes from each other. You can create them at will,
+  2 //: and all addresses in arguments are implicitly based on the 'default-space'
+  3 //: (unless they have the /raw property)
+  4 
+  5 //: A space is just an array of any scalar location.
+  6 :(before "End Mu Types Initialization")
+  7 put(Type_abbreviations, "space", new_type_tree("address:array:location"));
+  8 //: Spaces are often called 'scopes' in other languages.
+  9 put(Type_abbreviations, "scope", new_type_tree("address:array:location"));
+ 10 
+ 11 :(scenario set_default_space)
+ 12 # if default-space is 10, and if an array of 5 locals lies from location 12 to 16 (inclusive),
+ 13 # then local 0 is really location 12, local 1 is really location 13, and so on.
+ 14 def main [
+ 15   # pretend address:array:location; in practice we'll use new
+ 16   10:num <- copy 0  # refcount
+ 17   11:num <- copy 5  # length
+ 18   default-space:space <- copy 10/unsafe
+ 19   1:num <- copy 23
+ 20 ]
+ 21 +mem: storing 23 in location 13
+ 22 
+ 23 :(scenario lookup_sidesteps_default_space)
+ 24 def main [
+ 25   # pretend pointer from outside (2000 reserved for refcount)
+ 26   2001:num <- copy 34
+ 27   # pretend address:array:location; in practice we'll use new
+ 28   1000:num <- copy 0  # refcount
+ 29   1001:num <- copy 5  # length
+ 30   # actual start of this recipe
+ 31   default-space:space <- copy 1000/unsafe
+ 32   1:&:num <- copy 2000/unsafe  # even local variables always contain raw addresses
+ 33   8:num/raw <- copy *1:&:num
+ 34 ]
+ 35 +mem: storing 34 in location 8
+ 36 
+ 37 //:: first disable name conversion for 'default-space'
+ 38 :(scenario convert_names_passes_default_space)
+ 39 % Hide_errors = true;
+ 40 def main [
+ 41   default-space:num, x:num <- copy 0, 1
+ 42 ]
+ 43 +name: assign x 1
+ 44 -name: assign default-space 1
+ 45 
+ 46 :(before "End is_disqualified Special-cases")
+ 47 if (x.name == "default-space")
+ 48   x.initialized = true;
+ 49 :(before "End is_special_name Special-cases")
+ 50 if (s == "default-space") return true;
+ 51 
+ 52 //:: now implement space support
+ 53 :(before "End call Fields")
+ 54 int default_space;
+ 55 :(before "End call Constructor")
+ 56 default_space = 0;
+ 57 
+ 58 :(before "End canonize(x) Special-cases")
+ 59 absolutize(x);
+ 60 :(code)
+ 61 void absolutize(reagent& x) {
+ 62   if (is_raw(x) || is_dummy(x)) return;
+ 63   if (x.name == "default-space") return;
+ 64   if (!x.initialized)
+ 65     raise << to_original_string(current_instruction()) << ": reagent not initialized: '" << x.original_string << "'\n" << end();
+ 66   x.set_value(address(x.value, space_base(x)));
+ 67   x.properties.push_back(pair<string, string_tree*>("raw", NULL));
+ 68   assert(is_raw(x));
+ 69 }
+ 70 
+ 71 //: hook replaced in a later layer
+ 72 int space_base(const reagent& x) {
+ 73   return current_call().default_space ? (current_call().default_space+/*skip refcount*/1) : 0;
+ 74 }
+ 75 
+ 76 int address(int offset, int base) {
+ 77   assert(offset >= 0);
+ 78   if (base == 0) return offset;  // raw
+ 79   int size = get_or_insert(Memory, base);
+ 80   if (offset >= size) {
+ 81     // todo: test
+ 82     raise << "location " << offset << " is out of bounds " << size << " at " << base << '\n' << end();
+ 83     return 0;
+ 84   }
+ 85   return base + /*skip length*/1 + offset;
+ 86 }
+ 87 
+ 88 //:: reads and writes to the 'default-space' variable have special behavior
+ 89 
+ 90 :(after "Begin Preprocess write_memory(x, data)")
+ 91 if (x.name == "default-space") {
+ 92   if (!scalar(data) || !is_space(x))
+ 93     raise << maybe(current_recipe_name()) << "'default-space' should be of type address:array:location, but is " << to_string(x.type) << '\n' << end();
+ 94   current_call().default_space = data.at(0);
+ 95   return;
+ 96 }
+ 97 :(code)
+ 98 bool is_space(const reagent& r) {
+ 99   return is_address_of_array_of_numbers(r);
+100 }
+101 
+102 :(scenario get_default_space)
+103 def main [
+104   default-space:space <- copy 10/unsafe
+105   1:space/raw <- copy default-space:space
+106 ]
+107 +mem: storing 10 in location 1
+108 
+109 :(after "Begin Preprocess read_memory(x)")
+110 if (x.name == "default-space") {
+111   vector<double> result;
+112   result.push_back(current_call().default_space);
+113   return result;
+114 }
+115 
+116 //:: fix 'get'
+117 
+118 :(scenario lookup_sidesteps_default_space_in_get)
+119 def main [
+120   # pretend pointer to container from outside (2000 reserved for refcount)
+121   2001:num <- copy 34
+122   2002:num <- copy 35
+123   # pretend address:array:location; in practice we'll use new
+124   1000:num <- copy 0  # refcount
+125   1001:num <- copy 5  # length
+126   # actual start of this recipe
+127   default-space:space <- copy 1000/unsafe
+128   1:&:point <- copy 2000/unsafe
+129   9:num/raw <- get *1:&:point, 1:offset
+130 ]
+131 +mem: storing 35 in location 9
+132 
+133 :(before "Read element" following "case GET:")
+134 element.properties.push_back(pair<string, string_tree*>("raw", NULL));
+135 
+136 //:: fix 'index'
+137 
+138 :(scenario lookup_sidesteps_default_space_in_index)
+139 def main [
+140   # pretend pointer to array from outside (2000 reserved for refcount)
+141   2001:num <- copy 2  # length
+142   2002:num <- copy 34
+143   2003:num <- copy 35
+144   # pretend address:array:location; in practice we'll use new
+145   1000:num <- copy 0  # refcount
+146   1001:num <- copy 5  # length
+147   # actual start of this recipe
+148   default-space:space <- copy 1000/unsafe
+149   1:&:@:num <- copy 2000/unsafe
+150   9:num/raw <- index *1:&:@:num, 1
+151 ]
+152 +mem: storing 35 in location 9
+153 
+154 :(before "Read element" following "case INDEX:")
+155 element.properties.push_back(pair<string, string_tree*>("raw", NULL));
+156 
+157 //:: convenience operation to automatically deduce the amount of space to
+158 //:: allocate in a default space with names
+159 
+160 :(scenario new_default_space)
+161 def main [
+162   new-default-space
+163   x:num <- copy 0
+164   y:num <- copy 3
+165 ]
+166 # allocate space for x and y, as well as the chaining slot at 0
+167 +mem: array length is 3
+168 
+169 :(before "End is_disqualified Special-cases")
+170 if (x.name == "number-of-locals")
+171   x.initialized = true;
+172 :(before "End is_special_name Special-cases")
+173 if (s == "number-of-locals") return true;
+174 
+175 :(before "End Rewrite Instruction(curr, recipe result)")
+176 // rewrite `new-default-space` to
+177 //   `default-space:space <- new location:type, number-of-locals:literal`
+178 // where N is Name[recipe][""]
+179 if (curr.name == "new-default-space") {
+180   rewrite_default_space_instruction(curr);
+181 }
+182 :(after "Begin Preprocess read_memory(x)")
+183 if (x.name == "number-of-locals") {
+184   vector<double> result;
+185   result.push_back(Name[get(Recipe_ordinal, current_recipe_name())][""]);
+186   if (result.back() == 0)
+187     raise << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names?\n" << end();
+188   return result;
+189 }
+190 :(after "Begin Preprocess write_memory(x, data)")
+191 if (x.name == "number-of-locals") {
+192   raise << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end();
+193   return;
+194 }
+195 
+196 //:: 'local-scope' is like 'new-default-space' except that we'll reclaim the
+197 //:: default-space when the routine exits
+198 
+199 :(scenario local_scope)
+200 def main [
+201   1:&:@:location <- foo
+202   2:&:@:location <- foo
+203   3:bool <- equal 1:&, 2:&
+204 ]
+205 def foo [
+206   local-scope
+207   x:num <- copy 34
+208   return default-space:space
+209 ]
+210 # both calls to foo should have received the same default-space
+211 +mem: storing 1 in location 3
+212 
+213 :(scenario local_scope_frees_up_addresses)
+214 def main [
+215   local-scope
+216   x:text <- new [abc]
+217 ]
+218 +mem: clearing x:text
+219 
+220 :(before "End Rewrite Instruction(curr, recipe result)")
+221 if (curr.name == "local-scope") {
+222   rewrite_default_space_instruction(curr);
+223 }
+224 
+225 //: todo: do this in a transform, rather than magically in the 'return' instruction
+226 :(after "Falling Through End Of Recipe")
+227 try_reclaim_locals();
+228 :(after "Starting Reply")
+229 try_reclaim_locals();
+230 
+231 :(code)
+232 void try_reclaim_locals() {
+233   // only reclaim routines starting with 'local-scope'
+234   const recipe_ordinal r = get(Recipe_ordinal, current_recipe_name());
+235   const recipe& exiting_recipe = get(Recipe, r);
+236   if (exiting_recipe.steps.empty()) return;
+237   const instruction& inst = exiting_recipe.steps.at(0);
+238   if (inst.old_name != "local-scope") return;
+239   // reclaim any local variables unless they're being returned
+240   vector<double> zeros;
+241   for (int i = /*leave default space for last*/1;  i < SIZE(exiting_recipe.steps);  ++i) {
+242     const instruction& inst = exiting_recipe.steps.at(i);
+243     for (int i = 0;  i < SIZE(inst.products);  ++i) {
+244       const reagent& product = inst.products.at(i);
+245       // local variables only
+246       if (has_property(product, "lookup")) continue;
+247       if (has_property(product, "raw")) continue;  // tests often want to check such locations after they run
+248       if (escaping(product)) continue;
+249       // End Checks For Reclaiming Locals
+250       trace(9999, "mem") << "clearing " << product.original_string << end();
+251       zeros.resize(size_of(product));
+252       write_memory(product, zeros);
+253     }
+254   }
+255   trace(9999, "mem") << "automatically abandoning " << current_call().default_space << end();
+256   abandon(current_call().default_space,
+257           inst.products.at(0).type->right,
+258           /*refcount*/1 + /*array length*/1 + /*number-of-locals*/Name[r][""]);
+259 }
+260 
+261 :(code)
+262 // is this reagent one of the values returned by the current (return) instruction?
+263 // is the corresponding ingredient saved in the caller?
+264 bool escaping(const reagent& r) {
+265   assert(Current_routine);  // run-time only
+266   // nothing escapes when you fall through past end of recipe
+267   if (current_step_index() >= SIZE(Current_routine->steps())) return false;
+268   for (long long i = 0;  i < SIZE(current_instruction().ingredients);  ++i) {
+269     if (r == current_instruction().ingredients.at(i)) {
+270       if (caller_uses_product(i))
+271         return true;
+272     }
+273   }
+274   return false;
+275 }
+276 
+277 //: since we don't decrement refcounts for escaping values above, make sure we
+278 //: don't increment them when the caller saves them either
+279 
+280 :(after "Write Products of Instruction")
+281 Update_refcounts_in_write_memory = should_update_refcounts_in_write_memory();
+282 :(before "End Write Products of Instruction")
+283 Update_refcounts_in_write_memory = true;
+284 :(code)
+285 bool should_update_refcounts_in_write_memory() {
+286   const instruction& inst = current_instruction();
+287   // End should_update_refcounts_in_write_memory Special-cases For Primitives
+288   if (inst.operation < MAX_PRIMITIVE_RECIPES) return true;
+289   if (!contains_key(Recipe, inst.operation)) return true;
+290   const recipe& callee = get(Recipe, inst.operation);
+291   if (callee.steps.empty()) return true;
+292   return callee.steps.at(0).old_name != "local-scope";  // callees that call local-scope are already dealt with before return
+293 }
+294 
+295 bool caller_uses_product(int product_index) {
+296   assert(Current_routine);  // run-time only
+297   assert(!Current_routine->calls.empty());
+298   if (Current_routine->calls.size() == 1) return false;
+299   const call& caller = *++Current_routine->calls.begin();
+300   const instruction& caller_inst = to_instruction(caller);
+301   if (product_index >= SIZE(caller_inst.products)) return false;
+302   return !is_dummy(caller_inst.products.at(product_index));
+303 }
+304 
+305 void rewrite_default_space_instruction(instruction& curr) {
+306   if (!curr.ingredients.empty())
+307     raise << to_original_string(curr) << " can't take any ingredients\n" << end();
+308   curr.name = "new";
+309   curr.ingredients.push_back(reagent("location:type"));
+310   curr.ingredients.push_back(reagent("number-of-locals:literal"));
+311   if (!curr.products.empty())
+312     raise << "new-default-space can't take any results\n" << end();
+313   curr.products.push_back(reagent("default-space:space"));
+314 }
+315 
+316 :(scenario local_scope_frees_up_addresses_inside_containers)
+317 container foo [
+318   x:num
+319   y:&:num
+320 ]
+321 def main [
+322   local-scope
+323   x:&:num <- new number:type
+324   y:foo <- merge 34, x:&:num
+325   # x and y are both cleared when main returns
+326 ]
+327 +mem: clearing x:&:num
+328 +mem: decrementing refcount of 1006: 2 -> 1
+329 +mem: clearing y:foo
+330 +mem: decrementing refcount of 1006: 1 -> 0
+331 +mem: automatically abandoning 1006
+332 
+333 :(scenario local_scope_returns_addresses_inside_containers)
+334 container foo [
+335   x:num
+336   y:&:num
+337 ]
+338 def f [
+339   local-scope
+340   x:&:num <- new number:type
+341   *x:&:num <- copy 12
+342   y:foo <- merge 34, x:&:num
+343   # since y is 'escaping' f, it should not be cleared
+344   return y:foo
+345 ]
+346 def main [
+347   1:foo <- f
+348   3:num <- get 1:foo, x:offset
+349   4:&:num <- get 1:foo, y:offset
+350   5:num <- copy *4:&:num
+351   1:foo <- put 1:foo, y:offset, 0
+352   4:&:num <- copy 0
+353 ]
+354 +mem: storing 34 in location 1
+355 +mem: storing 1006 in location 2
+356 +mem: storing 34 in location 3
+357 # refcount of 1:foo shouldn't include any stray ones from f
+358 +run: {4: ("address" "number")} <- get {1: "foo"}, {y: "offset"}
+359 +mem: incrementing refcount of 1006: 1 -> 2
+360 # 1:foo wasn't abandoned/cleared
+361 +run: {5: "number"} <- copy {4: ("address" "number"), "lookup": ()}
+362 +mem: storing 12 in location 5
+363 +run: {1: "foo"} <- put {1: "foo"}, {y: "offset"}, {0: "literal"}
+364 +mem: decrementing refcount of 1006: 2 -> 1
+365 +run: {4: ("address" "number")} <- copy {0: "literal"}
+366 +mem: decrementing refcount of 1006: 1 -> 0
+367 +mem: automatically abandoning 1006
+368 
+369 :(scenario local_scope_claims_return_values_when_not_saved)
+370 def f [
+371   local-scope
+372   x:&:num <- new number:type
+373   return x:&:num
+374 ]
+375 def main [
+376   f  # doesn't save result
+377 ]
+378 # x reclaimed
+379 +mem: automatically abandoning 1004
+380 # f's local scope reclaimed
+381 +mem: automatically abandoning 1000
+382 
+383 //:: all recipes must set default-space one way or another
+384 
+385 :(before "End Globals")
+386 bool Hide_missing_default_space_errors = true;
+387 :(before "End Checks")
+388 Transform.push_back(check_default_space);  // idempotent
+389 :(code)
+390 void check_default_space(const recipe_ordinal r) {
+391   if (Hide_missing_default_space_errors) return;  // skip previous core tests; this is only for Mu code
+392   const recipe& caller = get(Recipe, r);
+393   // End check_default_space Special-cases
+394   // assume recipes with only numeric addresses know what they're doing (usually tests)
+395   if (!contains_non_special_name(r)) return;
+396   trace(9991, "transform") << "--- check that recipe " << caller.name << " sets default-space" << end();
+397   if (caller.steps.empty()) return;
+398   if (caller.steps.at(0).products.empty()
+399       || caller.steps.at(0).products.at(0).name != "default-space") {
+400     raise << caller.name << " does not seem to start with default-space or local-scope\n" << end();
+401   }
+402 }
+403 :(after "Load Mu Prelude")
+404 Hide_missing_default_space_errors = false;
+405 :(after "Test Runs")
+406 Hide_missing_default_space_errors = true;
+407 :(after "Running Main")
+408 Hide_missing_default_space_errors = false;
+409 
+410 :(code)
+411 bool contains_non_special_name(const recipe_ordinal r) {
+412   for (map<string, int>::iterator p = Name[r].begin();  p != Name[r].end();  ++p) {
+413     if (p->first.empty()) continue;
+414     if (p->first.find("stash_") == 0) continue;  // generated by rewrite_stashes_to_text (cross-layer)
+415     if (!is_special_name(p->first))
+416       return true;
+417   }
+418   return false;
+419 }
+420 
+421 // reagent comparison -- only between reagents in a single recipe
+422 bool operator==(const reagent& a, const reagent& b) {
+423   if (a.name != b.name) return false;
+424   if (property(a, "space") != property(b, "space")) return false;
+425   return true;
+426 }
+427 
+428 bool operator<(const reagent& a, const reagent& b) {
+429   int aspace = 0, bspace = 0;
+430   if (has_property(a, "space")) aspace = to_integer(property(a, "space")->value);
+431   if (has_property(b, "space")) bspace = to_integer(property(b, "space")->value);
+432   if (aspace != bspace) return aspace < bspace;
+433   return a.name < b.name;
+434 }
 
-- cgit 1.4.1-2-gfad0