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