feat: complete memory representation gate

This commit is contained in:
2026-10-26 11:49:25 +01:00
parent 7856f4f963
commit cd64afbb40
16 changed files with 2103 additions and 159 deletions
+136 -15
View File
@@ -1,17 +1,17 @@
#! grammar:
#! * type ..... -> identifier {'.' member} | ('@' | '*') ['mut'] type | '@proc' '(' parameters ')' type | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
#! * type ..... -> identifier {'.' member} | '?' type | ('@' | '*') ['mut'] type | '@proc' '(' parameters ')' type | '[' (expr | '_') ']' ['mut'] type | '[' [';' expr] ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
#! * binding ... -> :: | :=
#! * literal .. -> int | float | string | 'true' | 'false'
#! * literal .. -> int | float | string | 'true' | 'false' | 'null'
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&') expr | expr '^' | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
#! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
#! * block .... -> '{' {stmt} '}'
#! * arm ...... -> block | stmt (single statements become canonical blocks)
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
#! * stmt ..... -> decl | expr | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr arm ['else' arm] | 'while' expr arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
#! braceless guards/iterables require parentheses unless they are calls; else binds to the nearest if.
#! precedence (tightest first): calls/indices/selections/dereference, unary -/!/& higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
#! * stmt ..... -> decl | expr | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
#! braceless guards/iterables require parentheses unless they are calls or optional captures; else binds to the nearest if.
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -/!/& higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
#! binary operators associate left; unary operators associate right.
import "@std/debug"
@@ -30,7 +30,7 @@ NodeId :: distinct u32
no_node :: maxval!(NodeId)
LiteralKind :: enum {
int, float, string, bool
int, float, string, bool, null
}
ExprKind :: enum {
@@ -58,6 +58,11 @@ ExprKind :: enum {
index
selection
invalid
slice_type
optional_type
slice
builtin_call
literal_null
}
DeclKind :: enum {
@@ -89,6 +94,7 @@ UnaryOperator :: enum {
logical_not
address_of
dereference
unwrap
}
BinaryOperator :: enum {
@@ -144,6 +150,11 @@ NodeKind :: enum {
invalid_expr
invalid_stmt
invalid_decl
expr_slice_type
expr_optional_type
expr_slice
expr_builtin_call
literal_null
}
@hide:file
@@ -246,6 +257,21 @@ PointerTypeView :: struct {
many bool
}
SliceTypeView :: struct {
element NodeId
mutable bool
sentinel ?NodeId
}
SliceView :: struct {
base NodeId
start ?NodeId
end ?NodeId
sentinel ?NodeId
}
BuiltinCallView :: struct { name NodeId }
RecordLiteralView :: struct {
span Span
type ?NodeId
@@ -290,6 +316,7 @@ IfView :: struct {
condition NodeId
then_block NodeId
else_block ?NodeId
capture ?NodeId = null
}
AssignmentView :: struct {
@@ -302,6 +329,7 @@ WhileView :: struct {
span Span
condition NodeId
body NodeId
capture ?NodeId = null
}
RangeView :: struct {
@@ -413,6 +441,7 @@ add_literal proc(builder @mut Builder, kind LiteralKind, span Span) NodeId ! mem
.float: .literal_float
.string: .literal_string
.bool: .literal_bool
.null: .literal_null
}
return try add_node(builder, Node{ kind = node_kind }, span)
}
@@ -484,6 +513,39 @@ add_call proc(
}, span)
}
add_builtin_call proc(builder @mut Builder, span Span, name NodeId, arguments []NodeId) NodeId ! mem.AllocError {
id :: try add_call(builder, span, name, arguments)
builder.storage.nodes.items[usize(id)].kind = .expr_builtin_call
return id
}
add_optional_type proc(builder @mut Builder, span Span, element NodeId) NodeId ! mem.AllocError {
return try add_node(builder, Node{ kind = .expr_optional_type, main = NodeData{ node_id = element } }, span)
}
add_slice_type proc(builder @mut Builder, span Span, element NodeId, mutable bool, sentinel ?NodeId) NodeId ! mem.AllocError {
flag u32 :: if (mutable) 1 else 0
raw_sentinel :: if (sentinel) |id| id else no_node
return try add_node(builder, Node{
kind = .expr_slice_type,
main = NodeData{ extra = flag },
data0 = NodeData{ node_id = raw_sentinel },
data1 = NodeData{ node_id = element },
}, span)
}
add_slice proc(builder @mut Builder, span Span, base NodeId, start, end, sentinel ?NodeId) NodeId ! mem.AllocError {
raw_start :: if (start) |id| u32(id) else u32(no_node)
raw_end :: if (end) |id| u32(id) else u32(no_node)
raw_sentinel :: if (sentinel) |id| u32(id) else u32(no_node)
extra :: try add_extra(builder, raw_start)
_ = try add_extra(builder, raw_end)
_ = try add_extra(builder, raw_sentinel)
return try add_node(builder, Node{
kind = .expr_slice, main = NodeData{ node_id = base }, data0 = NodeData{ extra = extra },
}, span)
}
add_array_type proc(
builder @mut Builder, span Span, count ?NodeId, element NodeId, mutable bool,
) NodeId ! mem.AllocError {
@@ -697,9 +759,11 @@ add_if_stmt proc(
else_block ?NodeId,
) NodeId ! mem.AllocError {
alternative :: if (else_block) |id| id else no_node
extra :: try add_extra(builder, u32(condition))
_ = try add_extra(builder, u32(no_node))
return try add_node(builder, Node{
kind = .stmt_if,
main = NodeData{ node_id = condition },
main = NodeData{ extra = extra },
data0 = NodeData{ node_id = then_block },
data1 = NodeData{ node_id = alternative },
}, span)
@@ -731,6 +795,15 @@ add_while_stmt proc(
}, span)
}
set_capture proc(builder @mut Builder, id NodeId, capture NodeId) void {
node :: &builder.storage.nodes.items[usize(id)]
if (node.kind == .stmt_if) builder.storage.extra.items[usize(node.main.extra) + 1] = u32(capture)
else {
debug.assert(node.kind == .stmt_while)
node.data1.node_id = capture
}
}
add_for_stmt proc(
builder @mut Builder, span Span, iterable NodeId, capture, index ?NodeId,
pointer_capture, index_capture bool, body NodeId,
@@ -804,6 +877,7 @@ add_function_decl proc(
_ = try add_extra(builder, u32(return_type))
_ = try add_extra(builder, u32(interned_name))
for (params) |parameter| _ = try add_extra(builder, u32(parameter))
_ = try add_extra(builder, 0)
return try add_node(builder, Node{
kind = .func_decl,
main = NodeData{ extra = u32(start) },
@@ -812,6 +886,18 @@ add_function_decl proc(
}, span)
}
set_function_external proc(builder @mut Builder, id NodeId) void {
node :: builder.storage.nodes.items[usize(id)]
debug.assert(node.kind == .func_decl)
builder.storage.extra.items[usize(node.data1.extra) - 1] = 1
}
function_external proc(tree @Tree, id NodeId) bool {
node :: get_node(&tree.storage, id)
if (node.kind != .func_decl) return false
return tree.storage.extra.items[usize(node.data1.extra) - 1] != 0
}
add_import_decl proc(
builder @mut Builder,
span, path Span,
@@ -980,9 +1066,10 @@ if_stmt proc(tree @Tree, id NodeId) IfView {
debug.assert(node.kind == .stmt_if)
return IfView{
span = get_span(&tree.storage, id),
condition = node.main.node_id,
condition = NodeId(tree.storage.extra.items[usize(node.main.extra)]),
then_block = node.data0.node_id,
else_block = maybe_node_id(u32(node.data1.node_id)),
capture = maybe_node_id(tree.storage.extra.items[usize(node.main.extra) + 1]),
}
}
@@ -1003,6 +1090,7 @@ while_stmt proc(tree @Tree, id NodeId) WhileView {
span = get_span(&tree.storage, id),
condition = node.main.node_id,
body = node.data0.node_id,
capture = maybe_node_id(u32(node.data1.node_id)),
}
}
@@ -1071,14 +1159,15 @@ function_param_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type)
start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra
return usize(node.data1.extra - start)
suffix :: if (node.kind == .func_decl) u32(1) else u32(0)
return usize(node.data1.extra - start - suffix)
}
function_param proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type)
start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra
debug.assert(index < usize(node.data1.extra - start))
debug.assert(index < function_param_count(tree, id))
return NodeId(tree.storage.extra.items[usize(start) + index])
}
@@ -1140,6 +1229,11 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_index: .index
.expr_selection: .selection
.invalid_expr: .invalid
.expr_slice_type: .slice_type
.expr_optional_type: .optional_type
.expr_slice: .slice
.expr_builtin_call: .builtin_call
.literal_null: .literal_null
else: unreachable
}
}
@@ -1187,13 +1281,13 @@ call_expr proc(tree @Tree, id NodeId) CallExprView {
call_arg_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_call)
debug.assert(node.kind == .expr_call or node.kind == .expr_builtin_call)
return usize(node.data1.extra - node.data0.extra)
}
call_arg proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_call)
debug.assert(node.kind == .expr_call or node.kind == .expr_builtin_call)
debug.assert(index < usize(node.data1.extra - node.data0.extra))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
}
@@ -1219,6 +1313,33 @@ pointer_type proc(tree @Tree, id NodeId) PointerTypeView {
}
}
slice_type proc(tree @Tree, id NodeId) SliceTypeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_slice_type)
return SliceTypeView{
element = node.data1.node_id, mutable = node.main.extra != 0,
sentinel = maybe_node_id(u32(node.data0.node_id)),
}
}
slice_expr proc(tree @Tree, id NodeId) SliceView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_slice)
extra :: usize(node.data0.extra)
return SliceView{
base = node.main.node_id,
start = maybe_node_id(tree.storage.extra.items[extra]),
end = maybe_node_id(tree.storage.extra.items[extra + 1]),
sentinel = maybe_node_id(tree.storage.extra.items[extra + 2]),
}
}
builtin_call proc(tree @Tree, id NodeId) BuiltinCallView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_builtin_call)
return BuiltinCallView{ name = node.main.node_id }
}
array_element_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_array_literal)
@@ -1282,7 +1403,7 @@ union_type proc(tree @Tree, id NodeId) UnionTypeView {
type_operand proc(tree @Tree, id NodeId) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias)
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias or node.kind == .expr_optional_type)
return node.main.node_id
}
+10
View File
@@ -94,6 +94,11 @@ Code :: enum {
invalid_dereference
invalid_pointer_assignment
invalid_pointer_access
null_unwrap
invalid_slice_bounds
sentinel_mismatch
overlapping_memory_copy
unsupported_foreign_call
}
Diagnostic :: struct {
@@ -209,5 +214,10 @@ messages :: $enummap.init(Code, []u8, {
invalid_dereference = "dereference requires a pointer",
invalid_pointer_assignment = "pointer assignment requires a mutable pointee",
invalid_pointer_access = "pointer does not refer to live accessible storage",
null_unwrap = "cannot unwrap a null optional",
invalid_slice_bounds = "slice bounds are invalid",
sentinel_mismatch = "slice sentinel does not match the expected value",
overlapping_memory_copy = "memory copy source and destination overlap",
unsupported_foreign_call = "foreign function calls are unsupported by the interpreter",
})
+252 -25
View File
@@ -42,6 +42,9 @@ Address :: struct {
scalar ?@mut ir.Value = null
initialized ?@mut bool = null
raw bool = false
byte_view bool = false
readonly bool = false
bytes []mut ir.Value = &[]
# source handles retain root identity and native offsets, never frame-local address IDs.
root ?@mut ir.Value = null
root_type ir.Type = .invalid
@@ -55,6 +58,14 @@ Pointer :: struct {
root_type ir.Type
root_array ir.ArrayId
byte_offset usize
bytes []mut ir.Value = &[]
readonly bool = false
}
@hide:file
StringView :: struct {
root ir.Value = ir.Value{ type = .u8 }
cells []mut ir.Value
}
@hide:file
@@ -103,6 +114,32 @@ run proc(
globals[i].value = aggregate_view(global.type, global.array_type, aggregate_globals[global_offset..global_offset + cells])
global_offset += cells
}
# byte reinterpretation reuses run-wide scratch; loads are copied before the next instruction.
scratch_size usize := 16
for (module.arrays.items) |shape| {
if (shape.byte_size > scratch_size) scratch_size = shape.byte_size
}
scratch :: try mem.alloc(ir.Value, allocator, scratch_size)
defer mem.free(allocator, scratch)
source_scratch :: try mem.alloc(ir.Value, allocator, scratch_size)
defer mem.free(allocator, source_scratch)
string_size usize := 0
for (module.strings.items) |string| {
if (string.len == maxval!(usize) or string.len + 1 > maxval!(usize) - string_size) return .storage_limit_reached
string_size += string.len + 1
}
string_cells :: try mem.alloc(ir.Value, allocator, string_size)
defer mem.free(allocator, string_cells)
strings :: try mem.alloc(StringView, allocator, module.strings.items.len)
defer mem.free(allocator, strings)
string_offset usize := 0
for (module.strings.items) |string, index| {
cells :: string_cells[string_offset..string_offset + string.len + 1]
for (string.allocation[..string.len]) |byte, i| cells[i] = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = u64(byte) } }
cells[string.len] = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } }
strings[index] = StringView{ cells = cells }
string_offset += cells.len
}
pointers := arraylist.init(Pointer, allocator)
defer arraylist.deinit(&pointers)
frames := arraylist.init(Frame, allocator)
@@ -110,6 +147,9 @@ run proc(
for (frames.items) |frame| free_frame(allocator, frame)
arraylist.deinit(&frames)
}
if (module.functions.items[usize(entry)].external_name.len != 0) {
return try runtime_trap(diagnostic_store, .unsupported_foreign_call, module.functions.items[usize(entry)].span)
}
try push_frame(allocator, module, &frames, entry, ir.no_value, null)
while frames.items.len != 0 {
@@ -210,7 +250,14 @@ run proc(
}
}
.call: {
callee :: frames.items[frame_index].values[usize(instruction.left)].data.function
callable :: frames.items[frame_index].values[usize(instruction.left)]
if (callable.data.function == maxval!(ir.FunctionId)) {
return try runtime_trap(diagnostic_store, .null_unwrap, instruction.span)
}
callee :: callable.data.function
if (module.functions.items[usize(callee)].external_name.len != 0) {
return try runtime_trap(diagnostic_store, .unsupported_foreign_call, instruction.span)
}
try push_frame(allocator, module, &frames, callee, instruction.destination, null)
# frame indices survive stack growth; pointers into frames.items do not.
for (0..instruction.arguments_count) |argument_index| {
@@ -251,6 +298,95 @@ run proc(
continue
}
.union_tag: value = ir.union_tag(module, frames.items[frame_index].values[usize(instruction.left)])
.string_length: {
string :: frames.items[frame_index].values[usize(instruction.left)].data.string
value = ir.Value{ type = .usize, data = ir.ValueData{ unsigned = u64(ir.string_bytes(module, string).len) } }
}
.string_pointer: {
string :: frames.items[frame_index].values[usize(instruction.left)].data.string
view :: &strings[usize(string)]
value = try pointer_value(&pointers, Pointer{
root = &view.root, root_type = .u8, root_array = ir.no_array,
byte_offset = 0, bytes = view.cells, readonly = true,
})
}
.slice_check: {
start :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
end :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
if (start > end) return try runtime_trap(diagnostic_store, .invalid_slice_bounds, instruction.span)
if (instruction.extra != ir.no_value and end > frames.items[frame_index].values[usize(instruction.extra)].data.unsigned) {
return try runtime_trap(diagnostic_store, .invalid_slice_bounds, instruction.span)
}
continue
}
.optional_check: {
operand :: frames.items[frame_index].values[usize(instruction.left)]
absent := operand.data.unsigned == maxval!(u64)
if (operand.type == .bool) absent = !operand.data.boolean
else if (operand.type == .function) absent = operand.data.function == maxval!(ir.FunctionId)
if (absent) return try runtime_trap(diagnostic_store, .null_unwrap, instruction.span)
continue
}
.pointer_cast: {
value = frames.items[frame_index].values[usize(instruction.left)]
if (value.type == .function) {
bits :: if (value.data.function == maxval!(ir.FunctionId)) maxval!(u64) else u64(value.data.function)
value.data = ir.ValueData{ unsigned = bits }
}
value.type = instruction.type
}
.sentinel_check: {
handle :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
if (handle >= u64(pointers.items.len)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
address :: pointer_address(module, pointers.items[usize(handle)], instruction.pointee, instruction.array_type)
if (address == null) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
actual :: read_address(module, address?, scratch)
expected :: frames.items[frame_index].values[usize(instruction.right)]
if (!equal_value(module, actual, expected)) return try runtime_trap(diagnostic_store, .sentinel_mismatch, instruction.span)
continue
}
.memory_copy, .memory_set: {
count :: frames.items[frame_index].values[usize(instruction.extra)].data.unsigned
width :: ir.storage_size(module, instruction.pointee, instruction.array_type)
if (count == 0 or width == 0) continue
if (count > u64(divtrunc!(maxval!(usize), width))) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
size :: usize(count) * width
destination_handle :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
if (destination_handle >= u64(pointers.items.len)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
destination := pointers.items[usize(destination_handle)]
if (destination.readonly or !pointer_range(module, destination, size)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
source := destination
if (instruction.op == .memory_copy) {
source_handle :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
if (source_handle >= u64(pointers.items.len)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
source = pointers.items[usize(source_handle)]
if (!pointer_range(module, source, size)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
if (ptrcast!(@usize, &source.root)^ == ptrcast!(@usize, &destination.root)^) {
distance :: if (source.byte_offset > destination.byte_offset) source.byte_offset - destination.byte_offset else destination.byte_offset - source.byte_offset
if (distance < size) return try runtime_trap(diagnostic_store, .overlapping_memory_copy, instruction.span)
}
}
destination_size :: pointer_size(module, destination)
destination_bytes :: scratch[..destination_size]
mem.set(destination_bytes, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
ir.write_storage(module, destination_bytes, destination.root^)
target :: destination_bytes[destination.byte_offset..destination.byte_offset + size]
if (instruction.op == .memory_copy) {
source_bytes := source.bytes
if (source_bytes.len == 0) {
source_bytes = source_scratch[..pointer_size(module, source)]
mem.set(source_bytes, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
ir.write_storage(module, source_bytes, source.root^)
}
memcopy!(target, source_bytes[source.byte_offset..source.byte_offset + size])
} else {
element :: frames.items[frame_index].values[usize(instruction.right)]
for (0..usize(count)) |index| ir.write_storage(module, target[index * width..], element)
}
restored :: ir.read_storage(module, destination.root_type, destination.root_array, destination_bytes, true)
copy_value(module, destination.root, restored)
continue
}
.index: {
base :: frames.items[frame_index].values[usize(instruction.left)]
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
@@ -324,15 +460,15 @@ run proc(
if (index >= u64(shape.len)) {
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
}
width :: if (base.raw) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
width :: if (base.raw or base.byte_view) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
start :: usize(index) * width
address := base
address.type = instruction.pointee
address.array_type = instruction.array_type
address.cells = base.cells[start..start + width]
if (!base.byte_view) address.cells = base.cells[start..start + width]
address.scalar = null
address.byte_offset += usize(index) * ir.storage_size(module, shape.element, shape.element_array)
if (!address.raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
if (!address.raw and !address.byte_view and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
}
@@ -345,21 +481,21 @@ run proc(
if base.initialized |initialized| {
if (!initialized^) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span)
}
if (!ir.union_active(module, aggregate_view(.record, base.array_type, base.cells), usize(instruction.slot))) {
if (!ir.union_active(module, read_address(module, base, scratch), usize(instruction.slot))) {
return try runtime_trap(diagnostic_store, .inactive_union_field, instruction.span)
}
}
raw :: base.raw or shape.is_union
width :: if (raw) ir.storage_size(module, field.type, field.array_type) else if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
width :: if (raw or base.byte_view) ir.storage_size(module, field.type, field.array_type) else if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
start :: if (raw) field.byte_offset else field.offset
address := base
address.type = instruction.pointee
address.array_type = instruction.array_type
address.cells = base.cells[start..start + width]
if (!base.byte_view) address.cells = base.cells[start..start + width]
address.scalar = null
address.raw = raw
address.byte_offset += field.byte_offset
if (!raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
if (!raw and !address.byte_view and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
}
@@ -374,6 +510,8 @@ run proc(
root_type = address.root_type,
root_array = address.root_array,
byte_offset = address.byte_offset,
readonly = address.readonly,
bytes = address.bytes,
})
}
.pointer_address: {
@@ -382,17 +520,7 @@ run proc(
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
location :: pointers.items[usize(handle)]
root := Address{
type = location.root_type,
array_type = location.root_array,
cells = &[],
root = location.root,
root_type = location.root_type,
root_array = location.root_array,
}
if (ir.aggregate(root.type)) root.cells = root.root?^.data.elements
else root.scalar = root.root
if (project_pointer(module, root, location.byte_offset, instruction.pointee, instruction.array_type)) |address| {
if (pointer_address(module, location, instruction.pointee, instruction.array_type)) |address| {
frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
} else {
@@ -411,7 +539,7 @@ run proc(
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
address.byte_offset += usize(index) * stride
if (address.byte_offset > ir.storage_size(module, address.root_type, address.root_array)) {
if (address.byte_offset > pointer_size(module, address)) {
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
}
value = try pointer_value(&pointers, address)
@@ -426,13 +554,11 @@ run proc(
}
}
if (instruction.op == .load_address) {
if (address.raw) value = ir.read_storage(module, address.type, address.array_type, address.cells, true)
else value = if (ir.aggregate(address.type)) aggregate_view(address.type, address.array_type, address.cells) else address.scalar?^
value = read_address(module, address, scratch)
} else {
source :: frames.items[frame_index].values[usize(instruction.right)]
if (address.raw) ir.write_storage(module, address.cells, source)
else if (ir.aggregate(address.type)) ir.copy_aggregate(module, aggregate_view(address.type, address.array_type, address.cells), source)
else address.scalar?^ = source
if (address.readonly) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
write_address(module, address, source, scratch)
continue
}
}
@@ -526,6 +652,11 @@ run proc(
.not_equal: left.data.boolean != right.data.boolean
else: unreachable
}
.function: result = match instruction.op {
.equal: left.data.function == right.data.function
.not_equal: left.data.function != right.data.function
else: unreachable
}
.pointer: result = match instruction.op {
.equal: left.data.unsigned == right.data.unsigned
.not_equal: left.data.unsigned != right.data.unsigned
@@ -543,6 +674,102 @@ run proc(
unreachable
}
@hide:file
pointer_size proc(module @ir.Module, pointer Pointer) usize {
if (pointer.bytes.len != 0) return pointer.bytes.len
return ir.storage_size(module, pointer.root_type, pointer.root_array)
}
@hide:file
pointer_range proc(module @ir.Module, pointer Pointer, size usize) bool {
bound :: pointer_size(module, pointer)
return pointer.byte_offset <= bound and size <= bound - pointer.byte_offset
}
@hide:file
pointer_address proc(module @ir.Module, pointer Pointer, type ir.Type, shape ir.ArrayId) ?Address {
size :: ir.storage_size(module, type, shape)
if (!pointer_range(module, pointer, size)) return null
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,
}
if (pointer.bytes.len != 0) {
base.type = type
base.array_type = shape
base.cells = pointer.bytes[pointer.byte_offset..pointer.byte_offset + size]
base.raw = true
base.byte_offset = pointer.byte_offset
return base
}
if (ir.aggregate(base.type)) base.cells = pointer.root^.data.elements
else base.scalar = pointer.root
if (project_pointer(module, base, pointer.byte_offset, type, shape)) |address| return address
# differently typed projections retain the original root and native byte offset.
base.type = type
base.array_type = shape
base.byte_offset = pointer.byte_offset
base.byte_view = true
base.scalar = null
return base
}
@hide:file
read_address proc(module @ir.Module, address Address, scratch []mut ir.Value) ir.Value {
if (address.byte_view) {
size :: ir.storage_size(module, address.root_type, address.root_array)
cells :: scratch[..size]
mem.set(cells, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
ir.write_storage(module, cells, address.root?^)
return ir.read_storage(module, address.type, address.array_type, cells[address.byte_offset..], true)
}
if (address.raw) return ir.read_storage(module, address.type, address.array_type, address.cells, true)
if (ir.aggregate(address.type)) return aggregate_view(address.type, address.array_type, address.cells)
return address.scalar?^
}
@hide:file
write_address proc(module @ir.Module, address Address, source ir.Value, scratch []mut ir.Value) void {
if (address.byte_view) {
size :: ir.storage_size(module, address.root_type, address.root_array)
cells :: scratch[..size]
mem.set(cells, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
ir.write_storage(module, cells, address.root?^)
ir.write_storage(module, cells[address.byte_offset..], source)
restored :: ir.read_storage(module, address.root_type, address.root_array, cells, true)
copy_value(module, address.root?, restored)
} else if (address.raw) ir.write_storage(module, address.cells, source)
else if (ir.aggregate(address.type)) ir.copy_aggregate(module, aggregate_view(address.type, address.array_type, address.cells), source)
else address.scalar?^ = source
}
@hide:file
equal_value proc(module @ir.Module, left, right ir.Value) bool {
if (ir.aggregate(left.type)) {
shape :: module.arrays.items[usize(left.array_type)]
if (shape.is_union) {
for (left.data.elements) |byte, index| {
if (byte.data.unsigned != right.data.elements[index].data.unsigned) return false
}
return true
}
count :: if (shape.kind == .record) shape.fields.len else shape.len
for (0..count) |index| {
if (!equal_value(module, ir.aggregate_item(module, left, index), ir.aggregate_item(module, right, index))) return false
}
return true
}
return match left.type {
.float: left.data.real == right.data.real
.bool: left.data.boolean == right.data.boolean
.function: left.data.function == right.data.function
.string: left.data.string == right.data.string
.void: true
else: left.data.unsigned == right.data.unsigned
}
}
@hide:file
pointer_value proc(pointers @mut arraylist.ArrayList(Pointer), address Pointer) ir.Value ! mem.AllocError {
# ponytail: linear location interning; use a location-keyed map if distinct-address workloads grow.
+19 -2
View File
@@ -146,6 +146,14 @@ Op :: enum {
pointer_from_address
pointer_address
pointer_offset
pointer_cast
string_pointer
string_length
slice_check
sentinel_check
optional_check
memory_copy
memory_set
negate
convert
logical_not
@@ -172,6 +180,7 @@ Instruction :: struct {
destination ValueId = no_value
left ValueId = no_value
right ValueId = no_value
extra ValueId = no_value
slot u32 = 0
constant Value = Value{}
arguments_start usize = 0
@@ -205,6 +214,8 @@ Block :: struct {
#! producers use add_local, add_parameter, and emit; derived metadata is read-only to callers.
Function :: struct {
parameters arraylist.ArrayList(Type)
# owned by the module allocator; empty for interpreted/native-defined functions.
external_name []u8 = ""
locals arraylist.ArrayList(Type)
result Type = .void
result_array_type ArrayId = no_array
@@ -435,7 +446,7 @@ read_storage proc(module @Module, type Type, shape ArrayId, cells []mut Value, r
.float: data = ValueData{ real = ptrcast!(@f64, &bits)^ }
.bool: data = ValueData{ boolean = (bits & 1) != 0 }
.string: data = ValueData{ string = StringId(bits) }
.function: data = ValueData{ function = FunctionId(bits) }
.function: data = ValueData{ unsigned = bits }
.void: return Value{}
else: {}
}
@@ -462,7 +473,7 @@ write_storage proc(module @Module, cells []mut Value, value Value) void {
.float: bits = ptrcast!(@u64, &value.data.real)^
.bool: bits = if (value.data.boolean) 1 else 0
.string: bits = u64(value.data.string)
.function: bits = u64(value.data.function)
.function: bits = if (value.data.function == maxval!(FunctionId)) maxval!(u64) else u64(value.data.function)
.pointer: bits = value.data.unsigned
.void: return
else: bits = ptrcast!(@u64, &value.data.integer)^
@@ -568,6 +579,11 @@ emit proc(
and operation.op != .assign_local
and operation.op != .reset_local
and operation.op != .store_address
and operation.op != .slice_check
and operation.op != .sentinel_check
and operation.op != .optional_check
and operation.op != .memory_copy
and operation.op != .memory_set
)
count := body.value_count
if (produces_value) {
@@ -618,6 +634,7 @@ deinit proc(module @mut Module) void {
arraylist.deinit(&function.locals)
arraylist.deinit(&function.blocks)
arraylist.deinit(&function.array_storage)
if (function.external_name.len != 0) mem.free(module.allocator, function.external_name)
}
for (module.strings.items) |string|
mem.free(module.allocator, string.allocation)
+4
View File
@@ -21,6 +21,8 @@ SourceError :: enum { source_too_large }
@hide:file
keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
{ "proc", .proc },
{ "c_func", .c_func },
{ "null", .null },
{ "return", .return },
{ "break", .break },
{ "continue", .continue },
@@ -297,6 +299,8 @@ scan proc(
'@': .at
'&': .ampersand
'^': .caret
'?': .question
';': .semicolon
'(': .open_paren
')': .close_paren
'[': .open_bracket
+3
View File
@@ -29,6 +29,8 @@ TokenKind :: enum {
distinct, alias
import, test
mut
c_func
null
# literals
ident, string
@@ -49,6 +51,7 @@ TokenKind :: enum {
range, range_inclusive
at, ampersand, caret
question, semicolon
pipe
# logical
+181 -4
View File
@@ -178,6 +178,9 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
}
.pointer_address: {
pointer :: try load(state, instruction.left)
missing :: fresh(state)
try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {missing, pointer})
try check_failure(state, missing, .invalid_pointer_access, instruction.span)
address :: fresh(state)
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
@@ -189,6 +192,56 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
try write_type(output, state.module, Type{ kind = instruction.pointee, shape = instruction.array_type })
try io.print(", ptr %r{}, i64 %r{}\n", output, {pointer, index})
}
.pointer_cast: result = try load(state, instruction.left)
.string_pointer, .string_length: {
string :: try load(state, instruction.left)
field usize :: if (instruction.op == .string_pointer) 0 else 1
try io.print(" %r{} = extractvalue %string %r{}, {}\n", output, {result, string, field})
}
.optional_check: {
operand :: try load(state, instruction.left)
failed :: fresh(state)
if (state.values[usize(instruction.left)].kind == .bool) {
try io.print(" %r{} = xor i1 %r{}, true\n", output, {failed, operand})
} else try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {failed, operand})
try check_failure(state, failed, .null_unwrap, instruction.span)
return
}
.slice_check: {
start :: try load(state, instruction.left)
end :: try load(state, instruction.right)
reversed :: fresh(state)
try io.print(" %r{} = icmp ugt i64 %r{}, %r{}\n", output, {reversed, start, end})
failed := reversed
if (instruction.extra != ir.no_value) {
bound :: try load(state, instruction.extra)
outside :: fresh(state)
failed = fresh(state)
try io.print(" %r{} = icmp ugt i64 %r{}, %r{}\n", output, {outside, end, bound})
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {failed, reversed, outside})
}
try check_failure(state, failed, .invalid_slice_bounds, instruction.span)
return
}
.sentinel_check: {
pointer :: try load(state, instruction.left)
expected :: try load(state, instruction.right)
missing :: fresh(state)
try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {missing, pointer})
try check_failure(state, missing, .invalid_pointer_access, instruction.span)
actual :: fresh(state)
element :: Type{ kind = instruction.pointee, shape = instruction.array_type }
try io.print(" %r{} = load ", output, {actual})
try write_type(output, state.module, element)
try io.print(", ptr %r{}, align 1\n", output, {pointer})
failed :: try sentinel_difference(state, element, actual, expected)
try check_failure(state, failed, .sentinel_mismatch, instruction.span)
return
}
.memory_copy, .memory_set: {
try emit_memory(state, instruction)
return
}
.load_address, .store_address: {
address :: try load(state, instruction.left)
pointer :: fresh(state)
@@ -204,7 +257,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
}
try io.print(" %r{} = load ", output, {result})
try write_type(output, state.module, type)
try io.print(", ptr %r{}\n", output, {pointer})
try io.print(", ptr %r{}, align 1\n", output, {pointer})
}
.call: {
callee :: try load(state, instruction.left)
@@ -216,12 +269,12 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
}
if (instruction.type != .void) try io.print(" %r{} = call ", output, {result})
else try io.write_all(output, " call ")
try write_type(output, state.module, type)
try write_abi_result(output, state.module, type)
try io.print(" %r{}(", output, {callee})
for (0..instruction.arguments_count) |index| {
if (index != 0) try io.write_all(output, ", ")
operand :: block.arguments.items[instruction.arguments_start + index]
try write_type(output, state.module, state.values[usize(operand)])
try write_abi_parameter(output, state.module, state.values[usize(operand)])
try io.print(" %r{}", output, {first_argument + index})
}
try io.write_all(output, ")\n")
@@ -314,6 +367,130 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
try store(state, instruction.destination, result)
}
#! compares the complete sentinel value, including nested aggregate fields.
@hide:file
sentinel_difference proc(state @mut State, type Type, actual, expected usize) usize ! io.WriteError {
output :: state.output
if (ir.aggregate(type.kind)) {
shape :: state.module.arrays.items[usize(type.shape)]
count usize :: if (shape.is_union) divtrunc!(shape.byte_size, 8) else if (shape.kind == .record) shape.fields.len else shape.len
result := fresh(state)
try io.print(" %r{} = icmp ne i1 false, false\n", output, {result})
for (0..count) |index| {
child :: if (shape.is_union) Type{ kind = .u64 } else if (shape.kind == .record) Type{ kind = shape.fields[index].type, shape = shape.fields[index].array_type } else Type{ kind = shape.element, shape = shape.element_array }
left :: fresh(state)
right :: fresh(state)
try io.print(" %r{} = extractvalue ", output, {left})
try write_type(output, state.module, type)
try io.print(" %r{}, ", output, {actual})
if (shape.is_union) try io.write_all(output, "0, ")
try io.print("{}\n %r{} = extractvalue ", output, {index, right})
try write_type(output, state.module, type)
try io.print(" %r{}, ", output, {expected})
if (shape.is_union) try io.write_all(output, "0, ")
try io.print("{}\n", output, {index})
different :: try sentinel_difference(state, child, left, right)
combined :: fresh(state)
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {combined, result, different})
result = combined
}
return result
}
if (type.kind == .string) {
left_pointer :: fresh(state)
right_pointer :: fresh(state)
left_length :: fresh(state)
right_length :: fresh(state)
try io.print(" %r{} = extractvalue %string %r{}, 0\n", output, {left_pointer, actual})
try io.print(" %r{} = extractvalue %string %r{}, 0\n", output, {right_pointer, expected})
try io.print(" %r{} = extractvalue %string %r{}, 1\n", output, {left_length, actual})
try io.print(" %r{} = extractvalue %string %r{}, 1\n", output, {right_length, expected})
pointer_difference :: try sentinel_difference(state, Type{ kind = .pointer }, left_pointer, right_pointer)
length_difference :: try sentinel_difference(state, Type{ kind = .usize }, left_length, right_length)
result :: fresh(state)
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {result, pointer_difference, length_difference})
return result
}
result :: fresh(state)
if (type.kind == .void) {
try io.print(" %r{} = icmp ne i1 false, false\n", output, {result})
return result
}
opcode []u8 :: if (type.kind == .float) "fcmp une" else "icmp ne"
try io.print(" %r{} = {s} ", output, {result, opcode})
try write_type(output, state.module, type)
try io.print(" %r{}, %r{}\n", output, {actual, expected})
return result
}
@hide:file
emit_memory proc(state @mut State, instruction ir.Instruction) void ! io.WriteError {
output :: state.output
element :: Type{ kind = instruction.pointee, shape = instruction.array_type }
width :: ir.storage_size(state.module, element.kind, element.shape)
if (width == 0) return
destination :: try load(state, instruction.left)
operand :: try load(state, instruction.right)
count :: try load(state, instruction.extra)
size := count
if (width != 1) {
pair :: fresh(state)
overflow :: fresh(state)
size = fresh(state)
try io.print(" %r{} = call {{ i64, i1 }} @llvm.umul.with.overflow.i64(i64 %r{}, i64 {})\n", output, {pair, count, width})
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 1\n", output, {overflow, pair})
try check_failure(state, overflow, .integer_overflow, instruction.span)
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 0\n", output, {size, pair})
}
nonempty :: fresh(state)
destination_null :: fresh(state)
try io.print(" %r{} = icmp ne i64 %r{}, 0\n", output, {nonempty, count})
try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {destination_null, destination})
missing := destination_null
if (instruction.op == .memory_copy) {
source_null :: fresh(state)
missing = fresh(state)
try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {source_null, operand})
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {missing, destination_null, source_null})
}
invalid :: fresh(state)
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {invalid, nonempty, missing})
try check_failure(state, invalid, .invalid_pointer_access, instruction.span)
if (instruction.op == .memory_copy) {
destination_bits :: fresh(state)
source_bits :: fresh(state)
ordered :: fresh(state)
forward :: fresh(state)
backward :: fresh(state)
distance :: fresh(state)
overlap :: fresh(state)
try io.print(" %r{} = ptrtoint ptr %r{} to i64\n", output, {destination_bits, destination})
try io.print(" %r{} = ptrtoint ptr %r{} to i64\n", output, {source_bits, operand})
try io.print(" %r{} = icmp uge i64 %r{}, %r{}\n", output, {ordered, destination_bits, source_bits})
try io.print(" %r{} = sub i64 %r{}, %r{}\n", output, {forward, destination_bits, source_bits})
try io.print(" %r{} = sub i64 %r{}, %r{}\n", output, {backward, source_bits, destination_bits})
try io.print(" %r{} = select i1 %r{}, i64 %r{}, i64 %r{}\n", output, {distance, ordered, forward, backward})
try io.print(" %r{} = icmp ult i64 %r{}, %r{}\n", output, {overlap, distance, size})
try check_failure(state, overlap, .overlapping_memory_copy, instruction.span)
try io.print(" call void @llvm.memcpy.p0.p0.i64(ptr %r{}, ptr %r{}, i64 %r{}, i1 false)\n", output, {destination, operand, size})
return
}
label :: fresh(state)
index :: fresh(state)
next :: fresh(state)
active :: fresh(state)
pointer :: fresh(state)
try io.print(" br label %set_start{}\nset_start{}:\n br label %set_test{}\nset_test{}:\n", output, {label, label, label, label})
try io.print(" %r{} = phi i64 [ 0, %set_start{} ], [ %r{}, %set_body{} ]\n", output, {index, label, next, label})
try io.print(" %r{} = icmp ult i64 %r{}, %r{}\n", output, {active, index, count})
try io.print(" br i1 %r{}, label %set_body{}, label %set_end{}\nset_body{}:\n", output, {active, label, label, label})
try io.print(" %r{} = getelementptr ", output, {pointer})
try write_type(output, state.module, element)
try io.print(", ptr %r{}, i64 %r{}\n", output, {destination, index})
try store_payload(state, element, operand, pointer)
try io.print(" %r{} = add i64 %r{}, 1\n br label %set_test{}\nset_end{}:\n", output, {next, index, label, label})
}
@hide:file
check_union proc(state @mut State, pointer usize, shape ir.ArrayType, field usize, span diagnostics.Span) void ! io.WriteError {
if (shape.tag_type == .void) return
@@ -351,7 +528,7 @@ store_payload proc(state @mut State, type Type, value, pointer usize) void ! io.
}
try io.write_all(state.output, " store ")
try write_type(state.output, state.module, type)
try io.print(" %r{}, ptr %r{}\n", state.output, {value, pointer})
try io.print(" %r{}, ptr %r{}, align 1\n", state.output, {value, pointer})
}
@hide:file
+106 -16
View File
@@ -46,7 +46,7 @@ emit proc(
try io.write_all(output, runtime)
for (sources) |source, index| {
try io.print("\n@p{} = private constant ", output, {index})
try bytes_constant(output, source.path)
try bytes_constant(output, source.path, false)
}
try io.print("\n@paths = private constant [{} x %string] [", output, {sources.len})
for (sources) |source, index| {
@@ -54,14 +54,23 @@ emit proc(
try io.print("%string {{ ptr @p{}, i64 {} }}", output, {index, source.path.len})
}
try io.write_all(output, "]\n")
for (module.strings.items) |_, index| {
for (module.strings.items) |string, index| {
try io.print("@s{} = private constant ", output, {index})
try bytes_constant(output, ir.string_bytes(module, ir.StringId(index)))
try bytes_constant(output, string.allocation[..string.len], true)
try io.write_all(output, "\n")
}
for (diagnostic_store.list.items) |diagnostic, index| {
try io.print("@d{} = private constant ", output, {index})
try bytes_constant(output, enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?)
try bytes_constant(output, enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?, false)
try io.write_all(output, "\n")
}
memory_codes [6]diagnostics.Code :: [
.null_unwrap, .invalid_slice_bounds, .sentinel_mismatch,
.overlapping_memory_copy, .unsupported_foreign_call, .invalid_pointer_access,
]
for (memory_codes) |code| {
try io.print("@memory_{s} = private constant ", output, {memory_symbol(code)})
try bytes_constant(output, enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?, false)
try io.write_all(output, "\n")
}
try emit_reporting(output, diagnostic_store, line_starts.items, sources, primary)
@@ -77,6 +86,14 @@ emit proc(
else try emit_array_printer(output, module, ir.ArrayId(index))
}
for (module.functions.items) |function, index| {
if (function.external_name.len != 0) {
duplicate := false
for (module.functions.items[..index]) |previous| {
if (mem.eql(previous.external_name, function.external_name)) duplicate = true
}
if (duplicate or mem.eql(function.external_name, "write") or mem.eql(function.external_name, "__error")
or mem.eql(function.external_name, "snprintf") or mem.eql(function.external_name, "exit")) continue
}
values :: try mem.alloc(Type, allocator, function.value_count)
defer mem.free(allocator, values)
mem.set(values, Type{})
@@ -97,8 +114,10 @@ emit proc(
if (result.kind != .void) try io.write_all(output, " %r0 = ")
else try io.write_all(output, " ")
try io.write_all(output, "call ")
try write_type(output, module, result)
try io.print(" @f{}()\n", output, {u32(entry)})
try write_abi_result(output, module, result)
try io.write_all(output, " ")
try function_symbol(output, module, entry)
try io.write_all(output, "()\n")
if (result.kind != .void and result.kind != .invalid) {
try print_call(output, module, result, 0)
try io.write_all(output, " call void @out(i32 1, ptr @newline, i64 1)\n")
@@ -113,13 +132,15 @@ emit proc(
}
@hide:file
bytes_constant proc(output io.Writer, bytes []u8) void ! io.WriteError {
try io.print("[{} x i8] c\"", output, {bytes.len})
bytes_constant proc(output io.Writer, bytes []u8, terminated bool) void ! io.WriteError {
size usize :: if (terminated) bytes.len + 1 else bytes.len
try io.print("[{} x i8] c\"", output, {size})
digits :: "0123456789ABCDEF"
for (bytes) |byte| {
escaped [3]u8 :: ['\\', digits[usize(divtrunc!(byte, 16))], digits[usize(rem!(byte, 16))]]
try io.write_all(output, (&escaped)[..])
}
if (terminated) try io.write_all(output, "\\00")
try io.write_all(output, "\"")
}
@@ -161,6 +182,45 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
}
}
@hide:file
abi_extension proc(kind ir.Type) []u8 {
return match kind {
.i8, .i16: "signext"
.u8, .u16, .bool: "zeroext"
else: ""
}
}
@hide
write_abi_result proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteError {
extension :: abi_extension(type.kind)
if (extension.len != 0) try io.print("{s} ", output, {extension})
try write_type(output, module, type)
}
@hide
write_abi_parameter proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteError {
try write_type(output, module, type)
extension :: abi_extension(type.kind)
if (extension.len != 0) try io.print(" {s}", output, {extension})
}
@hide
function_symbol proc(output io.Writer, module @ir.Module, id ir.FunctionId) void ! io.WriteError {
name :: module.functions.items[usize(id)].external_name
if (name.len == 0) {
try io.print("@f{}", output, {u32(id)})
return
}
try io.write_all(output, "@\"")
digits :: "0123456789ABCDEF"
for (name) |byte| {
escaped [3]u8 :: ['\\', digits[usize(divtrunc!(byte, 16))], digits[usize(rem!(byte, 16))]]
try io.write_all(output, (&escaped)[..])
}
try io.write_all(output, "\"")
}
@hide:file
local_type proc(function @ir.Function, index usize) Type {
type := Type{ kind = function.locals.items[index] }
@@ -252,7 +312,7 @@ emit_global proc(output io.Writer, module @ir.Module, global ir.Global, index us
try io.print(" call void @f{}()\n", output, {u32(global.initializer)})
} else {
try io.write_all(output, " %value = call ")
try write_type(output, module, type)
try write_abi_result(output, module, type)
try io.print(" @f{}()\n store ", output, {u32(global.initializer)})
try write_type(output, module, type)
try io.print(" %value, ptr @g{}\n", output, {index})
@@ -406,6 +466,19 @@ check_local proc(state @State, slot u32, read bool, span diagnostics.Span) void
try io.print(" call void @check_init(ptr %i{}, i1 {}, i64 {}, i64 {}, i64 {})\n", state.output, {slot, read, usize(span.source), position.line, position.column})
}
@hide:file
memory_symbol proc(code diagnostics.Code) []u8 {
return match code {
.null_unwrap: "null_unwrap"
.invalid_slice_bounds: "invalid_slice_bounds"
.sentinel_mismatch: "sentinel_mismatch"
.overlapping_memory_copy: "overlapping_memory_copy"
.unsupported_foreign_call: "unsupported_foreign_call"
.invalid_pointer_access: "invalid_pointer_access"
else: unreachable
}
}
@hide:file
trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError {
position :: location(state.line_starts, span)
@@ -413,11 +486,14 @@ trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.
.integer_overflow: "overflow"
.index_out_of_bounds: "bounds"
.inactive_union_field: "inactive_union"
else: unreachable
else: ""
}
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
length :: message.len
try io.print(" call void @trap(i64 -1, i64 {}, i64 {}, i64 {}, ptr @{s}, i64 {})\n unreachable\n", state.output, {usize(span.source), position.line, position.column, symbol, length})
try io.print(" call void @trap(i64 -1, i64 {}, i64 {}, i64 {}, ptr @", state.output, {usize(span.source), position.line, position.column})
if (symbol.len != 0) try io.write_all(state.output, symbol)
else try io.print("memory_{s}", state.output, {memory_symbol(code)})
try io.print(", i64 {})\n unreachable\n", state.output, {length})
}
@hide
@@ -439,7 +515,14 @@ write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void !
}
.bool: try io.print("{}", output, {value.data.boolean})
.string: try io.print("{{ ptr @s{}, i64 {} }}", output, {u32(value.data.string), ir.string_bytes(module, value.data.string).len})
.function: try io.print("@f{}", output, {u32(value.data.function)})
.pointer: {
if (value.data.unsigned == maxval!(u64)) try io.write_all(output, "null")
else unreachable
}
.function: {
if (value.data.function == maxval!(ir.FunctionId)) try io.write_all(output, "null")
else try function_symbol(output, module, value.data.function)
}
.array: {
shape :: module.arrays.items[usize(value.array_type)]
try io.write_all(output, "[")
@@ -482,14 +565,21 @@ write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void !
@hide:file
emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.WriteError {
output :: state.output
try io.write_all(output, "define internal ")
try write_type(output, state.module, Type{ kind = function.result, shape = function.result_array_type })
try io.print(" @f{}(", output, {id})
if (function.external_name.len != 0) try io.write_all(output, "declare ")
else try io.write_all(output, "define internal ")
try write_abi_result(output, state.module, Type{ kind = function.result, shape = function.result_array_type })
try io.write_all(output, " ")
try function_symbol(output, state.module, ir.FunctionId(id))
try io.write_all(output, "(")
for (function.parameters.items) |_, index| {
if (index != 0) try io.write_all(output, ", ")
try write_type(output, state.module, local_type(function, index))
try write_abi_parameter(output, state.module, local_type(function, index))
try io.print(" %p{}", output, {index})
}
if (function.external_name.len != 0) {
try io.write_all(output, ")\n")
return
}
try io.write_all(output, ") {\nentry:\n")
for (function.locals.items) |_, index| {
type :: local_type(function, index)
+1
View File
@@ -8,6 +8,7 @@ runtime ::
`declare i32 @snprintf(ptr, i64, ptr, ...)
`declare void @exit(i32) noreturn
`declare double @llvm.trunc.f64(double)
`declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)
`declare { i8, i1 } @llvm.sadd.with.overflow.i8(i8, i8)
`declare { i8, i1 } @llvm.ssub.with.overflow.i8(i8, i8)
`declare { i8, i1 } @llvm.smul.with.overflow.i8(i8, i8)
+409 -21
View File
@@ -38,6 +38,7 @@ State :: struct {
block ir.BlockId = ir.BlockId(0)
terminated bool = false
loop ?LoopTargets = null
return_type typecheck.Type = typecheck.Type{}
}
#! lowers checked source into caller-owned executable storage; release with ir.deinit.
@@ -136,6 +137,8 @@ lower_type proc(state @State, type typecheck.Type) ir.Type {
if (type.kind == .enumeration) {
return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
}
if (type.kind == .slice) return .record
if (type.kind == .optional) return lower_optional_type(state, typecheck.array_type(state.checked, type.array_type).element)
return match type.kind {
.int: .int
.usize: .usize
@@ -156,13 +159,21 @@ lower_type proc(state @State, type typecheck.Type) ir.Type {
.void: .void
.function: .function
.enumeration: unreachable
.none, .invalid, .range, .type_value: .invalid
.none, .invalid, .range, .type_value, .null, .anyopaque: .invalid
.slice, .optional: unreachable
}
}
@hide:file
lower_optional_type proc(state @State, child typecheck.Type) ir.Type {
if (child.kind == .pointer or child.kind == .function) return lower_type(state, child)
return .record
}
@hide:file
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
if (type.kind == .record) return try prepare_record(state, type.record_type)
if (type.kind == .slice or type.kind == .optional) return try prepare_container(state, type)
if (type.kind != .array) return ir.no_array
# checker history includes unresolved shapes; materialize only final used types.
return try prepare_array(state, type.array_type)
@@ -173,7 +184,7 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
descriptor :: typecheck.array_type(state.checked, id)
child := ir.no_array
if (descriptor.element.kind == .array or descriptor.element.kind == .record) child = try array_shape(state, descriptor.element)
child = try array_shape(state, descriptor.element)
result :: try ir.add_array(
state.module,
lower_type(state, descriptor.element),
@@ -185,6 +196,22 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
return result
}
@hide:file
prepare_container proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
descriptor :: typecheck.array_type(state.checked, type.array_type)
if (type.kind == .optional and (descriptor.element.kind == .pointer or descriptor.element.kind == .function)) return ir.no_array
index :: usize(type.array_type)
if (state.arrays[index] != ir.no_array) return state.arrays[index]
fields [2]mut ir.RecordField := [{ type = .pointer }, { type = .usize }]
if (type.kind == .optional) {
fields[0] = ir.RecordField{ type = .bool }
fields[1] = ir.RecordField{ type = lower_type(state, descriptor.element), array_type = try array_shape(state, descriptor.element) }
}
shape :: try ir.add_record(state.module, fields[..])
state.arrays[index] = shape
return shape
}
@hide:file
prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
if (state.records[usize(id)] != ir.no_array) return state.records[usize(id)]
@@ -315,7 +342,7 @@ lower_global proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diag
if (trap_binding(state, id)) return
declaration :: ast.statement_decl(state.tree, id)
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
value :: try lower_expression(state, declaration.initializer)
value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id))
if (state.terminated or trap_type(state, id)) return
finish(state, value, declaration.span)
}
@@ -327,6 +354,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
state.function = usize(state.storage[usize(id)].index)
state.block = ir.BlockId(0)
state.terminated = false
state.return_type = typecheck.type_of(state.checked, declaration.return_type)
state.module.functions.items[state.function].result = lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
state.module.functions.items[state.function].result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
for (0..ast.function_param_count(tree, id)) |i| {
@@ -340,6 +368,14 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
)
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) }
}
if (ast.function_external(tree, id)) {
if (trap_binding(state, id) or trap_type(state, id)) return
name :: ast.source_text(tree, declaration.name)
owned :: try mem.alloc(u8, state.module.allocator, name.len)
memcopy!(owned, name)
state.module.functions.items[state.function].external_name = owned
return
}
try predeclare_locals(state, declaration.body)
# signature errors are entry traps; body errors are reached in statement order.
if (trap_binding(state, id) or trap_type(state, declaration.return_type)) return
@@ -375,10 +411,15 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
}
.if_stmt: {
conditional :: ast.if_stmt(state.tree, statement)
if (conditional.capture) |capture| try predeclare_capture(state, capture)
try predeclare_locals(state, conditional.then_block)
if (conditional.else_block) |alternative| try predeclare_locals(state, alternative)
}
.while_stmt: try predeclare_locals(state, ast.while_stmt(state.tree, statement).body)
.while_stmt: {
loop :: ast.while_stmt(state.tree, statement)
if (loop.capture) |capture| try predeclare_capture(state, capture)
try predeclare_locals(state, loop.body)
}
.for_stmt: {
loop :: ast.for_stmt(state.tree, statement)
family :: typecheck.type_of(state.checked, loop.iterable).range_element
@@ -397,6 +438,19 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
}
}
@hide:file
predeclare_capture proc(state @mut State, capture ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
type :: typecheck.type_of(state.checked, capture)
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), lower_type(state, type), try array_shape(state, type))
state.storage[usize(capture)] = Storage{ kind = .local, index = u32(slot) }
}
@hide:file
store_capture proc(state @mut State, capture ast.NodeId, value ir.ValueId, optional_type typecheck.Type, span diagnostics.Span) void ! (mem.AllocError | diagnostics.CapacityError) {
payload :: try optional_payload(state, value, optional_type, span)
_ = try emit(state, ir.Instruction{ op = .store_local, left = payload, slot = state.storage[usize(capture)].index, span = span })
}
@hide:file
lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (mem.AllocError | diagnostics.CapacityError) {
# reset every direct declaration before control can return or trap within the scope.
@@ -430,8 +484,10 @@ add_block proc(state @mut State) ir.BlockId ! mem.AllocError {
@hide:file
lower_if proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
conditional :: ast.if_stmt(state.tree, id)
condition :: try lower_expression(state, conditional.condition)
value :: try lower_expression(state, conditional.condition)
if (state.terminated) return
condition := value
if (conditional.capture != null) condition = try optional_present(state, value, typecheck.type_of(state.checked, conditional.condition), conditional.span)
origin :: state.block
consequent :: try add_block(state)
alternative :: try add_block(state)
@@ -443,6 +499,7 @@ lower_if proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
span = ast.node_span(state.tree, conditional.condition),
}
state.block = consequent
if (conditional.capture) |capture| try store_capture(state, capture, value, typecheck.type_of(state.checked, conditional.condition), conditional.span)
try lower_block(state, conditional.then_block, true)
consequent_end :: state.block
consequent_falls :: !state.terminated
@@ -479,8 +536,10 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
kind = .jump, target = guard, span = loop.span,
}
state.block = guard
condition :: try lower_expression(state, loop.condition)
value :: try lower_expression(state, loop.condition)
if (state.terminated) return
condition := value
if (loop.capture != null) condition = try optional_present(state, value, typecheck.type_of(state.checked, loop.condition), loop.span)
guard_end :: state.block
body :: try add_block(state)
exit :: try add_block(state)
@@ -495,6 +554,7 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
state.loop = LoopTargets{ continue_target = guard, exit = exit }
defer state.loop = previous_loop
state.block = body
if (loop.capture) |capture| try store_capture(state, capture, value, typecheck.type_of(state.checked, loop.condition), loop.span)
try lower_block(state, loop.body, true)
if (!state.terminated) {
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
@@ -594,7 +654,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
or ast.expr_kind(tree, assignment.target) == .unary
or (ast.expr_kind(tree, assignment.target) == .selection
and sema.binding(state.resolution, assignment.target).kind == .none)) {
value :: try lower_expression(state, assignment.value)
value :: try lower_as(state, assignment.value, typecheck.type_of(state.checked, assignment.target))
if (state.terminated) return
if (!is_place(state, assignment.target)) {
_ = try lower_expression(state, assignment.target)
@@ -613,7 +673,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
return
}
if (trap_binding(state, assignment.target) or trap_type(state, id)) return
value :: try lower_expression(state, assignment.value)
value :: try lower_as(state, assignment.value, typecheck.type_of(state.checked, assignment.target))
if (state.terminated) return
binding :: sema.binding(state.resolution, assignment.target)
_ = try emit(state, ir.Instruction{
@@ -636,7 +696,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
if (trap_binding(state, id)) return
declaration :: ast.statement_decl(tree, id)
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
value :: try lower_expression(state, declaration.initializer)
value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id))
if (state.terminated or trap_type(state, id)) return
_ = try emit(state, ir.Instruction{
op = .store_local,
@@ -650,7 +710,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
.return_stmt: {
statement :: ast.return_stmt(tree, id)
value := ir.no_value
if (statement.value) |expression| value = try lower_expression(state, expression)
if (statement.value) |expression| value = try lower_as(state, expression, state.return_type)
if (state.terminated or trap_type(state, id)) return
finish(state, value, statement.span)
}
@@ -827,7 +887,7 @@ lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
state.block = body
state.terminated = false
if (arm_view.expression) {
value :: try lower_expression(state, arm_view.body)
value :: try lower_as(state, arm_view.body, type)
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
op = .store_local, left = value, slot = result_slot, span = span,
})
@@ -867,7 +927,7 @@ lower_union proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
if (contextual and field.type == .record and (count != 1 or ast.field(tree, ast.record_field(tree, id, 0)).name != null)) {
payload = try lower_record_as(state, id, typecheck.union_payload_type(state.checked, id))
} else if (count != 0) {
payload = try lower_expression(state, ast.field(tree, ast.record_field(tree, id, 0)).value?)
payload = try lower_as(state, ast.field(tree, ast.record_field(tree, id, 0)).value?, typecheck.union_payload_type(state.checked, id))
}
}
if (state.terminated) return ir.no_value
@@ -883,15 +943,31 @@ lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.Va
scratch_start :: state.arguments.items.len
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
explicit_count :: ast.record_field_count(tree, id)
if (type.kind != .record) {
for (0..explicit_count) |i| {
_ = try lower_expression(state, ast.field(tree, ast.record_field(tree, id, i)).value?)
if (state.terminated) return ir.no_value
}
_ = trap_type(state, id)
return ir.no_value
}
descriptor :: typecheck.record_type(state.checked, type.record_type)
try arraylist.reserve(&state.arguments, scratch_start + explicit_count)
for (0..explicit_count) |_| try arraylist.append(&state.arguments, ir.no_value)
for (0..explicit_count) |i| {
value :: try lower_expression(state, ast.field(tree, ast.record_field(tree, id, i)).value?)
initializer :: ast.field(tree, ast.record_field(tree, id, i)).value?
expected := typecheck.Type{}
for (descriptor.fields) |field, slot| {
if (typecheck.initializer_field(state.checked, id, slot) == initializer) {
expected = field.type
break
}
}
value :: try lower_as(state, initializer, expected)
if (state.terminated) return ir.no_value
state.arguments.items[scratch_start + i] = value
}
if (trap_type(state, id)) return ir.no_value
descriptor :: typecheck.record_type(state.checked, type.record_type)
ordered_start :: state.arguments.items.len
try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len)
for (descriptor.fields) |_| try arraylist.append(&state.arguments, ir.no_value)
@@ -905,7 +981,7 @@ lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.Va
break
}
}
if (value == ir.no_value) value = try lower_expression(state, initializer)
if (value == ir.no_value) value = try lower_as(state, initializer, descriptor.fields[i].type)
if (state.terminated) return ir.no_value
state.arguments.items[ordered_start + i] = value
}
@@ -926,10 +1002,14 @@ lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.Va
@hide:file
is_place proc(state @State, id ast.NodeId) bool {
match ast.expr_kind(state.tree, id) {
.unary: return ast.unary_expr(state.tree, id).operator == .dereference
.unary: {
expression :: ast.unary_expr(state.tree, id)
return expression.operator == .dereference
}
.index: {
base :: ast.index_expr(state.tree, id).base
return typecheck.type_of(state.checked, base).kind == .pointer or is_place(state, base)
kind :: typecheck.type_of(state.checked, base).kind
return kind == .pointer or kind == .slice or kind == .string or is_place(state, base)
}
.selection: {
if (sema.binding(state.resolution, id).kind == .none) {
@@ -967,6 +1047,259 @@ pointer_address proc(
})
}
@hide:file
usize_constant proc(state @mut State, value usize, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
return try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = u64(value) } }, span)
}
@hide:file
null_constant proc(state @mut State, type ir.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ unsigned = maxval!(u64) } }, span)
}
@hide:file
record_pair proc(state @mut State, shape ir.ArrayId, first, second ir.ValueId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
block :: &state.module.functions.items[state.function].blocks.items[usize(state.block)]
start :: block.arguments.items.len
try arraylist.append(&block.arguments, first)
try arraylist.append(&block.arguments, second)
return try emit(state, ir.Instruction{
op = .record_literal, type = .record, array_type = shape,
arguments_start = start, arguments_count = 2, span = span,
})
}
@hide:file
project proc(state @mut State, value ir.ValueId, type ir.Type, shape ir.ArrayId, slot u32, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
return try emit(state, ir.Instruction{ op = .field, type = type, array_type = shape, left = value, slot = slot, span = span })
}
@hide:file
optional_present proc(state @mut State, value ir.ValueId, type typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
child :: typecheck.array_type(state.checked, type.array_type).element
runtime :: lower_type(state, child)
if (child.kind == .pointer or child.kind == .function) {
absent :: try null_constant(state, runtime, span)
return try emit(state, ir.Instruction{ op = .not_equal, type = .bool, left = value, right = absent, span = span })
}
return try project(state, value, .bool, ir.no_array, 0, span)
}
@hide:file
optional_payload proc(state @mut State, value ir.ValueId, type typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
child :: typecheck.array_type(state.checked, type.array_type).element
runtime :: lower_type(state, child)
if (child.kind == .pointer or child.kind == .function) return value
return try project(state, value, runtime, try array_shape(state, child), 1, span)
}
@hide:file
zero_value proc(state @mut State, type ir.Type, shape ir.ArrayId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
if (!ir.aggregate(type)) {
value := ir.Value{ type = type, data = ir.ValueData{ unsigned = 0 } }
if (type == .pointer or type == .function) value.data = ir.ValueData{ unsigned = maxval!(u64) }
return try constant(state, value, span)
}
descriptor :: state.module.arrays.items[usize(shape)]
if (descriptor.is_union) return try emit(state, ir.Instruction{ op = .union_literal, type = type, array_type = shape, span = span })
scratch :: state.arguments.items.len
defer state.arguments.items = state.arguments.items.ptr[..scratch]
count :: if (type == .array) descriptor.len else descriptor.fields.len
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
for (0..count) |i| {
child :: if (type == .array) descriptor.element else descriptor.fields[i].type
child_shape :: if (type == .array) descriptor.element_array else descriptor.fields[i].array_type
value :: try zero_value(state, child, child_shape, span)
state.arguments.items[scratch + i] = value
}
block :: &state.module.functions.items[state.function].blocks.items[usize(state.block)]
start :: block.arguments.items.len
for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch + i])
op ir.Op :: if (type == .array) .array_literal else .record_literal
return try emit(state, ir.Instruction{
op = op, type = type, array_type = shape,
arguments_start = start, arguments_count = count, span = span,
})
}
@hide:file
lower_as proc(state @mut State, id ast.NodeId, expected typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
actual :: typecheck.type_of(state.checked, id)
span :: ast.node_span(state.tree, id)
if (actual.kind == .invalid or expected.kind == .invalid) return try lower_expression(state, id)
if (expected.kind == .optional and actual.kind != .optional) {
child :: typecheck.array_type(state.checked, expected.array_type).element
runtime :: lower_type(state, child)
nullable :: child.kind == .pointer or child.kind == .function
if (actual.kind == .null and nullable) return try null_constant(state, runtime, span)
payload := ir.no_value
if (actual.kind == .null) payload = try zero_value(state, runtime, try array_shape(state, child), span)
else payload = try lower_as(state, id, child)
if (state.terminated or nullable) return payload
present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = actual.kind != .null } }, span)
return try record_pair(state, try array_shape(state, expected), present, payload, span)
}
if (expected.kind == .slice and actual.kind != .slice) {
parts :: try slice_parts(state, id)
if (state.terminated) return ir.no_value
return try record_pair(state, try array_shape(state, expected), parts.pointer, parts.bound, span)
}
return try lower_expression(state, id)
}
@hide:file
SliceParts :: struct {
pointer ir.ValueId
bound ir.ValueId
element typecheck.Type
}
@hide:file
borrow_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
if (is_place(state, id)) return try lower_place(state, id)
value :: try lower_expression(state, id)
if (state.terminated) return ir.no_value
checked :: typecheck.type_of(state.checked, id)
type :: lower_type(state, checked)
shape :: try array_shape(state, checked)
span :: ast.node_span(state.tree, id)
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), type, shape))
_ = try emit(state, ir.Instruction{ op = .store_local, type = type, array_type = shape, left = value, slot = slot, span = span })
return try emit(state, ir.Instruction{ op = .address_local, type = .address, pointee = type, array_type = shape, slot = slot, span = span })
}
@hide:file
slice_parts proc(state @mut State, id ast.NodeId) SliceParts ! (mem.AllocError | diagnostics.CapacityError) {
checked :: typecheck.type_of(state.checked, id)
span :: ast.node_span(state.tree, id)
result := SliceParts{ pointer = ir.no_value, bound = ir.no_value, element = typecheck.Type{} }
if (checked.kind == .slice) {
value :: try lower_expression(state, id)
if (state.terminated) return result
result.pointer = try project(state, value, .pointer, ir.no_array, 0, span)
result.bound = try project(state, value, .usize, ir.no_array, 1, span)
result.element = typecheck.array_type(state.checked, checked.array_type).element
return result
}
if (checked.kind == .string) {
value :: try lower_expression(state, id)
if (state.terminated) return result
result.pointer = try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
result.bound = try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span })
result.element = typecheck.Type{ kind = .u8 }
return result
}
if (checked.kind == .pointer) {
descriptor :: typecheck.pointer_type(state.checked, checked.pointer_type)
result.pointer = try lower_expression(state, id)
if (state.terminated) return result
result.element = descriptor.element
if (!descriptor.many and descriptor.element.kind == .array) {
array :: typecheck.array_type(state.checked, descriptor.element.array_type)
result.element = array.element
result.bound = try usize_constant(state, array.len, span)
}
return result
}
if (checked.kind == .array) {
descriptor :: typecheck.array_type(state.checked, checked.array_type)
address :: try borrow_expression(state, id)
if (state.terminated) return result
result.pointer = try emit(state, ir.Instruction{
op = .pointer_from_address, type = .pointer, left = address,
pointee = .array, array_type = try array_shape(state, checked), span = span,
})
result.bound = try usize_constant(state, descriptor.len, span)
result.element = descriptor.element
return result
}
_ = try lower_expression(state, id)
return result
}
@hide:file
lower_slice proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
expression :: ast.slice_expr(state.tree, id)
span :: ast.node_span(state.tree, id)
parts :: try slice_parts(state, expression.base)
if (state.terminated) return ir.no_value
start := ir.no_value
if (expression.start) |node| start = try lower_expression(state, node)
else start = try usize_constant(state, 0, span)
if (state.terminated) return ir.no_value
end := parts.bound
if (expression.end) |node| end = try lower_expression(state, node)
if (state.terminated or trap_type(state, id)) return ir.no_value
_ = try emit(state, ir.Instruction{ op = .slice_check, left = start, right = end, extra = parts.bound, span = span })
child :: parts.element
runtime :: lower_type(state, child)
shape :: try array_shape(state, child)
if (expression.sentinel) |node| {
sentinel :: try lower_as(state, node, child)
if (state.terminated) return ir.no_value
base_type :: typecheck.type_of(state.checked, expression.base)
has_sentinel := base_type.kind == .string
if (base_type.kind == .slice) has_sentinel = typecheck.array_type(state.checked, base_type.array_type).sentinel != null
if (parts.bound != ir.no_value and !has_sentinel) {
one :: try usize_constant(state, 1, span)
through :: try emit(state, ir.Instruction{ op = .add, type = .usize, left = end, right = one, span = span })
_ = try emit(state, ir.Instruction{ op = .slice_check, left = end, right = through, extra = parts.bound, span = span })
}
pointer :: try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = parts.pointer, right = end, pointee = runtime, array_type = shape, span = span })
_ = try emit(state, ir.Instruction{ op = .sentinel_check, left = pointer, right = sentinel, pointee = runtime, array_type = shape, span = span })
}
pointer :: try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = parts.pointer, right = start, pointee = runtime, array_type = shape, span = span })
length :: try emit(state, ir.Instruction{ op = .subtract, type = .usize, left = end, right = start, span = span })
return try record_pair(state, try array_shape(state, typecheck.type_of(state.checked, id)), pointer, length, span)
}
@hide:file
lower_builtin proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
span :: ast.node_span(tree, id)
name :: ast.source_text(tree, ast.node_span(tree, ast.builtin_call(tree, id).name))
if (trap_type(state, id)) return ir.no_value
if (mem.eql(name, "sizeof") or mem.eql(name, "alignof")) {
checked :: typecheck.type_of(state.checked, ast.call_arg(tree, id, 0))
type :: lower_type(state, checked)
shape :: try array_shape(state, checked)
size :: if (mem.eql(name, "sizeof")) ir.storage_size(state.module, type, shape) else ir.storage_alignment(state.module, type, shape)
return try usize_constant(state, size, span)
}
if (mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) {
operand :: ast.call_arg(tree, id, ast.call_arg_count(tree, id) - 1)
value :: try lower_expression(state, operand)
if (state.terminated or trap_type(state, id)) return ir.no_value
type :: typecheck.type_of(state.checked, id)
if (lower_type(state, type) == .record) return value
return try emit(state, ir.Instruction{ op = .pointer_cast, type = lower_type(state, type), left = value, span = span })
}
if (mem.eql(name, "memcopy") or mem.eql(name, "memset")) {
destination :: try slice_parts(state, ast.call_arg(tree, id, 0))
if (state.terminated) return ir.no_value
source := SliceParts{ pointer = ir.no_value, bound = ir.no_value, element = typecheck.Type{} }
value := ir.no_value
if (mem.eql(name, "memcopy")) source = try slice_parts(state, ast.call_arg(tree, id, 1))
else value = try lower_as(state, ast.call_arg(tree, id, 1), destination.element)
if (state.terminated or trap_type(state, id)) return ir.no_value
if (mem.eql(name, "memcopy")) {
zero :: try usize_constant(state, 0, span)
_ = try emit(state, ir.Instruction{ op = .slice_check, left = zero, right = source.bound, extra = destination.bound, span = span })
_ = try emit(state, ir.Instruction{ op = .slice_check, left = zero, right = destination.bound, extra = source.bound, span = span })
value = source.pointer
}
op ir.Op :: if (mem.eql(name, "memcopy")) .memory_copy else .memory_set
_ = try emit(state, ir.Instruction{
op = op,
left = destination.pointer, right = value, extra = destination.bound,
pointee = lower_type(state, destination.element), array_type = try array_shape(state, destination.element), span = span,
})
return ir.no_value
}
if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .invalid_operand, span), span)
return ir.no_value
}
@hide:file
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id)
@@ -1002,6 +1335,20 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
if (ast.expr_kind(state.tree, id) == .index) {
expression :: ast.index_expr(state.tree, id)
base_type :: typecheck.type_of(state.checked, expression.base)
if (base_type.kind == .slice or base_type.kind == .string) {
parts :: try slice_parts(state, expression.base)
if (state.terminated) return ir.no_value
index :: try lower_expression(state, expression.index)
if (state.terminated or trap_type(state, id)) return ir.no_value
one :: try usize_constant(state, 1, span)
end :: try emit(state, ir.Instruction{ op = .add, type = .usize, left = index, right = one, span = span })
_ = try emit(state, ir.Instruction{ op = .slice_check, left = index, right = end, extra = parts.bound, span = span })
pointer :: try emit(state, ir.Instruction{
op = .pointer_offset, type = .pointer, left = parts.pointer, right = index,
pointee = lower_type(state, checked), array_type = try array_shape(state, checked), span = span,
})
return try pointer_address(state, pointer, checked, span)
}
base := ir.no_value
if (base_type.kind == .pointer) {
pointer :: try lower_expression(state, expression.base)
@@ -1065,7 +1412,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
# ranges have no physical value; only lower_for decomposes an iterable.
if (!trap_type(state, id)) unreachable
}
.array_type, .pointer_type, .function_type, .record_type, .enum_type, .union_type, .distinct_type: {
.array_type, .pointer_type, .function_type, .record_type, .enum_type, .union_type, .distinct_type, .slice_type, .optional_type: {
if (!trap_type(state, id)) {
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
}
@@ -1075,6 +1422,9 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
if (!trap_type(state, id)) unreachable
}
.match_expr: return try lower_match(state, id)
.literal_null: return try null_constant(state, .pointer, span)
.slice: return try lower_slice(state, id)
.builtin_call: return try lower_builtin(state, id)
.record_literal: {
checked :: typecheck.type_of(state.checked, id)
if (checked.kind == .record and typecheck.record_type(state.checked, checked.record_type).is_union) {
@@ -1089,7 +1439,8 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
try arraylist.reserve(&state.arguments, scratch_start + count)
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
for (0..count) |i| {
element :: try lower_expression(state, ast.array_element(tree, id, i))
expected :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, id).array_type).element
element :: try lower_as(state, ast.array_element(tree, id, i), expected)
if (state.terminated) return ir.no_value
state.arguments.items[scratch_start + i] = element
}
@@ -1144,6 +1495,14 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
and sema.binding(state.resolution, id).kind == .none) {
selection :: ast.selection_expr(tree, id)
base_type :: typecheck.type_of(state.checked, selection.base)
if (base_type.kind == .slice or base_type.kind == .string or base_type.kind == .array) {
member :: ast.source_text(tree, ast.node_span(tree, selection.member))
if (mem.eql(member, "len") or mem.eql(member, "ptr")) {
parts :: try slice_parts(state, selection.base)
if (state.terminated or trap_type(state, id)) return ir.no_value
return if (mem.eql(member, "len")) parts.bound else parts.pointer
}
}
if (base_type.kind == .pointer) {
descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type)
if (!descriptor.many and descriptor.element.kind == .array) {
@@ -1192,7 +1551,20 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
}
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .unwrap) {
operand :: try lower_expression(state, expression.operand)
if (state.terminated or trap_type(state, id)) return ir.no_value
source :: typecheck.type_of(state.checked, expression.operand)
present :: try optional_present(state, operand, source, span)
_ = try emit(state, ir.Instruction{ op = .optional_check, left = present, span = span })
return try optional_payload(state, operand, source, span)
}
if (expression.operator == .address_of) {
if (typecheck.type_of(state.checked, id).kind == .slice) {
parts :: try slice_parts(state, expression.operand)
if (state.terminated or trap_type(state, id)) return ir.no_value
return try record_pair(state, shape, parts.pointer, parts.bound, span)
}
if (!is_place(state, expression.operand)) {
_ = try lower_expression(state, expression.operand)
if (!state.terminated) _ = trap_type(state, id)
@@ -1227,7 +1599,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
op ir.Op :: match expression.operator {
.negate: .negate
.logical_not: .logical_not
.address_of, .dereference: unreachable
.address_of, .dereference, .unwrap: unreachable
}
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
}
@@ -1236,6 +1608,17 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
if (expression.operator == .logical_and or expression.operator == .logical_or) {
return try lower_logical(state, id)
}
left_type :: typecheck.type_of(state.checked, expression.left)
right_type :: typecheck.type_of(state.checked, expression.right)
if ((expression.operator == .equal or expression.operator == .not_equal)
and (left_type.kind == .null or right_type.kind == .null)) {
optional_id :: if (left_type.kind == .null) expression.right else expression.left
value :: try lower_expression(state, optional_id)
if (state.terminated or trap_type(state, id)) return ir.no_value
present :: try optional_present(state, value, typecheck.type_of(state.checked, optional_id), span)
if (expression.operator == .not_equal) return present
return try emit(state, ir.Instruction{ op = .logical_not, type = .bool, left = present, span = span })
}
left :: try lower_expression(state, expression.left)
if (state.terminated) return ir.no_value
right :: try lower_expression(state, expression.right)
@@ -1288,7 +1671,12 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
try arraylist.reserve(&state.arguments, scratch_start + count)
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
for (0..count) |i| {
argument :: try lower_expression(state, ast.call_arg(tree, id, i))
signature :: typecheck.type_of(state.checked, ast.call_expr(tree, id).callee).function
expected := typecheck.Type{}
if (signature != ast.NodeId(maxval!(u32)) and i < ast.function_param_count(tree, signature)) {
expected = typecheck.type_of(state.checked, ast.param(tree, ast.function_param(tree, signature, i)).type)
}
argument :: try lower_as(state, ast.call_arg(tree, id, i), expected)
if (state.terminated) return ir.no_value
state.arguments.items[scratch_start + i] = argument
}
+94 -43
View File
@@ -110,7 +110,7 @@ parse_decl proc(state @mut State) ast.NodeId ! ParseError {
next_is(state, .import) or next_is(state, .test)
or (next_is(state, .ident) and next_n_is(state, 1, .double_colon) and next_n_is(state, 2, .import))
) return try parse_import_decl(state)
if (next_is(state, .ident) and next_n_is(state, 1, .proc)) return try parse_func_decl(state)
if (next_is(state, .ident) and (next_n_is(state, 1, .proc) or next_n_is(state, 1, .c_func))) return try parse_func_decl(state)
return try parse_statement_decl(state)
}
@@ -144,7 +144,8 @@ parse_import_decl proc(state @mut State) ast.NodeId ! ParseError {
@hide:file
parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
name :: try consume(state, .ident)
_ = try consume(state, .proc)
external :: next_is(state, .c_func)
_ = try consume_either(state, &[.proc, .c_func])
_ = try consume(state, .open_paren)
params arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
@@ -152,9 +153,9 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
try parse_parameters(state, &params)
_ = try consume(state, .close_paren)
return_type :: try parse_type(state)
body :: try parse_block(state)
body :: if (external) try ast.add_block(state.builder, ast.builder_span(state.builder, return_type), &[]) else try parse_block(state)
body_span :: ast.builder_span(state.builder, body)
return try ast.add_function_decl(
id :: try ast.add_function_decl(
state.builder,
ast.Span{
start = name.span.start,
@@ -167,6 +168,8 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
params.items,
body,
)
if (external) ast.set_function_external(state.builder, id)
return id
}
@hide:file
@@ -245,6 +248,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
or next_n_is(state, 1, .distinct)
or next_n_is(state, 1, .at)
or next_n_is(state, 1, .question)
or (next_n_is(state, 1, .star) and pointer_binding_starts(state))
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
or next_n_is(state, 1, .double_colon)
@@ -260,7 +264,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
.return: return try parse_return(state, allow_else)
.break, .continue: return try parse_loop_control(state, allow_else)
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct,
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star: {
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null: {
expression :: try parse_expr(state)
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
return try ast.add_expression_stmt(state.builder, expression)
@@ -348,6 +352,7 @@ parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.
parse_if proc(state @mut State) ast.NodeId ! ParseError {
keyword :: try consume(state, .if)
condition :: try parse_guard(state)
capture :: try parse_optional_capture(state)
braced :: next_is(state, .open_curly)
then_block :: try parse_arm(state, true)
# only a braced arm permits its else on a later line.
@@ -364,7 +369,7 @@ parse_if proc(state @mut State) ast.NodeId ! ParseError {
ast.builder_span(state.builder, block).end
else
ast.builder_span(state.builder, then_block).end
return try ast.add_if_stmt(
id :: try ast.add_if_stmt(
state.builder,
ast.Span{
start = keyword.span.start,
@@ -375,14 +380,17 @@ parse_if proc(state @mut State) ast.NodeId ! ParseError {
then_block,
alternative,
)
if (capture) |node| ast.set_capture(state.builder, id, node)
return id
}
@hide:file
parse_while proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
keyword :: try consume(state, .while)
condition :: try parse_guard(state)
capture :: try parse_optional_capture(state)
body :: try parse_arm(state, allow_else)
return try ast.add_while_stmt(
id :: try ast.add_while_stmt(
state.builder,
ast.Span{
start = keyword.span.start,
@@ -392,6 +400,18 @@ parse_while proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
condition,
body,
)
if (capture) |node| ast.set_capture(state.builder, id, node)
return id
}
@hide:file
parse_optional_capture proc(state @mut State) ?ast.NodeId ! ParseError {
missing ?ast.NodeId :: null
if (!next_is(state, .pipe)) return missing
state.next_token += 1
name :: try consume(state, .ident)
_ = try consume(state, .pipe)
return try ast.add_param(state.builder, name.span, name.span, name.data.str_id, maxval!(ast.NodeId))
}
@hide:file
@@ -529,6 +549,7 @@ parse_guard proc(state @mut State) ast.NodeId ! ParseError {
}
if (
!next_is(state, .open_curly)
and !next_is(state, .pipe)
and !parenthesized_guard(state, condition_start)
and ast.builder_expr_kind(state.builder, condition) != .call
and ast.builder_expr_kind(state.builder, condition) != .invalid
@@ -609,6 +630,14 @@ parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
@hide:file
parse_type proc(state @mut State) ast.NodeId ! ParseError {
if (next_is(state, .question)) {
token :: current(state)
state.next_token += 1
child :: try parse_type(state)
return try ast.add_optional_type(state.builder, ast.Span{
start = token.span.start, end = ast.builder_span(state.builder, child).end, source = token.span.source,
}, child)
}
if (next_is(state, .at) or next_is(state, .star)) {
token :: current(state)
state.next_token += 1
@@ -642,6 +671,20 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
}
if next_is(state, .open_bracket) {
open :: try consume(state, .open_bracket)
if (next_is(state, .close_bracket) or next_is(state, .semicolon)) {
sentinel ?ast.NodeId := null
if (next_is(state, .semicolon)) {
state.next_token += 1
sentinel = try parse_expr(state)
}
_ = try consume(state, .close_bracket)
mutable :: next_is(state, .mut)
if (mutable) state.next_token += 1
element :: try parse_type(state)
return try ast.add_slice_type(state.builder, ast.Span{
start = open.span.start, end = ast.builder_span(state.builder, element).end, source = open.span.source,
}, element, mutable, sentinel)
}
count ?ast.NodeId := null
if (
next_is(state, .ident)
@@ -690,7 +733,7 @@ starts_type proc(kind lexer.TokenKind) bool {
return (
kind == .ident or kind == .open_bracket or kind == .struct
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
or kind == .at or kind == .star
or kind == .at or kind == .star or kind == .question
)
}
@@ -699,7 +742,7 @@ member_name proc(kind lexer.TokenKind) bool {
return match kind {
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
.alias, .match, .import, .test: true
.alias, .match, .import, .test, .c_func, .null: true
else: false
}
}
@@ -1124,18 +1167,25 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
while true {
skip_continuation_newlines(state)
token = current(state)
if (token.kind == .caret) {
if (token.kind == .caret or token.kind == .question) {
state.next_token += 1
operator ast.UnaryOperator :: if (token.kind == .caret) .dereference else .unwrap
left = try ast.add_unary(state.builder, ast.Span{
start = state.tokens[expression_start].span.start,
end = token.span.end,
source = token.span.source,
}, .dereference, left)
}, operator, left)
left_grouped = false
continue
}
if (token.kind == .bang and next_n_is(state, 1, .open_paren)) {
state.next_token += 1
left = try parse_call(state, left, state.tokens[expression_start].span.start, true)
left_grouped = false
continue
}
if (token.kind == .open_paren) {
left = try parse_call(state, left, state.tokens[expression_start].span.start)
left = try parse_call(state, left, state.tokens[expression_start].span.start, false)
left_grouped = false
continue
}
@@ -1228,7 +1278,7 @@ validate_range_bound proc(
}
@hide:file
parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! ParseError {
parse_call proc(state @mut State, callee ast.NodeId, start u32, builtin bool) ast.NodeId ! ParseError {
_ = try consume(state, .open_paren)
previous :: state.allow_record_literals
state.allow_record_literals = true
@@ -1260,16 +1310,9 @@ parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! Par
return .unexpected_token
}
close :: try consume(state, .close_paren)
return try ast.add_call(
state.builder,
ast.Span{
start = start,
end = close.span.end,
source = state.builder.source_id,
},
callee,
arguments.items,
)
span :: ast.Span{ start = start, end = close.span.end, source = state.builder.source_id }
if (builtin) return try ast.add_builtin_call(state.builder, span, callee, arguments.items)
return try ast.add_call(state.builder, span, callee, arguments.items)
}
@hide:file
@@ -1278,29 +1321,32 @@ parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! Pars
previous :: state.allow_record_literals
state.allow_record_literals = true
defer state.allow_record_literals = previous
index :: try parse_expr(state)
first ?ast.NodeId := null
if (!next_is(state, .range)) first = try parse_expr_bp(state, BindingPowerScalar(1))
if (next_is(state, .range)) {
state.next_token += 1
end ?ast.NodeId := null
sentinel ?ast.NodeId := null
if (!next_is(state, .close_bracket) and !next_is(state, .semicolon)) end = try parse_expr(state)
if (next_is(state, .semicolon)) {
state.next_token += 1
sentinel = try parse_expr(state)
}
close :: try consume(state, .close_bracket)
return try ast.add_slice(state.builder, ast.Span{
start = start, end = close.span.end, source = state.builder.source_id,
}, base, first, end, sentinel)
}
if !next_is(state, .close_bracket) {
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
return try ast.add_invalid_expr(
state.builder,
ast.Span{
start = start,
end = ast.builder_span(state.builder, index).end,
source = state.builder.source_id,
},
diagnostic,
)
return try ast.add_invalid_expr(state.builder, ast.Span{
start = start, end = ast.builder_span(state.builder, first?).end, source = state.builder.source_id,
}, diagnostic)
}
close :: try consume(state, .close_bracket)
return try ast.add_index(
state.builder,
ast.Span{
start = start,
end = close.span.end,
source = state.builder.source_id,
},
base, index,
)
return try ast.add_index(state.builder, ast.Span{
start = start, end = close.span.end, source = state.builder.source_id,
}, base, first?)
}
@hide:file
@@ -1397,6 +1443,7 @@ named_binding_starts proc(state @State) bool {
next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc)
or next_n_is(state, 1, .at)
or next_n_is(state, 1, .question)
or (next_n_is(state, 1, .star) and pointer_binding_starts(state))
)
)
@@ -1406,7 +1453,7 @@ named_binding_starts proc(state @State) bool {
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state)
match token.kind {
.at, .star: return try parse_type(state)
.at, .star, .question: return try parse_type(state)
.struct: return try parse_record_type(state)
.enum: return try parse_enum_type(state)
.union: return try parse_union_type(state)
@@ -1468,6 +1515,10 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
state.next_token += 1
return try ast.add_literal(state.builder, .bool, token.span)
}
.null: {
state.next_token += 1
return try ast.add_literal(state.builder, .null, token.span)
}
.ident: {
state.next_token += 1
return try ast.add_identifier(state.builder, token.span, token.data.str_id)
+489 -20
View File
@@ -24,6 +24,10 @@ TypeKind :: enum {
type_value
u8, u16, u32, u64, usize
i8, i16, i32, isize
slice
optional
null
anyopaque
}
RecordId :: distinct u32
@@ -69,8 +73,10 @@ no_array :: maxval!(ArrayId)
ArrayType :: struct {
element Type
len usize
mutable bool
len usize = 0
mutable bool = false
kind TypeKind = .array
sentinel ?i64 = null
}
PointerId :: distinct u32
@@ -344,6 +350,7 @@ check proc(
}
}
# Calls use the signature result before contextual literal inference.
try prepare_optional_captures(&state, false)
for (0..count) |i| {
id :: ast.NodeId(i)
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
@@ -478,6 +485,17 @@ check proc(
}
}
for (0..count) |i| try validate(&state, ast.NodeId(i))
for (0..ast.package_decl_count(tree)) |i| {
id :: ast.package_decl(tree, i)
if (ast.decl_kind(tree, id) != .function or !ast.function_external(tree, id)) continue
for (0..i) |j| {
previous :: ast.package_decl(tree, j)
if (ast.decl_kind(tree, previous) != .function or !ast.function_external(tree, previous)) continue
if (ast.name_id(tree, id) == ast.name_id(tree, previous) and !same_signature(&state, id, previous)) {
try fail(&state, id, .type_mismatch, ast.function_decl(tree, id).name)
}
}
}
for state.records.items |record| {
for record.fields |field| {
if (field.default) |value| try compatible(&state, value, field.type)
@@ -492,8 +510,10 @@ check proc(
id :: ast.NodeId(i)
if (cells[i].role != .if_stmt and cells[i].role != .while_stmt) continue
condition :: if (cells[i].role == .if_stmt) ast.if_stmt(tree, id).condition else ast.while_stmt(tree, id).condition
capture :: if (cells[i].role == .if_stmt) ast.if_stmt(tree, id).capture else ast.while_stmt(tree, id).capture
actual :: types[usize(condition)]
if (actual.kind != .invalid and (actual.kind != .bool or actual.nominal != ast.NodeId(maxval!(u32)))) {
if (capture != null and actual.kind == .optional) continue
if (actual.kind != .invalid and (capture != null or actual.kind != .bool or actual.nominal != ast.NodeId(maxval!(u32)))) {
try fail(&state, condition, .condition_not_bool, ast.node_span(tree, condition))
}
}
@@ -511,6 +531,7 @@ check proc(
for (0..ast.package_decl_count(tree)) |i| {
id :: ast.package_decl(tree, i)
if (ast.decl_kind(tree, id) != .function) continue
if (ast.function_external(tree, id)) continue
function :: ast.function_decl(tree, id)
result :: types[usize(function.return_type)]
if (result.kind == .void or result.kind == .invalid or types[usize(id)].kind == .invalid) continue
@@ -764,6 +785,12 @@ collect_invalid proc(state @mut State, id ast.NodeId) void {
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
state.cells[usize(id)].role = .annotation
if (ast.expr_kind(state.tree, id) == .slice_type or ast.expr_kind(state.tree, id) == .optional_type) {
type :: try check_container_type(state, id)
state.types[usize(id)] = type
state.cells[usize(id)].type = type
return type
}
if (ast.expr_kind(state.tree, id) == .pointer_type) {
type :: try check_pointer_type(state, id)
state.types[usize(id)] = type
@@ -814,6 +841,9 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
.invalid: type = invalid_type(binding.data.diagnostic)
.builtin: {
type.kind = builtin_kind(binding.data.builtin)
if (type.kind == .anyopaque and state.pointer_depth == 0) {
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
}
if (type.kind == .void and !allow_void) {
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_void_type, ast.node_span(state.tree, id)))
}
@@ -890,7 +920,15 @@ collect_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError |
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
state.cells[usize(id)].type.function = id
}
try collect_block(state, function.body, function.return_type, false)
if (!ast.function_external(tree, id)) try collect_block(state, function.body, function.return_type, false)
else {
if (mem.eql(ast.source_text(tree, function.name), "main")) try fail(state, id, .invalid_entry, function.name)
if (!c_abi_type(state, state.types[usize(function.return_type)])) try fail(state, id, .invalid_operand, function.span)
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
if (!c_abi_type(state, state.cells[usize(parameter)].type)) try fail(state, id, .invalid_operand, ast.node_span(tree, parameter))
}
}
}
@hide:file
@@ -941,6 +979,11 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
state.cells[usize(statement)].type = Type{ kind = .void }
try collect_expression(state, conditional.condition)
try depend(state, conditional.condition, statement)
if (conditional.capture) |capture| {
try collect_capture(state, capture, conditional.then_block)
state.cells[usize(capture)].type = Type{}
state.cells[usize(conditional.condition)].capture = capture
}
try collect_block(state, conditional.then_block, return_annotation, in_loop)
try depend(state, conditional.then_block, id)
if (conditional.else_block) |other| {
@@ -974,6 +1017,11 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
state.cells[usize(statement)].type = Type{ kind = .void }
try collect_expression(state, loop.condition)
try depend(state, loop.condition, statement)
if (loop.capture) |capture| {
try collect_capture(state, capture, loop.body)
state.cells[usize(capture)].type = Type{}
state.cells[usize(loop.condition)].capture = capture
}
try collect_block(state, loop.body, return_annotation, true)
try depend(state, loop.body, id)
}
@@ -1042,6 +1090,25 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
}
.literal_bool: state.cells[usize(id)].type = Type{ kind = .bool }
.literal_string: state.cells[usize(id)].type = Type{ kind = .string }
.literal_null: state.cells[usize(id)].type = Type{ kind = .null }
.builtin_call: try collect_builtin(state, id)
.slice: {
view :: ast.slice_expr(tree, id)
try collect_expression(state, view.base)
try depend(state, view.base, id)
if (view.start) |child| {
try collect_expression(state, child)
try depend(state, child, id)
}
if (view.end) |child| {
try collect_expression(state, child)
try depend(state, child, id)
}
if (view.sentinel) |child| {
try collect_expression(state, child)
try depend(state, child, id)
}
}
.identifier, .selection: {
binding :: sema.binding(state.resolution, id)
if (binding.kind == .none and ast.expr_kind(tree, id) == .selection) {
@@ -1135,7 +1202,8 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
.array_type: {
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
}
.record_type, .union_type, .enum_type, .distinct_type, .pointer_type, .function_type: {
.record_type, .union_type, .enum_type, .distinct_type,
.pointer_type, .function_type, .slice_type, .optional_type: {
type := try check_type_expression(state, id, state.cells[usize(id)].nominal)
if (type.kind != .invalid) {
type.value_kind = type.kind
@@ -1236,6 +1304,15 @@ builtin_kind proc(type sema.BuiltinType) TypeKind {
.bool: .bool
.string: .string
.void: .void
.anyopaque: .anyopaque
.c_int: .i32
.c_uint: .u32
.c_long, .c_longlong: .int
.c_ulong, .c_ulonglong: .u64
.c_char: .i8
.c_uchar: .u8
.c_short: .i16
.c_ushort: .u16
}
}
@@ -1269,6 +1346,7 @@ pointer_addition proc(state @mut State, id ast.NodeId) bool {
expression.operator == .add and left.kind == .pointer
and left.nominal == ast.NodeId(maxval!(u32))
and state.pointers.items[usize(left.pointer_type)].many
and state.pointers.items[usize(left.pointer_type)].element.kind != .anyopaque
)
}
@@ -1281,7 +1359,9 @@ bool_equality proc(state @mut State, id ast.NodeId) bool {
(expression.operator == .equal or expression.operator == .not_equal)
and ((left.kind == .bool and right.kind == .bool)
or left.kind == .enumeration or right.kind == .enumeration
or left.kind == .pointer or right.kind == .pointer)
or left.kind == .pointer or right.kind == .pointer
or left.kind == .optional or right.kind == .optional
or left.kind == .null or right.kind == .null)
)
}
@@ -1349,6 +1429,41 @@ numeric_constraint proc(state @mut State, expression, operand, operation ast.Nod
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
if (state.types[usize(id)].kind == .invalid) return
index :: root(state, usize(id))
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .builtin_call) {
name :: builtin_name(state, id)
if (mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) {
state.cells[index].expected = expected
seed(state, id, expected)
return
}
}
if (expected.kind == .optional) {
if (group_type(state, id).kind != .optional and group_type(state, id).kind != .null) {
try demand(state, id, state.arrays.items[usize(expected.array_type)].element, code)
}
return
}
if (expected.kind == .slice) {
state.cells[index].expected = expected
if (ast.expr_kind(state.tree, id) == .unary and ast.unary_expr(state.tree, id).operator == .address_of) {
operand :: ast.unary_expr(state.tree, id).operand
if (ast.expr_kind(state.tree, operand) == .array_literal) {
descriptor :: state.arrays.items[usize(expected.array_type)]
array :: try intern_array(state, ArrayType{
element = descriptor.element, len = ast.array_element_count(state.tree, operand), mutable = descriptor.mutable,
})
try demand(state, operand, array, code)
seed(state, id, expected)
}
} else if (ast.expr_kind(state.tree, id) == .array_literal) {
descriptor :: state.arrays.items[usize(expected.array_type)]
array :: try intern_array(state, ArrayType{
element = descriptor.element, len = ast.array_element_count(state.tree, id), mutable = descriptor.mutable,
})
try demand(state, id, array, code)
}
return
}
if (expected.kind == .pointer and state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .unary
and ast.unary_expr(state.tree, id).operator == .address_of) {
@@ -1471,6 +1586,7 @@ mutable_target proc(state @mut State, id ast.NodeId, allow_global bool) bool {
return pointer.mutable and (pointer.many
or (pointer.element.kind == .array and state.arrays.items[usize(pointer.element.array_type)].mutable))
}
if (base.kind == .slice) return state.arrays.items[usize(base.array_type)].mutable
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base, allow_global)
}
if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
@@ -1492,10 +1608,10 @@ mutable_target proc(state @mut State, id ast.NodeId, allow_global bool) bool {
@hide:file
indexable_type proc(state @State, type Type) bool {
if (type.nominal != ast.NodeId(maxval!(u32))) return false
if (type.kind == .array) return true
if (type.kind == .array or type.kind == .slice or type.kind == .string) return true
if (type.kind != .pointer) return false
pointer :: state.pointers.items[usize(type.pointer_type)]
return pointer.many or pointer.element.kind == .array
return (pointer.many and pointer.element.kind != .anyopaque) or pointer.element.kind == .array
}
@hide:file
@@ -1508,7 +1624,7 @@ addressable_target proc(state @mut State, id ast.NodeId) bool {
if (kind == .index) {
view :: ast.index_expr(state.tree, id)
base :: group_type(state, view.base)
return indexable_type(state, base) and (base.kind == .pointer or addressable_target(state, view.base))
return indexable_type(state, base) and (base.kind == .pointer or base.kind == .slice or base.kind == .string or addressable_target(state, view.base))
}
if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
view :: ast.selection_expr(state.tree, id)
@@ -1528,6 +1644,7 @@ addressable_target proc(state @mut State, id ast.NodeId) bool {
@hide:file
same_signature proc(state @State, left, right ast.NodeId) bool {
if (left == right) return true
if (ast.function_external(state.tree, left) != ast.function_external(state.tree, right)) return false
tree :: state.tree
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
left_result :: ast.callable_result(tree, left)
@@ -1544,10 +1661,32 @@ same_signature proc(state @State, left, right ast.NodeId) bool {
@hide:file
assignable_type proc(state @State, actual, expected Type) bool {
if (same_type(state, actual, expected)) return true
if (expected.kind == .optional) {
child :: state.arrays.items[usize(expected.array_type)].element
if (actual.kind == .null) return true
if (actual.kind == .optional) {
source :: state.arrays.items[usize(actual.array_type)].element
if (source.kind != child.kind) return false
return assignable_type(state, source, child)
}
return assignable_type(state, actual, child)
}
if (expected.kind == .slice) {
b :: state.arrays.items[usize(expected.array_type)]
if (actual.kind == .string) return !b.mutable and b.element.kind == .u8 and (b.sentinel == null or b.sentinel? == 0)
if (actual.kind == .slice or actual.kind == .array) {
a :: state.arrays.items[usize(actual.array_type)]
return (
(!b.mutable or a.mutable) and (b.sentinel == null or same_sentinel(a.sentinel, b.sentinel))
and same_type(state, a.element, b.element)
)
}
}
if (actual.kind != .pointer or expected.kind != .pointer or actual.nominal != expected.nominal) return false
a :: state.pointers.items[usize(actual.pointer_type)]
b :: state.pointers.items[usize(expected.pointer_type)]
if (a.many != b.many or (!a.mutable and b.mutable)) return false
if (b.element.kind == .anyopaque) return true
if (b.mutable) return same_type(state, a.element, b.element)
return assignable_type(state, a.element, b.element)
}
@@ -1556,11 +1695,22 @@ assignable_type proc(state @State, actual, expected Type) bool {
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
actual :: state.types[usize(value)]
if (actual.kind == .invalid or expected.kind == .invalid) return
if (!assignable_type(state, actual, expected)) {
if (!assignable_value(state, value, actual, expected)) {
try fail(state, value, .type_mismatch, ast.node_span(state.tree, value))
}
}
@hide:file
assignable_value proc(state @mut State, value ast.NodeId, actual, expected Type) bool {
if (!assignable_type(state, actual, expected)) return false
target := expected
while (target.kind == .optional) target = state.arrays.items[usize(target.array_type)].element
if (actual.kind == .array and target.kind == .slice
and state.arrays.items[usize(target.array_type)].mutable
and addressable_target(state, value)) return mutable_target(state, value, true)
return true
}
@hide:file
validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
@@ -1571,6 +1721,9 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
match state.cells[usize(id)].role {
.declaration: {
declaration :: ast.statement_decl(tree, id)
if (declaration.type == null and state.types[usize(declaration.initializer)].kind == .null) {
try fail(state, declaration.initializer, .cannot_infer_type, ast.node_span(tree, declaration.initializer))
}
if (state.types[usize(declaration.initializer)].kind == .void) {
try fail(state, declaration.initializer, .type_mismatch, ast.node_span(tree, declaration.initializer))
}
@@ -1603,7 +1756,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
actual :: state.types[usize(value)]
if (actual.kind == .invalid) {
state.types[usize(id)] = actual
} else if (!assignable_type(state, actual, expected)) {
} else if (!assignable_value(state, value, actual, expected)) {
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
state.types[usize(id)] = state.types[usize(value)]
}
@@ -1621,10 +1774,17 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
}
if (expression.operator == .address_of) {
if (!addressable_target(state, expression.operand)) {
if (state.types[usize(id)].kind == .slice) return
try fail(state, id, .invalid_address_operand, expression.span)
}
return
}
if (expression.operator == .unwrap) {
if (state.types[usize(expression.operand)].kind != .optional and state.types[usize(expression.operand)].kind != .invalid) {
try fail(state, id, .invalid_operand, expression.span)
}
return
}
if (expression.operator == .dereference) {
if (state.types[usize(expression.operand)].kind != .invalid
and state.types[usize(expression.operand)].kind != .pointer) {
@@ -1653,6 +1813,8 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
}
return
}
if ((expression.operator == .equal or expression.operator == .not_equal)
and ((left.kind == .optional and right.kind == .null) or (left.kind == .null and right.kind == .optional))) return
if ((expression.operator == .equal or expression.operator == .not_equal)
and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer)
and (assignable_type(state, left, right) or assignable_type(state, right, left))) return
@@ -1718,6 +1880,12 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
}
}
.call: try validate_call(state, id)
.builtin_call: _ = try infer_builtin(state, id, true)
.slice: {
view :: ast.slice_expr(tree, id)
if (view.start) |node| try compatible(state, node, Type{ kind = .usize })
if (view.end) |node| try compatible(state, node, Type{ kind = .usize })
}
else: {}
}
}
@@ -1959,13 +2127,214 @@ check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
@hide:file
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
for state.arrays.items |existing, i| {
if (existing.len == descriptor.len and existing.mutable == descriptor.mutable
and same_type(state, existing.element, descriptor.element)) {
return Type{ kind = .array, array_type = ArrayId(i) }
if (existing.kind == descriptor.kind and existing.len == descriptor.len and existing.mutable == descriptor.mutable
and same_sentinel(existing.sentinel, descriptor.sentinel) and same_type(state, existing.element, descriptor.element)) {
return Type{ kind = descriptor.kind, array_type = ArrayId(i) }
}
}
try arraylist.append(&state.arrays, descriptor)
return Type{ kind = .array, array_type = ArrayId(state.arrays.items.len - 1) }
return Type{ kind = descriptor.kind, array_type = ArrayId(state.arrays.items.len - 1) }
}
@hide:file
check_container_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagnostics.CapacityError) {
optional :: ast.expr_kind(state.tree, id) == .optional_type
node :: if (optional) ast.type_operand(state.tree, id) else ast.slice_type(state.tree, id).element
if (!optional) state.pointer_depth += 1
child :: try check_annotation(state, node, false)
if (!optional) state.pointer_depth -= 1
if (child.kind == .invalid) return child
if (child.kind == .void or child.kind == .anyopaque or child.kind == .range or has_inferred_count(state, child)) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_element_type, ast.node_span(state.tree, node)))
}
kind TypeKind :: if (optional) .optional else .slice
descriptor := ArrayType{ kind = kind, element = child }
if (!optional) {
view :: ast.slice_type(state.tree, id)
descriptor.mutable = view.mutable
if (view.sentinel) |sentinel| {
try collect_expression(state, sentinel)
try demand(state, sentinel, child, .type_mismatch)
if (constant_integer(state, sentinel)) |value| {
if (!integer_kind(child.kind) or !integer_fits(value, child.kind)) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, sentinel)))
}
descriptor.sentinel = sentinel_bits(value)
} else return invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, sentinel)))
}
}
return try intern_array(state, descriptor)
}
@hide:file
c_abi_type proc(state @State, type Type) bool {
if (type.kind == .optional) return state.arrays.items[usize(type.array_type)].element.kind == .pointer
return integer_kind(type.kind) or type.kind == .float or type.kind == .bool or type.kind == .pointer or type.kind == .void or type.kind == .invalid
}
@hide:file
builtin_name proc(state @State, id ast.NodeId) []u8 {
return ast.source_text(state.tree, ast.node_span(state.tree, ast.builtin_call(state.tree, id).name))
}
@hide:file
collect_builtin proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
name :: builtin_name(state, id)
count :: ast.call_arg_count(state.tree, id)
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
for (0..count) |i| {
argument :: ast.call_arg(state.tree, id, i)
if (layout or (cast and count == 2 and i == 0)) {
_ = try check_annotation(state, argument, true)
} else try collect_expression(state, argument)
try depend(state, argument, id)
}
if (layout) state.cells[usize(id)].type = Type{ kind = .usize }
if (mem.eql(name, "memcopy") or mem.eql(name, "memset")) state.cells[usize(id)].type = Type{ kind = .void }
}
@hide:file
sequence_type proc(state @mut State, node ast.NodeId, defaults bool) ArrayType ! (mem.AllocError | diagnostics.CapacityError) {
type := try infer_shape(state, node, defaults)
if (type.kind == .pointer) {
pointer :: state.pointers.items[usize(type.pointer_type)]
if (pointer.many or pointer.element.kind != .array) return ArrayType{ element = Type{} }
type = pointer.element
descriptor := state.arrays.items[usize(type.array_type)]
descriptor.mutable = descriptor.mutable and pointer.mutable
return descriptor
}
if (type.kind == .array or type.kind == .slice) {
descriptor := state.arrays.items[usize(type.array_type)]
if (type.kind == .array) descriptor.mutable = descriptor.mutable and mutable_target(state, node, true)
return descriptor
}
if (type.kind == .string) return ArrayType{ element = Type{ kind = .u8 }, kind = .slice, sentinel = 0 }
return ArrayType{ element = Type{} }
}
@hide:file
infer_slice proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
view :: ast.slice_expr(state.tree, id)
base :: try infer_shape(state, view.base, defaults)
descriptor := try sequence_type(state, view.base, defaults)
if (base.kind == .pointer and state.pointers.items[usize(base.pointer_type)].many) {
pointer :: state.pointers.items[usize(base.pointer_type)]
descriptor = ArrayType{ element = pointer.element, mutable = pointer.mutable }
if (view.end == null or pointer.element.kind == .anyopaque) {
try fail(state, id, .invalid_operand, ast.node_span(state.tree, id))
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
}
if (view.start) |node| try demand(state, node, Type{ kind = .usize }, .invalid_index_type)
if (view.end) |node| try demand(state, node, Type{ kind = .usize }, .invalid_index_type)
if (descriptor.element.kind == .none) {
if (defaults and base.kind != .none and base.kind != .invalid) try fail(state, id, .not_indexable, ast.node_span(state.tree, view.base))
return group_type(state, id)
}
descriptor.kind = .slice
descriptor.len = 0
descriptor.sentinel = null
if (view.sentinel) |node| {
try demand(state, node, descriptor.element, .invalid_operand)
if (constant_integer(state, node)) |value| {
if (!integer_kind(descriptor.element.kind) or !integer_fits(value, descriptor.element.kind)) {
try fail(state, id, .invalid_operand, ast.node_span(state.tree, node))
} else {
descriptor.sentinel = sentinel_bits(value)
}
} else if (defaults) try fail(state, id, .invalid_operand, ast.node_span(state.tree, node))
}
type :: try intern_array(state, descriptor)
seed(state, id, type)
return group_type(state, id)
}
@hide:file
cast_pointer_type proc(state @State, type Type) Type {
if (type.kind == .optional) return state.arrays.items[usize(type.array_type)].element
return type
}
@hide:file
infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
name :: builtin_name(state, id)
count :: ast.call_arg_count(state.tree, id)
span :: ast.node_span(state.tree, id)
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
memory :: mem.eql(name, "memcopy") or mem.eql(name, "memset")
valid_count :: (layout and count == 1) or (cast and (count == 1 or count == 2)) or (memory and count == 2)
if (!valid_count) {
code diagnostics.Code :: if (layout or cast or memory) .argument_count_mismatch else .invalid_operand
if (defaults) try fail(state, id, code, span)
return group_type(state, id)
}
if (layout) return group_type(state, id)
if (cast) {
result := state.cells[root(state, usize(id))].expected
if (count == 2) result = state.types[usize(ast.call_arg(state.tree, id, 0))]
operand :: ast.call_arg(state.tree, id, count - 1)
source :: try infer_shape(state, operand, defaults)
if (result.kind == .none and mem.eql(name, "constcast")) {
result = source
if (source.kind == .slice) {
descriptor := state.arrays.items[usize(source.array_type)]
descriptor.mutable = true
result = try intern_array(state, descriptor)
} else if (source.kind == .pointer) {
descriptor := state.pointers.items[usize(source.pointer_type)]
descriptor.mutable = true
result = try intern_pointer(state, descriptor)
}
}
if (result.kind == .none) {
if (defaults) try fail(state, id, .cannot_infer_type, span)
return group_type(state, id)
}
a :: cast_pointer_type(state, source)
b :: cast_pointer_type(state, result)
allowed := false
if (a.kind == .pointer and b.kind == .pointer) {
left :: state.pointers.items[usize(a.pointer_type)]
right :: state.pointers.items[usize(b.pointer_type)]
if (mem.eql(name, "constcast")) allowed = left.many == right.many and same_type(state, left.element, right.element)
else allowed = left.mutable or !right.mutable
if (source.kind == .optional and result.kind != .optional) allowed = false
} else if (a.kind == .slice and b.kind == .slice and mem.eql(name, "constcast")) {
left :: state.arrays.items[usize(a.array_type)]
right :: state.arrays.items[usize(b.array_type)]
allowed = same_sentinel(left.sentinel, right.sentinel) and same_type(state, left.element, right.element)
} else if (source.kind == .null and result.kind == .optional and b.kind == .pointer) allowed = true
if (!allowed and source.kind != .none and source.kind != .invalid and defaults) {
try fail(state, id, .invalid_operand, span)
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
seed(state, id, result)
return group_type(state, id)
}
destination :: try sequence_type(state, ast.call_arg(state.tree, id, 0), defaults)
if (mem.eql(name, "memset") and destination.element.kind != .none) {
try demand(state, ast.call_arg(state.tree, id, 1), destination.element, .type_mismatch)
}
if (defaults) {
if (!destination.mutable or destination.element.kind == .none) {
try fail(state, id, .invalid_operand, span)
} else if (mem.eql(name, "memcopy")) {
source :: try sequence_type(state, ast.call_arg(state.tree, id, 1), defaults)
if (!same_type(state, destination.element, source.element)) try fail(state, id, .type_mismatch, span)
} else {
argument :: ast.call_arg(state.tree, id, 1)
actual :: try infer_shape(state, argument, defaults)
if (actual.kind != .none and actual.kind != .invalid and !assignable_value(state, argument, actual, destination.element)) {
try fail(state, argument, .type_mismatch, ast.node_span(state.tree, argument))
}
}
}
return group_type(state, id)
}
@hide:file
@@ -2003,15 +2372,40 @@ same_type proc(state @State, left, right Type) bool {
}
return true
}
if (left.kind != .array) return true
if (left.kind != .array and left.kind != .slice and left.kind != .optional) return true
a :: state.arrays.items[usize(left.array_type)]
b :: state.arrays.items[usize(right.array_type)]
return a.len == b.len and a.mutable == b.mutable and same_type(state, a.element, b.element)
return a.len == b.len and a.mutable == b.mutable and same_sentinel(a.sentinel, b.sentinel) and same_type(state, a.element, b.element)
}
@hide:file
same_sentinel proc(left, right ?i64) bool {
if (left) |a| {
if (right) |b| return a == b
return false
}
return right == null
}
@hide:file
sentinel_bits proc(value ConstantInteger) i64 {
if (!value.negative) return ptrcast!(@i64, &value.magnitude)^
if (value.magnitude == u64(maxval!(i64)) + 1) return minval!(i64)
return -i64(value.magnitude)
}
@hide:file
array_cells proc(state @State, type Type) ?usize {
if (type.kind == .void) return usize(0)
if (type.kind == .slice) return usize(2)
if (type.kind == .optional) {
child :: state.arrays.items[usize(type.array_type)].element
if (child.kind == .pointer or child.kind == .function) return usize(1)
if (array_cells(state, child)) |count| {
if (count < maxval!(usize)) return count + 1
}
return null
}
if (type.kind == .record) {
total := usize(0)
descriptor :: state.records.items[usize(type.record_type)]
@@ -2084,6 +2478,20 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
if (state.cells[index].type.kind == .invalid) return state.cells[index].type
state.cells[index].visiting = true
defer state.cells[index].visiting = false
if (state.cells[usize(id)].role == .expression) {
kind :: ast.expr_kind(state.tree, id)
if (kind == .builtin_call) return try infer_builtin(state, id, defaults)
if (kind == .slice) return try infer_slice(state, id, defaults)
if (kind == .unary and ast.unary_expr(state.tree, id).operator == .unwrap) {
operand :: try infer_shape(state, ast.unary_expr(state.tree, id).operand, defaults)
if (operand.kind == .optional) {
child :: state.arrays.items[usize(operand.array_type)].element
seed(state, id, child)
return child
}
return group_type(state, id)
}
}
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .binary) {
view :: ast.binary_expr(state.tree, id)
if (view.operator == .add) {
@@ -2113,11 +2521,23 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
return group_type(state, id)
}
element :: state.pointers.items[usize(operand.pointer_type)].element
if (element.kind == .anyopaque) {
try fail(state, id, .invalid_dereference, view.span)
seed(state, id, state.types[usize(id)])
return group_type(state, id)
}
seed(state, id, element)
return element
}
mutable :: mutable_target(state, view.operand, true)
expected :: state.cells[index].expected
if (expected.kind == .slice and operand.kind == .array) {
array :: state.arrays.items[usize(operand.array_type)]
writable :: array.mutable and (mutable or ast.expr_kind(state.tree, view.operand) == .array_literal)
type :: try intern_array(state, ArrayType{ kind = .slice, element = array.element, mutable = writable })
seed(state, id, type)
return type
}
many :: expected.kind == .pointer and state.pointers.items[usize(expected.pointer_type)].many
type :: try intern_pointer(state, PointerType{ element = operand, mutable = mutable, many = many })
seed(state, id, type)
@@ -2133,6 +2553,13 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
seed(state, id, Type{ kind = destination })
return group_type(state, id)
}
if (state.backings[usize(id)].kind == .none) {
signature :: try infer_shape(state, ast.call_expr(state.tree, id).callee, defaults)
if (signature.kind == .function) {
seed(state, id, state.types[usize(ast.callable_result(state.tree, signature.function))])
return group_type(state, id)
}
}
}
if (state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .selection
@@ -2143,6 +2570,23 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
if (enum_shape.kind == .enumeration) return try select_enum_member(state, id, view.member, enum_shape)
}
base := try infer_shape(state, view.base, defaults)
if ((base.kind == .array or base.kind == .slice or base.kind == .string) and base.nominal == ast.NodeId(maxval!(u32))) {
member :: ast.source_text(state.tree, ast.node_span(state.tree, view.member))
if (mem.eql(member, "len")) {
seed(state, id, Type{ kind = .usize })
return group_type(state, id)
}
if (mem.eql(member, "ptr")) {
element :: if (base.kind == .string) Type{ kind = .u8 } else state.arrays.items[usize(base.array_type)].element
mutable :: (
base.kind != .string and state.arrays.items[usize(base.array_type)].mutable
and (base.kind == .slice or mutable_target(state, view.base, true))
)
type :: try intern_pointer(state, PointerType{ element = element, mutable = mutable, many = true })
seed(state, id, type)
return type
}
}
if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)]
if (!pointer.many and pointer.element.kind == .array
@@ -2260,7 +2704,11 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
}
base = pointer.element
}
if (base.kind == .array and base.nominal == ast.NodeId(maxval!(u32))) {
if (base.kind == .string) {
seed(state, id, Type{ kind = .u8 })
return group_type(state, id)
}
if ((base.kind == .array or base.kind == .slice) and base.nominal == ast.NodeId(maxval!(u32))) {
element :: state.arrays.items[usize(base.array_type)].element
seed(state, id, element)
return element
@@ -2277,8 +2725,27 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
return group_type(state, id)
}
@hide:file
prepare_optional_captures proc(state @mut State, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
for (0..(state.cells.len)) |i| {
capture :: state.cells[i].capture
if (capture == ast.NodeId(maxval!(u32)) or state.cells[i].role != .expression) continue
if (state.cells[usize(capture)].role != .loop_capture) continue
type :: try infer_shape(state, ast.NodeId(i), defaults)
if (type.kind != .optional) continue
child :: state.arrays.items[usize(type.array_type)].element
seed(state, capture, child)
for (0..(state.cells.len)) |j| {
if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue
binding :: sema.binding(state.resolution, ast.NodeId(j))
if (binding.kind == .declaration and binding.data.declaration == capture) seed(state, ast.NodeId(j), child)
}
}
}
@hide:file
prepare_shapes proc(state @mut State, assignment_values, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
try prepare_optional_captures(state, defaults)
for 0..(state.cells.len) |i| {
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values) continue
id :: ast.NodeId(i)
@@ -2365,7 +2832,8 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
@hide:file
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
if (early and expected.kind != .array and expected.kind != .record
and expected.kind != .pointer and expected.kind != .enumeration and !integer_kind(expected.kind)) return
and expected.kind != .pointer and expected.kind != .slice and expected.kind != .optional
and expected.kind != .enumeration and !integer_kind(expected.kind)) return
try demand(state, id, expected, code)
}
@@ -2748,7 +3216,7 @@ type_syntax proc(kind ast.ExprKind) bool {
return (
kind == .record_type or kind == .union_type or kind == .enum_type
or kind == .distinct_type or kind == .alias_expr
or kind == .pointer_type or kind == .function_type
or kind == .pointer_type or kind == .function_type or kind == .slice_type or kind == .optional_type
)
}
@@ -2779,6 +3247,7 @@ check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type !
if (kind == .record_type) return try check_record(state, id, nominal)
if (kind == .pointer_type) return try check_pointer_type(state, id)
if (kind == .function_type) return try check_function_type(state, id)
if (kind == .slice_type or kind == .optional_type) return try check_container_type(state, id)
if (kind == .union_type) {
type :: try check_union(state, id, nominal)
state.cells[usize(id)].type = type
+41 -4
View File
@@ -14,6 +14,9 @@ BuiltinType :: enum {
f64, usize,
i8, i16, i32, isize,
u8, u16, u32, u64, uint,
anyopaque
c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong
c_char, c_uchar, c_short, c_ushort
}
@hide:file
@@ -41,6 +44,17 @@ builtins [_]Builtin :: [
{ name = "u32", type = .u32 },
{ name = "u64", type = .u64 },
{ name = "uint", type = .uint },
{ name = "anyopaque", type = .anyopaque },
{ name = "c_int", type = .c_int },
{ name = "c_uint", type = .c_uint },
{ name = "c_long", type = .c_long },
{ name = "c_ulong", type = .c_ulong },
{ name = "c_longlong", type = .c_longlong },
{ name = "c_ulonglong", type = .c_ulonglong },
{ name = "c_char", type = .c_char },
{ name = "c_uchar", type = .c_uchar },
{ name = "c_short", type = .c_short },
{ name = "c_ushort", type = .c_ushort },
]
@hide:file
@@ -371,7 +385,7 @@ resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (me
.if_stmt: {
conditional :: ast.if_stmt(tree, statement)
try resolve_expr(state, scope, conditional.condition)
try resolve_arm(state, scope, conditional.then_block)
try resolve_capture_arm(state, scope, conditional.then_block, conditional.capture)
if (conditional.else_block) |other| try resolve_arm(state, scope, other)
}
.assignment_stmt: {
@@ -382,7 +396,7 @@ resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (me
.while_stmt: {
loop :: ast.while_stmt(tree, statement)
try resolve_expr(state, scope, loop.condition)
try resolve_arm(state, scope, loop.body)
try resolve_capture_arm(state, scope, loop.body, loop.capture)
}
.for_stmt: {
loop :: ast.for_stmt(tree, statement)
@@ -405,6 +419,14 @@ resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (me
}
}
@hide:file
resolve_capture_arm proc(state @mut State, scope @Scope, block ast.NodeId, capture ?ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
child := Scope{ names = hashmap.init(state.allocator), parent = scope }
defer hashmap.deinit(&child.names)
if (capture) |id| try predeclare(state, &child, id, ast.node_span(state.tree, id))
try resolve_block(state, &child, block)
}
@hide:file
resolve_statement_decl proc(
state @mut State,
@@ -487,9 +509,9 @@ resolve_expr proc(
if (state.bindings[usize(id)].kind != .none) return
tree :: state.tree
match ast.expr_kind(tree, id) {
.literal_int, .literal_float, .literal_bool, .literal_string: {}
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null: {}
.enum_literal: {}
.distinct_type, .alias_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
.distinct_type, .alias_expr, .optional_type: try resolve_expr(state, scope, ast.type_operand(tree, id))
.enum_type: {
if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing)
for (0..ast.enum_member_count(tree, id)) |i| {
@@ -571,6 +593,21 @@ resolve_expr proc(
try resolve_expr(state, scope, ast.call_arg(tree, id, i))
}
}
.builtin_call: {
for (0..ast.call_arg_count(tree, id)) |i| try resolve_expr(state, scope, ast.call_arg(tree, id, i))
}
.slice: {
expression :: ast.slice_expr(tree, id)
try resolve_expr(state, scope, expression.base)
if (expression.start) |node| try resolve_expr(state, scope, node)
if (expression.end) |node| try resolve_expr(state, scope, node)
if (expression.sentinel) |node| try resolve_expr(state, scope, node)
}
.slice_type: {
type :: ast.slice_type(tree, id)
try resolve_expr(state, scope, type.element)
if (type.sentinel) |node| try resolve_expr(state, scope, node)
}
.pointer_type: try resolve_expr(state, scope, ast.pointer_type(tree, id).element)
.function_type: {
try resolve_expr(state, scope, ast.callable_result(tree, id))