//: Update refcounts when copying addresses.
//: The top of layer 34 has more on refcounts.
:(scenario refcounts)
def main [
1:address:number <- copy 1000/unsafe
2:address:number <- copy 1:address:number
1:address:number <- copy 0
2:address:number <- copy 0
]
+run: {1: ("address" "number")} <- copy {1000: "literal", "unsafe": ()}
+mem: incrementing refcount of 1000: 0 -> 1
+run: {2: ("address" "number")} <- copy {1: ("address" "number")}
+mem: incrementing refcount of 1000: 1 -> 2
+run: {1: ("address" "number")} <- copy {0: "literal"}
+mem: decrementing refcount of 1000: 2 -> 1
+run: {2: ("address" "number")} <- copy {0: "literal"}
+mem: decrementing refcount of 1000: 1 -> 0
:(before "End write_memory(x) Special-cases")
if (is_mu_address(x)) {
// compute old address of x, as well as new address we want to write in
assert(scalar(data));
assert(x.value);
update_refcounts(get_or_insert(Memory, x.value), data.at(0), payload_size(x));
}
:(code)
// variant of write_memory for addresses
void update_refcounts(int old_address, int new_address, int size) {
if (old_address == new_address) {
trace(9999, "mem") << "copying address to itself; refcount unchanged" << end();
return;
}
// decrement refcount of old address
assert(old_address >= 0);
if (old_address) {
int old_refcount = get_or_insert(Memory, old_address);
trace(9999, "mem") << "decrementing refcount of " << old_address << ": " << old_refcount << " -> " << (old_refcount-1) << end();
--old_refcount;
put(Memory, old_address, old_refcount);
if (old_refcount < 0) {
tb_shutdown();
DUMP("");
cerr << "Negative refcount: " << old_address << ' ' << old_refcount << '\n';
exit(0);
}
// End Decrement Reference Count(old_address, size)
}
// increment refcount of new address
if (new_address) {
int new_refcount = get_or_insert(Memory, new_address);
assert(new_refcount >= 0); // == 0 only when new_address == old_address
trace(9999, "mem") << "incrementing refcount of " << new_address << ": " << new_refcount << " -> " << (new_refcount+1) << end();
put(Memory, new_address, new_refcount+1);
}
}
int payload_size(reagent/*copy*/ x) {
// lookup_memory without drop_one_lookup
if (x.value)
x.set_value(get_or_insert(Memory, x.value)+/*skip refcount*/1);
drop_from_type(x, "address");
return size_of(x)+/*refcount*/1;
}
:(scenario refcounts_reflexive)
def main [
1:address:number <- new number:type
# idempotent copies leave refcount unchanged
1:address:number <- copy 1:address:number
]
+run: {1: ("address" "number")} <- new {number: "type"}
+mem: incrementing refcount of 1000: 0 -> 1
+run: {1: ("address" "number")} <- copy {1: ("address" "number")}
+mem: copying address to itself; refcount unchanged
:(scenario refcounts_call)
def main [
1:address:number <- new number:type
# passing in addresses to recipes increments refcount
foo 1:address:number
# return does NOT yet decrement refcount; memory must be explicitly managed
1:address:number <- new number:type
]
def foo [
2:address:number <- next-ingredient
]
+run: {1: ("address" "number")} <- new {number: "type"}
+mem: incrementing refcount of 1000: 0 -> 1
+run: {2: ("address" "number")} <- next-ingredient
+mem: incrementing refcount of 1000: 1 -> 2
+run: {1: ("address" "number")} <- new {number: "type"}
+mem: decrementing refcount of 1000: 2 -> 1
//: fix up any instructions that don't follow the usual flow of read_memory
//: before the RUN switch, and write_memory after
:(scenario refcounts_put)
container foo [
x:address:number
]
def main [
1:address:number <- new number:type
2:address:foo <- new foo:type
*2:address:foo <- put *2:address:foo, x:offset, 1:address:number
]
+run: {1: ("address" "number")} <- new {number: "type"}
+mem: incrementing refcount of 1000: 0 -> 1
+run: {2: ("address" "foo")} <- new {foo: "type"}
+mem: incrementing refcount of 1002: 0 -> 1
+run: {2: ("address" "foo"), "lookup": ()} <- put {2: ("address" "foo"), "lookup": ()}, {x: "offset"}, {1: ("address" "number")}
# put increments refcount
+mem: incrementing refcount of 1000: 1 -> 2
:(after "Write Memory in PUT in Run")
reagent/*copy*/ element = element_type(base.type, offset);
assert(!has_property(element, "lookup"));
element.value = address;
if (is_mu_address(element))
update_refcounts(get_or_insert(Memory, element.value), ingredients.at(2).at(0), payload_size(element));
:(scenario refcounts_put_index)
def main [
1:address:number <- new number:type
# fake array because we can't yet create an array of addresses (wait for the
# support for dilated reagents and parsing more complex type trees)
1003:number/raw <- copy 3 # skip refcount at 1002
2:address:array:address:number <- copy 1002/unsafe
*2:address:array:address:number <- put-index *2:address:array:address:number, 0, 1:address:number
]
+run: {1: ("address" "number")} <- new {number: "type"}
+mem: incrementing refcount of 1000: 0 -> 1
+run: {2: ("address" "array" "address" "number")} <- copy {1002: "literal", "unsafe": ()}
+mem: incrementing refcount of 1002: 0 -> 1
+run: {2: ("address" "array" "address" "number"), "lookup": ()} <- put-index {2: ("address" "array" "address" "number"), "lookup": ()}, {0: "literal"}, {1: ("address" "number")}
# put-index increments refcount
+mem: incrementing refcount of 1000: 1 -> 2
:(after "Write Memory in PUT_INDEX in Run")
if (is_mu_address(element))
update_refcounts(get_or_insert(Memory, element.value), value.at(0), payload_size(element));
:(scenario refcounts_maybe_convert)
exclusive-container foo [
x:number
p:address:number
]
def main [
1:address:number <- new number:type
2:foo <- merge 1/p, 1:address:number
4:address:number, 5:boolean <- maybe-convert 2:foo, 1:variant/p
]
+run: {1: ("address" "number")} <- new {number: "type"}
+mem: incrementing refcount of 1000: 0 -> 1
+run: {2: "foo"} <- merge {1: "literal", "p": ()}, {1: ("address" "number")}
+run: {4: ("address" "number")}, {5: "boolean"} <- maybe-convert {2: "foo"}, {1: "variant", "p": ()}
# maybe-convert increments refcount on success
+mem: incrementing refcount of 1000: 1 -> 2
:(after "Write Memory in Successful MAYBE_CONVERT")
if (is_mu_address(product))
update_refcounts(get_or_insert(Memory, product.value), get_or_insert(Memory, base_address+/*skip tag*/1), payload_size(product));
//: manage refcounts in instructions that copy multiple locations at a time
:(scenario refcounts_copy_nested)
container foo [
x:address:number # address inside container
]
def main [
1:address:number <- new number:type
2:address:foo <- new foo:type
*2:address:foo <- put *2:address:foo, x:offset, 1:address:number
3:foo <- copy *2:address:foo
]
+transform: --- compute address offsets for foo
+transform: checking container foo, element 0
+transform: container foo contains an address at offset 0
+run: {1: ("address" "number")} <- new {number: "type"}
+mem: incrementing refcount of 1000: 0 -> 1
+run: {2: ("address" "foo"), "lookup": ()} <- put {2: ("address" "foo"), "lookup": ()}, {x: "offset"}, {1: ("address" "number")}
+mem: incrementing refcount of 1000: 1 -> 2
# copying a container increments refcounts of any contained addresses
+run: {3: "foo"} <- copy {2: ("address" "foo"), "lookup": ()}
+mem: incrementing refcount of 1000: 2 -> 3
:(after "Types")
struct address_element_info {
int offset; // where inside a container type (after flattening nested containers!) the address lies
int payload_size; // size of type it points to
address_element_info(int o, int p) {
offset = o;
payload_size = p;
}
};
:(before "End container_metadata Fields")
vector<address_element_info> address; // list of offsets containing addresses, and the sizes of their corresponding payloads
//: populate metadata.address in a separate transform, because it requires
//: already knowing the sizes of all types
:(after "Transform.push_back(compute_container_sizes)")
Transform.push_back(compute_container_address_offsets);
:(code)
void compute_container_address_offsets(const recipe_ordinal r) {
recipe& caller = get(Recipe, r);
//? cerr << "compute offsets " << caller.name <<'\n';
for (int i = 0; i < SIZE(caller.steps); ++i) {
instruction& inst = caller.steps.at(i);
for (int i = 0; i < SIZE(inst.ingredients); ++i)
compute_container_address_offsets(inst.ingredients.at(i));
for (int i = 0; i < SIZE(inst.products); ++i)
compute_container_address_offsets(inst.products.at(i));
}
}
void compute_container_address_offsets(reagent& r) {
if (is_literal(r) || is_dummy(r)) return;
compute_container_address_offsets(r.type);
if (contains_key(Container_metadata, r.type))
r.metadata = get(Container_metadata, r.type);
}
void compute_container_address_offsets(type_tree* type) {
if (!type) return;
if (type->left) compute_container_address_offsets(type->left);
if (type->right) compute_container_address_offsets(type->right);
if (!contains_key(Type, type->value)) return; // error raised elsewhere
type_info& info = get(Type, type->value);
if (info.kind == CONTAINER) {
//? cerr << " " << to_string(type) << '\n';
container_metadata& metadata = get(Container_metadata, type);
if (!metadata.address.empty()) return;
trace(9992, "transform") << "--- compute address offsets for " << info.name << end();
for (int i = 0; i < SIZE(info.elements); ++i) {
reagent/*copy*/ element = info.elements.at(i);
trace(9993, "transform") << "checking container " << type->name << ", element " << i << end();
// Compute Container Address Offset(element)
if (is_mu_address(element)) {
metadata.address.push_back(address_element_info(metadata.offset.at(i), payload_size(element)));
trace(9993, "transform") << "container " << info.name << " contains an address at offset " << metadata.offset.at(i) << end();
}
}
}
}
:(before "End write_memory(x) Special-cases")
if (is_mu_container(x)) {
// Can't recurse here because we have to worry about shape-shifting
// containers. Always go off of x.metadata rather than the global
// Container_metadata.
assert(x.metadata.size);
for (int i = 0; i < SIZE(x.metadata.address); ++i) {
const address_element_info& info = x.metadata.address.at(i);
update_refcounts(get_or_insert(Memory, x.value + info.offset), data.at(info.offset), info.payload_size);
}
}
:(code)
bool is_mu_container(const reagent& r) {
if (r.type->value == 0) return false;
type_info& info = get(Type, r.type->value);
return info.kind == CONTAINER;
}
bool is_mu_exclusive_container(const reagent& r) {
if (r.type->value == 0) return false;
type_info& info = get(Type, r.type->value);
return info.kind == EXCLUSIVE_CONTAINER;
}
// todo:
// container containing container containing address
// exclusive container sometimes containing address
// container containing exclusive container sometimes containing address
// ensure the original unguarded write_memory loop is never run