// To recursively copy containers and any addresses they contain, use // 'deep-copy'. // // Invariant: After a deep-copy its ingredient and result will point to no // common addresses. // Implications: Refcounts of all data pointed to by the original ingredient // will remain unchanged. Refcounts of all data pointed to by the (newly // created) result will be 1, in the absence of cycles. // // We do handle cycles in the ingredient, however. All cycles are translated // to new cycles in the product. :(scenario deep_copy_number) def main [ local-scope x:number <- copy 34 y:number <- deep-copy x 10:boolean/raw <- equal x, y ] # non-address primitives are identical +mem: storing 1 in location 10 :(scenario deep_copy_container_without_address) container foo [ x:number y:number ] def main [ local-scope a:foo <- merge 34, 35 b:foo <- deep-copy a 10:boolean/raw <- equal a, b ] # containers are identical as long as they don't contain addresses +mem: storing 1 in location 10 :(scenario deep_copy_address) % Memory_allocated_until = 200; def main [ # avoid all memory allocations except the implicit ones inside deep-copy, so # that the result is deterministic 1:address:number <- copy 100/unsafe # pretend allocation *1:address:number <- copy 34 2:address:number <- deep-copy 1:address:number 10:boolean <- equal 1:address:number, 2:address:number 11:boolean <- equal *1:address:number, *2:address:number 2:address:number <- copy 0 ] # the result of deep-copy is a new address +mem: storing 0 in location 10 # however, the contents are identical +mem: storing 1 in location 11 # the result of deep-copy gets a refcount of 1 # (its address 202 = 200 base + 2 for temporary space inside deep-copy) +run: {2: ("address" "number")} <- copy {0: "literal"} +mem: decrementing refcount of 202: 1 -> 0 +abandon: saving 202 in free-list of size 2 :(scenario deep_copy_address_to_container) % Memory_allocated_until = 200; def main [ # avoid all memory allocations except the implicit ones inside deep-copy, so # that the result is deterministic 1:address:point <- copy 100/unsafe # pretend allocation *1:address:point <- merge 34, 35 2:address:point <- deep-copy 1:address:point 10:boolean <- equal 1:address:point, 2:address:point 11:boolean <- equal *1:address:point, *2:address:point ] # the result of deep-copy is a new address +mem: storing 0 in location 10 # however, the contents are identical +mem: storing 1 in location 11 :(scenario deep_copy_address_to_address) % Memory_allocated_until = 200; def main [ # avoid all memory allocations except the implicit ones inside deep-copy, so # that the result is deterministic 1:address:address:number <- copy 100/unsafe # pretend allocation *1:address:address:number <- copy 150/unsafe **1:address:address:number <- copy 34 2:address:address:number <- deep-copy 1:address:address:number 10:boolean <- equal 1:address:address:number, 2:address:address:number 11:boolean <- equal *1:address:address:number, *2:address:address:number 12:boolean <- equal **1:address:address:number, **2:address:address:number ] # the result of deep-copy is a new address +mem: storing 0 in location 10 # any addresses in it or pointed to it are also new +mem: storing 0 in location 11 # however, the non-address contents are identical +mem: storing 1 in location 12 :(scenario deep_copy_array) % Memory_allocated_until = 200; def main [ # avoid all memory allocations except the implicit ones inside deep-copy, so # that the result is deterministic 100:number <- copy 1 # pretend refcount 101:number <- copy 3 # pretend array length 1:address:array:number <- copy 100/unsafe # pretend allocation put-index *1:address:array:number, 0, 34 put-index *1:address:array:number, 1, 35 put-index *1:address:array:number, 2, 36 stash [old:], *1:address:array:number 2:address:array:number <- deep-copy 1:address:array:number stash 2:address:array:number stash [new:], *2:address:array:number 10:boolean <- equal 1:address:array:number, 2:address:array:number 11:boolean <- equal *1:address:array:number, *2:address:array:number ] +app: old: 3 34 35 36 +app: new: 3 34 35 36 # the result of deep-copy is a new address +mem: storing 0 in location 10 # however, the contents are identical +mem: storing 1 in location 11 :(scenario deep_copy_container_with_address) container foo [ x:number y:address:number ] def main [ local-scope y0:address:number <- new number:type *y0 <- copy 35 a:foo <- merge 34, y0 b:foo <- deep-copy a 10:boolean/raw <- equal a, b y1:address:number <- get b, y:offset 11:boolean/raw <- equal y0, y1 12:number/raw <- copy *y1 ] # containers containing addresses are not identical to their deep copies +mem: storing 0 in location 10 # the addresses they contain are not identical either +mem: storing 0 in location 11 +mem: storing 35 in location 12 :(scenario deep_copy_exclusive_container_with_address) exclusive-container foo [ x:number y:address:number ] def main [ local-scope y0:address:number <- new number:type *y0 <- copy 34 a:foo <- merge 1/y, y0 b:foo <- deep-copy a 10:boolean/raw <- equal a, b y1:address:number, z:boolean <- maybe-convert b, y:variant 11:boolean/raw <- equal y0, y1 12:number/raw <- copy *y1 ] # exclusive containers containing addresses are not identical to their deep copies +mem: storing 0 in location 10 # the addresses they contain are not identical either +mem: storing 0 in location 11 +mem: storing 34 in location 12 :(scenario deep_copy_exclusive_container_with_container_with_address) exclusive-container foo [ x:number y:bar # inline ] container bar [ x:address:number ] def main [ local-scope y0:address:number <- new number:type *y0 <- copy 34 a:bar <- merge y0 b:foo <- merge 1/y, a c:foo <- deep-copy b 10:boolean/raw <- equal b, c d:bar, z:boolean <- maybe-convert c, y:variant y1:address:number <- get d, x:offset 11:boolean/raw <- equal y0, y1 12:number/raw <- copy *y1 ] # exclusive containers containing addresses are not identical to their deep copies +mem: storing 0 in location 10 # sub-containers containing addresses are not identical either +mem: storing 0 in location 11 +mem: storing 34 in location 12 :(before "End Primitive Recipe Declarations") DEEP_COPY, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "deep-copy", DEEP_COPY); :(before "End Primitive Recipe Checks") case DEEP_COPY: { if (SIZE(inst.ingredients) != 1) { raise << maybe(get(Recipe, r).name) << "'deep-copy' takes exactly one ingredient rather than '" << to_original_string(inst) << "'\n" << end(); break; } if (SIZE(inst.products) != 1) { raise << maybe(get(Recipe, r).name) << "'deep-copy' takes exactly one ingredient rather than '" << to_original_string(inst) << "'\n" << end(); break; } if (!types_strictly_match(inst.ingredients.at(0), inst.products.at(0))) { raise << maybe(get(Recipe, r).name) << "'deep-copy' requires its ingredient and product to be the same type, but got '" << to_original_string(inst) << "'\n" << end(); break; } break; } :(before "End Primitive Recipe Implementations") case DEEP_COPY: { const reagent& input = current_instruction().ingredients.at(0); // allocate a tiny bit of temporary space for deep_copy() trace(9991, "run") << "deep-copy: allocating space for temporary" << end(); reagent tmp("tmp:address:number"); tmp.value = allocate(1); products.push_back(deep_copy(input, tmp)); // reclaim Mu memory allocated for tmp trace(9991, "run") << "deep-copy: reclaiming temporary" << end(); abandon(tmp.value, tmp.type->right, payload_size(tmp)); // reclaim host memory allocated for tmp.type when tmp goes out of scope break; } :(code) vector deep_copy(const reagent& in, const reagent& tmp) { map addresses_copied; return deep_copy(in, addresses_copied, tmp); } vector deep_copy(reagent/*copy*/ in, map& addresses_copied, const reagent& tmp) { canonize(in); vector result; if (is_mu_address(in)) result.push_back(deep_copy_address(in, addresses_copied, tmp)); else deep_copy(in, addresses_copied, tmp, result); return result; } // deep-copy an address and return a new address int deep_copy_address(const reagent& canonized_in, map& addresses_copied, const reagent& tmp) { if (canonized_in.value == 0) return 0; int in_address = payload_address(canonized_in); trace(9991, "run") << "deep-copy: copying address " << in_address << end(); if (contains_key(addresses_copied, in_address)) { int out = get(addresses_copied, in_address); trace(9991, "run") << "deep-copy: copy already exists: " << out << end(); return out; } int out = allocate(payload_size(canonized_in)); trace(9991, "run") << "deep-copy: new address is " << out << end(); put(addresses_copied, in_address, out); reagent/*copy*/ payload = canonized_in; payload.properties.push_back(pair("lookup", NULL)); trace(9991, "run") << "recursing on payload " << payload.value << ' ' << to_string(payload) << end(); vector data = deep_copy(payload, addresses_copied, tmp); trace(9991, "run") << "deep-copy: writing result " << out << ": " << to_string(data) << end(); // HACK: write_memory interface isn't ideal for this situation; we need // a temporary location to help copy the payload. trace(9991, "run") << "deep-copy: writing temporary " << tmp.value << ": " << out << end(); put(Memory, tmp.value, out); payload.value = tmp.value; // now modified for output vector old_data = read_memory(payload); trace(9991, "run") << "deep-copy: really writing to " << payload.value << ' ' << to_string(payload) << " (old value " << to_string(old_data) << " new value " << to_string(data) << ")" << end(); write_memory(payload, data, -1); trace(9991, "run") << "deep-copy: output is " << to_string(data) << end(); return out; } // deep-copy a non-address and return a vector of locations void deep_copy(const reagent& canonized_in, map& addresses_copied, const reagent& tmp, vector& out) { assert(!is_mu_address(canonized_in)); vector data = read_memory(canonized_in); out.insert(out.end(), data.begin(), data.end()); if (!contains_key(Container_metadata, canonized_in.type)) return; trace(9991, "run") << "deep-copy: scanning for addresses in " << to_string(data) << end(); const container_metadata& metadata = get(Container_metadata, canonized_in.type); for (map, set >::const_iterator p = metadata.address.begin(); p != metadata.address.end(); ++p) { if (!all_match(data, p->first)) continue; for (set::const_iterator info = p->second.begin(); info != p->second.end(); ++info) { // construct a fake reagent that reads directly from the appropriate // field of the container reagent curr; curr.type = new type_tree("address", new type_tree(*info->payload_type)); curr.set_value(canonized_in.value + info->offset); curr.properties.push_back(pair("raw", NULL)); trace(9991, "run") << "deep-copy: copying address " << curr.value << end(); out.at(info->offset) = deep_copy_address(curr, addresses_copied, tmp); } } } int payload_address(reagent/*copy*/ x) { x.properties.push_back(pair("lookup", NULL)); canonize(x); return x.value; } //: moar tests, just because I can't believe it all works :(scenario deep_copy_stress_test_1) container foo1 [ p:address:number ] container foo2 [ p:address:foo1 ] exclusive-container foo3 [ p:address:foo1 q:address:foo2 ] def main [ local-scope x:address:number <- new number:type *x <- copy 34 a:address:foo1 <- new foo1:type *a <- merge x b:address:foo2 <- new foo2:type *b <- merge a c:foo3 <- merge 1/q, b d:foo3 <- deep-copy c e:address:foo2, z:boolean <- maybe-convert d, q:variant f:address:foo1 <- get *e, p:offset g:address:number <- get *f, p:offset 1:number/raw <- copy *g ] +mem: storing 34 in location 1 :(scenario deep_copy_stress_test_2) container foo1 [ p:address:number ] container foo2 [ p:address:foo1 ] exclusive-container foo3 [ p:address:foo1 q:address:foo2 ] container foo4 [ p:number q:address:foo3 ] def main [ local-scope x:address:number <- new number:type *x <- copy 34 a:address:foo1 <- new foo1:type *a <- merge x b:address:foo2 <- new foo2:type *b <- merge a c:address:foo3 <- new foo3:type *c <- merge 1/q, b d:foo4 <- merge 35, c e:foo4 <- deep-copy d f:address:foo3 <- get e, q:offset g:address:foo2, z:boolean <- maybe-convert *f, q:variant h:address:foo1 <- get *g, p:offset y:address:number <- get *h, p:offset 1:number/raw <- copy *y ] +mem: storing 34 in location 1 :(scenario deep_copy_cycles) container foo [ p:number q:address:foo ] def main [ local-scope x:address:foo <- new foo:type *x <- put *x, p:offset, 34 *x <- put *x, q:offset, x # create a cycle y:address:foo <- deep-copy x 1:number/raw <- get *y, p:offset y2:address:foo <- get *y, q:offset stash y [vs] y2 2:boolean/raw <- equal y, y2 # is it still a cycle? 3:boolean/raw <- equal x, y # is it the same cycle? ] +mem: storing 34 in location 1 # deep copy also contains a cycle +mem: storing 1 in location 2 # but it's a completely different (disjoint) cycle +mem: storing 0 in location 3