about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2018-06-24 09:16:17 -0700
committerKartik Agaram <vc@akkartik.com>2018-06-24 09:18:20 -0700
commit23d3a02226973f80188e84fa5dcedb14413c5b7f (patch)
tree3c73284cb795e74d78e53b72df470cafca4c70cf
parent377b00b045289a3fa8e88d4b2f129d797c687e2f (diff)
downloadmu-23d3a02226973f80188e84fa5dcedb14413c5b7f.tar.gz
4266 - space for alloc-id in heap allocations
This has taken me almost 6 weeks :(
-rw-r--r--020run.cc39
-rw-r--r--021check_instruction.cc50
-rw-r--r--022arithmetic.cc5
-rw-r--r--023boolean.cc16
-rw-r--r--024jump.cc21
-rw-r--r--025compare.cc8
-rw-r--r--027call_ingredient.cc2
-rw-r--r--032array.cc101
-rw-r--r--033exclusive_container.cc36
-rw-r--r--034address.cc160
-rw-r--r--035lookup.cc304
-rw-r--r--037abandon.cc32
-rw-r--r--038new_text.cc73
-rw-r--r--042name.cc48
-rw-r--r--043space.cc124
-rw-r--r--044space_surround.cc44
-rw-r--r--045closure_name.cc14
-rw-r--r--046check_type_by_name.cc20
-rw-r--r--055shape_shifting_container.cc44
-rw-r--r--056shape_shifting_recipe.cc34
-rw-r--r--058to_text.cc1
-rw-r--r--060rewrite_literal_string.cc1
-rw-r--r--065duplex_list.mu53
-rw-r--r--069hash.cc12
-rw-r--r--074wait.cc18
-rw-r--r--082scenario_screen.cc69
-rw-r--r--085scenario_console.cc12
-rw-r--r--087file.cc4
-rw-r--r--089scenario_filesystem.cc16
-rw-r--r--091socket.cc2
-rw-r--r--101run_sandboxed.cc137
-rw-r--r--Readme.md6
-rw-r--r--edit/001-editor.mu22
-rw-r--r--exception1.mu2
-rw-r--r--index.html22
-rw-r--r--sandbox/001-editor.mu22
36 files changed, 926 insertions, 648 deletions
diff --git a/020run.cc b/020run.cc
index 3739ad2c..740f881b 100644
--- a/020run.cc
+++ b/020run.cc
@@ -87,6 +87,10 @@ void run_current_routine() {
       // Primitive Recipe Implementations
       case COPY: {
         copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
+        for (int i = 0;  i < SIZE(current_instruction().products);  ++i) {
+          if (is_mu_scalar(current_instruction().products.at(i)) && is_mu_address(current_instruction().ingredients.at(i)))
+            products.at(i).erase(products.at(i).begin());  // ignore alloc id
+        }
         break;
       }
       // End Primitive Recipe Implementations
@@ -125,6 +129,33 @@ bool should_copy_ingredients() {
   return true;
 }
 
+bool is_mu_scalar(reagent/*copy*/ r) {
+  return is_mu_scalar(r.type);
+}
+bool is_mu_scalar(const type_tree* type) {
+  if (!type) return false;
+  if (is_mu_address(type)) return false;
+  if (!type->atom) return false;
+  if (is_literal(type))
+    return type->name != "literal-string";
+  return size_of(type) == 1;
+}
+
+bool is_mu_address(reagent/*copy*/ r) {
+  // End Preprocess is_mu_address(reagent r)
+  return is_mu_address(r.type);
+}
+bool is_mu_address(const type_tree* type) {
+  if (!type) return false;
+  if (is_literal(type)) return false;
+  if (type->atom) return false;
+  if (!type->left->atom) {
+    raise << "invalid type " << to_string(type) << '\n' << end();
+    return false;
+  }
+  return type->left->value == Address_type_ordinal;
+}
+
 //: Some helpers.
 //: Important that they return references into the current routine.
 
@@ -299,6 +330,7 @@ bool ends_with(const string& s, const string& pat) {
 vector<double> read_memory(reagent/*copy*/ x) {
   // Begin Preprocess read_memory(x)
   vector<double> result;
+  if (x.name == "null") result.push_back(/*alloc id*/0);
   if (is_literal(x)) {
     result.push_back(x.value);
     return result;
@@ -358,7 +390,7 @@ int size_of(const type_tree* type) {
       raise << "invalid type " << to_string(type) << '\n' << end();
       return 0;
     }
-    if (type->left->value == Address_type_ordinal) return 1;
+    if (type->left->value == Address_type_ordinal) return 2;  // address and alloc id
     // End size_of(type) Non-atom Special-cases
   }
   // End size_of(type) Special-cases
@@ -421,6 +453,11 @@ def main [
 ]
 +run: _ <- copy {0: "literal"}
 
+:(scenario run_null)
+def main [
+  1:&:num <- copy null
+]
+
 :(scenario write_to_0_disallowed)
 % Hide_errors = true;
 def main [
diff --git a/021check_instruction.cc b/021check_instruction.cc
index 994eb209..6347dfb3 100644
--- a/021check_instruction.cc
+++ b/021check_instruction.cc
@@ -88,14 +88,15 @@ $error: 0
 
 :(code)
 // types_match with some leniency
-bool types_coercible(const reagent& to, const reagent& from) {
-  if (types_match(to, from)) return true;
+bool types_coercible(reagent/*copy*/ to, reagent/*copy*/ from) {
+  // Begin types_coercible(reagent to, reagent from)
+  if (types_match_sub(to, from)) return true;
   if (is_real_mu_number(from) && is_mu_character(to)) return true;
   // End types_coercible Special-cases
   return false;
 }
 
-bool types_match(const reagent& to, const reagent& from) {
+bool types_match_sub(const reagent& to, const reagent& from) {
   // to sidestep type-checking, use /unsafe in the source.
   // this will be highlighted in red inside vim. just for setting up some tests.
   if (is_unsafe(from)) return true;
@@ -103,15 +104,20 @@ bool types_match(const reagent& to, const reagent& from) {
     if (is_mu_array(to)) return false;
     // End Matching Types For Literal(to)
     if (!to.type) return false;
+    // allow writing null to any address
     if (is_mu_address(to)) return from.name == "null";
     return size_of(to) == 1;  // literals are always scalars
   }
-  return types_strictly_match(to, from);
+  return types_strictly_match_sub(to, from);
+}
+// variant for others to call
+bool types_match(reagent/*copy*/ to, reagent/*copy*/ from) {
+  // Begin types_match(reagent to, reagent from)
+  return types_match_sub(to, from);
 }
 
 //: copy arguments for later layers
-bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) {
-  // End Preprocess types_strictly_match(reagent to, reagent from)
+bool types_strictly_match_sub(const reagent& to, const reagent& from) {
   if (to.type == NULL) return false;  // error
   if (is_literal(from) && to.type->value == Number_type_ordinal) return true;
   // to sidestep type-checking, use /unsafe in the source.
@@ -122,6 +128,11 @@ bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) {
   if (!to.type) return !from.type;
   return types_strictly_match(to.type, from.type);
 }
+// variant for others to call
+bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) {
+  // Begin types_strictly_match(reagent to, reagent from)
+  return types_strictly_match_sub(to, from);
+}
 
 bool types_strictly_match(const type_tree* to, const type_tree* from) {
   if (from == to) return true;
@@ -185,21 +196,6 @@ bool is_mu_array(const type_tree* type) {
   return type->left->value == Array_type_ordinal;
 }
 
-bool is_mu_address(reagent/*copy*/ r) {
-  // End Preprocess is_mu_address(reagent r)
-  return is_mu_address(r.type);
-}
-bool is_mu_address(const type_tree* type) {
-  if (!type) return false;
-  if (is_literal(type)) return false;
-  if (type->atom) return false;
-  if (!type->left->atom) {
-    raise << "invalid type " << to_string(type) << '\n' << end();
-    return false;
-  }
-  return type->left->value == Address_type_ordinal;
-}
-
 bool is_mu_boolean(reagent/*copy*/ r) {
   // End Preprocess is_mu_boolean(reagent r)
   if (!r.type) return false;
@@ -234,15 +230,3 @@ bool is_mu_character(const type_tree* type) {
   if (is_literal(type)) return false;
   return type->value == Character_type_ordinal;
 }
-
-bool is_mu_scalar(reagent/*copy*/ r) {
-  return is_mu_scalar(r.type);
-}
-bool is_mu_scalar(const type_tree* type) {
-  if (!type) return false;
-  if (is_mu_address(type)) return true;
-  if (!type->atom) return false;
-  if (is_literal(type))
-    return type->name != "literal-string";
-  return size_of(type) == 1;
-}
diff --git a/022arithmetic.cc b/022arithmetic.cc
index 79a314bb..ce8cf1b9 100644
--- a/022arithmetic.cc
+++ b/022arithmetic.cc
@@ -79,7 +79,6 @@ case SUBTRACT: {
     break;
   }
   for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
-    if (is_raw(inst.ingredients.at(i))) continue;  // permit address offset computations in tests
     if (!is_mu_number(inst.ingredients.at(i))) {
       raise << maybe(get(Recipe, r).name) << "'subtract' requires number ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end();
       goto finish_checking_instruction;
@@ -104,10 +103,6 @@ case SUBTRACT: {
   products.at(0).push_back(result);
   break;
 }
-:(code)
-bool is_raw(const reagent& r) {
-  return has_property(r, "raw");
-}
 
 :(scenario subtract_literal)
 def main [
diff --git a/023boolean.cc b/023boolean.cc
index 114d9d8e..6d13325f 100644
--- a/023boolean.cc
+++ b/023boolean.cc
@@ -26,11 +26,17 @@ case AND: {
 case AND: {
   bool result = true;
   for (int i = 0;  i < SIZE(ingredients);  ++i)
-    result = result && ingredients.at(i).at(0);
+    result = result && scalar_ingredient(ingredients, i);
   products.resize(1);
   products.at(0).push_back(result);
   break;
 }
+:(code)
+double scalar_ingredient(const vector<vector<double> >& ingredients, int i) {
+  if (is_mu_address(current_instruction().ingredients.at(i)))
+    return ingredients.at(i).at(/*skip alloc id*/1);
+  return ingredients.at(i).at(0);
+}
 
 :(scenario and)
 def main [
@@ -84,7 +90,7 @@ case OR: {
 case OR: {
   bool result = false;
   for (int i = 0;  i < SIZE(ingredients);  ++i)
-    result = result || ingredients.at(i).at(0);
+    result = result || scalar_ingredient(ingredients, i);
   products.resize(1);
   products.at(0).push_back(result);
   break;
@@ -127,8 +133,8 @@ case NOT: {
     break;
   }
   for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
-    if (!is_mu_scalar(inst.ingredients.at(i))) {
-      raise << maybe(get(Recipe, r).name) << "'not' requires boolean ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end();
+    if (!is_mu_scalar(inst.ingredients.at(i)) && !is_mu_address(inst.ingredients.at(i))) {
+      raise << maybe(get(Recipe, r).name) << "'not' requires ingredients that can be interpreted as boolean, but got '" << inst.ingredients.at(i).original_string << "'\n" << end();
       goto finish_checking_instruction;
     }
   }
@@ -145,7 +151,7 @@ case NOT: {
 case NOT: {
   products.resize(SIZE(ingredients));
   for (int i = 0;  i < SIZE(ingredients);  ++i) {
-    products.at(i).push_back(!ingredients.at(i).at(0));
+    products.at(i).push_back(!scalar_ingredient(ingredients, i));
   }
   break;
 }
diff --git a/024jump.cc b/024jump.cc
index 37011290..14c14297 100644
--- a/024jump.cc
+++ b/024jump.cc
@@ -72,7 +72,7 @@ case JUMP_IF: {
     raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' should get exactly two ingredients\n" << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(0))) {
+  if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) {
     raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' requires a boolean for its first ingredient, but '" << inst.ingredients.at(0).name << "' has type '" << names_to_string_without_quotes(inst.ingredients.at(0).type) << "'\n" << end();
     break;
   }
@@ -90,7 +90,7 @@ case JUMP_IF: {
 :(before "End Primitive Recipe Implementations")
 case JUMP_IF: {
   assert(current_instruction().ingredients.at(1).initialized);
-  if (!ingredients.at(0).at(0)) {
+  if (!scalar_ingredient(ingredients, 0)) {
     trace(9998, "run") << "jump-if fell through" << end();
     break;
   }
@@ -109,7 +109,7 @@ def main [
 ]
 +run: jump-if {999: "literal"}, {1: "offset"}
 +run: jumping to instruction 2
--run: {1: "number"} <- copy {1: "literal"}
+-run: {123: "number"} <- copy {1: "literal"}
 -mem: storing 1 in location 123
 
 :(scenario jump_if_fallthrough)
@@ -122,6 +122,17 @@ def main [
 +run: {123: "number"} <- copy {1: "literal"}
 +mem: storing 1 in location 123
 
+:(scenario jump_if_on_address)
+def main [
+  10:num/alloc-id, 11:num <- copy 0, 999
+  jump-if 10:&:number, 1:offset
+  123:num <- copy 1
+]
++run: jump-if {10: ("address" "number")}, {1: "offset"}
++run: jumping to instruction 3
+-run: {123: "number"} <- copy {1: "literal"}
+-mem: storing 1 in location 123
+
 :(before "End Primitive Recipe Declarations")
 JUMP_UNLESS,
 :(before "End Primitive Recipe Numbers")
@@ -132,7 +143,7 @@ case JUMP_UNLESS: {
     raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' should get exactly two ingredients\n" << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(0))) {
+  if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) {
     raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' requires a boolean for its first ingredient, but '" << inst.ingredients.at(0).name << "' has type '" << names_to_string_without_quotes(inst.ingredients.at(0).type) << "'\n" << end();
     break;
   }
@@ -150,7 +161,7 @@ case JUMP_UNLESS: {
 :(before "End Primitive Recipe Implementations")
 case JUMP_UNLESS: {
   assert(current_instruction().ingredients.at(1).initialized);
-  if (ingredients.at(0).at(0)) {
+  if (scalar_ingredient(ingredients, 0)) {
     trace(9998, "run") << "jump-unless fell through" << end();
     break;
   }
diff --git a/025compare.cc b/025compare.cc
index 92878208..e12fa1dc 100644
--- a/025compare.cc
+++ b/025compare.cc
@@ -32,6 +32,10 @@ case EQUAL: {
   vector<double>& exemplar = ingredients.at(0);
   bool result = true;
   for (int i = /*skip exemplar*/1;  i < SIZE(ingredients);  ++i) {
+    if (SIZE(ingredients.at(i)) != SIZE(exemplar)) {
+      result = false;
+      break;
+    }
     if (!equal(ingredients.at(i).begin(), ingredients.at(i).end(), exemplar.begin())) {
       result = false;
       break;
@@ -103,6 +107,10 @@ case NOT_EQUAL: {
 case NOT_EQUAL: {
   vector<double>& exemplar = ingredients.at(0);
   products.resize(1);
+  if (SIZE(ingredients.at(1)) != SIZE(exemplar)) {
+    products.at(0).push_back(true);
+    break;
+  }
   bool equal_ingredients = equal(ingredients.at(1).begin(), ingredients.at(1).end(), exemplar.begin());
   products.at(0).push_back(!equal_ingredients);
   break;
diff --git a/027call_ingredient.cc b/027call_ingredient.cc
index 46fafe7e..df7bba3e 100644
--- a/027call_ingredient.cc
+++ b/027call_ingredient.cc
@@ -124,6 +124,8 @@ case REWIND_INGREDIENTS: {
   break;
 }
 
+//: another primitive: 'ingredient' for random access
+
 :(scenario ingredient)
 def main [
   f 1, 2
diff --git a/032array.cc b/032array.cc
index 34acc462..25d91b60 100644
--- a/032array.cc
+++ b/032array.cc
@@ -11,7 +11,7 @@ def main [
   # create an array occupying locations 1 (for the size) and 2-4 (for the elements)
   1:array:num:3 <- create-array
 ]
-+run: creating array of size 4
++run: creating array from 4 locations
 
 :(before "End Primitive Recipe Declarations")
 CREATE_ARRAY,
@@ -60,7 +60,7 @@ case CREATE_ARRAY: {
   trace("mem") << "storing " << array_length << " in location " << base_address << end();
   put(Memory, base_address, array_length);  // in array elements
   int size = size_of(product);  // in locations
-  trace(9998, "run") << "creating array of size " << size << end();
+  trace(9998, "run") << "creating array from " << size << " locations" << end();
   // initialize array
   for (int i = 1;  i <= size_of(product);  ++i)
     put(Memory, base_address+i, 0);
@@ -208,19 +208,23 @@ def main [
   2:num <- copy 14
   3:num <- copy 15
   4:num <- copy 16
-  5:num <- index 1:array:num:3, 0/index  # the index must be a non-negative whole number
+  10:num <- index 1:array:num:3, 0/index  # the index must be a non-negative whole number
 ]
-+mem: storing 14 in location 5
++mem: storing 14 in location 10
 
 :(scenario index_compound_element)
 def main [
   {1: (array (address number) 3)} <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
-  5:&:num <- index {1: (array (address number) 3)}, 0
+  # skip alloc id
+  3:num <- copy 14
+  # skip alloc id
+  5:num <- copy 15
+  # skip alloc id
+  7:num <- copy 16
+  10:address:num <- index {1: (array (address number) 3)}, 0
 ]
-+mem: storing 14 in location 5
+# skip alloc id
++mem: storing 14 in location 11
 
 :(scenario index_direct_offset)
 def main [
@@ -228,10 +232,10 @@ def main [
   2:num <- copy 14
   3:num <- copy 15
   4:num <- copy 16
-  5:num <- copy 0
-  6:num <- index 1:array:num, 5:num
+  10:num <- copy 0
+  20:num <- index 1:array:num, 10:num
 ]
-+mem: storing 14 in location 6
++mem: storing 14 in location 20
 
 :(before "End Primitive Recipe Declarations")
 INDEX,
@@ -330,7 +334,7 @@ void test_array_length_compound() {
   put(Memory, 2, 14);
   put(Memory, 3, 15);
   put(Memory, 4, 16);
-  reagent x("1:array:&:num");  // 3 types, but not a static array
+  reagent x("1:array:address:num");  // 3 types, but not a static array
   populate_value(x);
   CHECK_EQ(array_length(x), 3);
 }
@@ -346,61 +350,40 @@ def main [
   2:num <- copy 14
   3:num <- copy 15
   4:num <- copy 16
-  5:num <- index 1:array:num:3, 1.5  # non-whole number
+  10:num <- index 1:array:num:3, 1.5  # non-whole number
 ]
 # fraction is truncated away
-+mem: storing 15 in location 5
++mem: storing 15 in location 10
 
 :(scenario index_out_of_bounds)
 % Hide_errors = true;
 def main [
-  1:array:num:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
-  5:num <- copy 14
-  6:num <- copy 15
-  7:num <- copy 16
-  index 1:array:num:3, 4  # less than size of array in locations, but larger than its length in elements
+  1:array:point:3 <- create-array
+  index 1:array:point:3, 4  # less than size of array in locations, but larger than its length in elements
 ]
-+error: main: invalid index 4 in 'index 1:array:num:3, 4'
++error: main: invalid index 4 in 'index 1:array:point:3, 4'
 
 :(scenario index_out_of_bounds_2)
 % Hide_errors = true;
 def main [
-  1:array:point:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
-  5:num <- copy 14
-  6:num <- copy 15
-  7:num <- copy 16
-  index 1:array:point, -1
+  1:array:num:3 <- create-array
+  index 1:array:num, -1
 ]
-+error: main: invalid index -1 in 'index 1:array:point, -1'
++error: main: invalid index -1 in 'index 1:array:num, -1'
 
 :(scenario index_product_type_mismatch)
 % Hide_errors = true;
 def main [
   1:array:point:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
-  5:num <- copy 14
-  6:num <- copy 15
-  7:num <- copy 16
-  9:num <- index 1:array:point, 0
+  10:num <- index 1:array:point, 0
 ]
-+error: main: 'index' on '1:array:point' can't be saved in '9:num'; type should be 'point'
++error: main: 'index' on '1:array:point' can't be saved in '10:num'; type should be 'point'
 
 //: we might want to call 'index' without saving the results, say in a sandbox
 
 :(scenario index_without_product)
 def main [
   1:array:num:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
   index 1:array:num:3, 0
 ]
 # just don't die
@@ -410,9 +393,6 @@ def main [
 :(scenario put_index)
 def main [
   1:array:num:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
   1:array:num <- put-index 1:array:num, 1, 34
 ]
 +mem: storing 34 in location 3
@@ -488,12 +468,6 @@ case PUT_INDEX: {
 % Hide_errors = true;
 def main [
   1:array:point:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
-  5:num <- copy 14
-  6:num <- copy 15
-  7:num <- copy 16
   8:point <- merge 34, 35
   1:array:point <- put-index 1:array:point, 4, 8:point  # '4' is less than size of array in locations, but larger than its length in elements
 ]
@@ -503,22 +477,14 @@ def main [
 % Hide_errors = true;
 def main [
   1:array:point:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
-  5:num <- copy 14
-  6:num <- copy 15
-  7:num <- copy 16
-  8:point <- merge 34, 35
-  1:array:point <- put-index 1:array:point, -1, 8:point
+  10:point <- merge 34, 35
+  1:array:point <- put-index 1:array:point, -1, 10:point
 ]
-+error: main: invalid index -1 in '1:array:point <- put-index 1:array:point, -1, 8:point'
++error: main: invalid index -1 in '1:array:point <- put-index 1:array:point, -1, 10:point'
 
 :(scenario put_index_product_error)
 % Hide_errors = true;
 def main [
-  local-scope
-  load-ingredients
   1:array:num:3 <- create-array
   4:array:num:3 <- put-index 1:array:num:3, 0, 34
 ]
@@ -529,12 +495,9 @@ def main [
 :(scenario array_length)
 def main [
   1:array:num:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
-  5:num <- length 1:array:num:3
+  10:num <- length 1:array:num
 ]
-+mem: storing 3 in location 5
++mem: storing 3 in location 10
 
 :(before "End Primitive Recipe Declarations")
 LENGTH,
diff --git a/033exclusive_container.cc b/033exclusive_container.cc
index c81aa1b9..c2d69f60 100644
--- a/033exclusive_container.cc
+++ b/033exclusive_container.cc
@@ -457,39 +457,3 @@ def main [
 +mem: storing 1 in location 6
 +mem: storing 34 in location 7
 +mem: storing 35 in location 8
-
-//: a little helper: convert address to number
-
-:(before "End Primitive Recipe Declarations")
-DEADDRESS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "deaddress", DEADDRESS);
-:(before "End Primitive Recipe Checks")
-case DEADDRESS: {
-  // primary goal of these checks is to forbid address arithmetic
-  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
-    if (!is_mu_address(inst.ingredients.at(i))) {
-      raise << maybe(get(Recipe, r).name) << "'deaddress' requires address ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end();
-      goto finish_checking_instruction;
-    }
-  }
-  if (SIZE(inst.products) > SIZE(inst.ingredients)) {
-    raise << maybe(get(Recipe, r).name) << "too many products in '" << to_original_string(inst) << "'\n" << end();
-    break;
-  }
-  for (int i = 0;  i < SIZE(inst.products);  ++i) {
-    if (!is_real_mu_number(inst.products.at(i))) {
-      raise << maybe(get(Recipe, r).name) << "'deaddress' requires number products, but got '" << inst.products.at(i).original_string << "'\n" << end();
-      goto finish_checking_instruction;
-    }
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case DEADDRESS: {
-  products.resize(SIZE(ingredients));
-  for (int i = 0;  i < SIZE(ingredients);  ++i) {
-    products.at(i).push_back(ingredients.at(i).at(0));
-  }
-  break;
-}
diff --git a/034address.cc b/034address.cc
index b97dfb17..6c4bdda4 100644
--- a/034address.cc
+++ b/034address.cc
@@ -18,6 +18,37 @@
 //: write to the payload of an ingredient rather than its value, simply add
 //: the /lookup property to it. Modern computers provide efficient support for
 //: addresses and lookups, making this a realistic feature.
+//:
+//: To create addresses and allocate memory exclusively for their use, use
+//: 'new'. Memory is a finite resource so if the computer can't satisfy your
+//: request, 'new' may return a 0 (null) address.
+//:
+//: Computers these days have lots of memory so in practice we can often
+//: assume we'll never run out. If you start running out however, say in a
+//: long-running program, you'll need to switch mental gears and start
+//: husbanding our memory more carefully. The most important tool to avoid
+//: wasting memory is to 'abandon' an address when you don't need it anymore.
+//: That frees up the memory allocated to it to be reused in future calls to
+//: 'new'.
+
+//: Since memory can be reused multiple times, it can happen that you have a
+//: stale copy to an address that has since been abandoned and reused. Using
+//: the stale address is almost never safe, but it can be very hard to track
+//: down such copies because any errors caused by them may occur even millions
+//: of instructions after the copy or abandon instruction. To help track down
+//: such issues, Mu tracks an 'alloc id' for each allocation it makes. The
+//: first call to 'new' has an alloc id of 1, the second gets 2, and so on.
+//: The alloc id is never reused.
+:(before "End Globals")
+long long Next_alloc_id = 0;
+:(before "End Reset")
+Next_alloc_id = 0;
+
+//: The 'new' instruction records alloc ids both in the memory being allocated
+//: and *also* in the address. The 'abandon' instruction clears alloc ids in
+//: both places as well. Tracking alloc ids in this manner allows us to raise
+//: errors about stale addresses much earlier: 'lookup' operations always
+//: compare alloc ids between the address and its payload.
 
 //: todo: give 'new' a custodian ingredient. Following malloc/free is a temporary hack.
 
@@ -25,29 +56,33 @@
 # call 'new' two times with identical types without modifying the results; you
 # should get back different results
 def main [
-  1:&:num/raw <- new num:type
-  2:&:num/raw <- new num:type
-  3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw
+  10:&:num <- new num:type
+  12:&:num <- new num:type
+  20:bool <- equal 10:&:num, 12:&:num
 ]
-+mem: storing 0 in location 3
++mem: storing 1000 in location 11
++mem: storing 0 in location 20
 
 :(scenario new_array)
 # call 'new' with a second ingredient to allocate an array of some type rather than a single copy
 def main [
-  1:&:@:num/raw <- new num:type, 5
-  2:&:num/raw <- new num:type
-  3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw
+  10:&:@:num <- new num:type, 5
+  12:&:num <- new num:type
+  20:num/alloc2, 21:num/alloc1 <- deaddress 10:&:@:num, 12:&:num
+  30:num <- subtract 21:num/alloc2, 20:num/alloc1
 ]
-+run: {1: ("address" "array" "number"), "raw": ()} <- new {num: "type"}, {5: "literal"}
++run: {10: ("address" "array" "number")} <- new {num: "type"}, {5: "literal"}
 +mem: array length is 5
-# don't forget the extra location for array length
-+mem: storing 6 in location 3
+# skip alloc id in allocation
++mem: storing 1000 in location 11
+# don't forget the extra locations for alloc id and array length
++mem: storing 7 in location 30
 
 :(scenario dilated_reagent_with_new)
 def main [
-  1:&:&:num <- new {(& num): type}
+  10:&:&:num <- new {(& num): type}
 ]
-+new: size of '(& num)' is 1
++new: size of '(& num)' is 2
 
 //: 'new' takes a weird 'type' as its first ingredient; don't error on it
 :(before "End Mu Types Initialization")
@@ -135,22 +170,29 @@ def main [
 :(scenario new_discerns_singleton_list_from_atom_container)
 % Hide_errors = true;
 def main [
-  1:&:num/raw <- new {(num): type}  # should be '{num: type}'
+  1:&:num <- new {(num): type}  # should be '{num: type}'
 ]
-+error: main: product of 'new' has incorrect type: '1:&:num/raw <- new {(num): type}'
++error: main: product of 'new' has incorrect type: '1:&:num <- new {(num): type}'
 
 :(scenario new_with_type_abbreviation)
 def main [
-  1:&:num/raw <- new num:type
+  1:&:num <- new num:type
 ]
 $error: 0
 
 :(scenario new_with_type_abbreviation_inside_compound)
 def main [
-  {1: (& & num), raw: ()} <- new {(& num): type}
+  {1: (address address number), raw: ()} <- new {(& num): type}
 ]
 $error: 0
 
+:(scenario equal_result_of_new_with_null)
+def main [
+  1:&:num <- new num:type
+  10:bool <- equal 1:&:num, null
+]
++mem: storing 0 in location 10
+
 //: To implement 'new', a Mu transform turns all 'new' instructions into
 //: 'allocate' instructions that precompute the amount of memory they want to
 //: allocate.
@@ -221,15 +263,18 @@ case ALLOCATE: {
   int result = allocate(size);
   if (SIZE(current_instruction().ingredients) > 1) {
     // initialize array length
-    trace("mem") << "storing " << ingredients.at(1).at(0) << " in location " << result << end();
-    put(Memory, result, ingredients.at(1).at(0));
+    trace("mem") << "storing array length " << ingredients.at(1).at(0) << " in location " << result+/*skip alloc id*/1 << end();
+    put(Memory, result+/*skip alloc id*/1, ingredients.at(1).at(0));
   }
   products.resize(1);
+  products.at(0).push_back(/*alloc id*/0);
   products.at(0).push_back(result);
   break;
 }
 :(code)
 int allocate(int size) {
+  // include space for alloc id
+  ++size;
   trace("mem") << "allocating size " << size << end();
 //?   Total_alloc += size;
 //?   ++Num_alloc;
@@ -289,42 +334,45 @@ def main [
 
 :(scenario new_size)
 def main [
-  11:&:num/raw <- new num:type
-  12:&:num/raw <- new num:type
-  13:num/raw <- subtract 12:&:num/raw, 11:&:num/raw
+  10:&:num <- new num:type
+  12:&:num <- new num:type
+  20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:num, 12:&:num
+  30:num <- subtract 21:num/alloc2, 20:num/alloc1
 ]
-# size of number
-+mem: storing 1 in location 13
+# size of number + alloc id
++mem: storing 2 in location 30
 
 :(scenario new_array_size)
 def main [
-  1:&:@:num/raw <- new num:type, 5
-  2:&:num/raw <- new num:type
-  3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw
+  10:&:@:num <- new num:type, 5
+  12:&:num <- new num:type
+  20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:num, 12:&:num
+  30:num <- subtract 21:num/alloc2, 20:num/alloc1
 ]
-# 5 locations for array contents + array length
-+mem: storing 6 in location 3
+# 5 locations for array contents + array length + alloc id
++mem: storing 7 in location 30
 
 :(scenario new_empty_array)
 def main [
-  1:&:@:num/raw <- new num:type, 0
-  2:&:num/raw <- new num:type
-  3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw
+  10:&:@:num <- new num:type, 0
+  12:&:num <- new num:type
+  20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:@:num, 12:&:num
+  30:num <- subtract 21:num/alloc2, 20:num/alloc1
 ]
-+run: {1: ("address" "array" "number"), "raw": ()} <- new {num: "type"}, {0: "literal"}
++run: {10: ("address" "array" "number")} <- new {num: "type"}, {0: "literal"}
 +mem: array length is 0
 # one location for array length
-+mem: storing 1 in location 3
++mem: storing 2 in location 30
 
 //: If a routine runs out of its initial allocation, it should allocate more.
 :(scenario new_overflow)
-% Initial_memory_per_routine = 2;  // barely enough room for point allocation below
+% Initial_memory_per_routine = 3;  // barely enough room for point allocation below
 def main [
-  1:&:num/raw <- new num:type
-  2:&:point/raw <- new point:type  # not enough room in initial page
+  10:&:num <- new num:type
+  12:&:point <- new point:type  # not enough room in initial page
 ]
-+new: routine allocated memory from 1000 to 1002
-+new: routine allocated memory from 1002 to 1004
++new: routine allocated memory from 1000 to 1003
++new: routine allocated memory from 1003 to 1006
 
 :(scenario new_without_ingredient)
 % Hide_errors = true;
@@ -332,3 +380,39 @@ def main [
   1:&:num <- new  # missing ingredient
 ]
 +error: main: 'new' requires one or two ingredients, but got '1:&:num <- new'
+
+//: a little helper: convert address to number
+
+:(before "End Primitive Recipe Declarations")
+DEADDRESS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "deaddress", DEADDRESS);
+:(before "End Primitive Recipe Checks")
+case DEADDRESS: {
+  // primary goal of these checks is to forbid address arithmetic
+  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
+    if (!is_mu_address(inst.ingredients.at(i))) {
+      raise << maybe(get(Recipe, r).name) << "'deaddress' requires address ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end();
+      goto finish_checking_instruction;
+    }
+  }
+  if (SIZE(inst.products) > SIZE(inst.ingredients)) {
+    raise << maybe(get(Recipe, r).name) << "too many products in '" << to_original_string(inst) << "'\n" << end();
+    break;
+  }
+  for (int i = 0;  i < SIZE(inst.products);  ++i) {
+    if (!is_real_mu_number(inst.products.at(i))) {
+      raise << maybe(get(Recipe, r).name) << "'deaddress' requires number products, but got '" << inst.products.at(i).original_string << "'\n" << end();
+      goto finish_checking_instruction;
+    }
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case DEADDRESS: {
+  products.resize(SIZE(ingredients));
+  for (int i = 0;  i < SIZE(ingredients);  ++i) {
+    products.at(i).push_back(ingredients.at(i).at(/*skip alloc id*/1));
+  }
+  break;
+}
diff --git a/035lookup.cc b/035lookup.cc
index 2fa31d3a..ebed6b4d 100644
--- a/035lookup.cc
+++ b/035lookup.cc
@@ -5,12 +5,15 @@
 
 :(scenario copy_indirect)
 def main [
-  1:&:num <- copy 10/unsafe
-  10:num <- copy 34
-  # This loads location 1 as an address and looks up *that* location.
-  2:num <- copy 1:&:num/lookup
+  # skip alloc id for 10:&:num
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 94
+  # Treat locations 10 and 11 as an address to look up, pointing at the
+  # payload in locations 20 and 21.
+  30:num <- copy 10:&:num/lookup
 ]
-+mem: storing 34 in location 2
++mem: storing 94 in location 30
 
 :(before "End Preprocess read_memory(x)")
 canonize(x);
@@ -19,10 +22,11 @@ canonize(x);
 //: 'lookup' property
 :(scenario store_indirect)
 def main [
-  1:&:num <- copy 10/unsafe
-  1:&:num/lookup <- copy 34
+  # skip alloc id for 10:&:num
+  11:num <- copy 10
+  10:&:num/lookup <- copy 94
 ]
-+mem: storing 34 in location 10
++mem: storing 94 in location 11
 
 :(before "End Preprocess write_memory(x, data)")
 canonize(x);
@@ -31,20 +35,20 @@ canonize(x);
 :(scenario store_to_0_fails)
 % Hide_errors = true;
 def main [
-  1:&:num <- copy null
-  1:&:num/lookup <- copy 34
+  10:&:num <- copy null
+  10:&:num/lookup <- copy 94
 ]
--mem: storing 34 in location 0
-+error: can't write to location 0 in '1:&:num/lookup <- copy 34'
+-mem: storing 94 in location 0
++error: main: tried to lookup 0 in '10:&:num/lookup <- copy 94'
 
 //: attempts to /lookup address 0 always loudly fail
 :(scenario lookup_0_fails)
 % Hide_errors = true;
 def main [
-  1:&:num <- copy null
-  2:num <- copy 1:&:num/lookup
+  10:&:num <- copy null
+  20:num <- copy 10:&:num/lookup
 ]
-+error: main: tried to lookup 0 in '2:num <- copy 1:&:num/lookup'
++error: main: tried to lookup 0 in '20:num <- copy 10:&:num/lookup'
 
 :(scenario lookup_0_dumps_callstack)
 % Hide_errors = true;
@@ -52,10 +56,10 @@ def main [
   foo null
 ]
 def foo [
-  1:&:num <- next-input
-  2:num <- copy 1:&:num/lookup
+  10:&:num <- next-input
+  20:num <- copy 10:&:num/lookup
 ]
-+error: foo: tried to lookup 0 in '2:num <- copy 1:&:num/lookup'
++error: foo: tried to lookup 0 in '20:num <- copy 10:&:num/lookup'
 +error:   called from main: foo null
 
 :(code)
@@ -82,7 +86,7 @@ void lookup_memory(reagent& x) {
 }
 
 void lookup_memory_core(reagent& x, bool check_for_null) {
-  double address = x.value;
+  double address = x.value + /*skip alloc id in address*/1;
   double new_value = get_or_insert(Memory, address);
   trace("mem") << "location " << address << " contains " << no_scientific(new_value) << end();
   if (check_for_null && new_value == 0) {
@@ -94,12 +98,18 @@ void lookup_memory_core(reagent& x, bool check_for_null) {
       raise << "tried to lookup 0\n" << end();
     }
   }
-  x.set_value(new_value);
+  x.set_value(new_value+/*skip alloc id in payload*/1);
   drop_from_type(x, "address");
   drop_one_lookup(x);
 }
 
-:(before "End Preprocess types_strictly_match(reagent to, reagent from)")
+:(after "Begin types_coercible(reagent to, reagent from)")
+if (!canonize_type(to)) return false;
+if (!canonize_type(from)) return false;
+:(after "Begin types_match(reagent to, reagent from)")
+if (!canonize_type(to)) return false;
+if (!canonize_type(from)) return false;
+:(after "Begin types_strictly_match(reagent to, reagent from)")
 if (!canonize_type(to)) return false;
 if (!canonize_type(from)) return false;
 
@@ -156,31 +166,38 @@ void drop_one_lookup(reagent& r) {
 
 :(scenario get_indirect)
 def main [
-  1:&:point <- copy 10/unsafe
-  10:num <- copy 34
-  11:num <- copy 35
-  2:num <- get 1:&:point/lookup, 0:offset
+  # skip alloc id for 10:&:point
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 94
+  22:num <- copy 95
+  30:num <- get 10:&:point/lookup, 0:offset
 ]
-+mem: storing 34 in location 2
++mem: storing 94 in location 30
 
 :(scenario get_indirect2)
 def main [
-  1:&:point <- copy 10/unsafe
-  10:num <- copy 34
-  11:num <- copy 35
-  2:&:num <- copy 20/unsafe
-  2:&:num/lookup <- get 1:&:point/lookup, 0:offset
+  # skip alloc id for 10:&:point
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 94
+  22:num <- copy 95
+  # skip alloc id for destination
+  31:num <- copy 40
+  30:&:num/lookup <- get 10:&:point/lookup, 0:offset
 ]
-+mem: storing 34 in location 20
++mem: storing 94 in location 41
 
 :(scenario include_nonlookup_properties)
 def main [
-  1:&:point <- copy 10/unsafe
-  10:num <- copy 34
-  11:num <- copy 35
-  2:num <- get 1:&:point/lookup/foo, 0:offset
+  # skip alloc id for 10:&:point
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 94
+  22:num <- copy 95
+  30:num <- get 10:&:point/lookup/foo, 0:offset
 ]
-+mem: storing 34 in location 2
++mem: storing 94 in location 30
 
 :(after "Update GET base in Check")
 if (!canonize_type(base)) break;
@@ -191,12 +208,14 @@ canonize(base);
 
 :(scenario put_indirect)
 def main [
-  1:&:point <- copy 10/unsafe
-  10:num <- copy 34
-  11:num <- copy 35
-  1:&:point/lookup <- put 1:&:point/lookup, 0:offset, 36
+  # skip alloc id for 10:&:point
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 94
+  22:num <- copy 95
+  10:&:point/lookup <- put 10:&:point/lookup, 0:offset, 96
 ]
-+mem: storing 36 in location 10
++mem: storing 96 in location 21
 
 :(after "Update PUT base in Check")
 if (!canonize_type(base)) break;
@@ -208,12 +227,14 @@ canonize(base);
 :(scenario put_product_error_with_lookup)
 % Hide_errors = true;
 def main [
-  1:&:point <- copy 10/unsafe
-  10:num <- copy 34
-  11:num <- copy 35
-  1:&:point <- put 1:&:point/lookup, x:offset, 36
+  # skip alloc id for 10:&:point
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 94
+  22:num <- copy 95
+  10:&:point <- put 10:&:point/lookup, x:offset, 96
 ]
-+error: main: product of 'put' must be first ingredient '1:&:point/lookup', but got '1:&:point'
++error: main: product of 'put' must be first ingredient '10:&:point/lookup', but got '10:&:point'
 
 :(before "End PUT Product Checks")
 reagent/*copy*/ p = inst.products.at(0);
@@ -237,24 +258,27 @@ canonize_type(product);
 
 :(scenario copy_array_indirect)
 def main [
-  10:@:num:3 <- create-array
-  11:num <- copy 14
-  12:num <- copy 15
-  13:num <- copy 16
-  1:&:@:num <- copy 10/unsafe
-  2:@:num <- copy 1:&:@:num/lookup
+  # skip alloc id for 10:&:@:num
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 3  # array length
+  22:num <- copy 94
+  23:num <- copy 95
+  24:num <- copy 96
+  30:@:num <- copy 10:&:@:num/lookup
 ]
-+mem: storing 3 in location 2
-+mem: storing 14 in location 3
-+mem: storing 15 in location 4
-+mem: storing 16 in location 5
++mem: storing 3 in location 30
++mem: storing 94 in location 31
++mem: storing 95 in location 32
++mem: storing 96 in location 33
 
 :(scenario create_array_indirect)
 def main [
-  1:&:@:num:3 <- copy 1000/unsafe  # pretend allocation
-  1:&:@:num:3/lookup <- create-array
+  # skip alloc id for 10:&:@:num:3
+  11:num <- copy 3000
+  10:&:array:num:3/lookup <- create-array
 ]
-+mem: storing 3 in location 1000
++mem: storing 3 in location 3001
 
 :(after "Update CREATE_ARRAY product in Check")
 if (!canonize_type(product)) break;
@@ -263,14 +287,16 @@ canonize(product);
 
 :(scenario index_indirect)
 def main [
-  10:@:num:3 <- create-array
-  11:num <- copy 14
-  12:num <- copy 15
-  13:num <- copy 16
-  1:&:@:num <- copy 10/unsafe
-  2:num <- index 1:&:@:num/lookup, 1
+  # skip alloc id for 10:&:@:num
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 3  # array length
+  22:num <- copy 94
+  23:num <- copy 95
+  24:num <- copy 96
+  30:num <- index 10:&:@:num/lookup, 1
 ]
-+mem: storing 15 in location 2
++mem: storing 95 in location 30
 
 :(before "Update INDEX base in Check")
 if (!canonize_type(base)) break;
@@ -286,38 +312,44 @@ canonize(index);
 
 :(scenario put_index_indirect)
 def main [
-  10:@:num:3 <- create-array
-  11:num <- copy 14
-  12:num <- copy 15
-  13:num <- copy 16
-  1:&:@:num <- copy 10/unsafe
-  1:&:@:num/lookup <- put-index 1:&:@:num/lookup, 1, 34
+  # skip alloc id for 10:&:@:num
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 3  # array length
+  22:num <- copy 94
+  23:num <- copy 95
+  24:num <- copy 96
+  10:&:@:num/lookup <- put-index 10:&:@:num/lookup, 1, 97
 ]
-+mem: storing 34 in location 12
++mem: storing 97 in location 23
 
 :(scenario put_index_indirect_2)
 def main [
-  1:@:num:3 <- create-array
-  2:num <- copy 14
-  3:num <- copy 15
-  4:num <- copy 16
-  5:&:num <- copy 10/unsafe
-  10:num <- copy 1
-  1:@:num:3 <- put-index 1:@:num:3, 5:&:num/lookup, 34
+  10:num <- copy 3  # array length
+  11:num <- copy 94
+  12:num <- copy 95
+  13:num <- copy 96
+  # skip alloc id for address
+  21:num <- copy 30
+  # skip alloc id for payload
+  31:num <- copy 1  # index
+  10:@:num <- put-index 10:@:num, 20:&:num/lookup, 97
 ]
-+mem: storing 34 in location 3
++mem: storing 97 in location 12
 
 :(scenario put_index_product_error_with_lookup)
 % Hide_errors = true;
 def main [
-  10:@:num:3 <- create-array
-  11:num <- copy 14
-  12:num <- copy 15
-  13:num <- copy 16
-  1:&:@:num <- copy 10/unsafe
-  1:&:@:num <- put-index 1:&:@:num/lookup, 1, 34
+  # skip alloc id for 10:&:@:num
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 3  # array length
+  22:num <- copy 94
+  23:num <- copy 95
+  24:num <- copy 96
+  10:&:@:num <- put-index 10:&:@:num/lookup, 1, 34
 ]
-+error: main: product of 'put-index' must be first ingredient '1:&:@:num/lookup', but got '1:&:@:num'
++error: main: product of 'put-index' must be first ingredient '10:&:@:num/lookup', but got '10:&:@:num'
 
 :(before "End PUT_INDEX Product Checks")
 reagent/*copy*/ p = inst.products.at(0);
@@ -331,14 +363,14 @@ if (!types_strictly_match(p, i)) {
 
 :(scenario dilated_reagent_in_static_array)
 def main [
-  {1: (@ (& num) 3)} <- create-array
-  5:&:num <- new num:type
-  {1: (@ (& num) 3)} <- put-index {1: (@ (& num) 3)}, 0, 5:&:num
-  *5:&:num <- copy 34
-  6:num <- copy *5:&:num
+  {1: (array (& num) 3)} <- create-array
+  10:&:num <- new num:type
+  {1: (array (& num) 3)} <- put-index {1: (array (& num) 3)}, 0, 10:&:num
+  *10:&:num <- copy 94
+  20:num <- copy *10:&:num
 ]
-+run: creating array of size 4
-+mem: storing 34 in location 6
++run: creating array from 7 locations
++mem: storing 94 in location 20
 
 :(before "Update PUT_INDEX base in Check")
 if (!canonize_type(base)) break;
@@ -354,14 +386,16 @@ canonize(index);
 
 :(scenario length_indirect)
 def main [
-  10:@:num:3 <- create-array
-  11:num <- copy 14
-  12:num <- copy 15
-  13:num <- copy 16
-  1:&:@:num <- copy 10/unsafe
-  2:num <- length 1:&:@:num/lookup
+  # skip alloc id for 10:&:@:num
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 3  # array length
+  22:num <- copy 94
+  23:num <- copy 95
+  24:num <- copy 96
+  30:num <- length 10:&:array:num/lookup
 ]
-+mem: storing 3 in location 2
++mem: storing 3 in location 30
 
 :(before "Update LENGTH array in Check")
 if (!canonize_type(array)) break;
@@ -370,32 +404,40 @@ canonize(array);
 
 :(scenario maybe_convert_indirect)
 def main [
-  10:number-or-point <- merge 0/number, 34
-  1:&:number-or-point <- copy 10/unsafe
-  2:num, 3:bool <- maybe-convert 1:&:number-or-point/lookup, i:variant
+  # skip alloc id for 10:&:number-or-point
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:number-or-point <- merge 0/number, 94
+  30:num, 31:bool <- maybe-convert 10:&:number-or-point/lookup, i:variant
 ]
-+mem: storing 1 in location 3
-+mem: storing 34 in location 2
++mem: storing 1 in location 31
++mem: storing 94 in location 30
 
 :(scenario maybe_convert_indirect_2)
 def main [
-  10:number-or-point <- merge 0/number, 34
-  1:&:number-or-point <- copy 10/unsafe
-  2:&:num <- copy 20/unsafe
-  2:&:num/lookup, 3:bool <- maybe-convert 1:&:number-or-point/lookup, i:variant
+  # skip alloc id for 10:&:number-or-point
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:number-or-point <- merge 0/number, 94
+  # skip alloc id for 30:&:num
+  31:num <- copy 40
+  30:&:num/lookup, 50:bool <- maybe-convert 10:&:number-or-point/lookup, i:variant
 ]
-+mem: storing 1 in location 3
-+mem: storing 34 in location 20
++mem: storing 1 in location 50
++mem: storing 94 in location 41
 
 :(scenario maybe_convert_indirect_3)
 def main [
-  10:number-or-point <- merge 0/number, 34
-  1:&:number-or-point <- copy 10/unsafe
-  2:&:bool <- copy 20/unsafe
-  3:num, 2:&:bool/lookup <- maybe-convert 1:&:number-or-point/lookup, i:variant
+  # skip alloc id for 10:&:number-or-point
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:number-or-point <- merge 0/number, 94
+  # skip alloc id for 30:&:bool
+  31:num <- copy 40
+  50:num, 30:&:bool/lookup <- maybe-convert 10:&:number-or-point/lookup, i:variant
 ]
-+mem: storing 1 in location 20
-+mem: storing 34 in location 3
++mem: storing 1 in location 41
++mem: storing 94 in location 50
 
 :(before "Update MAYBE_CONVERT base in Check")
 if (!canonize_type(base)) break;
@@ -413,11 +455,13 @@ canonize(status);
 
 :(scenario merge_exclusive_container_indirect)
 def main [
-  1:&:number-or-point <- copy 10/unsafe
-  1:&:number-or-point/lookup <- merge 0/number, 34
+  # skip alloc id for 10:&:number-or-point
+  11:num <- copy 20
+  10:&:number-or-point/lookup <- merge 0/number, 34
 ]
-+mem: storing 0 in location 10
-+mem: storing 34 in location 11
+# skip alloc id
++mem: storing 0 in location 21
++mem: storing 34 in location 22
 
 :(before "Update size_mismatch Check for MERGE(x)
 canonize(x);
@@ -426,12 +470,14 @@ canonize(x);
 
 :(scenario lookup_abbreviation)
 def main [
-  1:&:num <- copy 10/unsafe
-  10:num <- copy 34
-  3:num <- copy *1:&:num
+  # skip alloc id for 10:&:num
+  11:num <- copy 20
+  # skip alloc id for payload
+  21:num <- copy 94
+  30:num <- copy *10:&:num
 ]
-+parse: ingredient: {1: ("&" "num"), "lookup": ()}
-+mem: storing 34 in location 3
++parse: ingredient: {10: ("&" "num"), "lookup": ()}
++mem: storing 94 in location 30
 
 :(before "End Parsing reagent")
 {
diff --git a/037abandon.cc b/037abandon.cc
index 74256687..ca7c242c 100644
--- a/037abandon.cc
+++ b/037abandon.cc
@@ -2,15 +2,15 @@
 
 :(scenario new_reclaim)
 def main [
-  1:&:num <- new number:type
-  2:num <- deaddress 1:&:num  # because 1 will get reset during abandon below
-  abandon 1:&:num
-  3:&:num <- new number:type  # must be same size as abandoned memory to reuse
-  4:num <- deaddress 3:&:num
-  5:bool <- equal 2:num, 4:num
+  10:&:num <- new number:type
+  20:num <- deaddress 10:&:num
+  abandon 10:&:num
+  30:&:num <- new number:type  # must be same size as abandoned memory to reuse
+  40:num <- deaddress 30:&:num
+  50:bool <- equal 20:num, 40:num
 ]
 # both allocations should have returned the same address
-+mem: storing 1 in location 5
++mem: storing 1 in location 50
 
 //: When abandoning addresses we'll save them to a 'free list', segregated by size.
 
@@ -39,7 +39,7 @@ case ABANDON: {
   for (int i = 0;  i < SIZE(current_instruction().ingredients);  ++i) {
     reagent/*copy*/ ingredient = current_instruction().ingredients.at(i);
     canonize(ingredient);
-    abandon(get_or_insert(Memory, ingredient.value), payload_size(ingredient));
+    abandon(get_or_insert(Memory, ingredient.value+/*skip alloc id*/1), payload_size(ingredient));
   }
   break;
 }
@@ -58,7 +58,7 @@ void abandon(int address, int payload_size) {
 int payload_size(reagent/*copy*/ x) {
   x.properties.push_back(pair<string, string_tree*>("lookup", NULL));
   lookup_memory_core(x, /*check_for_null*/false);
-  return size_of(x);
+  return size_of(x)+/*alloc id*/1;
 }
 
 :(after "Allocate Special-cases")
@@ -91,12 +91,12 @@ def main [
 
 :(scenario new_reclaim_array)
 def main [
-  1:&:@:num <- new number:type, 2
-  2:num <- deaddress 1:&:@:num
-  abandon 1:&:@:num
-  3:&:@:num <- new number:type, 2  # same size
-  4:num <- deaddress 3:&:@:num
-  5:bool <- equal 2:num, 4:num
+  10:&:@:num <- new number:type, 2
+  20:num <- deaddress 10:&:@:num
+  abandon 10:&:@:num
+  30:&:@:num <- new number:type, 2  # same size
+  40:num <- deaddress 30:&:@:num
+  50:bool <- equal 20:num, 40:num
 ]
 # both calls to new returned identical addresses
-+mem: storing 1 in location 5
++mem: storing 1 in location 50
diff --git a/038new_text.cc b/038new_text.cc
index f0616c54..7e3c02f6 100644
--- a/038new_text.cc
+++ b/038new_text.cc
@@ -6,21 +6,21 @@ put(Type_abbreviations, "text", new_type_tree("&:@:character"));
 
 :(scenario new_string)
 def main [
-  1:text <- new [abc def]
-  2:char <- index *1:text, 5
+  10:text <- new [abc def]
+  20:char <- index *10:text, 5
 ]
 # number code for 'e'
-+mem: storing 101 in location 2
++mem: storing 101 in location 20
 
 :(scenario new_string_handles_unicode)
 def main [
-  1:text <- new [a«c]
-  2:num <- length *1:text
-  3:char <- index *1:text, 1
+  10:text <- new [a«c]
+  20:num <- length *10:text
+  21:char <- index *10:text, 1
 ]
-+mem: storing 3 in location 2
++mem: storing 3 in location 20
 # unicode for '«'
-+mem: storing 171 in location 3
++mem: storing 171 in location 21
 
 :(before "End NEW Check Special-cases")
 if (is_literal_text(inst.ingredients.at(0))) break;
@@ -29,6 +29,7 @@ if (inst.name == "new" && !inst.ingredients.empty() && is_literal_text(inst.ingr
 :(after "case NEW" following "Primitive Recipe Implementations")
   if (is_literal_text(current_instruction().ingredients.at(0))) {
     products.resize(1);
+    products.at(0).push_back(/*alloc id*/0);
     products.at(0).push_back(new_mu_text(current_instruction().ingredients.at(0).name));
     trace("mem") << "new string alloc: " << products.at(0).at(0) << end();
     break;
@@ -40,8 +41,9 @@ int new_mu_text(const string& contents) {
   int string_length = unicode_length(contents);
 //?   Total_alloc += string_length+1;
 //?   ++Num_alloc;
-  int result = allocate(string_length+/*array length*/1);
+  int result = allocate(/*array length*/1 + string_length);
   int curr_address = result;
+  ++curr_address;  // skip alloc id
   trace("mem") << "storing string length " << string_length << " in location " << curr_address << end();
   put(Memory, curr_address, string_length);
   ++curr_address;  // skip length
@@ -62,16 +64,16 @@ int new_mu_text(const string& contents) {
 
 //: a new kind of typo
 
-:(scenario string_literal_without_instruction)
+:(scenario literal_text_without_instruction)
 % Hide_errors = true;
 def main [
   [abc]
 ]
 +error: main: instruction '[abc]' has no recipe in '[abc]'
 
-//: stash recognizes strings
+//: stash recognizes texts
 
-:(scenario stash_string)
+:(scenario stash_text)
 def main [
   1:text <- new [abc]
   stash [foo:], 1:text
@@ -80,30 +82,29 @@ def main [
 
 :(before "End inspect Special-cases(r, data)")
 if (is_mu_text(r)) {
-  assert(scalar(data));
-  return read_mu_text(data.at(0));
+  return read_mu_text(data.at(/*skip alloc id*/1));
 }
 
 :(before "End $print Special-cases")
 else if (is_mu_text(current_instruction().ingredients.at(i))) {
-  cout << read_mu_text(ingredients.at(i).at(0));
+  cout << read_mu_text(ingredients.at(i).at(/*skip alloc id*/1));
 }
 
-:(scenario unicode_string)
+:(scenario unicode_text)
 def main [
   1:text <- new [♠]
   stash [foo:], 1:text
 ]
 +app: foo: ♠
 
-:(scenario stash_space_after_string)
+:(scenario stash_space_after_text)
 def main [
   1:text <- new [abc]
   stash 1:text, [foo]
 ]
 +app: abc foo
 
-:(scenario stash_string_as_array)
+:(scenario stash_text_as_array)
 def main [
   1:text <- new [abc]
   stash *1:text
@@ -114,15 +115,15 @@ def main [
 :(before "End Preprocess is_mu_text(reagent x)")
 if (!canonize_type(x)) return false;
 
-//: Allocate more to routine when initializing a literal string
-:(scenario new_string_overflow)
-% Initial_memory_per_routine = 2;
+//: Allocate more to routine when initializing a literal text
+:(scenario new_text_overflow)
+% Initial_memory_per_routine = 3;
 def main [
-  1:&:num/raw <- new number:type
-  2:text/raw <- new [a]  # not enough room in initial page, if you take the array length into account
+  10:&:num/raw <- new number:type
+  20:text/raw <- new [a]  # not enough room in initial page, if you take the array length into account
 ]
-+new: routine allocated memory from 1000 to 1002
-+new: routine allocated memory from 1002 to 1004
++new: routine allocated memory from 1000 to 1003
++new: routine allocated memory from 1003 to 1006
 
 //: helpers
 :(code)
@@ -140,9 +141,9 @@ int unicode_length(const string& s) {
 
 string read_mu_text(int address) {
   if (address == 0) return "";
-  int length = get_or_insert(Memory, address);
+  int length = get_or_insert(Memory, address+/*alloc id*/1);
   if (length == 0) return "";
-  return read_mu_characters(address+1, length);
+  return read_mu_characters(address+/*alloc id*/1+/*length*/1, length);
 }
 
 string read_mu_characters(int start, int length) {
@@ -156,13 +157,21 @@ string read_mu_characters(int start, int length) {
 
 //: assert: perform sanity checks at runtime
 
-:(scenario assert)
+:(scenario assert_literal)
 % Hide_errors = true;  // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line.
 def main [
   assert 0, [this is an assert in Mu]
 ]
 +error: this is an assert in Mu
 
+:(scenario assert)
+% Hide_errors = true;  // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line.
+def main [
+  1:text <- new [this is an assert in Mu]
+  assert 0, 1:text
+]
++error: this is an assert in Mu
+
 :(before "End Primitive Recipe Declarations")
 ASSERT,
 :(before "End Primitive Recipe Numbers")
@@ -173,8 +182,8 @@ case ASSERT: {
     raise << maybe(get(Recipe, r).name) << "'assert' takes exactly two ingredients rather than '" << to_original_string(inst) << "'\n" << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(get(Recipe, r).name) << "'assert' requires a boolean for its first ingredient, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
+  if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "'assert' requires a scalar or address for its first ingredient, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
     break;
   }
   if (!is_literal_text(inst.ingredients.at(1)) && !is_mu_text(inst.ingredients.at(1))) {
@@ -185,11 +194,11 @@ case ASSERT: {
 }
 :(before "End Primitive Recipe Implementations")
 case ASSERT: {
-  if (!ingredients.at(0).at(0)) {
+  if (!scalar_ingredient(ingredients, 0)) {
     if (is_literal_text(current_instruction().ingredients.at(1)))
       raise << current_instruction().ingredients.at(1).name << '\n' << end();
     else
-      raise << read_mu_text(ingredients.at(1).at(0)) << '\n' << end();
+      raise << read_mu_text(ingredients.at(1).at(/*skip alloc id*/1)) << '\n' << end();
     if (!Hide_errors) exit(1);
   }
   break;
diff --git a/042name.cc b/042name.cc
index 3cfc8587..3078574e 100644
--- a/042name.cc
+++ b/042name.cc
@@ -6,8 +6,8 @@
 def main [
   x:num <- copy 0
 ]
-+name: assign x 1
-+mem: storing 0 in location 1
++name: assign x 2
++mem: storing 0 in location 2
 
 :(scenarios transform)
 :(scenario transform_names_fails_on_use_before_define)
@@ -42,7 +42,7 @@ void transform_names(const recipe_ordinal r) {
   map<string, int>& names = Name[r];
   // store the indices 'used' so far in the map
   int& curr_idx = names[""];
-  ++curr_idx;  // avoid using index 0, benign skip in some other cases
+  curr_idx = 2;  // reserve indices 0 and 1 for the chaining slot in a later layer
   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     instruction& inst = caller.steps.at(i);
     // End transform_names(inst) Special-cases
@@ -135,13 +135,21 @@ bool is_compound_type_starting_with(const type_tree* type, const string& expecte
   return type->left->value == get(Type_ordinal, expected_name);
 }
 
-int find_element_name(const type_ordinal t, const string& name, const string& recipe_name) {
+int find_element_offset(const type_ordinal t, const string& name, const string& recipe_name) {
   const type_info& container = get(Type, t);
   for (int i = 0;  i < SIZE(container.elements);  ++i)
     if (container.elements.at(i).name == name) return i;
   raise << maybe(recipe_name) << "unknown element '" << name << "' in container '" << get(Type, t).name << "'\n" << end();
   return -1;
 }
+int find_element_location(int base_address, const string& name, const type_tree* type, const string& recipe_name) {
+  int offset = find_element_offset(get_base_type(type)->value, name, recipe_name);
+  if (offset == -1) return offset;
+  int result = base_address;
+  for (int i = 0; i < offset; ++i)
+    result += size_of(element_type(type, i));
+  return result;
+}
 
 bool is_numeric_location(const reagent& x) {
   if (is_literal(x)) return false;
@@ -165,31 +173,35 @@ bool is_special_name(const string& s) {
   return false;
 }
 
+bool is_raw(const reagent& r) {
+  return has_property(r, "raw");
+}
+
 :(scenario transform_names_supports_containers)
 def main [
   x:point <- merge 34, 35
   y:num <- copy 3
 ]
-+name: assign x 1
++name: assign x 2
 # skip location 2 because x occupies two locations
-+name: assign y 3
++name: assign y 4
 
 :(scenario transform_names_supports_static_arrays)
 def main [
   x:@:num:3 <- create-array
   y:num <- copy 3
 ]
-+name: assign x 1
++name: assign x 2
 # skip locations 2, 3, 4 because x occupies four locations
-+name: assign y 5
++name: assign y 6
 
 :(scenario transform_names_passes_dummy)
 # _ is just a dummy result that never gets consumed
 def main [
   _, x:num <- copy 0, 1
 ]
-+name: assign x 1
--name: assign _ 1
++name: assign x 2
+-name: assign _ 2
 
 //: an escape hatch to suppress name conversion that we'll use later
 :(scenarios run)
@@ -198,7 +210,7 @@ def main [
 def main [
   x:num/raw <- copy 0
 ]
--name: assign x 1
+-name: assign x 2
 +error: can't write to location 0 in 'x:num/raw <- copy 0'
 
 :(scenarios transform)
@@ -266,7 +278,7 @@ if (inst.name == "get" || inst.name == "get-location" || inst.name == "put") {
     // since first non-address in base type must be a container, we don't have to canonize
     type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type);
     if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
-      inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
+      inst.ingredients.at(1).set_value(find_element_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
       trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end();
     }
   }
@@ -279,15 +291,13 @@ def main [
 ]
 +error: main: missing type for 'a' in 'get a, x:offset'
 
-//: this test is actually illegal so can't call run
-:(scenarios transform)
 :(scenario transform_names_handles_containers)
 def main [
-  a:point <- copy 0/unsafe
-  b:num <- copy 0/unsafe
+  a:point <- merge 0, 0
+  b:num <- copy 0
 ]
-+name: assign a 1
-+name: assign b 3
++name: assign a 2
++name: assign b 4
 
 //:: Support variant names for exclusive containers in 'maybe-convert'.
 
@@ -316,7 +326,7 @@ if (inst.name == "maybe-convert") {
     // since first non-address in base type must be an exclusive container, we don't have to canonize
     type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type);
     if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
-      inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
+      inst.ingredients.at(1).set_value(find_element_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
       trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end();
     }
   }
diff --git a/043space.cc b/043space.cc
index f290a0b9..f9125daf 100644
--- a/043space.cc
+++ b/043space.cc
@@ -7,44 +7,65 @@
 //:
 //: Warning: messing with 'default-space' can corrupt memory. Don't share
 //: default-space between recipes. Later we'll see how to chain spaces safely.
+//:
+//: Tests in this layer can write to a location as part of one type, and read
+//: it as part of another. This is unsafe and insecure, and we'll stop doing
+//: this once we switch to variable names.
 
 //: Under the hood, a space is an array of locations in memory.
 :(before "End Mu Types Initialization")
 put(Type_abbreviations, "space", 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 5  # length
-  default-space:space <- copy 10/unsafe
-  1:num <- copy 23
+  # prepare default-space address
+  10:num/alloc-id, 11:num <- copy 0, 1000
+  # prepare default-space payload
+  1000:num <- copy 0  # alloc id of payload
+  1001:num <- copy 5  # length
+  # actual start of this recipe
+  default-space:space <- copy 10:&:@:location
+  # if default-space is 1000, then:
+  #   1000: alloc id
+  #   1001: array size
+  #   1002: location 0 (space for the chaining slot; described later; often unused)
+  #   1003: location 1 (space for the chaining slot; described later; often unused)
+  #   1004: local 2 (assuming it is a scalar)
+  2:num <- copy 93
 ]
-+mem: storing 23 in location 12
++mem: storing 93 in location 1004
 
 :(scenario lookup_sidesteps_default_space)
 def main [
-  # pretend pointer from outside
-  2000:num <- copy 34
-  # pretend address:array:location; in practice we'll use 'new'
-  1000:num <- copy 5  # length
+  # prepare default-space address
+  10:num/alloc-id, 11:num <- copy 0, 1000
+  # prepare default-space payload
+  1000:num <- copy 0  # alloc id of payload
+  1001:num <- copy 5  # length
+  # prepare payload outside the local scope
+  2000:num/alloc-id, 2001:num <- copy 0, 34
   # 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
+  default-space:space <- copy 10:&:@:location
+  # a local address
+  2:num, 3:num <- copy 0, 2000
+  20:num/raw <- copy *2:&:num
 ]
-+mem: storing 34 in location 8
++mem: storing 2000 in location 1005
++mem: storing 34 in location 20
 
 //: precondition: disable name conversion for 'default-space'
 
+:(scenarios transform)
 :(scenario convert_names_passes_default_space)
 % Hide_errors = true;
 def main [
-  default-space:num, x:num <- copy 0, 1
+  default-space:num <- copy 0
+  x:num <- copy 1
 ]
-+name: assign x 1
++name: assign x 2
 -name: assign default-space 1
+-name: assign default-space 2
+:(scenarios run)
 
 :(before "End is_disqualified Special-cases")
 if (x.name == "default-space")
@@ -74,7 +95,7 @@ void absolutize(reagent& x) {
 
 //: hook replaced in a later layer
 int space_base(const reagent& x) {
-  return current_call().default_space ? current_call().default_space : 0;
+  return current_call().default_space ? (current_call().default_space + /*skip alloc id*/1) : 0;
 }
 
 int address(int offset, int base) {
@@ -95,9 +116,11 @@ int address(int offset, int base) {
 
 :(after "Begin Preprocess write_memory(x, data)")
 if (x.name == "default-space") {
-  if (!scalar(data) || !is_mu_space(x))
+  if (!is_mu_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);
+  if (SIZE(data) != 2)
+    raise << maybe(current_recipe_name()) << "'default-space' getting data from non-address\n" << end();
+  current_call().default_space = data.at(/*skip alloc id*/1);
   return;
 }
 :(code)
@@ -112,14 +135,21 @@ bool is_mu_space(reagent/*copy*/ x) {
 
 :(scenario get_default_space)
 def main [
-  default-space:space <- copy 10/unsafe
-  1:space/raw <- copy default-space:space
+  # prepare default-space address
+  10:num/alloc-id, 11:num <- copy 0, 1000
+  # prepare default-space payload
+  1000:num <- copy 0  # alloc id of payload
+  1001:num <- copy 5  # length
+  # actual start of this recipe
+  default-space:space <- copy 10:space
+  2:space/raw <- copy default-space:space
 ]
-+mem: storing 10 in location 1
++mem: storing 1000 in location 3
 
 :(after "Begin Preprocess read_memory(x)")
 if (x.name == "default-space") {
   vector<double> result;
+  result.push_back(/*alloc id*/0);
   result.push_back(current_call().default_space);
   return result;
 }
@@ -128,17 +158,20 @@ if (x.name == "default-space") {
 
 :(scenario lookup_sidesteps_default_space_in_get)
 def main [
-  # pretend pointer to container from outside
-  2000:num <- copy 34
-  2001:num <- copy 35
-  # pretend address:array:location; in practice we'll use 'new'
-  1000:num <- copy 5  # length
+  # prepare default-space address
+  10:num/alloc-id, 11:num <- copy 0, 1000
+  # prepare default-space payload
+  1000:num <- copy 0  # alloc id of payload
+  1001:num <- copy 5  # length
+  # prepare payload outside the local scope
+  2000:num/alloc-id, 2001:num/x, 2002:num/y <- copy 0, 34, 35
   # actual start of this recipe
-  default-space:space <- copy 1000/unsafe
-  1:&:point <- copy 2000/unsafe
-  9:num/raw <- get *1:&:point, 1:offset
+  default-space:space <- copy 10:space
+  # a local address
+  2:num, 3:num <- copy 0, 2000
+  3000:num/raw <- get *2:&:point, 1:offset
 ]
-+mem: storing 35 in location 9
++mem: storing 35 in location 3000
 
 :(before "Read element" following "case GET:")
 element.properties.push_back(pair<string, string_tree*>("raw", NULL));
@@ -147,18 +180,21 @@ element.properties.push_back(pair<string, string_tree*>("raw", NULL));
 
 :(scenario lookup_sidesteps_default_space_in_index)
 def main [
-  # pretend pointer to array from outside
-  2000:num <- copy 2  # length
-  2001:num <- copy 34
-  2002:num <- copy 35
-  # pretend address:array:location; in practice we'll use 'new'
-  1000:num <- copy 5  # length
+  # prepare default-space address
+  10:num/alloc-id, 11:num <- copy 0, 1000
+  # prepare default-space payload
+  1000:num <- copy 0  # alloc id of payload
+  1001:num <- copy 5  # length
+  # prepare an array address
+  20:num/alloc-id, 21:num <- copy 0, 2000
+  # prepare an array payload
+  2000:num/alloc-id, 2001:num/length, 2002:num/index:0, 2003:num/index:1 <- copy 0, 2, 34, 35
   # actual start of this recipe
-  default-space:space <- copy 1000/unsafe
-  1:&:@:num <- copy 2000/unsafe
-  9:num/raw <- index *1:&:@:num, 1
+  default-space:space <- copy 10:&:@:location
+  1:&:@:num <- copy 20:&:@:num/raw
+  3000:num/raw <- index *1:&:@:num, 1
 ]
-+mem: storing 35 in location 9
++mem: storing 35 in location 3000
 
 :(before "Read element" following "case INDEX:")
 element.properties.push_back(pair<string, string_tree*>("raw", NULL));
@@ -172,8 +208,8 @@ def main [
   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
+# allocate space for x and y, as well as the chaining slot at indices 0 and 1
++mem: array length is 4
 
 :(before "End is_disqualified Special-cases")
 if (x.name == "number-of-locals")
diff --git a/044space_surround.cc b/044space_surround.cc
index 310672be..9957630d 100644
--- a/044space_surround.cc
+++ b/044space_surround.cc
@@ -5,27 +5,35 @@
 //: (Surrounding spaces are like lexical scopes in other languages.)
 
 :(scenario surrounding_space)
-# location 1 in space 1 refers to the space surrounding the default space, here 20.
+# location 2 in space 1 (remember that locations 0 and 1 are reserved in all
+# spaces) refers to the space surrounding the default space, here 20.
 def main [
-  # pretend address:array:location; in practice we'll use 'new'
-  10:num <- copy 5  # length
-  # pretend address:array:location; in practice we'll use 'new"
-  20:num <- copy 5  # length
+  # prepare default-space address
+  10:num/alloc-id, 11:num <- copy 0, 1000
+  # prepare default-space payload
+  1000:num <- copy 0  # alloc id of payload
+  1001:num <- copy 5  # length
+  # prepare address of chained space
+  20:num/alloc-id, 21:num <- copy 0, 2000
+  # prepare payload of chained space
+  2000:num <- copy 0  # alloc id of payload
+  2001:num <- copy 5  # length
   # actual start of this recipe
-  default-space:space <- copy 10/unsafe
+  default-space:space <- copy 10:space
   #: later layers will explain the /names: property
-  0:space/names:dummy <- copy 20/unsafe
-  1:num <- copy 32
-  1:num/space:1 <- copy 33
+  0:space/names:dummy <- copy 20:space/raw
+  2:num <- copy 94
+  2:num/space:1 <- copy 95
 ]
 def dummy [  # just for the /names: property above
 ]
-# chain space: 10 + (length) 1
-+mem: storing 20 in location 11
-# store to default space: 10 + (skip length) 1 + (index) 1
-+mem: storing 32 in location 12
-# store to chained space: (contents of location 12) 20 + (length) 1 + (index) 1
-+mem: storing 33 in location 22
+# chain space: 1000 + (alloc id) 1 + (length) 1
++mem: storing 0 in location 1002
++mem: storing 2000 in location 1003
+# store to default space: 1000 + (alloc id) 1 + (length) 1 + (index) 2
++mem: storing 94 in location 1004
+# store to chained space: (contents of location 1003) 2000 + (alloc id) 1 + (length) 1 + (index) 2
++mem: storing 95 in location 2004
 
 //: If you think of a space as a collection of variables with a common
 //: lifetime, surrounding allows managing shorter lifetimes inside a longer
@@ -33,14 +41,16 @@ def dummy [  # just for the /names: property above
 
 :(replace{} "int space_base(const reagent& x)")
 int space_base(const reagent& x) {
-  int base = current_call().default_space ? current_call().default_space : 0;
+  int base = current_call().default_space ? (current_call().default_space+/*skip alloc id*/1) : 0;
   return space_base(x, space_index(x), base);
 }
 
 int space_base(const reagent& x, int space_index, int base) {
   if (space_index == 0)
     return base;
-  return space_base(x, space_index-1, get_or_insert(Memory, base+/*skip length*/1));
+  double chained_space_address = base+/*skip length*/1+/*skip alloc id of chaining slot*/1;
+  double chained_space_base = get_or_insert(Memory, chained_space_address) + /*skip alloc id of chained space*/1;
+  return space_base(x, space_index-1, chained_space_base);
 }
 
 int space_index(const reagent& x) {
diff --git a/045closure_name.cc b/045closure_name.cc
index e478337d..98bf982c 100644
--- a/045closure_name.cc
+++ b/045closure_name.cc
@@ -9,14 +9,14 @@
 :(scenario closure)
 def main [
   default-space:space <- new location:type, 30
-  1:space/names:new-counter <- new-counter
-  2:num/raw <- increment-counter 1:space/names:new-counter
-  3:num/raw <- increment-counter 1:space/names:new-counter
+  2:space/names:new-counter <- new-counter
+  10:num/raw <- increment-counter 2:space/names:new-counter
+  11:num/raw <- increment-counter 2:space/names:new-counter
 ]
 def new-counter [
   default-space:space <- new location:type, 30
   x:num <- copy 23
-  y:num <- copy 3  # variable that will be incremented
+  y:num <- copy 13  # variable that will be incremented
   return default-space:space
 ]
 def increment-counter [
@@ -27,7 +27,7 @@ def increment-counter [
   return y:num/space:1
 ]
 +name: lexically surrounding space for recipe increment-counter comes from new-counter
-+mem: storing 5 in location 3
++mem: storing 15 in location 11
 
 //: To make this work, compute the recipe that provides names for the
 //: surrounding space of each recipe.
@@ -165,6 +165,6 @@ def use-scope [
 ]
 def main [
   1:space/raw <- new-scope
-  2:num/raw <- use-scope 1:space/raw
+  3:num/raw <- use-scope 1:space/raw
 ]
-+mem: storing 34 in location 2
++mem: storing 34 in location 3
diff --git a/046check_type_by_name.cc b/046check_type_by_name.cc
index c68a9bae..e44f87d5 100644
--- a/046check_type_by_name.cc
+++ b/046check_type_by_name.cc
@@ -87,27 +87,27 @@ void check_type(set<reagent>& known, const reagent& x, const recipe& caller) {
 
 :(scenario transform_fills_in_missing_types)
 def main [
-  x:num <- copy 1
+  x:num <- copy 11
   y:num <- add x, 1
 ]
-# x is in location 1, y in location 2
-+mem: storing 2 in location 2
+# x is in location 2, y in location 3
++mem: storing 12 in location 3
 
 :(scenario transform_fills_in_missing_types_in_product)
 def main [
-  x:num <- copy 1
-  x <- copy 2
+  x:num <- copy 11
+  x <- copy 12
 ]
-# x is in location 1
-+mem: storing 2 in location 1
+# x is in location 2
++mem: storing 12 in location 2
 
 :(scenario transform_fills_in_missing_types_in_product_and_ingredient)
 def main [
-  x:num <- copy 1
+  x:num <- copy 11
   x <- add x, 1
 ]
-# x is in location 1
-+mem: storing 2 in location 1
+# x is in location 2
++mem: storing 12 in location 2
 
 :(scenario transform_fills_in_missing_label_type)
 def main [
diff --git a/055shape_shifting_container.cc b/055shape_shifting_container.cc
index 02e5214b..c3d0246f 100644
--- a/055shape_shifting_container.cc
+++ b/055shape_shifting_container.cc
@@ -67,7 +67,7 @@ container foo:_a:_b [
 def main [
   1:text <- new [abc]
   # compound types for type ingredients
-  {2: (foo number (address array character))} <- merge 34/x, 1:text/y
+  {3: (foo number (address array character))} <- merge 34/x, 1:text/y
 ]
 $error: 0
 
@@ -82,7 +82,7 @@ container bar:_a:_b [
 ]
 def main [
   1:text <- new [abc]
-  2:bar:num:@:char <- merge 34/x, 1:text/y
+  3:bar:num:@:char <- merge 34/x, 1:text/y
 ]
 $error: 0
 
@@ -247,9 +247,9 @@ container foo:_t [
 ]
 def main [
   1:foo:point <- merge 14, 15, 16
-  2:num <- get 1:foo:point, y:offset
+  4:num <- get 1:foo:point, y:offset
 ]
-+mem: storing 16 in location 2
++mem: storing 16 in location 4
 
 :(scenario get_on_shape_shifting_container_2)
 container foo:_t [
@@ -258,10 +258,10 @@ container foo:_t [
 ]
 def main [
   1:foo:point <- merge 14, 15, 16
-  2:point <- get 1:foo:point, x:offset
+  4:point <- get 1:foo:point, x:offset
 ]
-+mem: storing 14 in location 2
-+mem: storing 15 in location 3
++mem: storing 14 in location 4
++mem: storing 15 in location 5
 
 :(scenario get_on_shape_shifting_container_3)
 container foo:_t [
@@ -269,10 +269,12 @@ container foo:_t [
   y:num
 ]
 def main [
-  1:foo:&:point <- merge 34/unsafe, 48
-  3:&:point <- get 1:foo:&:point, x:offset
+  1:num/alloc-id, 2:num <- copy 0, 34
+  3:foo:&:point <- merge 1:&:point, 48
+  6:&:point <- get 1:foo:&:point, x:offset
 ]
-+mem: storing 34 in location 3
++mem: storing 0 in location 6
++mem: storing 34 in location 7
 
 :(scenario get_on_shape_shifting_container_inside_container)
 container foo:_t [
@@ -285,9 +287,9 @@ container bar [
 ]
 def main [
   1:bar <- merge 14, 15, 16, 17
-  2:num <- get 1:bar, 1:offset
+  5:num <- get 1:bar, 1:offset
 ]
-+mem: storing 17 in location 2
++mem: storing 17 in location 5
 
 :(scenario get_on_complex_shape_shifting_container)
 container foo:_a:_b [
@@ -296,11 +298,11 @@ container foo:_a:_b [
 ]
 def main [
   1:text <- new [abc]
-  {2: (foo number (address array character))} <- merge 34/x, 1:text/y
-  3:text <- get {2: (foo number (address array character))}, y:offset
-  4:bool <- equal 1:text, 3:text
+  {3: (foo number (address array character))} <- merge 34/x, 1:text/y
+  6:text <- get {3: (foo number (address array character))}, y:offset
+  8:bool <- equal 1:text, 6:text
 ]
-+mem: storing 1 in location 4
++mem: storing 1 in location 8
 
 :(before "End element_type Special-cases")
 replace_type_ingredients(element, type, info, " while computing element type of container");
@@ -346,8 +348,8 @@ exclusive-container foo:_a [
 ]
 def main [
   1:text <- new [abc]
-  2:foo:point <- merge 0/variant, 34/xx, 35/xy
-  10:point, 20:bool <- maybe-convert 2:foo:point, 0/variant
+  3:foo:point <- merge 0/variant, 34/xx, 35/xy
+  10:point, 20:bool <- maybe-convert 3:foo:point, 0/variant
 ]
 +mem: storing 1 in location 20
 +mem: storing 35 in location 11
@@ -565,8 +567,8 @@ container foo:_t [
   y:num
 ]
 def main [
-  10:foo:point <- merge 14, 15, 16
-  1:num <- get 10:foo, 1:offset
+  1:foo:point <- merge 14, 15, 16
+  10:num <- get 1:foo, 1:offset
 ]
 # todo: improve error message
 +error: illegal type "foo" seems to be missing a type ingredient or three while computing element type of container
@@ -586,7 +588,7 @@ def main [
 % Hide_errors = true;
 def foo [
   local-scope
-  x:adress:array:number <- copy 0  # typo
+  x:adress:array:number <- copy null  # typo
 ]
 # shouldn't crash
 
diff --git a/056shape_shifting_recipe.cc b/056shape_shifting_recipe.cc
index 0c97fcdd..68ae1e0a 100644
--- a/056shape_shifting_recipe.cc
+++ b/056shape_shifting_recipe.cc
@@ -3,7 +3,7 @@
 :(scenario shape_shifting_recipe)
 def main [
   10:point <- merge 14, 15
-  11:point <- foo 10:point
+  12:point <- foo 10:point
 ]
 # non-matching variant
 def foo a:num -> result:num [
@@ -17,8 +17,8 @@ def foo a:_t -> result:_t [
   load-ingredients
   result <- copy a
 ]
-+mem: storing 14 in location 11
-+mem: storing 15 in location 12
++mem: storing 14 in location 12
++mem: storing 15 in location 13
 
 //: Before anything else, disable transforms for shape-shifting recipes and
 //: make sure we never try to actually run a shape-shifting recipe. We should
@@ -538,7 +538,7 @@ void ensure_all_concrete_types(/*const*/ reagent& x, const recipe& exemplar) {
 :(scenario shape_shifting_recipe_2)
 def main [
   10:point <- merge 14, 15
-  11:point <- foo 10:point
+  12:point <- foo 10:point
 ]
 # non-matching shape-shifting variant
 def foo a:_t, b:_t -> result:num [
@@ -552,13 +552,13 @@ def foo a:_t -> result:_t [
   load-ingredients
   result <- copy a
 ]
-+mem: storing 14 in location 11
-+mem: storing 15 in location 12
++mem: storing 14 in location 12
++mem: storing 15 in location 13
 
 :(scenario shape_shifting_recipe_nonroot)
 def main [
   10:foo:point <- merge 14, 15, 16
-  20:point/raw <- bar 10:foo:point
+  20:point <- bar 10:foo:point
 ]
 # shape-shifting recipe with type ingredient following some other type
 def bar a:foo:_t -> result:_t [
@@ -592,7 +592,7 @@ def foo x:c:_bar:_baz [
 :(scenario shape_shifting_recipe_type_deduction_ignores_offsets)
 def main [
   10:foo:point <- merge 14, 15, 16
-  20:point/raw <- bar 10:foo:point
+  20:point <- bar 10:foo:point
 ]
 def bar a:foo:_t -> result:_t [
   local-scope
@@ -699,14 +699,14 @@ def main [
   1:&:point <- new point:type
   *1:&:point <- put *1:&:point, y:offset, 34
   3:&:point <- bar 1:&:point  # specialize _t to address:point
-  4:point <- copy *3:&:point
+  5:point <- copy *3:&:point
 ]
 def bar a:_t -> result:_t [
   local-scope
   load-ingredients
   result <- copy a
 ]
-+mem: storing 34 in location 5
++mem: storing 34 in location 6
 
 //: specializing a type ingredient with a compound type -- while *inside* another compound type
 :(scenario shape_shifting_recipe_supports_compound_types_2)
@@ -935,7 +935,7 @@ def foo x:_elem -> y:num [
 :(scenario specialize_most_similar_variant)
 def main [
   1:&:num <- new number:type
-  2:num <- foo 1:&:num
+  10:num <- foo 1:&:num
 ]
 def foo x:_elem -> y:num [
   local-scope
@@ -947,14 +947,14 @@ def foo x:&:_elem -> y:num [
   load-ingredients
   return 35
 ]
-+mem: storing 35 in location 2
++mem: storing 35 in location 10
 
 :(scenario specialize_most_similar_variant_2)
 # version with headers padded with lots of unrelated concrete types
 def main [
   1:num <- copy 23
   2:&:@:num <- copy null
-  3:num <- foo 2:&:@:num, 1:num
+  4:num <- foo 2:&:@:num, 1:num
 ]
 # variant with concrete type
 def foo dummy:&:@:num, x:num -> y:num, dummy:&:@:num [
@@ -969,7 +969,7 @@ def foo dummy:&:@:num, x:_elem -> y:num, dummy:&:@:num [
   return 35
 ]
 # prefer the concrete variant
-+mem: storing 34 in location 3
++mem: storing 34 in location 4
 
 :(scenario specialize_most_similar_variant_3)
 def main [
@@ -977,13 +977,13 @@ def main [
   foo 1:text
 ]
 def foo x:text [
-  2:num <- copy 34
+  10:num <- copy 34
 ]
 def foo x:&:_elem [
-  2:num <- copy 35
+  10:num <- copy 35
 ]
 # make sure the more precise version was used
-+mem: storing 34 in location 2
++mem: storing 34 in location 10
 
 :(scenario specialize_literal_as_number)
 def main [
diff --git a/058to_text.cc b/058to_text.cc
index 8c86e36c..9cb14e14 100644
--- a/058to_text.cc
+++ b/058to_text.cc
@@ -18,6 +18,7 @@ case TO_TEXT: {
 :(before "End Primitive Recipe Implementations")
 case TO_TEXT: {
   products.resize(1);
+  products.at(0).push_back(/*alloc id*/0);
   products.at(0).push_back(new_mu_text(inspect(current_instruction().ingredients.at(0), ingredients.at(0))));
   break;
 }
diff --git a/060rewrite_literal_string.cc b/060rewrite_literal_string.cc
index f4ed9b4c..95e38924 100644
--- a/060rewrite_literal_string.cc
+++ b/060rewrite_literal_string.cc
@@ -19,6 +19,7 @@ Transform.push_back(rewrite_literal_string_to_text);  // idempotent
 set<string> recipes_taking_literal_strings;
 :(code)
 void initialize_transform_rewrite_literal_string_to_text() {
+  recipes_taking_literal_strings.insert("assert");
   recipes_taking_literal_strings.insert("$print");
   recipes_taking_literal_strings.insert("$dump-trace");
   recipes_taking_literal_strings.insert("$system");
diff --git a/065duplex_list.mu b/065duplex_list.mu
index 7d369186..3a7de8f6 100644
--- a/065duplex_list.mu
+++ b/065duplex_list.mu
@@ -399,12 +399,17 @@ def remove-between start:&:duplex-list:_elem, end:&:duplex-list:_elem/contained-
   # start->next = end
   *next <- put *next, prev:offset, null
   *start <- put *start, next:offset, end
-  return-unless end
+  {
+    break-if end
+    stash [spliced:] next
+    return
+  }
   # end->prev->next = 0
   # end->prev = start
   prev:&:duplex-list:_elem <- get *end, prev:offset
   assert prev, [malformed duplex list - 2]
   *prev <- put *prev, next:offset, null
+  stash [spliced:] next
   *end <- put *end, prev:offset, start
 ]
 
@@ -437,6 +442,9 @@ scenario remove-range [
     12 <- 15
     20 <- 0
   ]
+  trace-should-contain [
+    app: spliced: 16 <-> 17 <-> 18
+  ]
 ]
 
 scenario remove-range-to-final [
@@ -472,6 +480,49 @@ scenario remove-range-to-final [
     12 <- 18
     20 <- 0  # no more elements
   ]
+  trace-should-contain [
+    app: spliced: 15 <-> 16 <-> 17
+  ]
+]
+
+scenario remove-range-to-penultimate [
+  local-scope
+  # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
+  list:&:duplex-list:num <- push 18, null
+  list <- push 17, list
+  list <- push 16, list
+  list <- push 15, list
+  list <- push 14, list
+  list <- push 13, list
+  run [
+    # delete 15 and 16
+    # start pointer: to the second element
+    list2:&:duplex-list:num <- next list
+    # end pointer: to the last (sixth) element
+    end:&:duplex-list:num <- next list2
+    end <- next end
+    end <- next end
+    remove-between list2, end
+    # now check the list
+    10:num/raw <- get *list, value:offset
+    list <- next list
+    11:num/raw <- get *list, value:offset
+    list <- next list
+    12:num/raw <- get *list, value:offset
+    list <- next list
+    13:num/raw <- get *list, value:offset
+    20:&:duplex-list:num/raw <- next list
+  ]
+  memory-should-contain [
+    10 <- 13
+    11 <- 14
+    12 <- 17
+    13 <- 18
+    20 <- 0  # no more elements
+  ]
+  trace-should-contain [
+    app: spliced: 15 <-> 16
+  ]
 ]
 
 scenario remove-range-empty [
diff --git a/069hash.cc b/069hash.cc
index 8a698d38..c810f98b 100644
--- a/069hash.cc
+++ b/069hash.cc
@@ -62,7 +62,7 @@ size_t hash_mu_address(size_t h, reagent& r) {
 }
 
 size_t hash_mu_text(size_t h, const reagent& r) {
-  string input = read_mu_text(get_or_insert(Memory, r.value));
+  string input = read_mu_text(get_or_insert(Memory, r.value+/*skip alloc id*/1));
   for (int i = 0;  i < SIZE(input);  ++i) {
     h = hash_iter(h, static_cast<size_t>(input.at(i)));
 //?     cerr << i << ": " << h << '\n';
@@ -319,11 +319,11 @@ def main [
 :(scenario hash_matches_old_version)
 def main [
   1:text <- new [abc]
-  2:num <- hash 1:text
-  3:num <- hash_old 1:text
-  4:bool <- equal 2:num, 3:num
+  3:num <- hash 1:text
+  4:num <- hash_old 1:text
+  5:bool <- equal 3:num, 4:num
 ]
-+mem: storing 1 in location 4
++mem: storing 1 in location 5
 
 :(before "End Primitive Recipe Declarations")
 HASH_OLD,
@@ -343,7 +343,7 @@ case HASH_OLD: {
 }
 :(before "End Primitive Recipe Implementations")
 case HASH_OLD: {
-  string input = read_mu_text(ingredients.at(0).at(0));
+  string input = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
   size_t h = 0 ;
 
   for (int i = 0;  i < SIZE(input);  ++i) {
diff --git a/074wait.cc b/074wait.cc
index eb17c8aa..aa3b59af 100644
--- a/074wait.cc
+++ b/074wait.cc
@@ -262,22 +262,20 @@ def main [
 :(scenario get_location_indirect)
 # 'get-location' can read from container address
 def main [
-  1:num <- copy 10
-  10:num <- copy 34
-  11:num <- copy 35
-  4:location <- get-location 1:&:point/lookup, 0:offset
+  1:num/alloc-id, 2:num <- copy 0, 10
+  10:num/alloc-id, 11:num/x, 12:num/y <- copy 0, 34, 35
+  20:location <- get-location 1:&:point/lookup, 0:offset
 ]
-+mem: storing 10 in location 4
++mem: storing 11 in location 20
 
 :(scenario get_location_indirect_2)
 def main [
-  1:num <- copy 10
-  10:num <- copy 34
-  11:num <- copy 35
-  4:&:num <- copy 20/unsafe
+  1:num/alloc-id, 2:num <- copy 0, 10
+  10:num/alloc-id, 11:num/x, 12:num/y <- copy 0, 34, 35
+  4:num/alloc-id, 5:num <- copy 0, 20
   4:&:location/lookup <- get-location 1:&:point/lookup, 0:offset
 ]
-+mem: storing 10 in location 20
++mem: storing 11 in location 21
 
 //: allow waiting on a routine to complete
 
diff --git a/082scenario_screen.cc b/082scenario_screen.cc
index 31cbfcc9..dc015fed 100644
--- a/082scenario_screen.cc
+++ b/082scenario_screen.cc
@@ -145,8 +145,14 @@ assert(Next_predefined_global_for_scenarios < Reserved_for_tests);
 
 :(before "End Globals")
 // Scenario Globals.
-extern const int SCREEN = Next_predefined_global_for_scenarios++;
+extern const int SCREEN = next_predefined_global_for_scenarios(/*size_of(address:screen)*/2);
 // End Scenario Globals.
+:(code)
+int next_predefined_global_for_scenarios(int size) {
+  int result = Next_predefined_global_for_scenarios;
+  Next_predefined_global_for_scenarios += size;
+  return result;
+}
 
 //: give 'screen' a fixed location in scenarios
 :(before "End Special Scenario Variable Names(r)")
@@ -250,19 +256,27 @@ struct raw_string_stream {
 
 :(code)
 void check_screen(const string& expected_contents, const int color) {
-  int screen_location = get_or_insert(Memory, SCREEN);
-  int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
-  assert(data_offset >= 0);
-  int screen_data_location = screen_location+data_offset;  // type: address:array:character
-  int screen_data_start = get_or_insert(Memory, screen_data_location);  // type: array:character
-  int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
-  int screen_width = get_or_insert(Memory, screen_location+width_offset);
-  int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
-  int screen_height = get_or_insert(Memory, screen_location+height_offset);
+  int screen_location = get_or_insert(Memory, SCREEN+/*skip address alloc id*/1) + /*skip payload alloc id*/1;
+  reagent screen("x:screen");  // just to ensure screen.type is reclaimed
+  int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen");  // type: address:array:character
+  assert(screen_data_location >= 0);
+//?   cerr << "screen data is at location " << screen_data_location << '\n';
+  int screen_data_start = get_or_insert(Memory, screen_data_location+/*skip address alloc id*/1) + /*skip payload alloc id*/1;  // type: array:character
+//?   cerr << "screen data start is at " << screen_data_start << '\n';
+  int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen");
+//?   cerr << "screen width is at location " << screen_width_location << '\n';
+  int screen_width = get_or_insert(Memory, screen_width_location);
+//?   cerr << "screen width: " << screen_width << '\n';
+  int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen");
+//?   cerr << "screen height is at location " << screen_height_location << '\n';
+  int screen_height = get_or_insert(Memory, screen_height_location);
+//?   cerr << "screen height: " << screen_height << '\n';
+  int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen");
+//?   cerr << "top of screen is at location " << top_index_location << '\n';
+  int top_index = get_or_insert(Memory, top_index_location);
+//?   cerr << "top of screen is index " << top_index << '\n';
   raw_string_stream cursor(expected_contents);
   // todo: too-long expected_contents should fail
-  int top_index_offset = find_element_name(get(Type_ordinal, "screen"), "top-idx", "");
-  int top_index = get_or_insert(Memory, screen_location+top_index_offset);
   for (int i=0, row=top_index/screen_width;  i < screen_height;  ++i, row=(row+1)%screen_height) {
     cursor.skip_whitespace_and_comments();
     if (cursor.at_end()) break;
@@ -385,18 +399,25 @@ case _DUMP_SCREEN: {
 
 :(code)
 void dump_screen() {
-  int screen_location = get_or_insert(Memory, SCREEN);
-  int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
-  int screen_width = get_or_insert(Memory, screen_location+width_offset);
-  int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
-  int screen_height = get_or_insert(Memory, screen_location+height_offset);
-  int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
-  assert(data_offset >= 0);
-  int screen_data_location = screen_location+data_offset;  // type: address:array:character
-  int screen_data_start = get_or_insert(Memory, screen_data_location);  // type: array:character
-  assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height);
-  int top_index_offset = find_element_name(get(Type_ordinal, "screen"), "top-idx", "");
-  int top_index = get_or_insert(Memory, screen_location+top_index_offset);
+  int screen_location = get_or_insert(Memory, SCREEN+/*skip address alloc id*/1) + /*skip payload alloc id*/1;
+  reagent screen("x:screen");  // just to ensure screen.type is reclaimed
+  int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen");  // type: address:array:character
+  assert(screen_data_location >= 0);
+//?   cerr << "screen data is at location " << screen_data_location << '\n';
+  int screen_data_start = get_or_insert(Memory, screen_data_location+/*skip address alloc id*/1) + /*skip payload alloc id*/1;  // type: array:character
+//?   cerr << "screen data start is at " << screen_data_start << '\n';
+  int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen");
+//?   cerr << "screen width is at location " << screen_width_location << '\n';
+  int screen_width = get_or_insert(Memory, screen_width_location);
+//?   cerr << "screen width: " << screen_width << '\n';
+  int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen");
+//?   cerr << "screen height is at location " << screen_height_location << '\n';
+  int screen_height = get_or_insert(Memory, screen_height_location);
+//?   cerr << "screen height: " << screen_height << '\n';
+  int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen");
+//?   cerr << "top of screen is at location " << top_index_location << '\n';
+  int top_index = get_or_insert(Memory, top_index_location);
+//?   cerr << "top of screen is index " << top_index << '\n';
   for (int i=0, row=top_index/screen_width;  i < screen_height;  ++i, row=(row+1)%screen_height) {
     cerr << '.';
     int curr = screen_data_start+/*length*/1+row*screen_width* /*size of screen-cell*/2;
diff --git a/085scenario_console.cc b/085scenario_console.cc
index 2c3ab4bc..31aa4fe7 100644
--- a/085scenario_console.cc
+++ b/085scenario_console.cc
@@ -34,7 +34,7 @@ scenario keyboard-in-scenario [
 ]
 
 :(before "End Scenario Globals")
-extern const int CONSOLE = Next_predefined_global_for_scenarios++;
+extern const int CONSOLE = next_predefined_global_for_scenarios(/*size_of(address:console)*/2);
 //: give 'console' a fixed location in scenarios
 :(before "End Special Scenario Variable Names(r)")
 Name[r]["console"] = CONSOLE;
@@ -61,8 +61,8 @@ case ASSUME_CONSOLE: {
   int size = /*length*/1 + num_events*size_of_event();
   int event_data_address = allocate(size);
   // store length
-  put(Memory, event_data_address, num_events);
-  int curr_address = event_data_address + /*skip length*/1;
+  put(Memory, event_data_address+/*skip alloc id*/1, num_events);
+  int curr_address = event_data_address + /*skip alloc id*/1 + /*skip length*/1;
   for (int i = 0;  i < SIZE(r.steps);  ++i) {
     const instruction& inst = r.steps.at(i);
     if (inst.name == "left-click") {
@@ -113,13 +113,13 @@ case ASSUME_CONSOLE: {
       }
     }
   }
-  assert(curr_address == event_data_address+size);
+  assert(curr_address == event_data_address+/*skip alloc id*/1+size);
   // wrap the array of events in a console object
   int console_address = allocate(size_of_console());
   trace("mem") << "storing console in " << console_address << end();
-  put(Memory, CONSOLE, console_address);
+  put(Memory, CONSOLE+/*skip alloc id*/1, console_address);
   trace("mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end();
-  put(Memory, console_address+/*offset of 'data' in container 'events'*/1, event_data_address);
+  put(Memory, console_address+/*skip alloc id*/1+/*offset of 'data' in container 'events'*/1+/*skip alloc id of 'data'*/1, event_data_address);
   break;
 }
 
diff --git a/087file.cc b/087file.cc
index 44da9b02..9fd056db 100644
--- a/087file.cc
+++ b/087file.cc
@@ -35,7 +35,7 @@ case _OPEN_FILE_FOR_READING: {
 }
 :(before "End Primitive Recipe Implementations")
 case _OPEN_FILE_FOR_READING: {
-  string filename = read_mu_text(ingredients.at(0).at(0));
+  string filename = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
   assert(sizeof(long long int) >= sizeof(FILE*));
   FILE* f = fopen(filename.c_str(), "r");
   long long int result = reinterpret_cast<long long int>(f);
@@ -70,7 +70,7 @@ case _OPEN_FILE_FOR_WRITING: {
 }
 :(before "End Primitive Recipe Implementations")
 case _OPEN_FILE_FOR_WRITING: {
-  string filename = read_mu_text(ingredients.at(0).at(0));
+  string filename = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
   assert(sizeof(long long int) >= sizeof(FILE*));
   long long int result = reinterpret_cast<long long int>(fopen(filename.c_str(), "w"));
   products.resize(1);
diff --git a/089scenario_filesystem.cc b/089scenario_filesystem.cc
index f14534ac..bacb61be 100644
--- a/089scenario_filesystem.cc
+++ b/089scenario_filesystem.cc
@@ -71,7 +71,7 @@ scenario escaping-file-contents [
 ]
 
 :(before "End Globals")
-extern const int RESOURCES = Next_predefined_global_for_scenarios++;
+extern const int RESOURCES = next_predefined_global_for_scenarios(/*size_of(address:resources)*/2);
 //: give 'resources' a fixed location in scenarios
 :(before "End Special Scenario Variable Names(r)")
 Name[r]["resources"] = RESOURCES;
@@ -203,26 +203,28 @@ string munge_resources_contents(const string& data, const string& filename, cons
 }
 
 void construct_resources_object(const map<string, string>& contents) {
-  int resources_data_address = allocate(SIZE(contents)*2 + /*array length*/1);
-  int curr = resources_data_address + /*skip length*/1;
+  int resources_data_address = allocate(SIZE(contents) * /*size of resource*/4 + /*array length*/1);
+  int curr = resources_data_address + /*skip alloc id*/1 + /*skip array length*/1;
   for (map<string, string>::const_iterator p = contents.begin();  p != contents.end();  ++p) {
+    ++curr;  // skip alloc id of resource.name
     put(Memory, curr, new_mu_text(p->first));
     trace("mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end();
     ++curr;
+    ++curr;  // skip alloc id of resource.contents
     put(Memory, curr, new_mu_text(p->second));
     trace("mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end();
     ++curr;
   }
-  curr = resources_data_address;
-  put(Memory, curr, SIZE(contents));  // size of array
+  curr = resources_data_address + /*skip alloc id of resources.data*/1;
+  put(Memory, curr, SIZE(contents));  // array length
   trace("mem") << "storing resources size " << get(Memory, curr) << " in location " << curr << end();
   // wrap the resources data in a 'resources' object
   int resources_address = allocate(size_of_resources());
-  curr = resources_address+/*offset of 'data' element*/1;
+  curr = resources_address+/*alloc id*/1+/*offset of 'data' element*/1+/*skip alloc id of 'data' element*/1;
   put(Memory, curr, resources_data_address);
   trace("mem") << "storing resources data address " << resources_data_address << " in location " << curr << end();
   // save in product
-  put(Memory, RESOURCES, resources_address);
+  put(Memory, RESOURCES+/*skip alloc id*/1, resources_address);
   trace("mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end();
 }
 
diff --git a/091socket.cc b/091socket.cc
index 7b6ca5b1..a0f3b948 100644
--- a/091socket.cc
+++ b/091socket.cc
@@ -40,7 +40,7 @@ case _OPEN_CLIENT_SOCKET: {
 }
 :(before "End Primitive Recipe Implementations")
 case _OPEN_CLIENT_SOCKET: {
-  string host = read_mu_text(ingredients.at(0).at(0));
+  string host = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
   int port = ingredients.at(1).at(0);
   socket_t* client = client_socket(host, port);
   products.resize(1);
diff --git a/101run_sandboxed.cc b/101run_sandboxed.cc
index a0b827e9..f7c7522f 100644
--- a/101run_sandboxed.cc
+++ b/101run_sandboxed.cc
@@ -3,20 +3,22 @@
 
 :(scenario run_interactive_code)
 def main [
-  1:num <- copy 0
-  2:text <- new [1:num/raw <- copy 34]
-  run-sandboxed 2:text
-  3:num <- copy 1:num
+  1:num <- copy 0  # reserve space for the sandbox
+  10:text <- new [1:num/raw <- copy 34]
+#?   $print 10:num [|] 11:num [: ] 1000:num [|] *10:text [ (] 10:text [)] 10/newline
+  run-sandboxed 10:text
+  20:num <- copy 1:num
 ]
-+mem: storing 34 in location 3
++mem: storing 34 in location 20
 
 :(scenario run_interactive_empty)
 def main [
-  1:text <- copy 0/unsafe
-  2:text <- run-sandboxed 1:text
+  10:text <- copy null
+  20:text <- run-sandboxed 10:text
 ]
 # result is null
-+mem: storing 0 in location 2
++mem: storing 0 in location 20
++mem: storing 0 in location 21
 
 //: As the name suggests, 'run-sandboxed' will prevent certain operations that
 //: regular Mu code can perform.
@@ -52,12 +54,16 @@ case RUN_SANDBOXED: {
 }
 :(before "End Primitive Recipe Implementations")
 case RUN_SANDBOXED: {
-  bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
+  bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(/*skip alloc id*/1));
   if (!new_code_pushed_to_stack) {
     products.resize(5);
+    products.at(0).push_back(/*alloc id*/0);
     products.at(0).push_back(0);
+    products.at(1).push_back(/*alloc id*/0);
     products.at(1).push_back(trace_error_contents());
+    products.at(2).push_back(/*alloc id*/0);
     products.at(2).push_back(0);
+    products.at(3).push_back(/*alloc id*/0);
     products.at(3).push_back(trace_app_contents());
     products.at(4).push_back(1);  // completed
     run_code_end();
@@ -90,6 +96,7 @@ string Save_trace_file;
 // all errors.
 // returns true if successfully called (no errors found during load and transform)
 bool run_interactive(int address) {
+//?   cerr << "run_interactive: " << address << '\n';
   assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0);
   // try to sandbox the run as best you can
   // todo: test this
@@ -98,6 +105,7 @@ bool run_interactive(int address) {
       Memory.erase(i);
   }
   string command = trim(strip_comments(read_mu_text(address)));
+//?   cerr << "command: " << command << '\n';
   Name[get(Recipe_ordinal, "interactive")].clear();
   run_code_begin(/*should_stash_snapshots*/true);
   if (command.empty()) return false;
@@ -213,15 +221,20 @@ load(string(
 "]\n" +
 "recipe sandbox [\n" +
   "local-scope\n" +
+//?   "$print [aaa] 10/newline\n" +
   "screen:&:screen <- new-fake-screen 30, 5\n" +
   "routine-id:num <- start-running interactive, screen\n" +
   "limit-time routine-id, 100000/instructions\n" +
   "wait-for-routine routine-id\n" +
+//?   "$print [bbb] 10/newline\n" +
   "instructions-run:num <- number-of-instructions routine-id\n" +
   "stash instructions-run [instructions run]\n" +
   "sandbox-state:num <- routine-state routine-id\n" +
   "completed?:bool <- equal sandbox-state, 1/completed\n" +
+//?   "$print [completed: ] completed? 10/newline\n" +
   "output:text <- $most-recent-products\n" +
+//?   "$print [zzz] 10/newline\n" +
+//?   "$print output\n" +
   "errors:text <- save-errors\n" +
   "stashes:text <- save-app-trace\n" +
   "$cleanup-run-sandboxed\n" +
@@ -281,6 +294,7 @@ case _MOST_RECENT_PRODUCTS: {
 :(before "End Primitive Recipe Implementations")
 case _MOST_RECENT_PRODUCTS: {
   products.resize(1);
+  products.at(0).push_back(/*alloc id*/0);
   products.at(0).push_back(new_mu_text(Most_recent_products));
   break;
 }
@@ -296,6 +310,7 @@ case SAVE_ERRORS: {
 :(before "End Primitive Recipe Implementations")
 case SAVE_ERRORS: {
   products.resize(1);
+  products.at(0).push_back(/*alloc id*/0);
   products.at(0).push_back(trace_error_contents());
   break;
 }
@@ -311,6 +326,7 @@ case SAVE_APP_TRACE: {
 :(before "End Primitive Recipe Implementations")
 case SAVE_APP_TRACE: {
   products.resize(1);
+  products.at(0).push_back(/*alloc id*/0);
   products.at(0).push_back(trace_app_contents());
   break;
 }
@@ -332,64 +348,64 @@ case _CLEANUP_RUN_SANDBOXED: {
 :(scenario "run_interactive_converts_result_to_text")
 def main [
   # try to interactively add 2 and 2
-  1:text <- new [add 2, 2]
-  2:text <- run-sandboxed 1:text
-  10:@:char <- copy *2:text
+  10:text <- new [add 2, 2]
+  20:text <- run-sandboxed 10:text
+  30:@:char <- copy *20:text
 ]
 # first letter in the output should be '4' in unicode
-+mem: storing 52 in location 11
++mem: storing 52 in location 31
 
 :(scenario "run_interactive_ignores_products_in_nested_functions")
 def main [
-  1:text <- new [foo]
-  2:text <- run-sandboxed 1:text
-  10:@:char <- copy *2:text
+  10:text <- new [foo]
+  20:text <- run-sandboxed 10:text
+  30:@:char <- copy *20:text
 ]
 def foo [
-  20:num <- copy 1234
+  40:num <- copy 1234
   {
     break
     reply 5678
   }
 ]
 # no product should have been tracked
-+mem: storing 0 in location 10
++mem: storing 0 in location 30
 
 :(scenario "run_interactive_ignores_products_in_previous_instructions")
 def main [
-  1:text <- new [
+  10:text <- new [
     add 1, 1  # generates a product
     foo]  # no products
-  2:text <- run-sandboxed 1:text
-  10:@:char <- copy *2:text
+  20:text <- run-sandboxed 10:text
+  30:@:char <- copy *20:text
 ]
 def foo [
-  20:num <- copy 1234
+  40:num <- copy 1234
   {
     break
     reply 5678
   }
 ]
 # no product should have been tracked
-+mem: storing 0 in location 10
++mem: storing 0 in location 30
 
 :(scenario "run_interactive_remembers_products_before_final_label")
 def main [
-  1:text <- new [
+  10:text <- new [
     add 1, 1  # generates a product
     +foo]  # no products
-  2:text <- run-sandboxed 1:text
-  10:@:char <- copy *2:text
+  20:text <- run-sandboxed 10:text
+  30:@:char <- copy *20:text
 ]
 def foo [
-  20:num <- copy 1234
+  40:num <- copy 1234
   {
     break
     reply 5678
   }
 ]
 # product tracked
-+mem: storing 50 in location 11
++mem: storing 50 in location 31
 
 :(scenario "run_interactive_returns_text")
 def main [
@@ -399,38 +415,41 @@ def main [
     y:text <- new [b]
     z:text <- append x:text, y:text
   ]
-  2:text <- run-sandboxed 1:text
-  10:@:char <- copy *2:text
+  10:text <- run-sandboxed 1:text
+#?   $print 10:text 10/newline
+  20:@:char <- copy *10:text
 ]
 # output contains "ab"
-+mem: storing 97 in location 11
-+mem: storing 98 in location 12
++mem: storing 97 in location 21
++mem: storing 98 in location 22
 
 :(scenario "run_interactive_returns_errors")
 def main [
   # run a command that generates an error
-  1:text <- new [x:num <- copy 34
+  10:text <- new [x:num <- copy 34
 get x:num, foo:offset]
-  2:text, 3:text <- run-sandboxed 1:text
-  10:@:char <- copy *3:text
+  20:text, 30:text <- run-sandboxed 10:text
+  40:@:char <- copy *30:text
 ]
 # error should be "unknown element foo in container number"
-+mem: storing 117 in location 11
-+mem: storing 110 in location 12
-+mem: storing 107 in location 13
-+mem: storing 110 in location 14
++mem: storing 117 in location 41
++mem: storing 110 in location 42
++mem: storing 107 in location 43
++mem: storing 110 in location 44
 # ...
 
 :(scenario run_interactive_with_comment)
 def main [
   # 2 instructions, with a comment after the first
-  1:&:@:num <- new [a:num <- copy 0  # abc
+  10:text <- new [a:num <- copy 0  # abc
 b:num <- copy 0
 ]
-  2:text, 3:text <- run-sandboxed 1:text
+  20:text, 30:text <- run-sandboxed 10:text
 ]
 # no errors
-+mem: storing 0 in location 3
+# skip alloc id
++mem: storing 0 in location 30
++mem: storing 0 in location 31
 
 :(after "Running One Instruction")
 if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at
@@ -441,6 +460,7 @@ if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_
 :(before "End Running One Instruction")
 if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at) {
   Most_recent_products = track_most_recent_products(current_instruction(), products);
+//?   cerr << "most recent products: " << Most_recent_products << '\n';
 }
 :(code)
 string track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) {
@@ -458,8 +478,8 @@ string track_most_recent_products(const instruction& instruction, const vector<v
     //    => abc
     if (i < SIZE(instruction.products)) {
       if (is_mu_text(instruction.products.at(i))) {
-        if (!scalar(products.at(i))) continue;  // error handled elsewhere
-        out << read_mu_text(products.at(i).at(0)) << '\n';
+        if (SIZE(products.at(i)) != 2) continue;  // weak silent check for address
+        out << read_mu_text(products.at(i).at(/*skip alloc id*/1)) << '\n';
         continue;
       }
     }
@@ -551,7 +571,7 @@ case RELOAD: {
 :(before "End Primitive Recipe Implementations")
 case RELOAD: {
   restore_non_recipe_snapshots();
-  string code = read_mu_text(ingredients.at(0).at(0));
+  string code = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
   run_code_begin(/*should_stash_snapshots*/false);
   routine* save_current_routine = Current_routine;
   Current_routine = NULL;
@@ -562,17 +582,30 @@ case RELOAD: {
   Sandbox_mode = false;
   Current_routine = save_current_routine;
   products.resize(1);
+  products.at(0).push_back(/*alloc id*/0);
   products.at(0).push_back(trace_error_contents());
   run_code_end();  // wait until we're done with the trace contents
   break;
 }
 
+:(scenario reload_loads_function_definitions)
+def main [
+  local-scope
+  x:text <- new [recipe foo [
+    1:num/raw <- copy 34
+  ]]
+  reload x
+  run-sandboxed [foo]
+  2:num/raw <- copy 1:num/raw
+]
++mem: storing 34 in location 2
+
 :(scenario reload_continues_past_error)
 def main [
   local-scope
   x:text <- new [recipe foo [
-  get 1234:num, foo:offset
-]]
+    get 1234:num, foo:offset
+  ]]
   reload x
   1:num/raw <- copy 34
 ]
@@ -593,9 +626,11 @@ def main [
     ]
   ]
   # save warning addresses in locations of type 'number' to avoid spurious changes to them due to 'abandon'
-  1:num/raw <- reload x
-  2:num/raw <- reload x
+  10:text/raw <- reload x
+  20:text/raw <- reload x
 ]
 # no errors on either load
-+mem: storing 0 in location 1
-+mem: storing 0 in location 2
++mem: storing 0 in location 10
++mem: storing 0 in location 11
++mem: storing 0 in location 20
++mem: storing 0 in location 21
diff --git a/Readme.md b/Readme.md
index 4e2b98c4..28784b30 100644
--- a/Readme.md
+++ b/Readme.md
@@ -336,9 +336,9 @@ channels.
 
 Routines are expected to communicate purely by message passing, though nothing
 stops them from sharing memory since all routines share a common address
-space. However, idiomatic Mu will make it hard to accidentally read or clobber
-random memory locations. Bounds checking is baked deeply into the semantics,
-and pointers can never be invalidated.
+space. However, idiomatic Mu will make it hard to accidentally read or
+clobber random memory locations. Bounds checking is baked deeply into
+the semantics, and using pointers after freeing them immediately fails.
 
 ---
 
diff --git a/edit/001-editor.mu b/edit/001-editor.mu
index 963bb1cf..b3399dbb 100644
--- a/edit/001-editor.mu
+++ b/edit/001-editor.mu
@@ -81,18 +81,20 @@ scenario editor-initializes-without-data [
   assume-screen 5/width, 3/height
   run [
     e:&:editor <- new-editor null/data, 2/left, 5/right
-    2:editor/raw <- copy *e
+    1:editor/raw <- copy *e
   ]
   memory-should-contain [
-    # 2 (data) <- just the § sentinel
-    # 3 (top of screen) <- the § sentinel
-    4 <- 0  # bottom-of-screen; null since text fits on screen
-    # 5 (before cursor) <- the § sentinel
-    6 <- 2  # left
-    7 <- 4  # right  (inclusive)
-    8 <- 0  # bottom (not set until render)
-    9 <- 1  # cursor row
-    10 <- 2  # cursor column
+    # 1,2 (data) <- just the § sentinel
+    # 3,4 (top of screen) <- the § sentinel
+    # 5 (bottom of screen) <- null since text fits on screen
+    5 <- 0
+    6 <- 0
+    # 7,8 (before cursor) <- the § sentinel
+    9 <- 2  # left
+    10 <- 4  # right  (inclusive)
+    11 <- 0  # bottom (not set until render)
+    12 <- 1  # cursor row
+    13 <- 2  # cursor column
   ]
   screen-should-contain [
     .     .
diff --git a/exception1.mu b/exception1.mu
index dd60c744..df4754e5 100644
--- a/exception1.mu
+++ b/exception1.mu
@@ -57,5 +57,5 @@ def f [
     return-continuation-until-mark 999/exception-tag, [error will robinson!], 0/unused
   }
   # normal return: 3 results including 0 continuation placeholder at start
-  return 0/continuation-placeholder, 0/no-error, 34/regular-result
+  return 0/continuation-placeholder, null/no-error, 34/regular-result
 ]
diff --git a/index.html b/index.html
index 88e9b14a..dde93753 100644
--- a/index.html
+++ b/index.html
@@ -158,18 +158,16 @@ for gradually constructing long strings in a piecemeal fashion.
 space at run-time as pointers or <em>addresses</em>. All Mu instructions can
 dereference or <a href='html/035lookup.cc.html'><em>lookup</em></a> addresses
 of values in addition to operating on regular values. These addresses are
-manually managed like C. However, all allocations are transparently
-reference-counted or <a href='html/036refcount.cc.html'><em>refcounted</em></a>,
-with every copy of a pointer updating refcounts appropriately. When the
-refcount of an allocation drops to zero it is transparently <a href='html/037abandon.cc.html'>reclaimed</a>
-and made available to future allocations. By construction it is impossible to
-reclaim memory prematurely, while some other part of a program is still
-pointing to it. This eliminates a whole class of undefined behavior and
-security vulnerabilities that plague C. Compared to Rust, Mu pays some
-additional runtime cost in exchange for C-like flexibility (you can copy
-addresses around all you like, and write from any copy of an address) and
-simpler implementation (no static analysis). Mu by convention abbreviates type
-<tt>address</tt> to <tt>&amp;</tt>.
+manually managed like C, and can be reclaimed using the <a href='html/037abandon.cc.html'><tt>abandon</tt></a>
+instruction. To ensure that stale addresses aren't used after being
+abandoned/reused, each allocation gets a unique <em>alloc id</em> that is also
+stored in the address returned. The lookup operation ensures that the alloc id
+of an address matches that of its payload. This eliminates a whole class of
+undefined behavior and security vulnerabilities that plague C. Compared to
+Rust, Mu pays some additional runtime cost in exchange for C-like flexibility
+(you can copy addresses around all you like, and write from any copy of an
+address) and simpler implementation (no static analysis). Mu by convention
+abbreviates type <tt>address</tt> to <tt>&amp;</tt>.
 
 <p/>Support for higher-order recipes that can pass <a href='html/072recipe.cc.html'>recipes</a>
 around like any other value.
diff --git a/sandbox/001-editor.mu b/sandbox/001-editor.mu
index 963bb1cf..b3399dbb 100644
--- a/sandbox/001-editor.mu
+++ b/sandbox/001-editor.mu
@@ -81,18 +81,20 @@ scenario editor-initializes-without-data [
   assume-screen 5/width, 3/height
   run [
     e:&:editor <- new-editor null/data, 2/left, 5/right
-    2:editor/raw <- copy *e
+    1:editor/raw <- copy *e
   ]
   memory-should-contain [
-    # 2 (data) <- just the § sentinel
-    # 3 (top of screen) <- the § sentinel
-    4 <- 0  # bottom-of-screen; null since text fits on screen
-    # 5 (before cursor) <- the § sentinel
-    6 <- 2  # left
-    7 <- 4  # right  (inclusive)
-    8 <- 0  # bottom (not set until render)
-    9 <- 1  # cursor row
-    10 <- 2  # cursor column
+    # 1,2 (data) <- just the § sentinel
+    # 3,4 (top of screen) <- the § sentinel
+    # 5 (bottom of screen) <- null since text fits on screen
+    5 <- 0
+    6 <- 0
+    # 7,8 (before cursor) <- the § sentinel
+    9 <- 2  # left
+    10 <- 4  # right  (inclusive)
+    11 <- 0  # bottom (not set until render)
+    12 <- 1  # cursor row
+    13 <- 2  # cursor column
   ]
   screen-should-contain [
     .     .