about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2018-01-03 00:31:10 -0800
committerKartik K. Agaram <vc@akkartik.com>2018-01-03 00:44:09 -0800
commitacce384bcc88d5b300b913c14b9872081a182155 (patch)
treea21c33d342c44382b08e37a212a2e79416baca45
parentc8eb6c1a64d76dc9a1005571c4eb71ddc6d8f2a9 (diff)
downloadmu-acce384bcc88d5b300b913c14b9872081a182155.tar.gz
4179 - experiment: rip out memory reclamation
I have a plan for a way to avoid use-after-free errors without all the
overheads of maintaining refcounts. Has the nice side-effect of
requiring manual memory management. The Mu way is to leak memory by
default and build tools to help decide when and where to expend effort
plugging memory leaks. Arguably programs should be distributed with
summaries of their resource use characteristics.

Eliminating refcount maintenance reduces time to run tests by 30% for
`mu edit`:

              this commit                 parent
  mu test:         3.9s                        4.5s
  mu test edit:  2:38                        3:48

Open questions:
  - making reclamation easier; some sort of support for destructors
  - reclaiming local scopes (which are allocated on the heap)
    - should we support automatically reclaiming allocations inside them?
-rw-r--r--034address.cc126
-rw-r--r--035lookup.cc164
-rw-r--r--036refcount.cc554
-rw-r--r--037abandon.cc210
-rw-r--r--038new_text.cc13
-rw-r--r--043space.cc249
-rw-r--r--044space_surround.cc25
-rw-r--r--069hash.cc19
-rw-r--r--071deep_copy.cc15
-rw-r--r--072recipe.cc3
-rw-r--r--073scheduler.cc69
-rw-r--r--074wait.cc14
-rw-r--r--076continuation.cc113
-rw-r--r--082scenario_screen.cc8
-rw-r--r--085scenario_console.cc16
-rw-r--r--089scenario_filesystem.cc16
16 files changed, 151 insertions, 1463 deletions
diff --git a/034address.cc b/034address.cc
index 217562a3..0b2b9797 100644
--- a/034address.cc
+++ b/034address.cc
@@ -18,104 +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 recap: an address is a bookmark to some potentially large payload, and
-//: you can replace any ingredient or product with a lookup to an address of
-//: the appropriate type. But how do we get addresses to begin with? That
-//: requires a little more explanation. Once we introduce the notion of
-//: bookmarks to data, we have to think about the life cycle of a piece of
-//: data and its bookmarks (because remember, bookmarks can be copied around
-//: just like anything else). Otherwise several bad outcomes can result (and
-//: indeed *have* resulted in past languages like C):
-//:
-//:   a) You can run out of memory if you don't have a way to reclaim
-//:   data.
-//:   b) If you allow data to be reclaimed, you have to be careful not to
-//:   leave any stale addresses pointing at it. Otherwise your program might
-//:   try to lookup such an address and find something unexpected. Such
-//:   "memory corruption" problems can be very hard to track down, and they
-//:   can also be exploited to break into your computer over the network, etc.
-//:
-//: To avoid these problems, we introduce the notion of a *reference count* or
-//: refcount. The life cycle of a bit of data accessed through addresses looks
-//: like this.
-//:
-//:    We create space in computer memory for it using the 'new' instruction.
-//:    The 'new' instruction takes a type as an ingredient, allocates
-//:    sufficient space to hold that type, and returns an address (bookmark)
-//:    to the allocated space.
-//:
-//:      x:address:num <- new number:type
-//:
-//:                     +------------+
-//:          x -------> |  number    |
-//:                     +------------+
-//:
-//:    That isn't entirely accurate. Under the hood, 'new' allocates an extra
-//:    number -- the refcount:
-//:
-//:                     +------------+------------+
-//:          x -------> | refcount   |  number    |
-//:                     +------------+------------+
-//:
-//:    This probably seems like a waste of space. In practice it isn't worth
-//:    allocating individual numbers and our payload will tend to be larger,
-//:    so the picture would look more like this (zooming out a bit):
-//:
-//:                         +-------------------------+
-//:                     +---+                         |
-//:          x -------> | r |                         |
-//:                     +---+        DATA             |
-//:                         |                         |
-//:                         |                         |
-//:                         +-------------------------+
-//:
-//:    (Here 'r' denotes the refcount. It occupies a tiny amount of space
-//:    compared to the payload.)
-//:
-//:    Anyways, back to our example where the data is just a single number.
-//:    After the call to 'new', Mu's map of memory looks like this:
-//:
-//:                     +---+------------+
-//:          x -------> | 1 |  number    |
-//:                     +---+------------+
-//:
-//:    The refcount of 1 here indicates that this number has one bookmark
-//:    outstanding. If you then make a copy of x, the refcount increments:
-//:
-//:      y:address:num <- copy x
-//:
-//:          x ---+     +---+------------+
-//:               +---> | 2 |  number    |
-//:          y ---+     +---+------------+
-//:
-//:    Whether you access the payload through x or y, Mu knows how many
-//:    bookmarks are outstanding to it. When you change x or y, the refcount
-//:    transparently decrements:
-//:
-//:      x <- copy 0  # an address is just a number, you can always write 0 to it
-//:
-//:                     +---+------------+
-//:          y -------> | 1 |  number    |
-//:                     +---+------------+
-//:
-//:    The final flourish is what happens when the refcount goes down to 0: Mu
-//:    reclaims the space occupied by both refcount and payload in memory, and
-//:    they're ready to be reused by later calls to 'new'.
-//:
-//:      y <- copy 0
-//:
-//:                     +---+------------+
-//:                     | 0 |  XXXXXXX   |
-//:                     +---+------------+
-//:
-//: Using refcounts fixes both our problems a) and b) above: you can use
-//: memory for many different purposes as many times as you want without
-//: running out of memory, and you don't have to worry about ever leaving a
-//: dangling bookmark when you reclaim memory.
-//:
-//: This layer implements creating addresses using 'new'. The next few layers
-//: will flesh out the rest of the life cycle.
 
 //: todo: give 'new' a custodian ingredient. Following malloc/free is a temporary hack.
 
@@ -138,8 +40,8 @@ def main [
 ]
 +run: {1: ("address" "array" "number"), "raw": ()} <- new {number: "type"}, {5: "literal"}
 +mem: array length is 5
