feat: materialize readonly typed compile-time slices
This commit is contained in:
+24
-1
@@ -84,6 +84,7 @@ ExprKind :: enum {
|
||||
catch_expr
|
||||
typed_constant
|
||||
union_constant
|
||||
slice_constant
|
||||
comptime_expr
|
||||
literal_void
|
||||
literal_undefined
|
||||
@@ -198,6 +199,7 @@ NodeKind :: enum {
|
||||
stmt_block
|
||||
expr_typed_constant
|
||||
expr_union_constant
|
||||
expr_slice_constant
|
||||
expr_comptime
|
||||
literal_float_value
|
||||
literal_void
|
||||
@@ -624,6 +626,17 @@ add_union_constant proc(
|
||||
}, at)
|
||||
}
|
||||
|
||||
#! owns a readonly slice's typed fixed-array backing, including any sentinel.
|
||||
add_slice_constant proc(
|
||||
builder @mut Builder, at Span, type, elements NodeId,
|
||||
) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_slice_constant,
|
||||
data0 = NodeData{ node_id = type },
|
||||
data1 = NodeData{ node_id = elements },
|
||||
}, at)
|
||||
}
|
||||
|
||||
add_comptime_expr proc(builder @mut Builder, at Span, operand NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_comptime,
|
||||
@@ -1613,6 +1626,7 @@ expression_kind proc(kind NodeKind) ExprKind {
|
||||
.expr_catch: .catch_expr
|
||||
.expr_typed_constant: .typed_constant
|
||||
.expr_union_constant: .union_constant
|
||||
.expr_slice_constant: .slice_constant
|
||||
.expr_comptime: .comptime_expr
|
||||
.expr_block: .block_expr
|
||||
.literal_float_value: .literal_float
|
||||
@@ -1675,6 +1689,15 @@ union_constant proc(tree @Tree, id NodeId) TypedConstantView {
|
||||
}
|
||||
}
|
||||
|
||||
slice_constant proc(tree @Tree, id NodeId) TypedConstantView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_slice_constant)
|
||||
return TypedConstantView{
|
||||
type = node.data0.node_id,
|
||||
value = node.data1.node_id,
|
||||
}
|
||||
}
|
||||
|
||||
catch_expr proc(tree @Tree, id NodeId) CatchExprView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_catch)
|
||||
@@ -2030,7 +2053,7 @@ node_layout proc(node Node) NodeLayout {
|
||||
.literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null,
|
||||
.literal_void, .literal_undefined, .expr_identifier, .invalid_expr: {}
|
||||
.expr_unary: layout.direct_children = 2
|
||||
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant, .expr_union_constant,
|
||||
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant, .expr_union_constant, .expr_slice_constant,
|
||||
.expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type:
|
||||
layout.direct_children = 6
|
||||
.expr_pointer_type: layout.direct_children = 4
|
||||
|
||||
@@ -1007,6 +1007,74 @@ computed_byte_slices_recompile_without_borrowing_evaluation_storage test {
|
||||
}
|
||||
}
|
||||
|
||||
computed_nonbyte_slices_recompile_owned_private_elements_and_callbacks 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"
|
||||
`Code :: distinct i16
|
||||
`Entry :: struct { value bool }
|
||||
`increment proc(value Code) i16 { return i16(value) + 100 }
|
||||
`main proc() i16 {
|
||||
` entries :: $(lib.make())
|
||||
` if (entries.len != 2 or entries[0].nested.len != 2) return -1
|
||||
` if (entries[0].nested[0] != -300 or entries[0].nested[1] != 1024) return -2
|
||||
` if (entries[1].nested.len != 1 or entries[1].nested[0] != 1024) return -3
|
||||
` if (i16(entries[0].value) != 40 or i16(entries[1].value) != 41) return -4
|
||||
` if (entries[1].run(entries[1].value) != 43) return -5
|
||||
` return entries[0].run(entries[0].value)
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&bytes)
|
||||
root_source :: try add_source(&session, root, "/nonbyte_slices/app/main.hon", &bytes, &diagnostic_store)
|
||||
imported := try owned_text(
|
||||
`@hide:file
|
||||
`Code :: distinct i16
|
||||
`@hide:file
|
||||
`Callback :: alias @proc(value Code) i16
|
||||
`@hide:file
|
||||
`Entry :: struct { value Code, run Callback, nested []i16 }
|
||||
`bias i16 :: 2
|
||||
`@hide:file
|
||||
`increment proc(value Code) i16 { return i16(value) + bias }
|
||||
`make proc() []Entry {
|
||||
` values [2]i16 :: [-300, 1024]
|
||||
` entries [2]Entry :: [
|
||||
` { value = 40, run = increment, nested = values[..] },
|
||||
` { value = 41, run = increment, nested = values[1..] },
|
||||
` ]
|
||||
` return entries[..]
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&imported)
|
||||
_ = try add_source(&session, dependency, "/nonbyte_slices/library/items.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)
|
||||
}
|
||||
}
|
||||
|
||||
computed_untagged_unions_recompile_without_borrowing_storage_bytes test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
|
||||
@@ -108,20 +108,34 @@ EvaluationMemory :: struct {
|
||||
frames arraylist.ArrayList(Frame)
|
||||
}
|
||||
|
||||
#! borrows one checked byte from retained evaluation storage; null means an invalid pointer/range.
|
||||
evaluation_byte proc(outcome @Outcome, module @ir.Module, pointer ir.Value, index usize) ?ir.Value {
|
||||
if (outcome.evaluation_memory == null) return null
|
||||
#! borrows one checked typed element from retained evaluation storage; null means an invalid pointer/range.
|
||||
#! aggregate storage may borrow scratch valid only until the next evaluation accessor call.
|
||||
evaluation_element proc(
|
||||
outcome @Outcome,
|
||||
module @ir.Module,
|
||||
pointer ir.Value,
|
||||
index usize,
|
||||
type ir.Type,
|
||||
shape ir.ArrayId,
|
||||
) ?ir.Value {
|
||||
if (outcome.evaluation_memory == null or pointer.type != .pointer) return null
|
||||
memory :: &outcome.evaluation_memory?[0]
|
||||
handle :: pointer.data.unsigned
|
||||
if (handle >= u64(memory.pointers.items.len)) return null
|
||||
location := memory.pointers.items[usize(handle)]
|
||||
if (index > maxval!(usize) - location.byte_offset) return null
|
||||
location.byte_offset += index
|
||||
if (pointer_address(module, location, .u8, ir.no_array)) |address|
|
||||
stride :: ir.storage_size(module, type, shape)
|
||||
if (stride != 0 and index > divtrunc!(maxval!(usize) - location.byte_offset, stride)) return null
|
||||
location.byte_offset += index * stride
|
||||
if (pointer_address(module, location, type, shape)) |address|
|
||||
return read_address(module, address, memory.scratch)
|
||||
return null
|
||||
}
|
||||
|
||||
#! borrows one checked byte from retained evaluation storage; null means an invalid pointer/range.
|
||||
evaluation_byte proc(outcome @Outcome, module @ir.Module, pointer ir.Value, index usize) ?ir.Value {
|
||||
return evaluation_element(outcome, module, pointer, index, .u8, ir.no_array)
|
||||
}
|
||||
|
||||
#! executes typed IR with invocation/global storage released on every exit.
|
||||
#! aggregate results own flat storage released by `deinit`; string IDs and shapes resolve through `module`.
|
||||
run proc(
|
||||
@@ -789,7 +803,7 @@ pointer_address proc(module @ir.Module, pointer Pointer, type ir.Type, shape ir.
|
||||
base := Address{
|
||||
type = pointer.root_type, array_type = pointer.root_array, cells = &[],
|
||||
root = pointer.root, root_type = pointer.root_type, root_array = pointer.root_array,
|
||||
readonly = pointer.readonly, bytes = pointer.bytes,
|
||||
readonly = pointer.readonly, bytes = pointer.bytes, raw = pointer.root^.raw,
|
||||
}
|
||||
if (pointer.bytes.len != 0) {
|
||||
base.type = type
|
||||
|
||||
@@ -48,6 +48,9 @@ State :: struct {
|
||||
arguments arraylist.ArrayList(ir.ValueId)
|
||||
cleanups arraylist.ArrayList(ast.NodeId)
|
||||
records []mut ir.ArrayId
|
||||
slice_elements arraylist.ArrayList(typecheck.Type)
|
||||
slice_backings arraylist.ArrayList(ast.NodeId)
|
||||
evaluation_functions ?@mut arraylist.ArrayList(ast.NodeId) = null
|
||||
function usize = 0
|
||||
block ir.BlockId = ir.BlockId(0)
|
||||
terminated bool = false
|
||||
@@ -156,11 +159,16 @@ lower_module proc(
|
||||
storage = storage,
|
||||
arrays = arrays,
|
||||
records = records,
|
||||
slice_elements = arraylist.init(typecheck.Type, allocator),
|
||||
slice_backings = arraylist.init(ast.NodeId, allocator),
|
||||
evaluation_functions = functions,
|
||||
arguments = arraylist.init(ir.ValueId, allocator),
|
||||
cleanups = arraylist.init(ast.NodeId, allocator),
|
||||
}
|
||||
defer arraylist.deinit(&state.arguments)
|
||||
defer arraylist.deinit(&state.cleanups)
|
||||
defer arraylist.deinit(&state.slice_elements)
|
||||
defer arraylist.deinit(&state.slice_backings)
|
||||
|
||||
# allocate every package identity before lowering any initializer or body.
|
||||
for (0..ast.package_decl_count(tree)) |i| {
|
||||
@@ -204,6 +212,22 @@ lower_module proc(
|
||||
.import, .invalid: {}
|
||||
}
|
||||
}
|
||||
backing_index := usize(0)
|
||||
while (backing_index < state.slice_backings.items.len) {
|
||||
try lower_slice_backing(&state, state.slice_backings.items[backing_index])
|
||||
backing_index += 1
|
||||
}
|
||||
# source element shapes are metadata, not record layout dependencies. defer
|
||||
# them until recursive records have cached their completed physical layout.
|
||||
cursor := usize(0)
|
||||
while (cursor < state.slice_elements.items.len) {
|
||||
type :: state.slice_elements.items[cursor]
|
||||
child :: typecheck.array_type(checked, type.array_type).element
|
||||
shape :: state.arrays[usize(type.array_type)]
|
||||
element_shape :: try array_shape(&state, child)
|
||||
module.arrays.items[usize(shape)].element_array = element_shape
|
||||
cursor += 1
|
||||
}
|
||||
return module
|
||||
}
|
||||
|
||||
@@ -308,6 +332,10 @@ prepare_container proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.
|
||||
}
|
||||
shape :: try ir.add_record(state.module, fields[..])
|
||||
state.arrays[index] = shape
|
||||
if (type.kind == .slice) {
|
||||
state.module.arrays.items[usize(shape)].element = lower_type(state, descriptor.element)
|
||||
try arraylist.append(&state.slice_elements, type)
|
||||
}
|
||||
return shape
|
||||
}
|
||||
|
||||
@@ -448,6 +476,19 @@ lower_global proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diag
|
||||
finish(state, value, declaration.span)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_slice_backing proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
global :: state.module.globals.items[usize(state.storage[usize(id)].index)]
|
||||
state.function = usize(global.initializer)
|
||||
state.block = ir.BlockId(0)
|
||||
state.terminated = false
|
||||
state.module.functions.items[state.function].result = global.type
|
||||
state.module.functions.items[state.function].result_array_type = global.array_type
|
||||
backing :: ast.slice_constant(state.tree, id).value
|
||||
value :: try lower_expression(state, backing)
|
||||
if (!state.terminated) finish(state, value, ast.node_span(state.tree, id))
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
@@ -2098,6 +2139,40 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id))
|
||||
}
|
||||
.slice_constant: {
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
if (state.storage[usize(id)].kind == .none) {
|
||||
backing :: ast.slice_constant(tree, id).value
|
||||
initializer :: try add_function(state.module, span)
|
||||
if (state.evaluation_functions) |declarations|
|
||||
try arraylist.append(declarations, maxval!(ast.NodeId))
|
||||
global :: u32(state.module.globals.items.len)
|
||||
try arraylist.append(&state.module.globals, ir.Global{
|
||||
type = lower_type(state, typecheck.type_of(state.checked, backing)),
|
||||
array_type = try array_shape(state, typecheck.type_of(state.checked, backing)),
|
||||
initializer = initializer,
|
||||
span = span,
|
||||
})
|
||||
state.storage[usize(id)] = Storage{ kind = .global, index = global }
|
||||
try arraylist.append(&state.slice_backings, id)
|
||||
}
|
||||
location :: state.storage[usize(id)]
|
||||
global :: state.module.globals.items[usize(location.index)]
|
||||
address :: try emit(state, ir.Instruction{
|
||||
op = .address_global, type = .address, pointee = global.type,
|
||||
array_type = global.array_type, slot = location.index, span = span,
|
||||
})
|
||||
pointer :: try emit(state, ir.Instruction{
|
||||
op = .pointer_from_address, type = .pointer, left = address,
|
||||
pointee = global.type, array_type = global.array_type, span = span,
|
||||
})
|
||||
descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, id).array_type)
|
||||
backing :: ast.slice_constant(tree, id).value
|
||||
elements :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, backing).array_type)
|
||||
extra :: if (descriptor.sentinel != null) usize(1) else usize(0)
|
||||
len :: elements.len - extra
|
||||
return try record_pair(state, shape, pointer, try usize_constant(state, len, span), span)
|
||||
}
|
||||
.union_constant: {
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
bytes :: ast.union_constant(tree, id).value
|
||||
|
||||
@@ -1301,6 +1301,14 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
try depend(state, value.type, id)
|
||||
try depend(state, value.value, id)
|
||||
}
|
||||
.slice_constant: {
|
||||
value :: ast.slice_constant(tree, id)
|
||||
state.cells[usize(id)].fixed = true
|
||||
state.cells[usize(id)].type = try check_annotation(state, value.type, false)
|
||||
try collect_expression(state, value.value)
|
||||
try depend(state, value.type, id)
|
||||
try depend(state, value.value, id)
|
||||
}
|
||||
.try_expr: {
|
||||
operand :: ast.type_operand(tree, id)
|
||||
try collect_expression(state, operand)
|
||||
@@ -2178,6 +2186,27 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
if (!descriptor.is_union or descriptor.tag_type.kind != .none)
|
||||
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
||||
}
|
||||
.slice_constant: {
|
||||
value :: ast.slice_constant(tree, id)
|
||||
type :: state.cells[usize(id)].type
|
||||
backing :: state.types[usize(value.value)]
|
||||
if (type.kind == .invalid or backing.kind == .invalid) return
|
||||
if (
|
||||
type.kind != .slice or backing.kind != .array
|
||||
or ast.expr_kind(tree, value.value) != .typed_constant
|
||||
) {
|
||||
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
||||
return
|
||||
}
|
||||
descriptor :: state.arrays.items[usize(type.array_type)]
|
||||
elements :: state.arrays.items[usize(backing.array_type)]
|
||||
if (
|
||||
descriptor.mutable or elements.mutable
|
||||
or !same_type(state, descriptor.element, elements.element)
|
||||
or (descriptor.sentinel != null and elements.len == 0)
|
||||
or ast.expr_kind(tree, ast.typed_constant(tree, value.value).value) != .array_literal
|
||||
) try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
||||
}
|
||||
.try_expr, .catch_expr: try validate_fallible_expression(state, id)
|
||||
.unary: {
|
||||
expression :: ast.unary_expr(tree, id)
|
||||
|
||||
@@ -551,6 +551,11 @@ resolve_expr proc(
|
||||
try resolve_expr(state, scope, value.type)
|
||||
try resolve_expr(state, scope, value.value)
|
||||
}
|
||||
.slice_constant: {
|
||||
value :: ast.slice_constant(tree, id)
|
||||
try resolve_expr(state, scope, value.type)
|
||||
try resolve_expr(state, scope, value.value)
|
||||
}
|
||||
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
|
||||
.literal_undefined: {}
|
||||
.enum_literal: {}
|
||||
|
||||
@@ -1707,7 +1707,7 @@ branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
|
||||
}
|
||||
match ast.expr_kind(&state.tree, id) {
|
||||
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
|
||||
.union_constant: return true
|
||||
.union_constant, .slice_constant: return true
|
||||
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
|
||||
.enum_literal: return true
|
||||
.record_literal: {
|
||||
@@ -2359,8 +2359,96 @@ union_bytes_materializable proc(checked @typecheck.Result, type typecheck.Type,
|
||||
}
|
||||
}
|
||||
|
||||
# rebuild exact slice backing annotations without retaining temporary checker IDs.
|
||||
@hide:file
|
||||
serialized_type proc(
|
||||
state @mut State,
|
||||
checked @typecheck.Result,
|
||||
type typecheck.Type,
|
||||
span ast.Span,
|
||||
depth usize,
|
||||
) ast.NodeId ! Error {
|
||||
if (depth >= 128) return no_node
|
||||
source := type.nominal
|
||||
if (source == no_node and type.kind == .record) {
|
||||
record :: typecheck.record_type(checked, type.record_type)
|
||||
source = if (record.declaration != no_node) record.declaration else record.source
|
||||
}
|
||||
if (source == no_node and type.kind == .enumeration)
|
||||
source = typecheck.enum_type(checked, type.enum_type).declaration
|
||||
if (source == no_node and type.kind == .function) source = type.function
|
||||
if (source != no_node) {
|
||||
if (ast.node_class(&state.tree, source) == .function) {
|
||||
params := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(¶ms)
|
||||
for (0..ast.function_param_count(&state.tree, source)) |i|
|
||||
try arraylist.append(¶ms, ast.function_param(&state.tree, source, i))
|
||||
source = try ast.add_function_type(state.builder, span,
|
||||
ast.callable_result(&state.tree, source), params.items)
|
||||
try refresh(state)
|
||||
}
|
||||
return (try materialize(state, Value{ kind = .type, node = source })).node
|
||||
}
|
||||
result := no_node
|
||||
if (type.kind == .array or type.kind == .slice or type.kind == .optional or type.kind == .fallible) {
|
||||
descriptor :: typecheck.array_type(checked, type.array_type)
|
||||
child :: try serialized_type(state, checked, descriptor.element, span, depth + 1)
|
||||
if (child == no_node) return no_node
|
||||
if (type.kind == .array) {
|
||||
count :: try ast.add_integer(state.builder, span, u64(descriptor.len))
|
||||
result = try ast.add_array_type(state.builder, span, count, child, descriptor.mutable)
|
||||
} else if (type.kind == .slice) {
|
||||
sentinel ?ast.NodeId := null
|
||||
if (descriptor.sentinel) |value| {
|
||||
unsigned :: (descriptor.element.kind == .u8 or descriptor.element.kind == .u16
|
||||
or descriptor.element.kind == .u32 or descriptor.element.kind == .u64 or descriptor.element.kind == .usize)
|
||||
negative :: value < 0 and !unsigned
|
||||
magnitude :: if (negative) u64(-(value + 1)) + 1 else ptrcast!(@u64, &value)^
|
||||
literal := try ast.add_integer(state.builder, span, magnitude)
|
||||
if (negative) literal = try ast.add_unary(state.builder, span, .negate, literal)
|
||||
sentinel = literal
|
||||
}
|
||||
result = try ast.add_slice_type(state.builder, span, child, descriptor.mutable, sentinel)
|
||||
} else if (type.kind == .optional) result = try ast.add_optional_type(state.builder, span, child)
|
||||
else {
|
||||
error_type :: try serialized_type(state, checked, descriptor.error_type, span, depth + 1)
|
||||
if (error_type == no_node) return no_node
|
||||
result = try ast.add_binary_type(state.builder, span, child, error_type, true)
|
||||
}
|
||||
} else if (type.kind == .pointer) {
|
||||
descriptor :: typecheck.pointer_type(checked, type.pointer_type)
|
||||
child :: try serialized_type(state, checked, descriptor.element, span, depth + 1)
|
||||
if (child == no_node) return no_node
|
||||
result = try ast.add_pointer_type(state.builder, span, child, descriptor.mutable, descriptor.many)
|
||||
} else {
|
||||
kind sema.BuiltinType :: match type.kind {
|
||||
.int: .i64
|
||||
.i8: .i8
|
||||
.i16: .i16
|
||||
.i32: .i32
|
||||
.isize: .isize
|
||||
.u8: .u8
|
||||
.u16: .u16
|
||||
.u32: .u32
|
||||
.u64: .u64
|
||||
.usize: .usize
|
||||
.float: .f64
|
||||
.bool: .bool
|
||||
.string: .string
|
||||
.void: .void
|
||||
.anyopaque: .anyopaque
|
||||
else: return no_node
|
||||
}
|
||||
result = try ast.add_identifier(state.builder, span, strpool.no_str)
|
||||
try refresh(state)
|
||||
state.bindings.items[usize(result)] = sema.Binding{ kind = .builtin, data = sema.BindingData{ builtin = kind } }
|
||||
}
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
|
||||
# values leaving temporary evaluation must own no interpreter addresses or module IDs.
|
||||
# snapshot readonly bytes and rebuild source literals/references before releasing their backing.
|
||||
# snapshot readonly storage and rebuild source literals/references before releasing their backing.
|
||||
@hide:file
|
||||
serialize_value proc(
|
||||
state @mut State,
|
||||
@@ -2371,7 +2459,15 @@ serialize_value proc(
|
||||
type typecheck.Type,
|
||||
value ir.Value,
|
||||
span ast.Span,
|
||||
remaining @mut usize,
|
||||
) ast.NodeId ! Error {
|
||||
if (remaining^ == 0 or state.depth >= 128) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_execution_limit, span)
|
||||
return no_node
|
||||
}
|
||||
remaining^ -= 1
|
||||
state.depth += 1
|
||||
defer state.depth -= 1
|
||||
uninitialized := value.uninitialized
|
||||
if (ir.aggregate(value.type)) {
|
||||
if (module.arrays.items[usize(value.array_type)].is_union)
|
||||
@@ -2386,13 +2482,64 @@ serialize_value proc(
|
||||
result := no_node
|
||||
if (type.kind == .slice) {
|
||||
slice :: typecheck.array_type(checked, type.array_type)
|
||||
if (slice.mutable or slice.element.kind != .u8 or slice.element.nominal != no_node) return no_node
|
||||
if (slice.mutable) return no_node
|
||||
pointer :: ir.aggregate_item(module, value, 0)
|
||||
length :: ir.aggregate_item(module, value, 1)
|
||||
if (pointer.uninitialized or length.uninitialized) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
|
||||
return no_node
|
||||
}
|
||||
if (slice.element.kind != .u8 or slice.element.nominal != no_node) {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
count :: usize(length.data.unsigned)
|
||||
extra :: if (slice.sentinel != null) usize(1) else usize(0)
|
||||
if (count > remaining^ or extra > remaining^ - count) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_execution_limit, span)
|
||||
return no_node
|
||||
}
|
||||
if (count != 0 and interpreter.evaluation_element(outcome, module, pointer,
|
||||
count - 1, shape.element, shape.element_array) == null) return no_node
|
||||
elements := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&elements)
|
||||
for (0..(count + extra)) |index| {
|
||||
borrowed :: interpreter.evaluation_element(outcome, module, pointer,
|
||||
index, shape.element, shape.element_array)
|
||||
if (borrowed == null) return no_node
|
||||
child := borrowed?
|
||||
# recursive slice reads may overwrite the accessor's raw-view scratch.
|
||||
scratch_backed :: ir.aggregate(child.type) and child.raw
|
||||
owned []mut ir.Value := &[]
|
||||
if (scratch_backed) {
|
||||
owned = try mem.alloc(ir.Value, state.allocator, child.data.elements.len)
|
||||
memcopy!(owned, child.data.elements)
|
||||
child.data = ir.ValueData{ elements = owned }
|
||||
}
|
||||
defer if (scratch_backed) mem.free(state.allocator, owned)
|
||||
node :: try serialize_value(state, module, checked, outcome, functions,
|
||||
slice.element, child, span, remaining)
|
||||
if (node == no_node) return no_node
|
||||
if (index == count and slice.sentinel != null) {
|
||||
# checker sentinels retain full-width integer bits, including u64 maxima.
|
||||
if (child.data.integer != slice.sentinel?) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .sentinel_mismatch, span)
|
||||
return no_node
|
||||
}
|
||||
}
|
||||
try arraylist.append(&elements, node)
|
||||
}
|
||||
annotation :: try serialized_type(state, checked, type, span, 0)
|
||||
if (annotation == no_node) return no_node
|
||||
child_type :: if (type.nominal == no_node) ast.slice_type(&state.tree, annotation).element
|
||||
else try serialized_type(state, checked, slice.element, span, 0)
|
||||
if (child_type == no_node) return no_node
|
||||
length_node :: try ast.add_integer(state.builder, span, u64(count + extra))
|
||||
array_type :: try ast.add_array_type(state.builder, span, length_node, child_type, false)
|
||||
literal :: try ast.add_array_literal(state.builder, span, elements.items)
|
||||
backing :: try ast.add_typed_constant(state.builder, span, array_type, literal)
|
||||
result = try ast.add_slice_constant(state.builder, span, annotation, backing)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
if (
|
||||
length.data.unsigned != 0
|
||||
and interpreter.evaluation_byte(outcome, module, pointer, usize(length.data.unsigned - 1)) == null
|
||||
@@ -2437,7 +2584,17 @@ 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, outcome, functions, array.element, child, span)
|
||||
node :: try serialize_value(
|
||||
state,
|
||||
module,
|
||||
checked,
|
||||
outcome,
|
||||
functions,
|
||||
array.element,
|
||||
child,
|
||||
span,
|
||||
remaining,
|
||||
)
|
||||
if (node == no_node) return no_node
|
||||
try arraylist.append(&elements, node)
|
||||
}
|
||||
@@ -2474,7 +2631,7 @@ serialize_value proc(
|
||||
return result
|
||||
}
|
||||
child :: ir.aggregate_item(module, value, i)
|
||||
node :: try serialize_value(state, module, checked, outcome, functions, field.type, child, span)
|
||||
node :: try serialize_value(state, module, checked, outcome, functions, field.type, child, span, remaining)
|
||||
if (node == no_node) return no_node
|
||||
name := field.name
|
||||
if (name != null) name = try member_name(state, name?)
|
||||
@@ -2489,7 +2646,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, outcome, functions, channel, child, span)
|
||||
result = try serialize_value(state, module, checked, outcome, functions, channel, child, span, remaining)
|
||||
if (result == no_node) return no_node
|
||||
# keep named success/error types distinct even when their members overlap.
|
||||
annotation := no_node
|
||||
@@ -2518,12 +2675,12 @@ serialize_value proc(
|
||||
value.data.function == maxval!(ir.FunctionId)
|
||||
else value.data.unsigned == maxval!(u64)
|
||||
if (absent) result = try ast.add_literal(state.builder, .null, span)
|
||||
else result = try serialize_value(state, module, checked, outcome, functions, container.element, value, span)
|
||||
else result = try serialize_value(state, module, checked, outcome, functions, container.element, value, span, remaining)
|
||||
} 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, outcome, functions, container.element, child, span)
|
||||
payload :: try serialize_value(state, module, checked, outcome, functions, container.element, child, span, remaining)
|
||||
if (payload == no_node) return no_node
|
||||
name :: try ast.add_generated_identifier(state.builder, span,
|
||||
try strpool.intern(state.strings, "some"), "some")
|
||||
@@ -2557,9 +2714,11 @@ constant_equal proc(state @mut State, left, right ast.NodeId) bool ! Error {
|
||||
kind :: ast.expr_kind(&state.tree, left)
|
||||
if (kind != ast.expr_kind(&state.tree, right)) return false
|
||||
if (kind == .literal_void) return true
|
||||
if (kind == .typed_constant or kind == .union_constant) {
|
||||
a :: if (kind == .union_constant) ast.union_constant(&state.tree, left) else ast.typed_constant(&state.tree, left)
|
||||
b :: if (kind == .union_constant) ast.union_constant(&state.tree, right) else ast.typed_constant(&state.tree, right)
|
||||
if (kind == .typed_constant or kind == .union_constant or kind == .slice_constant) {
|
||||
a :: if (kind == .union_constant) ast.union_constant(&state.tree, left)
|
||||
else if (kind == .slice_constant) ast.slice_constant(&state.tree, left) else ast.typed_constant(&state.tree, left)
|
||||
b :: if (kind == .union_constant) ast.union_constant(&state.tree, right)
|
||||
else if (kind == .slice_constant) ast.slice_constant(&state.tree, right) else ast.typed_constant(&state.tree, right)
|
||||
return try same_type_context(state, a.type, b.type, true) and try constant_equal(state, a.value, b.value)
|
||||
}
|
||||
if (kind == .slice) {
|
||||
@@ -2605,8 +2764,9 @@ constant_hash proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
kind :: ast.expr_kind(&state.tree, id)
|
||||
hash :: enum_index(kind)
|
||||
if (kind == .literal_void) return hash
|
||||
if (kind == .typed_constant or kind == .union_constant) {
|
||||
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id) else ast.typed_constant(&state.tree, id)
|
||||
if (kind == .typed_constant or kind == .union_constant or kind == .slice_constant) {
|
||||
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id)
|
||||
else if (kind == .slice_constant) ast.slice_constant(&state.tree, id) else ast.typed_constant(&state.tree, id)
|
||||
return mix(mix(hash, try type_hash_context(state, value.type, true)), try constant_hash(state, value.value))
|
||||
}
|
||||
if (kind == .slice) {
|
||||
@@ -2743,7 +2903,8 @@ 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, &outcome, functions.items, source_type, value, span)
|
||||
remaining := usize(100000)
|
||||
node :: try serialize_value(state, &module, &checked, &outcome, functions.items, source_type, value, span, &remaining)
|
||||
if (node == no_node) {
|
||||
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
|
||||
@@ -2751,7 +2912,8 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
|
||||
}
|
||||
if (
|
||||
(ir.aggregate(value.type) and source_type.kind != .slice)
|
||||
or (source_type.kind == .slice and ast.expr_kind(&state.tree, node) == .slice)
|
||||
or (source_type.kind == .slice and (ast.expr_kind(&state.tree, node) == .slice
|
||||
or ast.expr_kind(&state.tree, node) == .slice_constant))
|
||||
or source_type.kind == .enumeration or source_type.kind == .void
|
||||
)
|
||||
return Value{ kind = .aggregate, node = node, annotation = result_type }
|
||||
@@ -4847,8 +5009,8 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
return Value{}
|
||||
}
|
||||
match kind {
|
||||
.union_constant: {
|
||||
value :: ast.union_constant(&state.tree, id)
|
||||
.union_constant, .slice_constant: {
|
||||
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id) else ast.slice_constant(&state.tree, id)
|
||||
return Value{ kind = .aggregate, node = id, annotation = value.type }
|
||||
}
|
||||
.typed_constant: {
|
||||
@@ -5335,8 +5497,9 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
.record_type, .array_type, .slice_type, .pointer_type, .function_type, .optional_type,
|
||||
.enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type, .alias_expr:
|
||||
return try builtin(state, id, .type)
|
||||
.typed_constant, .union_constant: {
|
||||
.typed_constant, .union_constant, .slice_constant: {
|
||||
annotation :: if (kind == .union_constant) ast.union_constant(&state.tree, id).type
|
||||
else if (kind == .slice_constant) ast.slice_constant(&state.tree, id).type
|
||||
else ast.typed_constant(&state.tree, id).type
|
||||
return if (is_type(state, annotation)) annotation else try canonical_type(state, annotation)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user