feat: materialize computed compile-time callbacks
This commit is contained in:
@@ -897,3 +897,52 @@ compound_updates_and_type_composition_recompile_without_retaining_rewrites test
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
computed_callbacks_recompile_without_retaining_interpreter_function_ids test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
modules [2]mut ir.Module := undefined
|
||||
{
|
||||
session := init(mem.c_allocator)
|
||||
defer deinit(&session)
|
||||
root :: try add_package(&session)
|
||||
dependency :: try add_package(&session)
|
||||
bytes := try owned_text(
|
||||
`lib :: import "../library"
|
||||
`increment proc(value i16) i16 { return value + 100 }
|
||||
`main proc() i16 {
|
||||
` callback :: $(lib.choose())
|
||||
` return callback(41)
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&bytes)
|
||||
root_source :: try add_source(&session, root, "/callbacks/app/main.hon", &bytes, &diagnostic_store)
|
||||
imported := try owned_text(
|
||||
`Callback :: alias @proc(value i16) i16
|
||||
`bias i16 :: 1
|
||||
`@hide:file
|
||||
`increment proc(value i16) i16 { return value + bias }
|
||||
`choose proc() Callback { return increment }
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&imported)
|
||||
_ = try add_source(&session, dependency, "/callbacks/library/callback.hon", &imported, &diagnostic_store)
|
||||
try arraylist.append(&session.imports, Import{
|
||||
node = declaration(&session, root_source, 0).node,
|
||||
target = dependency,
|
||||
})
|
||||
modules[0] = try compile(&session, root, &diagnostic_store)
|
||||
errdefer ir.deinit(&modules[0])
|
||||
modules[1] = try compile(&session, root, &diagnostic_store)
|
||||
}
|
||||
defer ir.deinit(&modules[0])
|
||||
defer ir.deinit(&modules[1])
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
for modules |module| {
|
||||
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
+26
-3
@@ -87,11 +87,21 @@ lower_package proc(
|
||||
root_package ast.NodeId,
|
||||
diagnostic_store @mut diagnostics.Store,
|
||||
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
return try lower_module(allocator, tree, resolution, checked, root_package, null, diagnostic_store)
|
||||
return try lower_module(
|
||||
allocator,
|
||||
tree,
|
||||
resolution,
|
||||
checked,
|
||||
root_package,
|
||||
null,
|
||||
null,
|
||||
diagnostic_store,
|
||||
)
|
||||
}
|
||||
|
||||
#! lowers an isolated compile-time call wrapper without applying program entry-name rules.
|
||||
#! the caller supplies a checked parameterless function and owns the returned IR.
|
||||
#! the caller supplies a checked parameterless function, an empty `functions` list and owns the returned IR.
|
||||
#! fills `functions` by IR FunctionId; global initializers map to maxval!(ast.NodeId).
|
||||
#! retains `allocator` in the returned module; release it with ir.deinit; temporary lowering storage is released before returning.
|
||||
lower_evaluation proc(
|
||||
allocator mem.Allocator,
|
||||
@@ -99,9 +109,19 @@ lower_evaluation proc(
|
||||
resolution @sema.Resolution,
|
||||
checked @typecheck.Result,
|
||||
entry ast.NodeId,
|
||||
functions @mut arraylist.ArrayList(ast.NodeId),
|
||||
diagnostic_store @mut diagnostics.Store,
|
||||
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
return try lower_module(allocator, tree, resolution, checked, tree.root, entry, diagnostic_store)
|
||||
return try lower_module(
|
||||
allocator,
|
||||
tree,
|
||||
resolution,
|
||||
checked,
|
||||
tree.root,
|
||||
entry,
|
||||
functions,
|
||||
diagnostic_store,
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -112,6 +132,7 @@ lower_module proc(
|
||||
checked @typecheck.Result,
|
||||
root_package ast.NodeId,
|
||||
entry ?ast.NodeId,
|
||||
functions ?@mut arraylist.ArrayList(ast.NodeId),
|
||||
diagnostic_store @mut diagnostics.Store,
|
||||
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
module := ir.init(allocator)
|
||||
@@ -147,12 +168,14 @@ lower_module proc(
|
||||
.function: {
|
||||
if (ast.function_external(tree, id) and typecheck.type_of(checked, id).kind == .none) continue
|
||||
function :: try add_function(&module, ast.node_span(tree, id))
|
||||
if (functions) |declarations| try arraylist.append(declarations, id)
|
||||
storage[usize(id)] = Storage{ kind = .function, index = u32(function) }
|
||||
}
|
||||
.statement: {
|
||||
if (typecheck.type_of(checked, id).kind == .type_value) continue
|
||||
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
||||
initializer :: try add_function(&module, ast.node_span(tree, id))
|
||||
if (functions) |declarations| try arraylist.append(declarations, maxval!(ast.NodeId))
|
||||
global :: u32(module.globals.items.len)
|
||||
try arraylist.append(&module.globals, ir.Global{
|
||||
type = lower_type(&state, typecheck.type_of(checked, id)),
|
||||
|
||||
@@ -2332,11 +2332,16 @@ evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Erro
|
||||
}
|
||||
|
||||
# values leaving the temporary interpreter must own no interpreter addresses or module IDs.
|
||||
# rebuild contextual source literals while the module and its escaped flat cells are alive.
|
||||
# rebuild contextual source literals and source-bound callback references before releasing it.
|
||||
@hide:file
|
||||
serialize_value proc(
|
||||
state @mut State, module @ir.Module, checked @typecheck.Result,
|
||||
type typecheck.Type, value ir.Value, span ast.Span,
|
||||
state @mut State,
|
||||
module @ir.Module,
|
||||
checked @typecheck.Result,
|
||||
functions []ast.NodeId,
|
||||
type typecheck.Type,
|
||||
value ir.Value,
|
||||
span ast.Span,
|
||||
) ast.NodeId ! Error {
|
||||
uninitialized := value.uninitialized
|
||||
if (ir.aggregate(value.type)) {
|
||||
@@ -2356,7 +2361,7 @@ serialize_value proc(
|
||||
defer arraylist.deinit(&elements)
|
||||
for (0..array.len) |i| {
|
||||
child :: ir.aggregate_item(module, value, i)
|
||||
node :: try serialize_value(state, module, checked, array.element, child, span)
|
||||
node :: try serialize_value(state, module, checked, functions, array.element, child, span)
|
||||
if (node == no_node) return no_node
|
||||
try arraylist.append(&elements, node)
|
||||
}
|
||||
@@ -2376,7 +2381,7 @@ serialize_value proc(
|
||||
return result
|
||||
}
|
||||
child :: ir.aggregate_item(module, value, i)
|
||||
node :: try serialize_value(state, module, checked, field.type, child, span)
|
||||
node :: try serialize_value(state, module, checked, functions, field.type, child, span)
|
||||
if (node == no_node) return no_node
|
||||
name := field.name
|
||||
if (name != null) name = try member_name(state, name?)
|
||||
@@ -2391,7 +2396,7 @@ serialize_value proc(
|
||||
index :: if (failed) usize(1) else usize(0)
|
||||
channel :: if (failed) container.error_type else container.element
|
||||
child :: ir.aggregate_item(module, value, index)
|
||||
result = try serialize_value(state, module, checked, channel, child, span)
|
||||
result = try serialize_value(state, module, checked, functions, channel, child, span)
|
||||
if (result == no_node) return no_node
|
||||
# keep named success/error types distinct even when their members overlap.
|
||||
annotation := no_node
|
||||
@@ -2419,19 +2424,23 @@ serialize_value proc(
|
||||
absent :: if (container.element.kind == .function)
|
||||
value.data.function == maxval!(ir.FunctionId)
|
||||
else value.data.unsigned == maxval!(u64)
|
||||
if (!absent) return no_node
|
||||
result = try ast.add_literal(state.builder, .null, span)
|
||||
if (absent) result = try ast.add_literal(state.builder, .null, span)
|
||||
else result = try serialize_value(state, module, checked, functions, container.element, value, span)
|
||||
} else if (!ir.aggregate_item(module, value, 0).data.boolean) {
|
||||
result = try ast.add_literal(state.builder, .null, span)
|
||||
} else {
|
||||
child :: ir.aggregate_item(module, value, 1)
|
||||
payload :: try serialize_value(state, module, checked, container.element, child, span)
|
||||
payload :: try serialize_value(state, module, checked, functions, container.element, child, span)
|
||||
if (payload == no_node) return no_node
|
||||
name :: try ast.add_generated_identifier(state.builder, span,
|
||||
try strpool.intern(state.strings, "some"), "some")
|
||||
arguments [1]ast.NodeId :: [payload]
|
||||
result = try ast.add_builtin_call(state.builder, span, name, arguments[..])
|
||||
}
|
||||
} else if (type.kind == .function) {
|
||||
index :: usize(value.data.function)
|
||||
if (index >= functions.len or functions[index] == no_node) return no_node
|
||||
result = try reference(state, functions[index], functions[index])
|
||||
} else if (ir.integer_bits(value.type) != 0 and type.kind != .pointer) {
|
||||
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
|
||||
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
|
||||
@@ -2609,7 +2618,10 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, diagnostic.code, diagnostic.span)
|
||||
return Value{}
|
||||
}
|
||||
module := try lower.lower_evaluation(state.allocator, &tree, state.resolution, &checked, wrapper, &diagnostic_store)
|
||||
functions := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&functions)
|
||||
module := try lower.lower_evaluation(state.allocator, &tree, state.resolution, &checked,
|
||||
wrapper, &functions, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
outcome := try interpreter.run_evaluation(state.allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
@@ -2623,7 +2635,7 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
|
||||
}
|
||||
value :: outcome.value
|
||||
source_type :: typecheck.type_of(&checked, result_type)
|
||||
node :: try serialize_value(state, &module, &checked, source_type, value, span)
|
||||
node :: try serialize_value(state, &module, &checked, functions.items, source_type, value, span)
|
||||
if (node == no_node) {
|
||||
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
|
||||
|
||||
Reference in New Issue
Block a user