-# don't forget the extra location for array length, and the second extra location for the refcount
-+mem: storing 7 in location 3
+# don't forget the extra location for array length
++mem: storing 6 in location 3
 
 :(scenario dilated_reagent_with_new)
 def main [
@@ -320,8 +222,8 @@ case ALLOCATE: {
   int result = allocate(size);
   if (SIZE(current_instruction().ingredients) > 1) {
     // initialize array length
-    trace("mem") << "storing " << ingredients.at(1).at(0) << " in location " << result+/*skip refcount*/1 << end();
-    put(Memory, result+/*skip refcount*/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(result);
@@ -329,8 +231,6 @@ case ALLOCATE: {
 }
 :(code)
 int allocate(int size) {
-  // include space for refcount
-  ++size;
   trace("mem") << "allocating size " << size << end();
 //?   Total_alloc += size;
 //?   ++Num_alloc;
@@ -394,8 +294,8 @@ def main [
   12:address:num/raw <- new number:type
   13:num/raw <- subtract 12:address:num/raw, 11:address:num/raw
 ]
-# size of number + refcount
-+mem: storing 2 in location 13
+# size of number
++mem: storing 1 in location 13
 
 :(scenario new_array_size)
 def main [
@@ -403,8 +303,8 @@ def main [
   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 + refcount
-+mem: storing 7 in location 3
+# 5 locations for array contents + array length
++mem: storing 6 in location 3
 
 :(scenario new_empty_array)
 def main [
@@ -414,18 +314,18 @@ def main [
 ]
 +run: {1: ("address" "array" "number"), "raw": ()} <- new {number: "type"}, {0: "literal"}
 +mem: array length is 0
-# one location for array length, and one for the refcount
-+mem: storing 2 in location 3
+# one location for array length
++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 343d7900..c72a2938 100644
--- a/035lookup.cc
+++ b/035lookup.cc
@@ -1,33 +1,4 @@
-//: 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 the address layer for
-//: details on these 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:num <- add *x, 1
-//:
-//: After this instruction runs the value of z will be 35.
+//: Go from an address to the payload it points at using /lookup.
 //:
 //: The tests in this layer use unsafe operations so as to stay decoupled from
 //: 'new'.
@@ -35,11 +6,10 @@
 :(scenario copy_indirect)
 def main [
   1:address:num <- copy 10/unsafe
-  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
 ]
-# 1 contains 10. Skip refcount and lookup location 11.
 +mem: storing 34 in location 2
 
 :(before "End Preprocess read_memory(x)")
@@ -52,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);
@@ -102,11 +72,7 @@ void lookup_memory_core(reagent& x, bool check_for_null) {
   trace("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) {
-    trace("mem") << "skipping refcount at " << x.value << end();
-    x.set_value(x.value+1);  // skip refcount
-  }
-  else if (check_for_null) {
+  if (check_for_null && x.value == 0) {
     if (Current_routine)
       raise << maybe(current_recipe_name()) << "tried to /lookup 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
     else
@@ -115,25 +81,6 @@ void lookup_memory_core(reagent& x, bool check_for_null) {
   drop_one_lookup(x);
 }
 
-void test_lookup_address_skips_refcount() {
-  reagent x("*x:address:num");
-  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() {
-  Hide_errors = true;
-  reagent x("*x:address:num");
-  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);
-}
-
 :(before "End Preprocess types_strictly_match(reagent to, reagent from)")
 if (!canonize_type(to)) return false;
 if (!canonize_type(from)) return false;
@@ -200,9 +147,8 @@ void drop_one_lookup(reagent& r) {
 :(scenario get_indirect)
 def main [
   1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:num <- copy 34
-  12:num <- copy 35
+  10:num <- copy 34
+  11:num <- copy 35
   2:num <- get 1:address:point/lookup, 0:offset
 ]
 +mem: storing 34 in location 2
@@ -210,20 +156,18 @@ def main [
 :(scenario get_indirect2)
 def main [
   1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:num <- copy 34
-  12:num <- copy 35
+  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 34 in location 21
++mem: storing 34 in location 20
 
 :(scenario include_nonlookup_properties)
 def main [
   1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:num <- copy 34
-  12:num <- copy 35
+  10:num <- copy 34
+  11:num <- copy 35
   2:num <- get 1:address:point/lookup/foo, 0:offset
 ]
 +mem: storing 34 in location 2
@@ -238,12 +182,11 @@ canonize(base);
 :(scenario put_indirect)
 def main [
   1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  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;
@@ -256,9 +199,8 @@ canonize(base);
 % Hide_errors = true;
 def main [
   1:address:point <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:num <- copy 34
-  12:num <- copy 35
+  10:num <- copy 34
+  11:num <- copy 35
   1:address:point <- put 1:address:point/lookup, x:offset, 36
 ]
 +error: main: product of 'put' must be first ingredient '1:address:point/lookup', but got '1:address:point'
@@ -285,11 +227,10 @@ canonize_type(product);
 
 :(scenario copy_array_indirect)
 def main [
-  # 10 reserved for refcount
-  11:array:num:3 <- create-array
-  12:num <- copy 14
-  13:num <- copy 15
-  14:num <- copy 16
+  10:array:num:3 <- create-array
+  11:num <- copy 14
+  12:num <- copy 15
+  13:num <- copy 16
   1:address:array:num <- copy 10/unsafe
   2:array:num <- copy 1:address:array:num/lookup
 ]
@@ -300,11 +241,10 @@ def main [
 
 :(scenario create_array_indirect)
 def main [
-  1000:num/raw <- copy 1  # pretend refcount
   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;
@@ -313,11 +253,10 @@ canonize(product);
 
 :(scenario index_indirect)
 def main [
-  # 10 reserved for refcount
-  11:array:num:3 <- create-array
-  12:num <- copy 14
-  13:num <- copy 15
-  14:num <- copy 16
+  10:array:num:3 <- create-array
+  11:num <- copy 14
+  12:num <- copy 15
+  13:num <- copy 16
   1:address:array:num <- copy 10/unsafe
   2:num <- index 1:address:array:num/lookup, 1
 ]
@@ -337,15 +276,14 @@ canonize(index);
 
 :(scenario put_index_indirect)
 def main [
-  # 10 reserved for refcount
-  11:array:num:3 <- create-array
-  12:num <- copy 14
-  13:num <- copy 15
-  14:num <- copy 16
+  10:array:num:3 <- create-array
+  11:num <- copy 14
+  12:num <- copy 15
+  13:num <- copy 16
   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 13
++mem: storing 34 in location 12
 
 :(scenario put_index_indirect_2)
 def main [
@@ -354,8 +292,7 @@ def main [
   3:num <- copy 15
   4:num <- copy 16
   5:address:num <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:num <- copy 1
+  10:num <- copy 1
   1:array:num:3 <- put-index 1:array:num:3, 5:address:num/lookup, 34
 ]
 +mem: storing 34 in location 3
@@ -363,11 +300,10 @@ def main [
 :(scenario put_index_product_error_with_lookup)
 % Hide_errors = true;
 def main [
-  # 10 reserved for refcount
-  11:array:num:3 <- create-array
-  12:num <- copy 14
-  13:num <- copy 15
-  14:num <- copy 16
+  10:array:num:3 <- create-array
+  11:num <- copy 14
+  12:num <- copy 15
+  13:num <- copy 16
   1:address:array:num <- copy 10/unsafe
   1:address:array:num <- put-index 1:address:array:num/lookup, 1, 34
 ]
@@ -408,11 +344,10 @@ canonize(index);
 
 :(scenario length_indirect)
 def main [
-  # 10 reserved for refcount
-  11:array:num:3 <- create-array
-  12:num <- copy 14
-  13:num <- copy 15
-  14:num <- copy 16
+  10:array:num:3 <- create-array
+  11:num <- copy 14
+  12:num <- copy 15
+  13:num <- copy 16
   1:address:array:num <- copy 10/unsafe
   2:num <- length 1:address:array:num/lookup
 ]
@@ -425,8 +360,7 @@ canonize(array);
 
 :(scenario maybe_convert_indirect)
 def main [
-  # 10 reserved for refcount
-  11:number-or-point <- merge 0/number, 34
+  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
 ]
@@ -435,24 +369,22 @@ def main [
 
 :(scenario maybe_convert_indirect_2)
 def main [
-  # 10 reserved for refcount
-  11:number-or-point <- merge 0/number, 34
+  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 3
-+mem: storing 34 in location 21
++mem: storing 34 in location 20
 
 :(scenario maybe_convert_indirect_3)
 def main [
-  # 10 reserved for refcount
-  11:number-or-point <- merge 0/number, 34
+  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 1 in location 20
 +mem: storing 34 in location 3
 
 :(before "Update MAYBE_CONVERT base in Check")
@@ -474,9 +406,8 @@ 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
++mem: storing 0 in location 10
++mem: storing 34 in location 11
 
 :(before "Update size_mismatch Check for MERGE(x)
 canonize(x);
@@ -486,8 +417,7 @@ canonize(x);
 :(scenario lookup_abbreviation)
 def main [
   1:address:number <- copy 10/unsafe
-  # 10 reserved for refcount
-  11:number <- copy 34
+  10:number <- copy 34
   3:number <- copy *1:address:number
 ]
 +parse: ingredient: {1: ("address" "number"), "lookup": ()}
diff --git a/036refcount.cc b/036refcount.cc
index 418e22b0..f9668be4 100644
--- a/036refcount.cc
+++ b/036refcount.cc
@@ -1,218 +1,9 @@
-//: Update refcounts when copying addresses.
-//: The top of the address layer has more on refcounts.
-
-:(scenario refcounts)
-def main [
-  1:address:num <- copy 1000/unsafe
-  2:address:num <- copy 1:address:num
-  1:address:num <- copy 0
-  2:address:num <- 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
-
-:(after "Writing Instruction Product(i)")
-if (is_primitive(current_instruction().operation)) {
-  reagent/*copy*/ tmp = current_instruction().products.at(i);
-  canonize(tmp);
-  update_any_refcounts(tmp, products.at(i));
-}
-
-:(before "End Globals")
-bool Reclaim_memory = true;
-:(before "End Commandline Options(*arg)")
-else if (is_equal(*arg, "--no-reclaim")) {
-  cerr << "Disabling memory reclamation. Some tests will fail.\n";
-  Reclaim_memory = false;
-}
-:(code)
-void update_any_refcounts(const reagent& canonized_x, const vector<double>& data) {
-  if (!Reclaim_memory) return;
-  increment_any_refcounts(canonized_x, data);  // increment first so we don't reclaim on x <- copy x
-  decrement_any_refcounts(canonized_x);
-}
-
-void increment_any_refcounts(const reagent& canonized_x, const vector<double>& data) {
-  if (is_mu_address(canonized_x)) {
-    assert(scalar(data));
-    assert(!canonized_x.metadata.size);
-    increment_refcount(data.at(0));
-  }
-  // End Increment Refcounts(canonized_x)
-}
-
-void increment_refcount(int new_address) {
-  assert(new_address >= 0);
-  if (new_address == 0) return;
-  ++Total_refcount_updates;
-  int new_refcount = get_or_insert(Memory, new_address);
-  trace("mem") << "incrementing refcount of " << new_address << ": " << new_refcount << " -> " << new_refcount+1 << end();
-  put(Memory, new_address, new_refcount+1);
-}
-
-void decrement_any_refcounts(const reagent& canonized_x) {
-  // Begin Decrement Refcounts(canonized_x)
-  if (is_mu_address(canonized_x) && canonized_x.value != 0) {
-    assert(!canonized_x.metadata.size);
-    decrement_refcount(get_or_insert(Memory, canonized_x.value), payload_type(canonized_x.type), payload_size(canonized_x));
-  }
-  // End Decrement Refcounts(canonized_x)
-}
-
-void decrement_refcount(int old_address, const type_tree* payload_type, int payload_size) {
-  assert(old_address >= 0);
-  if (old_address == 0) return;
-  ++Total_refcount_updates;
-  int old_refcount = get_or_insert(Memory, old_address);
-  trace("mem") << "decrementing refcount of " << old_address << ": " << old_refcount << " -> " << old_refcount-1 << end();
-  --old_refcount;
-  put(Memory, old_address, old_refcount);
-  if (old_refcount < 0) {
-    cerr << "Negative refcount!!! " << old_address << ' ' << old_refcount << '\n';
-    if (Trace_stream) Trace_stream->dump();
-    exit(1);
-  }
-  // End Decrement Refcount(old_address, payload_type, 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) + /*refcount*/1;
+  return size_of(x);
 }
 
-:(scenario refcounts_reflexive)
-def main [
-  1:address:num <- new number:type
-  # idempotent copies leave refcount unchanged
-  1:address:num <- copy 1:address:num
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {1: ("address" "number")} <- copy {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+mem: decrementing refcount of 1000: 2 -> 1
-
-:(scenario refcounts_call)
-def main [
-  1:address:num <- new number:type
-  # passing in addresses to recipes increments refcount
-  foo 1:address:num
-  # return does NOT yet decrement refcount; memory must be explicitly managed
-  1:address:num <- new number:type
-]
-def foo [
-  2:address:num <- next-ingredient
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: foo {1: ("address" "number")}
-# leave ambiguous precisely when the next increment happens
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: decrementing refcount of 1000: 2 -> 1
-
-//: fix up any instructions that don't follow the usual flow of read_memory
-//: before the RUN switch, and write_memory after
-
-:(scenario refcounts_put)
-container foo [
-  x:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:address:foo <- new foo:type
-  *2:address:foo <- put *2:address:foo, x:offset, 1:address:num
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: ("address" "foo")} <- new {foo: "type"}
-+mem: incrementing refcount of 1002: 0 -> 1
-+run: {2: ("address" "foo"), "lookup": ()} <- put {2: ("address" "foo"), "lookup": ()}, {x: "offset"}, {1: ("address" "number")}
-# put increments refcount
-+mem: incrementing refcount of 1000: 1 -> 2
-
-:(after "Write Memory in PUT in Run")
-reagent/*copy*/ element = element_type(base.type, offset);
-assert(!has_property(element, "lookup"));
-element.set_value(address);
-update_any_refcounts(element, ingredients.at(2));
-
-:(scenario refcounts_put_index)
-def main [
-  1:address:num <- new number:type
-  2:address:array:address:num <- new {(address number): type}, 3
-  *2:address:array:address:num <- put-index *2:address:array:address:num, 0, 1:address:num
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: ("address" "array" "address" "number")} <- new {(address number): "type"}, {3: "literal"}
-+mem: incrementing refcount of 1002: 0 -> 1
-+run: {2: ("address" "array" "address" "number"), "lookup": ()} <- put-index {2: ("address" "array" "address" "number"), "lookup": ()}, {0: "literal"}, {1: ("address" "number")}
-# put-index increments refcount
-+mem: incrementing refcount of 1000: 1 -> 2
-
-:(after "Write Memory in PUT_INDEX in Run")
-reagent/*local*/ element;
-element.set_value(address);
-element.type = copy_array_element(base.type);
-update_any_refcounts(element, value);
-
-:(scenario refcounts_maybe_convert)
-exclusive-container foo [
-  x:num
-  p:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:foo <- merge 1/p, 1:address:num
-  4:address:num, 5:bool <- maybe-convert 2:foo, 1:variant/p
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-# merging in an address increments refcount
-+run: {2: "foo"} <- merge {1: "literal", "p": ()}, {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {4: ("address" "number")}, {5: "boolean"} <- maybe-convert {2: "foo"}, {1: "variant", "p": ()}
-# maybe-convert increments refcount on success
-+mem: incrementing refcount of 1000: 2 -> 3
-
-:(after "Write Memory in Successful MAYBE_CONVERT")
-// todo: double-check data here as well
-vector<double> data;
-for (int i = 0;  i < size_of(product);  ++i)
-  data.push_back(get_or_insert(Memory, base_address+/*skip tag*/1+i));
-update_any_refcounts(product, data);
-
-//:: manage refcounts in instructions that copy multiple locations at a time
-
-:(scenario refcounts_copy_nested)
-container foo [
-  x:address:num  # address inside container
-]
-def main [
-  1:address:num <- new number:type
-  2:address:foo <- new foo:type
-  *2:address:foo <- put *2:address:foo, x:offset, 1:address:num
-  3:foo <- copy *2:address:foo
-]
-+transform: compute address offsets for container foo
-+transform: checking container foo, element 0
-+transform: address at offset 0
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: ("address" "foo"), "lookup": ()} <- put {2: ("address" "foo"), "lookup": ()}, {x: "offset"}, {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-# copying a container increments refcounts of any contained addresses
-+run: {3: "foo"} <- copy {2: ("address" "foo"), "lookup": ()}
-+mem: incrementing refcount of 1000: 2 -> 3
-
 :(before "End type_tree Definition")
 struct address_element_info {
   // Where inside a container type (after flattening nested containers!) the
@@ -268,7 +59,7 @@ struct tag_condition_info {
 //
 //  IF offset o1 has tag t2 AND offset o2 has tag t2 AND .., THEN
 //    for all address_element_infos:
-//      you need to update refcounts for the address at offset pointing to a payload of type payload_type (just in case we need to abandon something in the process)
+//      there is an address at 'offset' pointing to a payload of type payload_type
 map<set<tag_condition_info>, set<address_element_info> > address;
 :(code)
 bool operator<(const set<tag_condition_info>& a, const set<tag_condition_info>& b) {
@@ -699,40 +490,6 @@ void test_container_address_offsets_from_repeated_address_and_array_types() {
   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 }
 
-//: use metadata.address to update refcounts within containers, arrays and
-//: exclusive containers
-
-:(before "End Increment Refcounts(canonized_x)")
-if (is_mu_container(canonized_x) || is_mu_exclusive_container(canonized_x)) {
-  const container_metadata& metadata = get(Container_metadata, canonized_x.type);
-  for (map<set<tag_condition_info>, set<address_element_info> >::const_iterator p = metadata.address.begin();  p != metadata.address.end();  ++p) {
-    if (!all_match(data, p->first)) continue;
-    for (set<address_element_info>::const_iterator info = p->second.begin();  info != p->second.end();  ++info)
-      increment_refcount(data.at(info->offset));
-  }
-}
-
-:(before "End Decrement Refcounts(canonized_x)")
-if (is_mu_container(canonized_x) || is_mu_exclusive_container(canonized_x)) {
-  trace("mem") << "need to read old value of '" << to_string(canonized_x) << "' to figure out what refcounts to decrement" << end();
-  // read from canonized_x but without canonizing again
-  reagent/*copy*/ tmp = canonized_x;
-  tmp.properties.push_back(pair<string, string_tree*>("raw", NULL));
-  vector<double> data = read_memory(tmp);
-  trace("mem") << "done reading old value of '" << to_string(canonized_x) << "'" << end();
-  const container_metadata& metadata = get(Container_metadata, canonized_x.type);
-  for (map<set<tag_condition_info>, set<address_element_info> >::const_iterator p = metadata.address.begin();  p != metadata.address.end();  ++p) {
-    if (!all_match(data, p->first)) continue;
-    for (set<address_element_info>::const_iterator info = p->second.begin();  info != p->second.end();  ++info) {
-      int element_address = get_or_insert(Memory, canonized_x.value + info->offset);
-      reagent/*local*/ element;
-      element.set_value(element_address+/*skip refcount*/1);
-      element.type = new type_tree(*info->payload_type);
-      decrement_refcount(element_address, info->payload_type, size_of(element)+/*refcount*/1);
-    }
-  }
-}
-
 :(code)
 bool all_match(const vector<double>& data, const set<tag_condition_info>& conditions) {
   for (set<tag_condition_info>::const_iterator p = conditions.begin();  p != conditions.end();  ++p) {
@@ -742,271 +499,6 @@ bool all_match(const vector<double>& data, const set<tag_condition_info>& condit
   return true;
 }
 
-:(scenario refcounts_put_container)
-container foo [
-  a:bar  # contains an address
-]
-container bar [
-  x:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:bar <- merge 1:address:num
-  3:address:foo <- new foo:type
-  *3:address:foo <- put *3:address:foo, a:offset, 2:bar
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: "bar"} <- merge {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {3: ("address" "foo"), "lookup": ()} <- put {3: ("address" "foo"), "lookup": ()}, {a: "offset"}, {2: "bar"}
-# put increments refcount inside container
-+mem: incrementing refcount of 1000: 2 -> 3
-
-:(scenario refcounts_put_index_array)
-container bar [
-  x:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:bar <- merge 1:address:num
-  3:address:array:bar <- new bar:type, 3
-  *3:address:array:bar <- put-index *3:address:array:bar, 0, 2:bar
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: "bar"} <- merge {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {3: ("address" "array" "bar"), "lookup": ()} <- put-index {3: ("address" "array" "bar"), "lookup": ()}, {0: "literal"}, {2: "bar"}
-# put-index increments refcount inside container
-+mem: incrementing refcount of 1000: 2 -> 3
-
-:(scenario refcounts_maybe_convert_container)
-exclusive-container foo [
-  a:num
-  b:bar  # contains an address
-]
-container bar [
-  x:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:bar <- merge 1:address:num
-  3:foo <- merge 1/b, 2:bar
-  5:bar, 6:bool <- maybe-convert 3:foo, 1:variant/b
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: "bar"} <- merge {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {3: "foo"} <- merge {1: "literal", "b": ()}, {2: "bar"}
-+mem: incrementing refcount of 1000: 2 -> 3
-+run: {5: "bar"}, {6: "boolean"} <- maybe-convert {3: "foo"}, {1: "variant", "b": ()}
-+mem: incrementing refcount of 1000: 3 -> 4
-
-:(scenario refcounts_copy_doubly_nested)
-container foo [
-  a:bar  # no addresses
-  b:curr  # contains addresses
-]
-container bar [
-  x:num
-  y:num
-]
-container curr [
-  x:num
-  y:address:num  # address inside container inside container
-]
-def main [
-  1:address:num <- new number:type
-  2:address:curr <- new curr:type
-  *2:address:curr <- put *2:address:curr, 1:offset/y, 1:address:num
-  3:address:foo <- new foo:type
-  *3:address:foo <- put *3:address:foo, 1:offset/b, *2:address:curr
-  4:foo <- copy *3:address:foo
-]
-+transform: compute address offsets for container foo
-+transform: checking container foo, element 1
-+transform: address at offset 3
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-# storing an address in a container updates its refcount
-+run: {2: ("address" "curr"), "lookup": ()} <- put {2: ("address" "curr"), "lookup": ()}, {1: "offset", "y": ()}, {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-# storing a container in a container updates refcounts of any contained addresses
-+run: {3: ("address" "foo"), "lookup": ()} <- put {3: ("address" "foo"), "lookup": ()}, {1: "offset", "b": ()}, {2: ("address" "curr"), "lookup": ()}
-+mem: incrementing refcount of 1000: 2 -> 3
-# copying a container containing a container containing an address updates refcount
-+run: {4: "foo"} <- copy {3: ("address" "foo"), "lookup": ()}
-+mem: incrementing refcount of 1000: 3 -> 4
-
-:(scenario refcounts_copy_exclusive_container_within_container)
-container foo [
-  a:num
-  b:bar
-]
-exclusive-container bar [
-  x:num
-  y:num
-  z:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:bar <- merge 0/x, 34
-  3:foo <- merge 12, 2:bar
-  5:bar <- merge 1/y, 35
-  6:foo <- merge 13, 5:bar
-  8:bar <- merge 2/z, 1:address:num
-  9:foo <- merge 14, 8:bar
-  11:foo <- copy 9:foo
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-# no change while merging items of other types
-+run: {8: "bar"} <- merge {2: "literal", "z": ()}, {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {9: "foo"} <- merge {14: "literal"}, {8: "bar"}
-+mem: incrementing refcount of 1000: 2 -> 3
-+run: {11: "foo"} <- copy {9: "foo"}
-+mem: incrementing refcount of 1000: 3 -> 4
-
-:(scenario refcounts_copy_container_within_exclusive_container)
-exclusive-container foo [
-  a:num
-  b:bar
-]
-container bar [
-  x:num
-  y:num
-  z:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:foo <- merge 0/a, 34
-  6:foo <- merge 0/a, 35
-  10:bar <- merge 2/x, 15/y, 1:address:num
-  13:foo <- merge 1/b, 10:bar
-  17:foo <- copy 13:foo
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-# no change while merging items of other types
-+run: {10: "bar"} <- merge {2: "literal", "x": ()}, {15: "literal", "y": ()}, {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {13: "foo"} <- merge {1: "literal", "b": ()}, {10: "bar"}
-+mem: incrementing refcount of 1000: 2 -> 3
-+run: {17: "foo"} <- copy {13: "foo"}
-+mem: incrementing refcount of 1000: 3 -> 4
-
-:(scenario refcounts_copy_exclusive_container_within_exclusive_container)
-exclusive-container foo [
-  a:num
-  b:bar
-]
-exclusive-container bar [
-  x:num
-  y:address:num
-]
-def main [
-  1:address:num <- new number:type
-  10:foo <- merge 1/b, 1/y, 1:address:num
-  20:foo <- copy 10:foo
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-# no change while merging items of other types
-+run: {10: "foo"} <- merge {1: "literal", "b": ()}, {1: "literal", "y": ()}, {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {20: "foo"} <- copy {10: "foo"}
-+mem: incrementing refcount of 1000: 2 -> 3
-
-:(scenario refcounts_copy_array_within_container)
-container foo [
-  x:address:array:num
-]
-def main [
-  1:address:array:num <- new number:type, 3
-  2:foo <- merge 1:address:array:num
-  3:address:array:num <- new number:type, 5
-  2:foo <- merge 3:address:array:num
-]
-+run: {1: ("address" "array" "number")} <- new {number: "type"}, {3: "literal"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: "foo"} <- merge {1: ("address" "array" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {2: "foo"} <- merge {3: ("address" "array" "number")}
-+mem: decrementing refcount of 1000: 2 -> 1
-
-:(scenario refcounts_copy_address_within_static_array_within_container)
-container foo [
-  a:array:bar:3
-  b:address:num
-]
-container bar [
-  y:num
-  z:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:bar <- merge 34, 1:address:num
-  10:array:bar:3 <- create-array
-  put-index 10:array:bar:3, 1, 2:bar
-  20:foo <- merge 10:array:bar:3, 1:address:num
-  1:address:num <- copy 0
-  2:bar <- merge 34, 1:address:num
-  put-index 10:array:bar:3, 1, 2:bar
-  20:foo <- merge 10:array:bar:3, 1:address:num
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: "bar"} <- merge {34: "literal"}, {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: put-index {10: ("array" "bar" "3")}, {1: "literal"}, {2: "bar"}
-+mem: incrementing refcount of 1000: 2 -> 3
-+run: {20: "foo"} <- merge {10: ("array" "bar" "3")}, {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 3 -> 4
-+mem: incrementing refcount of 1000: 4 -> 5
-+run: {1: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1000: 5 -> 4
-+run: {2: "bar"} <- merge {34: "literal"}, {1: ("address" "number")}
-+mem: decrementing refcount of 1000: 4 -> 3
-+run: put-index {10: ("array" "bar" "3")}, {1: "literal"}, {2: "bar"}
-+mem: decrementing refcount of 1000: 3 -> 2
-+run: {20: "foo"} <- merge {10: ("array" "bar" "3")}, {1: ("address" "number")}
-+mem: decrementing refcount of 1000: 2 -> 1
-+mem: decrementing refcount of 1000: 1 -> 0
-
-:(scenario refcounts_handle_exclusive_containers_with_different_tags)
-container foo1 [
-  x:address:num
-  y:num
-]
-container foo2 [
-  x:num
-  y:address:num
-]
-exclusive-container bar [
-  a:foo1
-  b:foo2
-]
-def main [
-  1:address:num <- copy 12000/unsafe  # pretend allocation
-  *1:address:num <- copy 34
-  2:bar <- merge 0/foo1, 1:address:num, 97
-  5:address:num <- copy 13000/unsafe  # pretend allocation
-  *5:address:num <- copy 35
-  6:bar <- merge 1/foo2, 98, 5:address:num
-  2:bar <- copy 6:bar
-]
-+run: {2: "bar"} <- merge {0: "literal", "foo1": ()}, {1: ("address" "number")}, {97: "literal"}
-+mem: incrementing refcount of 12000: 1 -> 2
-+run: {6: "bar"} <- merge {1: "literal", "foo2": ()}, {98: "literal"}, {5: ("address" "number")}
-+mem: incrementing refcount of 13000: 1 -> 2
-+run: {2: "bar"} <- copy {6: "bar"}
-+mem: incrementing refcount of 13000: 2 -> 3
-+mem: decrementing refcount of 12000: 2 -> 1
-
-:(code)
 bool is_mu_container(const reagent& r) {
   return is_mu_container(r.type);
 }
@@ -1030,45 +522,3 @@ bool is_mu_exclusive_container(const type_tree* type) {
   type_info& info = get(Type, type->value);
   return info.kind == EXCLUSIVE_CONTAINER;
 }
-
-//:: Counters for trying to understand where Mu programs are spending time
-//:: updating refcounts.
-
-:(before "End Globals")
-int Total_refcount_updates = 0;
-map<recipe_ordinal, map</*step index*/int, /*num refcount updates*/int> > Num_refcount_updates;
-:(after "Running One Instruction")
-int initial_num_refcount_updates = Total_refcount_updates;
-:(before "End Running One Instruction")
-if (Run_profiler) {
-  Num_refcount_updates[current_call().running_recipe][current_call().running_step_index]
-      += (Total_refcount_updates - initial_num_refcount_updates);
-  initial_num_refcount_updates = Total_refcount_updates;
-}
-:(before "End Non-primitive Call(caller_frame)")
-if (Run_profiler) {
-  Num_refcount_updates[caller_frame.running_recipe][caller_frame.running_step_index]
-      += (Total_refcount_updates - initial_num_refcount_updates);
-  initial_num_refcount_updates = Total_refcount_updates;
-}
-:(after "Begin Return")
-if (Run_profiler) {
-  Num_refcount_updates[current_call().running_recipe][current_call().running_step_index]
-      += (Total_refcount_updates - initial_num_refcount_updates);
-  initial_num_refcount_updates = Total_refcount_updates;
-}
-:(before "End dump_profile")
-fout.open("profile.refcounts");
-if (fout) {
-  for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin();  p != Recipe.end();  ++p)
-    dump_recipe_profile(p->first, p->second, fout);
-}
-fout.close();
-:(code)
-void dump_recipe_profile(recipe_ordinal ridx, const recipe& r, ostream& out) {
-  out << "recipe " << r.name << " [\n";
-  for (int i = 0;  i < SIZE(r.steps);  ++i) {
-    out << std::setw(6) << Num_refcount_updates[ridx][i] << ' ' << to_string(r.steps.at(i)) << '\n';
-  }
-  out << "]\n\n";
-}
diff --git a/037abandon.cc b/037abandon.cc
index 4a1517c5..b60fd946 100644
--- a/037abandon.cc
+++ b/037abandon.cc
@@ -1,11 +1,10 @@
 //: Reclaiming memory when it's no longer used.
-//: The top of the address layer has the complete life cycle of memory.
 
 :(scenario new_reclaim)
 def main [
   1:address:num <- new number:type
   2:num <- copy 1:address:num  # because 1 will get reset during abandon below
-  1:address:num <- copy 0  # abandon
+  abandon 1:address: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
@@ -13,41 +12,40 @@ def main [
 # both allocations should have returned the same address
 +mem: storing 1 in location 5
 
-:(before "End Decrement Refcount(old_address, payload_type, payload_size)")
-if (old_refcount == 0) {
-  trace("mem") << "automatically abandoning " << old_address << end();
-  abandon(old_address, payload_type, payload_size);
-}
-
 //: 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, const type_tree* payload_type, int payload_size) {
-  trace("abandon") << "updating refcounts inside " << address << ": " << to_string(payload_type) << end();
-//?   Total_free += size;
-//?   ++Num_free;
-//?   cerr << "abandon: " << size << '\n';
-  // decrement any contained refcounts
-  if (is_mu_array(payload_type)) {
-    reagent/*local*/ element;
-    element.type = copy_array_element(payload_type);
-    int array_length = get_or_insert(Memory, address+/*skip refcount*/1);
-    assert(element.type->name != "array");
-    int element_size = size_of(element);
-    for (int i = 0;  i < array_length;  ++i) {
-      element.set_value(address + /*skip refcount and length*/2 + i*element_size);
-      decrement_any_refcounts(element);
-    }
+:(before "End Primitive Recipe Declarations")
+ABANDON,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "abandon", ABANDON);
+:(before "End Primitive Recipe Checks")
+case ABANDON: {
+  if (!inst.products.empty()) {
+    raise << maybe(get(Recipe, r).name) << "'abandon' shouldn't write to any products in '" << to_original_string(inst) << "'\n" << end();
+    break;
+  }
+  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
+    if (!is_mu_address(inst.ingredients.at(i)))
+      raise << maybe(get(Recipe, r).name) << "ingredients of 'abandon' should be addresses, but ingredient " << i << " is '" << to_string(inst.ingredients.at(i)) << '\n' << end();
+    break;
   }
-  else if (is_mu_container(payload_type) || is_mu_exclusive_container(payload_type)) {
-    reagent tmp;
-    tmp.type = new type_tree(*payload_type);
-    tmp.set_value(address + /*skip refcount*/1);
-    decrement_any_refcounts(tmp);
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case ABANDON: {
+  for (int i = 0;  i < SIZE(current_instruction().ingredients);  ++i) {
+    reagent/*copy*/ ingredient = current_instruction().ingredients.at(i);
+    canonize(ingredient);
+    abandon(get_or_insert(Memory, ingredient.value), payload_size(ingredient));
   }
+  break;
+}
+
+:(code)
+void abandon(int address, int payload_size) {
   // clear memory
   for (int curr = address;  curr < address+payload_size;  ++curr)
     put(Memory, curr, 0);
@@ -77,7 +75,7 @@ if (get_or_insert(Current_routine->free_list, size)) {
 def main [
   1:address:num <- new number:type
   2:num <- copy 1:address:num
-  1:address:num <- copy 0  # abandon
+  abandon 1:address: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
@@ -89,158 +87,10 @@ def main [
 def main [
   1:address:array:num <- new number:type, 2
   2:num <- copy 1:address:array:num
-  1:address:array:num <- copy 0  # abandon
+  abandon 1:address:array: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 5
-
-:(scenario abandon_on_overwrite)
-def main [
-  1:address:num <- new number:type
-  # over-writing one allocation with another
-  1:address:num <- new number:type
-  1:address:num <- 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 abandon_after_call)
-def main [
-  1:address:num <- new number:type
-  # passing in addresses to recipes increments refcount
-  foo 1:address:num
-  1:address:num <- copy 0
-]
-def foo [
-  2:address:num <- next-ingredient
-  # return does NOT yet decrement refcount; memory must be explicitly managed
-  2:address:num <- copy 0
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: foo {1: ("address" "number")}
-# leave ambiguous precisely when the next increment happens
-+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 abandon_on_overwrite_array)
-def main [
-  1:num <- copy 30
-  # allocate an array
-  10:address:array:num <- new number:type, 20
-  11:num <- copy 10:address:array:num  # doesn't increment refcount
-  # allocate another array in its place, implicitly freeing the previous allocation
-  10:address:array:num <- 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 1000 in free-list of size 22
-
-:(scenario refcounts_abandon_address_in_container)
-# container containing an address
-container foo [
-  x:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:address:foo <- new foo:type
-  *2:address:foo <- put *2:address:foo, x:offset, 1:address:num
-  1:address:num <- copy 0
-  2:address:foo <- copy 0
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: ("address" "foo")} <- new {foo: "type"}
-+mem: incrementing refcount of 1002: 0 -> 1
-+run: {2: ("address" "foo"), "lookup": ()} <- put {2: ("address" "foo"), "lookup": ()}, {x: "offset"}, {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" "foo")} <- copy {0: "literal"}
-# start abandoning container containing address
-+mem: decrementing refcount of 1002: 1 -> 0
-# nested abandon
-+mem: decrementing refcount of 1000: 1 -> 0
-+abandon: saving 1000 in free-list of size 2
-# actually abandon the container containing address
-+abandon: saving 1002 in free-list of size 2
-
-# todo: move past dilated reagent
-:(scenario refcounts_abandon_address_in_array)
-def main [
-  1:address:num <- new number:type
-  2:address:array:address:num <- new {(address number): type}, 3
-  *2:address:array:address:num <- put-index *2:address:array:address:num, 1, 1:address:num
-  1:address:num <- copy 0
-  2:address:array:address:num <- copy 0
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: ("address" "array" "address" "number"), "lookup": ()} <- put-index {2: ("address" "array" "address" "number"), "lookup": ()}, {1: "literal"}, {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" "array" "address" "number")} <- copy {0: "literal"}
-# nested abandon
-+mem: decrementing refcount of 1000: 1 -> 0
-+abandon: saving 1000 in free-list of size 2
-
-:(scenario refcounts_abandon_address_in_container_in_array)
-# container containing an address
-container foo [
-  x:address:num
-]
-def main [
-  1:address:num <- new number:type
-  2:address:array:foo <- new foo:type, 3
-  3:foo <- merge 1:address:num
-  *2:address:array:foo <- put-index *2:address:array:foo, 1, 3:foo
-  1:address:num <- copy 0
-  3:foo <- merge 0
-  2:address:array:foo <- copy 0
-]
-+run: {1: ("address" "number")} <- new {number: "type"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {3: "foo"} <- merge {1: ("address" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {2: ("address" "array" "foo"), "lookup": ()} <- put-index {2: ("address" "array" "foo"), "lookup": ()}, {1: "literal"}, {3: "foo"}
-+mem: incrementing refcount of 1000: 2 -> 3
-+run: {1: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1000: 3 -> 2
-+run: {3: "foo"} <- merge {0: "literal"}
-+mem: decrementing refcount of 1000: 2 -> 1
-+run: {2: ("address" "array" "foo")} <- copy {0: "literal"}
-# nested abandon
-+mem: decrementing refcount of 1000: 1 -> 0
-+abandon: saving 1000 in free-list of size 2
-
-:(scenario refcounts_abandon_array_within_container)
-container foo [
-  x:address:array:num
-]
-def main [
-  1:address:array:num <- new number:type, 3
-  2:foo <- merge 1:address:array:num
-  1:address:array:num <- copy 0
-  2:foo <- copy 0
-]
-+run: {1: ("address" "array" "number")} <- new {number: "type"}, {3: "literal"}
-+mem: incrementing refcount of 1000: 0 -> 1
-+run: {2: "foo"} <- merge {1: ("address" "array" "number")}
-+mem: incrementing refcount of 1000: 1 -> 2
-+run: {1: ("address" "array" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1000: 2 -> 1
-+run: {2: "foo"} <- copy {0: "literal"}
-+mem: decrementing refcount of 1000: 1 -> 0
-+mem: automatically abandoning 1000
-# make sure we save it in a free-list of the appropriate size
-+abandon: saving 1000 in free-list of size 5
diff --git a/038new_text.cc b/038new_text.cc
index ea73dac6..4b666f1c 100644
--- a/038new_text.cc
+++ b/038new_text.cc
@@ -41,9 +41,7 @@ int new_mu_text(const string& contents) {
 //?   Total_alloc += string_length+1;
 //?   ++Num_alloc;
   int result = allocate(string_length+/*array length*/1);
-  trace("mem") << "storing string refcount 0 in location " << result << end();
-  put(Memory, result, 0);
-  int curr_address = result+/*skip refcount*/1;
+  int curr_address = result;
   trace("mem") << "storing string length " << string_length << " in location " << curr_address << end();
   put(Memory, curr_address, string_length);
   ++curr_address;  // skip length
@@ -118,13 +116,13 @@ if (!canonize_type(x)) return false;
 
 //: Allocate more to routine when initializing a literal string
 :(scenario new_string_overflow)
-% Initial_memory_per_routine = 3;
+% Initial_memory_per_routine = 2;
 def main [
   1:address:num/raw <- new number:type
-  2:text/raw <- new [a]  # not enough room in initial page, if you take the refcount and 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)
@@ -142,7 +140,6 @@ int unicode_length(const string& s) {
 
 string read_mu_text(int address) {
   if (address == 0) return "";
-  ++address;  // skip refcount
   int length = get_or_insert(Memory, address);
   if (length == 0) return "";
   return read_mu_characters(address+1, length);
diff --git a/043space.cc b/043space.cc
index 41c7b41d..f290a0b9 100644
--- a/043space.cc
+++ b/043space.cc
@@ -17,20 +17,18 @@ put(Type_abbreviations, "space", new_type_tree("address:array:location"));
 # then local 0 is really location 12, local 1 is really location 13, and so on.
 def main [
   # pretend address:array:location; in practice we'll use 'new'
-  10:num <- copy 0  # refcount
-  11:num <- copy 5  # length
+  10:num <- copy 5  # length
   default-space:space <- copy 10/unsafe
   1:num <- copy 23
 ]
-+mem: storing 23 in location 13
++mem: storing 23 in location 12
 
 :(scenario lookup_sidesteps_default_space)
 def main [
-  # pretend pointer from outside (2000 reserved for refcount)
-  2001:num <- copy 34
+  # pretend pointer from outside
+  2000:num <- copy 34
   # pretend address:array:location; in practice we'll use 'new'
-  1000:num <- copy 0  # refcount
-  1001:num <- copy 5  # length
+  1000:num <- copy 5  # length
   # actual start of this recipe
   default-space:space <- copy 1000/unsafe
   1:&:num <- copy 2000/unsafe  # even local variables always contain raw addresses
@@ -76,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 refcount*/1) : 0;
+  return current_call().default_space ? current_call().default_space : 0;
 }
 
 int address(int offset, int base) {
@@ -130,12 +128,11 @@ if (x.name == "default-space") {
 
 :(scenario lookup_sidesteps_default_space_in_get)
 def main [
-  # pretend pointer to container from outside (2000 reserved for refcount)
-  2001:num <- copy 34
-  2002:num <- copy 35
+  # pretend pointer to container from outside
+  2000:num <- copy 34
+  2001:num <- copy 35
   # pretend address:array:location; in practice we'll use 'new'
-  1000:num <- copy 0  # refcount
-  1001:num <- copy 5  # length
+  1000:num <- copy 5  # length
   # actual start of this recipe
   default-space:space <- copy 1000/unsafe
   1:&:point <- copy 2000/unsafe
@@ -150,13 +147,12 @@ element.properties.push_back(pair<string, string_tree*>("raw", NULL));
 
 :(scenario lookup_sidesteps_default_space_in_index)
 def main [
-  # pretend pointer to array from outside (2000 reserved for refcount)
-  2001:num <- copy 2  # length
-  2002:num <- copy 34
-  2003:num <- copy 35
+  # pretend pointer to array from outside
+  2000:num <- copy 2  # length
+  2001:num <- copy 34
+  2002:num <- copy 35
   # pretend address:array:location; in practice we'll use 'new'
-  1000:num <- copy 0  # refcount
-  1001:num <- copy 5  # length
+  1000:num <- copy 5  # length
   # actual start of this recipe
   default-space:space <- copy 1000/unsafe
   1:&:@:num <- copy 2000/unsafe
@@ -219,215 +215,6 @@ if (x.name == "number-of-locals") {
   return;
 }
 
-//:: try to reclaim the default-space when a recipe returns
-
-:(scenario local_scope_reclaimed_on_return)
-def main [
-  1:num <- foo
-  2:num <- foo
-  3:bool <- equal 1:num, 2:num
-]
-def foo [
-  local-scope
-  result:num <- copy default-space:space
-  return result:num
-]
-# both calls to foo should have received the same default-space
-+mem: storing 1 in location 3
-
-//: todo: do this in a transform, rather than magically in the 'return' instruction
-:(after "Falling Through End Of Recipe")
-reclaim_default_space();
-:(after "Begin Return")
-reclaim_default_space();
-:(code)
-void reclaim_default_space() {
-  if (!Reclaim_memory) return;
-  reagent default_space("default-space:address:array:location");
-  decrement_any_refcounts(default_space);
-}
-:(after "Begin Decrement Refcounts(canonized_x)")
-if (is_mu_space(canonized_x)) {
-  int space_address = (canonized_x.name == "default-space") ? current_call().default_space : get_or_insert(Memory, canonized_x.value);
-  if (space_address == 0) return;
-  // this branch relies on global state
-  string recipe_name;
-  if (has_property(canonized_x, "names")) {
-    assert(property(canonized_x, "names")->atom);
-    recipe_name = property(canonized_x, "names")->value;
-  }
-  else {
-    if (canonized_x.name != "default-space")
-      cerr << current_recipe_name() << ": " << to_string(canonized_x) << '\n';
-    assert(canonized_x.name == "default-space");
-    recipe_name = current_recipe_name();
-  }
-  const recipe_ordinal space_recipe_ordinal = get(Recipe_ordinal, recipe_name);
-  const recipe& space_recipe = get(Recipe, space_recipe_ordinal);
-  if (canonized_x.name == "default-space" && !has_property(canonized_x, "names") && !starts_by_setting_default_space(space_recipe)) return;
-  // Reclaim Space(space_address, space_recipe_ordinal, space_recipe)
-  decrement_refcount(space_address, canonized_x.type->right,
-      /*refcount*/1 + /*array length*/1 + /*number-of-locals*/Name[space_recipe_ordinal][""]);
-  return;
-}
-:(code)
-bool starts_by_setting_default_space(const recipe& r) {
-  return !r.steps.empty()
-      && !r.steps.at(0).products.empty()
-      && r.steps.at(0).products.at(0).name == "default-space";
-}
-
-//:
-
-:(scenario local_scope_reclaims_locals)
-def main [
-  local-scope
-  x:text <- new [abc]
-]
-# x
-+mem: automatically abandoning 1004
-# local-scope
-+mem: automatically abandoning 1000
-
-:(before "Reclaim Space(space_address, space_recipe_ordinal, space_recipe)")
-if (get_or_insert(Memory, space_address) <= 1) {
-  set<string> reclaimed_locals;
-  trace("mem") << "trying to reclaim locals" << end();
-  // update any refcounts for variables in the space -- in the context of the space
-  call_stack calls_stash = save_call_stack(space_address, space_recipe_ordinal);
-  Current_routine->calls.swap(calls_stash);
-  // no early returns until we restore 'calls' below
-  for (int i = /*leave default space for last*/1;  i < SIZE(space_recipe.steps);  ++i) {
-    const instruction& inst = space_recipe.steps.at(i);
-    for (int i = 0;  i < SIZE(inst.products);  ++i) {
-      reagent/*copy*/ product = inst.products.at(i);
-      if (reclaimed_locals.find(product.name) != reclaimed_locals.end()) continue;
-      reclaimed_locals.insert(product.name);
-      // local variables only
-      if (has_property(product, "lookup")) continue;
-      if (has_property(product, "raw")) continue;  // tests often want to check such locations after they run
-      // End Checks For Reclaiming Locals
-      trace("mem") << "trying to reclaim local " << product.original_string << end();
-      canonize(product);
-      decrement_any_refcounts(product);
-    }
-  }
-  Current_routine->calls.swap(calls_stash);  // restore
-}
-:(code)
-call_stack save_call_stack(int space_address, recipe_ordinal space_recipe_ordinal) {
-  call dummy_call(space_recipe_ordinal);
-  dummy_call.default_space = space_address;
-  call_stack result;
-  result.push_front(dummy_call);
-  return result;
-}
-
-:(scenario local_variables_can_outlive_call)
-def main [
-  local-scope
-  x:&:num <- new num:type
-  y:space <- copy default-space:space
-]
--mem: automatically abandoning 1005
-
-//:
-
-:(scenario local_scope_does_not_reclaim_escaping_locals)
-def main [
-  1:text <- foo
-]
-def foo [
-  local-scope
-  x:text <- new [abc]
-  return x:text
-]
-# local-scope
-+mem: automatically abandoning 1000
-# x
--mem: automatically abandoning 1004
-
-:(after "Begin Return")  // before reclaiming default-space
-increment_refcounts_of_return_ingredients(ingredients);
-:(code)
-void increment_refcounts_of_return_ingredients(const vector<vector<double> >& ingredients) {
-  assert(current_instruction().operation == RETURN);
-  if (SIZE(Current_routine->calls) == 1)  // no caller to receive result
-    return;
-  const instruction& caller_instruction = to_instruction(*++Current_routine->calls.begin());
-  for (int i = 0;  i < min(SIZE(current_instruction().ingredients), SIZE(caller_instruction.products));  ++i) {
-    if (!is_dummy(caller_instruction.products.at(i))) {
-      // no need to canonize ingredient because we ignore its value
-      increment_any_refcounts(current_instruction().ingredients.at(i), ingredients.at(i));
-    }
-  }
-}
-
-//:
-
-:(scenario local_scope_frees_up_addresses_inside_containers)
-container foo [
-  x:num
-  y:&:num
-]
-def main [
-  local-scope
-  x:&:num <- new number:type
-  y:foo <- merge 34, x:&:num
-  # x and y are both cleared when main returns
-]
-+mem: automatically abandoning 1006
-
-:(scenario local_scope_returns_addresses_inside_containers)
-container foo [
-  x:num
-  y:&:num
-]
-def f [
-  local-scope
-  x:&:num <- new number:type
-  *x:&:num <- copy 12
-  y:foo <- merge 34, x:&:num
-  # since y is 'escaping' f, it should not be cleared
-  return y:foo
-]
-def main [
-  1:foo <- f
-  3:num <- get 1:foo, x:offset
-  4:&:num <- get 1:foo, y:offset
-  5:num <- copy *4:&:num
-  1:foo <- put 1:foo, y:offset, 0
-  4:&:num <- copy 0
-]
-+mem: storing 34 in location 1
-+mem: storing 1006 in location 2
-+mem: storing 34 in location 3
-# refcount of 1:foo shouldn't include any stray ones from f
-+run: {4: ("address" "number")} <- get {1: "foo"}, {y: "offset"}
-+mem: incrementing refcount of 1006: 1 -> 2
-# 1:foo wasn't abandoned/cleared
-+run: {5: "number"} <- copy {4: ("address" "number"), "lookup": ()}
-+mem: storing 12 in location 5
-+run: {1: "foo"} <- put {1: "foo"}, {y: "offset"}, {0: "literal"}
-+mem: decrementing refcount of 1006: 2 -> 1
-+run: {4: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 1006: 1 -> 0
-+mem: automatically abandoning 1006
-
-:(scenario local_scope_claims_return_values_when_not_saved)
-def f [
-  local-scope
-  x:&:num <- new number:type
-  return x:&:num
-]
-def main [
-  f  # doesn't save result
-]
-# x reclaimed
-+mem: automatically abandoning 1004
-# f's local scope reclaimed
-+mem: automatically abandoning 1000
-
 //:: all recipes must set default-space one way or another
 
 :(before "End Globals")
@@ -446,6 +233,12 @@ void check_default_space(const recipe_ordinal r) {
   if (!starts_by_setting_default_space(caller))
     raise << caller.name << " does not seem to start with 'local-scope' or 'default-space'\n" << end();
 }
+bool starts_by_setting_default_space(const recipe& r) {
+  return !r.steps.empty()
+      && !r.steps.at(0).products.empty()
+      && r.steps.at(0).products.at(0).name == "default-space";
+}
+
 :(after "Load Mu Prelude")
 Hide_missing_default_space_errors = false;
 :(after "Test Runs")
diff --git a/044space_surround.cc b/044space_surround.cc
index f3d8319c..310672be 100644
--- a/044space_surround.cc
+++ b/044space_surround.cc
@@ -8,11 +8,9 @@
 # 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'
-  10:num <- copy 0  # refcount
-  11:num <- copy 5  # length
+  10:num <- copy 5  # length
   # pretend address:array:location; in practice we'll use 'new"
-  20:num <- copy 0  # refcount
-  21:num <- copy 5  # length
+  20:num <- copy 5  # length
   # actual start of this recipe
   default-space:space <- copy 10/unsafe
   #: later layers will explain the /names: property
@@ -22,15 +20,12 @@ def main [
 ]
 def dummy [  # just for the /names: property above
 ]
-# chain space: 10 + (refcount and length) 2
-+mem: storing 20 in location 12
-# store to default space: 10 + (skip refcount and length) 2 + (index) 1
-+mem: storing 32 in location 13
-# store to chained space: (contents of location 12) 20 + (refcount and length) 2 + (index) 1
-+mem: storing 33 in location 23
-
-:(before "End Checks For Reclaiming Locals")
-if (space_index(inst.products.at(i)) > 0) continue;
+# 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
@@ -38,14 +33,14 @@ if (space_index(inst.products.at(i)) > 0) continue;
 
 :(replace{} "int space_base(const reagent& x)")
 int space_base(const reagent& x) {
-  int base = current_call().default_space ? (current_call().default_space+/*skip refcount*/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) {
   if (space_index == 0)
     return base;
-  return space_base(x, space_index-1, get_or_insert(Memory, base+/*skip length*/1))+/*skip refcount*/1;
+  return space_base(x, space_index-1, get_or_insert(Memory, base+/*skip length*/1));
 }
 
 int space_index(const reagent& x) {
diff --git a/069hash.cc b/069hash.cc
index 8334e80c..4400c1e8 100644
--- a/069hash.cc
+++ b/069hash.cc
@@ -57,10 +57,6 @@ size_t hash_mu_address(size_t h, reagent& r) {
   if (r.value == 0) return 0;
   trace("mem") << "location " << r.value << " is " << no_scientific(get_or_insert(Memory, r.value)) << end();
   r.set_value(get_or_insert(Memory, r.value));
-  if (r.value != 0) {
-    trace("mem") << "skipping refcount at " << r.value << end();
-    r.set_value(r.value+1);  // skip refcount
-  }
   drop_from_type(r, "address");
   return hash(h, r);
 }
@@ -230,21 +226,6 @@ def main [
 # different addresses hash to the same result as long as the values the point to do so
 +mem: storing 1 in location 5
 
-:(scenario hash_ignores_address_refcount)
-def main [
-  1:&:num <- new number:type
-  *1:&:num <- copy 34
-  2:num <- hash 1:&:num
-  return-unless 2:num
-  # increment refcount
-  3:&:num <- copy 1:&:num
-  4:num <- hash 3:&:num
-  return-unless 4:num
-  5:bool <- equal 2:num, 4:num
-]
-# hash doesn't change when refcount changes
-+mem: storing 1 in location 5
-
 :(scenario hash_container_depends_only_on_elements)
 container foo [
   x:num
diff --git a/071deep_copy.cc b/071deep_copy.cc
index ffeb00bd..f5f0f1eb 100644
--- a/071deep_copy.cc
+++ b/071deep_copy.cc
@@ -3,9 +3,6 @@
 //
 // Invariant: After a deep-copy its ingredient and result will point to no
 // common addresses.
-// Implications: Refcounts of all data pointed to by the original ingredient
-// will remain unchanged. Refcounts of all data pointed to by the (newly
-// created) result will be 1, in the absence of cycles.
 //
 // We do handle cycles in the ingredient, however. All cycles are translated
 // to new cycles in the product.
@@ -60,11 +57,6 @@ def main [
 +mem: storing 0 in location 10
 # however, the contents are identical
 +mem: storing 1 in location 11
-# the result of deep-copy gets a refcount of 1
-# (its address 202 = 200 base + 2 for temporary space inside deep-copy)
-+run: {2: ("address" "number")} <- copy {0: "literal"}
-+mem: decrementing refcount of 202: 1 -> 0
-+abandon: saving 202 in free-list of size 2
 
 :(scenario deep_copy_address_to_container)
 % Memory_allocated_until = 200;
@@ -107,8 +99,7 @@ def main [
 def main [
   # avoid all memory allocations except the implicit ones inside deep-copy, so
   # that the result is deterministic
-  100:num <- copy 1  # pretend refcount
-  101:num <- copy 3  # pretend array length
+  100:num <- copy 3  # pretend array length
   1:&:@:num <- copy 100/unsafe  # pretend allocation
   put-index *1:&:@:num, 0, 34
   put-index *1:&:@:num, 1, 35
@@ -234,7 +225,7 @@ vector<double> deep_copy(const reagent& in) {
   vector<double> result = deep_copy(in, addresses_copied, tmp);
   // reclaim Mu memory allocated for tmp
   trace(9991, "run") << "deep-copy: reclaiming temporary" << end();
-  abandon(tmp.value, payload_type(tmp.type), payload_size(tmp));
+  abandon(tmp.value, payload_size(tmp));
   // reclaim host memory allocated for tmp.type when tmp goes out of scope
   return result;
 }
@@ -263,8 +254,6 @@ int deep_copy_address(const reagent& canonized_in, map<int, int>& addresses_copi
   if (contains_key(addresses_copied, in_address)) {
     int out = get(addresses_copied, in_address);
     trace(9991, "run") << "deep-copy: copy already exists: " << out << end();
-    assert(contains_key(Memory, out));  // refcount must already be incremented
-    ++get(Memory, out);
     return out;
   }
   int out = allocate(payload_size(canonized_in));
diff --git a/072recipe.cc b/072recipe.cc
index e76db812..fc53df09 100644
--- a/072recipe.cc
+++ b/072recipe.cc
@@ -97,9 +97,6 @@ case CALL: {
   Current_routine->calls.push_front(call(ingredients.at(0).at(0)));
   ingredients.erase(ingredients.begin());  // drop the callee
   finish_call_housekeeping(call_instruction, ingredients);
-  Num_refcount_updates[caller_frame.running_recipe][caller_frame.running_step_index]
-      += (Total_refcount_updates - initial_num_refcount_updates);
-  initial_num_refcount_updates = Total_refcount_updates;
   // not done with caller
   write_products = false;
   fall_through_to_next_instruction = false;
diff --git a/073scheduler.cc b/073scheduler.cc
index a8af8853..51b3584f 100644
--- a/073scheduler.cc
+++ b/073scheduler.cc
@@ -130,7 +130,6 @@ void run_main(int argc, char* argv[]) {
     vector<double> arg;
     arg.push_back(new_mu_text(argv[i]));
     assert(get(Memory, arg.back()) == 0);
-    put(Memory, arg.back(), 1);  // update refcount
     current_call().ingredient_atoms.push_back(arg);
   }
   run(main_routine);
@@ -253,74 +252,6 @@ def f2 n:&:num [
 :(before "End is_indirect_call_with_ingredients Special-cases")
 if (r == START_RUNNING) return true;
 
-//: refcounting management when starting up new routines
-
-:(scenario start_running_immediately_updates_refcounts_of_ingredients)
-% Scheduling_interval = 1;
-def main [
-  local-scope
-  create-new-routine
-  # padding to make sure we run new-routine before returning
-  dummy:num <- copy 0
-  dummy:num <- copy 0
-]
-def create-new-routine [
-  local-scope
-  n:&:num <- new number:type
-  *n <- copy 34
-  start-running new-routine, n
-  # refcount of n decremented
-]
-def new-routine n:&:num [
-  local-scope
-  load-ingredients
-  1:num/raw <- copy *n
-]
-# check that n was successfully passed into new-routine before being reclaimed
-+mem: storing 34 in location 1
-
-//: ensure this works with indirect calls using 'call' as well
-:(scenario start_running_immediately_updates_refcounts_of_ingredients_of_indirect_calls)
-% Scheduling_interval = 1;
-def main [
-  local-scope
-  n:&:num <- new number:type
-  *n <- copy 34
-  call f1, n
-  1:num/raw <- copy *n
-]
-def f1 n:&:num [
-  local-scope
-  load-ingredients
-]
-# check that n was successfully passed into new-routine before being reclaimed
-+mem: storing 34 in location 1
-
-:(scenario next_ingredient_never_leaks_refcounts)
-def create-space n:&:num -> default-space:space [
-  default-space <- new location:type, 2
-  load-ingredients
-]
-def use-space [
-  local-scope
-  0:space/names:create-space <- next-ingredient
-  n:&:num/space:1 <- next-ingredient  # should decrement refcount
-  *n/space:1 <- copy 34
-  n2:num <- add *n/space:1, 1
-  return n2
-]
-def main [
-  local-scope
-  n:&:num <- copy 12000/unsafe  # pretend allocation with a known address
-  *n <- copy 23
-  space:space/names:create-space <- create-space n
-  n2:&:num <- copy 13000/unsafe
-  n3:num <- use-space space, n2
-]
-+run: {n: ("address" "number"), "space": "1"} <- next-ingredient
-+mem: decrementing refcount of 12000: 2 -> 1
-+run: {n: ("address" "number"), "space": "1", "lookup": ()} <- copy {34: "literal"}
-
 //: back to testing 'start-running'
 
 :(scenario start_running_returns_routine_id)
diff --git a/074wait.cc b/074wait.cc
index cf8df58c..eb17c8aa 100644
--- a/074wait.cc
+++ b/074wait.cc
@@ -263,23 +263,21 @@ def main [
 # 'get-location' can read from container address
 def main [
   1:num <- copy 10
-  # 10 reserved for refcount
-  11:num <- copy 34
-  12:num <- copy 35
+  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:num <- copy 10
-  # 10 reserved for refcount
-  11:num <- copy 34
-  12:num <- copy 35
+  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/076continuation.cc b/076continuation.cc
index d9a29737..38e0185b 100644
--- a/076continuation.cc
+++ b/076continuation.cc
@@ -213,11 +213,8 @@ if (is_mu_continuation(current_instruction().ingredients.at(0))) {
   if (!contains_key(Delimited_continuation, ingredients.at(0).at(0)))
     raise << maybe(current_recipe_name()) << "no such delimited continuation " << current_instruction().ingredients.at(0).original_string << '\n' << end();
   const call_stack& new_frames = get(Delimited_continuation, ingredients.at(0).at(0)).frames;
-  for (call_stack::const_reverse_iterator p = new_frames.rbegin(); p != new_frames.rend(); ++p) {
+  for (call_stack::const_reverse_iterator p = new_frames.rbegin(); p != new_frames.rend(); ++p)
     Current_routine->calls.push_front(*p);
-    // ensure that the presence of a continuation keeps its stack frames from being reclaimed
-    increment_refcount(Current_routine->calls.front().default_space);
-  }
   if (Trace_stream) {
     Trace_stream->callstack_depth += SIZE(new_frames);
     trace("trace") << "calling delimited continuation; growing callstack depth to " << Trace_stream->callstack_depth << end();
@@ -242,25 +239,7 @@ def g [
   return-continuation-until-mark 233/mark, 34
   stash [continuation called]
 ]
-# entering main
-+mem: new alloc: 1000
-+run: {k: "continuation"}, {1: "number", "raw": ()} <- call-with-continuation-mark {233: "literal", "mark": ()}, {f: "recipe-literal"}
-# entering f
-+mem: new alloc: 1004
-# entering g
-+mem: new alloc: 1007
-# return control to main
-+run: return-continuation-until-mark {233: "literal", "mark": ()}, {34: "literal"}
-# no allocs abandoned yet
 +mem: storing 34 in location 1
-# end of main
-# make sure no memory leaks..
-+mem: trying to reclaim local k:continuation
-+mem: automatically abandoning 1007
-+mem: automatically abandoning 1004
-+mem: automatically abandoning 1000
-# ..even though we never called the continuation
--app: continuation called
 
 :(scenario continuations_continue_to_matching_mark)
 def main [
@@ -340,96 +319,6 @@ def create-yielder -> n:num [
 +mem: storing 1 in location 10
 $error: 0
 
-//: Ensure that the presence of a continuation keeps its stack frames from being reclaimed.
-
-:(scenario continuations_preserve_local_scopes)
-def main [
-  local-scope
-  k:continuation <- call-with-continuation-mark 233/mark, f
-  call k
-  return 34
-]
-def f [
-  local-scope
-  g
-]
-def g [
-  local-scope
-  return-continuation-until-mark 233/mark
-  add 1, 1
-]
-# entering main
-+mem: new alloc: 1000
-+run: {k: "continuation"} <- call-with-continuation-mark {233: "literal", "mark": ()}, {f: "recipe-literal"}
-# entering f
-+mem: new alloc: 1004
-# entering g
-+mem: new alloc: 1007
-# return control to main
-+run: return-continuation-until-mark {233: "literal", "mark": ()}
-# no allocs abandoned yet
-# finish running main
-+run: call {k: "continuation"}
-+run: add {1: "literal"}, {1: "literal"}
-+run: return {34: "literal"}
-# now k is reclaimed
-+mem: trying to reclaim local k:continuation
-# at this point all allocs in the continuation are abandoned
-+mem: automatically abandoning 1007
-+mem: automatically abandoning 1004
-# finally the alloc for main is abandoned
-+mem: automatically abandoning 1000
-
-:(before "End Increment Refcounts(canonized_x)")
-if (is_mu_continuation(canonized_x)) {
-  int continuation_id = data.at(0);
-  if (continuation_id == 0) return;
-  if (!contains_key(Delimited_continuation, continuation_id)) {
-    raise << maybe(current_recipe_name()) << "missing delimited continuation: " << canonized_x.name << '\n';
-    return;
-  }
-  delimited_continuation& curr = get(Delimited_continuation, continuation_id);
-  trace("run") << "incrementing refcount of continuation " << continuation_id << ": " << curr.nrefs << " -> " << 1+curr.nrefs << end();
-  ++curr.nrefs;
-  return;
-}
-
-:(before "End Decrement Refcounts(canonized_x)")
-if (is_mu_continuation(canonized_x)) {
-  int continuation_id = get_or_insert(Memory, canonized_x.value);
-  if (continuation_id == 0) return;
-  delimited_continuation& curr = get(Delimited_continuation, continuation_id);
-  assert(curr.nrefs > 0);
-  --curr.nrefs;
-  trace("run") << "decrementing refcount of continuation " << continuation_id << ": " << 1+curr.nrefs << " -> " << curr.nrefs << end();
-  if (curr.nrefs > 0) return;
-  trace("run") << "reclaiming continuation " << continuation_id << end();
-  // temporarily push the stack frames for the continuation to the call stack before reclaiming their spaces
-  // (because reclaim_default_space() relies on the default-space being reclaimed being at the top of the stack)
-  for (call_stack::const_iterator p = curr.frames.begin(); p != curr.frames.end(); ++p) {
-    Current_routine->calls.push_front(*p);
-    reclaim_default_space();
-    Current_routine->calls.pop_front();
-  }
-  Delimited_continuation.erase(continuation_id);
-  return;
-}
-
-:(scenario continuations_can_be_copied)
-def main [
-  local-scope
-  k:continuation <- call-with-continuation-mark 233/mark, f
-  k2:continuation <- copy k
-  # reclaiming k and k2 shouldn't delete f's local scope twice
-]
-def f [
-  local-scope
-  load-ingredients
-  return-continuation-until-mark 233/mark
-  return 0
-]
-$error: 0
-
 :(code)
 bool is_mu_continuation(reagent/*copy*/ x) {
   canonize_type(x);
diff --git a/082scenario_screen.cc b/082scenario_screen.cc
index 0c75fb6c..31cbfcc9 100644
--- a/082scenario_screen.cc
+++ b/082scenario_screen.cc
@@ -250,11 +250,11 @@ struct raw_string_stream {
 
 :(code)
 void check_screen(const string& expected_contents, const int color) {
-  int screen_location = get_or_insert(Memory, SCREEN)+/*skip refcount*/1;
+  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) + /*skip refcount*/1;  // type: 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", "");
@@ -385,7 +385,7 @@ case _DUMP_SCREEN: {
 
 :(code)
 void dump_screen() {
-  int screen_location = get_or_insert(Memory, SCREEN) + /*skip refcount*/1;
+  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", "");
@@ -393,7 +393,7 @@ void dump_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) + /*skip refcount*/1;  // type: 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);
diff --git a/085scenario_console.cc b/085scenario_console.cc
index cb69bdb0..2c3ab4bc 100644
--- a/085scenario_console.cc
+++ b/085scenario_console.cc
@@ -58,11 +58,11 @@ case ASSUME_CONSOLE: {
   slurp_body(in, r);
   int num_events = count_events(r);
   // initialize the events like in new-fake-console
-  int size = /*space for refcount and length*/2 + num_events*size_of_event();
+  int size = /*length*/1 + num_events*size_of_event();
   int event_data_address = allocate(size);
   // store length
-  put(Memory, event_data_address+/*skip refcount*/1, num_events);
-  int curr_address = event_data_address + /*skip refcount and length*/2;
+  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") {
@@ -118,12 +118,8 @@ case ASSUME_CONSOLE: {
   int console_address = allocate(size_of_console());
   trace("mem") << "storing console in " << console_address << end();
   put(Memory, CONSOLE, console_address);
-  trace("mem") << "storing console data in " << console_address+/*skip refcount*/1+/*offset of 'data' in container 'events'*/1 << end();
-  put(Memory, console_address+/*skip refcount*/1+/*offset of 'data' in container 'events'*/1, event_data_address);
-  // increment refcount for event data
-  put(Memory, event_data_address, 1);
-  // increment refcount for console
-  put(Memory, console_address, 1);
+  trace("mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end();
+  put(Memory, console_address+/*offset of 'data' in container 'events'*/1, event_data_address);
   break;
 }
 
@@ -309,7 +305,7 @@ int size_of_console() {
   if (result) return result;
   assert(get(Type_ordinal, "console"));
   type_tree* type = new type_tree("console");
-  result = size_of(type)+/*refcount*/1;
+  result = size_of(type);
   delete type;
   return result;
 }
diff --git a/089scenario_filesystem.cc b/089scenario_filesystem.cc
index fa0946bf..f14534ac 100644
--- a/089scenario_filesystem.cc
+++ b/089scenario_filesystem.cc
@@ -204,31 +204,23 @@ string munge_resources_contents(const string& data, const string& filename, cons
 
 void construct_resources_object(const map<string, string>& contents) {
   int resources_data_address = allocate(SIZE(contents)*2 + /*array length*/1);
-  int curr = resources_data_address + /*skip refcount and length*/2;
+  int curr = resources_data_address + /*skip length*/1;
   for (map<string, string>::const_iterator p = contents.begin();  p != contents.end();  ++p) {
     put(Memory, curr, new_mu_text(p->first));
     trace("mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end();
-    put(Memory, get(Memory, curr), 1);
-    trace("mem") << "storing refcount 1 in location " << get(Memory, curr) << end();
     ++curr;
     put(Memory, curr, new_mu_text(p->second));
     trace("mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end();
-    put(Memory, get(Memory, curr), 1);
-    trace("mem") << "storing refcount 1 in location " << get(Memory, curr) << end();
     ++curr;
   }
-  curr = resources_data_address+/*skip refcount*/1;
+  curr = resources_data_address;
   put(Memory, curr, SIZE(contents));  // size of array
   trace("mem") << "storing resources size " << get(Memory, curr) << " in location " << curr << end();
-  put(Memory, resources_data_address, 1);  // initialize refcount
-  trace("mem") << "storing refcount 1 in location " << resources_data_address << end();
   // wrap the resources data in a 'resources' object
   int resources_address = allocate(size_of_resources());
-  curr = resources_address+/*skip refcount*/1+/*offset 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();
-  put(Memory, resources_address, 1);  // initialize refcount
-  trace("mem") << "storing refcount 1 in location " << resources_address << end();
   // save in product
   put(Memory, RESOURCES, resources_address);
   trace("mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end();
@@ -240,7 +232,7 @@ int size_of_resources() {
   if (result) return result;
   assert(get(Type_ordinal, "resources"));
   type_tree* type = new type_tree("resources");
-  result = size_of(type)+/*refcount*/1;
+  result = size_of(type);
   delete type;
   return result;
 }