about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2018-06-15 22:16:09 -0700
committerKartik Agaram <vc@akkartik.com>2018-06-15 22:16:09 -0700
commitce9b2b0515eaf92a9c68c8608fd9bf392c941d50 (patch)
treeeb1899f86308c712e54ef94a1c85243c26621c45
parent0edd9b9fc60440213e4df926ea511419ee291f1e (diff)
downloadmu-ce9b2b0515eaf92a9c68c8608fd9bf392c941d50.tar.gz
4258 - undo 4257
-rw-r--r--020run.cc14
-rw-r--r--021check_instruction.cc25
-rw-r--r--022arithmetic.cc15
-rw-r--r--023boolean.cc10
-rw-r--r--024jump.cc21
-rw-r--r--025compare.cc57
-rw-r--r--027call_ingredient.cc17
-rw-r--r--028call_return.cc4
-rw-r--r--030container.cc49
-rw-r--r--032array.cc68
-rw-r--r--033exclusive_container.cc8
-rw-r--r--034address.cc85
-rw-r--r--035lookup.cc106
-rw-r--r--037abandon.cc36
-rw-r--r--038new_text.cc72
-rw-r--r--042name.cc38
-rw-r--r--043space.cc69
-rw-r--r--044space_surround.cc31
-rw-r--r--045closure_name.cc8
-rw-r--r--046check_type_by_name.cc18
-rw-r--r--050scenario.cc1
-rw-r--r--053recipe_header.cc3
-rw-r--r--055shape_shifting_container.cc14
-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.cc17
-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.cc111
-rw-r--r--edit/001-editor.mu22
-rw-r--r--edit/002-typing.mu4
-rw-r--r--edit/003-shortcuts.mu23
-rw-r--r--index.html22
38 files changed, 381 insertions, 757 deletions
diff --git a/020run.cc b/020run.cc
index 0bba6bb2..3739ad2c 100644
--- a/020run.cc
+++ b/020run.cc
@@ -87,12 +87,6 @@ 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
-          if (is_mu_address(current_instruction().products.at(i)) && is_mu_scalar(current_instruction().ingredients.at(i)))
-            products.at(i).insert(products.at(i).begin(), /*alloc id*/0);
-        }
         break;
       }
       // End Primitive Recipe Implementations
@@ -202,7 +196,7 @@ if (argc > 1) {
 }
 transform_all();
 //? cerr << to_original_string(get(Type_ordinal, "editor")) << '\n';
