refactor: name recovery boundaries and compact IR strings
This commit is contained in:
@@ -35,7 +35,7 @@ Frame :: struct {
|
||||
}
|
||||
|
||||
#! executes only typed IR; invocation and global state are temporary and released on every exit.
|
||||
#! returned strings borrow `module`; runtime source errors are traps, not infrastructure errors.
|
||||
#! returned string IDs resolve through `module`; runtime source errors are traps, not infrastructure errors.
|
||||
run proc(
|
||||
allocator mem.Allocator,
|
||||
module @ir.Module,
|
||||
|
||||
@@ -153,12 +153,18 @@ function_aliases_and_borrowed_string_values_execute test {
|
||||
store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&store)
|
||||
text :: "line\n\"quoted\"\x00"
|
||||
owned :: try mem.alloc(u8, module.allocator, text.len)
|
||||
transferred := false
|
||||
defer if (!transferred) mem.free(module.allocator, owned)
|
||||
memcopy!(owned, text)
|
||||
try arraylist.append(&module.strings, ir.StringStorage{ allocation = owned, len = text.len })
|
||||
transferred = true
|
||||
target :: try add_function(&module, mem.empty(ir.Type), 0, .string, 1)
|
||||
try emit(&module, target, ir.Instruction{
|
||||
op = .constant,
|
||||
type = .string,
|
||||
destination = ir.ValueId(0),
|
||||
constant = ir.Value{ type = .string, data = ir.ValueData{ string = text } },
|
||||
constant = ir.Value{ type = .string, data = ir.ValueData{ string = ir.StringId(0) } },
|
||||
})
|
||||
return_value(&module, target, ir.ValueId(0))
|
||||
initializer :: try add_function(&module, mem.empty(ir.Type), 0, .function, 1)
|
||||
@@ -173,7 +179,7 @@ function_aliases_and_borrowed_string_values_execute test {
|
||||
outcome :: try run(mem.c_allocator, &module, &store)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(ir.Type.string, outcome.value.type)
|
||||
try testing.expect_equal([]u8, text, outcome.value.data.string)
|
||||
try testing.expect_equal([]u8, text, ir.string_bytes(&module, outcome.value.data.string))
|
||||
}
|
||||
|
||||
premature_local_reads_and_global_cycles_report_instruction_spans test {
|
||||
|
||||
+18
-3
@@ -7,6 +7,7 @@ GlobalId :: distinct u32
|
||||
LocalId :: distinct u32
|
||||
ValueId :: distinct u32
|
||||
BlockId :: distinct u32
|
||||
StringId :: distinct u32
|
||||
no_value :: maxval!(ValueId)
|
||||
|
||||
Type :: enum {
|
||||
@@ -23,7 +24,7 @@ ValueData :: union {
|
||||
integer i64
|
||||
real f64
|
||||
boolean bool
|
||||
string []u8
|
||||
string StringId
|
||||
function FunctionId
|
||||
}
|
||||
Value :: struct {
|
||||
@@ -101,13 +102,19 @@ Global :: struct {
|
||||
span diagnostics.Span
|
||||
}
|
||||
|
||||
#! retains the original allocation for freeing while exposing only decoded bytes.
|
||||
StringStorage :: struct {
|
||||
allocation []mut u8
|
||||
len usize
|
||||
}
|
||||
|
||||
#! owns executable storage, including decoded strings; no AST or binding table is retained.
|
||||
#! spans and diagnostic IDs refer to the caller's source and diagnostic store.
|
||||
Module :: struct {
|
||||
allocator mem.Allocator
|
||||
functions arraylist.ArrayList(Function)
|
||||
globals arraylist.ArrayList(Global)
|
||||
strings arraylist.ArrayList([]mut u8)
|
||||
strings arraylist.ArrayList(StringStorage)
|
||||
entry ?FunctionId = null
|
||||
entry_diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
||||
}
|
||||
@@ -136,6 +143,12 @@ init_block proc(allocator mem.Allocator) Block {
|
||||
}
|
||||
}
|
||||
|
||||
#! resolves a module-local ID to bytes borrowed until module deinit.
|
||||
string_bytes proc(module @Module, id StringId) []u8 {
|
||||
string :: module.strings.items[usize(id)]
|
||||
return string.allocation[..string.len]
|
||||
}
|
||||
|
||||
#! releases all executable storage using the retained allocator.
|
||||
deinit proc(module @mut Module) void {
|
||||
for (0..(module.functions.items.len)) |i| {
|
||||
@@ -149,7 +162,9 @@ deinit proc(module @mut Module) void {
|
||||
arraylist.deinit(&function.locals)
|
||||
arraylist.deinit(&function.blocks)
|
||||
}
|
||||
for (module.strings.items) |string| mem.free(module.allocator, string)
|
||||
for (module.strings.items) |string|
|
||||
mem.free(module.allocator, string.allocation)
|
||||
|
||||
arraylist.deinit(&module.functions)
|
||||
arraylist.deinit(&module.globals)
|
||||
arraylist.deinit(&module.strings)
|
||||
|
||||
+13
-4
@@ -560,8 +560,17 @@ lower_string proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
||||
trap(state, try diagnostics.add(state.diagnostics, .invalid_escape, span), span)
|
||||
return ir.no_value
|
||||
}
|
||||
# retain the complete allocation for freeing, but expose only decoded bytes.
|
||||
try arraylist.append(&state.module.strings, owned)
|
||||
if (state.module.strings.items.len > usize(maxval!(ir.StringId)))
|
||||
return .out_of_memory
|
||||
|
||||
string_id :: ir.StringId(state.module.strings.items.len)
|
||||
try arraylist.append(&state.module.strings, ir.StringStorage{
|
||||
allocation = owned,
|
||||
len = length,
|
||||
})
|
||||
transferred = true
|
||||
return try constant(state, ir.Value{ type = .string, data = ir.ValueData{ string = owned[..length] } }, span)
|
||||
}
|
||||
return try constant(state, ir.Value{
|
||||
type = .string,
|
||||
data = ir.ValueData{ string = string_id },
|
||||
}, span)
|
||||
}
|
||||
@@ -91,7 +91,20 @@ owns_decoded_strings_after_source_and_frontend_are_freed test {
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(ir.Type.string, outcome.value.type)
|
||||
try testing.expect(mem.eql(outcome.value.data.string, "a\\\"\n\r\t\x00Az"))
|
||||
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "a\\\"\n\r\t\x00Az"))
|
||||
}
|
||||
|
||||
resolves_module_local_string_ids_after_frontend_cleanup test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(
|
||||
"first :: \"not the result\"\nidentity proc(value string) string { return value }\nmain proc() string { return identity(\"second\\n\\x00\") }\n",
|
||||
&diagnostic_store,
|
||||
)
|
||||
defer ir.deinit(&module)
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal([]u8, "second\n\x00", ir.string_bytes(&module, outcome.value.data.string))
|
||||
}
|
||||
|
||||
honors_float_context_and_float_division test {
|
||||
|
||||
+3
-3
@@ -133,19 +133,19 @@ run_file proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) i32 ! DriverE
|
||||
module := try compile(allocator, source.items, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
outcome :: try interpreter.run(allocator, &module, &diagnostic_store)
|
||||
if (outcome.trap == null) try print_result(io.stdout(runtime), outcome.value)
|
||||
if (outcome.trap == null) try print_result(io.stdout(runtime), &module, outcome.value)
|
||||
try report_diagnostics(allocator, io.stderr(runtime), path, source.items, &diagnostic_store, outcome.trap)
|
||||
if (outcome.trap != null or diagnostics.count(&diagnostic_store) != 0) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
@hide:file
|
||||
print_result proc(output io.Writer, value ir.Value) void ! io.WriteError {
|
||||
print_result proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
|
||||
match value.type {
|
||||
.int: try io.print("{}\n", output, {value.data.integer})
|
||||
.float: try io.print("{d}\n", output, {value.data.real})
|
||||
.bool: try io.print("{}\n", output, {value.data.boolean})
|
||||
.string: try io.print("{s}\n", output, {value.data.string})
|
||||
.string: try io.print("{s}\n", output, {ir.string_bytes(module, value.data.string)})
|
||||
.void: {}
|
||||
else: unreachable
|
||||
}
|
||||
|
||||
+10
-21
@@ -408,16 +408,7 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
||||
arguments arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&arguments)
|
||||
|
||||
while (
|
||||
!next_is(state, .close_paren)
|
||||
and !next_is(state, .newline)
|
||||
and !next_is(state, .close_curly)
|
||||
and !next_is(state, .open_curly)
|
||||
and !next_is(state, .else)
|
||||
and !next_is(state, .return)
|
||||
and !next_is(state, .if)
|
||||
and !next_is(state, .eof)
|
||||
) {
|
||||
while (!is_expression_boundary(current(state).kind)) {
|
||||
argument :: try parse_expr(state)
|
||||
try arraylist.append(&arguments, argument)
|
||||
if (!next_is(state, .comma)) break
|
||||
@@ -482,17 +473,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
else
|
||||
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||
|
||||
if (
|
||||
token.kind != .newline
|
||||
and token.kind != .comma
|
||||
and token.kind != .close_paren
|
||||
and token.kind != .close_curly
|
||||
and token.kind != .open_curly
|
||||
and token.kind != .else
|
||||
and token.kind != .return
|
||||
and token.kind != .if
|
||||
and token.kind != .eof
|
||||
) {
|
||||
if token.kind != .comma and !is_expression_boundary(token.kind) {
|
||||
state.next_token += 1
|
||||
}
|
||||
|
||||
@@ -501,6 +482,14 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
is_expression_boundary proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .if, .eof: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
recover_decl proc(
|
||||
state @mut State,
|
||||
|
||||
Reference in New Issue
Block a user