about summary refs log tree commit diff stats
path: root/036refcount.cc
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 /036refcount.cc
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?
Diffstat (limited to '036refcount.cc')
-rw-r--r--036refcount.cc554
1 files changed, 2 insertions, 552 deletions
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";
-}