feat: complete memory representation gate

This commit is contained in:
2026-10-26 11:49:25 +01:00
parent 7856f4f963
commit cd64afbb40
16 changed files with 2103 additions and 159 deletions
+15 -3
View File
@@ -97,7 +97,19 @@ indices require `usize`; integer literals and flexible inferred values receive t
data pointers use `@T` for one value and `*T` for many values, with `mut` after the pointer marker for writable pointees. `&value` borrows addressable initialized storage; postfix `^` reads or writes the pointee. pointer mutability is independent of binding mutability: an immutable pointer binding or parameter can mutate its `mut` pointee. mutable pointers weaken to readonly pointers with the same child type. address-taking also accepts an expected many-pointer type, such as `pointer *mut i16 :: &values[0]`; arbitrary temporaries are not addressable. data pointers use `@T` for one value and `*T` for many values, with `mut` after the pointer marker for writable pointees. `&value` borrows addressable initialized storage; postfix `^` reads or writes the pointee. pointer mutability is independent of binding mutability: an immutable pointer binding or parameter can mutate its `mut` pointee. mutable pointers weaken to readonly pointers with the same child type. address-taking also accepts an expected many-pointer type, such as `pointer *mut i16 :: &values[0]`; arbitrary temporaries are not addressable.
pointer-to-record field selection dereferences implicitly. pointers to arrays support bounded indexing, `.len`, and `.ptr`; `.ptr` returns a many-pointer and retains both pointer and array-element mutability restrictions. many-pointer indices require `usize` and caller-provided valid storage. many-pointer `pointer + offset` requires a `usize` offset, advances by the pointee's storage size and preserves its type/mutability; single-pointer addition, reversed addition and pointer subtraction are invalid, matching Bro. operands evaluate left to right once; one-past pointers can be formed but not dereferenced. pointers preserve alias identity through calls, returns, globals and aggregate copies; dereferenced aggregates still copy by value. `==` and `!=` compare locations. recursive records may contain pointers to themselves without recursive by-value layout. CLI results render pointer leaves as `<pointer>`. pointee lifetimes remain caller-managed; there is no borrow checker. slices, optionals, layout/cast builtins and allocator libc calls remain unimplemented. pointer-to-record field selection dereferences implicitly. pointers to arrays support bounded indexing, `.len`, and `.ptr`; `.ptr` returns a many-pointer and retains both pointer and array-element mutability restrictions. many-pointer indices require `usize` and caller-provided valid storage. many-pointer `pointer + offset` requires a `usize` offset, advances by the pointee's storage size and preserves its type/mutability; single-pointer addition, reversed addition and pointer subtraction are invalid, matching Bro. operands evaluate left to right once; one-past pointers can be formed but not dereferenced. pointers preserve alias identity through calls, returns, globals and aggregate copies; dereferenced aggregates still copy by value. `==` and `!=` compare locations. recursive records may contain pointers to themselves without recursive by-value layout. CLI results render pointer leaves as `<pointer>`. pointee lifetimes remain caller-managed; there is no borrow checker.
slices use `[]T` or `[]mut T` and borrow storage rather than copying elements. `values[start..end]` checks ordered, upper-exclusive `usize` bounds; either bound may be omitted for arrays, pointers to arrays and slices. many-pointer slices require an explicit end. empty and one-past slices are valid, but indexing at `.len` traps. `.ptr` returns a many-pointer and `.len` returns `usize`; copies, calls and aggregate storage preserve aliases. mutable slices can write through immutable bindings and parameters, while readonly views cannot strengthen implicitly. contextual `&[]` creates an empty mutable slice; borrowed backing storage must outlive every view.
sentinel slices use `[;sentinel]T` and checked construction such as `pointer[..length;0]`. the sentinel is a representable compile-time integer stored immediately after the logical elements, outside `.len`; construction checks both accessible bounds and its value. sentinel views can weaken to plain slices, not the reverse. string values expose readonly byte indexing, slicing, `.ptr` and `.len`, and convert to `[]u8` or `[;0]u8`; their module-owned backing includes an implicit NUL without changing the logical length.
optionals use `?T`; values of `T` and `null` initialize optional storage, parameters, results and aggregate fields. `== null` and `!= null` test presence. postfix `value?` unwraps or traps, while `if value |payload|` and `while value |payload|` bind a payload only inside the taken body; loop guards are re-evaluated and `continue` returns to the guard. optional data/function pointers use a nullable pointer representation; other optionals retain a separate presence flag and by-value payload. optional pointer weakening preserves aliases and readonly restrictions.
`sizeof!(T)` and `alignof!(T)` report the supported target's native layout, including aggregate padding, slice descriptors and nullable pointers. `ptrcast!(value)` takes its full result pointer type from context; `ptrcast!(T, value)` states it explicitly. casts preserve the storage location and optional presence, permit byte reinterpretation, and cannot remove readonly qualification. `constcast!` changes pointer or slice mutability without changing the pointee type; it does not make genuinely readonly storage writable or extend its lifetime. `anyopaque` is a pointer-only pointee, not a storable or dereferenceable value.
`memcopy!(destination, source)` copies matching typed slices or pointers to arrays, checking equal lengths and rejecting nonempty overlap. `memset!(destination, value)` repeats complete typed values, including aggregate fields, rather than repeating a byte. destinations require writable elements; empty and zero-sized copies/sets do not access storage. interpreter byte views synchronize with the original typed storage so casts and memory operations preserve aliases.
native external declarations use `malloc c_func(size c_ulong) ?*mut anyopaque`, without a body. supported signatures contain concrete integer/float/boolean scalars, data pointers, optional data pointers and `void` results; aggregate C ABI and variadics remain outside this gate. Darwin `c_char`/`c_uchar`, `c_short`/`c_ushort`, `c_int`/`c_uint`, `c_long`/`c_ulong` and `c_longlong`/`c_ulonglong` map to the corresponding 8/16/32/64-bit integer representations. executable checks cover `malloc`, `realloc`, `free` and `posix_memalign` through allocator-style callbacks. foreign calls trap only when reached in the interpreter; arbitrary interpreter FFI is not supported. compiling the existing generic/fallible standard library remains a later gate.
records use `Point :: struct { x i16, y i16 = 2 }`, with comma- or newline-separated fields. named record declarations have nominal identity; two separately declared records are not interchangeable. construct them with `Point { x = 40 }` or a contextual keyed literal, such as `point Point := { x = 40 }`. omitted fields use their declared defaults; missing required fields, duplicate/unknown fields, incompatible values and recursive by-value layouts are diagnosed. explicit initializers run in source order, followed by omitted defaults in declaration order. an explicit value overrides its field's default without evaluating that default. records use `Point :: struct { x i16, y i16 = 2 }`, with comma- or newline-separated fields. named record declarations have nominal identity; two separately declared records are not interchangeable. construct them with `Point { x = 40 }` or a contextual keyed literal, such as `point Point := { x = 40 }`. omitted fields use their declared defaults; missing required fields, duplicate/unknown fields, incompatible values and recursive by-value layouts are diagnosed. explicit initializers run in source order, followed by omitted defaults in declaration order. an explicit value overrides its field's default without evaluating that default.
@@ -131,7 +143,7 @@ resolver scopes use `@std/hashmap.AutoHashMap(K, V)`, which shares `init`, `put`
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration. `sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration.
type checking (`@source/sema/check`) runs after name resolution. `check.check` borrows the AST and binding table and returns owned per-node types, pointer/array/record/enum descriptors and construction mappings; query with `check.type_of`, `check.pointer_type`, `check.array_type`, `check.record_type`, `check.enum_type`, `check.field_index` and `check.initializer_field`, and release with `check.deinit`. declarations infer their initializer types, including forward references and function aliases; function types identify an AST declaration or explicit signature node supplying the callable signature. annotations support builtin types, data pointers, callback signatures, fixed arrays and local/imported records, enums, unions and distinct/alias types; type names are not ordinary runtime values. `void` is valid as a function result or tagged-union payload, not as a parameter or standalone stored value. type checking (`@source/sema/check`) runs after name resolution. `check.check` borrows the AST and binding table and returns owned per-node types, pointer/container/record/enum descriptors and construction mappings; query with `check.type_of`, `check.pointer_type`, `check.array_type`, `check.record_type`, `check.enum_type`, `check.field_index` and `check.initializer_field`, and release with `check.deinit`. `ArrayType.kind` distinguishes fixed arrays, slices and optionals; its element records the slice element or optional payload, with mutability and sentinel metadata for slices. declarations infer their initializer types, including forward references and function aliases; function types identify an AST declaration or explicit signature node supplying the callable signature. annotations support builtin types, data pointers, callback signatures, fixed arrays, slices, optionals and local/imported records, enums, unions and distinct/alias types; type names are not ordinary runtime values. `void` is valid as a function result or tagged-union payload, not as a parameter or standalone stored value.
function fallthrough obligations are separate from body types. `check.fallthrough_diagnostic(&result, function)` accepts a function declaration's `NodeId` and returns its original missing-return diagnostic ID, or `null` when no obligation exists. nested body errors retain their original identities. lowering consumes this fact without interpreting diagnostic codes and places its trap only on a surviving fallthrough path; `check.deinit` releases the obligation storage. function fallthrough obligations are separate from body types. `check.fallthrough_diagnostic(&result, function)` accepts a function declaration's `NodeId` and returns its original missing-return diagnostic ID, or `null` when no obligation exists. nested body errors retain their original identities. lowering consumes this fact without interpreting diagnostic codes and places its trap only on a surviving fallthrough path; `check.deinit` releases the obligation storage.
@@ -141,7 +153,7 @@ numeric types include `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `isi
the conversion policy is value-preserving by default: any future implicit widening must preserve every value of the source type, not merely the value observed at one use. narrowing, potentially inexact integer-to-float conversion, float-to-integer conversion, and signedness changes that cannot preserve the full source range require an explicit operation. integer arithmetic remains checked rather than wrapping; floating arithmetic retains IEEE rounding. the conversion policy is value-preserving by default: any future implicit widening must preserve every value of the source type, not merely the value observed at one use. narrowing, potentially inexact integer-to-float conversion, float-to-integer conversion, and signedness changes that cannot preserve the full source range require an explicit operation. integer arithmetic remains checked rather than wrapping; floating arithmetic retains IEEE rounding.
explicit numeric conversion uses `T(value)`, such as `u8(index)`, `usize(byte)` or `f64(count)`, with exactly one numeric argument. integer conversions trap with `integer arithmetic overflow` if the value is outside the destination range; float-to-integer conversion truncates toward zero, then checks bounds, rejecting NaN and infinity. integer-to-float conversion permits rounding, including `f64(9007199254740993)`. operands retain their independent types rather than being narrowed by the result context; a positive constant integer conversion operand above `i64`'s maximum defaults to `u64`. boolean conversions, arbitrary-width integers, `f32`, C scalar types and implicit typed-value widening are not implemented. native storage uses LLVM `i8`/`i16`/`i32`/`i64`, including array elements and function parameters/results. explicit numeric conversion uses `T(value)`, such as `u8(index)`, `usize(byte)` or `f64(count)`, with exactly one numeric argument. integer conversions trap with `integer arithmetic overflow` if the value is outside the destination range; float-to-integer conversion truncates toward zero, then checks bounds, rejecting NaN and infinity. integer-to-float conversion permits rounding, including `f64(9007199254740993)`. operands retain their independent types rather than being narrowed by the result context; a positive constant integer conversion operand above `i64`'s maximum defaults to `u64`. boolean conversions, arbitrary-width integers, `f32` and implicit typed-value widening are not implemented. native storage uses LLVM `i8`/`i16`/`i32`/`i64`, including array elements and function parameters/results.
`@std/strconv.parse_float(allocator, text)` parses a complete borrowed `[]u8` decimal into `f64`. it accepts an optional sign, decimal point, and exponent; whitespace, embedded NUL bytes, trailing input, hexadecimal, infinities, and NaNs return `invalid_number`. finite subnormals and signed zero are preserved; overflow and nonzero values rounded to zero return `out_of_range`. allocation failures propagate as `out_of_memory`, and temporary storage is always released. the Darwin backend uses libc's C-locale conversion without changing the process locale; rounding follows the caller's floating-point rounding mode. numeric lowering uses this interface, keeping exact integer-to-float validation compiler-specific. `@std/strconv.parse_float(allocator, text)` parses a complete borrowed `[]u8` decimal into `f64`. it accepts an optional sign, decimal point, and exponent; whitespace, embedded NUL bytes, trailing input, hexadecimal, infinities, and NaNs return `invalid_number`. finite subnormals and signed zero are preserved; overflow and nonzero values rounded to zero return `out_of_range`. allocation failures propagate as `out_of_memory`, and temporary storage is always released. the Darwin backend uses libc's C-locale conversion without changing the process locale; rounding follows the caller's floating-point rounding mode. numeric lowering uses this interface, keeping exact integer-to-float validation compiler-specific.
+8 -5
View File
@@ -10,9 +10,12 @@ integers, checked numeric conversions, records/tuples, construction/defaults,
enums, distinct/alias types, overlapping untagged/tagged unions, field reads/writes enums, distinct/alias types, overlapping untagged/tagged unions, field reads/writes
and exhaustive statement/value matching work through both execution paths. and exhaustive statement/value matching work through both execution paths.
source data pointers/references support aliasing, mutation, calls and aggregate source data pointers/references support aliasing, mutation, calls and aggregate
storage; explicit callback signatures and many-pointer addition work through both storage; explicit callback signatures, many-pointer addition, mutable/sentinel
execution paths. the driver retains frontend storage slices, optionals and layout/cast/memory builtins work through both execution paths.
and source provenance through `@source/compilation`, with compilation-wide AST node identities. native scalar/pointer `c_func` declarations support allocator-required libc calls;
executed foreign calls remain diagnostic traps in the interpreter.
the driver retains frontend storage and source provenance through
`@source/compilation`, with compilation-wide AST node identities.
file commands retain single-file behavior; directory commands compile the file commands retain single-file behavior; directory commands compile the
package's production files and load its dependencies in separate scopes. package's production files and load its dependencies in separate scopes.
@@ -41,10 +44,10 @@ test discovery and FFI remain outside these completed slices.
- [x] native integer widths (`i8`–`i64`, `u8`–`u64`, `isize`/`usize`) and checked explicit numeric conversions, including float truncation/range checks and integer-to-float rounding. preserve exact types through arrays, calls and range captures; retain strict implicit conversion rules. - [x] native integer widths (`i8`–`i64`, `u8`–`u64`, `isize`/`usize`) and checked explicit numeric conversions, including float truncation/range checks and integer-to-float rounding. preserve exact types through arrays, calls and range captures; retain strict implicit conversion rules.
- [x] records/tuples, typed/contextual/anonymous construction, field defaults and reads/writes, nested aggregate value copies, imported record types and constructors through interpreter/native execution. - [x] records/tuples, typed/contextual/anonymous construction, field defaults and reads/writes, nested aggregate value copies, imported record types and constructors through interpreter/native execution.
- [x] nominal enums and distinct types, transparent declaration/type aliases, overlapping untagged/tagged unions, contextual payload construction, active-field checks and exhaustive scalar/enum/tagged-union matching through interpreter/native execution. pointer captures, inline specialization, sum composition and yielding value blocks remain with their respective later gates. - [x] nominal enums and distinct types, transparent declaration/type aliases, overlapping untagged/tagged unions, contextual payload construction, active-field checks and exhaustive scalar/enum/tagged-union matching through interpreter/native execution. pointer captures, inline specialization, sum composition and yielding value blocks remain with their respective later gates.
- [ ] memory representation: source pointers/references, mutable and sentinel slices, optionals, layout/cast builtins and allocator-required libc calls. internal IR array addresses are not source pointers. examples: `std/mem/mem.hon`, `std/arraylist/arraylist.hon`. - [x] memory representation: source pointers/references, mutable and sentinel slices, optionals, layout/cast builtins and allocator-required libc calls. internal IR array addresses are not source pointers. examples: `std/mem/mem.hon`, `std/arraylist/arraylist.hon`.
- [x] source data pointers/references: `@T`/`*T`, `mut`, address-taking and dereference, pointer-aware fields/indexing, array `.len`/`.ptr`, readonly weakening, location equality, recursive pointer-linked records and shared pointees across interpreter/native calls and aggregate copies. - [x] source data pointers/references: `@T`/`*T`, `mut`, address-taking and dereference, pointer-aware fields/indexing, array `.len`/`.ptr`, readonly weakening, location equality, recursive pointer-linked records and shared pointees across interpreter/native calls and aggregate copies.
- [x] function-pointer signatures: explicit `@proc(...) Result`, structural signature checking, callbacks in parameters/results/records/arrays, vtable calls and imported signature aliases; many-pointer `+ usize` arithmetic preserves pointee type, mutability, stride and location identity through interpreter/native execution. - [x] function-pointer signatures: explicit `@proc(...) Result`, structural signature checking, callbacks in parameters/results/records/arrays, vtable calls and imported signature aliases; many-pointer `+ usize` arithmetic preserves pointee type, mutability, stride and location identity through interpreter/native execution.
- [ ] mutable/sentinel slices, optionals, layout/cast builtins and allocator-required libc calls. - [x] mutable/sentinel slices with checked bounds, borrowed aliases and string views; optional values/pointers/callbacks, null checks, unwrap and conditional/loop captures; target layout, pointer/const casts and typed memory copy/set through interpreter/native execution. scalar/pointer C ABI declarations build/run allocator callbacks using `malloc`, `realloc`, `free` and `posix_memalign`; arbitrary interpreter FFI, generic/fallible standard-library compilation and native startup/IO remain with later gates.
- [ ] fallible results and cleanup: error-set composition, `try`/`catch`, `defer`/`errdefer`; infrastructure errors remain distinct from diagnostic traps. examples: `source/main.hon:41`, `source/sema/check/check.hon:117`. - [ ] fallible results and cleanup: error-set composition, `try`/`catch`, `defer`/`errdefer`; infrastructure errors remain distinct from diagnostic traps. examples: `source/main.hon:41`, `source/sema/check/check.hon:117`.
- [ ] generic specialization and compile-time execution/reflection sufficient for existing `ArrayList`, hash maps, `enummap`, `meta` and `io.print`; include type/value parameters and compile-time generated storage. - [ ] generic specialization and compile-time execution/reflection sufficient for existing `ArrayList`, hash maps, `enummap`, `meta` and `io.print`; include type/value parameters and compile-time generated storage.
- [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`. - [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
+136 -15
View File
@@ -1,17 +1,17 @@
#! grammar: #! 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 ... -> :: | := #! * binding ... -> :: | :=
#! * literal .. -> int | float | string | 'true' | 'false' #! * literal .. -> int | float | string | 'true' | 'false' | 'null'
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or' #! * 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 '}' #! * 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 | ['test'] [identifier '::'] 'import' string #! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
#! * block .... -> '{' {stmt} '}' #! * block .... -> '{' {stmt} '}'
#! * arm ...... -> block | stmt (single statements become canonical blocks) #! * arm ...... -> block | stmt (single statements become canonical blocks)
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated. #! * 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. #! 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 #! * 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; else binds to the nearest if. #! 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, unary -/!/& higher than multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses. #! 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. #! binary operators associate left; unary operators associate right.
import "@std/debug" import "@std/debug"
@@ -30,7 +30,7 @@ NodeId :: distinct u32
no_node :: maxval!(NodeId) no_node :: maxval!(NodeId)
LiteralKind :: enum { LiteralKind :: enum {
int, float, string, bool int, float, string, bool, null
} }
ExprKind :: enum { ExprKind :: enum {
@@ -58,6 +58,11 @@ ExprKind :: enum {
index index
selection selection
invalid invalid
slice_type
optional_type
slice
builtin_call
literal_null
} }
DeclKind :: enum { DeclKind :: enum {
@@ -89,6 +94,7 @@ UnaryOperator :: enum {
logical_not logical_not
address_of address_of
dereference dereference
unwrap
} }
BinaryOperator :: enum { BinaryOperator :: enum {
@@ -144,6 +150,11 @@ NodeKind :: enum {
invalid_expr invalid_expr
invalid_stmt invalid_stmt
invalid_decl invalid_decl
expr_slice_type
expr_optional_type
expr_slice
expr_builtin_call
literal_null
} }
@hide:file @hide:file
@@ -246,6 +257,21 @@ PointerTypeView :: struct {
many bool 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 { RecordLiteralView :: struct {
span Span span Span
type ?NodeId type ?NodeId
@@ -290,6 +316,7 @@ IfView :: struct {
condition NodeId condition NodeId
then_block NodeId then_block NodeId
else_block ?NodeId else_block ?NodeId
capture ?NodeId = null
} }
AssignmentView :: struct { AssignmentView :: struct {
@@ -302,6 +329,7 @@ WhileView :: struct {
span Span span Span
condition NodeId condition NodeId
body NodeId body NodeId
capture ?NodeId = null
} }
RangeView :: struct { RangeView :: struct {
@@ -413,6 +441,7 @@ add_literal proc(builder @mut Builder, kind LiteralKind, span Span) NodeId ! mem
.float: .literal_float .float: .literal_float
.string: .literal_string .string: .literal_string
.bool: .literal_bool .bool: .literal_bool
.null: .literal_null
} }
return try add_node(builder, Node{ kind = node_kind }, span) return try add_node(builder, Node{ kind = node_kind }, span)
} }
@@ -484,6 +513,39 @@ add_call proc(
}, span) }, 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( add_array_type proc(
builder @mut Builder, span Span, count ?NodeId, element NodeId, mutable bool, builder @mut Builder, span Span, count ?NodeId, element NodeId, mutable bool,
) NodeId ! mem.AllocError { ) NodeId ! mem.AllocError {
@@ -697,9 +759,11 @@ add_if_stmt proc(
else_block ?NodeId, else_block ?NodeId,
) NodeId ! mem.AllocError { ) NodeId ! mem.AllocError {
alternative :: if (else_block) |id| id else no_node 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{ return try add_node(builder, Node{
kind = .stmt_if, kind = .stmt_if,
main = NodeData{ node_id = condition }, main = NodeData{ extra = extra },
data0 = NodeData{ node_id = then_block }, data0 = NodeData{ node_id = then_block },
data1 = NodeData{ node_id = alternative }, data1 = NodeData{ node_id = alternative },
}, span) }, span)
@@ -731,6 +795,15 @@ add_while_stmt proc(
}, span) }, 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( add_for_stmt proc(
builder @mut Builder, span Span, iterable NodeId, capture, index ?NodeId, builder @mut Builder, span Span, iterable NodeId, capture, index ?NodeId,
pointer_capture, index_capture bool, body 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(return_type))
_ = try add_extra(builder, u32(interned_name)) _ = try add_extra(builder, u32(interned_name))
for (params) |parameter| _ = try add_extra(builder, u32(parameter)) for (params) |parameter| _ = try add_extra(builder, u32(parameter))
_ = try add_extra(builder, 0)
return try add_node(builder, Node{ return try add_node(builder, Node{
kind = .func_decl, kind = .func_decl,
main = NodeData{ extra = u32(start) }, main = NodeData{ extra = u32(start) },
@@ -812,6 +886,18 @@ add_function_decl proc(
}, span) }, 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( add_import_decl proc(
builder @mut Builder, builder @mut Builder,
span, path Span, span, path Span,
@@ -980,9 +1066,10 @@ if_stmt proc(tree @Tree, id NodeId) IfView {
debug.assert(node.kind == .stmt_if) debug.assert(node.kind == .stmt_if)
return IfView{ return IfView{
span = get_span(&tree.storage, id), 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, then_block = node.data0.node_id,
else_block = maybe_node_id(u32(node.data1.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), span = get_span(&tree.storage, id),
condition = node.main.node_id, condition = node.main.node_id,
body = node.data0.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) node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type) 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 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 { function_param proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id) node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type) 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 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]) return NodeId(tree.storage.extra.items[usize(start) + index])
} }
@@ -1140,6 +1229,11 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_index: .index .expr_index: .index
.expr_selection: .selection .expr_selection: .selection
.invalid_expr: .invalid .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 else: unreachable
} }
} }
@@ -1187,13 +1281,13 @@ call_expr proc(tree @Tree, id NodeId) CallExprView {
call_arg_count proc(tree @Tree, id NodeId) usize { call_arg_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id) 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) return usize(node.data1.extra - node.data0.extra)
} }
call_arg proc(tree @Tree, id NodeId, index usize) NodeId { call_arg proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id) 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)) debug.assert(index < usize(node.data1.extra - node.data0.extra))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index]) 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 { array_element_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id) node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_array_literal) 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 { type_operand proc(tree @Tree, id NodeId) NodeId {
node :: get_node(&tree.storage, id) 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 return node.main.node_id
} }
+10
View File
@@ -94,6 +94,11 @@ Code :: enum {
invalid_dereference invalid_dereference
invalid_pointer_assignment invalid_pointer_assignment
invalid_pointer_access invalid_pointer_access
null_unwrap
invalid_slice_bounds
sentinel_mismatch
overlapping_memory_copy
unsupported_foreign_call
} }
Diagnostic :: struct { Diagnostic :: struct {
@@ -209,5 +214,10 @@ messages :: $enummap.init(Code, []u8, {
invalid_dereference = "dereference requires a pointer", invalid_dereference = "dereference requires a pointer",
invalid_pointer_assignment = "pointer assignment requires a mutable pointee", invalid_pointer_assignment = "pointer assignment requires a mutable pointee",
invalid_pointer_access = "pointer does not refer to live accessible storage", invalid_pointer_access = "pointer does not refer to live accessible storage",
null_unwrap = "cannot unwrap a null optional",
invalid_slice_bounds = "slice bounds are invalid",
sentinel_mismatch = "slice sentinel does not match the expected value",
overlapping_memory_copy = "memory copy source and destination overlap",
unsupported_foreign_call = "foreign function calls are unsupported by the interpreter",
}) })
+252 -25
View File
@@ -42,6 +42,9 @@ Address :: struct {
scalar ?@mut ir.Value = null scalar ?@mut ir.Value = null
initialized ?@mut bool = null initialized ?@mut bool = null
raw bool = false 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. # source handles retain root identity and native offsets, never frame-local address IDs.
root ?@mut ir.Value = null root ?@mut ir.Value = null
root_type ir.Type = .invalid root_type ir.Type = .invalid
@@ -55,6 +58,14 @@ Pointer :: struct {
root_type ir.Type root_type ir.Type
root_array ir.ArrayId root_array ir.ArrayId
byte_offset usize 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 @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]) globals[i].value = aggregate_view(global.type, global.array_type, aggregate_globals[global_offset..global_offset + cells])
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) pointers := arraylist.init(Pointer, allocator)
defer arraylist.deinit(&pointers) defer arraylist.deinit(&pointers)
frames := arraylist.init(Frame, allocator) frames := arraylist.init(Frame, allocator)
@@ -110,6 +147,9 @@ run proc(
for (frames.items) |frame| free_frame(allocator, frame) for (frames.items) |frame| free_frame(allocator, frame)
arraylist.deinit(&frames) 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) try push_frame(allocator, module, &frames, entry, ir.no_value, null)
while frames.items.len != 0 { while frames.items.len != 0 {
@@ -210,7 +250,14 @@ run proc(
} }
} }
.call: { .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) try push_frame(allocator, module, &frames, callee, instruction.destination, null)
# frame indices survive stack growth; pointers into frames.items do not. # frame indices survive stack growth; pointers into frames.items do not.
for (0..instruction.arguments_count) |argument_index| { for (0..instruction.arguments_count) |argument_index| {
@@ -251,6 +298,95 @@ run proc(
continue continue
} }
.union_tag: value = ir.union_tag(module, frames.items[frame_index].values[usize(instruction.left)]) .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: { .index: {
base :: frames.items[frame_index].values[usize(instruction.left)] base :: frames.items[frame_index].values[usize(instruction.left)]
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
@@ -324,15 +460,15 @@ run proc(
if (index >= u64(shape.len)) { if (index >= u64(shape.len)) {
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span) 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 start :: usize(index) * width
address := base address := base
address.type = instruction.pointee address.type = instruction.pointee
address.array_type = instruction.array_type 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.scalar = null
address.byte_offset += usize(index) * ir.storage_size(module, shape.element, shape.element_array) 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 frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } } value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
} }
@@ -345,21 +481,21 @@ run proc(
if base.initialized |initialized| { if base.initialized |initialized| {
if (!initialized^) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span) 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) return try runtime_trap(diagnostic_store, .inactive_union_field, instruction.span)
} }
} }
raw :: base.raw or shape.is_union 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 start :: if (raw) field.byte_offset else field.offset
address := base address := base
address.type = instruction.pointee address.type = instruction.pointee
address.array_type = instruction.array_type 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.scalar = null
address.raw = raw address.raw = raw
address.byte_offset += field.byte_offset 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 frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } } value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
} }
@@ -374,6 +510,8 @@ run proc(
root_type = address.root_type, root_type = address.root_type,
root_array = address.root_array, root_array = address.root_array,
byte_offset = address.byte_offset, byte_offset = address.byte_offset,
readonly = address.readonly,
bytes = address.bytes,
}) })
} }
.pointer_address: { .pointer_address: {
@@ -382,17 +520,7 @@ run proc(
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
} }
location :: pointers.items[usize(handle)] location :: pointers.items[usize(handle)]
root := Address{ if (pointer_address(module, location, instruction.pointee, instruction.array_type)) |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| {
frames.items[frame_index].addresses[usize(instruction.destination)] = address frames.items[frame_index].addresses[usize(instruction.destination)] = address
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } } value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
} else { } else {
@@ -411,7 +539,7 @@ run proc(
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
} }
address.byte_offset += usize(index) * stride 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) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
} }
value = try pointer_value(&pointers, address) value = try pointer_value(&pointers, address)
@@ -426,13 +554,11 @@ run proc(
} }
} }
if (instruction.op == .load_address) { if (instruction.op == .load_address) {
if (address.raw) value = ir.read_storage(module, address.type, address.array_type, address.cells, true) value = read_address(module, address, scratch)
else value = if (ir.aggregate(address.type)) aggregate_view(address.type, address.array_type, address.cells) else address.scalar?^
} else { } else {
source :: frames.items[frame_index].values[usize(instruction.right)] source :: frames.items[frame_index].values[usize(instruction.right)]
if (address.raw) ir.write_storage(module, address.cells, source) if (address.readonly) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
else if (ir.aggregate(address.type)) ir.copy_aggregate(module, aggregate_view(address.type, address.array_type, address.cells), source) write_address(module, address, source, scratch)
else address.scalar?^ = source
continue continue
} }
} }
@@ -526,6 +652,11 @@ run proc(
.not_equal: left.data.boolean != right.data.boolean .not_equal: left.data.boolean != right.data.boolean
else: unreachable 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 { .pointer: result = match instruction.op {
.equal: left.data.unsigned == right.data.unsigned .equal: left.data.unsigned == right.data.unsigned
.not_equal: left.data.unsigned != right.data.unsigned .not_equal: left.data.unsigned != right.data.unsigned
@@ -543,6 +674,102 @@ run proc(
unreachable 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 @hide:file
pointer_value proc(pointers @mut arraylist.ArrayList(Pointer), address Pointer) ir.Value ! mem.AllocError { 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. # ponytail: linear location interning; use a location-keyed map if distinct-address workloads grow.
+19 -2
View File
@@ -146,6 +146,14 @@ Op :: enum {
pointer_from_address pointer_from_address
pointer_address pointer_address
pointer_offset pointer_offset
pointer_cast
string_pointer
string_length
slice_check
sentinel_check
optional_check
memory_copy
memory_set
negate negate
convert convert
logical_not logical_not
@@ -172,6 +180,7 @@ Instruction :: struct {
destination ValueId = no_value destination ValueId = no_value
left ValueId = no_value left ValueId = no_value
right ValueId = no_value right ValueId = no_value
extra ValueId = no_value
slot u32 = 0 slot u32 = 0
constant Value = Value{} constant Value = Value{}
arguments_start usize = 0 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. #! producers use add_local, add_parameter, and emit; derived metadata is read-only to callers.
Function :: struct { Function :: struct {
parameters arraylist.ArrayList(Type) parameters arraylist.ArrayList(Type)
# owned by the module allocator; empty for interpreted/native-defined functions.
external_name []u8 = ""
locals arraylist.ArrayList(Type) locals arraylist.ArrayList(Type)
result Type = .void result Type = .void
result_array_type ArrayId = no_array 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)^ } .float: data = ValueData{ real = ptrcast!(@f64, &bits)^ }
.bool: data = ValueData{ boolean = (bits & 1) != 0 } .bool: data = ValueData{ boolean = (bits & 1) != 0 }
.string: data = ValueData{ string = StringId(bits) } .string: data = ValueData{ string = StringId(bits) }
.function: data = ValueData{ function = FunctionId(bits) } .function: data = ValueData{ unsigned = bits }
.void: return Value{} .void: return Value{}
else: {} else: {}
} }
@@ -462,7 +473,7 @@ write_storage proc(module @Module, cells []mut Value, value Value) void {
.float: bits = ptrcast!(@u64, &value.data.real)^ .float: bits = ptrcast!(@u64, &value.data.real)^
.bool: bits = if (value.data.boolean) 1 else 0 .bool: bits = if (value.data.boolean) 1 else 0
.string: bits = u64(value.data.string) .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 .pointer: bits = value.data.unsigned
.void: return .void: return
else: bits = ptrcast!(@u64, &value.data.integer)^ else: bits = ptrcast!(@u64, &value.data.integer)^
@@ -568,6 +579,11 @@ emit proc(
and operation.op != .assign_local and operation.op != .assign_local
and operation.op != .reset_local and operation.op != .reset_local
and operation.op != .store_address 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 count := body.value_count
if (produces_value) { if (produces_value) {
@@ -618,6 +634,7 @@ deinit proc(module @mut Module) void {
arraylist.deinit(&function.locals) arraylist.deinit(&function.locals)
arraylist.deinit(&function.blocks) arraylist.deinit(&function.blocks)
arraylist.deinit(&function.array_storage) arraylist.deinit(&function.array_storage)
if (function.external_name.len != 0) mem.free(module.allocator, function.external_name)
} }
for (module.strings.items) |string| for (module.strings.items) |string|
mem.free(module.allocator, string.allocation) mem.free(module.allocator, string.allocation)
+4
View File
@@ -21,6 +21,8 @@ SourceError :: enum { source_too_large }
@hide:file @hide:file
keywords strmap.StaticStringMap(TokenKind) :: strmap.init([ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
{ "proc", .proc }, { "proc", .proc },
{ "c_func", .c_func },
{ "null", .null },
{ "return", .return }, { "return", .return },
{ "break", .break }, { "break", .break },
{ "continue", .continue }, { "continue", .continue },
@@ -297,6 +299,8 @@ scan proc(
'@': .at '@': .at
'&': .ampersand '&': .ampersand
'^': .caret '^': .caret
'?': .question
';': .semicolon
'(': .open_paren '(': .open_paren
')': .close_paren ')': .close_paren
'[': .open_bracket '[': .open_bracket
+3
View File
@@ -29,6 +29,8 @@ TokenKind :: enum {
distinct, alias distinct, alias
import, test import, test
mut mut
c_func
null
# literals # literals
ident, string ident, string
@@ -49,6 +51,7 @@ TokenKind :: enum {
range, range_inclusive range, range_inclusive
at, ampersand, caret at, ampersand, caret
question, semicolon
pipe pipe
# logical # logical
+181 -4
View File
@@ -178,6 +178,9 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
} }
.pointer_address: { .pointer_address: {
pointer :: try load(state, instruction.left) 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) address :: fresh(state)
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer}) 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}) 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 write_type(output, state.module, Type{ kind = instruction.pointee, shape = instruction.array_type })
try io.print(", ptr %r{}, i64 %r{}\n", output, {pointer, index}) 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: { .load_address, .store_address: {
address :: try load(state, instruction.left) address :: try load(state, instruction.left)
pointer :: fresh(state) 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 io.print(" %r{} = load ", output, {result})
try write_type(output, state.module, type) 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: { .call: {
callee :: try load(state, instruction.left) 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}) if (instruction.type != .void) try io.print(" %r{} = call ", output, {result})
else try io.write_all(output, " call ") 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}) try io.print(" %r{}(", output, {callee})
for (0..instruction.arguments_count) |index| { for (0..instruction.arguments_count) |index| {
if (index != 0) try io.write_all(output, ", ") if (index != 0) try io.write_all(output, ", ")
operand :: block.arguments.items[instruction.arguments_start + index] 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.print(" %r{}", output, {first_argument + index})
} }
try io.write_all(output, ")\n") 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) 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 @hide:file
check_union proc(state @mut State, pointer usize, shape ir.ArrayType, field usize, span diagnostics.Span) void ! io.WriteError { 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 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 io.write_all(state.output, " store ")
try write_type(state.output, state.module, type) 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 @hide:file
+106 -16
View File
@@ -46,7 +46,7 @@ emit proc(
try io.write_all(output, runtime) try io.write_all(output, runtime)
for (sources) |source, index| { for (sources) |source, index| {
try io.print("\n@p{} = private constant ", output, {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}) try io.print("\n@paths = private constant [{} x %string] [", output, {sources.len})
for (sources) |source, index| { for (sources) |source, index| {
@@ -54,14 +54,23 @@ emit proc(
try io.print("%string {{ ptr @p{}, i64 {} }}", output, {index, source.path.len}) try io.print("%string {{ ptr @p{}, i64 {} }}", output, {index, source.path.len})
} }
try io.write_all(output, "]\n") 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 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") try io.write_all(output, "\n")
} }
for (diagnostic_store.list.items) |diagnostic, index| { for (diagnostic_store.list.items) |diagnostic, index| {
try io.print("@d{} = private constant ", output, {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 io.write_all(output, "\n")
} }
try emit_reporting(output, diagnostic_store, line_starts.items, sources, primary) 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)) else try emit_array_printer(output, module, ir.ArrayId(index))
} }
for (module.functions.items) |function, 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) values :: try mem.alloc(Type, allocator, function.value_count)
defer mem.free(allocator, values) defer mem.free(allocator, values)
mem.set(values, Type{}) mem.set(values, Type{})
@@ -97,8 +114,10 @@ emit proc(
if (result.kind != .void) try io.write_all(output, " %r0 = ") if (result.kind != .void) try io.write_all(output, " %r0 = ")
else try io.write_all(output, " ") else try io.write_all(output, " ")
try io.write_all(output, "call ") try io.write_all(output, "call ")
try write_type(output, module, result) try write_abi_result(output, module, result)
try io.print(" @f{}()\n", output, {u32(entry)}) 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) { if (result.kind != .void and result.kind != .invalid) {
try print_call(output, module, result, 0) try print_call(output, module, result, 0)
try io.write_all(output, " call void @out(i32 1, ptr @newline, i64 1)\n") try io.write_all(output, " call void @out(i32 1, ptr @newline, i64 1)\n")
@@ -113,13 +132,15 @@ emit proc(
} }
@hide:file @hide:file
bytes_constant proc(output io.Writer, bytes []u8) void ! io.WriteError { bytes_constant proc(output io.Writer, bytes []u8, terminated bool) void ! io.WriteError {
try io.print("[{} x i8] c\"", output, {bytes.len}) size usize :: if (terminated) bytes.len + 1 else bytes.len
try io.print("[{} x i8] c\"", output, {size})
digits :: "0123456789ABCDEF" digits :: "0123456789ABCDEF"
for (bytes) |byte| { for (bytes) |byte| {
escaped [3]u8 :: ['\\', digits[usize(divtrunc!(byte, 16))], digits[usize(rem!(byte, 16))]] escaped [3]u8 :: ['\\', digits[usize(divtrunc!(byte, 16))], digits[usize(rem!(byte, 16))]]
try io.write_all(output, (&escaped)[..]) try io.write_all(output, (&escaped)[..])
} }
if (terminated) try io.write_all(output, "\\00")
try io.write_all(output, "\"") 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 @hide:file
local_type proc(function @ir.Function, index usize) Type { local_type proc(function @ir.Function, index usize) Type {
type := Type{ kind = function.locals.items[index] } 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)}) try io.print(" call void @f{}()\n", output, {u32(global.initializer)})
} else { } else {
try io.write_all(output, " %value = call ") 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 io.print(" @f{}()\n store ", output, {u32(global.initializer)})
try write_type(output, module, type) try write_type(output, module, type)
try io.print(" %value, ptr @g{}\n", output, {index}) 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}) 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 @hide:file
trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError { trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError {
position :: location(state.line_starts, span) 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" .integer_overflow: "overflow"
.index_out_of_bounds: "bounds" .index_out_of_bounds: "bounds"
.inactive_union_field: "inactive_union" .inactive_union_field: "inactive_union"
else: unreachable else: ""
} }
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)? message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
length :: message.len 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 @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}) .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}) .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: { .array: {
shape :: module.arrays.items[usize(value.array_type)] shape :: module.arrays.items[usize(value.array_type)]
try io.write_all(output, "[") try io.write_all(output, "[")
@@ -482,14 +565,21 @@ write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void !
@hide:file @hide:file
emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.WriteError { emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.WriteError {
output :: state.output output :: state.output
try io.write_all(output, "define internal ") if (function.external_name.len != 0) try io.write_all(output, "declare ")
try write_type(output, state.module, Type{ kind = function.result, shape = function.result_array_type }) else try io.write_all(output, "define internal ")
try io.print(" @f{}(", output, {id}) 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| { for (function.parameters.items) |_, index| {
if (index != 0) try io.write_all(output, ", ") 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}) 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") try io.write_all(output, ") {\nentry:\n")
for (function.locals.items) |_, index| { for (function.locals.items) |_, index| {
type :: local_type(function, index) type :: local_type(function, index)
+1
View File
@@ -8,6 +8,7 @@ runtime ::
`declare i32 @snprintf(ptr, i64, ptr, ...) `declare i32 @snprintf(ptr, i64, ptr, ...)
`declare void @exit(i32) noreturn `declare void @exit(i32) noreturn
`declare double @llvm.trunc.f64(double) `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.sadd.with.overflow.i8(i8, i8)
`declare { i8, i1 } @llvm.ssub.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) `declare { i8, i1 } @llvm.smul.with.overflow.i8(i8, i8)
+409 -21
View File
@@ -38,6 +38,7 @@ State :: struct {
block ir.BlockId = ir.BlockId(0) block ir.BlockId = ir.BlockId(0)
terminated bool = false terminated bool = false
loop ?LoopTargets = null loop ?LoopTargets = null
return_type typecheck.Type = typecheck.Type{}
} }
#! lowers checked source into caller-owned executable storage; release with ir.deinit. #! 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) { if (type.kind == .enumeration) {
return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing }) 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 { return match type.kind {
.int: .int .int: .int
.usize: .usize .usize: .usize
@@ -156,13 +159,21 @@ lower_type proc(state @State, type typecheck.Type) ir.Type {
.void: .void .void: .void
.function: .function .function: .function
.enumeration: unreachable .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 @hide:file
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) { 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 == .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 if (type.kind != .array) return ir.no_array
# checker history includes unresolved shapes; materialize only final used types. # checker history includes unresolved shapes; materialize only final used types.
return try prepare_array(state, type.array_type) 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)] if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
descriptor :: typecheck.array_type(state.checked, id) descriptor :: typecheck.array_type(state.checked, id)
child := ir.no_array 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( result :: try ir.add_array(
state.module, state.module,
lower_type(state, descriptor.element), lower_type(state, descriptor.element),
@@ -185,6 +196,22 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
return result 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 @hide:file
prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) { 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)] 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 if (trap_binding(state, id)) return
declaration :: ast.statement_decl(state.tree, id) declaration :: ast.statement_decl(state.tree, id)
if (declaration.type) |annotation| if (trap_type(state, annotation)) return 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 if (state.terminated or trap_type(state, id)) return
finish(state, value, declaration.span) 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.function = usize(state.storage[usize(id)].index)
state.block = ir.BlockId(0) state.block = ir.BlockId(0)
state.terminated = false 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 = 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)) 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| { 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) } 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) try predeclare_locals(state, declaration.body)
# signature errors are entry traps; body errors are reached in statement order. # 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 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: { .if_stmt: {
conditional :: ast.if_stmt(state.tree, statement) conditional :: ast.if_stmt(state.tree, statement)
if (conditional.capture) |capture| try predeclare_capture(state, capture)
try predeclare_locals(state, conditional.then_block) try predeclare_locals(state, conditional.then_block)
if (conditional.else_block) |alternative| try predeclare_locals(state, alternative) 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: { .for_stmt: {
loop :: ast.for_stmt(state.tree, statement) loop :: ast.for_stmt(state.tree, statement)
family :: typecheck.type_of(state.checked, loop.iterable).range_element 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 @hide:file
lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (mem.AllocError | diagnostics.CapacityError) { 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. # 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 @hide:file
lower_if proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { lower_if proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
conditional :: ast.if_stmt(state.tree, id) 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 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 origin :: state.block
consequent :: try add_block(state) consequent :: try add_block(state)
alternative :: 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), span = ast.node_span(state.tree, conditional.condition),
} }
state.block = consequent 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) try lower_block(state, conditional.then_block, true)
consequent_end :: state.block consequent_end :: state.block
consequent_falls :: !state.terminated 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, kind = .jump, target = guard, span = loop.span,
} }
state.block = guard state.block = guard
condition :: try lower_expression(state, loop.condition) value :: try lower_expression(state, loop.condition)
if (state.terminated) return 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 guard_end :: state.block
body :: try add_block(state) body :: try add_block(state)
exit :: 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 } state.loop = LoopTargets{ continue_target = guard, exit = exit }
defer state.loop = previous_loop defer state.loop = previous_loop
state.block = body 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) try lower_block(state, loop.body, true)
if (!state.terminated) { if (!state.terminated) {
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ 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) == .unary
or (ast.expr_kind(tree, assignment.target) == .selection or (ast.expr_kind(tree, assignment.target) == .selection
and sema.binding(state.resolution, assignment.target).kind == .none)) { 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 (state.terminated) return
if (!is_place(state, assignment.target)) { if (!is_place(state, assignment.target)) {
_ = try lower_expression(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 return
} }
if (trap_binding(state, assignment.target) or trap_type(state, id)) 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 if (state.terminated) return
binding :: sema.binding(state.resolution, assignment.target) binding :: sema.binding(state.resolution, assignment.target)
_ = try emit(state, ir.Instruction{ _ = 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 if (trap_binding(state, id)) return
declaration :: ast.statement_decl(tree, id) declaration :: ast.statement_decl(tree, id)
if (declaration.type) |annotation| if (trap_type(state, annotation)) return 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 if (state.terminated or trap_type(state, id)) return
_ = try emit(state, ir.Instruction{ _ = try emit(state, ir.Instruction{
op = .store_local, op = .store_local,
@@ -650,7 +710,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
.return_stmt: { .return_stmt: {
statement :: ast.return_stmt(tree, id) statement :: ast.return_stmt(tree, id)
value := ir.no_value 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 if (state.terminated or trap_type(state, id)) return
finish(state, value, statement.span) 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.block = body
state.terminated = false state.terminated = false
if (arm_view.expression) { 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{ if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
op = .store_local, left = value, slot = result_slot, span = span, 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)) { 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)) payload = try lower_record_as(state, id, typecheck.union_payload_type(state.checked, id))
} else if (count != 0) { } 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 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 scratch_start :: state.arguments.items.len
defer state.arguments.items = state.arguments.items.ptr[..scratch_start] defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
explicit_count :: ast.record_field_count(tree, id) 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) 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) |_| try arraylist.append(&state.arguments, ir.no_value)
for (0..explicit_count) |i| { 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 if (state.terminated) return ir.no_value
state.arguments.items[scratch_start + i] = value state.arguments.items[scratch_start + i] = value
} }
if (trap_type(state, id)) return ir.no_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 ordered_start :: state.arguments.items.len
try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len) try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len)
for (descriptor.fields) |_| try arraylist.append(&state.arguments, ir.no_value) 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 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 if (state.terminated) return ir.no_value
state.arguments.items[ordered_start + i] = 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 @hide:file
is_place proc(state @State, id ast.NodeId) bool { is_place proc(state @State, id ast.NodeId) bool {
match ast.expr_kind(state.tree, id) { 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: { .index: {
base :: ast.index_expr(state.tree, id).base 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: { .selection: {
if (sema.binding(state.resolution, id).kind == .none) { 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 @hide:file
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id) 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) { if (ast.expr_kind(state.tree, id) == .index) {
expression :: ast.index_expr(state.tree, id) expression :: ast.index_expr(state.tree, id)
base_type :: typecheck.type_of(state.checked, expression.base) 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 base := ir.no_value
if (base_type.kind == .pointer) { if (base_type.kind == .pointer) {
pointer :: try lower_expression(state, expression.base) 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. # ranges have no physical value; only lower_for decomposes an iterable.
if (!trap_type(state, id)) unreachable 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)) { if (!trap_type(state, id)) {
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span) 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 if (!trap_type(state, id)) unreachable
} }
.match_expr: return try lower_match(state, id) .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: { .record_literal: {
checked :: typecheck.type_of(state.checked, id) checked :: typecheck.type_of(state.checked, id)
if (checked.kind == .record and typecheck.record_type(state.checked, checked.record_type).is_union) { 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) try arraylist.reserve(&state.arguments, scratch_start + count)
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value) for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
for (0..count) |i| { 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 if (state.terminated) return ir.no_value
state.arguments.items[scratch_start + i] = element 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) { and sema.binding(state.resolution, id).kind == .none) {
selection :: ast.selection_expr(tree, id) selection :: ast.selection_expr(tree, id)
base_type :: typecheck.type_of(state.checked, selection.base) 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) { if (base_type.kind == .pointer) {
descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type) descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type)
if (!descriptor.many and descriptor.element.kind == .array) { 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: { .unary: {
expression :: ast.unary_expr(tree, id) 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 (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)) { if (!is_place(state, expression.operand)) {
_ = try lower_expression(state, expression.operand) _ = try lower_expression(state, expression.operand)
if (!state.terminated) _ = trap_type(state, id) 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 { op ir.Op :: match expression.operator {
.negate: .negate .negate: .negate
.logical_not: .logical_not .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 }) 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) { if (expression.operator == .logical_and or expression.operator == .logical_or) {
return try lower_logical(state, id) 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) left :: try lower_expression(state, expression.left)
if (state.terminated) return ir.no_value if (state.terminated) return ir.no_value
right :: try lower_expression(state, expression.right) 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) try arraylist.reserve(&state.arguments, scratch_start + count)
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value) for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
for (0..count) |i| { 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 if (state.terminated) return ir.no_value
state.arguments.items[scratch_start + i] = argument state.arguments.items[scratch_start + i] = argument
} }
+94 -43
View File
@@ -110,7 +110,7 @@ parse_decl proc(state @mut State) ast.NodeId ! ParseError {
next_is(state, .import) or next_is(state, .test) 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)) 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) ) 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) return try parse_statement_decl(state)
} }
@@ -144,7 +144,8 @@ parse_import_decl proc(state @mut State) ast.NodeId ! ParseError {
@hide:file @hide:file
parse_func_decl proc(state @mut State) ast.NodeId ! ParseError { parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
name :: try consume(state, .ident) 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) _ = try consume(state, .open_paren)
params arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator) params arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
@@ -152,9 +153,9 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
try parse_parameters(state, &params) try parse_parameters(state, &params)
_ = try consume(state, .close_paren) _ = try consume(state, .close_paren)
return_type :: try parse_type(state) 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) body_span :: ast.builder_span(state.builder, body)
return try ast.add_function_decl( id :: try ast.add_function_decl(
state.builder, state.builder,
ast.Span{ ast.Span{
start = name.span.start, start = name.span.start,
@@ -167,6 +168,8 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
params.items, params.items,
body, body,
) )
if (external) ast.set_function_external(state.builder, id)
return id
} }
@hide:file @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, .enum) or next_n_is(state, 1, .union)
or next_n_is(state, 1, .distinct) or next_n_is(state, 1, .distinct)
or next_n_is(state, 1, .at) 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, .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, .open_bracket) and array_starts_type(state))
or next_n_is(state, 1, .double_colon) 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) .return: return try parse_return(state, allow_else)
.break, .continue: return try parse_loop_control(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, .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) expression :: try parse_expr(state)
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else) if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
return try ast.add_expression_stmt(state.builder, expression) 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 { parse_if proc(state @mut State) ast.NodeId ! ParseError {
keyword :: try consume(state, .if) keyword :: try consume(state, .if)
condition :: try parse_guard(state) condition :: try parse_guard(state)
capture :: try parse_optional_capture(state)
braced :: next_is(state, .open_curly) braced :: next_is(state, .open_curly)
then_block :: try parse_arm(state, true) then_block :: try parse_arm(state, true)
# only a braced arm permits its else on a later line. # 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 ast.builder_span(state.builder, block).end
else else
ast.builder_span(state.builder, then_block).end ast.builder_span(state.builder, then_block).end
return try ast.add_if_stmt( id :: try ast.add_if_stmt(
state.builder, state.builder,
ast.Span{ ast.Span{
start = keyword.span.start, start = keyword.span.start,
@@ -375,14 +380,17 @@ parse_if proc(state @mut State) ast.NodeId ! ParseError {
then_block, then_block,
alternative, alternative,
) )
if (capture) |node| ast.set_capture(state.builder, id, node)
return id
} }
@hide:file @hide:file
parse_while proc(state @mut State, allow_else bool) ast.NodeId ! ParseError { parse_while proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
keyword :: try consume(state, .while) keyword :: try consume(state, .while)
condition :: try parse_guard(state) condition :: try parse_guard(state)
capture :: try parse_optional_capture(state)
body :: try parse_arm(state, allow_else) body :: try parse_arm(state, allow_else)
return try ast.add_while_stmt( id :: try ast.add_while_stmt(
state.builder, state.builder,
ast.Span{ ast.Span{
start = keyword.span.start, start = keyword.span.start,
@@ -392,6 +400,18 @@ parse_while proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
condition, condition,
body, 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 @hide:file
@@ -529,6 +549,7 @@ parse_guard proc(state @mut State) ast.NodeId ! ParseError {
} }
if ( if (
!next_is(state, .open_curly) !next_is(state, .open_curly)
and !next_is(state, .pipe)
and !parenthesized_guard(state, condition_start) and !parenthesized_guard(state, condition_start)
and ast.builder_expr_kind(state.builder, condition) != .call and ast.builder_expr_kind(state.builder, condition) != .call
and ast.builder_expr_kind(state.builder, condition) != .invalid 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 @hide:file
parse_type proc(state @mut State) ast.NodeId ! ParseError { 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)) { if (next_is(state, .at) or next_is(state, .star)) {
token :: current(state) token :: current(state)
state.next_token += 1 state.next_token += 1
@@ -642,6 +671,20 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
} }
if next_is(state, .open_bracket) { if next_is(state, .open_bracket) {
open :: try consume(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 count ?ast.NodeId := null
if ( if (
next_is(state, .ident) next_is(state, .ident)
@@ -690,7 +733,7 @@ starts_type proc(kind lexer.TokenKind) bool {
return ( return (
kind == .ident or kind == .open_bracket or kind == .struct kind == .ident or kind == .open_bracket or kind == .struct
or kind == .enum or kind == .union or kind == .distinct or kind == .alias 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 { return match kind {
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue, .ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct, .true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
.alias, .match, .import, .test: true .alias, .match, .import, .test, .c_func, .null: true
else: false else: false
} }
} }
@@ -1124,18 +1167,25 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
while true { while true {
skip_continuation_newlines(state) skip_continuation_newlines(state)
token = current(state) token = current(state)
if (token.kind == .caret) { if (token.kind == .caret or token.kind == .question) {
state.next_token += 1 state.next_token += 1
operator ast.UnaryOperator :: if (token.kind == .caret) .dereference else .unwrap
left = try ast.add_unary(state.builder, ast.Span{ left = try ast.add_unary(state.builder, ast.Span{
start = state.tokens[expression_start].span.start, start = state.tokens[expression_start].span.start,
end = token.span.end, end = token.span.end,
source = token.span.source, 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 left_grouped = false
continue continue
} }
if (token.kind == .open_paren) { 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 left_grouped = false
continue continue
} }
@@ -1228,7 +1278,7 @@ validate_range_bound proc(
} }
@hide:file @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) _ = try consume(state, .open_paren)
previous :: state.allow_record_literals previous :: state.allow_record_literals
state.allow_record_literals = true 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 return .unexpected_token
} }
close :: try consume(state, .close_paren) close :: try consume(state, .close_paren)
return try ast.add_call( span :: ast.Span{ start = start, end = close.span.end, source = state.builder.source_id }
state.builder, if (builtin) return try ast.add_builtin_call(state.builder, span, callee, arguments.items)
ast.Span{ return try ast.add_call(state.builder, span, callee, arguments.items)
start = start,
end = close.span.end,
source = state.builder.source_id,
},
callee,
arguments.items,
)
} }
@hide:file @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 previous :: state.allow_record_literals
state.allow_record_literals = true state.allow_record_literals = true
defer state.allow_record_literals = previous 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) { if !next_is(state, .close_bracket) {
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span) diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
return try ast.add_invalid_expr( return try ast.add_invalid_expr(state.builder, ast.Span{
state.builder, start = start, end = ast.builder_span(state.builder, first?).end, source = state.builder.source_id,
ast.Span{ }, diagnostic)
start = start,
end = ast.builder_span(state.builder, index).end,
source = state.builder.source_id,
},
diagnostic,
)
} }
close :: try consume(state, .close_bracket) close :: try consume(state, .close_bracket)
return try ast.add_index( return try ast.add_index(state.builder, ast.Span{
state.builder, start = start, end = close.span.end, source = state.builder.source_id,
ast.Span{ }, base, first?)
start = start,
end = close.span.end,
source = state.builder.source_id,
},
base, index,
)
} }
@hide:file @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) 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, .ident) or next_n_is(state, 1, .proc)
or next_n_is(state, 1, .at) 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, .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 { parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state) token :: current(state)
match token.kind { 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) .struct: return try parse_record_type(state)
.enum: return try parse_enum_type(state) .enum: return try parse_enum_type(state)
.union: return try parse_union_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 state.next_token += 1
return try ast.add_literal(state.builder, .bool, token.span) 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: { .ident: {
state.next_token += 1 state.next_token += 1
return try ast.add_identifier(state.builder, token.span, token.data.str_id) return try ast.add_identifier(state.builder, token.span, token.data.str_id)
+489 -20
View File
@@ -24,6 +24,10 @@ TypeKind :: enum {
type_value type_value
u8, u16, u32, u64, usize u8, u16, u32, u64, usize
i8, i16, i32, isize i8, i16, i32, isize
slice
optional
null
anyopaque
} }
RecordId :: distinct u32 RecordId :: distinct u32
@@ -69,8 +73,10 @@ no_array :: maxval!(ArrayId)
ArrayType :: struct { ArrayType :: struct {
element Type element Type
len usize len usize = 0
mutable bool mutable bool = false
kind TypeKind = .array
sentinel ?i64 = null
} }
PointerId :: distinct u32 PointerId :: distinct u32
@@ -344,6 +350,7 @@ check proc(
} }
} }
# Calls use the signature result before contextual literal inference. # Calls use the signature result before contextual literal inference.
try prepare_optional_captures(&state, false)
for (0..count) |i| { for (0..count) |i| {
id :: ast.NodeId(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 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..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 state.records.items |record| {
for record.fields |field| { for record.fields |field| {
if (field.default) |value| try compatible(&state, value, field.type) if (field.default) |value| try compatible(&state, value, field.type)
@@ -492,8 +510,10 @@ check proc(
id :: ast.NodeId(i) id :: ast.NodeId(i)
if (cells[i].role != .if_stmt and cells[i].role != .while_stmt) continue 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 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)] 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)) 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| { for (0..ast.package_decl_count(tree)) |i| {
id :: ast.package_decl(tree, i) id :: ast.package_decl(tree, i)
if (ast.decl_kind(tree, id) != .function) continue if (ast.decl_kind(tree, id) != .function) continue
if (ast.function_external(tree, id)) continue
function :: ast.function_decl(tree, id) function :: ast.function_decl(tree, id)
result :: types[usize(function.return_type)] result :: types[usize(function.return_type)]
if (result.kind == .void or result.kind == .invalid or types[usize(id)].kind == .invalid) continue 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) { 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)] if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
state.cells[usize(id)].role = .annotation 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) { if (ast.expr_kind(state.tree, id) == .pointer_type) {
type :: try check_pointer_type(state, id) type :: try check_pointer_type(state, id)
state.types[usize(id)] = type 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) .invalid: type = invalid_type(binding.data.diagnostic)
.builtin: { .builtin: {
type.kind = builtin_kind(binding.data.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) { if (type.kind == .void and !allow_void) {
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_void_type, ast.node_span(state.tree, id))) 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 = invalid_type(binding.data.diagnostic)
state.cells[usize(id)].type.function = id 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 @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 } state.cells[usize(statement)].type = Type{ kind = .void }
try collect_expression(state, conditional.condition) try collect_expression(state, conditional.condition)
try depend(state, conditional.condition, statement) 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 collect_block(state, conditional.then_block, return_annotation, in_loop)
try depend(state, conditional.then_block, id) try depend(state, conditional.then_block, id)
if (conditional.else_block) |other| { 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 } state.cells[usize(statement)].type = Type{ kind = .void }
try collect_expression(state, loop.condition) try collect_expression(state, loop.condition)
try depend(state, loop.condition, statement) 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 collect_block(state, loop.body, return_annotation, true)
try depend(state, loop.body, id) 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_bool: state.cells[usize(id)].type = Type{ kind = .bool }
.literal_string: state.cells[usize(id)].type = Type{ kind = .string } .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: { .identifier, .selection: {
binding :: sema.binding(state.resolution, id) binding :: sema.binding(state.resolution, id)
if (binding.kind == .none and ast.expr_kind(tree, id) == .selection) { 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: { .array_type: {
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id))) 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) type := try check_type_expression(state, id, state.cells[usize(id)].nominal)
if (type.kind != .invalid) { if (type.kind != .invalid) {
type.value_kind = type.kind type.value_kind = type.kind
@@ -1236,6 +1304,15 @@ builtin_kind proc(type sema.BuiltinType) TypeKind {
.bool: .bool .bool: .bool
.string: .string .string: .string
.void: .void .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 expression.operator == .add and left.kind == .pointer
and left.nominal == ast.NodeId(maxval!(u32)) and left.nominal == ast.NodeId(maxval!(u32))
and state.pointers.items[usize(left.pointer_type)].many 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) (expression.operator == .equal or expression.operator == .not_equal)
and ((left.kind == .bool and right.kind == .bool) and ((left.kind == .bool and right.kind == .bool)
or left.kind == .enumeration or right.kind == .enumeration 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) { 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 if (state.types[usize(id)].kind == .invalid) return
index :: root(state, usize(id)) 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 if (expected.kind == .pointer and state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .unary and ast.expr_kind(state.tree, id) == .unary
and ast.unary_expr(state.tree, id).operator == .address_of) { 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 return pointer.mutable and (pointer.many
or (pointer.element.kind == .array and state.arrays.items[usize(pointer.element.array_type)].mutable)) 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) 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) { 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 @hide:file
indexable_type proc(state @State, type Type) bool { indexable_type proc(state @State, type Type) bool {
if (type.nominal != ast.NodeId(maxval!(u32))) return false 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 if (type.kind != .pointer) return false
pointer :: state.pointers.items[usize(type.pointer_type)] 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 @hide:file
@@ -1508,7 +1624,7 @@ addressable_target proc(state @mut State, id ast.NodeId) bool {
if (kind == .index) { if (kind == .index) {
view :: ast.index_expr(state.tree, id) view :: ast.index_expr(state.tree, id)
base :: group_type(state, view.base) 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) { if (kind == .selection and sema.binding(state.resolution, id).kind == .none) {
view :: ast.selection_expr(state.tree, id) view :: ast.selection_expr(state.tree, id)
@@ -1528,6 +1644,7 @@ addressable_target proc(state @mut State, id ast.NodeId) bool {
@hide:file @hide:file
same_signature proc(state @State, left, right ast.NodeId) bool { same_signature proc(state @State, left, right ast.NodeId) bool {
if (left == right) return true if (left == right) return true
if (ast.function_external(state.tree, left) != ast.function_external(state.tree, right)) return false
tree :: state.tree tree :: state.tree
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
left_result :: ast.callable_result(tree, left) left_result :: ast.callable_result(tree, left)
@@ -1544,10 +1661,32 @@ same_signature proc(state @State, left, right ast.NodeId) bool {
@hide:file @hide:file
assignable_type proc(state @State, actual, expected Type) bool { assignable_type proc(state @State, actual, expected Type) bool {
if (same_type(state, actual, expected)) return true 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 if (actual.kind != .pointer or expected.kind != .pointer or actual.nominal != expected.nominal) return false
a :: state.pointers.items[usize(actual.pointer_type)] a :: state.pointers.items[usize(actual.pointer_type)]
b :: state.pointers.items[usize(expected.pointer_type)] b :: state.pointers.items[usize(expected.pointer_type)]
if (a.many != b.many or (!a.mutable and b.mutable)) return false 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) if (b.mutable) return same_type(state, a.element, b.element)
return assignable_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) { compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
actual :: state.types[usize(value)] actual :: state.types[usize(value)]
if (actual.kind == .invalid or expected.kind == .invalid) return 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)) 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 @hide:file
validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree 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 { match state.cells[usize(id)].role {
.declaration: { .declaration: {
declaration :: ast.statement_decl(tree, id) 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) { if (state.types[usize(declaration.initializer)].kind == .void) {
try fail(state, declaration.initializer, .type_mismatch, ast.node_span(tree, declaration.initializer)) 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)] actual :: state.types[usize(value)]
if (actual.kind == .invalid) { if (actual.kind == .invalid) {
state.types[usize(id)] = actual 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)) try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
state.types[usize(id)] = state.types[usize(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 (expression.operator == .address_of) {
if (!addressable_target(state, expression.operand)) { if (!addressable_target(state, expression.operand)) {
if (state.types[usize(id)].kind == .slice) return
try fail(state, id, .invalid_address_operand, expression.span) try fail(state, id, .invalid_address_operand, expression.span)
} }
return 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 (expression.operator == .dereference) {
if (state.types[usize(expression.operand)].kind != .invalid if (state.types[usize(expression.operand)].kind != .invalid
and state.types[usize(expression.operand)].kind != .pointer) { 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 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) if ((expression.operator == .equal or expression.operator == .not_equal)
and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer) 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 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) .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: {} else: {}
} }
} }
@@ -1959,13 +2127,214 @@ check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
@hide:file @hide:file
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError { intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
for state.arrays.items |existing, i| { for state.arrays.items |existing, i| {
if (existing.len == descriptor.len and existing.mutable == descriptor.mutable if (existing.kind == descriptor.kind and existing.len == descriptor.len and existing.mutable == descriptor.mutable
and same_type(state, existing.element, descriptor.element)) { and same_sentinel(existing.sentinel, descriptor.sentinel) and same_type(state, existing.element, descriptor.element)) {
return Type{ kind = .array, array_type = ArrayId(i) } return Type{ kind = descriptor.kind, array_type = ArrayId(i) }
} }
} }
try arraylist.append(&state.arrays, descriptor) 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 @hide:file
@@ -2003,15 +2372,40 @@ same_type proc(state @State, left, right Type) bool {
} }
return true 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)] a :: state.arrays.items[usize(left.array_type)]
b :: state.arrays.items[usize(right.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 @hide:file
array_cells proc(state @State, type Type) ?usize { array_cells proc(state @State, type Type) ?usize {
if (type.kind == .void) return usize(0) 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) { if (type.kind == .record) {
total := usize(0) total := usize(0)
descriptor :: state.records.items[usize(type.record_type)] 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 if (state.cells[index].type.kind == .invalid) return state.cells[index].type
state.cells[index].visiting = true state.cells[index].visiting = true
defer state.cells[index].visiting = false 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) { if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .binary) {
view :: ast.binary_expr(state.tree, id) view :: ast.binary_expr(state.tree, id)
if (view.operator == .add) { 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) return group_type(state, id)
} }
element :: state.pointers.items[usize(operand.pointer_type)].element 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) seed(state, id, element)
return element return element
} }
mutable :: mutable_target(state, view.operand, true) mutable :: mutable_target(state, view.operand, true)
expected :: state.cells[index].expected 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 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 }) type :: try intern_pointer(state, PointerType{ element = operand, mutable = mutable, many = many })
seed(state, id, type) 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 }) seed(state, id, Type{ kind = destination })
return group_type(state, id) 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 if (state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .selection 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) if (enum_shape.kind == .enumeration) return try select_enum_member(state, id, view.member, enum_shape)
} }
base := try infer_shape(state, view.base, defaults) 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))) { if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)] pointer :: state.pointers.items[usize(base.pointer_type)]
if (!pointer.many and pointer.element.kind == .array 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 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 element :: state.arrays.items[usize(base.array_type)].element
seed(state, id, element) seed(state, id, element)
return 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) 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 @hide:file
prepare_shapes proc(state @mut State, assignment_values, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) { 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| { for 0..(state.cells.len) |i| {
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values) continue if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values) continue
id :: ast.NodeId(i) id :: ast.NodeId(i)
@@ -2365,7 +2832,8 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
@hide:file @hide:file
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) { 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 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) try demand(state, id, expected, code)
} }
@@ -2748,7 +3216,7 @@ type_syntax proc(kind ast.ExprKind) bool {
return ( return (
kind == .record_type or kind == .union_type or kind == .enum_type kind == .record_type or kind == .union_type or kind == .enum_type
or kind == .distinct_type or kind == .alias_expr 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 == .record_type) return try check_record(state, id, nominal)
if (kind == .pointer_type) return try check_pointer_type(state, id) if (kind == .pointer_type) return try check_pointer_type(state, id)
if (kind == .function_type) return try check_function_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) { if (kind == .union_type) {
type :: try check_union(state, id, nominal) type :: try check_union(state, id, nominal)
state.cells[usize(id)].type = type state.cells[usize(id)].type = type
+41 -4
View File
@@ -14,6 +14,9 @@ BuiltinType :: enum {
f64, usize, f64, usize,
i8, i16, i32, isize, i8, i16, i32, isize,
u8, u16, u32, u64, uint, 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 @hide:file
@@ -41,6 +44,17 @@ builtins [_]Builtin :: [
{ name = "u32", type = .u32 }, { name = "u32", type = .u32 },
{ name = "u64", type = .u64 }, { name = "u64", type = .u64 },
{ name = "uint", type = .uint }, { 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 @hide:file
@@ -371,7 +385,7 @@ resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (me
.if_stmt: { .if_stmt: {
conditional :: ast.if_stmt(tree, statement) conditional :: ast.if_stmt(tree, statement)
try resolve_expr(state, scope, conditional.condition) 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) if (conditional.else_block) |other| try resolve_arm(state, scope, other)
} }
.assignment_stmt: { .assignment_stmt: {
@@ -382,7 +396,7 @@ resolve_block proc(state @mut State, scope @mut Scope, id ast.NodeId) void ! (me
.while_stmt: { .while_stmt: {
loop :: ast.while_stmt(tree, statement) loop :: ast.while_stmt(tree, statement)
try resolve_expr(state, scope, loop.condition) 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: { .for_stmt: {
loop :: ast.for_stmt(tree, statement) 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 @hide:file
resolve_statement_decl proc( resolve_statement_decl proc(
state @mut State, state @mut State,
@@ -487,9 +509,9 @@ resolve_expr proc(
if (state.bindings[usize(id)].kind != .none) return if (state.bindings[usize(id)].kind != .none) return
tree :: state.tree tree :: state.tree
match ast.expr_kind(tree, id) { 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: {} .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: { .enum_type: {
if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing) if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing)
for (0..ast.enum_member_count(tree, id)) |i| { 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)) 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) .pointer_type: try resolve_expr(state, scope, ast.pointer_type(tree, id).element)
.function_type: { .function_type: {
try resolve_expr(state, scope, ast.callable_result(tree, id)) try resolve_expr(state, scope, ast.callable_result(tree, id))
+335 -1
View File
@@ -11,6 +11,279 @@ HONEY = ROOT / "build/honey"
# Explicit expectations guard against frontend bugs shared by both execution paths. # Explicit expectations guard against frontend bugs shared by both execution paths.
CASES = { CASES = {
"slice_mutation_alias_calls": (
"""
update proc(values []mut i16) []mut i16 {
values[1] = values[0] + 2
return values[1..]
}
main proc() i16 {
values [4]mut i16 := [0, 40, 0, 0]
view :: update(values[1..3])
copy :: view
copy[0] = copy[0] + 1
return values[2] - i16(view.len)
}
""", b"42\n", None,
),
"slice_empty_one_past_and_pointer_identity": (
"""
main proc() bool {
values [2]mut i16 := [40, 2]
all :: values[..]
empty :: all[2..2]
return empty.len == 0 and empty.ptr == all.ptr + 2
}
""", b"true\n", None,
),
"slice_nested_record_storage": (
"""
Row :: struct { values []mut i16 }
identity proc(row Row) Row { return row }
main proc() i16 {
values [2]mut i16 := [40, 2]
rows [1]Row :: [{ values = values[..] }]
selected :: identity(rows[0])
selected.values[0] = selected.values[0] + selected.values[1]
return values[0]
}
""", b"42\n", None,
),
"slice_empty_mutable_literal": (
"main proc() usize { values []mut i16 :: &[]\nreturn values.len }",
b"0\n", None,
),
"slice_string_sentinel": (
"""
read proc(text [;0]u8) u8 {
suffix :: text[1..;0]
return suffix.ptr[suffix.len] + suffix[0]
}
main proc() u8 { return read("!*") }
""", b"42\n", None,
),
"slice_checked_sentinel": (
"""
main proc() u8 {
values [3]mut u8 := [40, 2, 0]
pointer *mut u8 :: &values[0]
text [;0]mut u8 :: pointer[..2;0]
text[0] = text[0] + text[1]
plain []u8 :: text
return plain[0]
}
""", b"42\n", None,
),
"slice_reversed_bounds": (
"main proc() usize { a :: [1, 2]\nreturn a[2..1].len }",
b"", b"slice",
),
"slice_excessive_bounds": (
"main proc() usize { a :: [1, 2]\nreturn a[..3].len }",
b"", b"slice",
),
"slice_index_at_length": (
"main proc() i64 { a :: [40, 2]\ns :: a[..]\nreturn s[s.len] }",
b"", b"slice",
),
"slice_sentinel_mismatch": (
"main proc() usize { a :: [1, 2, 3]\nreturn a[..2;0].len }",
b"", b"sentinel",
),
"slice_readonly_write_rejected": (
"main proc() int { a :: [40, 2]\ns :: a[..]\ns[0] = 42\nreturn 42 }",
b"", b"writable",
),
"optional_values_storage_calls": (
"""
Box :: struct { value ?i16 }
choose proc(value ?i16) ?i16 { return value }
main proc() i16 {
box Box := { value = 40 }
options [2]mut ?i16 := [null, 2]
options[0] = choose(box.value)
return options[0]? + options[1]?
}
""", b"42\n", None,
),
"optional_capture_and_null_transition": (
"""
main proc() i16 {
value ?i16 := 40
total i16 := 0
while value |payload| {
total = total + payload
value = null
continue
}
if value |unused| return unused
if (value != null) return 0
value = 2
if value |payload| return total + payload
return 0
}
""", b"42\n", None,
),
"optional_pointer_identity_and_mutation": (
"""
main proc() i16 {
value i16 := 40
pointer ?@mut i16 := &value
if pointer |present| present^ = present^ + 2
readonly ?@i16 :: pointer
if (readonly == null) return 0
return readonly?^
}
""", b"42\n", None,
),
"optional_null_unwrap": (
"main proc() i16 { value ?i16 :: null\nreturn value? }",
b"", b"null",
),
"optional_untaken_unwrap": (
"main proc() i16 { value ?i16 :: null\nif (false) return value?\nreturn 42 }",
b"42\n", None,
),
"memory_layout_padded_aggregates": (
"""
Padded :: struct { tag u8, value u64 }
Empty :: struct {}
main proc() bool {
return (
sizeof!(Padded) == 16 and alignof!(Padded) == 8
and sizeof!([3]Padded) == 48 and sizeof!([]u8) == 16
and sizeof!(?@i16) == 8 and sizeof!(Empty) == 0
)
}
""", b"true\n", None,
),
"memory_pointer_cast_byte_alias": (
"""
main proc() u16 {
value u16 := 40
bytes *mut u8 :: ptrcast!(&value)
bytes[0] = bytes[0] + 2
whole @mut u16 :: ptrcast!(bytes)
return whole^
}
""", b"42\n", None,
),
"memory_constcast_slice": (
"""
main proc() i16 {
values [2]mut i16 := [40, 2]
readonly []i16 :: values[..]
writable []mut i16 :: constcast!(readonly)
writable[0] = writable[0] + writable[1]
return values[0]
}
""", b"42\n", None,
),
"memory_typed_copy_and_set": (
"""
Pair :: struct { number i16, ready bool }
main proc() i16 {
source [2]Pair :: [{ number = 40, ready = true }, { number = 2, ready = false }]
target [2]mut Pair := [{ number = 0, ready = false }, { number = 0, ready = false }]
memcopy!(target[..], source[..])
saved :: target[0]
memset!(target[..], { number = 21, ready = true })
if (!saved.ready or !target[1].ready) return 0
return target[0].number + target[1].number
}
""", b"42\n", None,
),
"memory_copy_overlap": (
"main proc() int { a [3]mut i16 := [1, 2, 3]\nmemcopy!(a[1..], a[..2])\nreturn 42 }",
b"", b"overlap",
),
"memory_copy_length_mismatch": (
"main proc() int { a [3]mut i16 := [1, 2, 3]\nmemcopy!(a[..1], a[1..])\nreturn 42 }",
b"", b"slice",
),
"memory_empty_copy_and_set": (
"main proc() int { a [0]mut i16 := []\nmemcopy!(a[..], a[..])\nmemset!(a[..], 42)\nreturn 42 }",
b"42\n", None,
),
"optional_nested_presence": (
"main proc() i16 { value ??i16 :: 42\nreturn value?? }", b"42\n", None,
),
"optional_callback_capture_from_call": (
"""
increment proc(value i16) i16 { return value + 2 }
factory proc() ?@proc(value i16) i16 { return increment }
main proc() i16 {
if factory() |callback| return callback(40)
return 0
}
""", b"42\n", None,
),
"slice_recursive_record": (
"""
Node :: struct { value i16, children []Node }
main proc() i16 {
children [0]Node :: []
node Node :: { value = 42, children = children[..] }
return node.value + i16(node.children.len)
}
""", b"42\n", None,
),
"slice_full_width_sentinel": (
"""
main proc() usize {
values [2]u64 :: [42, 18446744073709551615]
sentinel [;18446744073709551615]u64 :: values[..1;18446744073709551615]
return sentinel.len
}
""", b"1\n", None,
),
"memory_pointer_cast_preserves_readonly": (
"main proc() int { value i16 :: 40\npointer *mut u8 :: ptrcast!(&value)\nreturn 42 }",
b"", b"operator requires",
),
"slice_sentinel_strengthening_rejected": (
"main proc() int { a :: [1, 0]\ns []i64 :: a[..1]\nt [;0]i64 :: s\nreturn 42 }",
b"", b"incompatible",
),
"slice_fixed_signed_index_rejected": (
"main proc() i64 { a :: [42]\ns :: a[..]\ni i16 :: 0\nreturn s[i] }",
b"", b"index",
),
"optional_nonoptional_capture_rejected": (
"main proc() int { if true |payload| return 1\nreturn 42 }",
b"", b"condition",
),
"memory_foreign_main_rejected": (
"main c_func() i16", b"", b"main must",
),
"memory_opaque_dereference_rejected": (
"main proc() int { value i16 := 42\npointer *mut anyopaque :: ptrcast!(&value)\ndiscard :: pointer^\nreturn 42 }",
b"", b"dereference",
),
"memory_opaque_index_rejected": (
"main proc() int { value i16 := 42\npointer *mut anyopaque :: ptrcast!(&value)\ndiscard :: pointer[0]\nreturn 42 }",
b"", b"indexing",
),
"memory_opaque_slice_rejected": (
"main proc() int { value i16 := 42\npointer *mut anyopaque :: ptrcast!(&value)\ndiscard :: pointer[..1]\nreturn 42 }",
b"", b"operator requires",
),
"memory_opaque_arithmetic_rejected": (
"main proc() int { value i16 := 42\npointer *mut anyopaque :: ptrcast!(&value)\ndiscard :: pointer + 1\nreturn 42 }",
b"", b"operator requires",
),
"slice_contextual_borrow_preserves_readonly": (
"main proc() i16 { values [1]i16 :: [40]\nview []mut i16 :: &values\nview[0] = 42\nreturn values[0] }",
b"", b"incompatible",
),
"slice_contextual_scalar_borrow_rejected": (
"main proc() i16 { value i16 := 42\nview []mut i16 :: &value\nreturn view[0] }",
b"", b"incompatible",
),
"slice_implicit_borrow_preserves_binding_mutability": (
"main proc() i16 { values [1]mut i16 :: [40]\nview []mut i16 :: values\nview[0] = 42\nreturn values[0] }",
b"", b"incompatible",
),
"callback_concrete_vtable": ( "callback_concrete_vtable": (
""" """
VTable :: struct { run @proc(context @mut State, amount i16) i16 } VTable :: struct { run @proc(context @mut State, amount i16) i16 }
@@ -1657,6 +1930,66 @@ def check_dependency_provenance(temporary, executable):
executable.unlink() executable.unlink()
return len(cases) * 2 return len(cases) * 2
def check_allocator_libc(temporary, executable):
source = temporary / "allocator_libc.hon"
source.write_text(textwrap.dedent("""
malloc c_func(size c_ulong) ?*mut anyopaque
realloc c_func(memory ?*mut anyopaque, size c_ulong) ?*mut anyopaque
free c_func(memory ?*mut anyopaque) void
posix_memalign c_func(output *mut ?*mut anyopaque, alignment c_ulong, size c_ulong) c_int
VTable :: struct {
alloc @proc(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8
release @proc(context ?@mut anyopaque, memory ?*mut u8) void
}
Allocator :: struct { context ?@mut anyopaque, vtable @VTable }
allocate proc(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
if (alignment <= 16) return ptrcast!(malloc(c_ulong(size)))
output [1]mut ?*mut anyopaque := [null]
status :: posix_memalign((&output).ptr, c_ulong(alignment), c_ulong(size))
if (status != 0) return null
return ptrcast!(output[0])
}
release proc(_ ?@mut anyopaque, memory ?*mut u8) void { free(memory) }
table VTable :: { alloc = allocate, release = release }
main proc() i16 {
allocator Allocator :: { context = null, vtable = &table }
memory :: allocator.vtable.alloc(allocator.context, 4, 8)
if memory |bytes| {
values *mut i16 :: ptrcast!(bytes)
values[0] = 40
values[1] = 2
resized ?*mut i16 :: ptrcast!(realloc(bytes, 8))
if resized |items| {
aligned :: allocator.vtable.alloc(null, 8, 64)
if aligned |other| {
target *mut i16 :: ptrcast!(other)
memcopy!(target[..2], items[..2])
answer :: target[0] + target[1]
allocator.vtable.release(null, other)
free(items)
return answer
}
free(items)
return -1
}
free(bytes)
return -2
}
return -3
}
""").strip() + "\n")
interpreted = run(str(HONEY), "run", str(source))
assert interpreted.returncode == 1 and interpreted.stdout == b"", outcome(interpreted)
assert b"foreign function calls are unsupported by the interpreter" in interpreted.stderr, outcome(interpreted)
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
for optimization in ("-O0", "-O2"):
built = run(str(HONEY), "build", str(source), "-o", str(executable), optimization)
assert outcome(built) == (0, b"", b""), outcome(built)
assert outcome(run(str(executable))) == (0, b"42\n", b""), optimization
executable.unlink()
return 1
def main(): def main():
with tempfile.TemporaryDirectory(prefix="honey native ' ; $") as directory: with tempfile.TemporaryDirectory(prefix="honey native ' ; $") as directory:
@@ -1671,6 +2004,7 @@ def main():
source.write_text(textwrap.dedent(text).strip() + "\n") source.write_text(textwrap.dedent(text).strip() + "\n")
check(source, executable, (stdout, error)) check(source, executable, (stdout, error))
count += 1 count += 1
allocator_count = check_allocator_libc(temporary, executable)
count += check_packages(temporary, executable) count += check_packages(temporary, executable)
count += check_namespace_packages(temporary, executable) count += check_namespace_packages(temporary, executable)
count += check_record_packages(temporary, executable) count += check_record_packages(temporary, executable)
@@ -1694,7 +2028,7 @@ def main():
assert not executable.exists() assert not executable.exists()
invalid = run(str(HONEY), "build", str(source), "-o", str(executable), "-O3") invalid = run(str(HONEY), "build", str(source), "-o", str(executable), "-O3")
assert invalid.returncode == 2 and b"usage:" in invalid.stderr, outcome(invalid) assert invalid.returncode == 2 and b"usage:" in invalid.stderr, outcome(invalid)
print(f"{count} programs matched interpreter at -O0 and -O2; LLVM output and CLI errors passed") print(f"{count} programs matched interpreter at -O0 and -O2; {allocator_count} native allocator program, LLVM output and CLI errors passed")
if __name__ == "__main__": if __name__ == "__main__":