about summary refs log tree commit diff stats
path: root/034address.cc
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 /034address.cc
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.
Diffstat (limited to '034address.cc')
-rw-r--r--034address.cc709
1 files changed, 20 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;
-}