4266 - space for alloc-id in heap allocations

This has taken me almost 6 weeks :(
This commit is contained in:
Kartik Agaram 2018-06-24 09:16:17 -07:00
parent 377b00b045
commit 23d3a02226
36 changed files with 926 additions and 648 deletions

View File

@ -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 [

View File

@ -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;
}

View File

@ -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 [

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -124,6 +124,8 @@ case REWIND_INGREDIENTS: {
break;
}
//: another primitive: 'ingredient' for random access
:(scenario ingredient)
def main [
f 1, 2

View File

@ -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,

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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")
{

View File

@ -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

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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")

View File

@ -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) {

View File

@ -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

View File

@ -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 [

View File

@ -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

View File

@ -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 [

View File

@ -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;
}

View File

@ -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");

View File

@ -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 [

View File

@ -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) {

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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);

View File

@ -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

View File

@ -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.
---

View File

@ -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 [
. .

View File

@ -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
]

View File

@ -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>&amp;</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>&amp;</tt>.
<p/>Support for higher-order recipes that can pass <a href='html/072recipe.cc.html'>recipes</a>
around like any other value.

View File

@ -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 [
. .