diff options
author | Kartik Agaram <vc@akkartik.com> | 2018-06-24 09:16:17 -0700 |
---|---|---|
committer | Kartik Agaram <vc@akkartik.com> | 2018-06-24 09:18:20 -0700 |
commit | 23d3a02226973f80188e84fa5dcedb14413c5b7f (patch) | |
tree | 3c73284cb795e74d78e53b72df470cafca4c70cf | |
parent | 377b00b045289a3fa8e88d4b2f129d797c687e2f (diff) | |
download | mu-23d3a02226973f80188e84fa5dcedb14413c5b7f.tar.gz |
4266 - space for alloc-id in heap allocations
This has taken me almost 6 weeks :(
36 files changed, 926 insertions, 648 deletions
diff --git a/020run.cc b/020run.cc index 3739ad2c..740f881b 100644 --- a/020run.cc +++ b/020run.cc @@ -87,6 +87,10 @@ void run_current_routine() { // Primitive Recipe Implementations case COPY: { copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin())); + for (int i = 0; i < SIZE(current_instruction().products); ++i) { + if (is_mu_scalar(current_instruction().products.at(i)) && is_mu_address(current_instruction().ingredients.at(i))) + products.at(i).erase(products.at(i).begin()); // ignore alloc id + } break; } // End Primitive Recipe Implementations @@ -125,6 +129,33 @@ bool should_copy_ingredients() { return true; } +bool is_mu_scalar(reagent/*copy*/ r) { + return is_mu_scalar(r.type); +} +bool is_mu_scalar(const type_tree* type) { + if (!type) return false; + if (is_mu_address(type)) return false; + if (!type->atom) return false; + if (is_literal(type)) + return type->name != "literal-string"; + return size_of(type) == 1; +} + +bool is_mu_address(reagent/*copy*/ r) { + // End Preprocess is_mu_address(reagent r) + return is_mu_address(r.type); +} +bool is_mu_address(const type_tree* type) { + if (!type) return false; + if (is_literal(type)) return false; + if (type->atom) return false; + if (!type->left->atom) { + raise << "invalid type " << to_string(type) << '\n' << end(); + return false; + } + return type->left->value == Address_type_ordinal; +} + //: Some helpers. //: Important that they return references into the current routine. @@ -299,6 +330,7 @@ bool ends_with(const string& s, const string& pat) { vector<double> read_memory(reagent/*copy*/ x) { // Begin Preprocess read_memory(x) vector<double> result; + if (x.name == "null") result.push_back(/*alloc id*/0); if (is_literal(x)) { result.push_back(x.value); return result; @@ -358,7 +390,7 @@ int size_of(const type_tree* type) { raise << "invalid type " << to_string(type) << '\n' << end(); return 0; } - if (type->left->value == Address_type_ordinal) return 1; + if (type->left->value == Address_type_ordinal) return 2; // address and alloc id // End size_of(type) Non-atom Special-cases } // End size_of(type) Special-cases @@ -421,6 +453,11 @@ def main [ ] +run: _ <- copy {0: "literal"} +:(scenario run_null) +def main [ + 1:&:num <- copy null +] + :(scenario write_to_0_disallowed) % Hide_errors = true; def main [ diff --git a/021check_instruction.cc b/021check_instruction.cc index 994eb209..6347dfb3 100644 --- a/021check_instruction.cc +++ b/021check_instruction.cc @@ -88,14 +88,15 @@ $error: 0 :(code) // types_match with some leniency -bool types_coercible(const reagent& to, const reagent& from) { - if (types_match(to, from)) return true; +bool types_coercible(reagent/*copy*/ to, reagent/*copy*/ from) { + // Begin types_coercible(reagent to, reagent from) + if (types_match_sub(to, from)) return true; if (is_real_mu_number(from) && is_mu_character(to)) return true; // End types_coercible Special-cases return false; } -bool types_match(const reagent& to, const reagent& from) { +bool types_match_sub(const reagent& to, const reagent& from) { // to sidestep type-checking, use /unsafe in the source. // this will be highlighted in red inside vim. just for setting up some tests. if (is_unsafe(from)) return true; @@ -103,15 +104,20 @@ bool types_match(const reagent& to, const reagent& from) { if (is_mu_array(to)) return false; // End Matching Types For Literal(to) if (!to.type) return false; + // allow writing null to any address if (is_mu_address(to)) return from.name == "null"; return size_of(to) == 1; // literals are always scalars } - return types_strictly_match(to, from); + return types_strictly_match_sub(to, from); +} +// variant for others to call +bool types_match(reagent/*copy*/ to, reagent/*copy*/ from) { + // Begin types_match(reagent to, reagent from) + return types_match_sub(to, from); } //: copy arguments for later layers -bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) { - // End Preprocess types_strictly_match(reagent to, reagent from) +bool types_strictly_match_sub(const reagent& to, const reagent& from) { if (to.type == NULL) return false; // error if (is_literal(from) && to.type->value == Number_type_ordinal) return true; // to sidestep type-checking, use /unsafe in the source. @@ -122,6 +128,11 @@ bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) { if (!to.type) return !from.type; return types_strictly_match(to.type, from.type); } +// variant for others to call +bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) { + // Begin types_strictly_match(reagent to, reagent from) + return types_strictly_match_sub(to, from); +} bool types_strictly_match(const type_tree* to, const type_tree* from) { if (from == to) return true; @@ -185,21 +196,6 @@ bool is_mu_array(const type_tree* type) { return type->left->value == Array_type_ordinal; } -bool is_mu_address(reagent/*copy*/ r) { - // End Preprocess is_mu_address(reagent r) - return is_mu_address(r.type); -} -bool is_mu_address(const type_tree* type) { - if (!type) return false; - if (is_literal(type)) return false; - if (type->atom) return false; - if (!type->left->atom) { - raise << "invalid type " << to_string(type) << '\n' << end(); - return false; - } - return type->left->value == Address_type_ordinal; -} - bool is_mu_boolean(reagent/*copy*/ r) { // End Preprocess is_mu_boolean(reagent r) if (!r.type) return false; @@ -234,15 +230,3 @@ bool is_mu_character(const type_tree* type) { if (is_literal(type)) return false; return type->value == Character_type_ordinal; } - -bool is_mu_scalar(reagent/*copy*/ r) { - return is_mu_scalar(r.type); -} -bool is_mu_scalar(const type_tree* type) { - if (!type) return false; - if (is_mu_address(type)) return true; - if (!type->atom) return false; - if (is_literal(type)) - return type->name != "literal-string"; - return size_of(type) == 1; -} diff --git a/022arithmetic.cc b/022arithmetic.cc index 79a314bb..ce8cf1b9 100644 --- a/022arithmetic.cc +++ b/022arithmetic.cc @@ -79,7 +79,6 @@ case SUBTRACT: { break; } for (int i = 0; i < SIZE(inst.ingredients); ++i) { - if (is_raw(inst.ingredients.at(i))) continue; // permit address offset computations in tests if (!is_mu_number(inst.ingredients.at(i))) { raise << maybe(get(Recipe, r).name) << "'subtract' requires number ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end(); goto finish_checking_instruction; @@ -104,10 +103,6 @@ case SUBTRACT: { products.at(0).push_back(result); break; } -:(code) -bool is_raw(const reagent& r) { - return has_property(r, "raw"); -} :(scenario subtract_literal) def main [ diff --git a/023boolean.cc b/023boolean.cc index 114d9d8e..6d13325f 100644 --- a/023boolean.cc +++ b/023boolean.cc @@ -26,11 +26,17 @@ case AND: { case AND: { bool result = true; for (int i = 0; i < SIZE(ingredients); ++i) - result = result && ingredients.at(i).at(0); + result = result && scalar_ingredient(ingredients, i); products.resize(1); products.at(0).push_back(result); break; } +:(code) +double scalar_ingredient(const vector<vector<double> >& ingredients, int i) { + if (is_mu_address(current_instruction().ingredients.at(i))) + return ingredients.at(i).at(/*skip alloc id*/1); + return ingredients.at(i).at(0); +} :(scenario and) def main [ @@ -84,7 +90,7 @@ case OR: { case OR: { bool result = false; for (int i = 0; i < SIZE(ingredients); ++i) - result = result || ingredients.at(i).at(0); + result = result || scalar_ingredient(ingredients, i); products.resize(1); products.at(0).push_back(result); break; @@ -127,8 +133,8 @@ case NOT: { break; } for (int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_scalar(inst.ingredients.at(i))) { - raise << maybe(get(Recipe, r).name) << "'not' requires boolean ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end(); + if (!is_mu_scalar(inst.ingredients.at(i)) && !is_mu_address(inst.ingredients.at(i))) { + raise << maybe(get(Recipe, r).name) << "'not' requires ingredients that can be interpreted as boolean, but got '" << inst.ingredients.at(i).original_string << "'\n" << end(); goto finish_checking_instruction; } } @@ -145,7 +151,7 @@ case NOT: { case NOT: { products.resize(SIZE(ingredients)); for (int i = 0; i < SIZE(ingredients); ++i) { - products.at(i).push_back(!ingredients.at(i).at(0)); + products.at(i).push_back(!scalar_ingredient(ingredients, i)); } break; } diff --git a/024jump.cc b/024jump.cc index 37011290..14c14297 100644 --- a/024jump.cc +++ b/024jump.cc @@ -72,7 +72,7 @@ case JUMP_IF: { raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' should get exactly two ingredients\n" << end(); break; } - if (!is_mu_scalar(inst.ingredients.at(0))) { + if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' requires a boolean for its first ingredient, but '" << inst.ingredients.at(0).name << "' has type '" << names_to_string_without_quotes(inst.ingredients.at(0).type) << "'\n" << end(); break; } @@ -90,7 +90,7 @@ case JUMP_IF: { :(before "End Primitive Recipe Implementations") case JUMP_IF: { assert(current_instruction().ingredients.at(1).initialized); - if (!ingredients.at(0).at(0)) { + if (!scalar_ingredient(ingredients, 0)) { trace(9998, "run") << "jump-if fell through" << end(); break; } @@ -109,7 +109,7 @@ def main [ ] +run: jump-if {999: "literal"}, {1: "offset"} +run: jumping to instruction 2 --run: {1: "number"} <- copy {1: "literal"} +-run: {123: "number"} <- copy {1: "literal"} -mem: storing 1 in location 123 :(scenario jump_if_fallthrough) @@ -122,6 +122,17 @@ def main [ +run: {123: "number"} <- copy {1: "literal"} +mem: storing 1 in location 123 +:(scenario jump_if_on_address) +def main [ + 10:num/alloc-id, 11:num <- copy 0, 999 + jump-if 10:&:number, 1:offset + 123:num <- copy 1 +] ++run: jump-if {10: ("address" "number")}, {1: "offset"} ++run: jumping to instruction 3 +-run: {123: "number"} <- copy {1: "literal"} +-mem: storing 1 in location 123 + :(before "End Primitive Recipe Declarations") JUMP_UNLESS, :(before "End Primitive Recipe Numbers") @@ -132,7 +143,7 @@ case JUMP_UNLESS: { raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' should get exactly two ingredients\n" << end(); break; } - if (!is_mu_scalar(inst.ingredients.at(0))) { + if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' requires a boolean for its first ingredient, but '" << inst.ingredients.at(0).name << "' has type '" << names_to_string_without_quotes(inst.ingredients.at(0).type) << "'\n" << end(); break; } @@ -150,7 +161,7 @@ case JUMP_UNLESS: { :(before "End Primitive Recipe Implementations") case JUMP_UNLESS: { assert(current_instruction().ingredients.at(1).initialized); - if (ingredients.at(0).at(0)) { + if (scalar_ingredient(ingredients, 0)) { trace(9998, "run") << "jump-unless fell through" << end(); break; } diff --git a/025compare.cc b/025compare.cc index 92878208..e12fa1dc 100644 --- a/025compare.cc +++ b/025compare.cc @@ -32,6 +32,10 @@ case EQUAL: { vector<double>& exemplar = ingredients.at(0); bool result = true; for (int i = /*skip exemplar*/1; i < SIZE(ingredients); ++i) { + if (SIZE(ingredients.at(i)) != SIZE(exemplar)) { + result = false; + break; + } if (!equal(ingredients.at(i).begin(), ingredients.at(i).end(), exemplar.begin())) { result = false; break; @@ -103,6 +107,10 @@ case NOT_EQUAL: { case NOT_EQUAL: { vector<double>& exemplar = ingredients.at(0); products.resize(1); + if (SIZE(ingredients.at(1)) != SIZE(exemplar)) { + products.at(0).push_back(true); + break; + } bool equal_ingredients = equal(ingredients.at(1).begin(), ingredients.at(1).end(), exemplar.begin()); products.at(0).push_back(!equal_ingredients); break; diff --git a/027call_ingredient.cc b/027call_ingredient.cc index 46fafe7e..df7bba3e 100644 --- a/027call_ingredient.cc +++ b/027call_ingredient.cc @@ -124,6 +124,8 @@ case REWIND_INGREDIENTS: { break; } +//: another primitive: 'ingredient' for random access + :(scenario ingredient) def main [ f 1, 2 diff --git a/032array.cc b/032array.cc index 34acc462..25d91b60 100644 --- a/032array.cc +++ b/032array.cc @@ -11,7 +11,7 @@ def main [ # create an array occupying locations 1 (for the size) and 2-4 (for the elements) 1:array:num:3 <- create-array ] -+run: creating array of size 4 ++run: creating array from 4 locations :(before "End Primitive Recipe Declarations") CREATE_ARRAY, @@ -60,7 +60,7 @@ case CREATE_ARRAY: { trace("mem") << "storing " << array_length << " in location " << base_address << end(); put(Memory, base_address, array_length); // in array elements int size = size_of(product); // in locations - trace(9998, "run") << "creating array of size " << size << end(); + trace(9998, "run") << "creating array from " << size << " locations" << end(); // initialize array for (int i = 1; i <= size_of(product); ++i) put(Memory, base_address+i, 0); @@ -208,19 +208,23 @@ def main [ 2:num <- copy 14 3:num <- copy 15 4:num <- copy 16 - 5:num <- index 1:array:num:3, 0/index # the index must be a non-negative whole number + 10:num <- index 1:array:num:3, 0/index # the index must be a non-negative whole number ] -+mem: storing 14 in location 5 ++mem: storing 14 in location 10 :(scenario index_compound_element) def main [ {1: (array (address number) 3)} <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:&:num <- index {1: (array (address number) 3)}, 0 + # skip alloc id + 3:num <- copy 14 + # skip alloc id + 5:num <- copy 15 + # skip alloc id + 7:num <- copy 16 + 10:address:num <- index {1: (array (address number) 3)}, 0 ] -+mem: storing 14 in location 5 +# skip alloc id ++mem: storing 14 in location 11 :(scenario index_direct_offset) def main [ @@ -228,10 +232,10 @@ def main [ 2:num <- copy 14 3:num <- copy 15 4:num <- copy 16 - 5:num <- copy 0 - 6:num <- index 1:array:num, 5:num + 10:num <- copy 0 + 20:num <- index 1:array:num, 10:num ] -+mem: storing 14 in location 6 ++mem: storing 14 in location 20 :(before "End Primitive Recipe Declarations") INDEX, @@ -330,7 +334,7 @@ void test_array_length_compound() { put(Memory, 2, 14); put(Memory, 3, 15); put(Memory, 4, 16); - reagent x("1:array:&:num"); // 3 types, but not a static array + reagent x("1:array:address:num"); // 3 types, but not a static array populate_value(x); CHECK_EQ(array_length(x), 3); } @@ -346,61 +350,40 @@ def main [ 2:num <- copy 14 3:num <- copy 15 4:num <- copy 16 - 5:num <- index 1:array:num:3, 1.5 # non-whole number + 10:num <- index 1:array:num:3, 1.5 # non-whole number ] # fraction is truncated away -+mem: storing 15 in location 5 ++mem: storing 15 in location 10 :(scenario index_out_of_bounds) % Hide_errors = true; def main [ - 1:array:num:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:num <- copy 14 - 6:num <- copy 15 - 7:num <- copy 16 - index 1:array:num:3, 4 # less than size of array in locations, but larger than its length in elements + 1:array:point:3 <- create-array + index 1:array:point:3, 4 # less than size of array in locations, but larger than its length in elements ] -+error: main: invalid index 4 in 'index 1:array:num:3, 4' ++error: main: invalid index 4 in 'index 1:array:point:3, 4' :(scenario index_out_of_bounds_2) % Hide_errors = true; def main [ - 1:array:point:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:num <- copy 14 - 6:num <- copy 15 - 7:num <- copy 16 - index 1:array:point, -1 + 1:array:num:3 <- create-array + index 1:array:num, -1 ] -+error: main: invalid index -1 in 'index 1:array:point, -1' ++error: main: invalid index -1 in 'index 1:array:num, -1' :(scenario index_product_type_mismatch) % Hide_errors = true; def main [ 1:array:point:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:num <- copy 14 - 6:num <- copy 15 - 7:num <- copy 16 - 9:num <- index 1:array:point, 0 + 10:num <- index 1:array:point, 0 ] -+error: main: 'index' on '1:array:point' can't be saved in '9:num'; type should be 'point' ++error: main: 'index' on '1:array:point' can't be saved in '10:num'; type should be 'point' //: we might want to call 'index' without saving the results, say in a sandbox :(scenario index_without_product) def main [ 1:array:num:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 index 1:array:num:3, 0 ] # just don't die @@ -410,9 +393,6 @@ def main [ :(scenario put_index) def main [ 1:array:num:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 1:array:num <- put-index 1:array:num, 1, 34 ] +mem: storing 34 in location 3 @@ -488,12 +468,6 @@ case PUT_INDEX: { % Hide_errors = true; def main [ 1:array:point:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:num <- copy 14 - 6:num <- copy 15 - 7:num <- copy 16 8:point <- merge 34, 35 1:array:point <- put-index 1:array:point, 4, 8:point # '4' is less than size of array in locations, but larger than its length in elements ] @@ -503,22 +477,14 @@ def main [ % Hide_errors = true; def main [ 1:array:point:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:num <- copy 14 - 6:num <- copy 15 - 7:num <- copy 16 - 8:point <- merge 34, 35 - 1:array:point <- put-index 1:array:point, -1, 8:point + 10:point <- merge 34, 35 + 1:array:point <- put-index 1:array:point, -1, 10:point ] -+error: main: invalid index -1 in '1:array:point <- put-index 1:array:point, -1, 8:point' ++error: main: invalid index -1 in '1:array:point <- put-index 1:array:point, -1, 10:point' :(scenario put_index_product_error) % Hide_errors = true; def main [ - local-scope - load-ingredients 1:array:num:3 <- create-array 4:array:num:3 <- put-index 1:array:num:3, 0, 34 ] @@ -529,12 +495,9 @@ def main [ :(scenario array_length) def main [ 1:array:num:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:num <- length 1:array:num:3 + 10:num <- length 1:array:num ] -+mem: storing 3 in location 5 ++mem: storing 3 in location 10 :(before "End Primitive Recipe Declarations") LENGTH, diff --git a/033exclusive_container.cc b/033exclusive_container.cc index c81aa1b9..c2d69f60 100644 --- a/033exclusive_container.cc +++ b/033exclusive_container.cc @@ -457,39 +457,3 @@ def main [ +mem: storing 1 in location 6 +mem: storing 34 in location 7 +mem: storing 35 in location 8 - -//: a little helper: convert address to number - -:(before "End Primitive Recipe Declarations") -DEADDRESS, -:(before "End Primitive Recipe Numbers") -put(Recipe_ordinal, "deaddress", DEADDRESS); -:(before "End Primitive Recipe Checks") -case DEADDRESS: { - // primary goal of these checks is to forbid address arithmetic - for (int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_address(inst.ingredients.at(i))) { - raise << maybe(get(Recipe, r).name) << "'deaddress' requires address ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end(); - goto finish_checking_instruction; - } - } - if (SIZE(inst.products) > SIZE(inst.ingredients)) { - raise << maybe(get(Recipe, r).name) << "too many products in '" << to_original_string(inst) << "'\n" << end(); - break; - } - for (int i = 0; i < SIZE(inst.products); ++i) { - if (!is_real_mu_number(inst.products.at(i))) { - raise << maybe(get(Recipe, r).name) << "'deaddress' requires number products, but got '" << inst.products.at(i).original_string << "'\n" << end(); - goto finish_checking_instruction; - } - } - break; -} -:(before "End Primitive Recipe Implementations") -case DEADDRESS: { - products.resize(SIZE(ingredients)); - for (int i = 0; i < SIZE(ingredients); ++i) { - products.at(i).push_back(ingredients.at(i).at(0)); - } - break; -} diff --git a/034address.cc b/034address.cc index b97dfb17..6c4bdda4 100644 --- a/034address.cc +++ b/034address.cc @@ -18,6 +18,37 @@ //: write to the payload of an ingredient rather than its value, simply add //: the /lookup property to it. Modern computers provide efficient support for //: addresses and lookups, making this a realistic feature. +//: +//: To create addresses and allocate memory exclusively for their use, use +//: 'new'. Memory is a finite resource so if the computer can't satisfy your +//: request, 'new' may return a 0 (null) address. +//: +//: Computers these days have lots of memory so in practice we can often +//: assume we'll never run out. If you start running out however, say in a +//: long-running program, you'll need to switch mental gears and start +//: husbanding our memory more carefully. The most important tool to avoid +//: wasting memory is to 'abandon' an address when you don't need it anymore. +//: That frees up the memory allocated to it to be reused in future calls to +//: 'new'. + +//: Since memory can be reused multiple times, it can happen that you have a +//: stale copy to an address that has since been abandoned and reused. Using +//: the stale address is almost never safe, but it can be very hard to track +//: down such copies because any errors caused by them may occur even millions +//: of instructions after the copy or abandon instruction. To help track down +//: such issues, Mu tracks an 'alloc id' for each allocation it makes. The +//: first call to 'new' has an alloc id of 1, the second gets 2, and so on. +//: The alloc id is never reused. +:(before "End Globals") +long long Next_alloc_id = 0; +:(before "End Reset") +Next_alloc_id = 0; + +//: The 'new' instruction records alloc ids both in the memory being allocated +//: and *also* in the address. The 'abandon' instruction clears alloc ids in +//: both places as well. Tracking alloc ids in this manner allows us to raise +//: errors about stale addresses much earlier: 'lookup' operations always +//: compare alloc ids between the address and its payload. //: todo: give 'new' a custodian ingredient. Following malloc/free is a temporary hack. @@ -25,29 +56,33 @@ # call 'new' two times with identical types without modifying the results; you # should get back different results def main [ - 1:&:num/raw <- new num:type - 2:&:num/raw <- new num:type - 3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw + 10:&:num <- new num:type + 12:&:num <- new num:type + 20:bool <- equal 10:&:num, 12:&:num ] -+mem: storing 0 in location 3 ++mem: storing 1000 in location 11 ++mem: storing 0 in location 20 :(scenario new_array) # call 'new' with a second ingredient to allocate an array of some type rather than a single copy def main [ - 1:&:@:num/raw <- new num:type, 5 - 2:&:num/raw <- new num:type - 3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw + 10:&:@:num <- new num:type, 5 + 12:&:num <- new num:type + 20:num/alloc2, 21:num/alloc1 <- deaddress 10:&:@:num, 12:&:num + 30:num <- subtract 21:num/alloc2, 20:num/alloc1 ] -+run: {1: ("address" "array" "number"), "raw": ()} <- new {num: "type"}, {5: "literal"} ++run: {10: ("address" "array" "number")} <- new {num: "type"}, {5: "literal"} +mem: array length is 5 -# don't forget the extra location for array length -+mem: storing 6 in location 3 +# skip alloc id in allocation ++mem: storing 1000 in location 11 +# don't forget the extra locations for alloc id and array length ++mem: storing 7 in location 30 :(scenario dilated_reagent_with_new) def main [ - 1:&:&:num <- new {(& num): type} + 10:&:&:num <- new {(& num): type} ] -+new: size of '(& num)' is 1 ++new: size of '(& num)' is 2 //: 'new' takes a weird 'type' as its first ingredient; don't error on it :(before "End Mu Types Initialization") @@ -135,22 +170,29 @@ def main [ :(scenario new_discerns_singleton_list_from_atom_container) % Hide_errors = true; def main [ - 1:&:num/raw <- new {(num): type} # should be '{num: type}' + 1:&:num <- new {(num): type} # should be '{num: type}' ] -+error: main: product of 'new' has incorrect type: '1:&:num/raw <- new {(num): type}' ++error: main: product of 'new' has incorrect type: '1:&:num <- new {(num): type}' :(scenario new_with_type_abbreviation) def main [ - 1:&:num/raw <- new num:type + 1:&:num <- new num:type ] $error: 0 :(scenario new_with_type_abbreviation_inside_compound) def main [ - {1: (& & num), raw: ()} <- new {(& num): type} + {1: (address address number), raw: ()} <- new {(& num): type} ] $error: 0 +:(scenario equal_result_of_new_with_null) +def main [ + 1:&:num <- new num:type + 10:bool <- equal 1:&:num, null +] ++mem: storing 0 in location 10 + //: To implement 'new', a Mu transform turns all 'new' instructions into //: 'allocate' instructions that precompute the amount of memory they want to //: allocate. @@ -221,15 +263,18 @@ case ALLOCATE: { int result = allocate(size); if (SIZE(current_instruction().ingredients) > 1) { // initialize array length - trace("mem") << "storing " << ingredients.at(1).at(0) << " in location " << result << end(); - put(Memory, result, ingredients.at(1).at(0)); + trace("mem") << "storing array length " << ingredients.at(1).at(0) << " in location " << result+/*skip alloc id*/1 << end(); + put(Memory, result+/*skip alloc id*/1, ingredients.at(1).at(0)); } products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(result); break; } :(code) int allocate(int size) { + // include space for alloc id + ++size; trace("mem") << "allocating size " << size << end(); //? Total_alloc += size; //? ++Num_alloc; @@ -289,42 +334,45 @@ def main [ :(scenario new_size) def main [ - 11:&:num/raw <- new num:type - 12:&:num/raw <- new num:type - 13:num/raw <- subtract 12:&:num/raw, 11:&:num/raw + 10:&:num <- new num:type + 12:&:num <- new num:type + 20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:num, 12:&:num + 30:num <- subtract 21:num/alloc2, 20:num/alloc1 ] -# size of number -+mem: storing 1 in location 13 +# size of number + alloc id ++mem: storing 2 in location 30 :(scenario new_array_size) def main [ - 1:&:@:num/raw <- new num:type, 5 - 2:&:num/raw <- new num:type - 3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw + 10:&:@:num <- new num:type, 5 + 12:&:num <- new num:type + 20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:num, 12:&:num + 30:num <- subtract 21:num/alloc2, 20:num/alloc1 ] -# 5 locations for array contents + array length -+mem: storing 6 in location 3 +# 5 locations for array contents + array length + alloc id ++mem: storing 7 in location 30 :(scenario new_empty_array) def main [ - 1:&:@:num/raw <- new num:type, 0 - 2:&:num/raw <- new num:type - 3:num/raw <- subtract 2:&:num/raw, 1:&:@:num/raw + 10:&:@:num <- new num:type, 0 + 12:&:num <- new num:type + 20:num/alloc1, 21:num/alloc2 <- deaddress 10:&:@:num, 12:&:num + 30:num <- subtract 21:num/alloc2, 20:num/alloc1 ] -+run: {1: ("address" "array" "number"), "raw": ()} <- new {num: "type"}, {0: "literal"} ++run: {10: ("address" "array" "number")} <- new {num: "type"}, {0: "literal"} +mem: array length is 0 # one location for array length -+mem: storing 1 in location 3 ++mem: storing 2 in location 30 //: If a routine runs out of its initial allocation, it should allocate more. :(scenario new_overflow) -% Initial_memory_per_routine = 2; // barely enough room for point allocation below +% Initial_memory_per_routine = 3; // barely enough room for point allocation below def main [ - 1:&:num/raw <- new num:type - 2:&:point/raw <- new point:type # not enough room in initial page + 10:&:num <- new num:type + 12:&:point <- new point:type # not enough room in initial page ] -+new: routine allocated memory from 1000 to 1002 -+new: routine allocated memory from 1002 to 1004 ++new: routine allocated memory from 1000 to 1003 ++new: routine allocated memory from 1003 to 1006 :(scenario new_without_ingredient) % Hide_errors = true; @@ -332,3 +380,39 @@ def main [ 1:&:num <- new # missing ingredient ] +error: main: 'new' requires one or two ingredients, but got '1:&:num <- new' + +//: a little helper: convert address to number + +:(before "End Primitive Recipe Declarations") +DEADDRESS, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "deaddress", DEADDRESS); +:(before "End Primitive Recipe Checks") +case DEADDRESS: { + // primary goal of these checks is to forbid address arithmetic + for (int i = 0; i < SIZE(inst.ingredients); ++i) { + if (!is_mu_address(inst.ingredients.at(i))) { + raise << maybe(get(Recipe, r).name) << "'deaddress' requires address ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end(); + goto finish_checking_instruction; + } + } + if (SIZE(inst.products) > SIZE(inst.ingredients)) { + raise << maybe(get(Recipe, r).name) << "too many products in '" << to_original_string(inst) << "'\n" << end(); + break; + } + for (int i = 0; i < SIZE(inst.products); ++i) { + if (!is_real_mu_number(inst.products.at(i))) { + raise << maybe(get(Recipe, r).name) << "'deaddress' requires number products, but got '" << inst.products.at(i).original_string << "'\n" << end(); + goto finish_checking_instruction; + } + } + break; +} +:(before "End Primitive Recipe Implementations") +case DEADDRESS: { + products.resize(SIZE(ingredients)); + for (int i = 0; i < SIZE(ingredients); ++i) { + products.at(i).push_back(ingredients.at(i).at(/*skip alloc id*/1)); + } + break; +} diff --git a/035lookup.cc b/035lookup.cc index 2fa31d3a..ebed6b4d 100644 --- a/035lookup.cc +++ b/035lookup.cc @@ -5,12 +5,15 @@ :(scenario copy_indirect) def main [ - 1:&:num <- copy 10/unsafe - 10:num <- copy 34 - # This loads location 1 as an address and looks up *that* location. - 2:num <- copy 1:&:num/lookup + # skip alloc id for 10:&:num + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 94 + # Treat locations 10 and 11 as an address to look up, pointing at the + # payload in locations 20 and 21. + 30:num <- copy 10:&:num/lookup ] -+mem: storing 34 in location 2 ++mem: storing 94 in location 30 :(before "End Preprocess read_memory(x)") canonize(x); @@ -19,10 +22,11 @@ canonize(x); //: 'lookup' property :(scenario store_indirect) def main [ - 1:&:num <- copy 10/unsafe - 1:&:num/lookup <- copy 34 + # skip alloc id for 10:&:num + 11:num <- copy 10 + 10:&:num/lookup <- copy 94 ] -+mem: storing 34 in location 10 ++mem: storing 94 in location 11 :(before "End Preprocess write_memory(x, data)") canonize(x); @@ -31,20 +35,20 @@ canonize(x); :(scenario store_to_0_fails) % Hide_errors = true; def main [ - 1:&:num <- copy null - 1:&:num/lookup <- copy 34 + 10:&:num <- copy null + 10:&:num/lookup <- copy 94 ] --mem: storing 34 in location 0 -+error: can't write to location 0 in '1:&:num/lookup <- copy 34' +-mem: storing 94 in location 0 ++error: main: tried to lookup 0 in '10:&:num/lookup <- copy 94' //: attempts to /lookup address 0 always loudly fail :(scenario lookup_0_fails) % Hide_errors = true; def main [ - 1:&:num <- copy null - 2:num <- copy 1:&:num/lookup + 10:&:num <- copy null + 20:num <- copy 10:&:num/lookup ] -+error: main: tried to lookup 0 in '2:num <- copy 1:&:num/lookup' ++error: main: tried to lookup 0 in '20:num <- copy 10:&:num/lookup' :(scenario lookup_0_dumps_callstack) % Hide_errors = true; @@ -52,10 +56,10 @@ def main [ foo null ] def foo [ - 1:&:num <- next-input - 2:num <- copy 1:&:num/lookup + 10:&:num <- next-input + 20:num <- copy 10:&:num/lookup ] -+error: foo: tried to lookup 0 in '2:num <- copy 1:&:num/lookup' ++error: foo: tried to lookup 0 in '20:num <- copy 10:&:num/lookup' +error: called from main: foo null :(code) @@ -82,7 +86,7 @@ void lookup_memory(reagent& x) { } void lookup_memory_core(reagent& x, bool check_for_null) { - double address = x.value; + double address = x.value + /*skip alloc id in address*/1; double new_value = get_or_insert(Memory, address); trace("mem") << "location " << address << " contains " << no_scientific(new_value) << end(); if (check_for_null && new_value == 0) { @@ -94,12 +98,18 @@ void lookup_memory_core(reagent& x, bool check_for_null) { raise << "tried to lookup 0\n" << end(); } } - x.set_value(new_value); + x.set_value(new_value+/*skip alloc id in payload*/1); drop_from_type(x, "address"); drop_one_lookup(x); } -:(before "End Preprocess types_strictly_match(reagent to, reagent from)") +:(after "Begin types_coercible(reagent to, reagent from)") +if (!canonize_type(to)) return false; +if (!canonize_type(from)) return false; +:(after "Begin types_match(reagent to, reagent from)") +if (!canonize_type(to)) return false; +if (!canonize_type(from)) return false; +:(after "Begin types_strictly_match(reagent to, reagent from)") if (!canonize_type(to)) return false; if (!canonize_type(from)) return false; @@ -156,31 +166,38 @@ void drop_one_lookup(reagent& r) { :(scenario get_indirect) def main [ - 1:&:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 - 2:num <- get 1:&:point/lookup, 0:offset + # skip alloc id for 10:&:point + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 94 + 22:num <- copy 95 + 30:num <- get 10:&:point/lookup, 0:offset ] -+mem: storing 34 in location 2 ++mem: storing 94 in location 30 :(scenario get_indirect2) def main [ - 1:&:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 - 2:&:num <- copy 20/unsafe - 2:&:num/lookup <- get 1:&:point/lookup, 0:offset + # skip alloc id for 10:&:point + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 94 + 22:num <- copy 95 + # skip alloc id for destination + 31:num <- copy 40 + 30:&:num/lookup <- get 10:&:point/lookup, 0:offset ] -+mem: storing 34 in location 20 ++mem: storing 94 in location 41 :(scenario include_nonlookup_properties) def main [ - 1:&:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 - 2:num <- get 1:&:point/lookup/foo, 0:offset + # skip alloc id for 10:&:point + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 94 + 22:num <- copy 95 + 30:num <- get 10:&:point/lookup/foo, 0:offset ] -+mem: storing 34 in location 2 ++mem: storing 94 in location 30 :(after "Update GET base in Check") if (!canonize_type(base)) break; @@ -191,12 +208,14 @@ canonize(base); :(scenario put_indirect) def main [ - 1:&:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 - 1:&:point/lookup <- put 1:&:point/lookup, 0:offset, 36 + # skip alloc id for 10:&:point + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 94 + 22:num <- copy 95 + 10:&:point/lookup <- put 10:&:point/lookup, 0:offset, 96 ] -+mem: storing 36 in location 10 ++mem: storing 96 in location 21 :(after "Update PUT base in Check") if (!canonize_type(base)) break; @@ -208,12 +227,14 @@ canonize(base); :(scenario put_product_error_with_lookup) % Hide_errors = true; def main [ - 1:&:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 - 1:&:point <- put 1:&:point/lookup, x:offset, 36 + # skip alloc id for 10:&:point + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 94 + 22:num <- copy 95 + 10:&:point <- put 10:&:point/lookup, x:offset, 96 ] -+error: main: product of 'put' must be first ingredient '1:&:point/lookup', but got '1:&:point' ++error: main: product of 'put' must be first ingredient '10:&:point/lookup', but got '10:&:point' :(before "End PUT Product Checks") reagent/*copy*/ p = inst.products.at(0); @@ -237,24 +258,27 @@ canonize_type(product); :(scenario copy_array_indirect) def main [ - 10:@:num:3 <- create-array - 11:num <- copy 14 - 12:num <- copy 15 - 13:num <- copy 16 - 1:&:@:num <- copy 10/unsafe - 2:@:num <- copy 1:&:@:num/lookup + # skip alloc id for 10:&:@:num + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 3 # array length + 22:num <- copy 94 + 23:num <- copy 95 + 24:num <- copy 96 + 30:@:num <- copy 10:&:@:num/lookup ] -+mem: storing 3 in location 2 -+mem: storing 14 in location 3 -+mem: storing 15 in location 4 -+mem: storing 16 in location 5 ++mem: storing 3 in location 30 ++mem: storing 94 in location 31 ++mem: storing 95 in location 32 ++mem: storing 96 in location 33 :(scenario create_array_indirect) def main [ - 1:&:@:num:3 <- copy 1000/unsafe # pretend allocation - 1:&:@:num:3/lookup <- create-array + # skip alloc id for 10:&:@:num:3 + 11:num <- copy 3000 + 10:&:array:num:3/lookup <- create-array ] -+mem: storing 3 in location 1000 ++mem: storing 3 in location 3001 :(after "Update CREATE_ARRAY product in Check") if (!canonize_type(product)) break; @@ -263,14 +287,16 @@ canonize(product); :(scenario index_indirect) def main [ - 10:@:num:3 <- create-array - 11:num <- copy 14 - 12:num <- copy 15 - 13:num <- copy 16 - 1:&:@:num <- copy 10/unsafe - 2:num <- index 1:&:@:num/lookup, 1 + # skip alloc id for 10:&:@:num + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 3 # array length + 22:num <- copy 94 + 23:num <- copy 95 + 24:num <- copy 96 + 30:num <- index 10:&:@:num/lookup, 1 ] -+mem: storing 15 in location 2 ++mem: storing 95 in location 30 :(before "Update INDEX base in Check") if (!canonize_type(base)) break; @@ -286,38 +312,44 @@ canonize(index); :(scenario put_index_indirect) def main [ - 10:@:num:3 <- create-array - 11:num <- copy 14 - 12:num <- copy 15 - 13:num <- copy 16 - 1:&:@:num <- copy 10/unsafe - 1:&:@:num/lookup <- put-index 1:&:@:num/lookup, 1, 34 + # skip alloc id for 10:&:@:num + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 3 # array length + 22:num <- copy 94 + 23:num <- copy 95 + 24:num <- copy 96 + 10:&:@:num/lookup <- put-index 10:&:@:num/lookup, 1, 97 ] -+mem: storing 34 in location 12 ++mem: storing 97 in location 23 :(scenario put_index_indirect_2) def main [ - 1:@:num:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:&:num <- copy 10/unsafe - 10:num <- copy 1 - 1:@:num:3 <- put-index 1:@:num:3, 5:&:num/lookup, 34 + 10:num <- copy 3 # array length + 11:num <- copy 94 + 12:num <- copy 95 + 13:num <- copy 96 + # skip alloc id for address + 21:num <- copy 30 + # skip alloc id for payload + 31:num <- copy 1 # index + 10:@:num <- put-index 10:@:num, 20:&:num/lookup, 97 ] -+mem: storing 34 in location 3 ++mem: storing 97 in location 12 :(scenario put_index_product_error_with_lookup) % Hide_errors = true; def main [ - 10:@:num:3 <- create-array - 11:num <- copy 14 - 12:num <- copy 15 - 13:num <- copy 16 - 1:&:@:num <- copy 10/unsafe - 1:&:@:num <- put-index 1:&:@:num/lookup, 1, 34 + # skip alloc id for 10:&:@:num + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 3 # array length + 22:num <- copy 94 + 23:num <- copy 95 + 24:num <- copy 96 + 10:&:@:num <- put-index 10:&:@:num/lookup, 1, 34 ] -+error: main: product of 'put-index' must be first ingredient '1:&:@:num/lookup', but got '1:&:@:num' ++error: main: product of 'put-index' must be first ingredient '10:&:@:num/lookup', but got '10:&:@:num' :(before "End PUT_INDEX Product Checks") reagent/*copy*/ p = inst.products.at(0); @@ -331,14 +363,14 @@ if (!types_strictly_match(p, i)) { :(scenario dilated_reagent_in_static_array) def main [ - {1: (@ (& num) 3)} <- create-array - 5:&:num <- new num:type - {1: (@ (& num) 3)} <- put-index {1: (@ (& num) 3)}, 0, 5:&:num - *5:&:num <- copy 34 - 6:num <- copy *5:&:num + {1: (array (& num) 3)} <- create-array + 10:&:num <- new num:type + {1: (array (& num) 3)} <- put-index {1: (array (& num) 3)}, 0, 10:&:num + *10:&:num <- copy 94 + 20:num <- copy *10:&:num ] -+run: creating array of size 4 -+mem: storing 34 in location 6 ++run: creating array from 7 locations ++mem: storing 94 in location 20 :(before "Update PUT_INDEX base in Check") if (!canonize_type(base)) break; @@ -354,14 +386,16 @@ canonize(index); :(scenario length_indirect) def main [ - 10:@:num:3 <- create-array - 11:num <- copy 14 - 12:num <- copy 15 - 13:num <- copy 16 - 1:&:@:num <- copy 10/unsafe - 2:num <- length 1:&:@:num/lookup + # skip alloc id for 10:&:@:num + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 3 # array length + 22:num <- copy 94 + 23:num <- copy 95 + 24:num <- copy 96 + 30:num <- length 10:&:array:num/lookup ] -+mem: storing 3 in location 2 ++mem: storing 3 in location 30 :(before "Update LENGTH array in Check") if (!canonize_type(array)) break; @@ -370,32 +404,40 @@ canonize(array); :(scenario maybe_convert_indirect) def main [ - 10:number-or-point <- merge 0/number, 34 - 1:&:number-or-point <- copy 10/unsafe - 2:num, 3:bool <- maybe-convert 1:&:number-or-point/lookup, i:variant + # skip alloc id for 10:&:number-or-point + 11:num <- copy 20 + # skip alloc id for payload + 21:number-or-point <- merge 0/number, 94 + 30:num, 31:bool <- maybe-convert 10:&:number-or-point/lookup, i:variant ] -+mem: storing 1 in location 3 -+mem: storing 34 in location 2 ++mem: storing 1 in location 31 ++mem: storing 94 in location 30 :(scenario maybe_convert_indirect_2) def main [ - 10:number-or-point <- merge 0/number, 34 - 1:&:number-or-point <- copy 10/unsafe - 2:&:num <- copy 20/unsafe - 2:&:num/lookup, 3:bool <- maybe-convert 1:&:number-or-point/lookup, i:variant + # skip alloc id for 10:&:number-or-point + 11:num <- copy 20 + # skip alloc id for payload + 21:number-or-point <- merge 0/number, 94 + # skip alloc id for 30:&:num + 31:num <- copy 40 + 30:&:num/lookup, 50:bool <- maybe-convert 10:&:number-or-point/lookup, i:variant ] -+mem: storing 1 in location 3 -+mem: storing 34 in location 20 ++mem: storing 1 in location 50 ++mem: storing 94 in location 41 :(scenario maybe_convert_indirect_3) def main [ - 10:number-or-point <- merge 0/number, 34 - 1:&:number-or-point <- copy 10/unsafe - 2:&:bool <- copy 20/unsafe - 3:num, 2:&:bool/lookup <- maybe-convert 1:&:number-or-point/lookup, i:variant + # skip alloc id for 10:&:number-or-point + 11:num <- copy 20 + # skip alloc id for payload + 21:number-or-point <- merge 0/number, 94 + # skip alloc id for 30:&:bool + 31:num <- copy 40 + 50:num, 30:&:bool/lookup <- maybe-convert 10:&:number-or-point/lookup, i:variant ] -+mem: storing 1 in location 20 -+mem: storing 34 in location 3 ++mem: storing 1 in location 41 ++mem: storing 94 in location 50 :(before "Update MAYBE_CONVERT base in Check") if (!canonize_type(base)) break; @@ -413,11 +455,13 @@ canonize(status); :(scenario merge_exclusive_container_indirect) def main [ - 1:&:number-or-point <- copy 10/unsafe - 1:&:number-or-point/lookup <- merge 0/number, 34 + # skip alloc id for 10:&:number-or-point + 11:num <- copy 20 + 10:&:number-or-point/lookup <- merge 0/number, 34 ] -+mem: storing 0 in location 10 -+mem: storing 34 in location 11 +# skip alloc id ++mem: storing 0 in location 21 ++mem: storing 34 in location 22 :(before "Update size_mismatch Check for MERGE(x) canonize(x); @@ -426,12 +470,14 @@ canonize(x); :(scenario lookup_abbreviation) def main [ - 1:&:num <- copy 10/unsafe - 10:num <- copy 34 - 3:num <- copy *1:&:num + # skip alloc id for 10:&:num + 11:num <- copy 20 + # skip alloc id for payload + 21:num <- copy 94 + 30:num <- copy *10:&:num ] -+parse: ingredient: {1: ("&" "num"), "lookup": ()} -+mem: storing 34 in location 3 ++parse: ingredient: {10: ("&" "num"), "lookup": ()} ++mem: storing 94 in location 30 :(before "End Parsing reagent") { diff --git a/037abandon.cc b/037abandon.cc index 74256687..ca7c242c 100644 --- a/037abandon.cc +++ b/037abandon.cc @@ -2,15 +2,15 @@ :(scenario new_reclaim) def main [ - 1:&:num <- new number:type - 2:num <- deaddress 1:&:num # because 1 will get reset during abandon below - abandon 1:&:num - 3:&:num <- new number:type # must be same size as abandoned memory to reuse - 4:num <- deaddress 3:&:num - 5:bool <- equal 2:num, 4:num + 10:&:num <- new number:type + 20:num <- deaddress 10:&:num + abandon 10:&:num + 30:&:num <- new number:type # must be same size as abandoned memory to reuse + 40:num <- deaddress 30:&:num + 50:bool <- equal 20:num, 40:num ] # both allocations should have returned the same address -+mem: storing 1 in location 5 ++mem: storing 1 in location 50 //: When abandoning addresses we'll save them to a 'free list', segregated by size. @@ -39,7 +39,7 @@ case ABANDON: { for (int i = 0; i < SIZE(current_instruction().ingredients); ++i) { reagent/*copy*/ ingredient = current_instruction().ingredients.at(i); canonize(ingredient); - abandon(get_or_insert(Memory, ingredient.value), payload_size(ingredient)); + abandon(get_or_insert(Memory, ingredient.value+/*skip alloc id*/1), payload_size(ingredient)); } break; } @@ -58,7 +58,7 @@ void abandon(int address, int payload_size) { int payload_size(reagent/*copy*/ x) { x.properties.push_back(pair<string, string_tree*>("lookup", NULL)); lookup_memory_core(x, /*check_for_null*/false); - return size_of(x); + return size_of(x)+/*alloc id*/1; } :(after "Allocate Special-cases") @@ -91,12 +91,12 @@ def main [ :(scenario new_reclaim_array) def main [ - 1:&:@:num <- new number:type, 2 - 2:num <- deaddress 1:&:@:num - abandon 1:&:@:num - 3:&:@:num <- new number:type, 2 # same size - 4:num <- deaddress 3:&:@:num - 5:bool <- equal 2:num, 4:num + 10:&:@:num <- new number:type, 2 + 20:num <- deaddress 10:&:@:num + abandon 10:&:@:num + 30:&:@:num <- new number:type, 2 # same size + 40:num <- deaddress 30:&:@:num + 50:bool <- equal 20:num, 40:num ] # both calls to new returned identical addresses -+mem: storing 1 in location 5 ++mem: storing 1 in location 50 diff --git a/038new_text.cc b/038new_text.cc index f0616c54..7e3c02f6 100644 --- a/038new_text.cc +++ b/038new_text.cc @@ -6,21 +6,21 @@ put(Type_abbreviations, "text", new_type_tree("&:@:character")); :(scenario new_string) def main [ - 1:text <- new [abc def] - 2:char <- index *1:text, 5 + 10:text <- new [abc def] + 20:char <- index *10:text, 5 ] # number code for 'e' -+mem: storing 101 in location 2 ++mem: storing 101 in location 20 :(scenario new_string_handles_unicode) def main [ - 1:text <- new [a«c] - 2:num <- length *1:text - 3:char <- index *1:text, 1 + 10:text <- new [a«c] + 20:num <- length *10:text + 21:char <- index *10:text, 1 ] -+mem: storing 3 in location 2 ++mem: storing 3 in location 20 # unicode for '«' -+mem: storing 171 in location 3 ++mem: storing 171 in location 21 :(before "End NEW Check Special-cases") if (is_literal_text(inst.ingredients.at(0))) break; @@ -29,6 +29,7 @@ if (inst.name == "new" && !inst.ingredients.empty() && is_literal_text(inst.ingr :(after "case NEW" following "Primitive Recipe Implementations") if (is_literal_text(current_instruction().ingredients.at(0))) { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(new_mu_text(current_instruction().ingredients.at(0).name)); trace("mem") << "new string alloc: " << products.at(0).at(0) << end(); break; @@ -40,8 +41,9 @@ int new_mu_text(const string& contents) { int string_length = unicode_length(contents); //? Total_alloc += string_length+1; //? ++Num_alloc; - int result = allocate(string_length+/*array length*/1); + int result = allocate(/*array length*/1 + string_length); int curr_address = result; + ++curr_address; // skip alloc id trace("mem") << "storing string length " << string_length << " in location " << curr_address << end(); put(Memory, curr_address, string_length); ++curr_address; // skip length @@ -62,16 +64,16 @@ int new_mu_text(const string& contents) { //: a new kind of typo -:(scenario string_literal_without_instruction) +:(scenario literal_text_without_instruction) % Hide_errors = true; def main [ [abc] ] +error: main: instruction '[abc]' has no recipe in '[abc]' -//: stash recognizes strings +//: stash recognizes texts -:(scenario stash_string) +:(scenario stash_text) def main [ 1:text <- new [abc] stash [foo:], 1:text @@ -80,30 +82,29 @@ def main [ :(before "End inspect Special-cases(r, data)") if (is_mu_text(r)) { - assert(scalar(data)); - return read_mu_text(data.at(0)); + return read_mu_text(data.at(/*skip alloc id*/1)); } :(before "End $print Special-cases") else if (is_mu_text(current_instruction().ingredients.at(i))) { - cout << read_mu_text(ingredients.at(i).at(0)); + cout << read_mu_text(ingredients.at(i).at(/*skip alloc id*/1)); } -:(scenario unicode_string) +:(scenario unicode_text) def main [ 1:text <- new [♠] stash [foo:], 1:text ] +app: foo: ♠ -:(scenario stash_space_after_string) +:(scenario stash_space_after_text) def main [ 1:text <- new [abc] stash 1:text, [foo] ] +app: abc foo -:(scenario stash_string_as_array) +:(scenario stash_text_as_array) def main [ 1:text <- new [abc] stash *1:text @@ -114,15 +115,15 @@ def main [ :(before "End Preprocess is_mu_text(reagent x)") if (!canonize_type(x)) return false; -//: Allocate more to routine when initializing a literal string -:(scenario new_string_overflow) -% Initial_memory_per_routine = 2; +//: Allocate more to routine when initializing a literal text +:(scenario new_text_overflow) +% Initial_memory_per_routine = 3; def main [ - 1:&:num/raw <- new number:type - 2:text/raw <- new [a] # not enough room in initial page, if you take the array length into account + 10:&:num/raw <- new number:type + 20:text/raw <- new [a] # not enough room in initial page, if you take the array length into account ] -+new: routine allocated memory from 1000 to 1002 -+new: routine allocated memory from 1002 to 1004 ++new: routine allocated memory from 1000 to 1003 ++new: routine allocated memory from 1003 to 1006 //: helpers :(code) @@ -140,9 +141,9 @@ int unicode_length(const string& s) { string read_mu_text(int address) { if (address == 0) return ""; - int length = get_or_insert(Memory, address); + int length = get_or_insert(Memory, address+/*alloc id*/1); if (length == 0) return ""; - return read_mu_characters(address+1, length); + return read_mu_characters(address+/*alloc id*/1+/*length*/1, length); } string read_mu_characters(int start, int length) { @@ -156,13 +157,21 @@ string read_mu_characters(int start, int length) { //: assert: perform sanity checks at runtime -:(scenario assert) +:(scenario assert_literal) % Hide_errors = true; // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line. def main [ assert 0, [this is an assert in Mu] ] +error: this is an assert in Mu +:(scenario assert) +% Hide_errors = true; // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line. +def main [ + 1:text <- new [this is an assert in Mu] + assert 0, 1:text +] ++error: this is an assert in Mu + :(before "End Primitive Recipe Declarations") ASSERT, :(before "End Primitive Recipe Numbers") @@ -173,8 +182,8 @@ case ASSERT: { raise << maybe(get(Recipe, r).name) << "'assert' takes exactly two ingredients rather than '" << to_original_string(inst) << "'\n" << end(); break; } - if (!is_mu_scalar(inst.ingredients.at(0))) { - raise << maybe(get(Recipe, r).name) << "'assert' requires a boolean for its first ingredient, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); + if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) { + raise << maybe(get(Recipe, r).name) << "'assert' requires a scalar or address for its first ingredient, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); break; } if (!is_literal_text(inst.ingredients.at(1)) && !is_mu_text(inst.ingredients.at(1))) { @@ -185,11 +194,11 @@ case ASSERT: { } :(before "End Primitive Recipe Implementations") case ASSERT: { - if (!ingredients.at(0).at(0)) { + if (!scalar_ingredient(ingredients, 0)) { if (is_literal_text(current_instruction().ingredients.at(1))) raise << current_instruction().ingredients.at(1).name << '\n' << end(); else - raise << read_mu_text(ingredients.at(1).at(0)) << '\n' << end(); + raise << read_mu_text(ingredients.at(1).at(/*skip alloc id*/1)) << '\n' << end(); if (!Hide_errors) exit(1); } break; diff --git a/042name.cc b/042name.cc index 3cfc8587..3078574e 100644 --- a/042name.cc +++ b/042name.cc @@ -6,8 +6,8 @@ def main [ x:num <- copy 0 ] -+name: assign x 1 -+mem: storing 0 in location 1 ++name: assign x 2 ++mem: storing 0 in location 2 :(scenarios transform) :(scenario transform_names_fails_on_use_before_define) @@ -42,7 +42,7 @@ void transform_names(const recipe_ordinal r) { map<string, int>& names = Name[r]; // store the indices 'used' so far in the map int& curr_idx = names[""]; - ++curr_idx; // avoid using index 0, benign skip in some other cases + curr_idx = 2; // reserve indices 0 and 1 for the chaining slot in a later layer for (int i = 0; i < SIZE(caller.steps); ++i) { instruction& inst = caller.steps.at(i); // End transform_names(inst) Special-cases @@ -135,13 +135,21 @@ bool is_compound_type_starting_with(const type_tree* type, const string& expecte return type->left->value == get(Type_ordinal, expected_name); } -int find_element_name(const type_ordinal t, const string& name, const string& recipe_name) { +int find_element_offset(const type_ordinal t, const string& name, const string& recipe_name) { const type_info& container = get(Type, t); for (int i = 0; i < SIZE(container.elements); ++i) if (container.elements.at(i).name == name) return i; raise << maybe(recipe_name) << "unknown element '" << name << "' in container '" << get(Type, t).name << "'\n" << end(); return -1; } +int find_element_location(int base_address, const string& name, const type_tree* type, const string& recipe_name) { + int offset = find_element_offset(get_base_type(type)->value, name, recipe_name); + if (offset == -1) return offset; + int result = base_address; + for (int i = 0; i < offset; ++i) + result += size_of(element_type(type, i)); + return result; +} bool is_numeric_location(const reagent& x) { if (is_literal(x)) return false; @@ -165,31 +173,35 @@ bool is_special_name(const string& s) { return false; } +bool is_raw(const reagent& r) { + return has_property(r, "raw"); +} + :(scenario transform_names_supports_containers) def main [ x:point <- merge 34, 35 y:num <- copy 3 ] -+name: assign x 1 ++name: assign x 2 # skip location 2 because x occupies two locations -+name: assign y 3 ++name: assign y 4 :(scenario transform_names_supports_static_arrays) def main [ x:@:num:3 <- create-array y:num <- copy 3 ] -+name: assign x 1 ++name: assign x 2 # skip locations 2, 3, 4 because x occupies four locations -+name: assign y 5 ++name: assign y 6 :(scenario transform_names_passes_dummy) # _ is just a dummy result that never gets consumed def main [ _, x:num <- copy 0, 1 ] -+name: assign x 1 --name: assign _ 1 ++name: assign x 2 +-name: assign _ 2 //: an escape hatch to suppress name conversion that we'll use later :(scenarios run) @@ -198,7 +210,7 @@ def main [ def main [ x:num/raw <- copy 0 ] --name: assign x 1 +-name: assign x 2 +error: can't write to location 0 in 'x:num/raw <- copy 0' :(scenarios transform) @@ -266,7 +278,7 @@ if (inst.name == "get" || inst.name == "get-location" || inst.name == "put") { // since first non-address in base type must be a container, we don't have to canonize type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type); if (contains_key(Type, base_type)) { // otherwise we'll raise an error elsewhere - inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); + inst.ingredients.at(1).set_value(find_element_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end(); } } @@ -279,15 +291,13 @@ def main [ ] +error: main: missing type for 'a' in 'get a, x:offset' -//: this test is actually illegal so can't call run -:(scenarios transform) :(scenario transform_names_handles_containers) def main [ - a:point <- copy 0/unsafe - b:num <- copy 0/unsafe + a:point <- merge 0, 0 + b:num <- copy 0 ] -+name: assign a 1 -+name: assign b 3 ++name: assign a 2 ++name: assign b 4 //:: Support variant names for exclusive containers in 'maybe-convert'. @@ -316,7 +326,7 @@ if (inst.name == "maybe-convert") { // since first non-address in base type must be an exclusive container, we don't have to canonize type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type); if (contains_key(Type, base_type)) { // otherwise we'll raise an error elsewhere - inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); + inst.ingredients.at(1).set_value(find_element_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end(); } } diff --git a/043space.cc b/043space.cc index f290a0b9..f9125daf 100644 --- a/043space.cc +++ b/043space.cc @@ -7,44 +7,65 @@ //: //: Warning: messing with 'default-space' can corrupt memory. Don't share //: default-space between recipes. Later we'll see how to chain spaces safely. +//: +//: Tests in this layer can write to a location as part of one type, and read +//: it as part of another. This is unsafe and insecure, and we'll stop doing +//: this once we switch to variable names. //: Under the hood, a space is an array of locations in memory. :(before "End Mu Types Initialization") put(Type_abbreviations, "space", new_type_tree("address:array:location")); :(scenario set_default_space) -# if default-space is 10, and if an array of 5 locals lies from location 12 to 16 (inclusive), -# then local 0 is really location 12, local 1 is really location 13, and so on. def main [ - # pretend address:array:location; in practice we'll use 'new' - 10:num <- copy 5 # length - default-space:space <- copy 10/unsafe - 1:num <- copy 23 + # prepare default-space address + 10:num/alloc-id, 11:num <- copy 0, 1000 + # prepare default-space payload + 1000:num <- copy 0 # alloc id of payload + 1001:num <- copy 5 # length + # actual start of this recipe + default-space:space <- copy 10:&:@:location + # if default-space is 1000, then: + # 1000: alloc id + # 1001: array size + # 1002: location 0 (space for the chaining slot; described later; often unused) + # 1003: location 1 (space for the chaining slot; described later; often unused) + # 1004: local 2 (assuming it is a scalar) + 2:num <- copy 93 ] -+mem: storing 23 in location 12 ++mem: storing 93 in location 1004 :(scenario lookup_sidesteps_default_space) def main [ - # pretend pointer from outside - 2000:num <- copy 34 - # pretend address:array:location; in practice we'll use 'new' - 1000:num <- copy 5 # length + # prepare default-space address + 10:num/alloc-id, 11:num <- copy 0, 1000 + # prepare default-space payload + 1000:num <- copy 0 # alloc id of payload + 1001:num <- copy 5 # length + # prepare payload outside the local scope + 2000:num/alloc-id, 2001:num <- copy 0, 34 # actual start of this recipe - default-space:space <- copy 1000/unsafe - 1:&:num <- copy 2000/unsafe # even local variables always contain raw addresses - 8:num/raw <- copy *1:&:num + default-space:space <- copy 10:&:@:location + # a local address + 2:num, 3:num <- copy 0, 2000 + 20:num/raw <- copy *2:&:num ] -+mem: storing 34 in location 8 ++mem: storing 2000 in location 1005 ++mem: storing 34 in location 20 //: precondition: disable name conversion for 'default-space' +:(scenarios transform) :(scenario convert_names_passes_default_space) % Hide_errors = true; def main [ - default-space:num, x:num <- copy 0, 1 + default-space:num <- copy 0 + x:num <- copy 1 ] -+name: assign x 1 ++name: assign x 2 -name: assign default-space 1 +-name: assign default-space 2 +:(scenarios run) :(before "End is_disqualified Special-cases") if (x.name == "default-space") @@ -74,7 +95,7 @@ void absolutize(reagent& x) { //: hook replaced in a later layer int space_base(const reagent& x) { - return current_call().default_space ? current_call().default_space : 0; + return current_call().default_space ? (current_call().default_space + /*skip alloc id*/1) : 0; } int address(int offset, int base) { @@ -95,9 +116,11 @@ int address(int offset, int base) { :(after "Begin Preprocess write_memory(x, data)") if (x.name == "default-space") { - if (!scalar(data) || !is_mu_space(x)) + if (!is_mu_space(x)) raise << maybe(current_recipe_name()) << "'default-space' should be of type address:array:location, but is " << to_string(x.type) << '\n' << end(); - current_call().default_space = data.at(0); + if (SIZE(data) != 2) + raise << maybe(current_recipe_name()) << "'default-space' getting data from non-address\n" << end(); + current_call().default_space = data.at(/*skip alloc id*/1); return; } :(code) @@ -112,14 +135,21 @@ bool is_mu_space(reagent/*copy*/ x) { :(scenario get_default_space) def main [ - default-space:space <- copy 10/unsafe - 1:space/raw <- copy default-space:space + # prepare default-space address + 10:num/alloc-id, 11:num <- copy 0, 1000 + # prepare default-space payload + 1000:num <- copy 0 # alloc id of payload + 1001:num <- copy 5 # length + # actual start of this recipe + default-space:space <- copy 10:space + 2:space/raw <- copy default-space:space ] -+mem: storing 10 in location 1 ++mem: storing 1000 in location 3 :(after "Begin Preprocess read_memory(x)") if (x.name == "default-space") { vector<double> result; + result.push_back(/*alloc id*/0); result.push_back(current_call().default_space); return result; } @@ -128,17 +158,20 @@ if (x.name == "default-space") { :(scenario lookup_sidesteps_default_space_in_get) def main [ - # pretend pointer to container from outside - 2000:num <- copy 34 - 2001:num <- copy 35 - # pretend address:array:location; in practice we'll use 'new' - 1000:num <- copy 5 # length + # prepare default-space address + 10:num/alloc-id, 11:num <- copy 0, 1000 + # prepare default-space payload + 1000:num <- copy 0 # alloc id of payload + 1001:num <- copy 5 # length + # prepare payload outside the local scope + 2000:num/alloc-id, 2001:num/x, 2002:num/y <- copy 0, 34, 35 # actual start of this recipe - default-space:space <- copy 1000/unsafe - 1:&:point <- copy 2000/unsafe - 9:num/raw <- get *1:&:point, 1:offset + default-space:space <- copy 10:space + # a local address + 2:num, 3:num <- copy 0, 2000 + 3000:num/raw <- get *2:&:point, 1:offset ] -+mem: storing 35 in location 9 ++mem: storing 35 in location 3000 :(before "Read element" following "case GET:") element.properties.push_back(pair<string, string_tree*>("raw", NULL)); @@ -147,18 +180,21 @@ element.properties.push_back(pair<string, string_tree*>("raw", NULL)); :(scenario lookup_sidesteps_default_space_in_index) def main [ - # pretend pointer to array from outside - 2000:num <- copy 2 # length - 2001:num <- copy 34 - 2002:num <- copy 35 - # pretend address:array:location; in practice we'll use 'new' - 1000:num <- copy 5 # length + # prepare default-space address + 10:num/alloc-id, 11:num <- copy 0, 1000 + # prepare default-space payload + 1000:num <- copy 0 # alloc id of payload + 1001:num <- copy 5 # length + # prepare an array address + 20:num/alloc-id, 21:num <- copy 0, 2000 + # prepare an array payload + 2000:num/alloc-id, 2001:num/length, 2002:num/index:0, 2003:num/index:1 <- copy 0, 2, 34, 35 # actual start of this recipe - default-space:space <- copy 1000/unsafe - 1:&:@:num <- copy 2000/unsafe - 9:num/raw <- index *1:&:@:num, 1 + default-space:space <- copy 10:&:@:location + 1:&:@:num <- copy 20:&:@:num/raw + 3000:num/raw <- index *1:&:@:num, 1 ] -+mem: storing 35 in location 9 ++mem: storing 35 in location 3000 :(before "Read element" following "case INDEX:") element.properties.push_back(pair<string, string_tree*>("raw", NULL)); @@ -172,8 +208,8 @@ def main [ x:num <- copy 0 y:num <- copy 3 ] -# allocate space for x and y, as well as the chaining slot at 0 -+mem: array length is 3 +# allocate space for x and y, as well as the chaining slot at indices 0 and 1 ++mem: array length is 4 :(before "End is_disqualified Special-cases") if (x.name == "number-of-locals") diff --git a/044space_surround.cc b/044space_surround.cc index 310672be..9957630d 100644 --- a/044space_surround.cc +++ b/044space_surround.cc @@ -5,27 +5,35 @@ //: (Surrounding spaces are like lexical scopes in other languages.) :(scenario surrounding_space) -# location 1 in space 1 refers to the space surrounding the default space, here 20. +# location 2 in space 1 (remember that locations 0 and 1 are reserved in all +# spaces) refers to the space surrounding the default space, here 20. def main [ - # pretend address:array:location; in practice we'll use 'new' - 10:num <- copy 5 # length - # pretend address:array:location; in practice we'll use 'new" - 20:num <- copy 5 # length + # prepare default-space address + 10:num/alloc-id, 11:num <- copy 0, 1000 + # prepare default-space payload + 1000:num <- copy 0 # alloc id of payload + 1001:num <- copy 5 # length + # prepare address of chained space + 20:num/alloc-id, 21:num <- copy 0, 2000 + # prepare payload of chained space + 2000:num <- copy 0 # alloc id of payload + 2001:num <- copy 5 # length # actual start of this recipe - default-space:space <- copy 10/unsafe + default-space:space <- copy 10:space #: later layers will explain the /names: property - 0:space/names:dummy <- copy 20/unsafe - 1:num <- copy 32 - 1:num/space:1 <- copy 33 + 0:space/names:dummy <- copy 20:space/raw + 2:num <- copy 94 + 2:num/space:1 <- copy 95 ] def dummy [ # just for the /names: property above ] -# chain space: 10 + (length) 1 -+mem: storing 20 in location 11 -# store to default space: 10 + (skip length) 1 + (index) 1 -+mem: storing 32 in location 12 -# store to chained space: (contents of location 12) 20 + (length) 1 + (index) 1 -+mem: storing 33 in location 22 +# chain space: 1000 + (alloc id) 1 + (length) 1 ++mem: storing 0 in location 1002 ++mem: storing 2000 in location 1003 +# store to default space: 1000 + (alloc id) 1 + (length) 1 + (index) 2 ++mem: storing 94 in location 1004 +# store to chained space: (contents of location 1003) 2000 + (alloc id) 1 + (length) 1 + (index) 2 ++mem: storing 95 in location 2004 //: If you think of a space as a collection of variables with a common //: lifetime, surrounding allows managing shorter lifetimes inside a longer @@ -33,14 +41,16 @@ def dummy [ # just for the /names: property above :(replace{} "int space_base(const reagent& x)") int space_base(const reagent& x) { - int base = current_call().default_space ? current_call().default_space : 0; + int base = current_call().default_space ? (current_call().default_space+/*skip alloc id*/1) : 0; return space_base(x, space_index(x), base); } int space_base(const reagent& x, int space_index, int base) { if (space_index == 0) return base; - return space_base(x, space_index-1, get_or_insert(Memory, base+/*skip length*/1)); + double chained_space_address = base+/*skip length*/1+/*skip alloc id of chaining slot*/1; + double chained_space_base = get_or_insert(Memory, chained_space_address) + /*skip alloc id of chained space*/1; + return space_base(x, space_index-1, chained_space_base); } int space_index(const reagent& x) { diff --git a/045closure_name.cc b/045closure_name.cc index e478337d..98bf982c 100644 --- a/045closure_name.cc +++ b/045closure_name.cc @@ -9,14 +9,14 @@ :(scenario closure) def main [ default-space:space <- new location:type, 30 - 1:space/names:new-counter <- new-counter - 2:num/raw <- increment-counter 1:space/names:new-counter - 3:num/raw <- increment-counter 1:space/names:new-counter + 2:space/names:new-counter <- new-counter + 10:num/raw <- increment-counter 2:space/names:new-counter + 11:num/raw <- increment-counter 2:space/names:new-counter ] def new-counter [ default-space:space <- new location:type, 30 x:num <- copy 23 - y:num <- copy 3 # variable that will be incremented + y:num <- copy 13 # variable that will be incremented return default-space:space ] def increment-counter [ @@ -27,7 +27,7 @@ def increment-counter [ return y:num/space:1 ] +name: lexically surrounding space for recipe increment-counter comes from new-counter -+mem: storing 5 in location 3 ++mem: storing 15 in location 11 //: To make this work, compute the recipe that provides names for the //: surrounding space of each recipe. @@ -165,6 +165,6 @@ def use-scope [ ] def main [ 1:space/raw <- new-scope - 2:num/raw <- use-scope 1:space/raw + 3:num/raw <- use-scope 1:space/raw ] -+mem: storing 34 in location 2 ++mem: storing 34 in location 3 diff --git a/046check_type_by_name.cc b/046check_type_by_name.cc index c68a9bae..e44f87d5 100644 --- a/046check_type_by_name.cc +++ b/046check_type_by_name.cc @@ -87,27 +87,27 @@ void check_type(set<reagent>& known, const reagent& x, const recipe& caller) { :(scenario transform_fills_in_missing_types) def main [ - x:num <- copy 1 + x:num <- copy 11 y:num <- add x, 1 ] -# x is in location 1, y in location 2 -+mem: storing 2 in location 2 +# x is in location 2, y in location 3 ++mem: storing 12 in location 3 :(scenario transform_fills_in_missing_types_in_product) def main [ - x:num <- copy 1 - x <- copy 2 + x:num <- copy 11 + x <- copy 12 ] -# x is in location 1 -+mem: storing 2 in location 1 +# x is in location 2 ++mem: storing 12 in location 2 :(scenario transform_fills_in_missing_types_in_product_and_ingredient) def main [ - x:num <- copy 1 + x:num <- copy 11 x <- add x, 1 ] -# x is in location 1 -+mem: storing 2 in location 1 +# x is in location 2 ++mem: storing 12 in location 2 :(scenario transform_fills_in_missing_label_type) def main [ diff --git a/055shape_shifting_container.cc b/055shape_shifting_container.cc index 02e5214b..c3d0246f 100644 --- a/055shape_shifting_container.cc +++ b/055shape_shifting_container.cc @@ -67,7 +67,7 @@ container foo:_a:_b [ def main [ 1:text <- new [abc] # compound types for type ingredients - {2: (foo number (address array character))} <- merge 34/x, 1:text/y + {3: (foo number (address array character))} <- merge 34/x, 1:text/y ] $error: 0 @@ -82,7 +82,7 @@ container bar:_a:_b [ ] def main [ 1:text <- new [abc] - 2:bar:num:@:char <- merge 34/x, 1:text/y + 3:bar:num:@:char <- merge 34/x, 1:text/y ] $error: 0 @@ -247,9 +247,9 @@ container foo:_t [ ] def main [ 1:foo:point <- merge 14, 15, 16 - 2:num <- get 1:foo:point, y:offset + 4:num <- get 1:foo:point, y:offset ] -+mem: storing 16 in location 2 ++mem: storing 16 in location 4 :(scenario get_on_shape_shifting_container_2) container foo:_t [ @@ -258,10 +258,10 @@ container foo:_t [ ] def main [ 1:foo:point <- merge 14, 15, 16 - 2:point <- get 1:foo:point, x:offset + 4:point <- get 1:foo:point, x:offset ] -+mem: storing 14 in location 2 -+mem: storing 15 in location 3 ++mem: storing 14 in location 4 ++mem: storing 15 in location 5 :(scenario get_on_shape_shifting_container_3) container foo:_t [ @@ -269,10 +269,12 @@ container foo:_t [ y:num ] def main [ - 1:foo:&:point <- merge 34/unsafe, 48 - 3:&:point <- get 1:foo:&:point, x:offset + 1:num/alloc-id, 2:num <- copy 0, 34 + 3:foo:&:point <- merge 1:&:point, 48 + 6:&:point <- get 1:foo:&:point, x:offset ] -+mem: storing 34 in location 3 ++mem: storing 0 in location 6 ++mem: storing 34 in location 7 :(scenario get_on_shape_shifting_container_inside_container) container foo:_t [ @@ -285,9 +287,9 @@ container bar [ ] def main [ 1:bar <- merge 14, 15, 16, 17 - 2:num <- get 1:bar, 1:offset + 5:num <- get 1:bar, 1:offset ] -+mem: storing 17 in location 2 ++mem: storing 17 in location 5 :(scenario get_on_complex_shape_shifting_container) container foo:_a:_b [ @@ -296,11 +298,11 @@ container foo:_a:_b [ ] def main [ 1:text <- new [abc] - {2: (foo number (address array character))} <- merge 34/x, 1:text/y - 3:text <- get {2: (foo number (address array character))}, y:offset - 4:bool <- equal 1:text, 3:text + {3: (foo number (address array character))} <- merge 34/x, 1:text/y + 6:text <- get {3: (foo number (address array character))}, y:offset + 8:bool <- equal 1:text, 6:text ] -+mem: storing 1 in location 4 ++mem: storing 1 in location 8 :(before "End element_type Special-cases") replace_type_ingredients(element, type, info, " while computing element type of container"); @@ -346,8 +348,8 @@ exclusive-container foo:_a [ ] def main [ 1:text <- new [abc] - 2:foo:point <- merge 0/variant, 34/xx, 35/xy - 10:point, 20:bool <- maybe-convert 2:foo:point, 0/variant + 3:foo:point <- merge 0/variant, 34/xx, 35/xy + 10:point, 20:bool <- maybe-convert 3:foo:point, 0/variant ] +mem: storing 1 in location 20 +mem: storing 35 in location 11 @@ -565,8 +567,8 @@ container foo:_t [ y:num ] def main [ - 10:foo:point <- merge 14, 15, 16 - 1:num <- get 10:foo, 1:offset + 1:foo:point <- merge 14, 15, 16 + 10:num <- get 1:foo, 1:offset ] # todo: improve error message +error: illegal type "foo" seems to be missing a type ingredient or three while computing element type of container @@ -586,7 +588,7 @@ def main [ % Hide_errors = true; def foo [ local-scope - x:adress:array:number <- copy 0 # typo + x:adress:array:number <- copy null # typo ] # shouldn't crash diff --git a/056shape_shifting_recipe.cc b/056shape_shifting_recipe.cc index 0c97fcdd..68ae1e0a 100644 --- a/056shape_shifting_recipe.cc +++ b/056shape_shifting_recipe.cc @@ -3,7 +3,7 @@ :(scenario shape_shifting_recipe) def main [ 10:point <- merge 14, 15 - 11:point <- foo 10:point + 12:point <- foo 10:point ] # non-matching variant def foo a:num -> result:num [ @@ -17,8 +17,8 @@ def foo a:_t -> result:_t [ load-ingredients result <- copy a ] -+mem: storing 14 in location 11 -+mem: storing 15 in location 12 ++mem: storing 14 in location 12 ++mem: storing 15 in location 13 //: Before anything else, disable transforms for shape-shifting recipes and //: make sure we never try to actually run a shape-shifting recipe. We should @@ -538,7 +538,7 @@ void ensure_all_concrete_types(/*const*/ reagent& x, const recipe& exemplar) { :(scenario shape_shifting_recipe_2) def main [ 10:point <- merge 14, 15 - 11:point <- foo 10:point + 12:point <- foo 10:point ] # non-matching shape-shifting variant def foo a:_t, b:_t -> result:num [ @@ -552,13 +552,13 @@ def foo a:_t -> result:_t [ load-ingredients result <- copy a ] -+mem: storing 14 in location 11 -+mem: storing 15 in location 12 ++mem: storing 14 in location 12 ++mem: storing 15 in location 13 :(scenario shape_shifting_recipe_nonroot) def main [ 10:foo:point <- merge 14, 15, 16 - 20:point/raw <- bar 10:foo:point + 20:point <- bar 10:foo:point ] # shape-shifting recipe with type ingredient following some other type def bar a:foo:_t -> result:_t [ @@ -592,7 +592,7 @@ def foo x:c:_bar:_baz [ :(scenario shape_shifting_recipe_type_deduction_ignores_offsets) def main [ 10:foo:point <- merge 14, 15, 16 - 20:point/raw <- bar 10:foo:point + 20:point <- bar 10:foo:point ] def bar a:foo:_t -> result:_t [ local-scope @@ -699,14 +699,14 @@ def main [ 1:&:point <- new point:type *1:&:point <- put *1:&:point, y:offset, 34 3:&:point <- bar 1:&:point # specialize _t to address:point - 4:point <- copy *3:&:point + 5:point <- copy *3:&:point ] def bar a:_t -> result:_t [ local-scope load-ingredients result <- copy a ] -+mem: storing 34 in location 5 ++mem: storing 34 in location 6 //: specializing a type ingredient with a compound type -- while *inside* another compound type :(scenario shape_shifting_recipe_supports_compound_types_2) @@ -935,7 +935,7 @@ def foo x:_elem -> y:num [ :(scenario specialize_most_similar_variant) def main [ 1:&:num <- new number:type - 2:num <- foo 1:&:num + 10:num <- foo 1:&:num ] def foo x:_elem -> y:num [ local-scope @@ -947,14 +947,14 @@ def foo x:&:_elem -> y:num [ load-ingredients return 35 ] -+mem: storing 35 in location 2 ++mem: storing 35 in location 10 :(scenario specialize_most_similar_variant_2) # version with headers padded with lots of unrelated concrete types def main [ 1:num <- copy 23 2:&:@:num <- copy null - 3:num <- foo 2:&:@:num, 1:num + 4:num <- foo 2:&:@:num, 1:num ] # variant with concrete type def foo dummy:&:@:num, x:num -> y:num, dummy:&:@:num [ @@ -969,7 +969,7 @@ def foo dummy:&:@:num, x:_elem -> y:num, dummy:&:@:num [ return 35 ] # prefer the concrete variant -+mem: storing 34 in location 3 ++mem: storing 34 in location 4 :(scenario specialize_most_similar_variant_3) def main [ @@ -977,13 +977,13 @@ def main [ foo 1:text ] def foo x:text [ - 2:num <- copy 34 + 10:num <- copy 34 ] def foo x:&:_elem [ - 2:num <- copy 35 + 10:num <- copy 35 ] # make sure the more precise version was used -+mem: storing 34 in location 2 ++mem: storing 34 in location 10 :(scenario specialize_literal_as_number) def main [ diff --git a/058to_text.cc b/058to_text.cc index 8c86e36c..9cb14e14 100644 --- a/058to_text.cc +++ b/058to_text.cc @@ -18,6 +18,7 @@ case TO_TEXT: { :(before "End Primitive Recipe Implementations") case TO_TEXT: { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(new_mu_text(inspect(current_instruction().ingredients.at(0), ingredients.at(0)))); break; } diff --git a/060rewrite_literal_string.cc b/060rewrite_literal_string.cc index f4ed9b4c..95e38924 100644 --- a/060rewrite_literal_string.cc +++ b/060rewrite_literal_string.cc @@ -19,6 +19,7 @@ Transform.push_back(rewrite_literal_string_to_text); // idempotent set<string> recipes_taking_literal_strings; :(code) void initialize_transform_rewrite_literal_string_to_text() { + recipes_taking_literal_strings.insert("assert"); recipes_taking_literal_strings.insert("$print"); recipes_taking_literal_strings.insert("$dump-trace"); recipes_taking_literal_strings.insert("$system"); diff --git a/065duplex_list.mu b/065duplex_list.mu index 7d369186..3a7de8f6 100644 --- a/065duplex_list.mu +++ b/065duplex_list.mu @@ -399,12 +399,17 @@ def remove-between start:&:duplex-list:_elem, end:&:duplex-list:_elem/contained- # start->next = end *next <- put *next, prev:offset, null *start <- put *start, next:offset, end - return-unless end + { + break-if end + stash [spliced:] next + return + } # end->prev->next = 0 # end->prev = start prev:&:duplex-list:_elem <- get *end, prev:offset assert prev, [malformed duplex list - 2] *prev <- put *prev, next:offset, null + stash [spliced:] next *end <- put *end, prev:offset, start ] @@ -437,6 +442,9 @@ scenario remove-range [ 12 <- 15 20 <- 0 ] + trace-should-contain [ + app: spliced: 16 <-> 17 <-> 18 + ] ] scenario remove-range-to-final [ @@ -472,6 +480,49 @@ scenario remove-range-to-final [ 12 <- 18 20 <- 0 # no more elements ] + trace-should-contain [ + app: spliced: 15 <-> 16 <-> 17 + ] +] + +scenario remove-range-to-penultimate [ + local-scope + # construct a duplex list with six elements [13, 14, 15, 16, 17, 18] + list:&:duplex-list:num <- push 18, null + list <- push 17, list + list <- push 16, list + list <- push 15, list + list <- push 14, list + list <- push 13, list + run [ + # delete 15 and 16 + # start pointer: to the second element + list2:&:duplex-list:num <- next list + # end pointer: to the last (sixth) element + end:&:duplex-list:num <- next list2 + end <- next end + end <- next end + remove-between list2, end + # now check the list + 10:num/raw <- get *list, value:offset + list <- next list + 11:num/raw <- get *list, value:offset + list <- next list + 12:num/raw <- get *list, value:offset + list <- next list + 13:num/raw <- get *list, value:offset + 20:&:duplex-list:num/raw <- next list + ] + memory-should-contain [ + 10 <- 13 + 11 <- 14 + 12 <- 17 + 13 <- 18 + 20 <- 0 # no more elements + ] + trace-should-contain [ + app: spliced: 15 <-> 16 + ] ] scenario remove-range-empty [ diff --git a/069hash.cc b/069hash.cc index 8a698d38..c810f98b 100644 --- a/069hash.cc +++ b/069hash.cc @@ -62,7 +62,7 @@ size_t hash_mu_address(size_t h, reagent& r) { } size_t hash_mu_text(size_t h, const reagent& r) { - string input = read_mu_text(get_or_insert(Memory, r.value)); + string input = read_mu_text(get_or_insert(Memory, r.value+/*skip alloc id*/1)); for (int i = 0; i < SIZE(input); ++i) { h = hash_iter(h, static_cast<size_t>(input.at(i))); //? cerr << i << ": " << h << '\n'; @@ -319,11 +319,11 @@ def main [ :(scenario hash_matches_old_version) def main [ 1:text <- new [abc] - 2:num <- hash 1:text - 3:num <- hash_old 1:text - 4:bool <- equal 2:num, 3:num + 3:num <- hash 1:text + 4:num <- hash_old 1:text + 5:bool <- equal 3:num, 4:num ] -+mem: storing 1 in location 4 ++mem: storing 1 in location 5 :(before "End Primitive Recipe Declarations") HASH_OLD, @@ -343,7 +343,7 @@ case HASH_OLD: { } :(before "End Primitive Recipe Implementations") case HASH_OLD: { - string input = read_mu_text(ingredients.at(0).at(0)); + string input = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); size_t h = 0 ; for (int i = 0; i < SIZE(input); ++i) { diff --git a/074wait.cc b/074wait.cc index eb17c8aa..aa3b59af 100644 --- a/074wait.cc +++ b/074wait.cc @@ -262,22 +262,20 @@ def main [ :(scenario get_location_indirect) # 'get-location' can read from container address def main [ - 1:num <- copy 10 - 10:num <- copy 34 - 11:num <- copy 35 - 4:location <- get-location 1:&:point/lookup, 0:offset + 1:num/alloc-id, 2:num <- copy 0, 10 + 10:num/alloc-id, 11:num/x, 12:num/y <- copy 0, 34, 35 + 20:location <- get-location 1:&:point/lookup, 0:offset ] -+mem: storing 10 in location 4 ++mem: storing 11 in location 20 :(scenario get_location_indirect_2) def main [ - 1:num <- copy 10 - 10:num <- copy 34 - 11:num <- copy 35 - 4:&:num <- copy 20/unsafe + 1:num/alloc-id, 2:num <- copy 0, 10 + 10:num/alloc-id, 11:num/x, 12:num/y <- copy 0, 34, 35 + 4:num/alloc-id, 5:num <- copy 0, 20 4:&:location/lookup <- get-location 1:&:point/lookup, 0:offset ] -+mem: storing 10 in location 20 ++mem: storing 11 in location 21 //: allow waiting on a routine to complete diff --git a/082scenario_screen.cc b/082scenario_screen.cc index 31cbfcc9..dc015fed 100644 --- a/082scenario_screen.cc +++ b/082scenario_screen.cc @@ -145,8 +145,14 @@ assert(Next_predefined_global_for_scenarios < Reserved_for_tests); :(before "End Globals") // Scenario Globals. -extern const int SCREEN = Next_predefined_global_for_scenarios++; +extern const int SCREEN = next_predefined_global_for_scenarios(/*size_of(address:screen)*/2); // End Scenario Globals. +:(code) +int next_predefined_global_for_scenarios(int size) { + int result = Next_predefined_global_for_scenarios; + Next_predefined_global_for_scenarios += size; + return result; +} //: give 'screen' a fixed location in scenarios :(before "End Special Scenario Variable Names(r)") @@ -250,19 +256,27 @@ struct raw_string_stream { :(code) void check_screen(const string& expected_contents, const int color) { - int screen_location = get_or_insert(Memory, SCREEN); - int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", ""); - assert(data_offset >= 0); - int screen_data_location = screen_location+data_offset; // type: address:array:character - int screen_data_start = get_or_insert(Memory, screen_data_location); // type: array:character - int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", ""); - int screen_width = get_or_insert(Memory, screen_location+width_offset); - int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", ""); - int screen_height = get_or_insert(Memory, screen_location+height_offset); + int screen_location = get_or_insert(Memory, SCREEN+/*skip address alloc id*/1) + /*skip payload alloc id*/1; + reagent screen("x:screen"); // just to ensure screen.type is reclaimed + int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen"); // type: address:array:character + assert(screen_data_location >= 0); +//? cerr << "screen data is at location " << screen_data_location << '\n'; + int screen_data_start = get_or_insert(Memory, screen_data_location+/*skip address alloc id*/1) + /*skip payload alloc id*/1; // type: array:character +//? cerr << "screen data start is at " << screen_data_start << '\n'; + int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen"); +//? cerr << "screen width is at location " << screen_width_location << '\n'; + int screen_width = get_or_insert(Memory, screen_width_location); +//? cerr << "screen width: " << screen_width << '\n'; + int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen"); +//? cerr << "screen height is at location " << screen_height_location << '\n'; + int screen_height = get_or_insert(Memory, screen_height_location); +//? cerr << "screen height: " << screen_height << '\n'; + int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen"); +//? cerr << "top of screen is at location " << top_index_location << '\n'; + int top_index = get_or_insert(Memory, top_index_location); +//? cerr << "top of screen is index " << top_index << '\n'; raw_string_stream cursor(expected_contents); // todo: too-long expected_contents should fail - int top_index_offset = find_element_name(get(Type_ordinal, "screen"), "top-idx", ""); - int top_index = get_or_insert(Memory, screen_location+top_index_offset); for (int i=0, row=top_index/screen_width; i < screen_height; ++i, row=(row+1)%screen_height) { cursor.skip_whitespace_and_comments(); if (cursor.at_end()) break; @@ -385,18 +399,25 @@ case _DUMP_SCREEN: { :(code) void dump_screen() { - int screen_location = get_or_insert(Memory, SCREEN); - int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", ""); - int screen_width = get_or_insert(Memory, screen_location+width_offset); - int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", ""); - int screen_height = get_or_insert(Memory, screen_location+height_offset); - int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", ""); - assert(data_offset >= 0); - int screen_data_location = screen_location+data_offset; // type: address:array:character - int screen_data_start = get_or_insert(Memory, screen_data_location); // type: array:character - assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height); - int top_index_offset = find_element_name(get(Type_ordinal, "screen"), "top-idx", ""); - int top_index = get_or_insert(Memory, screen_location+top_index_offset); + int screen_location = get_or_insert(Memory, SCREEN+/*skip address alloc id*/1) + /*skip payload alloc id*/1; + reagent screen("x:screen"); // just to ensure screen.type is reclaimed + int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen"); // type: address:array:character + assert(screen_data_location >= 0); +//? cerr << "screen data is at location " << screen_data_location << '\n'; + int screen_data_start = get_or_insert(Memory, screen_data_location+/*skip address alloc id*/1) + /*skip payload alloc id*/1; // type: array:character +//? cerr << "screen data start is at " << screen_data_start << '\n'; + int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen"); +//? cerr << "screen width is at location " << screen_width_location << '\n'; + int screen_width = get_or_insert(Memory, screen_width_location); +//? cerr << "screen width: " << screen_width << '\n'; + int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen"); +//? cerr << "screen height is at location " << screen_height_location << '\n'; + int screen_height = get_or_insert(Memory, screen_height_location); +//? cerr << "screen height: " << screen_height << '\n'; + int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen"); +//? cerr << "top of screen is at location " << top_index_location << '\n'; + int top_index = get_or_insert(Memory, top_index_location); +//? cerr << "top of screen is index " << top_index << '\n'; for (int i=0, row=top_index/screen_width; i < screen_height; ++i, row=(row+1)%screen_height) { cerr << '.'; int curr = screen_data_start+/*length*/1+row*screen_width* /*size of screen-cell*/2; diff --git a/085scenario_console.cc b/085scenario_console.cc index 2c3ab4bc..31aa4fe7 100644 --- a/085scenario_console.cc +++ b/085scenario_console.cc @@ -34,7 +34,7 @@ scenario keyboard-in-scenario [ ] :(before "End Scenario Globals") -extern const int CONSOLE = Next_predefined_global_for_scenarios++; +extern const int CONSOLE = next_predefined_global_for_scenarios(/*size_of(address:console)*/2); //: give 'console' a fixed location in scenarios :(before "End Special Scenario Variable Names(r)") Name[r]["console"] = CONSOLE; @@ -61,8 +61,8 @@ case ASSUME_CONSOLE: { int size = /*length*/1 + num_events*size_of_event(); int event_data_address = allocate(size); // store length - put(Memory, event_data_address, num_events); - int curr_address = event_data_address + /*skip length*/1; + put(Memory, event_data_address+/*skip alloc id*/1, num_events); + int curr_address = event_data_address + /*skip alloc id*/1 + /*skip length*/1; for (int i = 0; i < SIZE(r.steps); ++i) { const instruction& inst = r.steps.at(i); if (inst.name == "left-click") { @@ -113,13 +113,13 @@ case ASSUME_CONSOLE: { } } } - assert(curr_address == event_data_address+size); + assert(curr_address == event_data_address+/*skip alloc id*/1+size); // wrap the array of events in a console object int console_address = allocate(size_of_console()); trace("mem") << "storing console in " << console_address << end(); - put(Memory, CONSOLE, console_address); + put(Memory, CONSOLE+/*skip alloc id*/1, console_address); trace("mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end(); - put(Memory, console_address+/*offset of 'data' in container 'events'*/1, event_data_address); + put(Memory, console_address+/*skip alloc id*/1+/*offset of 'data' in container 'events'*/1+/*skip alloc id of 'data'*/1, event_data_address); break; } diff --git a/087file.cc b/087file.cc index 44da9b02..9fd056db 100644 --- a/087file.cc +++ b/087file.cc @@ -35,7 +35,7 @@ case _OPEN_FILE_FOR_READING: { } :(before "End Primitive Recipe Implementations") case _OPEN_FILE_FOR_READING: { - string filename = read_mu_text(ingredients.at(0).at(0)); + string filename = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); assert(sizeof(long long int) >= sizeof(FILE*)); FILE* f = fopen(filename.c_str(), "r"); long long int result = reinterpret_cast<long long int>(f); @@ -70,7 +70,7 @@ case _OPEN_FILE_FOR_WRITING: { } :(before "End Primitive Recipe Implementations") case _OPEN_FILE_FOR_WRITING: { - string filename = read_mu_text(ingredients.at(0).at(0)); + string filename = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); assert(sizeof(long long int) >= sizeof(FILE*)); long long int result = reinterpret_cast<long long int>(fopen(filename.c_str(), "w")); products.resize(1); diff --git a/089scenario_filesystem.cc b/089scenario_filesystem.cc index f14534ac..bacb61be 100644 --- a/089scenario_filesystem.cc +++ b/089scenario_filesystem.cc @@ -71,7 +71,7 @@ scenario escaping-file-contents [ ] :(before "End Globals") -extern const int RESOURCES = Next_predefined_global_for_scenarios++; +extern const int RESOURCES = next_predefined_global_for_scenarios(/*size_of(address:resources)*/2); //: give 'resources' a fixed location in scenarios :(before "End Special Scenario Variable Names(r)") Name[r]["resources"] = RESOURCES; @@ -203,26 +203,28 @@ string munge_resources_contents(const string& data, const string& filename, cons } void construct_resources_object(const map<string, string>& contents) { - int resources_data_address = allocate(SIZE(contents)*2 + /*array length*/1); - int curr = resources_data_address + /*skip length*/1; + int resources_data_address = allocate(SIZE(contents) * /*size of resource*/4 + /*array length*/1); + int curr = resources_data_address + /*skip alloc id*/1 + /*skip array length*/1; for (map<string, string>::const_iterator p = contents.begin(); p != contents.end(); ++p) { + ++curr; // skip alloc id of resource.name put(Memory, curr, new_mu_text(p->first)); trace("mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end(); ++curr; + ++curr; // skip alloc id of resource.contents put(Memory, curr, new_mu_text(p->second)); trace("mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end(); ++curr; } - curr = resources_data_address; - put(Memory, curr, SIZE(contents)); // size of array + curr = resources_data_address + /*skip alloc id of resources.data*/1; + put(Memory, curr, SIZE(contents)); // array length trace("mem") << "storing resources size " << get(Memory, curr) << " in location " << curr << end(); // wrap the resources data in a 'resources' object int resources_address = allocate(size_of_resources()); - curr = resources_address+/*offset of 'data' element*/1; + curr = resources_address+/*alloc id*/1+/*offset of 'data' element*/1+/*skip alloc id of 'data' element*/1; put(Memory, curr, resources_data_address); trace("mem") << "storing resources data address " << resources_data_address << " in location " << curr << end(); // save in product - put(Memory, RESOURCES, resources_address); + put(Memory, RESOURCES+/*skip alloc id*/1, resources_address); trace("mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end(); } diff --git a/091socket.cc b/091socket.cc index 7b6ca5b1..a0f3b948 100644 --- a/091socket.cc +++ b/091socket.cc @@ -40,7 +40,7 @@ case _OPEN_CLIENT_SOCKET: { } :(before "End Primitive Recipe Implementations") case _OPEN_CLIENT_SOCKET: { - string host = read_mu_text(ingredients.at(0).at(0)); + string host = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); int port = ingredients.at(1).at(0); socket_t* client = client_socket(host, port); products.resize(1); diff --git a/101run_sandboxed.cc b/101run_sandboxed.cc index a0b827e9..f7c7522f 100644 --- a/101run_sandboxed.cc +++ b/101run_sandboxed.cc @@ -3,20 +3,22 @@ :(scenario run_interactive_code) def main [ - 1:num <- copy 0 - 2:text <- new [1:num/raw <- copy 34] - run-sandboxed 2:text - 3:num <- copy 1:num + 1:num <- copy 0 # reserve space for the sandbox + 10:text <- new [1:num/raw <- copy 34] +#? $print 10:num [|] 11:num [: ] 1000:num [|] *10:text [ (] 10:text [)] 10/newline + run-sandboxed 10:text + 20:num <- copy 1:num ] -+mem: storing 34 in location 3 ++mem: storing 34 in location 20 :(scenario run_interactive_empty) def main [ - 1:text <- copy 0/unsafe - 2:text <- run-sandboxed 1:text + 10:text <- copy null + 20:text <- run-sandboxed 10:text ] # result is null -+mem: storing 0 in location 2 ++mem: storing 0 in location 20 ++mem: storing 0 in location 21 //: As the name suggests, 'run-sandboxed' will prevent certain operations that //: regular Mu code can perform. @@ -52,12 +54,16 @@ case RUN_SANDBOXED: { } :(before "End Primitive Recipe Implementations") case RUN_SANDBOXED: { - bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0)); + bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(/*skip alloc id*/1)); if (!new_code_pushed_to_stack) { products.resize(5); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(0); + products.at(1).push_back(/*alloc id*/0); products.at(1).push_back(trace_error_contents()); + products.at(2).push_back(/*alloc id*/0); products.at(2).push_back(0); + products.at(3).push_back(/*alloc id*/0); products.at(3).push_back(trace_app_contents()); products.at(4).push_back(1); // completed run_code_end(); @@ -90,6 +96,7 @@ string Save_trace_file; // all errors. // returns true if successfully called (no errors found during load and transform) bool run_interactive(int address) { +//? cerr << "run_interactive: " << address << '\n'; assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0); // try to sandbox the run as best you can // todo: test this @@ -98,6 +105,7 @@ bool run_interactive(int address) { Memory.erase(i); } string command = trim(strip_comments(read_mu_text(address))); +//? cerr << "command: " << command << '\n'; Name[get(Recipe_ordinal, "interactive")].clear(); run_code_begin(/*should_stash_snapshots*/true); if (command.empty()) return false; @@ -213,15 +221,20 @@ load(string( "]\n" + "recipe sandbox [\n" + "local-scope\n" + +//? "$print [aaa] 10/newline\n" + "screen:&:screen <- new-fake-screen 30, 5\n" + "routine-id:num <- start-running interactive, screen\n" + "limit-time routine-id, 100000/instructions\n" + "wait-for-routine routine-id\n" + +//? "$print [bbb] 10/newline\n" + "instructions-run:num <- number-of-instructions routine-id\n" + "stash instructions-run [instructions run]\n" + "sandbox-state:num <- routine-state routine-id\n" + "completed?:bool <- equal sandbox-state, 1/completed\n" + +//? "$print [completed: ] completed? 10/newline\n" + "output:text <- $most-recent-products\n" + +//? "$print [zzz] 10/newline\n" + +//? "$print output\n" + "errors:text <- save-errors\n" + "stashes:text <- save-app-trace\n" + "$cleanup-run-sandboxed\n" + @@ -281,6 +294,7 @@ case _MOST_RECENT_PRODUCTS: { :(before "End Primitive Recipe Implementations") case _MOST_RECENT_PRODUCTS: { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(new_mu_text(Most_recent_products)); break; } @@ -296,6 +310,7 @@ case SAVE_ERRORS: { :(before "End Primitive Recipe Implementations") case SAVE_ERRORS: { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(trace_error_contents()); break; } @@ -311,6 +326,7 @@ case SAVE_APP_TRACE: { :(before "End Primitive Recipe Implementations") case SAVE_APP_TRACE: { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(trace_app_contents()); break; } @@ -332,64 +348,64 @@ case _CLEANUP_RUN_SANDBOXED: { :(scenario "run_interactive_converts_result_to_text") def main [ # try to interactively add 2 and 2 - 1:text <- new [add 2, 2] - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 10:text <- new [add 2, 2] + 20:text <- run-sandboxed 10:text + 30:@:char <- copy *20:text ] # first letter in the output should be '4' in unicode -+mem: storing 52 in location 11 ++mem: storing 52 in location 31 :(scenario "run_interactive_ignores_products_in_nested_functions") def main [ - 1:text <- new [foo] - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 10:text <- new [foo] + 20:text <- run-sandboxed 10:text + 30:@:char <- copy *20:text ] def foo [ - 20:num <- copy 1234 + 40:num <- copy 1234 { break reply 5678 } ] # no product should have been tracked -+mem: storing 0 in location 10 ++mem: storing 0 in location 30 :(scenario "run_interactive_ignores_products_in_previous_instructions") def main [ - 1:text <- new [ + 10:text <- new [ add 1, 1 # generates a product foo] # no products - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 20:text <- run-sandboxed 10:text + 30:@:char <- copy *20:text ] def foo [ - 20:num <- copy 1234 + 40:num <- copy 1234 { break reply 5678 } ] # no product should have been tracked -+mem: storing 0 in location 10 ++mem: storing 0 in location 30 :(scenario "run_interactive_remembers_products_before_final_label") def main [ - 1:text <- new [ + 10:text <- new [ add 1, 1 # generates a product +foo] # no products - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 20:text <- run-sandboxed 10:text + 30:@:char <- copy *20:text ] def foo [ - 20:num <- copy 1234 + 40:num <- copy 1234 { break reply 5678 } ] # product tracked -+mem: storing 50 in location 11 ++mem: storing 50 in location 31 :(scenario "run_interactive_returns_text") def main [ @@ -399,38 +415,41 @@ def main [ y:text <- new [b] z:text <- append x:text, y:text ] - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 10:text <- run-sandboxed 1:text +#? $print 10:text 10/newline + 20:@:char <- copy *10:text ] # output contains "ab" -+mem: storing 97 in location 11 -+mem: storing 98 in location 12 ++mem: storing 97 in location 21 ++mem: storing 98 in location 22 :(scenario "run_interactive_returns_errors") def main [ # run a command that generates an error - 1:text <- new [x:num <- copy 34 + 10:text <- new [x:num <- copy 34 get x:num, foo:offset] - 2:text, 3:text <- run-sandboxed 1:text - 10:@:char <- copy *3:text + 20:text, 30:text <- run-sandboxed 10:text + 40:@:char <- copy *30:text ] # error should be "unknown element foo in container number" -+mem: storing 117 in location 11 -+mem: storing 110 in location 12 -+mem: storing 107 in location 13 -+mem: storing 110 in location 14 ++mem: storing 117 in location 41 ++mem: storing 110 in location 42 ++mem: storing 107 in location 43 ++mem: storing 110 in location 44 # ... :(scenario run_interactive_with_comment) def main [ # 2 instructions, with a comment after the first - 1:&:@:num <- new [a:num <- copy 0 # abc + 10:text <- new [a:num <- copy 0 # abc b:num <- copy 0 ] - 2:text, 3:text <- run-sandboxed 1:text + 20:text, 30:text <- run-sandboxed 10:text ] # no errors -+mem: storing 0 in location 3 +# skip alloc id ++mem: storing 0 in location 30 ++mem: storing 0 in location 31 :(after "Running One Instruction") if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at @@ -441,6 +460,7 @@ if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_ :(before "End Running One Instruction") if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at) { Most_recent_products = track_most_recent_products(current_instruction(), products); +//? cerr << "most recent products: " << Most_recent_products << '\n'; } :(code) string track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) { @@ -458,8 +478,8 @@ string track_most_recent_products(const instruction& instruction, const vector<v // => abc if (i < SIZE(instruction.products)) { if (is_mu_text(instruction.products.at(i))) { - if (!scalar(products.at(i))) continue; // error handled elsewhere - out << read_mu_text(products.at(i).at(0)) << '\n'; + if (SIZE(products.at(i)) != 2) continue; // weak silent check for address + out << read_mu_text(products.at(i).at(/*skip alloc id*/1)) << '\n'; continue; } } @@ -551,7 +571,7 @@ case RELOAD: { :(before "End Primitive Recipe Implementations") case RELOAD: { restore_non_recipe_snapshots(); - string code = read_mu_text(ingredients.at(0).at(0)); + string code = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); run_code_begin(/*should_stash_snapshots*/false); routine* save_current_routine = Current_routine; Current_routine = NULL; @@ -562,17 +582,30 @@ case RELOAD: { Sandbox_mode = false; Current_routine = save_current_routine; products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(trace_error_contents()); run_code_end(); // wait until we're done with the trace contents break; } +:(scenario reload_loads_function_definitions) +def main [ + local-scope + x:text <- new [recipe foo [ + 1:num/raw <- copy 34 + ]] + reload x + run-sandboxed [foo] + 2:num/raw <- copy 1:num/raw +] ++mem: storing 34 in location 2 + :(scenario reload_continues_past_error) def main [ local-scope x:text <- new [recipe foo [ - get 1234:num, foo:offset -]] + get 1234:num, foo:offset + ]] reload x 1:num/raw <- copy 34 ] @@ -593,9 +626,11 @@ def main [ ] ] # save warning addresses in locations of type 'number' to avoid spurious changes to them due to 'abandon' - 1:num/raw <- reload x - 2:num/raw <- reload x + 10:text/raw <- reload x + 20:text/raw <- reload x ] # no errors on either load -+mem: storing 0 in location 1 -+mem: storing 0 in location 2 ++mem: storing 0 in location 10 ++mem: storing 0 in location 11 ++mem: storing 0 in location 20 ++mem: storing 0 in location 21 diff --git a/Readme.md b/Readme.md index 4e2b98c4..28784b30 100644 --- a/Readme.md +++ b/Readme.md @@ -336,9 +336,9 @@ channels. Routines are expected to communicate purely by message passing, though nothing stops them from sharing memory since all routines share a common address -space. However, idiomatic Mu will make it hard to accidentally read or clobber -random memory locations. Bounds checking is baked deeply into the semantics, -and pointers can never be invalidated. +space. However, idiomatic Mu will make it hard to accidentally read or +clobber random memory locations. Bounds checking is baked deeply into +the semantics, and using pointers after freeing them immediately fails. --- diff --git a/edit/001-editor.mu b/edit/001-editor.mu index 963bb1cf..b3399dbb 100644 --- a/edit/001-editor.mu +++ b/edit/001-editor.mu @@ -81,18 +81,20 @@ scenario editor-initializes-without-data [ assume-screen 5/width, 3/height run [ e:&:editor <- new-editor null/data, 2/left, 5/right - 2:editor/raw <- copy *e + 1:editor/raw <- copy *e ] memory-should-contain [ - # 2 (data) <- just the § sentinel - # 3 (top of screen) <- the § sentinel - 4 <- 0 # bottom-of-screen; null since text fits on screen - # 5 (before cursor) <- the § sentinel - 6 <- 2 # left - 7 <- 4 # right (inclusive) - 8 <- 0 # bottom (not set until render) - 9 <- 1 # cursor row - 10 <- 2 # cursor column + # 1,2 (data) <- just the § sentinel + # 3,4 (top of screen) <- the § sentinel + # 5 (bottom of screen) <- null since text fits on screen + 5 <- 0 + 6 <- 0 + # 7,8 (before cursor) <- the § sentinel + 9 <- 2 # left + 10 <- 4 # right (inclusive) + 11 <- 0 # bottom (not set until render) + 12 <- 1 # cursor row + 13 <- 2 # cursor column ] screen-should-contain [ . . diff --git a/exception1.mu b/exception1.mu index dd60c744..df4754e5 100644 --- a/exception1.mu +++ b/exception1.mu @@ -57,5 +57,5 @@ def f [ return-continuation-until-mark 999/exception-tag, [error will robinson!], 0/unused } # normal return: 3 results including 0 continuation placeholder at start - return 0/continuation-placeholder, 0/no-error, 34/regular-result + return 0/continuation-placeholder, null/no-error, 34/regular-result ] diff --git a/index.html b/index.html index 88e9b14a..dde93753 100644 --- a/index.html +++ b/index.html @@ -158,18 +158,16 @@ for gradually constructing long strings in a piecemeal fashion. space at run-time as pointers or <em>addresses</em>. All Mu instructions can dereference or <a href='html/035lookup.cc.html'><em>lookup</em></a> addresses of values in addition to operating on regular values. These addresses are -manually managed like C. However, all allocations are transparently -reference-counted or <a href='html/036refcount.cc.html'><em>refcounted</em></a>, -with every copy of a pointer updating refcounts appropriately. When the -refcount of an allocation drops to zero it is transparently <a href='html/037abandon.cc.html'>reclaimed</a> -and made available to future allocations. By construction it is impossible to -reclaim memory prematurely, while some other part of a program is still -pointing to it. This eliminates a whole class of undefined behavior and -security vulnerabilities that plague C. Compared to Rust, Mu pays some -additional runtime cost in exchange for C-like flexibility (you can copy -addresses around all you like, and write from any copy of an address) and -simpler implementation (no static analysis). Mu by convention abbreviates type -<tt>address</tt> to <tt>&</tt>. +manually managed like C, and can be reclaimed using the <a href='html/037abandon.cc.html'><tt>abandon</tt></a> +instruction. To ensure that stale addresses aren't used after being +abandoned/reused, each allocation gets a unique <em>alloc id</em> that is also +stored in the address returned. The lookup operation ensures that the alloc id +of an address matches that of its payload. This eliminates a whole class of +undefined behavior and security vulnerabilities that plague C. Compared to +Rust, Mu pays some additional runtime cost in exchange for C-like flexibility +(you can copy addresses around all you like, and write from any copy of an +address) and simpler implementation (no static analysis). Mu by convention +abbreviates type <tt>address</tt> to <tt>&</tt>. <p/>Support for higher-order recipes that can pass <a href='html/072recipe.cc.html'>recipes</a> around like any other value. diff --git a/sandbox/001-editor.mu b/sandbox/001-editor.mu index 963bb1cf..b3399dbb 100644 --- a/sandbox/001-editor.mu +++ b/sandbox/001-editor.mu @@ -81,18 +81,20 @@ scenario editor-initializes-without-data [ assume-screen 5/width, 3/height run [ e:&:editor <- new-editor null/data, 2/left, 5/right - 2:editor/raw <- copy *e + 1:editor/raw <- copy *e ] memory-should-contain [ - # 2 (data) <- just the § sentinel - # 3 (top of screen) <- the § sentinel - 4 <- 0 # bottom-of-screen; null since text fits on screen - # 5 (before cursor) <- the § sentinel - 6 <- 2 # left - 7 <- 4 # right (inclusive) - 8 <- 0 # bottom (not set until render) - 9 <- 1 # cursor row - 10 <- 2 # cursor column + # 1,2 (data) <- just the § sentinel + # 3,4 (top of screen) <- the § sentinel + # 5 (bottom of screen) <- null since text fits on screen + 5 <- 0 + 6 <- 0 + # 7,8 (before cursor) <- the § sentinel + 9 <- 2 # left + 10 <- 4 # right (inclusive) + 11 <- 0 # bottom (not set until render) + 12 <- 1 # cursor row + 13 <- 2 # cursor column ] screen-should-contain [ . . |