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