about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-05-03 10:15:17 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-05-03 10:15:17 -0700
commitdc9afcbd7d7f1dcfae7b9ae659ccea4944b95a29 (patch)
tree90141172495d4f607d2ac03da912b98556b6686f
parent02909fecf6fba87604ff73fe3067e43e0ad068ee (diff)
downloadmu-dc9afcbd7d7f1dcfae7b9ae659ccea4944b95a29.tar.gz
2894
Reorganize the 'address' layer and split it up before we start greatly
expanding them to manage refcounts in nested objects.
-rw-r--r--034address.cc709
-rw-r--r--035refcount.cc74
-rw-r--r--036abandon.cc143
-rw-r--r--037lookup.cc460
-rw-r--r--038new_test.cc (renamed from 035new_text.cc)0
-rw-r--r--039location_array.cc (renamed from 036location_array.cc)0
6 files changed, 697 insertions, 689 deletions
diff --git a/034address.cc b/034address.cc
index 64618d13..17e89a85 100644
--- a/034address.cc
+++ b/034address.cc
@@ -114,41 +114,12 @@
 //: running out of memory, and you don't have to worry about ever leaving a
 //: dangling bookmark when you reclaim memory.
 //:
-//: Ok, let's rewind the clock back to this situation where we have an
-//: address:
+//: This layer implements creating addresses using 'new'. The next few layers
+//: will flesh out the rest of the life cycle.
 //:
-//:                     +---+------------+
-//:          x -------> | 1 |  number    |
-//:                     +---+------------+
-//:
-//: Once you have an address you can read or modify its payload by performing
-//: a lookup:
-//:
-//:     x/lookup <- copy 34
-//:
-//: or more concisely:
-//:
-//:     *x <- copy 34
-//:
-//: This modifies not x, but the payload x points to:
-//:
-//:                     +---+------------+
-//:          x -------> | 1 |         34 |
-//:                     +---+------------+
-//:
-//: You can also read from the payload in instructions like this:
-//:
-//:     z:number <- add *x, 1
-//:
-//: After this instruction runs the value of z will be 35.
-//:
-//: The rest of this (long) layer is divided up into 4 sections:
-//:   the implementation of the 'new' instruction
-//:   how instructions lookup addresses
-//:   how instructions update refcounts when modifying address variables
-//:   how instructions abandon and reclaim memory when refcounts drop to 0
+//: The tests in this layer use unsafe operations so as to stay decoupled from
+//: 'new'.
 
-//:: the 'new' instruction allocates unique memory including a refcount
 //: todo: give 'new' a custodian ingredient. Following malloc/free is a temporary hack.
 
 :(scenario new)
