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