1 //: Update refcounts when copying addresses.
   2 //: The top of the address layer has more on refcounts.
   3 
   4 :(scenario refcounts)
   5 def main [
   6   1:address:num <- copy 1000/unsafe
   7   2:address:num <- copy 1:address:num
   8   1:address:num <- copy 0
   9   2:address:num <- copy 0
  10 ]
  11 +run: {1: ("address" "number")} <- copy {1000: "literal", "unsafe": ()}
  12 +mem: incrementing refcount of 1000: 0 -> 1
  13 +run: {2: ("address" "number")} <- copy {1: ("address" "number")}
  14 +mem: incrementing refcount of 1000: 1 -> 2
  15 +run: {1: ("address" "number")} <- copy {0: "literal"}
  16 +mem: decrementing refcount of 1000: 2 -> 1
  17 +run: {2: ("address" "number")} <- copy {0: "literal"}
  18 +mem: decrementing refcount of 1000: 1 -> 0
  19 
  20 :(before "End write_memory(x) Special-cases")
  21 update_any_refcounts(x, data);
  22 
  23 :(before "End Globals")
  24 bool Reclaim_memory = true;
  25 :(before "End Commandline Options(*arg)")
  26 else if (is_equal(*arg, "--no-reclaim")) {
  27   cerr << "Disabling memory reclamation. Some tests will fail.\n";
  28   Reclaim_memory = false;
  29 }
  30 :(code)
  31 void update_any_refcounts(const reagent& canonized_x, const vector<double>& data) {
  32   if (!Reclaim_memory) return;
  33   if (!should_update_refcounts()) return;
  34   increment_any_refcounts(canonized_x, data);  // increment first so we don't reclaim on x <- copy x
  35   decrement_any_refcounts(canonized_x);
  36 }
  37 
  38 //: escape hatch for a later layer
  39 bool should_update_refcounts() {
  40   // End should_update_refcounts() Special-cases
  41   return true;
  42 }
  43 
  44 void increment_any_refcounts(const reagent& canonized_x, const vector<double>& data) {
  45   if (is_mu_address(canonized_x)) {
  46   ¦ assert(scalar(data));
  47   ¦ assert(!canonized_x.metadata.size);
  48   ¦ increment_refcount(data.at(0));
  49   }
  50   // End Increment Refcounts(canonized_x)
  51 }
  52 
  53 void increment_refcount(int new_address) {
  54   assert(new_address >= 0);
  55   if (new_address == 0) return;
  56   ++Total_refcount_updates;
  57   int new_refcount = get_or_insert(Memory, new_address);
  58   trace(9999, "mem") << "incrementing refcount of " << new_address << ": " << new_refcount << " -> " << new_refcount+1 << end();
  59   put(Memory, new_address, new_refcount+1);
  60 }
  61 
  62 void decrement_any_refcounts(const reagent& canonized_x) {
  63   if (is_mu_address(canonized_x)) {
  64   ¦ assert(canonized_x.value);
  65   ¦ assert(!canonized_x.metadata.size);
  66   ¦ decrement_refcount(get_or_insert(Memory, canonized_x.value), payload_type(canonized_x.type), payload_size(canonized_x));
  67   }
  68   // End Decrement Refcounts(canonized_x)
  69 }
  70 
  71 void decrement_refcount(int old_address, const type_tree* payload_type, int payload_size) {
  72   assert(old_address >= 0);
  73   if (old_address == 0) return;
  74   ++Total_refcount_updates;
  75   int old_refcount = get_or_insert(Memory, old_address);
  76   trace(9999, "mem") << "decrementing refcount of " << old_address << ": " << old_refcount << " -> " << old_refcount-1 << end();
  77   --old_refcount;
  78   put(Memory, old_address, old_refcount);
  79   if (old_refcount < 0) {
  80   ¦ tb_shutdown();
  81   ¦ cerr << "Negative refcount!!! " << old_address << ' ' << old_refcount << '\n';
  82   ¦ if (Trace_stream) {
  83   ¦ ¦ cerr << "Saving trace to last_trace.\n";
  84   ¦ ¦ ofstream fout("last_trace");
  85   ¦ ¦ fout << Trace_stream->readable_contents("");
  86   ¦ ¦ fout.close();
  87   ¦ }
  88   ¦ exit(0);
  89   }
  90   // End Decrement Refcount(old_address, payload_type, payload_size)
  91 }
  92 
  93 int payload_size(reagent/*copy*/ x) {
  94   x.properties.push_back(pair<string, string_tree*>("lookup", NULL));
  95   lookup_memory_core(x, /*check for nulls*/false);
  96   return size_of(x) + /*refcount*/1;
  97 }
  98 
  99 :(scenario refcounts_reflexive)
 100 def main [
 101   1:address:num <- new number:type
 102   # idempotent copies leave refcount unchanged
 103   1:address:num <- copy 1:address:num
 104 ]
 105 +run: {1: ("address" "number")} <- new {number: "type"}
 106 +mem: incrementing refcount of 1000: 0 -> 1
 107 +run: {1: ("address" "number")} <- copy {1: ("address" "number")}
 108 +mem: incrementing refcount of 1000: 1 -> 2
 109 +mem: decrementing refcount of 1000: 2 -> 1
 110 
 111 :(scenario refcounts_call)
 112 def main [
 113   1:address:num <- new number:type
 114   # passing in addresses to recipes increments refcount
 115   foo 1:address:num
 116   # return does NOT yet decrement refcount; memory must be explicitly managed
 117   1:address:num <- new number:type
 118 ]
 119 def foo [
 120   2:address:num <- next-ingredient
 121 ]
 122 +run: {1: ("address" "number")} <- new {number: "type"}
 123 +mem: incrementing refcount of 1000: 0 -> 1
 124 +run: foo {1: ("address" "number")}
 125 # leave ambiguous precisely when the next increment happens
 126 +mem: incrementing refcount of 1000: 1 -> 2
 127 +run: {1: ("address" "number")} <- new {number: "type"}
 128 +mem: decrementing refcount of 1000: 2 -> 1
 129 
 130 //: fix up any instructions that don't follow the usual flow of read_memory
 131 //: before the RUN switch, and write_memory after
 132 
 133 :(scenario refcounts_put)
 134 container foo [
 135   x:address:num
 136 ]
 137 def main [
 138   1:address:num <- new number:type
 139   2:address:foo <- new foo:type
 140   *2:address:foo <- put *2:address:foo, x:offset, 1:address:num
 141 ]
 142 +run: {1: ("address" "number")} <- new {number: "type"}
 143 +mem: incrementing refcount of 1000: 0 -> 1
 144 +run: {2: ("address" "foo")} <- new {foo: "type"}
 145 +mem: incrementing refcount of 1002: 0 -> 1
 146 +run: {2: ("address" "foo"), "lookup": ()} <- put {2: ("address" "foo"), "lookup": ()}, {x: "offset"}, {1: ("address" "number")}
 147 # put increments refcount
 148 +mem: incrementing refcount of 1000: 1 -> 2
 149 
 150 :(after "Write Memory in PUT in Run")
 151 reagent/*copy*/ element = element_type(base.type, offset);
 152 assert(!has_property(element, "lookup"));
 153 element.set_value(address);
 154 update_any_refcounts(element, ingredients.at(2));
 155 
 156 :(scenario refcounts_put_index)
 157 def main [
 158   1:address:num <- new number:type
 159   2:address:array:address:num <- new {(address number): type}, 3
 160   *2:address:array:address:num <- put-index *2:address:array:address:num, 0, 1:address:num
 161 ]
 162 +run: {1: ("address" "number")} <- new {number: "type"}
 163 +mem: incrementing refcount of 1000: 0 -> 1
 164 +run: {2: ("address" "array" "address" "number")} <- new {(address number): "type"}, {3: "literal"}
 165 +mem: incrementing refcount of 1002: 0 -> 1
 166 +run: {2: ("address" "array" "address" "number"), "lookup": ()} <- put-index {2: ("address" "array" "address" "number"), "lookup": ()}, {0: "literal"}, {1: ("address" "number")}
 167 # put-index increments refcount
 168 +mem: incrementing refcount of 1000: 1 -> 2
 169 
 170 :(after "Write Memory in PUT_INDEX in Run")
 171 reagent/*local*/ element;
 172 element.set_value(address);
 173 element.type = copy_array_element(base.type);
 174 update_any_refcounts(element, value);
 175 
 176 :(scenario refcounts_maybe_convert)
 177 exclusive-container foo [
 178   x:num
 179   p:address:num
 180 ]
 181 def main [
 182   1:address:num <- new number:type
 183   2:foo <- merge 1/p, 1:address:num
 184   4:address:num, 5:bool <- maybe-convert 2:foo, 1:variant/p
 185 ]
 186 +run: {1: ("address" "number")} <- new {number: "type"}
 187 +mem: incrementing refcount of 1000: 0 -> 1
 188 # merging in an address increments refcount
 189 +run: {2: "foo"} <- merge {1: "literal", "p": ()}, {1: ("address" "number")}
 190 +mem: incrementing refcount of 1000: 1 -> 2
 191 +run: {4: ("address" "number")}, {5: "boolean"} <- maybe-convert {2: "foo"}, {1: "variant", "p": ()}
 192 # maybe-convert increments refcount on success
 193 +mem: incrementing refcount of 1000: 2 -> 3
 194 
 195 :(after "Write Memory in Successful MAYBE_CONVERT")
 196 // todo: double-check data here as well
 197 vector<double> data;
 198 for (int i = 0;  i < size_of(product);  ++i)
 199   data.push_back(get_or_insert(Memory, base_address+/*skip tag*/1+i));
 200 update_any_refcounts(product, data);
 201 
 202 //:: manage refcounts in instructions that copy multiple locations at a time
 203 
 204 :(scenario refcounts_copy_nested)
 205 container foo [
 206   x:address:num  # address inside container
 207 ]
 208 def main [
 209   1:address:num <- new number:type
 210   2:address:foo <- new foo:type
 211   *2:address:foo <- put *2:address:foo, x:offset, 1:address:num
 212   3:foo <- copy *2:address:foo
 213 ]
 214 +transform: compute address offsets for container foo
 215 +transform: checking container foo, element 0
 216 +transform: address at offset 0
 217 +run: {1: ("address" "number")} <- new {number: "type"}
 218 +mem: incrementing refcount of 1000: 0 -> 1
 219 +run: {2: ("address" "foo"), "lookup": ()} <- put {2: ("address" "foo"), "lookup": ()}, {x: "offset"}, {1: ("address" "number")}
 220 +mem: incrementing refcount of 1000: 1 -> 2
 221 # copying a container increments refcounts of any contained addresses
 222 +run: {3: "foo"} <- copy {2: ("address" "foo"), "lookup": ()}
 223 +mem: incrementing refcount of 1000: 2 -> 3
 224 
 225 :(before "End type_tree Definition")
 226 struct address_element_info {
 227   // Where inside a container type (after flattening nested containers!) the
 228   // address lies
 229   int offset;
 230 
 231   // All the information we need to compute sizes of items inside an address
 232   // inside a container. `payload_type` Doesn't need to be a full-scale
 233   // reagent, since an address inside a container can never be an array, and
 234   // arrays are the only type that need to know their location to compute their
 235   // size.
 236   const type_tree* payload_type;
 237 
 238   address_element_info(int o, const type_tree* p);
 239   address_element_info(const address_element_info& other);
 240   ~address_element_info();
 241   address_element_info& operator=(const address_element_info& other);
 242 };
 243 :(code)
 244 address_element_info::address_element_info(int o, const type_tree* p) {
 245   offset = o;
 246   payload_type = p;
 247 }
 248 address_element_info::address_element_info(const address_element_info& other) {
 249   offset = other.offset;
 250   payload_type = copy(other.payload_type);
 251 }
 252 address_element_info::~address_element_info() {
 253   if (payload_type) {
 254   ¦ delete payload_type;
 255   ¦ payload_type = NULL;
 256   }
 257 }
 258 address_element_info& address_element_info::operator=(const address_element_info& other) {
 259   offset = other.offset;
 260   if (payload_type) delete payload_type;
 261   payload_type = copy(other.payload_type);
 262   return *this;
 263 }
 264 
 265 :(before "End type_tree Definition")
 266 // For exclusive containers we might sometimes have an address at some offset
 267 // if some other offset has a specific tag. This struct encapsulates such
 268 // guards.
 269 struct tag_condition_info {
 270   int offset;
 271   int tag;
 272   tag_condition_info(int o, int t) :offset(o), tag(t) {}
 273 };
 274 
 275 :(before "End container_metadata Fields")
 276 // a list of facts of the form:
 277 //
 278 //  IF offset o1 has tag t2 AND offset o2 has tag t2 AND .., THEN
 279 //    for all address_element_infos:
 280 //      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)
 281 map<set<tag_condition_info>, set<address_element_info> > address;
 282 :(code)
 283 bool operator<(const set<tag_condition_info>& a, const set<tag_condition_info>& b) {
 284   if (a.size() != b.size()) return a.size() < b.size();
 285   for (set<tag_condition_info>::const_iterator pa = a.begin(), pb = b.begin();  pa != a.end();  ++pa, ++pb) {
 286   ¦ if (pa->offset != pb->offset) return pa->offset < pb->offset;
 287   ¦ if (pa->tag != pb->tag) return pa->tag < pb->tag;
 288   }
 289   return false;  // equal
 290 }
 291 bool operator<(const tag_condition_info& a, const tag_condition_info& b) {
 292   if (a.offset != b.offset) return a.offset < b.offset;
 293   if (a.tag != b.tag) return a.tag < b.tag;
 294   return false;  // equal
 295 }
 296 bool operator<(const set<address_element_info>& a, const set<address_element_info>& b) {
 297   if (a.size() != b.size()) return a.size() < b.size();
 298   for (set<address_element_info>::const_iterator pa = a.begin(), pb = b.begin();  pa != a.end();  ++pa, ++pb) {
 299   ¦ if (pa->offset != pb->offset) return pa->offset < pb->offset;
 300   }
 301   return false;  // equal
 302 }
 303 bool operator<(const address_element_info& a, const address_element_info& b) {
 304   if (a.offset != b.offset) return a.offset < b.offset;
 305   return false;  // equal
 306 }
 307 
 308 //: populate metadata.address in a separate transform, because it requires
 309 //: already knowing the sizes of all types
 310 
 311 :(after "Transform.push_back(compute_container_sizes)")
 312 Transform.push_back(compute_container_address_offsets);  // idempotent
 313 :(code)
 314 void compute_container_address_offsets(const recipe_ordinal r) {
 315   recipe& caller = get(Recipe, r);
 316   trace(9992, "transform") << "--- compute address offsets for " << caller.name << end();
 317   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
 318   ¦ instruction& inst = caller.steps.at(i);
 319   ¦ trace(9993, "transform") << "- compute address offsets for " << to_string(inst) << end();
 320   ¦ for (int i = 0;  i < SIZE(inst.ingredients);  ++i)
 321   ¦ ¦ compute_container_address_offsets(inst.ingredients.at(i), " in '"+inst.original_string+"'");
 322   ¦ for (int i = 0;  i < SIZE(inst.products);  ++i)
 323   ¦ ¦ compute_container_address_offsets(inst.products.at(i), " in '"+inst.original_string+"'");
 324   }
 325 }
 326 
 327 void compute_container_address_offsets(reagent& r, const string& location_for_error_messages) {
 328   if (is_literal(r) || is_dummy(r)) return;
 329   compute_container_address_offsets(r.type, location_for_error_messages);
 330   if (contains_key(Container_metadata, r.type))
 331   ¦ r.metadata = get(Container_metadata, r.type);
 332 }
 333 
 334 // the recursive structure of this function needs to exactly match
 335 // compute_container_sizes
 336 void compute_container_address_offsets(const type_tree* type, const string& location_for_error_messages) {
 337   if (!type) return;
 338   if (!type->atom) {
 339   ¦ if (!type->left->atom) {
 340   ¦ ¦ raise << "invalid type " << to_string(type) << location_for_error_messages << '\n' << end();
 341   ¦ ¦ return;
 342   ¦ }
 343   ¦ if (type->left->name == "address")
 344   ¦ ¦ compute_container_address_offsets(payload_type(type), location_for_error_messages);
 345   ¦ else if (type->left->name == "array")
 346   ¦ ¦ compute_container_address_offsets(array_element(type), location_for_error_messages);
 347   ¦ // End compute_container_address_offsets Non-atom Special-cases
 348   }
 349   const type_tree* base_type = type;
 350   // Update base_type in compute_container_address_offsets
 351   if (!contains_key(Type, base_type->value)) return;  // error raised elsewhere
 352   type_info& info = get(Type, base_type->value);
 353   if (info.kind == CONTAINER) {
 354   ¦ compute_container_address_offsets(info, type, location_for_error_messages);
 355   }
 356   if (info.kind == EXCLUSIVE_CONTAINER) {
 357   ¦ compute_exclusive_container_address_offsets(info, type, location_for_error_messages);
 358   }
 359 }
 360 
 361 void compute_container_address_offsets(const type_info& container_info, const type_tree* full_type, const string& location_for_error_messages) {
 362   container_metadata& metadata = get(Container_metadata, full_type);
 363   if (!metadata.address.empty()) return;
 364   trace(9994, "transform") << "compute address offsets for container " << container_info.name << end();
 365   append_addresses(0, full_type, metadata.address, set<tag_condition_info>(), location_for_error_messages);
 366 }
 367 
 368 void compute_exclusive_container_address_offsets(const type_info& exclusive_container_info, const type_tree* full_type, const string& location_for_error_messages) {
 369   container_metadata& metadata = get(Container_metadata, full_type);
 370   trace(9994, "transform") << "compute address offsets for exclusive container " << exclusive_container_info.name << end();
 371   for (int tag = 0;  tag < SIZE(exclusive_container_info.elements);  ++tag) {
 372   ¦ set<tag_condition_info> key;
 373   ¦ key.insert(tag_condition_info(/*tag is at offset*/0, tag));
 374   ¦ append_addresses(/*skip tag offset*/1, variant_type(full_type, tag).type, metadata.address, key, location_for_error_messages);
 375   }
 376 }
 377 
 378 void append_addresses(int base_offset, const type_tree* type, map<set<tag_condition_info>, set<address_element_info> >& out, const set<tag_condition_info>& key, const string& location_for_error_messages) {
 379   if (is_mu_address(type)) {
 380   ¦ get_or_insert(out, key).insert(address_element_info(base_offset, new type_tree(*payload_type(type))));
 381   ¦ return;
 382   }
 383   const type_tree* base_type = type;
 384   // Update base_type in append_container_address_offsets
 385   const type_info& info = get(Type, base_type->value);
 386   if (info.kind == CONTAINER) {
 387   ¦ for (int curr_index = 0, curr_offset = base_offset;  curr_index < SIZE(info.elements);  ++curr_index) {
 388   ¦ ¦ trace(9993, "transform") << "checking container " << base_type->name << ", element " << curr_index << end();
 389   ¦ ¦ reagent/*copy*/ element = element_type(type, curr_index);  // not base_type
 390   ¦ ¦ // Compute Container Address Offset(element)
 391   ¦ ¦ if (is_mu_address(element)) {
 392   ¦ ¦ ¦ trace(9993, "transform") << "address at offset " << curr_offset << end();
 393   ¦ ¦ ¦ get_or_insert(out, key).insert(address_element_info(curr_offset, new type_tree(*payload_type(element.type))));
 394   ¦ ¦ ¦ ++curr_offset;
 395   ¦ ¦ }
 396   ¦ ¦ else if (is_mu_array(element)) {
 397   ¦ ¦ ¦ curr_offset += /*array length*/1;
 398   ¦ ¦ ¦ const type_tree* array_element_type = array_element(element.type);
 399   ¦ ¦ ¦ int array_element_size = size_of(array_element_type);
 400   ¦ ¦ ¦ for (int i = 0; i < static_array_length(element.type); ++i) {
 401   ¦ ¦ ¦ ¦ append_addresses(curr_offset, array_element_type, out, key, location_for_error_messages);
 402   ¦ ¦ ¦ ¦ curr_offset += array_element_size;
 403   ¦ ¦ ¦ }
 404   ¦ ¦ }
 405   ¦ ¦ else if (is_mu_container(element)) {
 406   ¦ ¦ ¦ append_addresses(curr_offset, element.type, out, key, location_for_error_messages);
 407   ¦ ¦ ¦ curr_offset += size_of(element);
 408   ¦ ¦ }
 409   ¦ ¦ else if (is_mu_exclusive_container(element)) {
 410   ¦ ¦ ¦ const type_tree* element_base_type = element.type;
 411   ¦ ¦ ¦ // Update element_base_type For Exclusive Container in append_addresses
 412   ¦ ¦ ¦ const type_info& element_info = get(Type, element_base_type->value);
 413   ¦ ¦ ¦ for (int tag = 0;  tag < SIZE(element_info.elements);  ++tag) {
 414   ¦ ¦ ¦ ¦ set<tag_condition_info> new_key = key;
 415   ¦ ¦ ¦ ¦ new_key.insert(tag_condition_info(curr_offset, tag));
 416   ¦ ¦ ¦ ¦ if (!contains_key(out, new_key))
 417   ¦ ¦ ¦ ¦ ¦ append_addresses(curr_offset+/*skip tag*/1, variant_type(element.type, tag).type, out, new_key, location_for_error_messages);
 418   ¦ ¦ ¦ }
 419   ¦ ¦ ¦ curr_offset += size_of(element);
 420   ¦ ¦ }
 421   ¦ ¦ else {
 422   ¦ ¦ ¦ // non-address primitive
 423   ¦ ¦ ¦ ++curr_offset;
 424   ¦ ¦ }
 425   ¦ }
 426   }
 427   else if (info.kind == EXCLUSIVE_CONTAINER) {
 428   ¦ for (int tag = 0;  tag < SIZE(info.elements);  ++tag) {
 429   ¦ ¦ set<tag_condition_info> new_key = key;
 430   ¦ ¦ new_key.insert(tag_condition_info(base_offset, tag));
 431   ¦ ¦ if (!contains_key(out, new_key))
 432   ¦ ¦ ¦ append_addresses(base_offset+/*skip tag*/1, variant_type(type, tag).type, out, new_key, location_for_error_messages);
 433   ¦ }
 434   }
 435 }
 436 
 437 //: for the following unit tests we'll do the work of the transform by hand
 438 
 439 :(before "End Unit Tests")
 440 void test_container_address_offsets_empty() {
 441   int old_size = SIZE(Container_metadata);
 442   // define a container with no addresses
 443   reagent r("x:point");
 444   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 445   // scan
 446   compute_container_address_offsets(r, "");
 447   // global metadata contains just the entry for foo
 448   // no entries for non-container types or other junk
 449   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 450   // the reagent we scanned knows it has no addresses
 451   CHECK(r.metadata.address.empty());
 452   // the global table contains an identical entry
 453   CHECK(contains_key(Container_metadata, r.type));
 454   CHECK(get(Container_metadata, r.type).address.empty());
 455   // compute_container_address_offsets creates no new entries
 456   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 457 }
 458 
 459 void test_container_address_offsets() {
 460   int old_size = SIZE(Container_metadata);
 461   // define a container with an address at offset 0 that we have the size for
 462   run("container foo [\n"
 463   ¦ ¦ "  x:address:num\n"
 464   ¦ ¦ "]\n");
 465   reagent r("x:foo");
 466   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 467   // scan
 468   compute_container_address_offsets(r, "");
 469   // global metadata contains just the entry for foo
 470   // no entries for non-container types or other junk
 471   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 472   // the reagent we scanned knows it has an address at offset 0
 473   CHECK_EQ(SIZE(r.metadata.address), 1);
 474   CHECK(contains_key(r.metadata.address, set<tag_condition_info>()));
 475   const set<address_element_info>& address_offsets = get(r.metadata.address, set<tag_condition_info>());  // unconditional for containers
 476   CHECK_EQ(SIZE(address_offsets), 1);
 477   CHECK_EQ(address_offsets.begin()->offset, 0);
 478   CHECK(address_offsets.begin()->payload_type->atom);
 479   CHECK_EQ(address_offsets.begin()->payload_type->name, "number");
 480   // the global table contains an identical entry
 481   CHECK(contains_key(Container_metadata, r.type));
 482   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, r.type).address, set<tag_condition_info>());
 483   CHECK_EQ(SIZE(address_offsets2), 1);
 484   CHECK_EQ(address_offsets2.begin()->offset, 0);
 485   CHECK(address_offsets2.begin()->payload_type->atom);
 486   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 487   // compute_container_address_offsets creates no new entries
 488   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 489 }
 490 
 491 void test_container_address_offsets_2() {
 492   int old_size = SIZE(Container_metadata);
 493   // define a container with an address at offset 1 that we have the size for
 494   run("container foo [\n"
 495   ¦ ¦ "  x:num\n"
 496   ¦ ¦ "  y:address:num\n"
 497   ¦ ¦ "]\n");
 498   reagent r("x:foo");
 499   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 500   // global metadata contains just the entry for foo
 501   // no entries for non-container types or other junk
 502   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 503   // scan
 504   compute_container_address_offsets(r, "");
 505   // compute_container_address_offsets creates no new entries
 506   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 507   // the reagent we scanned knows it has an address at offset 1
 508   CHECK_EQ(SIZE(r.metadata.address), 1);
 509   CHECK(contains_key(r.metadata.address, set<tag_condition_info>()));
 510   const set<address_element_info>& address_offsets = get(r.metadata.address, set<tag_condition_info>());
 511   CHECK_EQ(SIZE(address_offsets), 1);
 512   CHECK_EQ(address_offsets.begin()->offset, 1);  //
 513   CHECK(address_offsets.begin()->payload_type->atom);
 514   CHECK_EQ(address_offsets.begin()->payload_type->name, "number");
 515   // the global table contains an identical entry
 516   CHECK(contains_key(Container_metadata, r.type));
 517   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, r.type).address, set<tag_condition_info>());
 518   CHECK_EQ(SIZE(address_offsets2), 1);
 519   CHECK_EQ(address_offsets2.begin()->offset, 1);  //
 520   CHECK(address_offsets2.begin()->payload_type->atom);
 521   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 522 }
 523 
 524 void test_container_address_offsets_nested() {
 525   int old_size = SIZE(Container_metadata);
 526   // define a container with a nested container containing an address
 527   run("container foo [\n"
 528   ¦ ¦ "  x:address:num\n"
 529   ¦ ¦ "  y:num\n"
 530   ¦ ¦ "]\n"
 531   ¦ ¦ "container bar [\n"
 532   ¦ ¦ "  p:point\n"
 533   ¦ ¦ "  f:foo\n"  // nested container containing address
 534   ¦ ¦ "]\n");
 535   reagent r("x:bar");
 536   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 537   // global metadata contains entries for bar and included types: point and foo
 538   // no entries for non-container types or other junk
 539   CHECK_EQ(SIZE(Container_metadata)-old_size, 3);
 540   // scan
 541   compute_container_address_offsets(r, "");
 542   // the reagent we scanned knows it has an address at offset 2
 543   CHECK_EQ(SIZE(r.metadata.address), 1);
 544   CHECK(contains_key(r.metadata.address, set<tag_condition_info>()));
 545   const set<address_element_info>& address_offsets = get(r.metadata.address, set<tag_condition_info>());
 546   CHECK_EQ(SIZE(address_offsets), 1);
 547   CHECK_EQ(address_offsets.begin()->offset, 2);  //
 548   CHECK(address_offsets.begin()->payload_type->atom);
 549   CHECK_EQ(address_offsets.begin()->payload_type->name, "number");
 550   // the global table also knows its address offset
 551   CHECK(contains_key(Container_metadata, r.type));
 552   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, r.type).address, set<tag_condition_info>());
 553   CHECK_EQ(SIZE(address_offsets2), 1);
 554   CHECK_EQ(address_offsets2.begin()->offset, 2);  //
 555   CHECK(address_offsets2.begin()->payload_type->atom);
 556   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 557   // compute_container_address_offsets creates no new entries
 558   CHECK_EQ(SIZE(Container_metadata)-old_size, 3);
 559 }
 560 
 561 void test_container_address_offsets_from_address() {
 562   int old_size = SIZE(Container_metadata);
 563   // define a container with an address at offset 0
 564   run("container foo [\n"
 565   ¦ ¦ "  x:address:num\n"
 566   ¦ ¦ "]\n");
 567   reagent r("x:address:foo");
 568   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 569   // global metadata contains just the entry for foo
 570   // no entries for non-container types or other junk
 571   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 572   // scan an address to the container
 573   compute_container_address_offsets(r, "");
 574   // compute_container_address_offsets creates no new entries
 575   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 576   // scanning precomputed metadata for the container
 577   reagent container("x:foo");
 578   CHECK(contains_key(Container_metadata, container.type));
 579   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
 580   CHECK_EQ(SIZE(address_offsets2), 1);
 581   CHECK_EQ(address_offsets2.begin()->offset, 0);
 582   CHECK(address_offsets2.begin()->payload_type->atom);
 583   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 584 }
 585 
 586 void test_container_address_offsets_from_array() {
 587   int old_size = SIZE(Container_metadata);
 588   // define a container with an address at offset 0
 589   run("container foo [\n"
 590   ¦ ¦ "  x:address:num\n"
 591   ¦ ¦ "]\n");
 592   reagent r("x:array:foo");
 593   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 594   // global metadata contains just the entry for foo
 595   // no entries for non-container types or other junk
 596   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 597   // scan an array of the container
 598   compute_container_address_offsets(r, "");
 599   // compute_container_address_offsets creates no new entries
 600   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 601   // scanning precomputed metadata for the container
 602   reagent container("x:foo");
 603   CHECK(contains_key(Container_metadata, container.type));
 604   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
 605   CHECK_EQ(SIZE(address_offsets2), 1);
 606   CHECK_EQ(address_offsets2.begin()->offset, 0);
 607   CHECK(address_offsets2.begin()->payload_type->atom);
 608   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 609 }
 610 
 611 void test_container_address_offsets_from_address_to_array() {
 612   int old_size = SIZE(Container_metadata);
 613   // define a container with an address at offset 0
 614   run("container foo [\n"
 615   ¦ ¦ "  x:address:num\n"
 616   ¦ ¦ "]\n");
 617   reagent r("x:address:array:foo");
 618   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 619   // global metadata contains just the entry for foo
 620   // no entries for non-container types or other junk
 621   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 622   // scan an address to an array of the container
 623   compute_container_address_offsets(r, "");
 624   // compute_container_address_offsets creates no new entries
 625   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 626   // scanning precomputed metadata for the container
 627   reagent container("x:foo");
 628   CHECK(contains_key(Container_metadata, container.type));
 629   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
 630   CHECK_EQ(SIZE(address_offsets2), 1);
 631   CHECK_EQ(address_offsets2.begin()->offset, 0);
 632   CHECK(address_offsets2.begin()->payload_type->atom);
 633   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 634 }
 635 
 636 void test_container_address_offsets_from_static_array() {
 637   int old_size = SIZE(Container_metadata);
 638   // define a container with an address at offset 0
 639   run("container foo [\n"
 640   ¦ ¦ "  x:address:num\n"
 641   ¦ ¦ "]\n");
 642   reagent r("x:array:foo:10");
 643   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 644   // global metadata contains just the entry for foo
 645   // no entries for non-container types or other junk
 646   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 647   // scan a static array of the container
 648   compute_container_address_offsets(r, "");
 649   // compute_container_address_offsets creates no new entries
 650   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 651   // scanning precomputed metadata for the container
 652   reagent container("x:foo");
 653   CHECK(contains_key(Container_metadata, container.type));
 654   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
 655   CHECK_EQ(SIZE(address_offsets2), 1);
 656   CHECK_EQ(address_offsets2.begin()->offset, 0);
 657   CHECK(address_offsets2.begin()->payload_type->atom);
 658   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 659 }
 660 
 661 void test_container_address_offsets_from_address_to_static_array() {
 662   int old_size = SIZE(Container_metadata);
 663   // define a container with an address at offset 0
 664   run("container foo [\n"
 665   ¦ ¦ "  x:address:num\n"
 666   ¦ ¦ "]\n");
 667   reagent r("x:address:array:foo:10");
 668   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 669   // global metadata contains just the entry for foo
 670   // no entries for non-container types or other junk
 671   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 672   // scan an address to a static array of the container
 673   compute_container_address_offsets(r, "");
 674   // compute_container_address_offsets creates no new entries
 675   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 676   // scanning precomputed metadata for the container
 677   reagent container("x:foo");
 678   CHECK(contains_key(Container_metadata, container.type));
 679   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
 680   CHECK_EQ(SIZE(address_offsets2), 1);
 681   CHECK_EQ(address_offsets2.begin()->offset, 0);
 682   CHECK(address_offsets2.begin()->payload_type->atom);
 683   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 684 }
 685 
 686 void test_container_address_offsets_from_repeated_address_and_array_types() {
 687   int old_size = SIZE(Container_metadata);
 688   // define a container with an address at offset 0
 689   run("container foo [\n"
 690   ¦ ¦ "  x:address:num\n"
 691   ¦ ¦ "]\n");
 692   // scan a deep nest of 'address' and 'array' types modifying a container
 693   reagent r("x:address:array:address:address:array:foo:10");
 694   compute_container_sizes(r, "");  // need to first pre-populate the metadata
 695   // global metadata contains just the entry for foo
 696   // no entries for non-container types or other junk
 697   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 698   compute_container_address_offsets(r, "");
 699   // compute_container_address_offsets creates no new entries
 700   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
 701   // scanning precomputed metadata for the container
 702   reagent container("x:foo");
 703   CHECK(contains_key(Container_metadata, container.type));
 704   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
 705   CHECK_EQ(SIZE(address_offsets2), 1);
 706   CHECK_EQ(address_offsets2.begin()->offset, 0);
 707   CHECK(address_offsets2.begin()->payload_type->atom);
 708   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
 709 }
 710 
 711 //: use metadata.address to update refcounts within containers, arrays and
 712 //: exclusive containers
 713 
 714 :(before "End Increment Refcounts(canonized_x)")
 715 if (is_mu_container(canonized_x) || is_mu_exclusive_container(canonized_x)) {
 716   const container_metadata& metadata = get(Container_metadata, canonized_x.type);
 717   for (map<set<tag_condition_info>, set<address_element_info> >::const_iterator p = metadata.address.begin();  p != metadata.address.end();  ++p) {
 718   ¦ if (!all_match(data, p->first)) continue;
 719   ¦ for (set<address_element_info>::const_iterator info = p->second.begin();  info != p->second.end();  ++info)
 720   ¦ ¦ increment_refcount(data.at(info->offset));
 721   }
 722 }
 723 
 724 :(before "End Decrement Refcounts(canonized_x)")
 725 if (is_mu_container(canonized_x) || is_mu_exclusive_container(canonized_x)) {
 726   trace(9999, "mem") << "need to read old value of '" << to_string(canonized_x) << "' to figure out what refcounts to decrement" << end();
 727   // read from canonized_x but without canonizing again
 728   reagent/*copy*/ tmp = canonized_x;
 729   tmp.properties.push_back(pair<string, string_tree*>("raw", NULL));
 730   vector<double> data = read_memory(tmp);
 731   trace(9999, "mem") << "done reading old value of '" << to_string(canonized_x) << "'" << end();
 732   const container_metadata& metadata = get(Container_metadata, canonized_x.type);
 733   for (map<set<tag_condition_info>, set<address_element_info> >::const_iterator p = metadata.address.begin();  p != metadata.address.end();  ++p) {
 734   ¦ if (!all_match(data, p->first)) continue;
 735   ¦ for (set<address_element_info>::const_iterator info = p->second.begin();  info != p->second.end();  ++info) {
 736   ¦ ¦ int element_address = get_or_insert(Memory, canonized_x.value + info->offset);
 737   ¦ ¦ reagent/*local*/ element;
 738   ¦ ¦ element.set_value(element_address+/*skip refcount*/1);
 739   ¦ ¦ element.type = new type_tree(*info->payload_type);
 740   ¦ ¦ decrement_refcount(element_address, info->payload_type, size_of(element)+/*refcount*/1);
 741   ¦ }
 742   }
 743 }
 744 
 745 :(code)
 746 bool all_match(const vector<double>& data, const set<tag_condition_info>& conditions) {
 747   for (set<tag_condition_info>::const_iterator p = conditions.begin();  p != conditions.end();  ++p) {
 748   ¦ if (data.at(p->offset) != p->tag)
 749   ¦ ¦ return false;
 750   }
 751   return true;
 752 }
 753 
 754 :(scenario refcounts_put_container)
 755 container foo [
 756   a:bar  # contains an address
 757 ]
 758 container bar [
 759   x:address:num
 760 ]
 761 def main [
 762   1:address:num <- new number:type
 763   2:bar <- merge 1:address:num
 764   3:address:foo <- new foo:type
 765   *3:address:foo <- put *3:address:foo, a:offset, 2:bar
 766 ]
 767 +run: {1: ("address" "number")} <- new {number: "type"}
 768 +mem: incrementing refcount of 1000: 0 -> 1
 769 +run: {2: "bar"} <- merge {1: ("address" "number")}
 770 +mem: incrementing refcount of 1000: 1 -> 2
 771 +run: {3: ("address" "foo"), "lookup": ()} <- put {3: ("address" "foo"), "lookup": ()}, {a: "offset"}, {2: "bar"}
 772 # put increments refcount inside container
 773 +mem: incrementing refcount of 1000: 2 -> 3
 774 
 775 :(scenario refcounts_put_index_array)
 776 container bar [
 777   x:address:num
 778 ]
 779 def main [
 780   1:address:num <- new number:type
 781   2:bar <- merge 1:address:num
 782   3:address:array:bar <- new bar:type, 3
 783   *3:address:array:bar <- put-index *3:address:array:bar, 0, 2:bar
 784 ]
 785 +run: {1: ("address" "number")} <- new {number: "type"}
 786 +mem: incrementing refcount of 1000: 0 -> 1
 787 +run: {2: "bar"} <- merge {1: ("address" "number")}
 788 +mem: incrementing refcount of 1000: 1 -> 2
 789 +run: {3: ("address" "array" "bar"), "lookup": ()} <- put-index {3: ("address" "array" "bar"), "lookup": ()}, {0: "literal"}, {2: "bar"}
 790 # put-index increments refcount inside container
 791 +mem: incrementing refcount of 1000: 2 -> 3
 792 
 793 :(scenario refcounts_maybe_convert_container)
 794 exclusive-container foo [
 795   a:num
 796   b:bar  # contains an address
 797 ]
 798 container bar [
 799   x:address:num
 800 ]
 801 def main [
 802   1:address:num <- new number:type
 803   2:bar <- merge 1:address:num
 804   3:foo <- merge 1/b, 2:bar
 805   5:bar, 6:bool <- maybe-convert 3:foo, 1:variant/b
 806 ]
 807 +run: {1: ("address" "number")} <- new {number: "type"}
 808 +mem: incrementing refcount of 1000: 0 -> 1
 809 +run: {2: "bar"} <- merge {1: ("address" "number")}
 810 +mem: incrementing refcount of 1000: 1 -> 2
 811 +run: {3: "foo"} <- merge {1: "literal", "b": ()}, {2: "bar"}
 812 +mem: incrementing refcount of 1000: 2 -> 3
 813 +run: {5: "bar"}, {6: "boolean"} <- maybe-convert {3: "foo"}, {1: "variant", "b": ()}
 814 +mem: incrementing refcount of 1000: 3 -> 4
 815 
 816 :(scenario refcounts_copy_doubly_nested)
 817 container foo [
 818   a:bar  # no addresses
 819   b:curr  # contains addresses
 820 ]
 821 container bar [
 822   x:num
 823   y:num
 824 ]
 825 container curr [
 826   x:num
 827   y:address:num  # address inside container inside container
 828 ]
 829 def main [
 830   1:address:num <- new number:type
 831   2:address:curr <- new curr:type
 832   *2:address:curr <- put *2:address:curr, 1:offset/y, 1:address:num
 833   3:address:foo <- new foo:type
 834   *3:address:foo <- put *3:address:foo, 1:offset/b, *2:address:curr
 835   4:foo <- copy *3:address:foo
 836 ]
 837 +transform: compute address offsets for container foo
 838 +transform: checking container foo, element 1
 839 +transform: address at offset 3
 840 +run: {1: ("address" "number")} <- new {number: "type"}
 841 +mem: incrementing refcount of 1000: 0 -> 1
 842 # storing an address in a container updates its refcount
 843 +run: {2: ("address" "curr"), "lookup": ()} <- put {2: ("address" "curr"), "lookup": ()}, {1: "offset", "y": ()}, {1: ("address" "number")}
 844 +mem: incrementing refcount of 1000: 1 -> 2
 845 # storing a container in a container updates refcounts of any contained addresses
 846 +run: {3: ("address" "foo"), "lookup": ()} <- put {3: ("address" "foo"), "lookup": ()}, {1: "offset", "b": ()}, {2: ("address" "curr"), "lookup": ()}
 847 +mem: incrementing refcount of 1000: 2 -> 3
 848 # copying a container containing a container containing an address updates refcount
 849 +run: {4: "foo"} <- copy {3: ("address" "foo"), "lookup": ()}
 850 +mem: incrementing refcount of 1000: 3 -> 4
 851 
 852 :(scenario refcounts_copy_exclusive_container_within_container)
 853 container foo [
 854   a:num
 855   b:bar
 856 ]
 857 exclusive-container bar [
 858   x:num
 859   y:num
 860   z:address:num
 861 ]
 862 def main [
 863   1:address:num <- new number:type
 864   2:bar <- merge 0/x, 34
 865   3:foo <- merge 12, 2:bar
 866   5:bar <- merge 1/y, 35
 867   6:foo <- merge 13, 5:bar
 868   8:bar <- merge 2/z, 1:address:num
 869   9:foo <- merge 14, 8:bar
 870   11:foo <- copy 9:foo
 871 ]
 872 +run: {1: ("address" "number")} <- new {number: "type"}
 873 +mem: incrementing refcount of 1000: 0 -> 1
 874 # no change while merging items of other types
 875 +run: {8: "bar"} <- merge {2: "literal", "z": ()}, {1: ("address" "number")}
 876 +mem: incrementing refcount of 1000: 1 -> 2
 877 +run: {9: "foo"} <- merge {14: "literal"}, {8: "bar"}
 878 +mem: incrementing refcount of 1000: 2 -> 3
 879 +run: {11: "foo"} <- copy {9: "foo"}
 880 +mem: incrementing refcount of 1000: 3 -> 4
 881 
 882 :(scenario refcounts_copy_container_within_exclusive_container)
 883 exclusive-container foo [
 884   a:num
 885   b:bar
 886 ]
 887 container bar [
 888   x:num
 889   y:num
 890   z:address:num
 891 ]
 892 def main [
 893   1:address:num <- new number:type
 894   2:foo <- merge 0/a, 34
 895   6:foo <- merge 0/a, 35
 896   10:bar <- merge 2/x, 15/y, 1:address:num
 897   13:foo <- merge 1/b, 10:bar
 898   17:foo <- copy 13:foo
 899 ]
 900 +run: {1: ("address" "number")} <- new {number: "type"}
 901 +mem: incrementing refcount of 1000: 0 -> 1
 902 # no change while merging items of other types
 903 +run: {10: "bar"} <- merge {2: "literal", "x": ()}, {15: "literal", "y": ()}, {1: ("address" "number")}
 904 +mem: incrementing refcount of 1000: 1 -> 2
 905 +run: {13: "foo"} <- merge {1: "literal", "b": ()}, {10: "bar"}
 906 +mem: incrementing refcount of 1000: 2 -> 3
 907 +run: {17: "foo"} <- copy {13: "foo"}
 908 +mem: incrementing refcount of 1000: 3 -> 4
 909 
 910 :(scenario refcounts_copy_exclusive_container_within_exclusive_container)
 911 exclusive-container foo [
 912   a:num
 913   b:bar
 914 ]
 915 exclusive-container bar [
 916   x:num
 917   y:address:num
 918 ]
 919 def main [
 920   1:address:num <- new number:type
 921   10:foo <- merge 1/b, 1/y, 1:address:num
 922   20:foo <- copy 10:foo
 923 ]
 924 +run: {1: ("address" "number")} <- new {number: "type"}
 925 +mem: incrementing refcount of 1000: 0 -> 1
 926 # no change while merging items of other types
 927 +run: {10: "foo"} <- merge {1: "literal", "b": ()}, {1: "literal", "y": ()}, {1: ("address" "number")}
 928 +mem: incrementing refcount of 1000: 1 -> 2
 929 +run: {20: "foo"} <- copy {10: "foo"}
 930 +mem: incrementing refcount of 1000: 2 -> 3
 931 
 932 :(scenario refcounts_copy_array_within_container)
 933 container foo [
 934   x:address:array:num
 935 ]
 936 def main [
 937   1:address:array:num <- new number:type, 3
 938   2:foo <- merge 1:address:array:num
 939   3:address:array:num <- new number:type, 5
 940   2:foo <- merge 3:address:array:num
 941 ]
 942 +run: {1: ("address" "array" "number")} <- new {number: "type"}, {3: "literal"}
 943 +mem: incrementing refcount of 1000: 0 -> 1
 944 +run: {2: "foo"} <- merge {1: ("address" "array" "number")}
 945 +mem: incrementing refcount of 1000: 1 -> 2
 946 +run: {2: "foo"} <- merge {3: ("address" "array" "number")}
 947 +mem: decrementing refcount of 1000: 2 -> 1
 948 
 949 :(scenario refcounts_copy_address_within_static_array_within_container)
 950 container foo [
 951   a:array:bar:3
 952   b:address:num
 953 ]
 954 container bar [
 955   y:num
 956   z:address:num
 957 ]
 958 def main [
 959   1:address:num <- new number:type
 960   2:bar <- merge 34, 1:address:num
 961   10:array:bar:3 <- create-array
 962   put-index 10:array:bar:3, 1, 2:bar
 963   20:foo <- merge 10:array:bar:3, 1:address:num
 964   1:address:num <- copy 0
 965   2:bar <- merge 34, 1:address:num
 966   put-index 10:array:bar:3, 1, 2:bar
 967   20:foo <- merge 10:array:bar:3, 1:address:num
 968 ]
 969 +run: {1: ("address" "number")} <- new {number: "type"}
 970 +mem: incrementing refcount of 1000: 0 -> 1
 971 +run: {2: "bar"} <- merge {34: "literal"}, {1: ("address" "number")}
 972 +mem: incrementing refcount of 1000: 1 -> 2
 973 +run: put-index {10: ("array" "bar" "3")}, {1: "literal"}, {2: "bar"}
 974 +mem: incrementing refcount of 1000: 2 -> 3
 975 +run: {20: "foo"} <- merge {10: ("array" "bar" "3")}, {1: ("address" "number")}
 976 +mem: incrementing refcount of 1000: 3 -> 4
 977 +mem: incrementing refcount of 1000: 4 -> 5
 978 +run: {1: ("address" "number")} <- copy {0: "literal"}
 979 +mem: decrementing refcount of 1000: 5 -> 4
 980 +run: {2: "bar"} <- merge {34: "literal"}, {1: ("address" "number")}
 981 +mem: decrementing refcount of 1000: 4 -> 3
 982 +run: put-index {10: ("array" "bar" "3")}, {1: "literal"}, {2: "bar"}
 983 +mem: decrementing refcount of 1000: 3 -> 2
 984 +run: {20: "foo"} <- merge {10: ("array" "bar" "3")}, {1: ("address" "number")}
 985 +mem: decrementing refcount of 1000: 2 -> 1
 986 +mem: decrementing refcount of 1000: 1 -> 0
 987 
 988 :(scenario refcounts_handle_exclusive_containers_with_different_tags)
 989 container foo1 [
 990   x:address:num
 991   y:num
 992 ]
 993 container foo2 [
 994   x:num
 995   y:address:num
 996 ]
 997 exclusive-container bar [
 998   a:foo1
 999   b:foo2
1000 ]
1001 def main [
1002   1:address:num <- copy 12000/unsafe  # pretend allocation
1003   *1:address:num <- copy 34
1004   2:bar <- merge 0/foo1, 1:address:num, 97
1005   5:address:num <- copy 13000/unsafe  # pretend allocation
1006   *5:address:num <- copy 35
1007   6:bar <- merge 1/foo2, 98, 5:address:num
1008   2:bar <- copy 6:bar
1009 ]
1010 +run: {2: "bar"} <- merge {0: "literal", "foo1": ()}, {1: ("address" "number")}, {97: "literal"}
1011 +mem: incrementing refcount of 12000: 1 -> 2
1012 +run: {6: "bar"} <- merge {1: "literal", "foo2": ()}, {98: "literal"}, {5: ("address" "number")}
1013 +mem: incrementing refcount of 13000: 1 -> 2
1014 +run: {2: "bar"} <- copy {6: "bar"}
1015 +mem: incrementing refcount of 13000: 2 -> 3
1016 +mem: decrementing refcount of 12000: 2 -> 1
1017 
1018 :(code)
1019 bool is_mu_container(const reagent& r) {
1020   return is_mu_container(r.type);
1021 }
1022 bool is_mu_container(const type_tree* type) {
1023   if (!type) return false;
1024   // End is_mu_container(type) Special-cases
1025   if (type->value == 0) return false;
1026   type_info& info = get(Type, type->value);
1027   return info.kind == CONTAINER;
1028 }
1029 
1030 bool is_mu_exclusive_container(const reagent& r) {
1031   return is_mu_exclusive_container(r.type);
1032 }
1033 bool is_mu_exclusive_container(const type_tree* type) {
1034   if (!type) return false;
1035   // End is_mu_exclusive_container(type) Special-cases
1036   if (type->value == 0) return false;
1037   type_info& info = get(Type, type->value);
1038   return info.kind == EXCLUSIVE_CONTAINER;
1039 }
1040 
1041 //:: Counters for trying to understand where Mu programs are spending time
1042 //:: updating refcounts.
1043 
1044 :(before "End Globals")
1045 int Total_refcount_updates = 0;
1046 map<recipe_ordinal, map</*step index*/int, /*num refcount updates*/int> > Num_refcount_updates;
1047 :(after "Running One Instruction")
1048 int initial_num_refcount_updates = Total_refcount_updates;
1049 :(before "End Running One Instruction")
1050 if (Run_profiler) {
1051   Num_refcount_updates[current_call().running_recipe][current_call().running_step_index]
1052   ¦ ¦ += (Total_refcount_updates - initial_num_refcount_updates);
1053   initial_num_refcount_updates = Total_refcount_updates;
1054 }
1055 :(before "End Non-primitive Call(caller_frame)")
1056 Num_refcount_updates[caller_frame.running_recipe][caller_frame.running_step_index]
1057   ¦ += (Total_refcount_updates - initial_num_refcount_updates);
1058 initial_num_refcount_updates = Total_refcount_updates;
1059 :(after "Starting Reply")
1060 if (Run_profiler) {
1061   Num_refcount_updates[current_call().running_recipe][current_call().running_step_index]
1062   ¦ ¦ += (Total_refcount_updates - initial_num_refcount_updates);
1063   initial_num_refcount_updates = Total_refcount_updates;
1064 }
1065 :(before "End dump_profile")
1066 fout.open("profile.refcounts");
1067 if (fout) {
1068   for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin();  p != Recipe.end();  ++p)
1069   ¦ dump_recipe_profile(p->first, p->second, fout);
1070 }
1071 fout.close();
1072 :(code)
1073 void dump_recipe_profile(recipe_ordinal ridx, const recipe& r, ostream& out) {
1074   out << "recipe " << r.name << " [\n";
1075   for (int i = 0;  i < SIZE(r.steps);  ++i) {
1076   ¦ out << std::setw(6) << Num_refcount_updates[ridx][i] << ' ' << to_string(r.steps.at(i)) << '\n';
1077   }
1078   out << "]\n\n";
1079 }