-//? cerr << to_original_string(get(Recipe, get(Recipe_ordinal, "handle-keyboard-event"))) << '\n';
+//? cerr << to_original_string(get(Recipe, get(Recipe_ordinal, "event-loop"))) << '\n';
 //? DUMP("");
 //? exit(0);
 if (trace_contains_errors()) {
@@ -347,9 +341,8 @@ void write_memory(reagent/*copy*/ x, const vector<double>& data) {
 }
 
 :(code)
-int size_of(reagent/*copy*/ r) {
+int size_of(const reagent& r) {
   if (!r.type) return 0;
-  // Begin size_of(reagent r) Special-cases
   // End size_of(reagent r) Special-cases
   return size_of(r.type);
 }
@@ -358,7 +351,6 @@ int size_of(const type_tree* type) {
   if (type->atom) {
     if (type->value == -1) return 1;  // error value, but we'll raise it elsewhere
     if (type->value == 0) return 1;
-//?     if (type->value == Address_type_ordinal) return 2;  // address and alloc id
     // End size_of(type) Atom Special-cases
   }
   else {
@@ -366,7 +358,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 2;  // address and alloc id
+    if (type->left->value == Address_type_ordinal) return 1;
     // End size_of(type) Non-atom Special-cases
   }
   // End size_of(type) Special-cases
diff --git a/021check_instruction.cc b/021check_instruction.cc
index 4bba31e5..d7628008 100644
--- a/021check_instruction.cc
+++ b/021check_instruction.cc
@@ -112,9 +112,8 @@ def main [
 
 :(code)
 // types_match with some leniency
-bool types_coercible(reagent/*copy*/ to, reagent/*copy*/ from) {
-  // Begin types_coercible(reagent to, reagent from)
-  if (types_match_sub(to, from)) return true;
+bool types_coercible(const reagent& to, const reagent& from) {
+  if (types_match(to, from)) return true;
   if (is_mu_address(from) && is_real_mu_number(to)) return true;
   if (is_mu_boolean(from) && is_real_mu_number(to)) return true;
   if (is_real_mu_number(from) && is_mu_character(to)) return true;
@@ -122,11 +121,10 @@ bool types_coercible(reagent/*copy*/ to, reagent/*copy*/ from) {
   return false;
 }
 
-bool types_match_sub(const reagent& to, const reagent& from) {
+bool types_match(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;
-
   if (is_literal(from)) {
     if (is_mu_array(to)) return false;
     // End Matching Types For Literal(to)
@@ -136,16 +134,12 @@ bool types_match_sub(const reagent& to, const reagent& from) {
     if (is_mu_boolean(to)) return from.name == "0" || from.name == "1";
     return size_of(to) == 1;  // literals are always scalars
   }
-  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);
+  return types_strictly_match(to, from);
 }
 
 //: copy arguments for later layers
-bool types_strictly_match_sub(const reagent& to, const reagent& from) {
+bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) {
+  // End Preprocess types_strictly_match(reagent to, 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.
@@ -156,11 +150,6 @@ bool types_strictly_match_sub(const reagent& to, const reagent& 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;
@@ -279,7 +268,7 @@ bool is_mu_scalar(reagent/*copy*/ r) {
 }
 bool is_mu_scalar(const type_tree* type) {
   if (!type) return false;
-  if (is_mu_address(type)) return false;
+  if (is_mu_address(type)) return true;
   if (!type->atom) return false;
   if (is_literal(type))
     return type->name != "literal-string";
diff --git a/022arithmetic.cc b/022arithmetic.cc
index 50a54578..530541aa 100644
--- a/022arithmetic.cc
+++ b/022arithmetic.cc
@@ -95,25 +95,18 @@ case SUBTRACT: {
   }
   break;
 }
-:(code)
-bool is_raw(const reagent& r) {
-  return has_property(r, "raw");
-}
-
 :(before "End Primitive Recipe Implementations")
 case SUBTRACT: {
-  double result = scalar_ingredient(ingredients, 0);
+  double result = ingredients.at(0).at(0);
   for (int i = 1;  i < SIZE(ingredients);  ++i)
-    result -= scalar_ingredient(ingredients, i);
+    result -= ingredients.at(i).at(0);
   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(1);  // skip alloc id
-  return ingredients.at(i).at(0);
+bool is_raw(const reagent& r) {
+  return has_property(r, "raw");
 }
 
 :(scenario subtract_literal)
diff --git a/023boolean.cc b/023boolean.cc
index 976cbff9..3d51fd7a 100644
--- a/023boolean.cc
+++ b/023boolean.cc
@@ -26,7 +26,7 @@ case AND: {
 case AND: {
   bool result = true;
   for (int i = 0;  i < SIZE(ingredients);  ++i)
-    result = result && scalar_ingredient(ingredients, i);
+    result = result && ingredients.at(i).at(0);
   products.resize(1);
   products.at(0).push_back(result);
   break;
@@ -84,7 +84,7 @@ case OR: {
 case OR: {
   bool result = false;
   for (int i = 0;  i < SIZE(ingredients);  ++i)
-    result = result || scalar_ingredient(ingredients, i);
+    result = result || ingredients.at(i).at(0);
   products.resize(1);
   products.at(0).push_back(result);
   break;
@@ -127,8 +127,8 @@ case NOT: {
     break;
   }
   for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
-    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();
+    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();
       goto finish_checking_instruction;
     }
   }
@@ -145,7 +145,7 @@ case NOT: {
 case NOT: {
   products.resize(SIZE(ingredients));
   for (int i = 0;  i < SIZE(ingredients);  ++i) {
-    products.at(i).push_back(!scalar_ingredient(ingredients, i));
+    products.at(i).push_back(!ingredients.at(i).at(0));
   }
   break;
 }
diff --git a/024jump.cc b/024jump.cc
index b7011c09..37011290 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_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) {
+  if (!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 (!scalar_ingredient(ingredients, 0)) {
+  if (!ingredients.at(0).at(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: {123: "number"} <- copy {1: "literal"}
+-run: {1: "number"} <- copy {1: "literal"}
 -mem: storing 1 in location 123
 
 :(scenario jump_if_fallthrough)
@@ -122,17 +122,6 @@ def main [
 +run: {123: "number"} <- copy {1: "literal"}
 +mem: storing 1 in location 123
 
-:(scenario jump_if_on_address)
-def main [
-  10:&:num <- copy 999/unsafe
-  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")
@@ -143,7 +132,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_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) {
+  if (!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;
   }
@@ -161,7 +150,7 @@ case JUMP_UNLESS: {
 :(before "End Primitive Recipe Implementations")
 case JUMP_UNLESS: {
   assert(current_instruction().ingredients.at(1).initialized);
-  if (scalar_ingredient(ingredients, 0)) {
+  if (ingredients.at(0).at(0)) {
     trace(9998, "run") << "jump-unless fell through" << end();
     break;
   }
diff --git a/025compare.cc b/025compare.cc
index c82f3578..92878208 100644
--- a/025compare.cc
+++ b/025compare.cc
@@ -29,8 +29,6 @@ case EQUAL: {
 }
 :(before "End Primitive Recipe Implementations")
 case EQUAL: {
-  // todo: keep the address exception from slowing down the common case
-  drop_alloc_ids_if_comparing_address_to_literal_0(ingredients);
   vector<double>& exemplar = ingredients.at(0);
   bool result = true;
   for (int i = /*skip exemplar*/1;  i < SIZE(ingredients);  ++i) {
@@ -43,23 +41,6 @@ case EQUAL: {
   products.at(0).push_back(result);
   break;
 }
-:(code)
-void drop_alloc_ids_if_comparing_address_to_literal_0(vector<vector<double> >& ingredients) {
-  bool any_ingredient_is_null = false;
-  bool any_ingredient_is_address = false;
-  for (int i = 0;  i < SIZE(current_instruction().ingredients);  ++i) {
-    if (current_instruction().ingredients.at(i).name == "0")
-      any_ingredient_is_null = true;
-    if (is_mu_address(current_instruction().ingredients.at(i)))
-      any_ingredient_is_address = true;
-  }
-  if (any_ingredient_is_null && any_ingredient_is_address) {
-    for (int i = 0;  i < SIZE(ingredients);  ++i) {
-      if (is_mu_address(current_instruction().ingredients.at(i)))
-        ingredients.at(i).erase(ingredients.at(i).begin());
-    }
-  }
-}
 
 :(scenario equal)
 def main [
@@ -93,42 +74,6 @@ def main [
 ]
 +mem: storing 0 in location 1
 
-:(scenario equal_address_null)
-def main [
-  1:&:num <- copy 0
-  10:bool <- equal 1:&:num, 0
-]
-+mem: storing 1 in location 10
-
-:(scenario equal_address_null_2)
-def main [
-  1:&:num <- copy 0
-  10:bool <- equal 0, 1:&:num
-]
-+mem: storing 1 in location 10
-
-:(scenario equal_address_null_3)
-def main [
-  1:&:num <- new num:type
-  10:bool <- equal 1:&:num, 0
-]
-+mem: storing 0 in location 10
-
-:(scenario equal_address_null_multiple)
-def main [
-  1:&:num <- copy 0
-  10:bool <- equal 0, 1:&:num, 0
-]
-+mem: storing 1 in location 10
-
-:(scenario equal_address_null_multiple_2)
-def main [
-  1:&:num <- copy 0
-  3:&:num <- copy 0
-  10:bool <- equal 0, 1:&:num, 0, 3:&:num
-]
-+mem: storing 1 in location 10
-
 :(before "End Primitive Recipe Declarations")
 NOT_EQUAL,
 :(before "End Primitive Recipe Numbers")
@@ -156,8 +101,6 @@ case NOT_EQUAL: {
 }
 :(before "End Primitive Recipe Implementations")
 case NOT_EQUAL: {
-  // todo: keep the address exception from slowing down the common case
-  drop_alloc_ids_if_comparing_address_to_literal_0(ingredients);
   vector<double>& exemplar = ingredients.at(0);
   products.resize(1);
   bool equal_ingredients = equal(ingredients.at(1).begin(), ingredients.at(1).end(), exemplar.begin());
diff --git a/027call_ingredient.cc b/027call_ingredient.cc
index 00e44ea3..46fafe7e 100644
--- a/027call_ingredient.cc
+++ b/027call_ingredient.cc
@@ -68,9 +68,6 @@ case NEXT_INGREDIENT: {
     }
     products.push_back(
         current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
-    if (is_mu_scalar(current_call().ingredients.at(current_call().next_ingredient_to_process))
-        && is_mu_address(current_instruction().products.at(0)))
-      products.at(0).insert(products.at(0).begin(), /*alloc id*/0);
     assert(SIZE(products) == 1);  products.resize(2);  // push a new vector
     products.at(1).push_back(1);
     ++current_call().next_ingredient_to_process;
@@ -97,18 +94,6 @@ def f [
 ]
 +error: f: no ingredient to save in '11:num'
 
-:(scenario pass_null_ingredient_for_address)
-def main [
-  f 0
-]
-def f [
-  1:address:num <- next-ingredient
-]
-+mem: storing 0 in location 2
-$error: 0
-
-//: another primitive: 'rewind-ingredients' to rescan ingredients from the start
-
 :(scenario rewind_ingredients)
 def main [
   f 2
@@ -139,8 +124,6 @@ case REWIND_INGREDIENTS: {
   break;
 }
 
-//: another primitive: 'ingredient' for random access
-
 :(scenario ingredient)
 def main [
   f 1, 2
diff --git a/028call_return.cc b/028call_return.cc
index aa5cb584..c8c1bca6 100644
--- a/028call_return.cc
+++ b/028call_return.cc
@@ -52,10 +52,6 @@ case RETURN: {
     trace(9998, "run") << "result " << i << " is " << to_string(ingredients.at(i)) << end();
   // make return products available to caller
   copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
-  for (int i = 0;  i < SIZE(current_instruction().products);  ++i) {
-    if (is_mu_address(current_instruction().products.at(i)) && scalar(ingredients.at(i)))
-      products.at(i).insert(products.at(i).begin(), /*alloc id*/0);
-  }
   // End Return
   break;  // continue to process rest of *caller* instruction
 }
diff --git a/030container.cc b/030container.cc
index b722e711..f4aaafd4 100644
--- a/030container.cc
+++ b/030container.cc
@@ -7,16 +7,14 @@ get_or_insert(Type, point);  // initialize
 get(Type, point).kind = CONTAINER;
 get(Type, point).name = "point";
 get(Type, point).elements.push_back(reagent("x:number"));
-get(Type, point).elements.back().set_value(0);
 get(Type, point).elements.push_back(reagent("y:number"));
-get(Type, point).elements.back().set_value(1);
 
 //: Containers can be copied around with a single instruction just like
 //: numbers, no matter how large they are.
 
 //: Tests in this layer often explicitly set up memory before reading it as a
-//: container. Don't do this in general. I'm tagging exceptions with /unsafe to
-//: skip later checks.
+//: container. Don't do this in general. I'm tagging such cases with /unsafe;
+//: they'll be exceptions to later checks.
 :(scenario copy_multiple_locations)
 def main [
   1:num <- copy 34
@@ -42,9 +40,7 @@ get_or_insert(Type, point_number);  // initialize
 get(Type, point_number).kind = CONTAINER;
 get(Type, point_number).name = "point-number";
 get(Type, point_number).elements.push_back(reagent("xy:point"));
-get(Type, point_number).elements.back().set_value(0);
 get(Type, point_number).elements.push_back(reagent("z:number"));
-get(Type, point_number).elements.back().set_value(1);
 
 :(scenario copy_handles_nested_container_elements)
 def main [
@@ -131,10 +127,6 @@ def main [
 //: 'get' takes a 'base' container and an 'offset' into it and returns the
 //: appropriate element of the container value.
 
-//: The offset is different from the distance (in memory locations) an element
-//: is at from the start. This is because elements can occupy multiple memory
-//: locations in the container.
-
 :(scenario get)
 def main [
   12:num <- copy 34
@@ -203,7 +195,9 @@ case GET: {
   // Update GET base_type in Run
   int offset = ingredients.at(1).at(0);
   if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break;  // copied from Check above
-  int src = element_location(base_address, offset, base.type);
+  int src = base_address;
+  for (int i = 0; i < offset; ++i)
+    src += size_of(element_type(base.type, i));
   trace(9998, "run") << "address to copy is " << src << end();
   //: use base.type rather than base_type because later layers will introduce compound types
   reagent/*copy*/ element = element_type(base.type, offset);
@@ -228,12 +222,6 @@ const reagent element_type(const type_tree* type, int offset_value) {
   // End element_type Special-cases
   return element;
 }
-int element_location(int base_address, int offset, const type_tree* type) {
-  int result = base_address;
-  for (int i = 0; i < offset; ++i)
-    result += size_of(element_type(type, i));
-  return result;
-}
 
 :(scenario get_handles_nested_container_elements)
 def main [
@@ -364,17 +352,14 @@ case PUT: {
   // Update PUT base_type in Run
   int offset = ingredients.at(1).at(0);
   if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break;  // copied from Check above
-  int address = element_location(base_address, offset, base.type);
+  int address = base_address;
+  for (int i = 0; i < offset; ++i)
+    address += size_of(element_type(base.type, i));
   trace(9998, "run") << "address to copy to is " << address << end();
   // optimization: directly write the element rather than updating 'product'
   // and writing the entire container
   // Write Memory in PUT in Run
   write_products = false;
-  if (is_mu_address(element_type(base.type, offset)) && is_literal(current_instruction().ingredients.at(2)) && current_instruction().ingredients.at(2).name == "0") {
-    trace("mem") << "storing 0 in location " << address << end();
-    put(Memory, address, /*alloc id*/0);
-    ++address;
-  }
   for (int i = 0;  i < SIZE(ingredients.at(2));  ++i) {
     trace("mem") << "storing " << no_scientific(ingredients.at(2).at(i)) << " in location " << address+i << end();
     put(Memory, address+i, ingredients.at(2).at(i));
@@ -392,23 +377,6 @@ def main [
 ]
 +error: main: product of 'put' must be first ingredient '1:point', but got '3:point'
 
-:(scenario put_null_address)
-container foo [
-  x:num
-  y:&:num
-  z:num
-]
-def main [
-  1:num <- copy 34
-  2:num <- copy 0  # alloc id
-  3:num <- copy 1000  # pretend address
-  4:num <- copy 36
-  put 1:foo, y:offset, 0
-]
-+run: put {1: "foo"}, {y: "offset"}, {0: "literal"}
-+mem: storing 0 in location 2
-+mem: storing 0 in location 3
-
 //:: Allow containers to be defined in Mu code.
 
 :(scenarios load)
@@ -522,7 +490,6 @@ void insert_container(const string& command, kind_of_type kind, istream& in) {
       break;
     }
     info.elements.push_back(reagent(element));
-    info.elements.back().set_value(SIZE(info.elements)-1);
     expand_type_abbreviations(info.elements.back().type);  // todo: use abbreviation before declaration
     replace_unknown_types_with_unique_ordinals(info.elements.back().type, info);
     trace(9993, "parse") << "  element: " << to_string(info.elements.back()) << end();
diff --git a/032array.cc b/032array.cc
index d893b529..3bde42fb 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 from 4 locations
++run: creating array of size 4
 
 :(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 from " << size << " locations" << end();
+  trace(9998, "run") << "creating array of size " << size << end();
   // initialize array
   for (int i = 1;  i <= size_of(product);  ++i)
     put(Memory, base_address+i, 0);
@@ -208,23 +208,19 @@ def main [
   2:num <- copy 14
   3:num <- copy 15
   4:num <- copy 16
-  10:num <- index 1:array:num:3, 0/index  # the index must be a non-negative whole number
+  5:num <- index 1:array:num:3, 0/index  # the index must be a non-negative whole number
 ]
-+mem: storing 14 in location 10
++mem: storing 14 in location 5
 
 :(scenario index_compound_element)
 def main [
   {1: (array (address number) 3)} <- create-array
-  # 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
+  2:num <- copy 14
+  3:num <- copy 15
+  4:num <- copy 16
+  5:address:num <- index {1: (array (address number) 3)}, 0
 ]
-# skip alloc id
-+mem: storing 14 in location 11
++mem: storing 14 in location 5
 
 :(scenario index_direct_offset)
 def main [
@@ -232,10 +228,10 @@ def main [
   2:num <- copy 14
   3:num <- copy 15
   4:num <- copy 16
-  10:num <- copy 0
-  20:num <- index 1:array:num, 10:num
+  5:num <- copy 0
+  6:num <- index 1:array:num, 5:num
 ]
-+mem: storing 14 in location 20
++mem: storing 14 in location 6
 
 :(before "End Primitive Recipe Declarations")
 INDEX,
@@ -350,38 +346,38 @@ def main [
   2:num <- copy 14
   3:num <- copy 15
   4:num <- copy 16
-  10:num <- index 1:array:num:3, 1.5  # non-whole number
+  5:num <- index 1:array:num:3, 1.5  # non-whole number
 ]
 # fraction is truncated away
-+mem: storing 15 in location 10
++mem: storing 15 in location 5
 
 :(scenario index_out_of_bounds)
 % Hide_errors = true;
 def main [
-  2:array:point:3 <- create-array
-  3:num <- copy 14
-  4:num <- copy 15
-  5:num <- copy 16
-  6:num <- copy 17
-  7:num <- copy 18
-  8:num <- copy 19
-  index 1:array:point:3/skip-alloc-id, 4  # less than size of array in locations, but larger than its length in elements
+  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
 ]
-+error: main: invalid index 4 in 'index 1:array:point:3/skip-alloc-id, 4'
++error: main: invalid index 4 in 'index 1:array:num:3, 4'
 
 :(scenario index_out_of_bounds_2)
 % Hide_errors = true;
 def main [
-  2:array:point:3 <- create-array
-  3:num <- copy 14
-  4:num <- copy 15
-  5:num <- copy 16
-  6:num <- copy 14
-  7:num <- copy 15
-  8:num <- copy 16
-  index 1:array:point/skip-alloc-id, -1
+  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
 ]
-+error: main: invalid index -1 in 'index 1:array:point/skip-alloc-id, -1'
++error: main: invalid index -1 in 'index 1:array:point, -1'
 
 :(scenario index_product_type_mismatch)
 % Hide_errors = true;
diff --git a/033exclusive_container.cc b/033exclusive_container.cc
index e4fa6398..44161d7c 100644
--- a/033exclusive_container.cc
+++ b/033exclusive_container.cc
@@ -12,14 +12,12 @@ get_or_insert(Type, tmp);  // initialize
 get(Type, tmp).kind = EXCLUSIVE_CONTAINER;
 get(Type, tmp).name = "number-or-point";
 get(Type, tmp).elements.push_back(reagent("i:number"));
-get(Type, tmp).elements.back().set_value(0);
 get(Type, tmp).elements.push_back(reagent("p:point"));
-get(Type, tmp).elements.back().set_value(1);
 }
 
-//: Tests in this layer often explicitly set up memory before reading it as an
-//: array. Don't do this in general. I'm tagging exceptions with /raw to keep
-//: checks in future layers from flagging them.
+//: Tests in this layer often explicitly set up memory before reading it as a
+//: container. Don't do this in general. I'm tagging such cases with /unsafe;
+//: they'll be exceptions to later checks.
 :(scenario copy_exclusive_container)
 # Copying exclusive containers copies all their contents and an extra location for the tag.
 def main [
diff --git a/034address.cc b/034address.cc
index 94c930bd..bce51b2e 100644
--- a/034address.cc
+++ b/034address.cc
@@ -18,37 +18,6 @@
 //: 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.
 
@@ -57,30 +26,28 @@ Next_alloc_id = 0;
 # should get back different results
 def main [
   1:address:num/raw <- new number:type
-  3:address:num/raw <- new number:type
-  5:bool/raw <- equal 1:address:num/raw, 3:address:num/raw
+  2:address:num/raw <- new number:type
+  3:bool/raw <- equal 1:address:num/raw, 2:address:num/raw
 ]
-+mem: storing 1000 in location 2
 +mem: storing 0 in location 3
 
 :(scenario new_array)
 # call 'new' with a second ingredient to allocate an array of some type rather than a single copy
 def main [
   1:address:array:num/raw <- new number:type, 5
-  3:address:num/raw <- new number:type
-  5:num/raw <- subtract 3:address:num/raw, 1:address:array:num/raw
+  2:address:num/raw <- new number:type
+  3:num/raw <- subtract 2:address:num/raw, 1:address:array:num/raw
 ]
 +run: {1: ("address" "array" "number"), "raw": ()} <- new {number: "type"}, {5: "literal"}
 +mem: array length is 5
-+mem: storing 1000 in location 2
 # don't forget the extra location for array length
-+mem: storing 7 in location 5
++mem: storing 6 in location 3
 
 :(scenario dilated_reagent_with_new)
 def main [
   1:address:address:num <- new {(address number): type}
 ]
-+new: size of '(address number)' is 2
++new: size of '(address number)' is 1
 
 //: 'new' takes a weird 'type' as its first ingredient; don't error on it
 :(before "End Mu Types Initialization")
@@ -184,13 +151,6 @@ def main [
 ]
 $error: 0
 
-:(scenario equal_result_of_new_with_null)
-def main [
-  1:&:num <- new num:type
-  10:bool <- equal 1:&:num, 0
-]
-+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.
@@ -261,18 +221,15 @@ case ALLOCATE: {
   int result = allocate(size);
   if (SIZE(current_instruction().ingredients) > 1) {
     // initialize array length
-    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));
+    trace("mem") << "storing " << ingredients.at(1).at(0) << " in location " << result << end();
+    put(Memory, result, ingredients.at(1).at(0));
   }
   products.resize(1);
-  products.at(0).push_back(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;
@@ -333,41 +290,41 @@ def main [
 :(scenario new_size)
 def main [
   11:address:num/raw <- new number:type
-  13:address:num/raw <- new number:type
-  15:num/raw <- subtract 13:address:num/raw, 11:address:num/raw
+  12:address:num/raw <- new number:type
+  13:num/raw <- subtract 12:address:num/raw, 11:address:num/raw
 ]
-# size of number + alloc id
-+mem: storing 2 in location 15
+# size of number
++mem: storing 1 in location 13
 
 :(scenario new_array_size)
 def main [
   1:address:array:num/raw <- new number:type, 5
-  3:address:num/raw <- new number:type
-  5:num/raw <- subtract 3:address:num/raw, 1:address:array:num/raw
+  2:address:num/raw <- new number:type
+  3:num/raw <- subtract 2:address:num/raw, 1:address:array:num/raw
 ]
 # 5 locations for array contents + array length
-+mem: storing 7 in location 5
++mem: storing 6 in location 3
 
 :(scenario new_empty_array)
 def main [
   1:address:array:num/raw <- new number:type, 0
-  3:address:num/raw <- new number:type
-  5:num/raw <- subtract 3:address:num/raw, 1:address:array:num/raw
+  2:address:num/raw <- new number:type
+  3:num/raw <- subtract 2:address:num/raw, 1:address:array:num/raw
 ]
 +run: {1: ("address" "array" "number"), "raw": ()} <- new {number: "type"}, {0: "literal"}
 +mem: array length is 0
 # one location for array length
-+mem: storing 2 in location 5
++mem: storing 1 in location 3
 
 //: If a routine runs out of its initial allocation, it should allocate more.
 :(scenario new_overflow)
-% Initial_memory_per_routine = 3;  // barely enough room for point allocation below
+% Initial_memory_per_routine = 2;  // barely enough room for point allocation below
 def main [
   1:address:num/raw <- new number:type
   2:address:point/raw <- new point:type  # not enough room in initial page
 ]
-+new: routine allocated memory from 1000 to 1003
-+new: routine allocated memory from 1003 to 1006
++new: routine allocated memory from 1000 to 1002
++new: routine allocated memory from 1002 to 1004
 
 :(scenario new_without_ingredient)
 % Hide_errors = true;
diff --git a/035lookup.cc b/035lookup.cc
index 9708ce5b..a2647f5d 100644
--- a/035lookup.cc
+++ b/035lookup.cc
@@ -6,8 +6,7 @@
 :(scenario copy_indirect)
 def main [
   1:address:num <- copy 10/unsafe
-  # skip alloc id
-  11:num <- copy 34
+  10:num <- copy 34
   # This loads location 1 as an address and looks up *that* location.
   2:num <- copy 1:address:num/lookup
 ]
@@ -23,7 +22,7 @@ def main [
   1:address:num <- copy 10/unsafe
   1:address:num/lookup <- copy 34
 ]
-+mem: storing 34 in location 11
++mem: storing 34 in location 10
 
 :(before "End Preprocess write_memory(x, data)")
 canonize(x);
@@ -36,7 +35,7 @@ def main [
   1:address:num/lookup <- copy 34
 ]
 -mem: storing 34 in location 0
-+error: main: tried to lookup 0 in '1:address:num/lookup <- copy 34'
++error: can't write to location 0 in '1:address:num/lookup <- copy 34'
 
 //: attempts to /lookup address 0 always loudly fail
 :(scenario lookup_0_fails)
@@ -83,7 +82,7 @@ void lookup_memory(reagent& x) {
 }
 
 void lookup_memory_core(reagent& x, bool check_for_null) {
-  double address = x.value + /*skip alloc id in address*/1;
+  double address = x.value;
   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) {
@@ -95,18 +94,12 @@ void lookup_memory_core(reagent& x, bool check_for_null) {
       raise << "tried to lookup 0\n" << end();
     }
   }
-  x.set_value(new_value+/*skip alloc id in payload*/1);
+  x.set_value(new_value);
   drop_from_type(x, "address");
   drop_one_lookup(x);
 }
 
-:(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)")
+:(before "End Preprocess types_strictly_match(reagent to, reagent from)")
 if (!canonize_type(to)) return false;
 if (!canonize_type(from)) return false;
 
@@ -164,33 +157,30 @@ void drop_one_lookup(reagent& r) {
 :(scenario get_indirect)
 def main [
   1:address:point <- copy 10/unsafe
-  # skip alloc id
-  11:num <- copy 34
-  12:num <- copy 35
-  20:num <- get 1:address:point/lookup, 0:offset
+  10:num <- copy 34
+  11:num <- copy 35
+  2:num <- get 1:address:point/lookup, 0:offset
 ]
-+mem: storing 34 in location 20
++mem: storing 34 in location 2
 
 :(scenario get_indirect2)
 def main [
   1:address:point <- copy 10/unsafe
-  # skip alloc id
-  11:num <- copy 94
-  12:num <- copy 95
-  20:address:num <- copy 30/unsafe
-  20:address:num/lookup <- get 1:address:point/lookup, 0:offset
+  10:num <- copy 34
+  11:num <- copy 35
+  2:address:num <- copy 20/unsafe
+  2:address:num/lookup <- get 1:address:point/lookup, 0:offset
 ]
-+mem: storing 94 in location 31
++mem: storing 34 in location 20
 
 :(scenario include_nonlookup_properties)
 def main [
   1:address:point <- copy 10/unsafe
-  # skip alloc id
-  11:num <- copy 34
-  12:num <- copy 35
-  20:num <- get 1:address:point/lookup/foo, 0:offset
+  10:num <- copy 34
+  11:num <- copy 35
+  2:num <- get 1:address:point/lookup/foo, 0:offset
 ]
-+mem: storing 34 in location 20
++mem: storing 34 in location 2
 
 :(after "Update GET base in Check")
 if (!canonize_type(base)) break;
@@ -202,12 +192,11 @@ canonize(base);
 :(scenario put_indirect)
 def main [
   1:address:point <- copy 10/unsafe
-  # skip alloc id
-  11:num <- copy 34
-  12:num <- copy 35
+  10:num <- copy 34
+  11:num <- copy 35
   1:address:point/lookup <- put 1:address:point/lookup, 0:offset, 36
 ]
-+mem: storing 36 in location 11
++mem: storing 36 in location 10
 
 :(after "Update PUT base in Check")
 if (!canonize_type(base)) break;
@@ -252,7 +241,7 @@ def main [
   11:num <- copy 14
   12:num <- copy 15
   13:num <- copy 16
-  1:address:array:num <- copy 9/unsafe/skip-alloc-id
+  1:address:array:num <- copy 10/unsafe
   2:array:num <- copy 1:address:array:num/lookup
 ]
 +mem: storing 3 in location 2
@@ -265,7 +254,7 @@ def main [
   1:address:array:num:3 <- copy 1000/unsafe  # pretend allocation
   1:address:array:num:3/lookup <- create-array
 ]
-+mem: storing 3 in location 1001
++mem: storing 3 in location 1000
 
 :(after "Update CREATE_ARRAY product in Check")
 if (!canonize_type(product)) break;
@@ -278,7 +267,7 @@ def main [
   11:num <- copy 14
   12:num <- copy 15
   13:num <- copy 16
-  1:address:array:num <- copy 9/unsafe/skip-alloc-id
+  1:address:array:num <- copy 10/unsafe
   2:num <- index 1:address:array:num/lookup, 1
 ]
 +mem: storing 15 in location 2
@@ -301,7 +290,7 @@ def main [
   11:num <- copy 14
   12:num <- copy 15
   13:num <- copy 16
-  1:address:array:num <- copy 9/unsafe/skip-alloc-id
+  1:address:array:num <- copy 10/unsafe
   1:address:array:num/lookup <- put-index 1:address:array:num/lookup, 1, 34
 ]
 +mem: storing 34 in location 12
@@ -312,7 +301,7 @@ def main [
   2:num <- copy 14
   3:num <- copy 15
   4:num <- copy 16
-  5:address:num <- copy 9/unsafe/skip-alloc-id
+  5:address:num <- copy 10/unsafe
   10:num <- copy 1
   1:array:num:3 <- put-index 1:array:num:3, 5:address:num/lookup, 34
 ]
@@ -325,7 +314,7 @@ def main [
   11:num <- copy 14
   12:num <- copy 15
   13:num <- copy 16
-  1:address:array:num <- copy 9/unsafe/skip-alloc-id
+  1:address:array:num <- copy 10/unsafe
   1:address:array:num <- put-index 1:address:array:num/lookup, 1, 34
 ]
 +error: main: product of 'put-index' must be first ingredient '1:address:array:num/lookup', but got '1:address:array:num'
@@ -348,7 +337,7 @@ def main [
   *5:address:num <- copy 34
   6:num <- copy *5:address:num
 ]
-+run: creating array from 7 locations
++run: creating array of size 4
 +mem: storing 34 in location 6
 
 :(before "Update PUT_INDEX base in Check")
@@ -369,7 +358,7 @@ def main [
   11:num <- copy 14
   12:num <- copy 15
   13:num <- copy 16
-  1:address:array:num <- copy 9/unsafe/skip-alloc-id
+  1:address:array:num <- copy 10/unsafe
   2:num <- length 1:address:array:num/lookup
 ]
 +mem: storing 3 in location 2
@@ -381,8 +370,8 @@ canonize(array);
 
 :(scenario maybe_convert_indirect)
 def main [
-  11:number-or-point <- merge 0/number, 34
-  1:address:number-or-point <- copy 10/unsafe/skip-alloc-id
+  10:number-or-point <- merge 0/number, 34
+  1:address:number-or-point <- copy 10/unsafe
   2:num, 3:bool <- maybe-convert 1:address:number-or-point/lookup, i:variant
 ]
 +mem: storing 1 in location 3
@@ -390,23 +379,23 @@ def main [
 
 :(scenario maybe_convert_indirect_2)
 def main [
-  11:number-or-point <- merge 0/number, 34
-  1:address:number-or-point <- copy 10/unsafe/skip-alloc-id
-  3:address:num <- copy 20/unsafe
-  3:address:num/lookup, 5:bool <- maybe-convert 1:address:number-or-point/lookup, i:variant
+  10:number-or-point <- merge 0/number, 34
+  1:address:number-or-point <- copy 10/unsafe
+  2:address:num <- copy 20/unsafe
+  2:address:num/lookup, 3:bool <- maybe-convert 1:address:number-or-point/lookup, i:variant
 ]
-+mem: storing 1 in location 5
-+mem: storing 34 in location 21
++mem: storing 1 in location 3
++mem: storing 34 in location 20
 
 :(scenario maybe_convert_indirect_3)
 def main [
-  11:number-or-point <- merge 0/number, 34
-  1:address:number-or-point <- copy 10/unsafe/skip-alloc-id
-  3:address:bool <- copy 20/unsafe
-  5:num, 3:address:bool/lookup <- maybe-convert 1:address:number-or-point/lookup, i:variant
+  10:number-or-point <- merge 0/number, 34
+  1:address:number-or-point <- copy 10/unsafe
+  2:address:bool <- copy 20/unsafe
+  3:num, 2:address:bool/lookup <- maybe-convert 1:address:number-or-point/lookup, i:variant
 ]
-+mem: storing 1 in location 21
-+mem: storing 34 in location 5
++mem: storing 1 in location 20
++mem: storing 34 in location 3
 
 :(before "Update MAYBE_CONVERT base in Check")
 if (!canonize_type(base)) break;
@@ -427,9 +416,8 @@ def main [
   1:address:number-or-point <- copy 10/unsafe
   1:address:number-or-point/lookup <- merge 0/number, 34
 ]
-# skip alloc id
-+mem: storing 0 in location 11
-+mem: storing 34 in location 12
++mem: storing 0 in location 10
++mem: storing 34 in location 11
 
 :(before "Update size_mismatch Check for MERGE(x)
 canonize(x);
@@ -439,7 +427,7 @@ canonize(x);
 :(scenario lookup_abbreviation)
 def main [
   1:address:number <- copy 10/unsafe
-  11:number <- copy 34
+  10:number <- copy 34
   3:number <- copy *1:address:number
 ]
 +parse: ingredient: {1: ("address" "number"), "lookup": ()}
diff --git a/037abandon.cc b/037abandon.cc
index 1c2bc395..5a4adbd1 100644
--- a/037abandon.cc
+++ b/037abandon.cc
@@ -3,14 +3,14 @@
 :(scenario new_reclaim)
 def main [
   1:address:num <- new number:type
-  3:num <- copy 1:address:num  # because 1 will get reset during abandon below
+  2:num <- copy 1:address:num  # because 1 will get reset during abandon below
   abandon 1:address:num
-  4:address:num <- new number:type  # must be same size as abandoned memory to reuse
-  6:num <- copy 4:address:num
-  7:bool <- equal 3:num, 6:num
+  3:address:num <- new number:type  # must be same size as abandoned memory to reuse
+  4:num <- copy 3:address:num
+  5:bool <- equal 2:num, 4:num
 ]
 # both allocations should have returned the same address
-+mem: storing 1 in location 7
++mem: storing 1 in location 5
 
 //: 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+/*skip alloc id*/1), payload_size(ingredient));
+    abandon(get_or_insert(Memory, ingredient.value), payload_size(ingredient));
   }
   break;
 }
@@ -50,7 +50,7 @@ void abandon(int address, int payload_size) {
   for (int curr = address;  curr < address+payload_size;  ++curr)
     put(Memory, curr, 0);
   // append existing free list to address
-  trace("mem") << "saving " << address << " in free-list of size " << payload_size << end();
+  trace("abandon") << "saving " << address << " in free-list of size " << payload_size << end();
   put(Memory, address, get_or_insert(Current_routine->free_list, payload_size));
   put(Current_routine->free_list, payload_size, address);
 }
@@ -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) + /*alloc id*/1;
+  return size_of(x);
 }
 
 :(after "Allocate Special-cases")
@@ -80,23 +80,23 @@ if (get_or_insert(Current_routine->free_list, size)) {
 :(scenario new_differing_size_no_reclaim)
 def main [
   1:address:num <- new number:type
-  3:num <- copy 1:address:num
+  2:num <- copy 1:address:num
   abandon 1:address:num
-  4:address:array:num <- new number:type, 2  # different size
-  6:num <- copy 4:address:array:num
-  7:bool <- equal 3:num, 6:num
+  3:address:array:num <- new number:type, 2  # different size
+  4:num <- copy 3:address:array:num
+  5:bool <- equal 2:num, 4:num
 ]
 # no reuse
-+mem: storing 0 in location 7
++mem: storing 0 in location 5
 
 :(scenario new_reclaim_array)
 def main [
   1:address:array:num <- new number:type, 2
-  3:num <- copy 1:address:array:num
+  2:num <- copy 1:address:array:num
   abandon 1:address:array:num
-  4:address:array:num <- new number:type, 2  # same size
-  6:num <- copy 4:address:array:num
-  7:bool <- equal 3:num, 6:num
+  3:address:array:num <- new number:type, 2  # same size
+  4:num <- copy 3:address:array:num
+  5:bool <- equal 2:num, 4:num
 ]
 # both calls to new returned identical addresses
-+mem: storing 1 in location 7
++mem: storing 1 in location 5
diff --git a/038new_text.cc b/038new_text.cc
index b2a5db75..4b666f1c 100644
--- a/038new_text.cc
+++ b/038new_text.cc
@@ -4,25 +4,23 @@
 :(before "End Mu Types Initialization")
 put(Type_abbreviations, "text", new_type_tree("address:array:character"));
 
-:(scenario new_text)
+:(scenario new_string)
 def main [
   1:text <- new [abc def]
-  # location 2 is part of 1:text
-  3:char <- index *1:text, 5
+  2:char <- index *1:text, 5
 ]
 # number code for 'e'
-+mem: storing 101 in location 3
++mem: storing 101 in location 2
 
-:(scenario new_text_handles_unicode)
+:(scenario new_string_handles_unicode)
 def main [
   1:text <- new [a«c]
-  # location 2 is part of 1:text
-  3:num <- length *1:text
-  4:char <- index *1:text, 1
+  2:num <- length *1:text
+  3:char <- index *1:text, 1
 ]
-+mem: storing 3 in location 3
++mem: storing 3 in location 2
 # unicode for '«'
-+mem: storing 171 in location 4
++mem: storing 171 in location 3
 
 :(before "End NEW Check Special-cases")
 if (is_literal_text(inst.ingredients.at(0))) break;
@@ -31,7 +29,6 @@ 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;
@@ -43,9 +40,8 @@ int new_mu_text(const string& contents) {
   int string_length = unicode_length(contents);
 //?   Total_alloc += string_length+1;
 //?   ++Num_alloc;
-  int result = allocate(/*array length*/1 + string_length);
+  int result = allocate(string_length+/*array length*/1);
   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
@@ -66,16 +62,16 @@ int new_mu_text(const string& contents) {
 
 //: a new kind of typo
 
-:(scenario literal_text_without_instruction)
+:(scenario string_literal_without_instruction)
 % Hide_errors = true;
 def main [
   [abc]
 ]
 +error: main: instruction '[abc]' has no recipe in '[abc]'
 
-//: stash recognizes texts
+//: stash recognizes strings
 
-:(scenario stash_text)
+:(scenario stash_string)
 def main [
   1:text <- new [abc]
   stash [foo:], 1:text
@@ -84,29 +80,30 @@ def main [
 
 :(before "End inspect Special-cases(r, data)")
 if (is_mu_text(r)) {
-  return read_mu_text(data.at(/*skip alloc id*/1));
+  assert(scalar(data));
+  return read_mu_text(data.at(0));
 }
 
 :(before "End $print Special-cases")
 else if (is_mu_text(current_instruction().ingredients.at(i))) {
-  cout << read_mu_text(ingredients.at(i).at(/*skip alloc id*/1));
+  cout << read_mu_text(ingredients.at(i).at(0));
 }
 
-:(scenario unicode_text)
+:(scenario unicode_string)
 def main [
   1:text <- new [♠]
   stash [foo:], 1:text
 ]
 +app: foo: ♠
 
-:(scenario stash_space_after_text)
+:(scenario stash_space_after_string)
 def main [
   1:text <- new [abc]
   stash 1:text, [foo]
 ]
 +app: abc foo
 
-:(scenario stash_text_as_array)
+:(scenario stash_string_as_array)
 def main [
   1:text <- new [abc]
   stash *1:text
@@ -117,16 +114,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 text
-:(scenario new_text_overflow)
-% Initial_memory_per_routine = 3;
+//: Allocate more to routine when initializing a literal string
+:(scenario new_string_overflow)
+% Initial_memory_per_routine = 2;
 def main [
   1:address:num/raw <- new number:type
-  # location 2 is part of 1:address
-  3:text/raw <- new [a]  # not enough room in initial page, if you take the array length into account
+  2: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 1003
-+new: routine allocated memory from 1003 to 1006
++new: routine allocated memory from 1000 to 1002
++new: routine allocated memory from 1002 to 1004
 
 //: helpers
 :(code)
@@ -144,9 +140,9 @@ int unicode_length(const string& s) {
 
 string read_mu_text(int address) {
   if (address == 0) return "";
-  int length = get_or_insert(Memory, address+/*alloc id*/1);
+  int length = get_or_insert(Memory, address);
   if (length == 0) return "";
-  return read_mu_characters(address+/*alloc id*/1+/*length*/1, length);
+  return read_mu_characters(address+1, length);
 }
 
 string read_mu_characters(int start, int length) {
@@ -160,18 +156,10 @@ string read_mu_characters(int start, int length) {
 
 //: assert: perform sanity checks at runtime
 
-:(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
+  assert 0, [this is an assert in Mu]
 ]
 +error: this is an assert in Mu
 
@@ -185,7 +173,7 @@ 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_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) {
+  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();
     break;
   }
@@ -197,11 +185,11 @@ case ASSERT: {
 }
 :(before "End Primitive Recipe Implementations")
 case ASSERT: {
-  if (!scalar_ingredient(ingredients, 0)) {
+  if (!ingredients.at(0).at(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(/*skip alloc id*/1)) << '\n' << end();
+      raise << read_mu_text(ingredients.at(1).at(0)) << '\n' << end();
     if (!Hide_errors) exit(1);
   }
   break;
diff --git a/042name.cc b/042name.cc
index e1f35136..f183962c 100644
--- a/042name.cc
+++ b/042name.cc
@@ -6,8 +6,8 @@
 def main [
   x:num <- copy 0
 ]
-+name: assign x 2
-+mem: storing 0 in location 2
++name: assign x 1
++mem: storing 0 in location 1
 
 :(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 = 2;  // reserve indices 0 and 1 for the chaining slot in a later layer
+  ++curr_idx;  // avoid using index 0, benign skip in some other cases
   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     instruction& inst = caller.steps.at(i);
     // End transform_names(inst) Special-cases
@@ -135,21 +135,13 @@ 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_offset(const type_ordinal t, const string& name, const string& recipe_name) {
+int find_element_name(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;
@@ -178,26 +170,26 @@ def main [
   x:point <- merge 34, 35
   y:num <- copy 3
 ]
-+name: assign x 2
++name: assign x 1
 # skip location 2 because x occupies two locations
-+name: assign y 4
++name: assign y 3
 
 :(scenario transform_names_supports_static_arrays)
 def main [
   x:@:num:3 <- create-array
   y:num <- copy 3
 ]
-+name: assign x 2
++name: assign x 1
 # skip locations 2, 3, 4 because x occupies four locations
-+name: assign y 6
++name: assign y 5
 
 :(scenario transform_names_passes_dummy)
 # _ is just a dummy result that never gets consumed
 def main [
   _, x:num <- copy 0, 1
 ]
-+name: assign x 2
--name: assign _ 2
++name: assign x 1
+-name: assign _ 1
 
 //: an escape hatch to suppress name conversion that we'll use later
 :(scenarios run)
@@ -206,7 +198,7 @@ def main [
 def main [
   x:num/raw <- copy 0
 ]
--name: assign x 2
+-name: assign x 1
 +error: can't write to location 0 in 'x:num/raw <- copy 0'
 
 :(scenarios transform)
@@ -274,7 +266,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_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
+      inst.ingredients.at(1).set_value(find_element_name(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();
     }
   }
@@ -294,8 +286,8 @@ def main [
   a:point <- copy 0/unsafe
   b:num <- copy 0/unsafe
 ]
-+name: assign a 2
-+name: assign b 4
++name: assign a 1
++name: assign b 3
 
 //:: Support variant names for exclusive containers in 'maybe-convert'.
 
@@ -324,7 +316,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_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
+      inst.ingredients.at(1).set_value(find_element_name(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 e62e9285..f290a0b9 100644
--- a/043space.cc
+++ b/043space.cc
@@ -13,31 +13,25 @@
 put(Type_abbreviations, "space", new_type_tree("address:array:location"));
 
 :(scenario set_default_space)
-# if default-space is 10, then:
-#   10: alloc id
-#   11: array size
-#   12: local 0 (space for the chaining slot; described later; often unused)
-#   13: local 0 (space for the chaining slot; described later; often unused)
-#   14: local 2 (assuming it is a scalar)
-#   15: local 3
-#   ..and so on
+# 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'
-  11:num <- copy 5  # length
-  default-space:space <- copy 10/unsafe/skip-alloc-id
-  2:num <- copy 23
+  10:num <- copy 5  # length
+  default-space:space <- copy 10/unsafe
+  1:num <- copy 23
 ]
-+mem: storing 23 in location 14
++mem: storing 23 in location 12
 
 :(scenario lookup_sidesteps_default_space)
 def main [
   # pretend pointer from outside
-  2001:num <- copy 34
+  2000:num <- copy 34
   # pretend address:array:location; in practice we'll use 'new'
-  1001:num <- copy 5  # length
+  1000:num <- copy 5  # length
   # actual start of this recipe
-  default-space:space <- copy 1000/unsafe/skip-alloc-id
-  1:&:num <- copy 2000/unsafe/skip-alloc-id  # even local variables always contain raw addresses
+  default-space:space <- copy 1000/unsafe
+  1:&:num <- copy 2000/unsafe  # even local variables always contain raw addresses
   8:num/raw <- copy *1:&:num
 ]
 +mem: storing 34 in location 8
@@ -49,9 +43,8 @@ def main [
 def main [
   default-space:num, x:num <- copy 0, 1
 ]
-+name: assign x 2
++name: assign x 1
 -name: assign default-space 1
--name: assign default-space 2
 
 :(before "End is_disqualified Special-cases")
 if (x.name == "default-space")
@@ -81,7 +74,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 + /*skip alloc id*/1) : 0;
+  return current_call().default_space ? current_call().default_space : 0;
 }
 
 int address(int offset, int base) {
@@ -102,13 +95,9 @@ int address(int offset, int base) {
 
 :(after "Begin Preprocess write_memory(x, data)")
 if (x.name == "default-space") {
-  if (!is_mu_space(x)) {
+  if (!scalar(data) || !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();
-    return;
-  }
-  double space_location = data.at(/*skip alloc id*/1);
-  trace("mem") << "storing " << no_scientific(space_location) << " to default_space" << end();
-  current_call().default_space = space_location;
+  current_call().default_space = data.at(0);
   return;
 }
 :(code)
@@ -126,13 +115,11 @@ def main [
   default-space:space <- copy 10/unsafe
   1:space/raw <- copy default-space:space
 ]
-# skip alloc id
-+mem: storing 10 in location 2
++mem: storing 10 in location 1
 
 :(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;
 }
@@ -142,13 +129,13 @@ if (x.name == "default-space") {
 :(scenario lookup_sidesteps_default_space_in_get)
 def main [
   # pretend pointer to container from outside
-  2001:num <- copy 34
-  2002:num <- copy 35
+  2000:num <- copy 34
+  2001:num <- copy 35
   # pretend address:array:location; in practice we'll use 'new'
-  1001:num <- copy 5  # length
+  1000:num <- copy 5  # length
   # actual start of this recipe
-  default-space:space <- copy 1000/unsafe/skip-alloc-id
-  1:&:point <- copy 2000/unsafe/skip-alloc-id
+  default-space:space <- copy 1000/unsafe
+  1:&:point <- copy 2000/unsafe
   9:num/raw <- get *1:&:point, 1:offset
 ]
 +mem: storing 35 in location 9
@@ -161,14 +148,14 @@ 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
-  2001:num <- copy 2  # length
-  2002:num <- copy 34
-  2003:num <- copy 35
+  2000:num <- copy 2  # length
+  2001:num <- copy 34
+  2002:num <- copy 35
   # pretend address:array:location; in practice we'll use 'new'
-  1001:num <- copy 5  # length
+  1000:num <- copy 5  # length
   # actual start of this recipe
-  default-space:space <- copy 1000/unsafe/skip-alloc-id
-  1:&:@:num <- copy 2000/unsafe/skip-alloc-id
+  default-space:space <- copy 1000/unsafe
+  1:&:@:num <- copy 2000/unsafe
   9:num/raw <- index *1:&:@:num, 1
 ]
 +mem: storing 35 in location 9
@@ -185,8 +172,8 @@ def main [
   x:num <- copy 0
   y:num <- copy 3
 ]
-# allocate space for x and y, as well as the chaining slot at indices 0 and 1
-+mem: array length is 4
+# allocate space for x and y, as well as the chaining slot at 0
++mem: array length is 3
 
 :(before "End is_disqualified Special-cases")
 if (x.name == "number-of-locals")
diff --git a/044space_surround.cc b/044space_surround.cc
index 8efda681..310672be 100644
--- a/044space_surround.cc
+++ b/044space_surround.cc
@@ -8,24 +8,24 @@
 # location 1 in space 1 refers to the space surrounding the default space, here 20.
 def main [
   # pretend address:array:location; in practice we'll use 'new'
-  11:num <- copy 5  # length
+  10:num <- copy 5  # length
   # pretend address:array:location; in practice we'll use 'new"
-  21:num <- copy 5  # length
+  20:num <- copy 5  # length
   # actual start of this recipe
-  default-space:space <- copy 10/unsafe/skip-alloc-id
+  default-space:space <- copy 10/unsafe
   #: later layers will explain the /names: property
-  0:space/names:dummy <- copy 20/unsafe/skip-alloc-id
-  2:num <- copy 32
-  2:num/space:1 <- copy 33
+  0:space/names:dummy <- copy 20/unsafe
+  1:num <- copy 32
+  1:num/space:1 <- copy 33
 ]
 def dummy [  # just for the /names: property above
 ]
-# write chained space: 10 + (alloc id for default-space) 1 + (length) 1 + (alloc id for chained space) 1
-+mem: storing 20 in location 13
-# store to inside default space: 10 + (alloc id) 1 + (length) 1 + (index) 2
-+mem: storing 32 in location 14
-# store to inside chained space: (contents of location 12) 20 + (alloc id) 1 + (length) 1 + (index) 2
-+mem: storing 33 in location 24
+# 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
 
 //: If you think of a space as a collection of variables with a common
 //: lifetime, surrounding allows managing shorter lifetimes inside a longer
@@ -33,17 +33,14 @@ 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+/*skip alloc id*/1) : 0;
+  int base = current_call().default_space ? current_call().default_space : 0;
   return space_base(x, space_index(x), base);
 }
 
 int space_base(const reagent& x, int space_index, int base) {
-  trace("space") << "default_space is at location " << base << " with " << space_index << " chained spaces to go" << end();
   if (space_index == 0)
     return base;
-  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);
+  return space_base(x, space_index-1, get_or_insert(Memory, base+/*skip length*/1));
 }
 
 int space_index(const reagent& x) {
diff --git a/045closure_name.cc b/045closure_name.cc
index f5c8d2aa..e478337d 100644
--- a/045closure_name.cc
+++ b/045closure_name.cc
@@ -9,9 +9,9 @@
 :(scenario closure)
 def main [
   default-space:space <- new location:type, 30
-  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
+  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
 ]
 def new-counter [
   default-space:space <- new location:type, 30
@@ -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 11
++mem: storing 5 in location 3
 
 //: To make this work, compute the recipe that provides names for the
 //: surrounding space of each recipe.
diff --git a/046check_type_by_name.cc b/046check_type_by_name.cc
index 8a023024..df5a2a6d 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 10
+  x:num <- copy 1
   y:num <- add x, 1
 ]
-# x is in location 2, y in location 3
-+mem: storing 11 in location 3
+# x is in location 1, y in location 2
++mem: storing 2 in location 2
 
 :(scenario transform_fills_in_missing_types_in_product)
 def main [
-  x:num <- copy 10
-  x <- copy 11
+  x:num <- copy 1
+  x <- copy 2
 ]
-# x is in location 2
-+mem: storing 11 in location 2
+# x is in location 1
++mem: storing 2 in location 1
 
 :(scenario transform_fills_in_missing_types_in_product_and_ingredient)
 def main [
-  x:num <- copy 10
+  x:num <- copy 1
   x <- add x, 1
 ]
 # x is in location 1
-+mem: storing 11 in location 2
++mem: storing 2 in location 1
 
 :(scenario transform_fills_in_missing_label_type)
 def main [
diff --git a/050scenario.cc b/050scenario.cc
index c4f2541b..e84a18e9 100644
--- a/050scenario.cc
+++ b/050scenario.cc
@@ -79,7 +79,6 @@ Scenario_names = Scenario_names_snapshot;
 :(before "End Command Handlers")
 else if (command == "scenario") {
   scenario result = parse_scenario(in);
-//?   result.name.clear();  // disable running scenarios
   if (!result.name.empty())
     Scenarios.push_back(result);
 }
diff --git a/053recipe_header.cc b/053recipe_header.cc
index b948ce61..057234f9 100644
--- a/053recipe_header.cc
+++ b/053recipe_header.cc
@@ -207,9 +207,6 @@ case NEXT_INGREDIENT_WITHOUT_TYPECHECKING: {
   if (current_call().next_ingredient_to_process < SIZE(current_call().ingredient_atoms)) {
     products.push_back(
         current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
-    if (is_mu_scalar(current_call().ingredients.at(current_call().next_ingredient_to_process))
-        && is_mu_address(current_instruction().products.at(0)))
-      products.at(0).insert(products.at(0).begin(), /*alloc id*/0);
     assert(SIZE(products) == 1);  products.resize(2);  // push a new vector
     products.at(1).push_back(1);
     ++current_call().next_ingredient_to_process;
diff --git a/055shape_shifting_container.cc b/055shape_shifting_container.cc
index 3bf1fe80..0e7409d8 100644
--- a/055shape_shifting_container.cc
+++ b/055shape_shifting_container.cc
@@ -295,12 +295,12 @@ container foo:_a:_b [
   y:_b
 ]
 def main [
-  10:text <- new [abc]
-  {20: (foo number (address array character))} <- merge 34/x, 10:text/y
-  30:text <- get {20: (foo number (address array character))}, y:offset
-  40:bool <- equal 10:text, 30:text
+  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
 ]
-+mem: storing 1 in location 40
++mem: storing 1 in location 4
 
 :(before "End element_type Special-cases")
 replace_type_ingredients(element, type, info, " while computing element type of container");
@@ -346,8 +346,8 @@ exclusive-container foo:_a [
 ]
 def main [
   1:text <- new [abc]
-  3:foo:point <- merge 0/variant, 34/xx, 35/xy
-  10:point, 20:bool <- maybe-convert 3:foo:point, 0/variant
+  2:foo:point <- merge 0/variant, 34/xx, 35/xy
+  10:point, 20:bool <- maybe-convert 2:foo:point, 0/variant
 ]
 +mem: storing 1 in location 20
 +mem: storing 35 in location 11
diff --git a/058to_text.cc b/058to_text.cc
index 9cb14e14..8c86e36c 100644
--- a/058to_text.cc
+++ b/058to_text.cc
@@ -18,7 +18,6 @@ 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 95e38924..f4ed9b4c 100644
--- a/060rewrite_literal_string.cc
+++ b/060rewrite_literal_string.cc
@@ -19,7 +19,6 @@ 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 b299fda7..037cb923 100644
--- a/065duplex_list.mu
+++ b/065duplex_list.mu
@@ -393,17 +393,12 @@ def remove-between start:&:duplex-list:_elem, end:&:duplex-list:_elem/contained-
   # start->next = end
   *next <- put *next, prev:offset, 0
   *start <- put *start, next:offset, end
-  {
-    break-if end
-    stash [spliced:] next
-    return
-  }
+  return-unless end
   # 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, 0
-  stash [spliced:] next
   *end <- put *end, prev:offset, start
 ]
 
@@ -436,9 +431,6 @@ scenario remove-range [
     12 <- 15
     20 <- 0
   ]
-  trace-should-contain [
-    app: spliced: 16 <-> 17 <-> 18
-  ]
 ]
 
 scenario remove-range-to-final [
@@ -474,49 +466,6 @@ 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, 0
-  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 1d2f706e..4400c1e8 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+/*skip alloc id*/1));
+  string input = read_mu_text(get_or_insert(Memory, r.value));
   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]
-  3:num <- hash 1:text
-  4:num <- hash_old 1:text
-  5:bool <- equal 3:num, 4:num
+  2:num <- hash 1:text
+  3:num <- hash_old 1:text
+  4:bool <- equal 2:num, 3:num
 ]
-+mem: storing 1 in location 5
++mem: storing 1 in location 4
 
 :(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(/*skip alloc id*/1));
+  string input = read_mu_text(ingredients.at(0).at(0));
   size_t h = 0 ;
 
   for (int i = 0;  i < SIZE(input);  ++i) {
diff --git a/074wait.cc b/074wait.cc
index 47bbb0cc..eb17c8aa 100644
--- a/074wait.cc
+++ b/074wait.cc
@@ -262,23 +262,22 @@ def main [
 :(scenario get_location_indirect)
 # 'get-location' can read from container address
 def main [
-  1:&:point <- copy 10/unsafe
-  # skip alloc id
-  11:num <- copy 34
-  12:num <- copy 35
+  1:num <- copy 10
+  10:num <- copy 34
+  11:num <- copy 35
   4:location <- get-location 1:&:point/lookup, 0:offset
 ]
-+mem: storing 11 in location 4
++mem: storing 10 in location 4
 
 :(scenario get_location_indirect_2)
 def main [
-  1:&:point <- copy 10/unsafe
-  11:num <- copy 34
-  12:num <- copy 35
+  1:num <- copy 10
+  10:num <- copy 34
+  11:num <- copy 35
   4:&:num <- copy 20/unsafe
   4:&:location/lookup <- get-location 1:&:point/lookup, 0:offset
 ]
-+mem: storing 11 in location 21
++mem: storing 10 in location 20
 
 //: allow waiting on a routine to complete
 
diff --git a/082scenario_screen.cc b/082scenario_screen.cc
index dc015fed..31cbfcc9 100644
--- a/082scenario_screen.cc
+++ b/082scenario_screen.cc
@@ -145,14 +145,8 @@ assert(Next_predefined_global_for_scenarios < Reserved_for_tests);
 
 :(before "End Globals")
 // Scenario Globals.
-extern const int SCREEN = next_predefined_global_for_scenarios(/*size_of(address:screen)*/2);
+extern const int SCREEN = Next_predefined_global_for_scenarios++;
 // 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)")
@@ -256,27 +250,19 @@ struct raw_string_stream {
 
 :(code)
 void check_screen(const string& expected_contents, const int color) {
-  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';
+  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);
   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;
@@ -399,25 +385,18 @@ case _DUMP_SCREEN: {
 
 :(code)
 void dump_screen() {
-  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';
+  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);
   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 31aa4fe7..2c3ab4bc 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(/*size_of(address:console)*/2);
+extern const int CONSOLE = Next_predefined_global_for_scenarios++;
 //: 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+/*skip alloc id*/1, num_events);
-  int curr_address = event_data_address + /*skip alloc id*/1 + /*skip length*/1;
+  put(Memory, event_data_address, num_events);
+  int curr_address = event_data_address + /*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+/*skip alloc id*/1+size);
+  assert(curr_address == event_data_address+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+/*skip alloc id*/1, console_address);
+  put(Memory, CONSOLE, console_address);
   trace("mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end();
-  put(Memory, console_address+/*skip alloc id*/1+/*offset of 'data' in container 'events'*/1+/*skip alloc id of 'data'*/1, event_data_address);
+  put(Memory, console_address+/*offset of 'data' in container 'events'*/1, event_data_address);
   break;
 }
 
diff --git a/087file.cc b/087file.cc
index 9fd056db..44da9b02 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(/*skip alloc id*/1));
+  string filename = read_mu_text(ingredients.at(0).at(0));
   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(/*skip alloc id*/1));
+  string filename = read_mu_text(ingredients.at(0).at(0));
   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 bacb61be..f14534ac 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(/*size_of(address:resources)*/2);
+extern const int RESOURCES = Next_predefined_global_for_scenarios++;
 //: give 'resources' a fixed location in scenarios
 :(before "End Special Scenario Variable Names(r)")
 Name[r]["resources"] = RESOURCES;
@@ -203,28 +203,26 @@ 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) * /*size of resource*/4 + /*array length*/1);
-  int curr = resources_data_address + /*skip alloc id*/1 + /*skip array length*/1;
+  int resources_data_address = allocate(SIZE(contents)*2 + /*array length*/1);
+  int curr = resources_data_address + /*skip 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 + /*skip alloc id of resources.data*/1;
-  put(Memory, curr, SIZE(contents));  // array length
+  curr = resources_data_address;
+  put(Memory, curr, SIZE(contents));  // size of array
   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+/*alloc id*/1+/*offset of 'data' element*/1+/*skip alloc id of 'data' element*/1;
+  curr = resources_address+/*offset 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+/*skip alloc id*/1, resources_address);
+  put(Memory, RESOURCES, resources_address);
   trace("mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end();
 }
 
diff --git a/091socket.cc b/091socket.cc
index a0f3b948..7b6ca5b1 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(/*skip alloc id*/1));
+  string host = read_mu_text(ingredients.at(0).at(0));
   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 6d866b60..a0b827e9 100644
--- a/101run_sandboxed.cc
+++ b/101run_sandboxed.cc
@@ -3,23 +3,20 @@
 
 :(scenario run_interactive_code)
 def main [
-  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
+  1:num <- copy 0
+  2:text <- new [1:num/raw <- copy 34]
+  run-sandboxed 2:text
+  3:num <- copy 1:num
 ]
-#? ?
-+mem: storing 34 in location 20
++mem: storing 34 in location 3
 
 :(scenario run_interactive_empty)
 def main [
-  10:text <- copy 0/unsafe
-  20:text <- run-sandboxed 10:text
+  1:text <- copy 0/unsafe
+  2:text <- run-sandboxed 1:text
 ]
 # result is null
-+mem: storing 0 in location 20
-+mem: storing 0 in location 21
++mem: storing 0 in location 2
 
 //: As the name suggests, 'run-sandboxed' will prevent certain operations that
 //: regular Mu code can perform.
@@ -55,16 +52,12 @@ case RUN_SANDBOXED: {
 }
 :(before "End Primitive Recipe Implementations")
 case RUN_SANDBOXED: {
-  bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(/*skip alloc id*/1));
+  bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
   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();
@@ -97,7 +90,6 @@ 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
@@ -106,7 +98,6 @@ 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;
@@ -222,20 +213,15 @@ 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" +
@@ -295,7 +281,6 @@ 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;
 }
@@ -311,7 +296,6 @@ 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;
 }
@@ -327,7 +311,6 @@ 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;
 }
@@ -349,64 +332,64 @@ case _CLEANUP_RUN_SANDBOXED: {
 :(scenario "run_interactive_converts_result_to_text")
 def main [
   # try to interactively add 2 and 2
-  10:text <- new [add 2, 2]
-  20:text <- run-sandboxed 10:text
-  30:@:char <- copy *20:text
+  1:text <- new [add 2, 2]
+  2:text <- run-sandboxed 1:text
+  10:@:char <- copy *2:text
 ]
 # first letter in the output should be '4' in unicode
-+mem: storing 52 in location 31
++mem: storing 52 in location 11
 
 :(scenario "run_interactive_ignores_products_in_nested_functions")
 def main [
-  10:text <- new [foo]
-  20:text <- run-sandboxed 10:text
-  30:@:char <- copy *20:text
+  1:text <- new [foo]
+  2:text <- run-sandboxed 1:text
+  10:@:char <- copy *2:text
 ]
 def foo [
-  40:num <- copy 1234
+  20:num <- copy 1234
   {
     break
     reply 5678
   }
 ]
 # no product should have been tracked
-+mem: storing 0 in location 30
++mem: storing 0 in location 10
 
 :(scenario "run_interactive_ignores_products_in_previous_instructions")
 def main [
-  10:text <- new [
+  1:text <- new [
     add 1, 1  # generates a product
     foo]  # no products
-  20:text <- run-sandboxed 10:text
-  30:@:char <- copy *20:text
+  2:text <- run-sandboxed 1:text
+  10:@:char <- copy *2:text
 ]
 def foo [
-  40:num <- copy 1234
+  20:num <- copy 1234
   {
     break
     reply 5678
   }
 ]
 # no product should have been tracked
-+mem: storing 0 in location 30
++mem: storing 0 in location 10
 
 :(scenario "run_interactive_remembers_products_before_final_label")
 def main [
-  10:text <- new [
+  1:text <- new [
     add 1, 1  # generates a product
     +foo]  # no products
-  20:text <- run-sandboxed 10:text
-  30:@:char <- copy *20:text
+  2:text <- run-sandboxed 1:text
+  10:@:char <- copy *2:text
 ]
 def foo [
-  40:num <- copy 1234
+  20:num <- copy 1234
   {
     break
     reply 5678
   }
 ]
 # product tracked
-+mem: storing 50 in location 31
++mem: storing 50 in location 11
 
 :(scenario "run_interactive_returns_text")
 def main [
@@ -416,42 +399,38 @@ def main [
     y:text <- new [b]
     z:text <- append x:text, y:text
   ]
-  10:text <- run-sandboxed 1:text
-#?   $print 10:text 10/newline
-  20:@:char <- copy *10:text
+  2:text <- run-sandboxed 1:text
+  10:@:char <- copy *2:text
 ]
 # output contains "ab"
-#? ?
-+mem: storing 97 in location 21
-+mem: storing 98 in location 22
++mem: storing 97 in location 11
++mem: storing 98 in location 12
 
 :(scenario "run_interactive_returns_errors")
 def main [
   # run a command that generates an error
-  10:text <- new [x:num <- copy 34
+  1:text <- new [x:num <- copy 34
 get x:num, foo:offset]
-  20:text, 30:text <- run-sandboxed 10:text
-  40:@:char <- copy *30:text
+  2:text, 3:text <- run-sandboxed 1:text
+  10:@:char <- copy *3:text
 ]
 # error should be "unknown element foo in container number"
-+mem: storing 117 in location 41
-+mem: storing 110 in location 42
-+mem: storing 107 in location 43
-+mem: storing 110 in location 44
++mem: storing 117 in location 11
++mem: storing 110 in location 12
++mem: storing 107 in location 13
++mem: storing 110 in location 14
 # ...
 
 :(scenario run_interactive_with_comment)
 def main [
   # 2 instructions, with a comment after the first
-  10:text <- new [a:num <- copy 0  # abc
+  1:&:@:num <- new [a:num <- copy 0  # abc
 b:num <- copy 0
 ]
-  20:text, 30:text <- run-sandboxed 10:text
+  2:text, 3:text <- run-sandboxed 1:text
 ]
 # no errors
-# skip alloc id
-+mem: storing 0 in location 30
-+mem: storing 0 in location 31
++mem: storing 0 in location 3
 
 :(after "Running One Instruction")
 if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at
@@ -462,7 +441,6 @@ 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) {
@@ -480,8 +458,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 (SIZE(products.at(i)) != 2) continue;  // weak silent check for address
-        out << read_mu_text(products.at(i).at(/*skip alloc id*/1)) << '\n';
+        if (!scalar(products.at(i))) continue;  // error handled elsewhere
+        out << read_mu_text(products.at(i).at(0)) << '\n';
         continue;
       }
     }
@@ -584,7 +562,6 @@ 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;
diff --git a/edit/001-editor.mu b/edit/001-editor.mu
index 036ef07a..8855395a 100644
--- a/edit/001-editor.mu
+++ b/edit/001-editor.mu
@@ -81,20 +81,18 @@ scenario editor-initializes-without-data [
   assume-screen 5/width, 3/height
   run [
     e:&:editor <- new-editor 0/data, 2/left, 5/right
-    1:editor/raw <- copy *e
+    2:editor/raw <- copy *e
   ]
   memory-should-contain [
-    # 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
+    # 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
   ]
   screen-should-contain [
     .     .
diff --git a/edit/002-typing.mu b/edit/002-typing.mu
index 67fe76a0..47885c4f 100644
--- a/edit/002-typing.mu
+++ b/edit/002-typing.mu
@@ -280,11 +280,7 @@ scenario editor-handles-empty-event-queue [
   assume-screen 10/width, 5/height
   e:&:editor <- new-editor [abc], 0/left, 10/right
   editor-render screen, e
-#?   x:num <- get *screen, num-rows:offset
-#?   $print [a: ] x 10/newline
   assume-console []
-#?   x:num <- get *screen, num-rows:offset
-#?   $print [z: ] x 10/newline
   run [
     editor-event-loop screen, console, e
   ]
diff --git a/edit/003-shortcuts.mu b/edit/003-shortcuts.mu
index 78c6e49f..02ea77d0 100644
--- a/edit/003-shortcuts.mu
+++ b/edit/003-shortcuts.mu
@@ -2006,13 +2006,7 @@ after <handle-special-character> [
     delete-to-start-of-line?:bool <- equal c, 21/ctrl-u
     break-unless delete-to-start-of-line?
     <begin-delete-to-start-of-line>
-    $print [before: ] cursor-row [ ] cursor-column 10/newline
     deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor
-    x:text <- to-text deleted-cells
-    $print x 10/newline
-    cursor-row <- get *editor, cursor-row:offset
-    cursor-column <- get *editor, cursor-column:offset
-    $print [after: ] cursor-row [ ] cursor-column 10/newline
     <end-delete-to-start-of-line>
     go-render?:bool <- minimal-render-for-ctrl-u screen, editor, deleted-cells
     return
@@ -2022,7 +2016,6 @@ after <handle-special-character> [
 def minimal-render-for-ctrl-u screen:&:screen, editor:&:editor, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [
   local-scope
   load-inputs
-  $print [minimal render for ctrl-u] 10/newline
   curr-column:num <- get *editor, cursor-column:offset
   # accumulate the current line as text and render it
   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
@@ -2032,7 +2025,6 @@ def minimal-render-for-ctrl-u screen:&:screen, editor:&:editor, deleted-cells:&:
   {
     # if we have a wrapped line, give up and render the whole screen
     wrap?:bool <- greater-or-equal i, right
-    $print [wrap? ] wrap? 10/newline
     return-if wrap?, 1/go-render
     curr <- next curr
     break-unless curr
@@ -2069,7 +2061,6 @@ def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor
   {
     at-start-of-text?:bool <- equal start, init
     break-if at-start-of-text?
-    $print [0] 10/newline
     curr:char <- get *start, value:offset
     at-start-of-line?:bool <- equal curr, 10/newline
     break-if at-start-of-line?
@@ -2080,23 +2071,14 @@ def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor
     assert start, [delete-to-start-of-line tried to move before start of text]
     loop
   }
-  $print [1] 10/newline
   # snip it out
   result:&:duplex-list:char <- next start
-  x:text <- to-text start
-  $print [start: ] x 10/newline
-  x:text <- to-text end
-  $print [end: ] x 10/newline
   remove-between start, end
-  x:text <- to-text result
-  $print [snip: ] x 10/newline
   # update top-of-screen if it's just been invalidated
   {
     break-unless update-top-of-screen?
-    $print [2] 10/newline
     put *editor, top-of-screen:offset, start
   }
-  $print [3] 10/newline
   # adjust cursor
   before-cursor <- copy start
   *editor <- put *editor, before-cursor:offset, before-cursor
@@ -2107,22 +2089,17 @@ def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor
   width:num <- subtract right, left
   num-deleted:num <- length result
   cursor-row-adjustment:num <- divide-with-remainder num-deleted, width
-  $print [adj ] num-deleted [/] width [=] cursor-row-adjustment 10/newline
   return-unless cursor-row-adjustment
-  $print [4] 10/newline
   cursor-row:num <- get *editor, cursor-row:offset
   cursor-row-in-editor:num <- subtract cursor-row, 1  # ignore menubar
   at-top?:bool <- lesser-or-equal cursor-row-in-editor, cursor-row-adjustment
   {
     break-unless at-top?
-    $print [5] 10/newline
     cursor-row <- copy 1  # top of editor, below menubar
   }
   {
     break-if at-top?
-    $print [6] 10/newline
     cursor-row <- subtract cursor-row, cursor-row-adjustment
-    $print cursor-row 10/newline
   }
   put *editor, cursor-row:offset, cursor-row
 ]
diff --git a/index.html b/index.html
index dde93753..88e9b14a 100644
--- a/index.html
+++ b/index.html
@@ -158,16 +158,18 @@ 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, 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>.
+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>.
 
 <p/>Support for higher-order recipes that can pass <a href='html/072recipe.cc.html'>recipes</a>
 around like any other value.