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