From e31cb59df97602de4c79851a2cdd5617139d3f70 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 19 Jan 2016 14:26:31 -0800 Subject: 2571 Reorganize layers in preparation for a better way to manage heap allocations without ever risking use-after-free errors. --- 038new.cc | 418 +++++++++++++++++++++++++++++++++++++++++++++++ 043new.cc | 418 ----------------------------------------------- 043space.cc | 237 +++++++++++++++++++++++++++ 044space.cc | 237 --------------------------- 044space_surround.cc | 57 +++++++ 045closure_name.cc | 145 ++++++++++++++++ 045space_surround.cc | 57 ------- 046closure_name.cc | 145 ---------------- 046global.cc | 83 ++++++++++ 047check_type_by_name.cc | 95 +++++++++++ 047global.cc | 83 ---------- 048check_type_by_name.cc | 95 ----------- 12 files changed, 1035 insertions(+), 1035 deletions(-) create mode 100644 038new.cc delete mode 100644 043new.cc create mode 100644 043space.cc delete mode 100644 044space.cc create mode 100644 044space_surround.cc create mode 100644 045closure_name.cc delete mode 100644 045space_surround.cc delete mode 100644 046closure_name.cc create mode 100644 046global.cc create mode 100644 047check_type_by_name.cc delete mode 100644 047global.cc delete mode 100644 048check_type_by_name.cc diff --git a/038new.cc b/038new.cc new file mode 100644 index 00000000..33902c3a --- /dev/null +++ b/038new.cc @@ -0,0 +1,418 @@ +//: A simple memory allocator to create space for new variables at runtime. + +:(scenarios run) +:(scenario new) +# call new two times with identical arguments; you should get back different results +recipe main [ + 1:address:number/raw <- new number:type + 2:address:number/raw <- new number:type + 3:boolean/raw <- equal 1:address:number/raw, 2:address:number/raw +] ++mem: storing 0 in location 3 + +:(before "End Globals") +long long int Memory_allocated_until = Reserved_for_tests; +long long int Initial_memory_per_routine = 100000; +:(before "End Setup") +Memory_allocated_until = Reserved_for_tests; +Initial_memory_per_routine = 100000; +:(before "End routine Fields") +long long int alloc, alloc_max; +:(before "End routine Constructor") +alloc = Memory_allocated_until; +Memory_allocated_until += Initial_memory_per_routine; +alloc_max = Memory_allocated_until; +trace(9999, "new") << "routine allocated memory from " << alloc << " to " << alloc_max << end(); + +//:: 'new' takes a weird 'type' as its first ingredient; don't error on it +:(before "End Mu Types Initialization") +put(Type_ordinal, "type", 0); + +//:: typecheck 'new' instructions +:(before "End Primitive Recipe Declarations") +NEW, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "new", NEW); +:(before "End Primitive Recipe Checks") +case NEW: { + if (inst.ingredients.empty() || SIZE(inst.ingredients) > 2) { + raise_error << maybe(get(Recipe, r).name) << "'new' requires one or two ingredients, but got " << inst.to_string() << '\n' << end(); + break; + } + // End NEW Check Special-cases + reagent type = inst.ingredients.at(0); + if (!is_mu_type_literal(type)) { + raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'new' should be a type, but got " << type.original_string << '\n' << end(); + break; + } + break; +} + +//:: translate 'new' to 'allocate' instructions that take a size instead of a type +:(after "Transform.push_back(check_instruction)") // check_instruction will guard against direct 'allocate' instructions below +Transform.push_back(transform_new_to_allocate); // idempotent + +:(code) +void transform_new_to_allocate(const recipe_ordinal r) { + trace(9991, "transform") << "--- convert 'new' to 'allocate' for recipe " << get(Recipe, r).name << end(); +//? cerr << "--- convert 'new' to 'allocate' for recipe " << get(Recipe, r).name << '\n'; + for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { + instruction& inst = get(Recipe, r).steps.at(i); + // Convert 'new' To 'allocate' + if (inst.name == "new") { + inst.operation = ALLOCATE; + string_tree* type_name = new string_tree(inst.ingredients.at(0).name); + // End Post-processing(type_name) When Converting 'new' + type_tree* type = new_type_tree(type_name); + inst.ingredients.at(0).set_value(size_of(type)); + trace(9992, "new") << "size of " << debug_string(type_name) << " is " << inst.ingredients.at(0).value << end(); + delete type; + delete type_name; + } + } +} + +//:: implement 'allocate' based on size + +:(before "End Primitive Recipe Declarations") +ALLOCATE, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "allocate", ALLOCATE); +:(before "End Primitive Recipe Implementations") +case ALLOCATE: { + // compute the space we need + long long int size = ingredients.at(0).at(0); + if (SIZE(ingredients) > 1) { + // array + trace(9999, "mem") << "array size is " << ingredients.at(1).at(0) << end(); + size = /*space for length*/1 + size*ingredients.at(1).at(0); + } +//? Total_alloc += size; +//? Num_alloc++; + // compute the region of memory to return + // really crappy at the moment + ensure_space(size); + const long long int result = Current_routine->alloc; + trace(9999, "mem") << "new alloc: " << result << end(); + // save result + products.resize(1); + products.at(0).push_back(result); + // initialize allocated space + for (long long int address = result; address < result+size; ++address) + put(Memory, address, 0); + // initialize array length + if (SIZE(current_instruction().ingredients) > 1) + put(Memory, result, ingredients.at(1).at(0)); + // bump + Current_routine->alloc += size; + // no support for reclaiming memory + assert(Current_routine->alloc <= Current_routine->alloc_max); + break; +} + +//:: ensure we never call 'allocate' directly; its types are not checked +:(before "End Primitive Recipe Checks") +case ALLOCATE: { + raise << "never call 'allocate' directly'; always use 'new'\n" << end(); + break; +} + +//:: ensure we never call 'new' without translating it (unless we add special-cases later) +:(before "End Primitive Recipe Implementations") +case NEW: { + raise << "no implementation for 'new'; why wasn't it translated to 'allocate'?\n" << end(); + break; +} + +//? :(before "End Globals") +//? long long int Total_alloc = 0; +//? long long int Num_alloc = 0; +//? long long int Total_free = 0; +//? long long int Num_free = 0; +//? :(before "End Setup") +//? Total_alloc = Num_alloc = Total_free = Num_free = 0; +//? :(before "End Teardown") +//? cerr << Total_alloc << "/" << Num_alloc +//? << " vs " << Total_free << "/" << Num_free << '\n'; +//? cerr << SIZE(Memory) << '\n'; + +:(code) +void ensure_space(long long int size) { + if (size > Initial_memory_per_routine) { + tb_shutdown(); + cerr << "can't allocate " << size << " locations, that's too much.\n"; + exit(0); + } + if (Current_routine->alloc + size > Current_routine->alloc_max) { + // waste the remaining space and create a new chunk + Current_routine->alloc = Memory_allocated_until; + Memory_allocated_until += Initial_memory_per_routine; + Current_routine->alloc_max = Memory_allocated_until; + trace(9999, "new") << "routine allocated memory from " << Current_routine->alloc << " to " << Current_routine->alloc_max << end(); + } +} + +:(scenario new_initializes) +% Memory_allocated_until = 10; +% put(Memory, Memory_allocated_until, 1); +recipe main [ + 1:address:number <- new number:type + 2:number <- copy *1:address:number +] ++mem: storing 0 in location 2 + +:(scenario new_array) +recipe main [ + 1:address:array:number/raw <- new number:type, 5 + 2:address:number/raw <- new number:type + 3:number/raw <- subtract 2:address:number/raw, 1:address:array:number/raw +] ++run: 1:address:array:number/raw <- new number:type, 5 ++mem: array size is 5 +# don't forget the extra location for array size ++mem: storing 6 in location 3 + +:(scenario new_empty_array) +recipe main [ + 1:address:array:number/raw <- new number:type, 0 + 2:address:number/raw <- new number:type + 3:number/raw <- subtract 2:address:number/raw, 1:address:array:number/raw +] ++run: 1:address:array:number/raw <- new number:type, 0 ++mem: array size is 0 ++mem: storing 1 in location 3 + +//: If a routine runs out of its initial allocation, it should allocate more. +:(scenario new_overflow) +% Initial_memory_per_routine = 2; +recipe main [ + 1:address:number/raw <- new number:type + 2:address:point/raw <- 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 + +//: We also provide a way to return memory, and to reuse reclaimed memory. +//: todo: custodians, etc. Following malloc/free is a temporary hack. + +:(scenario new_reclaim) +recipe main [ + 1:address:number <- new number:type + abandon 1:address:number + 2:address:number <- new number:type # must be same size as abandoned memory to reuse + 3:boolean <- equal 1:address:number, 2:address:number +] +# both allocations should have returned the same address ++mem: storing 1 in location 3 + +:(before "End Globals") +map Free_list; +:(before "End Setup") +Free_list.clear(); + +:(before "End Primitive Recipe Declarations") +ABANDON, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "abandon", ABANDON); +:(before "End Primitive Recipe Checks") +case ABANDON: { + if (SIZE(inst.ingredients) != 1) { + raise_error << maybe(get(Recipe, r).name) << "'abandon' requires one ingredient, but got '" << inst.to_string() << "'\n" << end(); + break; + } + reagent types = inst.ingredients.at(0); + canonize_type(types); + if (!types.type || types.type->value != get(Type_ordinal, "address")) { + raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'abandon' should be an address, but got " << inst.ingredients.at(0).original_string << '\n' << end(); + break; + } + break; +} +:(before "End Primitive Recipe Implementations") +case ABANDON: { + long long int address = ingredients.at(0).at(0); + reagent types = current_instruction().ingredients.at(0); + canonize(types); + // lookup_memory without drop_one_lookup { + types.set_value(get_or_insert(Memory, types.value)); + drop_address_from_type(types); + // } + abandon(address, size_of(types)); + break; +} + +:(code) +void abandon(long long int address, long long int size) { +//? Total_free += size; +//? Num_free++; +//? cerr << "abandon: " << size << '\n'; + // clear memory + for (long long int curr = address; curr < address+size; ++curr) + put(Memory, curr, 0); + // append existing free list to address + put(Memory, address, Free_list[size]); + Free_list[size] = address; +} + +:(before "ensure_space(size)" following "case ALLOCATE") +if (Free_list[size]) { + long long int result = Free_list[size]; + Free_list[size] = get_or_insert(Memory, result); + for (long long int curr = result+1; curr < result+size; ++curr) { + if (get_or_insert(Memory, curr) != 0) { + raise_error << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end(); + break; // always fatal + } + } + if (SIZE(current_instruction().ingredients) > 1) + put(Memory, result, ingredients.at(1).at(0)); + else + put(Memory, result, 0); + products.resize(1); + products.at(0).push_back(result); + break; +} + +:(scenario new_differing_size_no_reclaim) +recipe main [ + 1:address:number <- new number:type + abandon 1:address:number + 2:address:number <- new number:type, 2 # different size + 3:boolean <- equal 1:address:number, 2:address:number +] +# no reuse ++mem: storing 0 in location 3 + +:(scenario new_reclaim_array) +recipe main [ + 1:address:array:number <- new number:type, 2 + abandon 1:address:array:number + 2:address:array:number <- new number:type, 2 + 3:boolean <- equal 1:address:array:number, 2:address:array:number +] +# reuse ++mem: storing 1 in location 3 + +//:: Next, extend 'new' to handle a unicode string literal argument. + +:(scenario new_string) +recipe main [ + 1:address:array:character <- new [abc def] + 2:character <- index *1:address:array:character, 5 +] +# number code for 'e' ++mem: storing 101 in location 2 + +:(scenario new_string_handles_unicode) +recipe main [ + 1:address:array:character <- new [a«c] + 2:number <- length *1:address:array:character + 3:character <- index *1:address:array:character, 1 +] ++mem: storing 3 in location 2 +# unicode for '«' ++mem: storing 171 in location 3 + +:(before "End NEW Check Special-cases") +if (is_literal_string(inst.ingredients.at(0))) break; +:(before "Convert 'new' To 'allocate'") +if (inst.name == "new" && is_literal_string(inst.ingredients.at(0))) continue; +:(after "case NEW" following "Primitive Recipe Implementations") + if (is_literal_string(current_instruction().ingredients.at(0))) { + products.resize(1); + products.at(0).push_back(new_mu_string(current_instruction().ingredients.at(0).name)); + break; + } + +:(code) +long long int new_mu_string(const string& contents) { + // allocate an array just large enough for it + long long int string_length = unicode_length(contents); +//? Total_alloc += string_length+1; +//? Num_alloc++; + ensure_space(string_length+1); // don't forget the extra location for array size + // initialize string + long long int result = Current_routine->alloc; + put(Memory, Current_routine->alloc++, string_length); + long long int curr = 0; + const char* raw_contents = contents.c_str(); + for (long long int i = 0; i < string_length; ++i) { + uint32_t curr_character; + assert(curr < SIZE(contents)); + tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]); + put(Memory, Current_routine->alloc, curr_character); + curr += tb_utf8_char_length(raw_contents[curr]); + ++Current_routine->alloc; + } + // mu strings are not null-terminated in memory + return result; +} + +//: stash recognizes strings + +:(scenario stash_string) +recipe main [ + 1:address:array:character <- new [abc] + stash [foo:], 1:address:array:character +] ++app: foo: abc + +:(before "End print Special-cases(reagent r, data)") +if (is_mu_string(r)) { + assert(scalar(data)); + return read_mu_string(data.at(0))+' '; +} + +:(scenario unicode_string) +recipe main [ + 1:address:array:character <- new [♠] + stash [foo:], 1:address:array:character +] ++app: foo: ♠ + +:(scenario stash_space_after_string) +recipe main [ + 1:address:array:character <- new [abc] + stash 1:address:array:character [foo] +] ++app: abc foo + +//: Allocate more to routine when initializing a literal string +:(scenario new_string_overflow) +% Initial_memory_per_routine = 2; +recipe main [ + 1:address:number/raw <- new number:type + 2:address:array:character/raw <- new [a] # not enough room in initial page, if you take the array size into account +] ++new: routine allocated memory from 1000 to 1002 ++new: routine allocated memory from 1002 to 1004 + +//: helpers +:(code) +long long int unicode_length(const string& s) { + const char* in = s.c_str(); + long long int result = 0; + long long int curr = 0; + while (curr < SIZE(s)) { // carefully bounds-check on the string + // before accessing its raw pointer + ++result; + curr += tb_utf8_char_length(in[curr]); + } + return result; +} + +string read_mu_string(long long int address) { + long long int size = get_or_insert(Memory, address); + if (size == 0) return ""; + ostringstream tmp; + for (long long int curr = address+1; curr <= address+size; ++curr) { + tmp << to_unicode(static_cast(get_or_insert(Memory, curr))); + } + return tmp.str(); +} + +bool is_mu_type_literal(reagent r) { +//? if (!r.properties.empty()) +//? dump_property(r.properties.at(0).second, cerr); + return is_literal(r) && !r.properties.empty() && r.properties.at(0).second && r.properties.at(0).second->value == "type"; +} diff --git a/043new.cc b/043new.cc deleted file mode 100644 index b0942397..00000000 --- a/043new.cc +++ /dev/null @@ -1,418 +0,0 @@ -//: A simple memory allocator to create space for new variables at runtime. - -:(scenarios run) -:(scenario new) -# call new two times with identical arguments; you should get back different results -recipe main [ - 1:address:number/raw <- new number:type - 2:address:number/raw <- new number:type - 3:boolean/raw <- equal 1:address:number/raw, 2:address:number/raw -] -+mem: storing 0 in location 3 - -:(before "End Globals") -long long int Memory_allocated_until = Reserved_for_tests; -long long int Initial_memory_per_routine = 100000; -:(before "End Setup") -Memory_allocated_until = Reserved_for_tests; -Initial_memory_per_routine = 100000; -:(before "End routine Fields") -long long int alloc, alloc_max; -:(before "End routine Constructor") -alloc = Memory_allocated_until; -Memory_allocated_until += Initial_memory_per_routine; -alloc_max = Memory_allocated_until; -trace(9999, "new") << "routine allocated memory from " << alloc << " to " << alloc_max << end(); - -//:: 'new' takes a weird 'type' as its first ingredient; don't error on it -:(before "End Mu Types Initialization") -put(Type_ordinal, "type", 0); - -//:: typecheck 'new' instructions -:(before "End Primitive Recipe Declarations") -NEW, -:(before "End Primitive Recipe Numbers") -put(Recipe_ordinal, "new", NEW); -:(before "End Primitive Recipe Checks") -case NEW: { - if (inst.ingredients.empty() || SIZE(inst.ingredients) > 2) { - raise_error << maybe(get(Recipe, r).name) << "'new' requires one or two ingredients, but got " << inst.to_string() << '\n' << end(); - break; - } - // End NEW Check Special-cases - reagent type = inst.ingredients.at(0); - if (!is_mu_type_literal(type)) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'new' should be a type, but got " << type.original_string << '\n' << end(); - break; - } - break; -} - -//:: translate 'new' to 'allocate' instructions that take a size instead of a type -:(after "Transform.push_back(check_instruction)") // check_instruction will guard against direct 'allocate' instructions below -Transform.push_back(transform_new_to_allocate); // idempotent - -:(code) -void transform_new_to_allocate(const recipe_ordinal r) { - trace(9991, "transform") << "--- convert 'new' to 'allocate' for recipe " << get(Recipe, r).name << end(); -//? cerr << "--- convert 'new' to 'allocate' for recipe " << get(Recipe, r).name << '\n'; - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - instruction& inst = get(Recipe, r).steps.at(i); - // Convert 'new' To 'allocate' - if (inst.name == "new") { - inst.operation = ALLOCATE; - string_tree* type_name = new string_tree(inst.ingredients.at(0).name); - // End Post-processing(type_name) When Converting 'new' - type_tree* type = new_type_tree(type_name); - inst.ingredients.at(0).set_value(size_of(type)); - trace(9992, "new") << "size of " << debug_string(type_name) << " is " << inst.ingredients.at(0).value << end(); - delete type; - delete type_name; - } - } -} - -//:: implement 'allocate' based on size - -:(before "End Primitive Recipe Declarations") -ALLOCATE, -:(before "End Primitive Recipe Numbers") -put(Recipe_ordinal, "allocate", ALLOCATE); -:(before "End Primitive Recipe Implementations") -case ALLOCATE: { - // compute the space we need - long long int size = ingredients.at(0).at(0); - if (SIZE(ingredients) > 1) { - // array - trace(9999, "mem") << "array size is " << ingredients.at(1).at(0) << end(); - size = /*space for length*/1 + size*ingredients.at(1).at(0); - } -//? Total_alloc += size; -//? Num_alloc++; - // compute the region of memory to return - // really crappy at the moment - ensure_space(size); - const long long int result = Current_routine->alloc; - trace(9999, "mem") << "new alloc: " << result << end(); - // save result - products.resize(1); - products.at(0).push_back(result); - // initialize allocated space - for (long long int address = result; address < result+size; ++address) - put(Memory, address, 0); - // initialize array length - if (SIZE(current_instruction().ingredients) > 1) - put(Memory, result, ingredients.at(1).at(0)); - // bump - Current_routine->alloc += size; - // no support for reclaiming memory - assert(Current_routine->alloc <= Current_routine->alloc_max); - break; -} - -//:: ensure we never call 'allocate' directly; its types are not checked -:(before "End Primitive Recipe Checks") -case ALLOCATE: { - raise << "never call 'allocate' directly'; always use 'new'\n" << end(); - break; -} - -//:: ensure we never call 'new' without translating it (unless we add special-cases later) -:(before "End Primitive Recipe Implementations") -case NEW: { - raise << "no implementation for 'new'; why wasn't it translated to 'allocate'?\n" << end(); - break; -} - -//? :(before "End Globals") -//? long long int Total_alloc = 0; -//? long long int Num_alloc = 0; -//? long long int Total_free = 0; -//? long long int Num_free = 0; -//? :(before "End Setup") -//? Total_alloc = Num_alloc = Total_free = Num_free = 0; -//? :(before "End Teardown") -//? cerr << Total_alloc << "/" << Num_alloc -//? << " vs " << Total_free << "/" << Num_free << '\n'; -//? cerr << SIZE(Memory) << '\n'; - -:(code) -void ensure_space(long long int size) { - if (size > Initial_memory_per_routine) { - tb_shutdown(); - cerr << "can't allocate " << size << " locations, that's too much.\n"; - exit(0); - } - if (Current_routine->alloc + size > Current_routine->alloc_max) { - // waste the remaining space and create a new chunk - Current_routine->alloc = Memory_allocated_until; - Memory_allocated_until += Initial_memory_per_routine; - Current_routine->alloc_max = Memory_allocated_until; - trace(9999, "new") << "routine allocated memory from " << Current_routine->alloc << " to " << Current_routine->alloc_max << end(); - } -} - -:(scenario new_initializes) -% Memory_allocated_until = 10; -% put(Memory, Memory_allocated_until, 1); -recipe main [ - 1:address:number <- new number:type - 2:number <- copy *1:address:number -] -+mem: storing 0 in location 2 - -:(scenario new_array) -recipe main [ - 1:address:array:number/raw <- new number:type, 5 - 2:address:number/raw <- new number:type - 3:number/raw <- subtract 2:address:number/raw, 1:address:array:number/raw -] -+run: 1:address:array:number/raw <- new number:type, 5 -+mem: array size is 5 -# don't forget the extra location for array size -+mem: storing 6 in location 3 - -:(scenario new_empty_array) -recipe main [ - 1:address:array:number/raw <- new number:type, 0 - 2:address:number/raw <- new number:type - 3:number/raw <- subtract 2:address:number/raw, 1:address:array:number/raw -] -+run: 1:address:array:number/raw <- new number:type, 0 -+mem: array size is 0 -+mem: storing 1 in location 3 - -//: If a routine runs out of its initial allocation, it should allocate more. -:(scenario new_overflow) -% Initial_memory_per_routine = 2; -recipe main [ - 1:address:number/raw <- new number:type - 2:address:point/raw <- 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 - -//: We also provide a way to return memory, and to reuse reclaimed memory. -//: todo: custodians, etc. Following malloc/free is a temporary hack. - -:(scenario new_reclaim) -recipe main [ - 1:address:number <- new number:type - abandon 1:address:number - 2:address:number <- new number:type # must be same size as abandoned memory to reuse - 3:boolean <- equal 1:address:number, 2:address:number -] -# both allocations should have returned the same address -+mem: storing 1 in location 3 - -:(before "End Globals") -map Free_list; -:(before "End Setup") -Free_list.clear(); - -:(before "End Primitive Recipe Declarations") -ABANDON, -:(before "End Primitive Recipe Numbers") -put(Recipe_ordinal, "abandon", ABANDON); -:(before "End Primitive Recipe Checks") -case ABANDON: { - if (SIZE(inst.ingredients) != 1) { - raise_error << maybe(get(Recipe, r).name) << "'abandon' requires one ingredient, but got '" << inst.to_string() << "'\n" << end(); - break; - } - reagent types = inst.ingredients.at(0); - canonize_type(types); - if (!types.type || types.type->value != get(Type_ordinal, "address")) { - raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'abandon' should be an address, but got " << inst.ingredients.at(0).original_string << '\n' << end(); - break; - } - break; -} -:(before "End Primitive Recipe Implementations") -case ABANDON: { - long long int address = ingredients.at(0).at(0); - reagent types = current_instruction().ingredients.at(0); - canonize(types); - // lookup_memory without drop_one_lookup { - types.set_value(get_or_insert(Memory, types.value)); - drop_address_from_type(types); - // } - abandon(address, size_of(types)); - break; -} - -:(code) -void abandon(long long int address, long long int size) { -//? Total_free += size; -//? Num_free++; -//? cerr << "abandon: " << size << '\n'; - // clear memory - for (long long int curr = address; curr < address+size; ++curr) - put(Memory, curr, 0); - // append existing free list to address - put(Memory, address, Free_list[size]); - Free_list[size] = address; -} - -:(before "ensure_space(size)" following "case ALLOCATE") -if (Free_list[size]) { - long long int result = Free_list[size]; - Free_list[size] = get_or_insert(Memory, result); - for (long long int curr = result+1; curr < result+size; ++curr) { - if (get_or_insert(Memory, curr) != 0) { - raise_error << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end(); - break; // always fatal - } - } - if (SIZE(current_instruction().ingredients) > 1) - put(Memory, result, ingredients.at(1).at(0)); - else - put(Memory, result, 0); - products.resize(1); - products.at(0).push_back(result); - break; -} - -:(scenario new_differing_size_no_reclaim) -recipe main [ - 1:address:number <- new number:type - abandon 1:address:number - 2:address:number <- new number:type, 2 # different size - 3:boolean <- equal 1:address:number, 2:address:number -] -# no reuse -+mem: storing 0 in location 3 - -:(scenario new_reclaim_array) -recipe main [ - 1:address:array:number <- new number:type, 2 - abandon 1:address:array:number - 2:address:array:number <- new number:type, 2 - 3:boolean <- equal 1:address:array:number, 2:address:array:number -] -# reuse -+mem: storing 1 in location 3 - -//:: Next, extend 'new' to handle a unicode string literal argument. - -:(scenario new_string) -recipe main [ - 1:address:array:character <- new [abc def] - 2:character <- index *1:address:array:character, 5 -] -# number code for 'e' -+mem: storing 101 in location 2 - -:(scenario new_string_handles_unicode) -recipe main [ - 1:address:array:character <- new [a«c] - 2:number <- length *1:address:array:character - 3:character <- index *1:address:array:character, 1 -] -+mem: storing 3 in location 2 -# unicode for '«' -+mem: storing 171 in location 3 - -:(before "End NEW Check Special-cases") -if (is_literal_string(inst.ingredients.at(0))) break; -:(before "Convert 'new' To 'allocate'") -if (inst.name == "new" && is_literal_string(inst.ingredients.at(0))) continue; -:(after "case NEW" following "Primitive Recipe Implementations") - if (is_literal_string(current_instruction().ingredients.at(0))) { - products.resize(1); - products.at(0).push_back(new_mu_string(current_instruction().ingredients.at(0).name)); - break; - } - -:(code) -long long int new_mu_string(const string& contents) { - // allocate an array just large enough for it - long long int string_length = unicode_length(contents); -//? Total_alloc += string_length+1; -//? Num_alloc++; - ensure_space(string_length+1); // don't forget the extra location for array size - // initialize string - long long int result = Current_routine->alloc; - put(Memory, Current_routine->alloc++, string_length); - long long int curr = 0; - const char* raw_contents = contents.c_str(); - for (long long int i = 0; i < string_length; ++i) { - uint32_t curr_character; - assert(curr < SIZE(contents)); - tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]); - put(Memory, Current_routine->alloc, curr_character); - curr += tb_utf8_char_length(raw_contents[curr]); - ++Current_routine->alloc; - } - // mu strings are not null-terminated in memory - return result; -} - -//: stash recognizes strings - -:(scenario stash_string) -recipe main [ - x:address:array:character <- new [abc] - stash [foo:], x:address:array:character -] -+app: foo: abc - -:(before "End print Special-cases(reagent r, data)") -if (is_mu_string(r)) { - assert(scalar(data)); - return read_mu_string(data.at(0))+' '; -} - -:(scenario unicode_string) -recipe main [ - x:address:array:character <- new [♠] - stash [foo:], x:address:array:character -] -+app: foo: ♠ - -:(scenario stash_space_after_string) -recipe main [ - 1:address:array:character <- new [abc] - stash 1:address:array:character [foo] -] -+app: abc foo - -//: Allocate more to routine when initializing a literal string -:(scenario new_string_overflow) -% Initial_memory_per_routine = 2; -recipe main [ - 1:address:number/raw <- new number:type - 2:address:array:character/raw <- new [a] # not enough room in initial page, if you take the array size into account -] -+new: routine allocated memory from 1000 to 1002 -+new: routine allocated memory from 1002 to 1004 - -//: helpers -:(code) -long long int unicode_length(const string& s) { - const char* in = s.c_str(); - long long int result = 0; - long long int curr = 0; - while (curr < SIZE(s)) { // carefully bounds-check on the string - // before accessing its raw pointer - ++result; - curr += tb_utf8_char_length(in[curr]); - } - return result; -} - -string read_mu_string(long long int address) { - long long int size = get_or_insert(Memory, address); - if (size == 0) return ""; - ostringstream tmp; - for (long long int curr = address+1; curr <= address+size; ++curr) { - tmp << to_unicode(static_cast(get_or_insert(Memory, curr))); - } - return tmp.str(); -} - -bool is_mu_type_literal(reagent r) { -//? if (!r.properties.empty()) -//? dump_property(r.properties.at(0).second, cerr); - return is_literal(r) && !r.properties.empty() && r.properties.at(0).second && r.properties.at(0).second->value == "type"; -} diff --git a/043space.cc b/043space.cc new file mode 100644 index 00000000..4f57ebd5 --- /dev/null +++ b/043space.cc @@ -0,0 +1,237 @@ +//: Spaces help isolate recipes from each other. You can create them at will, +//: and all addresses in arguments are implicitly based on the 'default-space' +//: (unless they have the /raw property) + +:(scenario set_default_space) +# if default-space is 10, and if an array of 5 locals lies from location 11 to 15 (inclusive), +# then location 0 is really location 11, location 1 is really location 12, and so on. +recipe main [ + 10:number <- copy 5 # pretend array; in practice we'll use new + default-space:address:array:location <- copy 10/unsafe + 1:number <- copy 23 +] ++mem: storing 23 in location 12 + +:(scenario lookup_sidesteps_default_space) +recipe main [ + # pretend pointer from outside + 3:number <- copy 34 + # pretend array + 1000:number <- copy 5 + # actual start of this recipe + default-space:address:array:location <- copy 1000/unsafe + 1:address:number <- copy 3/unsafe + 8:number/raw <- copy *1:address:number +] ++mem: storing 34 in location 8 + +//:: first disable name conversion for 'default-space' +:(scenario convert_names_passes_default_space) +% Hide_errors = true; +recipe main [ + default-space:number, x:number <- copy 0, 1 +] ++name: assign x 1 +-name: assign default-space 1 + +:(before "End Disqualified Reagents") +if (x.name == "default-space") + x.initialized = true; +:(before "End is_special_name Cases") +if (s == "default-space") return true; + +//:: now implement space support +:(before "End call Fields") +long long int default_space; +:(before "End call Constructor") +default_space = 0; + +:(before "End canonize(x) Special-cases") + absolutize(x); +:(code) +void absolutize(reagent& x) { + if (is_raw(x) || is_dummy(x)) return; + if (x.name == "default-space") return; + if (!x.initialized) { + raise_error << current_instruction().to_string() << ": reagent not initialized: " << x.original_string << '\n' << end(); + } + x.set_value(address(x.value, space_base(x))); + x.properties.push_back(pair("raw", NULL)); + assert(is_raw(x)); +} + +//:: fix 'get' + +:(scenario lookup_sidesteps_default_space_in_get) +recipe main [ + # pretend pointer to container from outside + 12:number <- copy 34 + 13:number <- copy 35 + # pretend array + 1000:number <- copy 5 + # actual start of this recipe + default-space:address:array:location <- copy 1000/unsafe + 1:address:point <- copy 12/unsafe + 9:number/raw <- get *1:address:point, 1:offset +] ++mem: storing 35 in location 9 + +:(after "reagent tmp" following "case GET:") +tmp.properties.push_back(pair("raw", NULL)); + +//:: fix 'index' + +:(scenario lookup_sidesteps_default_space_in_index) +recipe main [ + # pretend pointer to array from outside + 12:number <- copy 2 + 13:number <- copy 34 + 14:number <- copy 35 + # pretend array + 1000:number <- copy 5 + # actual start of this recipe + default-space:address:array:location <- copy 1000/unsafe + 1:address:array:number <- copy 12/unsafe + 9:number/raw <- index *1:address:array:number, 1 +] ++mem: storing 35 in location 9 + +:(after "reagent tmp" following "case INDEX:") +tmp.properties.push_back(pair("raw", NULL)); + +//:: convenience operation to automatically deduce the amount of space to +//:: allocate in a default space with names + +:(scenario new_default_space) +recipe main [ + new-default-space + x:number <- copy 0 + y:number <- copy 3 +] +# allocate space for x and y, as well as the chaining slot at 0 ++mem: array size is 3 + +:(before "End Disqualified Reagents") +if (x.name == "number-of-locals") + x.initialized = true; +:(before "End is_special_name Cases") +if (s == "number-of-locals") return true; + +:(before "End Rewrite Instruction(curr, recipe result)") +// rewrite `new-default-space` to +// `default-space:address:array:location <- new location:type, number-of-locals:literal` +// where N is Name[recipe][""] +if (curr.name == "new-default-space") { + rewrite_default_space_instruction(curr); +} +:(after "vector read_memory(reagent x)") + if (x.name == "number-of-locals") { + vector result; + result.push_back(Name[get(Recipe_ordinal, current_recipe_name())][""]); + if (result.back() == 0) + raise_error << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names?\n" << end(); + return result; + } +:(after "void write_memory(reagent x, vector data)") + if (x.name == "number-of-locals") { + raise_error << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end(); + return; + } + +//:: a little hook to automatically reclaim the default-space when returning +//:: from a recipe + +:(scenario local_scope) +recipe main [ + 1:address <- foo + 2:address <- foo + 3:boolean <- equal 1:address, 2:address +] +recipe foo [ + local-scope + x:number <- copy 34 + reply default-space:address:array:location +] +# both calls to foo should have received the same default-space ++mem: storing 1 in location 3 + +:(after "Falling Through End Of Recipe") +try_reclaim_locals(); +:(after "Starting Reply") +try_reclaim_locals(); + +//: now 'local-scope' is identical to 'new-default-space' except that we'll +//: reclaim the default-space when the routine exits +:(before "End Rewrite Instruction(curr, recipe result)") +if (curr.name == "local-scope") { + rewrite_default_space_instruction(curr); +} + +:(code) +void try_reclaim_locals() { + // only reclaim routines starting with 'local-scope' + const recipe_ordinal r = get(Recipe_ordinal, current_recipe_name()); + if (get(Recipe, r).steps.empty()) return; + const instruction& inst = get(Recipe, r).steps.at(0); + if (inst.old_name != "local-scope") return; + abandon(current_call().default_space, + /*array length*/1+/*number-of-locals*/Name[r][""]); +} + +void rewrite_default_space_instruction(instruction& curr) { + if (!curr.ingredients.empty()) + raise_error << curr.to_string() << " can't take any ingredients\n" << end(); + curr.name = "new"; + curr.ingredients.push_back(reagent("location:type")); + curr.ingredients.push_back(reagent("number-of-locals:literal")); + if (!curr.products.empty()) + raise_error << "new-default-space can't take any results\n" << end(); + curr.products.push_back(reagent("default-space:address:array:location")); +} + +//:: helpers + +:(code) +long long int space_base(const reagent& x) { + // temporary stub; will be replaced in a later layer + return current_call().default_space; +} + +long long int address(long long int offset, long long int base) { + if (base == 0) return offset; // raw + if (offset >= static_cast(get_or_insert(Memory, base))) { + // todo: test + raise_error << "location " << offset << " is out of bounds " << no_scientific(get_or_insert(Memory, base)) << " at " << base << '\n' << end(); + } + return base+1 + offset; +} + +:(after "void write_memory(reagent x, vector data)") + if (x.name == "default-space") { + if (!scalar(data) + || !x.type + || x.type->value != get(Type_ordinal, "address") + || !x.type->right + || x.type->right->value != get(Type_ordinal, "array") + || !x.type->right->right + || x.type->right->right->value != get(Type_ordinal, "location") + || x.type->right->right->right) { + raise_error << maybe(current_recipe_name()) << "'default-space' should be of type address:array:location, but tried to write " << to_string(data) << '\n' << end(); + } + current_call().default_space = data.at(0); + return; + } + +:(scenario get_default_space) +recipe main [ + default-space:address:array:location <- copy 10/unsafe + 1:address:array:location/raw <- copy default-space:address:array:location +] ++mem: storing 10 in location 1 + +:(after "vector read_memory(reagent x)") + if (x.name == "default-space") { + vector result; + result.push_back(current_call().default_space); + return result; + } diff --git a/044space.cc b/044space.cc deleted file mode 100644 index 4f57ebd5..00000000 --- a/044space.cc +++ /dev/null @@ -1,237 +0,0 @@ -//: Spaces help isolate recipes from each other. You can create them at will, -//: and all addresses in arguments are implicitly based on the 'default-space' -//: (unless they have the /raw property) - -:(scenario set_default_space) -# if default-space is 10, and if an array of 5 locals lies from location 11 to 15 (inclusive), -# then location 0 is really location 11, location 1 is really location 12, and so on. -recipe main [ - 10:number <- copy 5 # pretend array; in practice we'll use new - default-space:address:array:location <- copy 10/unsafe - 1:number <- copy 23 -] -+mem: storing 23 in location 12 - -:(scenario lookup_sidesteps_default_space) -recipe main [ - # pretend pointer from outside - 3:number <- copy 34 - # pretend array - 1000:number <- copy 5 - # actual start of this recipe - default-space:address:array:location <- copy 1000/unsafe - 1:address:number <- copy 3/unsafe - 8:number/raw <- copy *1:address:number -] -+mem: storing 34 in location 8 - -//:: first disable name conversion for 'default-space' -:(scenario convert_names_passes_default_space) -% Hide_errors = true; -recipe main [ - default-space:number, x:number <- copy 0, 1 -] -+name: assign x 1 --name: assign default-space 1 - -:(before "End Disqualified Reagents") -if (x.name == "default-space") - x.initialized = true; -:(before "End is_special_name Cases") -if (s == "default-space") return true; - -//:: now implement space support -:(before "End call Fields") -long long int default_space; -:(before "End call Constructor") -default_space = 0; - -:(before "End canonize(x) Special-cases") - absolutize(x); -:(code) -void absolutize(reagent& x) { - if (is_raw(x) || is_dummy(x)) return; - if (x.name == "default-space") return; - if (!x.initialized) { - raise_error << current_instruction().to_string() << ": reagent not initialized: " << x.original_string << '\n' << end(); - } - x.set_value(address(x.value, space_base(x))); - x.properties.push_back(pair("raw", NULL)); - assert(is_raw(x)); -} - -//:: fix 'get' - -:(scenario lookup_sidesteps_default_space_in_get) -recipe main [ - # pretend pointer to container from outside - 12:number <- copy 34 - 13:number <- copy 35 - # pretend array - 1000:number <- copy 5 - # actual start of this recipe - default-space:address:array:location <- copy 1000/unsafe - 1:address:point <- copy 12/unsafe - 9:number/raw <- get *1:address:point, 1:offset -] -+mem: storing 35 in location 9 - -:(after "reagent tmp" following "case GET:") -tmp.properties.push_back(pair("raw", NULL)); - -//:: fix 'index' - -:(scenario lookup_sidesteps_default_space_in_index) -recipe main [ - # pretend pointer to array from outside - 12:number <- copy 2 - 13:number <- copy 34 - 14:number <- copy 35 - # pretend array - 1000:number <- copy 5 - # actual start of this recipe - default-space:address:array:location <- copy 1000/unsafe - 1:address:array:number <- copy 12/unsafe - 9:number/raw <- index *1:address:array:number, 1 -] -+mem: storing 35 in location 9 - -:(after "reagent tmp" following "case INDEX:") -tmp.properties.push_back(pair("raw", NULL)); - -//:: convenience operation to automatically deduce the amount of space to -//:: allocate in a default space with names - -:(scenario new_default_space) -recipe main [ - new-default-space - x:number <- copy 0 - y:number <- copy 3 -] -# allocate space for x and y, as well as the chaining slot at 0 -+mem: array size is 3 - -:(before "End Disqualified Reagents") -if (x.name == "number-of-locals") - x.initialized = true; -:(before "End is_special_name Cases") -if (s == "number-of-locals") return true; - -:(before "End Rewrite Instruction(curr, recipe result)") -// rewrite `new-default-space` to -// `default-space:address:array:location <- new location:type, number-of-locals:literal` -// where N is Name[recipe][""] -if (curr.name == "new-default-space") { - rewrite_default_space_instruction(curr); -} -:(after "vector read_memory(reagent x)") - if (x.name == "number-of-locals") { - vector result; - result.push_back(Name[get(Recipe_ordinal, current_recipe_name())][""]); - if (result.back() == 0) - raise_error << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names?\n" << end(); - return result; - } -:(after "void write_memory(reagent x, vector data)") - if (x.name == "number-of-locals") { - raise_error << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end(); - return; - } - -//:: a little hook to automatically reclaim the default-space when returning -//:: from a recipe - -:(scenario local_scope) -recipe main [ - 1:address <- foo - 2:address <- foo - 3:boolean <- equal 1:address, 2:address -] -recipe foo [ - local-scope - x:number <- copy 34 - reply default-space:address:array:location -] -# both calls to foo should have received the same default-space -+mem: storing 1 in location 3 - -:(after "Falling Through End Of Recipe") -try_reclaim_locals(); -:(after "Starting Reply") -try_reclaim_locals(); - -//: now 'local-scope' is identical to 'new-default-space' except that we'll -//: reclaim the default-space when the routine exits -:(before "End Rewrite Instruction(curr, recipe result)") -if (curr.name == "local-scope") { - rewrite_default_space_instruction(curr); -} - -:(code) -void try_reclaim_locals() { - // only reclaim routines starting with 'local-scope' - const recipe_ordinal r = get(Recipe_ordinal, current_recipe_name()); - if (get(Recipe, r).steps.empty()) return; - const instruction& inst = get(Recipe, r).steps.at(0); - if (inst.old_name != "local-scope") return; - abandon(current_call().default_space, - /*array length*/1+/*number-of-locals*/Name[r][""]); -} - -void rewrite_default_space_instruction(instruction& curr) { - if (!curr.ingredients.empty()) - raise_error << curr.to_string() << " can't take any ingredients\n" << end(); - curr.name = "new"; - curr.ingredients.push_back(reagent("location:type")); - curr.ingredients.push_back(reagent("number-of-locals:literal")); - if (!curr.products.empty()) - raise_error << "new-default-space can't take any results\n" << end(); - curr.products.push_back(reagent("default-space:address:array:location")); -} - -//:: helpers - -:(code) -long long int space_base(const reagent& x) { - // temporary stub; will be replaced in a later layer - return current_call().default_space; -} - -long long int address(long long int offset, long long int base) { - if (base == 0) return offset; // raw - if (offset >= static_cast(get_or_insert(Memory, base))) { - // todo: test - raise_error << "location " << offset << " is out of bounds " << no_scientific(get_or_insert(Memory, base)) << " at " << base << '\n' << end(); - } - return base+1 + offset; -} - -:(after "void write_memory(reagent x, vector data)") - if (x.name == "default-space") { - if (!scalar(data) - || !x.type - || x.type->value != get(Type_ordinal, "address") - || !x.type->right - || x.type->right->value != get(Type_ordinal, "array") - || !x.type->right->right - || x.type->right->right->value != get(Type_ordinal, "location") - || x.type->right->right->right) { - raise_error << maybe(current_recipe_name()) << "'default-space' should be of type address:array:location, but tried to write " << to_string(data) << '\n' << end(); - } - current_call().default_space = data.at(0); - return; - } - -:(scenario get_default_space) -recipe main [ - default-space:address:array:location <- copy 10/unsafe - 1:address:array:location/raw <- copy default-space:address:array:location -] -+mem: storing 10 in location 1 - -:(after "vector read_memory(reagent x)") - if (x.name == "default-space") { - vector result; - result.push_back(current_call().default_space); - return result; - } diff --git a/044space_surround.cc b/044space_surround.cc new file mode 100644 index 00000000..821066cc --- /dev/null +++ b/044space_surround.cc @@ -0,0 +1,57 @@ +//: So far you can have global variables by not setting default-space, and +//: local variables by setting default-space. You can isolate variables +//: between those extremes by creating 'surrounding' spaces. +//: +//: (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. +recipe main [ + 10:number <- copy 5 # pretend array + 20:number <- copy 5 # pretend array + default-space:address:array:location <- copy 10/unsafe + 0:address:array:location/names:dummy <- copy 20/unsafe # later layers will explain the /names: property + 1:number <- copy 32 + 1:number/space:1 <- copy 33 +] +recipe dummy [ +] +# chain space ++mem: storing 20 in location 11 +# store to default-space ++mem: storing 32 in location 12 +# store to chained space ++mem: storing 33 in location 22 + +//: If you think of a space as a collection of variables with a common +//: lifetime, surrounding allows managing shorter lifetimes inside a longer +//: one. + +:(replace{} "long long int space_base(const reagent& x)") +long long int space_base(const reagent& x) { + return space_base(x, space_index(x), current_call().default_space); +} + +long long int space_base(const reagent& x, long long int space_index, long long int base) { + if (space_index == 0) { + return base; + } + long long int result = space_base(x, space_index-1, get_or_insert(Memory, base+1)); + return result; +} + +long long int space_index(const reagent& x) { + for (long long int i = /*skip name:type*/1; i < SIZE(x.properties); ++i) { + if (x.properties.at(i).first == "space") { + if (!x.properties.at(i).second || x.properties.at(i).second->right) + raise_error << maybe(current_recipe_name()) << "/space metadata should take exactly one value in " << x.original_string << '\n' << end(); + return to_integer(x.properties.at(i).second->value); + } + } + return 0; +} + +:(scenario permit_space_as_variable_name) +recipe main [ + space:number <- copy 0 +] diff --git a/045closure_name.cc b/045closure_name.cc new file mode 100644 index 00000000..682c163f --- /dev/null +++ b/045closure_name.cc @@ -0,0 +1,145 @@ +//: Writing to a literal (not computed) address of 0 in a recipe chains two +//: spaces together. When a variable has a property of /space:1, it looks up +//: the variable in the chained/surrounding space. /space:2 looks up the +//: surrounding space of the surrounding space, etc. + +:(scenario closure) +recipe main [ + default-space:address:array:location <- new location:type, 30 + 1:address:array:location/names:new-counter <- new-counter + 2:number/raw <- increment-counter 1:address:array:location/names:new-counter + 3:number/raw <- increment-counter 1:address:array:location/names:new-counter +] + +recipe new-counter [ + default-space:address:array:location <- new location:type, 30 + x:number <- copy 23 + y:number <- copy 3 # variable that will be incremented + reply default-space:address:array:location +] + +recipe increment-counter [ + default-space:address:array:location <- new location:type, 30 + 0:address:array:location/names:new-counter <- next-ingredient # outer space must be created by 'new-counter' above + y:number/space:1 <- add y:number/space:1, 1 # increment + y:number <- copy 234 # dummy + reply y:number/space:1 +] + ++name: lexically surrounding space for recipe increment-counter comes from new-counter ++mem: storing 5 in location 3 + +//: To make this work, compute the recipe that provides names for the +//: surrounding space of each recipe. + +:(before "End Globals") +map Surrounding_space; + +:(before "Transform.push_back(transform_names)") +Transform.push_back(collect_surrounding_spaces); // idempotent + +:(code) +void collect_surrounding_spaces(const recipe_ordinal r) { + trace(9991, "transform") << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << end(); +//? cerr << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << '\n'; + for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { + const instruction& inst = get(Recipe, r).steps.at(i); + if (inst.is_label) continue; + for (long long int j = 0; j < SIZE(inst.products); ++j) { + if (is_literal(inst.products.at(j))) continue; + if (inst.products.at(j).name != "0") continue; + type_tree* type = inst.products.at(j).type; + if (!type + || type->value != get(Type_ordinal, "address") + || !type->right + || type->right->value != get(Type_ordinal, "array") + || !type->right->right + || type->right->right->value != get(Type_ordinal, "location") + || type->right->right->right) { + raise_error << "slot 0 should always have type address:array:location, but is " << inst.products.at(j).to_string() << '\n' << end(); + continue; + } + string_tree* s = property(inst.products.at(j), "names"); + if (!s) { + raise_error << "slot 0 requires a /names property in recipe " << get(Recipe, r).name << end(); + continue; + } + if (s->right) raise_error << "slot 0 should have a single value in /names, but got " << inst.products.at(j).to_string() << '\n' << end(); + const string& surrounding_recipe_name = s->value; + if (contains_key(Surrounding_space, r) + && Surrounding_space[r] != get(Recipe_ordinal, surrounding_recipe_name)) { + raise_error << "recipe " << get(Recipe, r).name << " can have only one 'surrounding' recipe but has " << get(Recipe, Surrounding_space[r]).name << " and " << surrounding_recipe_name << '\n' << end(); + continue; + } + trace(9993, "name") << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << end(); +//? cerr << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << '\n'; + if (!contains_key(Recipe_ordinal, surrounding_recipe_name)) { + raise << "can't find recipe providing surrounding space for " << get(Recipe, r).name << ": " << surrounding_recipe_name << '\n' << end(); + continue; + } + Surrounding_space[r] = get(Recipe_ordinal, surrounding_recipe_name); + } + } +} + +//: Once surrounding spaces are available, transform_names uses them to handle +//: /space properties. + +:(replace{} "long long int lookup_name(const reagent& r, const recipe_ordinal default_recipe)") +long long int lookup_name(const reagent& x, const recipe_ordinal default_recipe) { + if (!has_property(x, "space")) { + if (Name[default_recipe].empty()) raise_error << "name not found: " << x.name << '\n' << end(); + return Name[default_recipe][x.name]; + } + string_tree* p = property(x, "space"); + if (!p || p->right) raise_error << "/space property should have exactly one (non-negative integer) value\n" << end(); + long long int n = to_integer(p->value); + assert(n >= 0); + recipe_ordinal surrounding_recipe = lookup_surrounding_recipe(default_recipe, n); + set done; + vector path; + return lookup_name(x, surrounding_recipe, done, path); +} + +// If the recipe we need to lookup this name in doesn't have names done yet, +// recursively call transform_names on it. +long long int lookup_name(const reagent& x, const recipe_ordinal r, set& done, vector& path) { + if (!Name[r].empty()) return Name[r][x.name]; + if (contains_key(done, r)) { + raise_error << "can't compute address of " << x.to_string() << " because " << end(); + for (long long int i = 1; i < SIZE(path); ++i) { + raise_error << path.at(i-1) << " requires computing names of " << path.at(i) << '\n' << end(); + } + raise_error << path.at(SIZE(path)-1) << " requires computing names of " << r << "..ad infinitum\n" << end(); + return 0; + } + done.insert(r); + path.push_back(r); + transform_names(r); // Not passing 'done' through. Might this somehow cause an infinite loop? + assert(!Name[r].empty()); + return Name[r][x.name]; +} + +recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, long long int n) { + if (n == 0) return r; + if (!contains_key(Surrounding_space, r)) { + raise_error << "don't know surrounding recipe of " << get(Recipe, r).name << '\n' << end(); + return 0; + } + assert(Surrounding_space[r]); + return lookup_surrounding_recipe(Surrounding_space[r], n-1); +} + +//: weaken use-before-set detection just a tad +:(replace{} "bool already_transformed(const reagent& r, const map& names)") +bool already_transformed(const reagent& r, const map& names) { + if (has_property(r, "space")) { + string_tree* p = property(r, "space"); + if (!p || p->right) { + raise_error << "/space property should have exactly one (non-negative integer) value in " << r.original_string << '\n' << end(); + return false; + } + if (p->value != "0") return true; + } + return contains_key(names, r.name); +} diff --git a/045space_surround.cc b/045space_surround.cc deleted file mode 100644 index 821066cc..00000000 --- a/045space_surround.cc +++ /dev/null @@ -1,57 +0,0 @@ -//: So far you can have global variables by not setting default-space, and -//: local variables by setting default-space. You can isolate variables -//: between those extremes by creating 'surrounding' spaces. -//: -//: (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. -recipe main [ - 10:number <- copy 5 # pretend array - 20:number <- copy 5 # pretend array - default-space:address:array:location <- copy 10/unsafe - 0:address:array:location/names:dummy <- copy 20/unsafe # later layers will explain the /names: property - 1:number <- copy 32 - 1:number/space:1 <- copy 33 -] -recipe dummy [ -] -# chain space -+mem: storing 20 in location 11 -# store to default-space -+mem: storing 32 in location 12 -# store to chained space -+mem: storing 33 in location 22 - -//: If you think of a space as a collection of variables with a common -//: lifetime, surrounding allows managing shorter lifetimes inside a longer -//: one. - -:(replace{} "long long int space_base(const reagent& x)") -long long int space_base(const reagent& x) { - return space_base(x, space_index(x), current_call().default_space); -} - -long long int space_base(const reagent& x, long long int space_index, long long int base) { - if (space_index == 0) { - return base; - } - long long int result = space_base(x, space_index-1, get_or_insert(Memory, base+1)); - return result; -} - -long long int space_index(const reagent& x) { - for (long long int i = /*skip name:type*/1; i < SIZE(x.properties); ++i) { - if (x.properties.at(i).first == "space") { - if (!x.properties.at(i).second || x.properties.at(i).second->right) - raise_error << maybe(current_recipe_name()) << "/space metadata should take exactly one value in " << x.original_string << '\n' << end(); - return to_integer(x.properties.at(i).second->value); - } - } - return 0; -} - -:(scenario permit_space_as_variable_name) -recipe main [ - space:number <- copy 0 -] diff --git a/046closure_name.cc b/046closure_name.cc deleted file mode 100644 index 682c163f..00000000 --- a/046closure_name.cc +++ /dev/null @@ -1,145 +0,0 @@ -//: Writing to a literal (not computed) address of 0 in a recipe chains two -//: spaces together. When a variable has a property of /space:1, it looks up -//: the variable in the chained/surrounding space. /space:2 looks up the -//: surrounding space of the surrounding space, etc. - -:(scenario closure) -recipe main [ - default-space:address:array:location <- new location:type, 30 - 1:address:array:location/names:new-counter <- new-counter - 2:number/raw <- increment-counter 1:address:array:location/names:new-counter - 3:number/raw <- increment-counter 1:address:array:location/names:new-counter -] - -recipe new-counter [ - default-space:address:array:location <- new location:type, 30 - x:number <- copy 23 - y:number <- copy 3 # variable that will be incremented - reply default-space:address:array:location -] - -recipe increment-counter [ - default-space:address:array:location <- new location:type, 30 - 0:address:array:location/names:new-counter <- next-ingredient # outer space must be created by 'new-counter' above - y:number/space:1 <- add y:number/space:1, 1 # increment - y:number <- copy 234 # dummy - reply y:number/space:1 -] - -+name: lexically surrounding space for recipe increment-counter comes from new-counter -+mem: storing 5 in location 3 - -//: To make this work, compute the recipe that provides names for the -//: surrounding space of each recipe. - -:(before "End Globals") -map Surrounding_space; - -:(before "Transform.push_back(transform_names)") -Transform.push_back(collect_surrounding_spaces); // idempotent - -:(code) -void collect_surrounding_spaces(const recipe_ordinal r) { - trace(9991, "transform") << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << end(); -//? cerr << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << '\n'; - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - const instruction& inst = get(Recipe, r).steps.at(i); - if (inst.is_label) continue; - for (long long int j = 0; j < SIZE(inst.products); ++j) { - if (is_literal(inst.products.at(j))) continue; - if (inst.products.at(j).name != "0") continue; - type_tree* type = inst.products.at(j).type; - if (!type - || type->value != get(Type_ordinal, "address") - || !type->right - || type->right->value != get(Type_ordinal, "array") - || !type->right->right - || type->right->right->value != get(Type_ordinal, "location") - || type->right->right->right) { - raise_error << "slot 0 should always have type address:array:location, but is " << inst.products.at(j).to_string() << '\n' << end(); - continue; - } - string_tree* s = property(inst.products.at(j), "names"); - if (!s) { - raise_error << "slot 0 requires a /names property in recipe " << get(Recipe, r).name << end(); - continue; - } - if (s->right) raise_error << "slot 0 should have a single value in /names, but got " << inst.products.at(j).to_string() << '\n' << end(); - const string& surrounding_recipe_name = s->value; - if (contains_key(Surrounding_space, r) - && Surrounding_space[r] != get(Recipe_ordinal, surrounding_recipe_name)) { - raise_error << "recipe " << get(Recipe, r).name << " can have only one 'surrounding' recipe but has " << get(Recipe, Surrounding_space[r]).name << " and " << surrounding_recipe_name << '\n' << end(); - continue; - } - trace(9993, "name") << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << end(); -//? cerr << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << '\n'; - if (!contains_key(Recipe_ordinal, surrounding_recipe_name)) { - raise << "can't find recipe providing surrounding space for " << get(Recipe, r).name << ": " << surrounding_recipe_name << '\n' << end(); - continue; - } - Surrounding_space[r] = get(Recipe_ordinal, surrounding_recipe_name); - } - } -} - -//: Once surrounding spaces are available, transform_names uses them to handle -//: /space properties. - -:(replace{} "long long int lookup_name(const reagent& r, const recipe_ordinal default_recipe)") -long long int lookup_name(const reagent& x, const recipe_ordinal default_recipe) { - if (!has_property(x, "space")) { - if (Name[default_recipe].empty()) raise_error << "name not found: " << x.name << '\n' << end(); - return Name[default_recipe][x.name]; - } - string_tree* p = property(x, "space"); - if (!p || p->right) raise_error << "/space property should have exactly one (non-negative integer) value\n" << end(); - long long int n = to_integer(p->value); - assert(n >= 0); - recipe_ordinal surrounding_recipe = lookup_surrounding_recipe(default_recipe, n); - set done; - vector path; - return lookup_name(x, surrounding_recipe, done, path); -} - -// If the recipe we need to lookup this name in doesn't have names done yet, -// recursively call transform_names on it. -long long int lookup_name(const reagent& x, const recipe_ordinal r, set& done, vector& path) { - if (!Name[r].empty()) return Name[r][x.name]; - if (contains_key(done, r)) { - raise_error << "can't compute address of " << x.to_string() << " because " << end(); - for (long long int i = 1; i < SIZE(path); ++i) { - raise_error << path.at(i-1) << " requires computing names of " << path.at(i) << '\n' << end(); - } - raise_error << path.at(SIZE(path)-1) << " requires computing names of " << r << "..ad infinitum\n" << end(); - return 0; - } - done.insert(r); - path.push_back(r); - transform_names(r); // Not passing 'done' through. Might this somehow cause an infinite loop? - assert(!Name[r].empty()); - return Name[r][x.name]; -} - -recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, long long int n) { - if (n == 0) return r; - if (!contains_key(Surrounding_space, r)) { - raise_error << "don't know surrounding recipe of " << get(Recipe, r).name << '\n' << end(); - return 0; - } - assert(Surrounding_space[r]); - return lookup_surrounding_recipe(Surrounding_space[r], n-1); -} - -//: weaken use-before-set detection just a tad -:(replace{} "bool already_transformed(const reagent& r, const map& names)") -bool already_transformed(const reagent& r, const map& names) { - if (has_property(r, "space")) { - string_tree* p = property(r, "space"); - if (!p || p->right) { - raise_error << "/space property should have exactly one (non-negative integer) value in " << r.original_string << '\n' << end(); - return false; - } - if (p->value != "0") return true; - } - return contains_key(names, r.name); -} diff --git a/046global.cc b/046global.cc new file mode 100644 index 00000000..bd433af7 --- /dev/null +++ b/046global.cc @@ -0,0 +1,83 @@ +// So far we have local variables, and we can nest local variables of short +// lifetimes inside longer ones. Now let's support 'global' variables that +// last for the life of a routine. If we create multiple routines they won't +// have access to each other's globals. + +:(scenario global_space) +recipe main [ + # pretend arrays; in practice we'll use new + 10:number <- copy 5 + 20:number <- copy 5 + # actual start of this recipe + global-space:address:array:location <- copy 20/unsafe + default-space:address:array:location <- copy 10/unsafe + 1:number <- copy 23 + 1:number/space:global <- copy 24 +] ++mem: storing 23 in location 12 ++mem: storing 24 in location 22 + +//: to support it, create another special variable called global space +:(before "End Disqualified Reagents") +if (x.name == "global-space") + x.initialized = true; +:(before "End is_special_name Cases") +if (s == "global-space") return true; + +//: writes to this variable go to a field in the current routine +:(before "End routine Fields") +long long int global_space; +:(before "End routine Constructor") +global_space = 0; +:(after "void write_memory(reagent x, vector data)") + if (x.name == "global-space") { + if (!scalar(data) + || !x.type + || x.type->value != get(Type_ordinal, "address") + || !x.type->right + || x.type->right->value != get(Type_ordinal, "array") + || !x.type->right->right + || x.type->right->right->value != get(Type_ordinal, "location") + || x.type->right->right->right) { + raise_error << maybe(current_recipe_name()) << "'global-space' should be of type address:array:location, but tried to write " << to_string(data) << '\n' << end(); + } + if (Current_routine->global_space) + raise_error << "routine already has a global-space; you can't over-write your globals" << end(); + Current_routine->global_space = data.at(0); + return; + } + +//: now marking variables as /space:global looks them up inside this field +:(after "long long int space_base(const reagent& x)") + if (is_global(x)) { + if (!Current_routine->global_space) + raise_error << "routine has no global space\n" << end(); + return Current_routine->global_space; + } + +//: for now let's not bother giving global variables names. +//: don't want to make them too comfortable to use. + +:(scenario global_space_with_names) +% Hide_errors = true; +recipe main [ + global-space:address:array:location <- new location:type, 10 + x:number <- copy 23 + 1:number/space:global <- copy 24 +] +# don't complain about mixing numeric addresses and names +$error: 0 + +:(after "bool is_numeric_location(const reagent& x)") + if (is_global(x)) return false; + +//: helpers + +:(code) +bool is_global(const reagent& x) { + for (long long int i = /*skip name:type*/1; i < SIZE(x.properties); ++i) { + if (x.properties.at(i).first == "space") + return x.properties.at(i).second && x.properties.at(i).second->value == "global"; + } + return false; +} diff --git a/047check_type_by_name.cc b/047check_type_by_name.cc new file mode 100644 index 00000000..4842e15c --- /dev/null +++ b/047check_type_by_name.cc @@ -0,0 +1,95 @@ +//: Some simple sanity checks for types, and also attempts to guess them where +//: they aren't provided. +//: +//: You still have to provide the full type the first time you mention a +//: variable in a recipe. You have to explicitly name :offset and :variant +//: every single time. You can't use the same name with multiple types in a +//: single recipe. + +:(scenario transform_fails_on_reusing_name_with_different_type) +% Hide_errors = true; +recipe main [ + x:number <- copy 1 + x:boolean <- copy 1 +] ++error: main: x used with multiple types + +:(after "Begin Instruction Modifying Transforms") +Transform.push_back(check_or_set_types_by_name); // idempotent + +:(code) +void check_or_set_types_by_name(const recipe_ordinal r) { + trace(9991, "transform") << "--- deduce types for recipe " << get(Recipe, r).name << end(); +//? cerr << "--- deduce types for recipe " << get(Recipe, r).name << '\n'; + map type; + map type_name; + for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { + instruction& inst = get(Recipe, r).steps.at(i); + for (long long int in = 0; in < SIZE(inst.ingredients); ++in) { + deduce_missing_type(type, type_name, inst.ingredients.at(in)); + check_type(type, type_name, inst.ingredients.at(in), r); + } + for (long long int out = 0; out < SIZE(inst.products); ++out) { + deduce_missing_type(type, type_name, inst.products.at(out)); + check_type(type, type_name, inst.products.at(out), r); + } + } +} + +void deduce_missing_type(map& type, map& type_name, reagent& x) { + if (x.type) return; + if (!contains_key(type, x.name)) return; + x.type = new type_tree(*type[x.name]); + trace(9992, "transform") << x.name << " <= " << debug_string(x.type) << end(); + assert(!x.properties.at(0).second); + x.properties.at(0).second = new string_tree(*type_name[x.name]); +} + +void check_type(map& type, map& type_name, const reagent& x, const recipe_ordinal r) { + if (is_literal(x)) return; + if (is_integer(x.name)) return; // if you use raw locations you're probably doing something unsafe + if (!x.type) return; // might get filled in by other logic later + if (!contains_key(type, x.name)) { + trace(9992, "transform") << x.name << " => " << debug_string(x.type) << end(); + type[x.name] = x.type; + } + if (!contains_key(type_name, x.name)) + type_name[x.name] = x.properties.at(0).second; + if (!types_strictly_match(type[x.name], x.type)) + raise_error << maybe(get(Recipe, r).name) << x.name << " used with multiple types\n" << end(); +} + +:(scenario transform_fills_in_missing_types) +recipe main [ + x:number <- copy 1 + y:number <- add x, 1 +] + +:(scenario transform_fills_in_missing_types_in_product) +recipe main [ + x:number <- copy 1 + x <- copy 2 +] + +:(scenario transform_fills_in_missing_types_in_product_and_ingredient) +recipe main [ + x:number <- copy 1 + x <- add x, 1 +] ++mem: storing 2 in location 1 + +:(scenario transform_fails_on_missing_types_in_first_mention) +% Hide_errors = true; +recipe main [ + x <- copy 1 + x:number <- copy 2 +] ++error: main: missing type for x in 'x <- copy 1' + +:(scenario typo_in_address_type_fails) +% Hide_errors = true; +recipe main [ + y:address:charcter <- new character:type + *y <- copy 67 +] ++error: main: unknown type charcter in 'y:address:charcter <- new character:type' diff --git a/047global.cc b/047global.cc deleted file mode 100644 index bd433af7..00000000 --- a/047global.cc +++ /dev/null @@ -1,83 +0,0 @@ -// So far we have local variables, and we can nest local variables of short -// lifetimes inside longer ones. Now let's support 'global' variables that -// last for the life of a routine. If we create multiple routines they won't -// have access to each other's globals. - -:(scenario global_space) -recipe main [ - # pretend arrays; in practice we'll use new - 10:number <- copy 5 - 20:number <- copy 5 - # actual start of this recipe - global-space:address:array:location <- copy 20/unsafe - default-space:address:array:location <- copy 10/unsafe - 1:number <- copy 23 - 1:number/space:global <- copy 24 -] -+mem: storing 23 in location 12 -+mem: storing 24 in location 22 - -//: to support it, create another special variable called global space -:(before "End Disqualified Reagents") -if (x.name == "global-space") - x.initialized = true; -:(before "End is_special_name Cases") -if (s == "global-space") return true; - -//: writes to this variable go to a field in the current routine -:(before "End routine Fields") -long long int global_space; -:(before "End routine Constructor") -global_space = 0; -:(after "void write_memory(reagent x, vector data)") - if (x.name == "global-space") { - if (!scalar(data) - || !x.type - || x.type->value != get(Type_ordinal, "address") - || !x.type->right - || x.type->right->value != get(Type_ordinal, "array") - || !x.type->right->right - || x.type->right->right->value != get(Type_ordinal, "location") - || x.type->right->right->right) { - raise_error << maybe(current_recipe_name()) << "'global-space' should be of type address:array:location, but tried to write " << to_string(data) << '\n' << end(); - } - if (Current_routine->global_space) - raise_error << "routine already has a global-space; you can't over-write your globals" << end(); - Current_routine->global_space = data.at(0); - return; - } - -//: now marking variables as /space:global looks them up inside this field -:(after "long long int space_base(const reagent& x)") - if (is_global(x)) { - if (!Current_routine->global_space) - raise_error << "routine has no global space\n" << end(); - return Current_routine->global_space; - } - -//: for now let's not bother giving global variables names. -//: don't want to make them too comfortable to use. - -:(scenario global_space_with_names) -% Hide_errors = true; -recipe main [ - global-space:address:array:location <- new location:type, 10 - x:number <- copy 23 - 1:number/space:global <- copy 24 -] -# don't complain about mixing numeric addresses and names -$error: 0 - -:(after "bool is_numeric_location(const reagent& x)") - if (is_global(x)) return false; - -//: helpers - -:(code) -bool is_global(const reagent& x) { - for (long long int i = /*skip name:type*/1; i < SIZE(x.properties); ++i) { - if (x.properties.at(i).first == "space") - return x.properties.at(i).second && x.properties.at(i).second->value == "global"; - } - return false; -} diff --git a/048check_type_by_name.cc b/048check_type_by_name.cc deleted file mode 100644 index 4842e15c..00000000 --- a/048check_type_by_name.cc +++ /dev/null @@ -1,95 +0,0 @@ -//: Some simple sanity checks for types, and also attempts to guess them where -//: they aren't provided. -//: -//: You still have to provide the full type the first time you mention a -//: variable in a recipe. You have to explicitly name :offset and :variant -//: every single time. You can't use the same name with multiple types in a -//: single recipe. - -:(scenario transform_fails_on_reusing_name_with_different_type) -% Hide_errors = true; -recipe main [ - x:number <- copy 1 - x:boolean <- copy 1 -] -+error: main: x used with multiple types - -:(after "Begin Instruction Modifying Transforms") -Transform.push_back(check_or_set_types_by_name); // idempotent - -:(code) -void check_or_set_types_by_name(const recipe_ordinal r) { - trace(9991, "transform") << "--- deduce types for recipe " << get(Recipe, r).name << end(); -//? cerr << "--- deduce types for recipe " << get(Recipe, r).name << '\n'; - map type; - map type_name; - for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) { - instruction& inst = get(Recipe, r).steps.at(i); - for (long long int in = 0; in < SIZE(inst.ingredients); ++in) { - deduce_missing_type(type, type_name, inst.ingredients.at(in)); - check_type(type, type_name, inst.ingredients.at(in), r); - } - for (long long int out = 0; out < SIZE(inst.products); ++out) { - deduce_missing_type(type, type_name, inst.products.at(out)); - check_type(type, type_name, inst.products.at(out), r); - } - } -} - -void deduce_missing_type(map& type, map& type_name, reagent& x) { - if (x.type) return; - if (!contains_key(type, x.name)) return; - x.type = new type_tree(*type[x.name]); - trace(9992, "transform") << x.name << " <= " << debug_string(x.type) << end(); - assert(!x.properties.at(0).second); - x.properties.at(0).second = new string_tree(*type_name[x.name]); -} - -void check_type(map& type, map& type_name, const reagent& x, const recipe_ordinal r) { - if (is_literal(x)) return; - if (is_integer(x.name)) return; // if you use raw locations you're probably doing something unsafe - if (!x.type) return; // might get filled in by other logic later - if (!contains_key(type, x.name)) { - trace(9992, "transform") << x.name << " => " << debug_string(x.type) << end(); - type[x.name] = x.type; - } - if (!contains_key(type_name, x.name)) - type_name[x.name] = x.properties.at(0).second; - if (!types_strictly_match(type[x.name], x.type)) - raise_error << maybe(get(Recipe, r).name) << x.name << " used with multiple types\n" << end(); -} - -:(scenario transform_fills_in_missing_types) -recipe main [ - x:number <- copy 1 - y:number <- add x, 1 -] - -:(scenario transform_fills_in_missing_types_in_product) -recipe main [ - x:number <- copy 1 - x <- copy 2 -] - -:(scenario transform_fills_in_missing_types_in_product_and_ingredient) -recipe main [ - x:number <- copy 1 - x <- add x, 1 -] -+mem: storing 2 in location 1 - -:(scenario transform_fails_on_missing_types_in_first_mention) -% Hide_errors = true; -recipe main [ - x <- copy 1 - x:number <- copy 2 -] -+error: main: missing type for x in 'x <- copy 1' - -:(scenario typo_in_address_type_fails) -% Hide_errors = true; -recipe main [ - y:address:charcter <- new character:type - *y <- copy 67 -] -+error: main: unknown type charcter in 'y:address:charcter <- new character:type' -- cgit 1.4.1-2-gfad0