feat: materialize readonly typed compile-time slices
This commit is contained in:
@@ -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