feat: complete memory representation gate
This commit is contained in:
+136
-15
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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, ¶ms)
|
||||
_ = 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
@@ -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
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user