// 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:num <- copy 34 y:num <- deep-copy x 10:bool/raw <- equal x, y ] # non-address primitives are identical +mem: storing 1 in location 10 :(scenario deep_copy_null_pointer) def main [ local-scope x:&:num <- copy 0 y:&:num <- deep-copy x 10:bool/raw <- equal x, y ] # null addresses are identical +mem: storing 1 in location 10 :(scenario deep_copy_container_without_address) container foo [ x:num y:num ] def main [ local-scope a:foo <- merge 34, 35 b:foo <- deep-copy a 10:bool/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:&:num <- copy 100/unsafe # pretend allocation *1:&:num <- copy 34 2:&:num <- deep-copy 1:&:num 10:bool <- equal 1:&:num, 2:&:num 11:bool <- equal *1:&:num, *2:&:num 2:&:num <- 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:&:point <- copy 100/unsafe # pretend allocation *1:&:point <- merge 34, 35 2:&:point <- deep-copy 1:&:point 10:bool <- equal 1:&:point, 2:&:point 11:bool <- equal *1:&:point, *2:&: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:&:&:num <- copy 100/unsafe # pretend allocation *1:&:&:num <- copy 150/unsafe **1:&:&:num <- copy 34 2:&:&:num <- deep-copy 1:&:&:num 10:bool <- equal 1:&:&:num, 2:&:&:num 11:bool <- equal *1:&:&:num, *2:&:&:num 12:bool <- equal **1:&:&:num, **2:&:&:num ] # 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:num <- copy 1 # pretend refcount 101:num <- copy 3 # pretend array length 1:&:@:num <- copy 100/unsafe # pretend allocation put-index *1:&:@:num, 0, 34 put-index *1:&:@:num, 1, 35 put-index *1:&:@:num, 2, 36 stash [old:], *1:&:@:num 2:&:@:num <- deep-copy 1:&:@:num stash 2:&:@:num stash [new:], *2:&:@:num 10:bool <- equal 1:&:@:num, 2:&:@:num 11:bool <- equal *1:&:@:num, *2:&:@:num ] +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:num y:&:num ] def main [ local-scope y0:&:num <- new number:type *y0 <- copy 35 a:foo <- merge 34, y0 b:foo <- deep-copy a 10:bool/raw <- equal a, b y1:&:num <- get b, y:offset 11:bool/raw <- equal y0, y1 12:num/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:num y:&:num ] def main [ local-scope y0:&:num <- new number:type *y0 <- copy 34 a:foo <- merge 1/y, y0 b:foo <- deep-copy a 10:bool/raw <- equal a, b y1:&:num, z:bool <- maybe-convert b, y:variant 11:bool/raw <- equal y0, y1 12:num/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:num y:bar # inline ] container bar [ x:&:num ] def main [ local-scope y0:&:num <- new number:type *y0 <- copy 34 a:bar <- merge y0 b:foo <- merge 1/y, a c:foo <- deep-copy b 10:bool/raw <- equal b, c d:bar, z:bool <- maybe-convert c, y:variant y1:&:num <- get d, x:offset 11:bool/raw <- equal y0, y1 12:num/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: { products.push_back(deep_copy(current_instruction().ingredients.at(0))); break; } :(code) vector deep_copy(const reagent& in) { // 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.set_value(allocate(1)); map addresses_copied; vector result = deep_copy(in, addresses_copied, tmp); // reclaim Mu memory allocated for tmp trace(9991, "run") << "deep-copy: reclaiming temporary" << end(); abandon(tmp.value, payload_type(tmp.type), payload_size(tmp)); // reclaim host memory allocated for tmp.type when tmp goes out of scope return result; } vector deep_copy(reagent/*copy*/ in, map& addresses_copied, const reagent& tmp) { canonize(in); if (is_mu_address(in)) { // hack: skip primitive containers that do their own locking; they're designed to be shared between routines type_tree* payload = in.type->right; if (payload->left->name == "channel" || payload->left->name == "screen" || payload->left->name == "console" || payload->left->name == "resources") return read_memory(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 (address_value(canonized_in) == 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(); assert(contains_key(Memory, out)); // refcount must already be incremented ++get(Memory, out); 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.set_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); 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) { // hack: skip primitive containers that do their own locking; they're designed to be shared between routines if (!info->payload_type->atom && info->payload_type->left->name == "channel") continue; if (info->payload_type->name == "screen" || info->payload_type->name == "console" || info->payload_type->name == "resources") continue; // construct a fake reagent that reads directly from the appropriate // field of the container reagent curr; if (info->payload_type->atom) curr.type = new type_tree(new type_tree("address"), new type_tree(new type_tree(info->payload_type->name), NULL)); else curr.type = new type_tree(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; } int address_value(const reagent& x) { assert(is_mu_address(x)); vector result = read_memory(x); assert(SIZE(result) == 1); return static_cast(result.at(0)); } :(scenario deep_copy_stress_test_1) container foo1 [ p:&:num ] container foo2 [ p:&:foo1 ] exclusive-container foo3 [ p:&:foo1 q:&:foo2 ] def main [ local-scope x:&:num <- new number:type *x <- copy 34 a:&:foo1 <- new foo1:type *a <- merge x b:&:foo2 <- new foo2:type *b <- merge a c:foo3 <- merge 1/q, b d:foo3 <- deep-copy c e:&:foo2, z:bool <- maybe-convert d, q:variant f:&:foo1 <- get *e, p:offset g:&:num <- get *f, p:offset 1:num/raw <- copy *g ] +mem: storing 34 in location 1 :(scenario deep_copy_stress_test_2) container foo1 [ p:&:num ] container foo2 [ p:&:foo1 ] exclusive-container foo3 [ p:&:foo1 q:&:foo2 ] container foo4 [ p:num q:&:foo3 ] def main [ local-scope x:&:num <- new number:type *x <- copy 34 a:&:foo1 <- new foo1:type *a <- merge x b:&:foo2 <- new foo2:type *b <- merge a c:&:foo3 <- new foo3:type *c <- merge 1/q, b d:foo4 <- merge 35, c e:foo4 <- deep-copy d f:&:foo3 <- get e, q:offset g:&:foo2, z:bool <- maybe-convert *f, q:variant h:&:foo1 <- get *g, p:offset y:&:num <- get *h, p:offset 1:num/raw <- copy *y ] +mem: storing 34 in location 1 :(scenario deep_copy_cycles) container foo [ p:num q:&:foo ] def main [ local-scope x:&:foo <- new foo:type *x <- put *x, p:offset, 34 *x <- put *x, q:offset, x # create a cycle y:&:foo <- deep-copy x 1:num/raw <- get *y, p:offset y2:&:foo <- get *y, q:offset 2:bool/raw <- equal y, y2 # is it still a cycle? 3:bool/raw <- equal x, y # is it the same cycle? # not bothering cleaning up; both cycles leak memory ] +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 //: todo: version of deep_copy_cycles that breaks cycles at end and verifies no memory leaks //: needs approximate matching over scenario traces :(scenario deep_copy_skips_channel) # ugly! dummy 'channel' type if we happen to be building without that layer % if (!contains_key(Type_ordinal, "channel")) get_or_insert(Type, put(Type_ordinal, "channel", Next_type_ordinal++)).name = "channel"; def main [ local-scope x:&:channel:num <- new {(channel num): type} y:&:channel:num <- deep-copy x 10:bool/raw <- equal x, y ] # channels are never deep-copied +mem: storing 1 in location 10 :(scenario deep_copy_skips_nested_channel) # ugly! dummy 'channel' type if we happen to be building without that layer % if (!contains_key(Type_ordinal, "channel")) get_or_insert(Type, put(Type_ordinal, "channel", Next_type_ordinal++)).name = "channel"; container foo [ c:&:channel:num ] def main [ local-scope x:&:foo <- new foo:type y:&:foo <- deep-copy x xc:&:channel:num <- get *x, c:offset yc:&:channel:num <- get *y, c:offset 10:bool/raw <- equal xc, yc ] # channels are never deep-copied +mem: storing 1 in location 10 :(scenario deep_copy_skips_resources) # ugly! dummy 'resources' type if we happen to be building without that layer % if (!contains_key(Type_ordinal, "resources")) get_or_insert(Type, put(Type_ordinal, "resources", Next_type_ordinal++)).name = "resources"; def main [ local-scope x:&:resources <- new resources:type y:&:resources <- deep-copy x 10:bool/raw <- equal x, y ] # resources are never deep-copied +mem: storing 1 in location 10 :(scenario deep_copy_skips_nested_resources) # ugly! dummy 'resources' type if we happen to be building without that layer % if (!contains_key(Type_ordinal, "resources")) get_or_insert(Type, put(Type_ordinal, "resources", Next_type_ordinal++)).name = "resources"; container foo [ c:&:resources ] def main [ local-scope x:&:foo <- new foo:type y:&:foo <- deep-copy x xc:&:resources <- get *x, c:offset yc:&:resources <- get *y, c:offset 10:bool/raw <- equal xc, yc ] # resources are never deep-copied +mem: storing 1 in location 10