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