@@ -199,7 +170,7 @@ case NEW: {
 :(code)
 bool product_of_new_is_valid(const instruction& inst) {
   reagent product = inst.products.at(0);
-  canonize_type(product);
+  // Update NEW product in Check
   if (!product.type || product.type->value != get(Type_ordinal, "address"))
     return false;
   drop_from_type(product, "address");
@@ -213,6 +184,17 @@ bool product_of_new_is_valid(const instruction& inst) {
   return types_strictly_match(product, expected_product);
 }
 
+void drop_from_type(reagent& r, string expected_type) {
+  if (r.type->name != expected_type) {
+    raise << "can't drop2 " << expected_type << " from " << to_string(r) << '\n' << end();
+    return;
+  }
+  type_tree* tmp = r.type;
+  r.type = tmp->right;
+  tmp->right = NULL;
+  delete tmp;
+}
+
 //: To implement 'new', a Mu transform turns all 'new' instructions into
 //: 'allocate' instructions that precompute the amount of memory they want to
 //: allocate.
@@ -296,8 +278,10 @@ case ALLOCATE: {
   products.resize(1);
   products.at(0).push_back(result);
   // initialize allocated space
-  for (int address = result; address < result+size; ++address)
+  for (int address = result; address < result+size; ++address) {
+    trace(9999, "mem") << "storing 0 in location " << address << end();
     put(Memory, address, 0);
+  }
   if (SIZE(current_instruction().ingredients) > 1) {
     // initialize array length
     trace(9999, "mem") << "storing " << ingredients.at(1).at(0) << " in location " << result+/*skip refcount*/1 << end();
@@ -343,16 +327,8 @@ void ensure_space(int size) {
 % put(Memory, Memory_allocated_until, 1);
 def main [
   1:address:number <- new number:type
-  2:number <- copy 1:address:number/lookup
 ]
-+mem: storing 0 in location 2
-
-:(scenario new_error)
-% Hide_errors = true;
-def main [
-  1:number/raw <- new number:type
-]
-+error: main: product of 'new' has incorrect type: 1:number/raw <- new number:type
++mem: storing 0 in location 10
 
 :(scenario new_array)
 def main [
@@ -385,648 +361,3 @@ def main [
 ]
 +new: routine allocated memory from 1000 to 1003
 +new: routine allocated memory from 1003 to 1006
-
-//:: /lookup can go from an address to the payload it points at, skipping the refcount
-//: the tests in this section use unsafe operations so as to stay decoupled from 'new'
-
-:(scenario copy_indirect)
-def main [
-  1:address:number <- copy 10/unsafe
-  11:number <- copy 34
-  # This loads location 1 as an address and looks up *that* location.
-  2:number <- copy 1:address:number/lookup
-]
-# 1 contains 10. Skip refcount and lookup location 11.
-+mem: storing 34 in location 2
-
-:(before "End Preprocess read_memory(x)")
-canonize(x);
-
-//: similarly, write to addresses pointing at other locations using the
-//: 'lookup' property
-:(scenario store_indirect)
-def main [
-  1:address:number <- copy 10/unsafe
-  1:address:number/lookup <- copy 34
-]
-+mem: storing 34 in location 11
-
-:(before "End Preprocess write_memory(x)")
-canonize(x);
-if (x.value == 0) {
-  raise << "can't write to location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
-  return;
-}
-
-//: writes to address 0 always loudly fail
-:(scenario store_to_0_fails)
-% Hide_errors = true;
-def main [
-  1:address:number <- copy 0
-  1:address:number/lookup <- copy 34
-]
--mem: storing 34 in location 0
-+error: can't write to location 0 in '1:address:number/lookup <- copy 34'
-
-:(code)
-void canonize(reagent& x) {
-  if (is_literal(x)) return;
-  // End canonize(x) Special-cases
-  while (has_property(x, "lookup"))
-    lookup_memory(x);
-}
-
-void lookup_memory(reagent& x) {
-  if (!x.type || x.type->value != get(Type_ordinal, "address")) {
-    raise << maybe(current_recipe_name()) << "tried to /lookup " << x.original_string << " but it isn't an address\n" << end();
-    return;
-  }
-  // compute value
-  if (x.value == 0) {
-    raise << maybe(current_recipe_name()) << "tried to /lookup 0\n" << end();
-    return;
-  }
-  trace(9999, "mem") << "location " << x.value << " is " << no_scientific(get_or_insert(Memory, x.value)) << end();
-  x.set_value(get_or_insert(Memory, x.value));
-  drop_from_type(x, "address");
-  if (x.value != 0) {
-    trace(9999, "mem") << "skipping refcount at " << x.value << end();
-    x.set_value(x.value+1);  // skip refcount
-  }
-  drop_one_lookup(x);
-}
-
-void test_lookup_address_skips_refcount() {
-  reagent x("*x:address:number");
-  x.set_value(34);  // unsafe
-  put(Memory, 34, 1000);
-  lookup_memory(x);
-  CHECK_TRACE_CONTENTS("mem: skipping refcount at 1000");
-  CHECK_EQ(x.value, 1001);
-}
-
-void test_lookup_zero_address_does_not_skip_refcount() {
-  reagent x("*x:address:number");
-  x.set_value(34);  // unsafe
-  put(Memory, 34, 0);
-  lookup_memory(x);
-  CHECK_TRACE_DOESNT_CONTAIN("mem: skipping refcount at 0");
-  CHECK_EQ(x.value, 0);
-}
-
-:(after "bool types_strictly_match(reagent to, reagent from)")
-  if (!canonize_type(to)) return false;
-  if (!canonize_type(from)) return false;
-
-:(after "bool is_mu_array(reagent r)")
-  if (!canonize_type(r)) return false;
-
-:(after "bool is_mu_address(reagent r)")
-  if (!canonize_type(r)) return false;
-
-:(after "bool is_mu_number(reagent r)")
-  if (!canonize_type(r)) return false;
-:(after "bool is_mu_boolean(reagent r)")
-  if (!canonize_type(r)) return false;
-
-:(after "Update product While Type-checking Merge")
-if (!canonize_type(product)) continue;
-
-:(before "End Compute Call Ingredient")
-canonize_type(ingredient);
-:(before "End Preprocess NEXT_INGREDIENT product")
-canonize_type(product);
-:(before "End Check RETURN Copy(lhs, rhs)
-canonize_type(lhs);
-canonize_type(rhs);
-
-:(before "Compute Container Metadata(reagent rcopy)")
-if (!canonize_type(rcopy)) return;
-
-:(before "Compute Container Metadata(element)")
-assert(!has_property(element, "lookup"));
-
-:(code)
-bool canonize_type(reagent& r) {
-  while (has_property(r, "lookup")) {
-    if (!r.type || r.type->value != get(Type_ordinal, "address")) {
-      raise << "can't lookup non-address: " << to_string(r) << ": " << to_string(r.type) << '\n' << end();
-      return false;
-    }
-    drop_from_type(r, "address");
-    drop_one_lookup(r);
-  }
-  return true;
-}
-
-void drop_from_type(reagent& r, string expected_type) {
-  if (r.type->name != expected_type) {
-    raise << "can't drop2 " << expected_type << " from " << to_string(r) << '\n' << end();
-    return;
-  }
-  type_tree* tmp = r.type;
-  r.type = tmp->right;
-  tmp->right = NULL;
-  delete tmp;
-}
-
-void drop_one_lookup(reagent& r) {
-  for (vector<pair<string, string_tree*> >::iterator p = r.properties.begin(); p != r.properties.end(); ++p) {
-    if (p->first == "lookup") {
-      r.properties.erase(p);
-      return;
-    }
-  }
-  assert(false);
-}
-
-//: Tedious fixup to support addresses in container/array instructions of previous layers.
-//: Most instructions don't require fixup if they use the 'ingredients' and
-//: 'products' variables in run_current_routine().
-
-:(scenario get_indirect)
-def main [
-  1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:number <- copy 34
-  12:number <- copy 35
-  2:number <- get 1:address:point/lookup, 0:offset
-]
-+mem: storing 34 in location 2
-
-:(scenario get_indirect2)
-def main [
-  1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:number <- copy 34
-  12:number <- copy 35
-  2:address:number <- copy 20/unsafe
-  2:address:number/lookup <- get 1:address:point/lookup, 0:offset
-]
-+mem: storing 34 in location 21
-
-:(scenario include_nonlookup_properties)
-def main [
-  1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:number <- copy 34
-  12:number <- copy 35
-  2:number <- get 1:address:point/lookup/foo, 0:offset
-]
-+mem: storing 34 in location 2
-
-:(after "Update GET base in Check")
-if (!canonize_type(base)) break;
-:(after "Update GET product in Check")
-if (!canonize_type(product)) break;
-:(after "Update GET base in Run")
-canonize(base);
-
-:(scenario put_indirect)
-def main [
-  1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:number <- copy 34
-  12:number <- copy 35
-  1:address:point/lookup <- put 1:address:point/lookup, 0:offset, 36
-]
-+mem: storing 36 in location 11
-
-:(after "Update PUT base in Check")
-if (!canonize_type(base)) break;
-:(after "Update PUT offset in Check")
-if (!canonize_type(offset)) break;
-:(after "Update PUT base in Run")
-canonize(base);
-
-:(scenario copy_array_indirect)
-def main [
-  # 10 reserved for refcount
-  11:array:number:3 <- create-array
-  12:number <- copy 14
-  13:number <- copy 15
-  14:number <- copy 16
-  1:address:array:number <- copy 10/unsafe
-  2:array:number <- copy 1:address:array:number/lookup
-]
-+mem: storing 3 in location 2
-+mem: storing 14 in location 3
-+mem: storing 15 in location 4
-+mem: storing 16 in location 5
-
-:(before "Update CREATE_ARRAY product in Check")
-// 'create-array' does not support indirection. Static arrays are meant to be
-// allocated on the 'stack'.
-assert(!has_property(product, "lookup"));
-:(before "Update CREATE_ARRAY product in Run")
-// 'create-array' does not support indirection. Static arrays are meant to be
-// allocated on the 'stack'.
-assert(!has_property(product, "lookup"));
-
-:(scenario index_indirect)
-def main [
-  # 10 reserved for refcount
-  11:array:number:3 <- create-array
-  12:number <- copy 14
-  13:number <- copy 15
-  14:number <- copy 16
-  1:address:array:number <- copy 10/unsafe
-  2:number <- index 1:address:array:number/lookup, 1
-]
-+mem: storing 15 in location 2
-
-:(before "Update INDEX base in Check")
-if (!canonize_type(base)) break;
-:(before "Update INDEX index in Check")
-if (!canonize_type(index)) break;
-:(before "Update INDEX product in Check")
-if (!canonize_type(product)) break;
-
-:(before "Update INDEX base in Run")
-canonize(base);
-:(before "Update INDEX index in Run")
-canonize(index);
-
-:(scenario put_index_indirect)
-def main [
-  # 10 reserved for refcount
-  11:array:number:3 <- create-array
-  12:number <- copy 14
-  13:number <- copy 15
-  14:number <- copy 16
-  1:address:array:number <- copy 10/unsafe
-  1:address:array:number/lookup <- put-index 1:address:array:number/lookup, 1, 34
-]
-+mem: storing 34 in location 13
-
-:(scenario put_index_indirect_2)
-def main [
-  1:array:number:3 <- create-array
-  2:number <- copy 14
-  3:number <- copy 15
-  4:number <- copy 16
-  5:address:number <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:number <- copy 1
-  5:address:array:number/lookup <- put-index 1:array:number:3, 5:address:number/lookup, 34
-]
-+mem: storing 34 in location 3
-
-:(before "Update PUT_INDEX base in Check")
-if (!canonize_type(base)) break;
-:(before "Update PUT_INDEX index in Check")
-if (!canonize_type(index)) break;
-:(before "Update PUT_INDEX value in Check")
-if (!canonize_type(value)) break;
-
-:(before "Update PUT_INDEX base in Run")
-canonize(base);
-:(before "Update PUT_INDEX index in Run")
-canonize(index);
-
-:(scenario length_indirect)
-def main [
-  # 10 reserved for refcount
-  11:array:number:3 <- create-array
-  12:number <- copy 14
-  13:number <- copy 15
-  14:number <- copy 16
-  1:address:array:number <- copy 10/unsafe
-  2:number <- length 1:address:array:number/lookup
-]
-+mem: storing 3 in location 2
-
-:(before "Update LENGTH array in Check")
-if (!canonize_type(array)) break;
-:(before "Update LENGTH array in Run")
-canonize(array);
-
-:(scenario maybe_convert_indirect)
-def main [
-  # 10 reserved for refcount
-  11:number-or-point <- merge 0/number, 34
-  1:address:number-or-point <- copy 10/unsafe
-  2:number, 3:boolean <- maybe-convert 1:address:number-or-point/lookup, i:variant
-]
-+mem: storing 34 in location 2
-+mem: storing 1 in location 3
-
-:(scenario maybe_convert_indirect_2)
-def main [
-  # 10 reserved for refcount
-  11:number-or-point <- merge 0/number, 34
-  1:address:number-or-point <- copy 10/unsafe
-  2:address:number <- copy 20/unsafe
-  2:address:number/lookup, 3:boolean <- maybe-convert 1:address:number-or-point/lookup, i:variant
-]
-+mem: storing 34 in location 21
-+mem: storing 1 in location 3
-
-:(scenario maybe_convert_indirect_3)
-def main [
-  # 10 reserved for refcount
-  11:number-or-point <- merge 0/number, 34
-  1:address:number-or-point <- copy 10/unsafe
-  2:address:boolean <- copy 20/unsafe
-  3:number, 2:address:boolean/lookup <- maybe-convert 1:address:number-or-point/lookup, i:variant
-]
-+mem: storing 34 in location 3
-+mem: storing 1 in location 21
-
-:(before "Update MAYBE_CONVERT base in Check")
-if (!canonize_type(base)) break;
-:(before "Update MAYBE_CONVERT product in Check")
-if (!canonize_type(product)) break;
-:(before "Update MAYBE_CONVERT status in Check")
-if (!canonize_type(status)) break;
-
-:(before "Update MAYBE_CONVERT base in Run")
-canonize(base);
-:(before "Update MAYBE_CONVERT product in Run")
-canonize(product);
-:(before "Update MAYBE_CONVERT status in Run")
-canonize(status);
-
-:(scenario merge_exclusive_container_indirect)
-def main [
-  1:address:number-or-point <- copy 10/unsafe
-  1:address:number-or-point/lookup <- merge 0/number, 34
-]
-# skip 10 for refcount
-+mem: storing 0 in location 11
-+mem: storing 34 in location 12
-
-:(before "Update size_mismatch Check for MERGE(x)
-canonize(x);
-
-//: abbreviation for '/lookup': a prefix '*'
-
-:(scenario lookup_abbreviation)
-def main [
-  1:address:number <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:number <- copy 34
-  3:number <- copy *1:address:number
-]
-+parse: ingredient: {1: ("address" "number"), "lookup": ()}
-+mem: storing 34 in location 3
-
-:(before "End Parsing reagent")
-{
-  while (!name.empty() && name.at(0) == '*') {
-    name.erase(0, 1);
-    properties.push_back(pair<string, string_tree*>("lookup", NULL));
-  }
-  if (name.empty())
-    raise << "illegal name " << original_string << '\n' << end();
-}
-
-//:: update refcounts when copying addresses
-
-:(scenario refcounts)
-def main [
-  1:address:number <- copy 1000/unsafe
-  2:address:number <- copy 1:address:number
-  1:address:number <- copy 0
-  2:address:number <- copy 0
-]
-+run: {1: ("address" "number")} <- copy {1000: "literal", "unsafe": ()}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: ("address" "number")} <- copy {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {1: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1000: 2 -> 1
-+run: {2: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1000: 1 -> 0
-# the /unsafe corrupts memory but fortunately we won't be running any more 'new' in this scenario
-+mem: automatically abandoning 1000
-
-:(before "End write_memory(reagent x) Special-cases")
-if (x.type->value == get(Type_ordinal, "address")) {
-  // compute old address of x, as well as new address we want to write in
-  int old_address = get_or_insert(Memory, x.value);
-  assert(scalar(data));
-  int new_address = data.at(0);
-  // decrement refcount of old address
-  if (old_address) {
-    int old_refcount = get_or_insert(Memory, old_address);
-    trace(9999, "mem") << "decrementing refcount of " << old_address << ": " << old_refcount << " -> " << (old_refcount-1) << end();
-    put(Memory, old_address, old_refcount-1);
-  }
-  // perform the write
-  trace(9999, "mem") << "storing " << no_scientific(data.at(0)) << " in location " << x.value << end();
-  put(Memory, x.value, new_address);
-  // increment refcount of new address
-  if (new_address) {
-    int new_refcount = get_or_insert(Memory, new_address);
-    assert(new_refcount >= 0);  // == 0 only when new_address == old_address
-    trace(9999, "mem") << "incrementing refcount of " << new_address << ": " << new_refcount << " -> " << (new_refcount+1) << end();
-    put(Memory, new_address, new_refcount+1);
-  }
-  // abandon old address if necessary
-  // do this after all refcount updates are done just in case old and new are identical
-  assert(old_address >= 0);
-  if (old_address == 0) return;
-  if (get_or_insert(Memory, old_address) < 0) {
-    DUMP("");
-    cerr << old_address << ' ' << get_or_insert(Memory, old_address) << '\n';
-  }
-  assert(get_or_insert(Memory, old_address) >= 0);
-  if (get_or_insert(Memory, old_address) > 0) return;
-  // lookup_memory without drop_one_lookup {
-  trace(9999, "mem") << "automatically abandoning " << old_address << end();
-  trace(9999, "mem") << "computing size to abandon at " << x.value << end();
-  x.set_value(old_address+/*skip refcount*/1);
-  drop_from_type(x, "address");
-  // }
-  abandon(old_address, size_of(x)+/*refcount*/1);
-  return;
-}
-
-:(scenario refcounts_2)
-def main [
-  1:address:number <- new number:type
-  # over-writing one allocation with another
-  1:address:number <- new number:type
-  1:address:number <- copy 0
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: automatically abandoning 1000
-
-:(scenario refcounts_3)
-def main [
-  1:address:number <- new number:type
-  # passing in addresses to recipes increments refcount
-  foo 1:address:number
-  1:address:number <- copy 0
-]
-def foo [
-  2:address:number <- next-ingredient
-  # return does NOT yet decrement refcount; memory must be explicitly managed
-  2:address:number <- copy 0
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: ("address" "number")} <- next-ingredient
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {2: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1000: 2 -> 1
-+run: {1: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1000: 1 -> 0
-+mem: automatically abandoning 1000
-
-:(scenario refcounts_4)
-def main [
-  1:address:number <- new number:type
-  # idempotent copies leave refcount unchanged
-  1:address:number <- copy 1:address:number
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {1: ("address" "number")} <- copy {1: ("address" "number")}
-+mem: decrementing refcount of 1000: 1 -> 0
-+mem: incrementing refcount of 1000: 0 -> 1
-
-:(scenario refcounts_5)
-def main [
-  1:address:number <- new number:type
-  # passing in addresses to recipes increments refcount
-  foo 1:address:number
-  # return does NOT yet decrement refcount; memory must be explicitly managed
-  1:address:number <- new number:type
-]
-def foo [
-  2:address:number <- next-ingredient
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: ("address" "number")} <- next-ingredient
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: decrementing refcount of 1000: 2 -> 1
-
-:(scenario refcounts_array)
-def main [
-  1:number <- copy 30
-  # allocate an array
-  10:address:array:number <- new number:type, 20
-  11:number <- copy 10:address:array:number
-  # allocate another array in its place, implicitly freeing the previous allocation
-  10:address:array:number <- new number:type, 25
-]
-+run: {10: ("address" "array" "number")} <- new {number: "type"}, {20: "literal"}
-# abandoned array is of old size (20, not 25)
-+abandon: saving in free-list of size 22
-
-//:: abandon and reclaim memory when refcount drops to 0
-
-:(scenario new_reclaim)
-def main [
-  1:address:number <- new number:type
-  2:number <- copy 1:address:number  # because 1 will get reset during abandon below
-  1:address:number <- copy 0  # abandon
-  3:address:number <- new number:type  # must be same size as abandoned memory to reuse
-  4:boolean <- equal 2:number, 3:address:number
-]
-# both allocations should have returned the same address
-+mem: storing 1 in location 4
-
-//: When abandoning addresses we'll save them to a 'free list', segregated by size.
-
-:(before "End routine Fields")
-map<int, int> free_list;
-
-:(code)
-void abandon(int address, int size) {
-  trace(9999, "abandon") << "saving in free-list of size " << size << end();
-//?   Total_free += size;
-//?   Num_free++;
-//?   cerr << "abandon: " << size << '\n';
-  // clear memory
-  for (int curr = address; curr < address+size; ++curr)
-    put(Memory, curr, 0);
-  // append existing free list to address
-  put(Memory, address, get_or_insert(Current_routine->free_list, size));
-  put(Current_routine->free_list, size, address);
-}
-
-:(before "ensure_space(size)" following "case ALLOCATE")
-if (get_or_insert(Current_routine->free_list, size)) {
-  trace(9999, "abandon") << "picking up space from free-list of size " << size << end();
-  int result = get_or_insert(Current_routine->free_list, size);
-  trace(9999, "mem") << "new alloc from free list: " << result << end();
-  put(Current_routine->free_list, size, get_or_insert(Memory, result));
-  for (int curr = result+1; curr < result+size; ++curr) {
-    if (get_or_insert(Memory, curr) != 0) {
-      raise << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end();
-      break;  // always fatal
-    }
-  }
-  if (SIZE(current_instruction().ingredients) > 1)
-    put(Memory, result+/*skip refcount*/1, ingredients.at(1).at(0));
-  else
-    put(Memory, result, 0);
-  products.resize(1);
-  products.at(0).push_back(result);
-  break;
-}
-
-:(scenario new_differing_size_no_reclaim)
-def main [
-  1:address:number <- new number:type
-  2:number <- copy 1:address:number
-  1:address:number <- copy 0  # abandon
-  3:address:array:number <- new number:type, 2  # different size
-  4:boolean <- equal 2:number, 3:address:array:number
-]
-# no reuse
-+mem: storing 0 in location 4
-
-:(scenario new_reclaim_array)
-def main [
-  1:address:array:number <- new number:type, 2
-  2:number <- copy 1:address:array:number
-  1:address:array:number <- copy 0  # abandon
-  3:address:array:number <- new number:type, 2  # same size
-  4:boolean <- equal 2:number, 3:address:array:number
-]
-# reuse
-+mem: storing 1 in location 4
-
-//:: helpers for debugging
-
-:(before "End Primitive Recipe Declarations")
-_DUMP,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$dump", _DUMP);
-:(before "End Primitive Recipe Implementations")
-case _DUMP: {
-  reagent after_canonize = current_instruction().ingredients.at(0);
-  canonize(after_canonize);
-  cerr << maybe(current_recipe_name()) << current_instruction().ingredients.at(0).name << ' ' << no_scientific(current_instruction().ingredients.at(0).value) << " => " << no_scientific(after_canonize.value) << " => " << no_scientific(get_or_insert(Memory, after_canonize.value)) << '\n';
-  break;
-}
-
-//: grab an address, and then dump its value at intervals
-//: useful for tracking down memory corruption (writing to an out-of-bounds address)
-:(before "End Globals")
-int Bar = -1;
-:(before "End Primitive Recipe Declarations")
-_BAR,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$bar", _BAR);
-:(before "End Primitive Recipe Implementations")
-case _BAR: {
-  if (current_instruction().ingredients.empty()) {
-    if (Bar != -1) cerr << Bar << ": " << no_scientific(get_or_insert(Memory, Bar)) << '\n';
-    else cerr << '\n';
-  }
-  else {
-    reagent tmp = current_instruction().ingredients.at(0);
-    canonize(tmp);
-    Bar = tmp.value;
-  }
-  break;
-}
diff --git a/035refcount.cc b/035refcount.cc
new file mode 100644
index 00000000..c0256c61
--- /dev/null
+++ b/035refcount.cc
@@ -0,0 +1,74 @@
+//: Update refcounts when copying addresses.
+//: The top of layer 34 has more on refcounts.
+
+:(scenario refcounts)
+def main [
+  1:address:number <- copy 1000/unsafe
+  2:address:number <- copy 1:address:number
+  1:address:number <- copy 0
+  2:address:number <- copy 0
+]
++run: {1: ("address" "number")} <- copy {1000: "literal", "unsafe": ()}
++mem: incrementing refcount of 1000: 0 -> 1
++run: {2: ("address" "number")} <- copy {1: ("address" "number")}
++mem: incrementing refcount of 1000: 1 -> 2
++run: {1: ("address" "number")} <- copy {0: "literal"}
++mem: decrementing refcount of 1000: 2 -> 1
++run: {2: ("address" "number")} <- copy {0: "literal"}
++mem: decrementing refcount of 1000: 1 -> 0
+
+:(before "End write_memory(reagent x) Special-cases")
+if (x.type->value == get(Type_ordinal, "address")) {
+  // compute old address of x, as well as new address we want to write in
+  int old_address = get_or_insert(Memory, x.value);
+  assert(scalar(data));
+  int new_address = data.at(0);
+  // decrement refcount of old address
+  if (old_address) {
+    int old_refcount = get_or_insert(Memory, old_address);
+    trace(9999, "mem") << "decrementing refcount of " << old_address << ": " << old_refcount << " -> " << (old_refcount-1) << end();
+    put(Memory, old_address, old_refcount-1);
+  }
+  // perform the write
+  trace(9999, "mem") << "storing " << no_scientific(data.at(0)) << " in location " << x.value << end();
+  put(Memory, x.value, new_address);
+  // increment refcount of new address
+  if (new_address) {
+    int new_refcount = get_or_insert(Memory, new_address);
+    assert(new_refcount >= 0);  // == 0 only when new_address == old_address
+    trace(9999, "mem") << "incrementing refcount of " << new_address << ": " << new_refcount << " -> " << (new_refcount+1) << end();
+    put(Memory, new_address, new_refcount+1);
+  }
+  // End Update Reference Count
+  return;
+}
+
+:(scenario refcounts_4)
+def main [
+  1:address:number <- new number:type
+  # idempotent copies leave refcount unchanged
+  1:address:number <- copy 1:address:number
+]
++run: {1: ("address" "number")} <- new {number: "type"}
++mem: incrementing refcount of 1000: 0 -> 1
++run: {1: ("address" "number")} <- copy {1: ("address" "number")}
++mem: decrementing refcount of 1000: 1 -> 0
++mem: incrementing refcount of 1000: 0 -> 1
+
+:(scenario refcounts_5)
+def main [
+  1:address:number <- new number:type
+  # passing in addresses to recipes increments refcount
+  foo 1:address:number
+  # return does NOT yet decrement refcount; memory must be explicitly managed
+  1:address:number <- new number:type
+]
+def foo [
+  2:address:number <- next-ingredient
+]
++run: {1: ("address" "number")} <- new {number: "type"}
++mem: incrementing refcount of 1000: 0 -> 1
++run: {2: ("address" "number")} <- next-ingredient
++mem: incrementing refcount of 1000: 1 -> 2
++run: {1: ("address" "number")} <- new {number: "type"}
++mem: decrementing refcount of 1000: 2 -> 1
diff --git a/036abandon.cc b/036abandon.cc
new file mode 100644
index 00000000..22d93ec9
--- /dev/null
+++ b/036abandon.cc
@@ -0,0 +1,143 @@
+//: Reclaiming memory when it's no longer used.
+//: The top of layer 34 has the complete life cycle of memory.
+
+:(scenario new_reclaim)
+def main [
+  1:address:number <- new number:type
+  2:number <- copy 1:address:number  # because 1 will get reset during abandon below
+  1:address:number <- copy 0  # abandon
+  3:address:number <- new number:type  # must be same size as abandoned memory to reuse
+  4:boolean <- equal 2:number, 3:address:number
+]
+# both allocations should have returned the same address
++mem: storing 1 in location 4
+
+:(before "End Update Reference Count")
+// abandon old address if necessary
+// do this after all refcount updates are done, just in case old and new are identical
+assert(old_address >= 0);
+if (old_address == 0) return;
+if (get_or_insert(Memory, old_address) < 0) {
+  tb_shutdown();
+  DUMP("");
+  cerr << "Negative refcount: " << old_address << ' ' << get_or_insert(Memory, old_address) << '\n';
+  exit(0);
+}
+if (get_or_insert(Memory, old_address) > 0) return;
+// old_address has a 0 refcount
+// lookup_memory without drop_one_lookup {
+trace(9999, "mem") << "automatically abandoning " << old_address << end();
+trace(9999, "mem") << "computing size to abandon at " << x.value << end();
+x.set_value(old_address+/*skip refcount*/1);
+drop_from_type(x, "address");
+// }
+abandon(old_address, size_of(x)+/*refcount*/1);
+
+//: When abandoning addresses we'll save them to a 'free list', segregated by size.
+
+:(before "End routine Fields")
+map<int, int> free_list;
+
+:(code)
+void abandon(int address, int size) {
+  trace(9999, "abandon") << "saving in free-list of size " << size << end();
+//?   Total_free += size;
+//?   Num_free++;
+//?   cerr << "abandon: " << size << '\n';
+  // clear memory
+  for (int curr = address; curr < address+size; ++curr)
+    put(Memory, curr, 0);
+  // append existing free list to address
+  put(Memory, address, get_or_insert(Current_routine->free_list, size));
+  put(Current_routine->free_list, size, address);
+}
+
+:(before "ensure_space(size)" following "case ALLOCATE")
+if (get_or_insert(Current_routine->free_list, size)) {
+  trace(9999, "abandon") << "picking up space from free-list of size " << size << end();
+  int result = get_or_insert(Current_routine->free_list, size);
+  trace(9999, "mem") << "new alloc from free list: " << result << end();
+  put(Current_routine->free_list, size, get_or_insert(Memory, result));
+  for (int curr = result+1; curr < result+size; ++curr) {
+    if (get_or_insert(Memory, curr) != 0) {
+      raise << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end();
+      break;  // always fatal
+    }
+  }
+  if (SIZE(current_instruction().ingredients) > 1)
+    put(Memory, result+/*skip refcount*/1, ingredients.at(1).at(0));
+  else
+    put(Memory, result, 0);
+  products.resize(1);
+  products.at(0).push_back(result);
+  break;
+}
+
+:(scenario new_differing_size_no_reclaim)
+def main [
+  1:address:number <- new number:type
+  2:number <- copy 1:address:number
+  1:address:number <- copy 0  # abandon
+  3:address:array:number <- new number:type, 2  # different size
+  4:boolean <- equal 2:number, 3:address:array:number
+]
+# no reuse
++mem: storing 0 in location 4
+
+:(scenario new_reclaim_array)
+def main [
+  1:address:array:number <- new number:type, 2
+  2:number <- copy 1:address:array:number
+  1:address:array:number <- copy 0  # abandon
+  3:address:array:number <- new number:type, 2  # same size
+  4:boolean <- equal 2:number, 3:address:array:number
+]
+# reuse
++mem: storing 1 in location 4
+
+:(scenario refcounts_2)
+def main [
+  1:address:number <- new number:type
+  # over-writing one allocation with another
+  1:address:number <- new number:type
+  1:address:number <- copy 0
+]
++run: {1: ("address" "number")} <- new {number: "type"}
++mem: incrementing refcount of 1000: 0 -> 1
++run: {1: ("address" "number")} <- new {number: "type"}
++mem: automatically abandoning 1000
+
+:(scenario refcounts_3)
+def main [
+  1:address:number <- new number:type
+  # passing in addresses to recipes increments refcount
+  foo 1:address:number
+  1:address:number <- copy 0
+]
+def foo [
+  2:address:number <- next-ingredient
+  # return does NOT yet decrement refcount; memory must be explicitly managed
+  2:address:number <- copy 0
+]
++run: {1: ("address" "number")} <- new {number: "type"}
++mem: incrementing refcount of 1000: 0 -> 1
++run: {2: ("address" "number")} <- next-ingredient
++mem: incrementing refcount of 1000: 1 -> 2
++run: {2: ("address" "number")} <- copy {0: "literal"}
++mem: decrementing refcount of 1000: 2 -> 1
++run: {1: ("address" "number")} <- copy {0: "literal"}
++mem: decrementing refcount of 1000: 1 -> 0
++mem: automatically abandoning 1000
+
+:(scenario refcounts_array)
+def main [
+  1:number <- copy 30
+  # allocate an array
+  10:address:array:number <- new number:type, 20
+  11:number <- copy 10:address:array:number  # doesn't increment refcount
+  # allocate another array in its place, implicitly freeing the previous allocation
+  10:address:array:number <- new number:type, 25
+]
++run: {10: ("address" "array" "number")} <- new {number: "type"}, {25: "literal"}
+# abandoned array is of old size (20, not 25)
++abandon: saving in free-list of size 22
diff --git a/037lookup.cc b/037lookup.cc
new file mode 100644
index 00000000..2a7cb2e3
--- /dev/null
+++ b/037lookup.cc
@@ -0,0 +1,460 @@
+//: Go from an address to the payload it points at (skipping the refcount)
+//: using /lookup.
+//:
+//: Let's say we have this address (read the top of layer 34 for addresses and
+//: such diagrams):
+//:
+//:                     +---+------------+
+//:          x -------> | 1 |  number    |
+//:                     +---+------------+
+//:
+//: Once you have an address you can read or modify its payload by performing
+//: a lookup:
+//:
+//:     x/lookup <- copy 34
+//:
+//: or more concisely:
+//:
+//:     *x <- copy 34
+//:
+//: This modifies not x, but the payload x points to:
+//:
+//:                     +---+------------+
+//:          x -------> | 1 |         34 |
+//:                     +---+------------+
+//:
+//: You can also read from the payload in instructions like this:
+//:
+//:     z:number <- add *x, 1
+//:
+//: After this instruction runs the value of z will be 35.
+//:
+//: The tests in this layer use unsafe operations so as to stay decoupled from
+//: 'new'.
+
+:(scenario copy_indirect)
+def main [
+  1:address:number <- copy 10/unsafe
+  11:number <- copy 34
+  # This loads location 1 as an address and looks up *that* location.
+  2:number <- copy 1:address:number/lookup
+]
+# 1 contains 10. Skip refcount and lookup location 11.
++mem: storing 34 in location 2
+
+:(before "End Preprocess read_memory(x)")
+canonize(x);
+
+//: similarly, write to addresses pointing at other locations using the
+//: 'lookup' property
+:(scenario store_indirect)
+def main [
+  1:address:number <- copy 10/unsafe
+  1:address:number/lookup <- copy 34
+]
++mem: storing 34 in location 11
+
+:(before "End Preprocess write_memory(x)")
+canonize(x);
+if (x.value == 0) {
+  raise << "can't write to location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
+  return;
+}
+
+//: writes to address 0 always loudly fail
+:(scenario store_to_0_fails)
+% Hide_errors = true;
+def main [
+  1:address:number <- copy 0
+  1:address:number/lookup <- copy 34
+]
+-mem: storing 34 in location 0
++error: can't write to location 0 in '1:address:number/lookup <- copy 34'
+
+:(code)
+void canonize(reagent& x) {
+  if (is_literal(x)) return;
+  // End canonize(x) Special-cases
+  while (has_property(x, "lookup"))
+    lookup_memory(x);
+}
+
+void lookup_memory(reagent& x) {
+  if (!x.type || x.type->value != get(Type_ordinal, "address")) {
+    raise << maybe(current_recipe_name()) << "tried to /lookup " << x.original_string << " but it isn't an address\n" << end();
+    return;
+  }
+  // compute value
+  if (x.value == 0) {
+    raise << maybe(current_recipe_name()) << "tried to /lookup 0\n" << end();
+    return;
+  }
+  trace(9999, "mem") << "location " << x.value << " is " << no_scientific(get_or_insert(Memory, x.value)) << end();
+  x.set_value(get_or_insert(Memory, x.value));
+  drop_from_type(x, "address");
+  if (x.value != 0) {
+    trace(9999, "mem") << "skipping refcount at " << x.value << end();
+    x.set_value(x.value+1);  // skip refcount
+  }
+  drop_one_lookup(x);
+}
+
+void test_lookup_address_skips_refcount() {
+  reagent x("*x:address:number");
+  x.set_value(34);  // unsafe
+  put(Memory, 34, 1000);
+  lookup_memory(x);
+  CHECK_TRACE_CONTENTS("mem: skipping refcount at 1000");
+  CHECK_EQ(x.value, 1001);
+}
+
+void test_lookup_zero_address_does_not_skip_refcount() {
+  reagent x("*x:address:number");
+  x.set_value(34);  // unsafe
+  put(Memory, 34, 0);
+  lookup_memory(x);
+  CHECK_TRACE_DOESNT_CONTAIN("mem: skipping refcount at 0");
+  CHECK_EQ(x.value, 0);
+}
+
+:(after "bool types_strictly_match(reagent to, reagent from)")
+  if (!canonize_type(to)) return false;
+  if (!canonize_type(from)) return false;
+
+:(after "bool is_mu_array(reagent r)")
+  if (!canonize_type(r)) return false;
+
+:(after "bool is_mu_address(reagent r)")
+  if (!canonize_type(r)) return false;
+
+:(after "bool is_mu_number(reagent r)")
+  if (!canonize_type(r)) return false;
+:(after "bool is_mu_boolean(reagent r)")
+  if (!canonize_type(r)) return false;
+
+:(after "Update product While Type-checking Merge")
+if (!canonize_type(product)) continue;
+
+:(before "End Compute Call Ingredient")
+canonize_type(ingredient);
+:(before "End Preprocess NEXT_INGREDIENT product")
+canonize_type(product);
+:(before "End Check RETURN Copy(lhs, rhs)
+canonize_type(lhs);
+canonize_type(rhs);
+
+:(before "Compute Container Metadata(reagent rcopy)")
+if (!canonize_type(rcopy)) return;
+
+:(before "Compute Container Metadata(element)")
+assert(!has_property(element, "lookup"));
+
+:(code)
+bool canonize_type(reagent& r) {
+  while (has_property(r, "lookup")) {
+    if (!r.type || r.type->value != get(Type_ordinal, "address")) {
+      raise << "can't lookup non-address: " << to_string(r) << ": " << to_string(r.type) << '\n' << end();
+      return false;
+    }
+    drop_from_type(r, "address");
+    drop_one_lookup(r);
+  }
+  return true;
+}
+
+void drop_one_lookup(reagent& r) {
+  for (vector<pair<string, string_tree*> >::iterator p = r.properties.begin(); p != r.properties.end(); ++p) {
+    if (p->first == "lookup") {
+      r.properties.erase(p);
+      return;
+    }
+  }
+  assert(false);
+}
+
+//: Tedious fixup to support addresses in container/array instructions of previous layers.
+//: Most instructions don't require fixup if they use the 'ingredients' and
+//: 'products' variables in run_current_routine().
+
+:(scenario get_indirect)
+def main [
+  1:address:point <- copy 10/unsafe
+  # 10 reserved for refcount
+  11:number <- copy 34
+  12:number <- copy 35
+  2:number <- get 1:address:point/lookup, 0:offset
+]
++mem: storing 34 in location 2
+
+:(scenario get_indirect2)
+def main [
+  1:address:point <- copy 10/unsafe
+  # 10 reserved for refcount
+  11:number <- copy 34
+  12:number <- copy 35
+  2:address:number <- copy 20/unsafe
+  2:address:number/lookup <- get 1:address:point/lookup, 0:offset
+]
++mem: storing 34 in location 21
+
+:(scenario include_nonlookup_properties)
+def main [
+  1:address:point <- copy 10/unsafe
+  # 10 reserved for refcount
+  11:number <- copy 34
+  12:number <- copy 35
+  2:number <- get 1:address:point/lookup/foo, 0:offset
+]
++mem: storing 34 in location 2
+
+:(after "Update GET base in Check")
+if (!canonize_type(base)) break;
+:(after "Update GET product in Check")
+if (!canonize_type(product)) break;
+:(after "Update GET base in Run")
+canonize(base);
+
+:(scenario put_indirect)
+def main [
+  1:address:point <- copy 10/unsafe
+  # 10 reserved for refcount
+  11:number <- copy 34
+  12:number <- copy 35
+  1:address:point/lookup <- put 1:address:point/lookup, 0:offset, 36
+]
++mem: storing 36 in location 11
+
+:(after "Update PUT base in Check")
+if (!canonize_type(base)) break;
+:(after "Update PUT offset in Check")
+if (!canonize_type(offset)) break;
+:(after "Update PUT base in Run")
+canonize(base);
+
+:(scenario new_error)
+% Hide_errors = true;
+def main [
+  1:number/raw <- new number:type
+]
++error: main: product of 'new' has incorrect type: 1:number/raw <- new number:type
+
+:(after "Update NEW product in Check")
+canonize_type(product);
+
+:(scenario copy_array_indirect)
+def main [
+  # 10 reserved for refcount
+  11:array:number:3 <- create-array
+  12:number <- copy 14
+  13:number <- copy 15
+  14:number <- copy 16
+  1:address:array:number <- copy 10/unsafe
+  2:array:number <- copy 1:address:array:number/lookup
+]
++mem: storing 3 in location 2
++mem: storing 14 in location 3
++mem: storing 15 in location 4
++mem: storing 16 in location 5
+
+:(before "Update CREATE_ARRAY product in Check")
+// 'create-array' does not support indirection. Static arrays are meant to be
+// allocated on the 'stack'.
+assert(!has_property(product, "lookup"));
+:(before "Update CREATE_ARRAY product in Run")
+// 'create-array' does not support indirection. Static arrays are meant to be
+// allocated on the 'stack'.
+assert(!has_property(product, "lookup"));
+
+:(scenario index_indirect)
+def main [
+  # 10 reserved for refcount
+  11:array:number:3 <- create-array
+  12:number <- copy 14
+  13:number <- copy 15
+  14:number <- copy 16
+  1:address:array:number <- copy 10/unsafe
+  2:number <- index 1:address:array:number/lookup, 1
+]
++mem: storing 15 in location 2
+
+:(before "Update INDEX base in Check")
+if (!canonize_type(base)) break;
+:(before "Update INDEX index in Check")
+if (!canonize_type(index)) break;
+:(before "Update INDEX product in Check")
+if (!canonize_type(product)) break;
+
+:(before "Update INDEX base in Run")
+canonize(base);
+:(before "Update INDEX index in Run")
+canonize(index);
+
+:(scenario put_index_indirect)
+def main [
+  # 10 reserved for refcount
+  11:array:number:3 <- create-array
+  12:number <- copy 14
+  13:number <- copy 15
+  14:number <- copy 16
+  1:address:array:number <- copy 10/unsafe
+  1:address:array:number/lookup <- put-index 1:address:array:number/lookup, 1, 34
+]
++mem: storing 34 in location 13
+
+:(scenario put_index_indirect_2)
+def main [
+  1:array:number:3 <- create-array
+  2:number <- copy 14
+  3:number <- copy 15
+  4:number <- copy 16
+  5:address:number <- copy 10/unsafe
+  # 10 reserved for refcount
+  11:number <- copy 1
+  5:address:array:number/lookup <- put-index 1:array:number:3, 5:address:number/lookup, 34
+]
++mem: storing 34 in location 3
+
+:(before "Update PUT_INDEX base in Check")
+if (!canonize_type(base)) break;
+:(before "Update PUT_INDEX index in Check")
+if (!canonize_type(index)) break;
+:(before "Update PUT_INDEX value in Check")
+if (!canonize_type(value)) break;
+
+:(before "Update PUT_INDEX base in Run")
+canonize(base);
+:(before "Update PUT_INDEX index in Run")
+canonize(index);
+
+:(scenario length_indirect)
+def main [
+  # 10 reserved for refcount
+  11:array:number:3 <- create-array
+  12:number <- copy 14
+  13:number <- copy 15
+  14:number <- copy 16
+  1:address:array:number <- copy 10/unsafe
+  2:number <- length 1:address:array:number/lookup
+]
++mem: storing 3 in location 2
+
+:(before "Update LENGTH array in Check")
+if (!canonize_type(array)) break;
+:(before "Update LENGTH array in Run")
+canonize(array);
+
+:(scenario maybe_convert_indirect)
+def main [
+  # 10 reserved for refcount
+  11:number-or-point <- merge 0/number, 34
+  1:address:number-or-point <- copy 10/unsafe
+  2:number, 3:boolean <- maybe-convert 1:address:number-or-point/lookup, i:variant
+]
++mem: storing 34 in location 2
++mem: storing 1 in location 3
+
+:(scenario maybe_convert_indirect_2)
+def main [
+  # 10 reserved for refcount
+  11:number-or-point <- merge 0/number, 34
+  1:address:number-or-point <- copy 10/unsafe
+  2:address:number <- copy 20/unsafe
+  2:address:number/lookup, 3:boolean <- maybe-convert 1:address:number-or-point/lookup, i:variant
+]
++mem: storing 34 in location 21
++mem: storing 1 in location 3
+
+:(scenario maybe_convert_indirect_3)
+def main [
+  # 10 reserved for refcount
+  11:number-or-point <- merge 0/number, 34
+  1:address:number-or-point <- copy 10/unsafe
+  2:address:boolean <- copy 20/unsafe
+  3:number, 2:address:boolean/lookup <- maybe-convert 1:address:number-or-point/lookup, i:variant
+]
++mem: storing 34 in location 3
++mem: storing 1 in location 21
+
+:(before "Update MAYBE_CONVERT base in Check")
+if (!canonize_type(base)) break;
+:(before "Update MAYBE_CONVERT product in Check")
+if (!canonize_type(product)) break;
+:(before "Update MAYBE_CONVERT status in Check")
+if (!canonize_type(status)) break;
+
+:(before "Update MAYBE_CONVERT base in Run")
+canonize(base);
+:(before "Update MAYBE_CONVERT product in Run")
+canonize(product);
+:(before "Update MAYBE_CONVERT status in Run")
+canonize(status);
+
+:(scenario merge_exclusive_container_indirect)
+def main [
+  1:address:number-or-point <- copy 10/unsafe
+  1:address:number-or-point/lookup <- merge 0/number, 34
+]
+# skip 10 for refcount
++mem: storing 0 in location 11
++mem: storing 34 in location 12
+
+:(before "Update size_mismatch Check for MERGE(x)
+canonize(x);
+
+//: abbreviation for '/lookup': a prefix '*'
+
+:(scenario lookup_abbreviation)
+def main [
+  1:address:number <- copy 10/unsafe
+  # 10 reserved for refcount
+  11:number <- copy 34
+  3:number <- copy *1:address:number
+]
++parse: ingredient: {1: ("address" "number"), "lookup": ()}
++mem: storing 34 in location 3
+
+:(before "End Parsing reagent")
+{
+  while (!name.empty() && name.at(0) == '*') {
+    name.erase(0, 1);
+    properties.push_back(pair<string, string_tree*>("lookup", NULL));
+  }
+  if (name.empty())
+    raise << "illegal name " << original_string << '\n' << end();
+}
+
+//:: helpers for debugging
+
+:(before "End Primitive Recipe Declarations")
+_DUMP,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$dump", _DUMP);
+:(before "End Primitive Recipe Implementations")
+case _DUMP: {
+  reagent after_canonize = current_instruction().ingredients.at(0);
+  canonize(after_canonize);
+  cerr << maybe(current_recipe_name()) << current_instruction().ingredients.at(0).name << ' ' << no_scientific(current_instruction().ingredients.at(0).value) << " => " << no_scientific(after_canonize.value) << " => " << no_scientific(get_or_insert(Memory, after_canonize.value)) << '\n';
+  break;
+}
+
+//: grab an address, and then dump its value at intervals
+//: useful for tracking down memory corruption (writing to an out-of-bounds address)
+:(before "End Globals")
+int Bar = -1;
+:(before "End Primitive Recipe Declarations")
+_BAR,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$bar", _BAR);
+:(before "End Primitive Recipe Implementations")
+case _BAR: {
+  if (current_instruction().ingredients.empty()) {
+    if (Bar != -1) cerr << Bar << ": " << no_scientific(get_or_insert(Memory, Bar)) << '\n';
+    else cerr << '\n';
+  }
+  else {
+    reagent tmp = current_instruction().ingredients.at(0);
+    canonize(tmp);
+    Bar = tmp.value;
+  }
+  break;
+}
diff --git a/035new_text.cc b/038new_test.cc
index 4b3ad536..4b3ad536 100644
--- a/035new_text.cc
+++ b/038new_test.cc
diff --git a/036location_array.cc b/039location_array.cc
index 191e6d38..191e6d38 100644
--- a/036location_array.cc
+++ b/039location_array.cc