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