c interop type foundation
This commit is contained in:
+14
-15
@@ -12,17 +12,24 @@
|
||||
|
||||
### types and expressions
|
||||
|
||||
- `i8`, `i16`, `i32`, `i64`, `void`, and inferred integer-constrained `int`
|
||||
- exact-width `i8` through `i64`, `u8` through `u64`, `f32`, `f64`, `isize`, `usize`, `void`, and inferred integer-constrained `int`
|
||||
- target-dependent atomic C primitives from `c_char` through `c_longdouble`
|
||||
- C primitives remain semantically distinct from exact-width Brolang primitives until target lowering
|
||||
- contextual integer literals and constant folding of addition and negation trees
|
||||
- checked signed integer addition and unary negation
|
||||
- function calls, assignments, and returns
|
||||
- strict numeric conversions, checked integer addition, and unary negation
|
||||
- arrays `[N]T`, sentinel arrays `[N;S]T`, indexing, pointers, pointer offsets, slices, and explicit slicing
|
||||
- immutable UTF-8 sentinel-slice strings and Unicode code-point character literals
|
||||
- optionals with trapping postfix `?`, `orelse`, and nullable pointer representation
|
||||
- source-order native structs, keyed literals, and defined or opaque pointer-only `c_struct`
|
||||
- postfix pointer dereference, explicit `.ptr`/`.len`, general writable locations, function calls, assignments, and returns
|
||||
|
||||
### functions and packages
|
||||
|
||||
- demand-monomorphized functions
|
||||
- bodyful `c func` definitions using the c calling convention
|
||||
- bodyless `c func` declarations with exact, globally unique external symbol names
|
||||
- bodyful `c_func` definitions using the c calling convention
|
||||
- bodyless `c_func` declarations with exact, globally unique external symbol names
|
||||
- concrete-only foreign signatures
|
||||
- Apple Silicon C ABI scalar and pointer lowering, including narrow integer extension attributes
|
||||
- directory packages with merged declarations
|
||||
- file-local relative imports, aliases, and qualified member access
|
||||
|
||||
@@ -43,20 +50,12 @@
|
||||
|
||||
### scalar and compound types
|
||||
|
||||
- unsigned integers, floats, characters, and target-dependent c scalar types
|
||||
- arrays `[N]T` and sentinel arrays `[N; S]T`
|
||||
- single-item pointers `@T` and many-item pointers `*T`
|
||||
- element and pointee mutability through `mut`, separate from binding mutability
|
||||
- slices `[]T` and sentinel slices `[; S]T`
|
||||
- string literals as immutable sentinel slices backed by static arrays
|
||||
- character literals
|
||||
- optionals with trapping `?` unwrap, `orelse` fallback, and nullable pointers
|
||||
- data-only native structs and target-layout `c struct` types
|
||||
- tuples and native variadic functions
|
||||
- C unions, C enums, and by-value C record ABI lowering
|
||||
|
||||
### c imports
|
||||
|
||||
- c headers imported as synthetic package namespaces
|
||||
- typedefs, enums, opaque records, and external variables
|
||||
- function pointers, callbacks, macros, and static inline functions
|
||||
- target-specific by-value c record and union ABI lowering
|
||||
- target-specific by-value C record and union ABI lowering
|
||||
|
||||
@@ -9,11 +9,12 @@ odin build . -out:build/brolang
|
||||
./build/prototype
|
||||
```
|
||||
|
||||
Bodyless `c func` declarations bind exact external symbols and require concrete
|
||||
types:
|
||||
Bodyless `c_func` declarations bind exact external symbols and require concrete
|
||||
types. C primitives use atomic target-dependent names and remain semantically
|
||||
distinct from exact-width Brolang primitives:
|
||||
|
||||
```bro
|
||||
foreign_add :: c func(a, b i32) i32
|
||||
strlen :: c_func(value *c_char) c_ulong
|
||||
```
|
||||
|
||||
Additional native inputs and libraries are passed to the final `zig cc`
|
||||
@@ -56,7 +57,10 @@ Current prototype features:
|
||||
- Newline-terminated, multiline statements; `}` may terminate a block's final statement
|
||||
- `#` comments
|
||||
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
|
||||
- `i8`, `i16`, `i32`, `i64`, and loose integer-constrained `int`
|
||||
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int`
|
||||
- Target-dependent atomic `c_*` primitive types, `c_func`, and pointer-only `c_struct`
|
||||
- Arrays, sentinel arrays, pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
|
||||
- Explicit `.ptr`/`.len`, slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
|
||||
- Contextual integer constants and compile-time folding of addition and unary negation trees
|
||||
- Directory packages with merged declarations and file-local relative imports
|
||||
- Qualified imported globals and functions with package-aware symbol mangling
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
# milestones
|
||||
|
||||
1. interop type foundation
|
||||
1. interop type foundation (implemented)
|
||||
- unsigned integers, floats, and target-dependent c scalar types
|
||||
- atomic `c_*` primitive types remain distinct until target-aware lowering
|
||||
- `c_func` and pointer-only `c_struct`; `c` remains an ordinary identifier
|
||||
- keep binding mutability (`::` / `=`) separate from element or pointee mutability (`mut`)
|
||||
- arrays and indexing
|
||||
- `[N]T`: array with `N` logical elements
|
||||
@@ -13,7 +15,7 @@
|
||||
- pointers
|
||||
- `@T` / `@mut T`: non-null single-item pointer without arithmetic
|
||||
- `*T` / `*mut T`: non-null many-item pointer with arithmetic
|
||||
- optional pointers represent nullable pointers
|
||||
- optional pointers represent nullable pointers (i.e. `?@T` / `?@mut T`, `?*T` / `?*mut T`)
|
||||
- slices and slicing
|
||||
- `[]T`: pointer and length
|
||||
- `[;S]T`: pointer and length with a sentinel invariant
|
||||
@@ -22,9 +24,9 @@
|
||||
- character literals
|
||||
- optionals with trapping unwrap and fallback operations
|
||||
- native structs with compiler-controlled layout
|
||||
- pointer-only `c struct` support with target c layout
|
||||
- `Some :: c struct { ... }`: defined c-layout struct
|
||||
- `Some :: c struct`: opaque c-layout struct
|
||||
- pointer-only `c_struct` support with target c layout
|
||||
- `Some :: c_struct { ... }`: defined c-layout struct
|
||||
- `Some :: c_struct`: opaque c-layout struct
|
||||
- defer passing c structs by value until target ABI classification exists
|
||||
|
||||
2. restricted c header imports
|
||||
|
||||
+25
-9
@@ -2,6 +2,7 @@ package ast
|
||||
|
||||
import "../source"
|
||||
import "../symbol"
|
||||
import "../types"
|
||||
import "core:mem"
|
||||
|
||||
Expr_Id :: distinct u32
|
||||
@@ -60,20 +61,25 @@ index :: proc(id: $T, invalid: T, count: int) -> (int, bool) {
|
||||
return value, id != invalid && value < count
|
||||
}
|
||||
|
||||
Type_Syntax :: enum u8 {
|
||||
Invalid,
|
||||
Int,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
Void,
|
||||
}
|
||||
Type_Syntax :: types.Type
|
||||
|
||||
Expr_Kind :: enum u8 {
|
||||
Invalid,
|
||||
Integer,
|
||||
Float,
|
||||
String,
|
||||
Array,
|
||||
None,
|
||||
Name,
|
||||
Address,
|
||||
Deref,
|
||||
Index,
|
||||
Slice,
|
||||
Field,
|
||||
Unwrap,
|
||||
Orelse,
|
||||
Struct_Literal,
|
||||
Keyed,
|
||||
Negate,
|
||||
Add,
|
||||
Call,
|
||||
@@ -111,6 +117,7 @@ Stmt :: struct {
|
||||
name: symbol.Id,
|
||||
type: Type_Syntax,
|
||||
immutable: bool,
|
||||
target: Expr_Id,
|
||||
expr: Expr_Id,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
@@ -170,12 +177,15 @@ Module :: struct {
|
||||
imports: [dynamic]Import,
|
||||
files: [dynamic]File,
|
||||
packages: [dynamic]Package,
|
||||
strings: [dynamic]string,
|
||||
type_store: types.Store,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
init_module :: proc(allocator := context.allocator) -> Module {
|
||||
module: Module
|
||||
module.allocator = allocator
|
||||
module.type_store = types.init_store(allocator)
|
||||
module.exprs.allocator = allocator
|
||||
module.statements.allocator = allocator
|
||||
module.functions.allocator = allocator
|
||||
@@ -183,6 +193,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
||||
module.imports.allocator = allocator
|
||||
module.files.allocator = allocator
|
||||
module.packages.allocator = allocator
|
||||
module.strings.allocator = allocator
|
||||
return module
|
||||
}
|
||||
|
||||
@@ -200,6 +211,9 @@ destroy_module :: proc(module: ^Module) {
|
||||
for pkg in module.packages {
|
||||
delete(pkg.path, module.allocator)
|
||||
}
|
||||
for value in module.strings {
|
||||
delete(value, module.allocator)
|
||||
}
|
||||
delete(module.exprs)
|
||||
delete(module.statements)
|
||||
delete(module.functions)
|
||||
@@ -207,4 +221,6 @@ destroy_module :: proc(module: ^Module) {
|
||||
delete(module.imports)
|
||||
delete(module.files)
|
||||
delete(module.packages)
|
||||
delete(module.strings)
|
||||
types.destroy_store(&module.type_store)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package backend
|
||||
|
||||
import "../linker"
|
||||
import "../target"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
@@ -14,6 +15,7 @@ append_owned :: proc(command: ^[dynamic]string, value: string, allocator: mem.Al
|
||||
build_command :: proc(
|
||||
llvm_path, output_path: string,
|
||||
link_arguments: []linker.Argument,
|
||||
selected := target.DEFAULT,
|
||||
allocator := context.allocator,
|
||||
) -> []string {
|
||||
command: [dynamic]string
|
||||
@@ -21,6 +23,8 @@ build_command :: proc(
|
||||
append_owned(&command, "/usr/bin/env", allocator)
|
||||
append_owned(&command, "zig", allocator)
|
||||
append_owned(&command, "cc", allocator)
|
||||
append_owned(&command, "-target", allocator)
|
||||
append_owned(&command, target.name(selected), allocator)
|
||||
append_owned(&command, "-Wno-override-module", allocator)
|
||||
append_owned(&command, llvm_path, allocator)
|
||||
for argument in link_arguments {
|
||||
@@ -45,12 +49,16 @@ destroy_command :: proc(command: []string, allocator := context.allocator) {
|
||||
delete(command, allocator)
|
||||
}
|
||||
|
||||
compile :: proc(llvm_path, output_path: string, link_arguments: []linker.Argument = nil) -> bool {
|
||||
compile :: proc(
|
||||
llvm_path, output_path: string,
|
||||
link_arguments: []linker.Argument = nil,
|
||||
selected := target.DEFAULT,
|
||||
) -> bool {
|
||||
pid := os2.get_pid()
|
||||
temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid)
|
||||
defer _ = os.remove(temporary_output)
|
||||
|
||||
command := build_command(llvm_path, temporary_output, link_arguments)
|
||||
command := build_command(llvm_path, temporary_output, link_arguments, selected)
|
||||
defer destroy_command(command)
|
||||
state, stdout, stderr, err := os2.process_exec(
|
||||
os2.Process_Desc{command=command},
|
||||
|
||||
+765
-78
File diff suppressed because it is too large
Load Diff
@@ -9,12 +9,17 @@ import "./lower"
|
||||
import "./opt"
|
||||
import "./source"
|
||||
import "./symbol"
|
||||
import "./target"
|
||||
import "core:fmt"
|
||||
import vmem "core:mem/virtual"
|
||||
import "core:os"
|
||||
import "core:os/os2"
|
||||
|
||||
compile_package :: proc(input_path, output_path: string, link_arguments: []linker.Argument = nil) -> int {
|
||||
compile_package :: proc(
|
||||
input_path, output_path: string,
|
||||
link_arguments: []linker.Argument = nil,
|
||||
selected := target.DEFAULT,
|
||||
) -> int {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
@@ -61,7 +66,7 @@ compile_package :: proc(input_path, output_path: string, link_arguments: []linke
|
||||
return 2
|
||||
}
|
||||
vmem.arena_free_all(&lexer_arena)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols, vmem.arena_allocator(&checker_arena))
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols, selected, vmem.arena_allocator(&checker_arena))
|
||||
vmem.arena_free_all(&parser_arena)
|
||||
ir_module := lower.lower(&hir_module, vmem.arena_allocator(&lower_arena))
|
||||
vmem.arena_free_all(&checker_arena)
|
||||
@@ -78,7 +83,7 @@ compile_package :: proc(input_path, output_path: string, link_arguments: []linke
|
||||
}
|
||||
|
||||
source.print_all(&diagnostics)
|
||||
if !backend.compile(llvm_path, output_path, link_arguments) {
|
||||
if !backend.compile(llvm_path, output_path, link_arguments, selected) {
|
||||
return 2
|
||||
}
|
||||
if len(diagnostics.items) > 0 {
|
||||
|
||||
+31
-1
@@ -2,6 +2,7 @@ package hir
|
||||
|
||||
import "../source"
|
||||
import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "core:mem"
|
||||
|
||||
@@ -74,11 +75,27 @@ Linkage :: enum u8 {
|
||||
Expr_Kind :: enum u8 {
|
||||
Invalid,
|
||||
Integer,
|
||||
Float,
|
||||
String,
|
||||
Array,
|
||||
Struct,
|
||||
None,
|
||||
Optional_Some,
|
||||
Local,
|
||||
Global,
|
||||
Address,
|
||||
Deref,
|
||||
Index,
|
||||
Slice,
|
||||
Field,
|
||||
Length,
|
||||
Slice_Ptr,
|
||||
Unwrap,
|
||||
Orelse,
|
||||
Widen,
|
||||
Negate,
|
||||
Add,
|
||||
Pointer_Add,
|
||||
Call,
|
||||
}
|
||||
|
||||
@@ -114,6 +131,7 @@ Stmt :: struct {
|
||||
kind: Stmt_Kind,
|
||||
span: source.Span,
|
||||
local: Local_Id,
|
||||
target: Expr_Id,
|
||||
expr: Expr_Id,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
@@ -153,16 +171,23 @@ Module :: struct {
|
||||
statements: [dynamic]Stmt,
|
||||
functions: [dynamic]Function,
|
||||
globals: [dynamic]Global,
|
||||
strings: [dynamic]string,
|
||||
types: types.Store,
|
||||
target: target.Target,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
init_module :: proc(allocator := context.allocator) -> Module {
|
||||
init_module :: proc(selected := target.DEFAULT, allocator := context.allocator) -> Module {
|
||||
module: Module
|
||||
module.target = selected
|
||||
module.types = types.init_store(allocator)
|
||||
module.types.selected = selected
|
||||
module.allocator = allocator
|
||||
module.exprs.allocator = allocator
|
||||
module.statements.allocator = allocator
|
||||
module.functions.allocator = allocator
|
||||
module.globals.allocator = allocator
|
||||
module.strings.allocator = allocator
|
||||
return module
|
||||
}
|
||||
|
||||
@@ -182,8 +207,13 @@ destroy_module :: proc(module: ^Module) {
|
||||
delete(global.dependencies)
|
||||
delete(global.calls, module.allocator)
|
||||
}
|
||||
for value in module.strings {
|
||||
delete(value, module.allocator)
|
||||
}
|
||||
delete(module.exprs)
|
||||
delete(module.statements)
|
||||
delete(module.functions)
|
||||
delete(module.globals)
|
||||
delete(module.strings)
|
||||
types.destroy_store(&module.types)
|
||||
}
|
||||
|
||||
+29
-1
@@ -2,6 +2,7 @@ package ir
|
||||
|
||||
import "../source"
|
||||
import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "core:mem"
|
||||
|
||||
@@ -67,13 +68,28 @@ Linkage :: enum u8 {
|
||||
Opcode :: enum u8 {
|
||||
Param,
|
||||
Const,
|
||||
String,
|
||||
Aggregate,
|
||||
None,
|
||||
Optional_Some,
|
||||
Load_Global,
|
||||
Address_Global,
|
||||
Address_Of,
|
||||
Alloca,
|
||||
Index_Address,
|
||||
Field_Address,
|
||||
Load,
|
||||
Store,
|
||||
Slice,
|
||||
Length,
|
||||
Slice_Ptr,
|
||||
Unwrap,
|
||||
Orelse_Begin,
|
||||
Orelse,
|
||||
Widen,
|
||||
Neg_Checked,
|
||||
Add_Checked,
|
||||
Pointer_Add,
|
||||
Call,
|
||||
Trap,
|
||||
Return,
|
||||
@@ -117,13 +133,20 @@ Global :: struct {
|
||||
Module :: struct {
|
||||
functions: [dynamic]Function,
|
||||
globals: [dynamic]Global,
|
||||
strings: [dynamic]string,
|
||||
types: types.Store,
|
||||
target: target.Target,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
init_module :: proc(allocator := context.allocator) -> Module {
|
||||
init_module :: proc(selected := target.DEFAULT, allocator := context.allocator) -> Module {
|
||||
module: Module
|
||||
module.target = selected
|
||||
module.types = types.init_store(allocator)
|
||||
module.types.selected = selected
|
||||
module.functions.allocator = allocator
|
||||
module.globals.allocator = allocator
|
||||
module.strings.allocator = allocator
|
||||
module.allocator = allocator
|
||||
return module
|
||||
}
|
||||
@@ -144,6 +167,11 @@ destroy_module :: proc(module: ^Module) {
|
||||
for global in module.globals {
|
||||
destroy_instructions(global.initializer, module.allocator)
|
||||
}
|
||||
for value in module.strings {
|
||||
delete(value, module.allocator)
|
||||
}
|
||||
delete(module.functions)
|
||||
delete(module.globals)
|
||||
delete(module.strings)
|
||||
types.destroy_store(&module.types)
|
||||
}
|
||||
|
||||
@@ -15,14 +15,42 @@ is_identifier_continue :: proc(value: byte) -> bool {
|
||||
keyword_kind :: proc(text: string) -> token.Kind {
|
||||
switch text {
|
||||
case "func": return .Keyword_Func
|
||||
case "c_func": return .Keyword_C_Func
|
||||
case "struct": return .Keyword_Struct
|
||||
case "c_struct": return .Keyword_C_Struct
|
||||
case "import": return .Keyword_Import
|
||||
case "return": return .Keyword_Return
|
||||
case "mut": return .Keyword_Mut
|
||||
case "none": return .Keyword_None
|
||||
case "orelse": return .Keyword_Orelse
|
||||
case "void": return .Keyword_Void
|
||||
case "int": return .Keyword_Int
|
||||
case "i8": return .Keyword_I8
|
||||
case "i16": return .Keyword_I16
|
||||
case "i32": return .Keyword_I32
|
||||
case "i64": return .Keyword_I64
|
||||
case "u8": return .Keyword_U8
|
||||
case "u16": return .Keyword_U16
|
||||
case "u32": return .Keyword_U32
|
||||
case "u64": return .Keyword_U64
|
||||
case "isize": return .Keyword_Isize
|
||||
case "usize": return .Keyword_Usize
|
||||
case "f32": return .Keyword_F32
|
||||
case "f64": return .Keyword_F64
|
||||
case "c_char": return .Keyword_C_Char
|
||||
case "c_schar": return .Keyword_C_Schar
|
||||
case "c_uchar": return .Keyword_C_Uchar
|
||||
case "c_short": return .Keyword_C_Short
|
||||
case "c_ushort": return .Keyword_C_Ushort
|
||||
case "c_int": return .Keyword_C_Int
|
||||
case "c_uint": return .Keyword_C_Uint
|
||||
case "c_long": return .Keyword_C_Long
|
||||
case "c_ulong": return .Keyword_C_Ulong
|
||||
case "c_longlong": return .Keyword_C_Longlong
|
||||
case "c_ulonglong": return .Keyword_C_Ulonglong
|
||||
case "c_float": return .Keyword_C_Float
|
||||
case "c_double": return .Keyword_C_Double
|
||||
case "c_longdouble": return .Keyword_C_Longdouble
|
||||
case "_": return .Underscore
|
||||
}
|
||||
return .Identifier
|
||||
@@ -87,7 +115,34 @@ lex :: proc(
|
||||
append_token(&stream, source_file, .Minus, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '.':
|
||||
append_token(&stream, source_file, .Dot, cursor, cursor+1)
|
||||
start := cursor
|
||||
cursor += 1
|
||||
if cursor < len(bytes) && bytes[cursor] == '.' {
|
||||
cursor += 1
|
||||
append_token(&stream, source_file, .Range, start, cursor)
|
||||
} else {
|
||||
append_token(&stream, source_file, .Dot, start, cursor)
|
||||
}
|
||||
case '@':
|
||||
append_token(&stream, source_file, .At, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '*':
|
||||
append_token(&stream, source_file, .Star, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '&':
|
||||
append_token(&stream, source_file, .Ampersand, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '^':
|
||||
append_token(&stream, source_file, .Caret, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '?':
|
||||
append_token(&stream, source_file, .Question, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '[':
|
||||
append_token(&stream, source_file, .Left_Bracket, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case ']':
|
||||
append_token(&stream, source_file, .Right_Bracket, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '(':
|
||||
append_token(&stream, source_file, .Left_Paren, cursor, cursor+1)
|
||||
@@ -111,11 +166,13 @@ lex :: proc(
|
||||
for cursor < len(bytes) && bytes[cursor] != '"' && bytes[cursor] != '\n' {
|
||||
if bytes[cursor] == '\\' {
|
||||
cursor += 1
|
||||
if cursor >= len(bytes) || (bytes[cursor] != '\\' && bytes[cursor] != '"') {
|
||||
if cursor >= len(bytes) ||
|
||||
(bytes[cursor] != '\\' && bytes[cursor] != '"' && bytes[cursor] != 'n' &&
|
||||
bytes[cursor] != 'r' && bytes[cursor] != 't' && bytes[cursor] != '0') {
|
||||
source.add(
|
||||
diagnostics,
|
||||
source.Span{file=source_file.id, start=source.Offset(max(cursor-1, start)), end=source.Offset(min(cursor+1, len(bytes)))},
|
||||
"import strings only support '\\\\' and '\\\"' escapes",
|
||||
"strings only support '\\\\', '\\\"', '\\n', '\\r', '\\t', and '\\0' escapes",
|
||||
)
|
||||
valid = false
|
||||
}
|
||||
@@ -136,20 +193,44 @@ lex :: proc(
|
||||
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
|
||||
}
|
||||
case ';':
|
||||
append_token(&stream, source_file, .Semicolon, cursor, cursor+1)
|
||||
cursor += 1
|
||||
case '\'':
|
||||
start := cursor
|
||||
cursor += 1
|
||||
for cursor < len(bytes) && bytes[cursor] != '\'' && bytes[cursor] != '\n' {
|
||||
if bytes[cursor] == '\\' && cursor+1 < len(bytes) {
|
||||
cursor += 1
|
||||
}
|
||||
cursor += 1
|
||||
}
|
||||
if cursor < len(bytes) && bytes[cursor] == '\'' {
|
||||
cursor += 1
|
||||
append_token(&stream, source_file, .Character, start, cursor)
|
||||
} else {
|
||||
id := source.add(
|
||||
diagnostics,
|
||||
source.Span{file=source_file.id, start=source.Offset(cursor), end=source.Offset(cursor+1)},
|
||||
"semicolons are invalid; terminate statements with a newline",
|
||||
source.Span{file=source_file.id, start=source.Offset(start), end=source.Offset(cursor)},
|
||||
"unterminated character literal",
|
||||
)
|
||||
append_token(&stream, source_file, .Invalid, cursor, cursor+1, diagnostic=id)
|
||||
cursor += 1
|
||||
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
|
||||
}
|
||||
case:
|
||||
if value >= '0' && value <= '9' {
|
||||
start := cursor
|
||||
for cursor < len(bytes) && bytes[cursor] >= '0' && bytes[cursor] <= '9' {
|
||||
cursor += 1
|
||||
}
|
||||
append_token(&stream, source_file, .Integer, start, cursor)
|
||||
kind := token.Kind.Integer
|
||||
if cursor+1 < len(bytes) && bytes[cursor] == '.' &&
|
||||
bytes[cursor+1] != '.' && bytes[cursor+1] >= '0' && bytes[cursor+1] <= '9' {
|
||||
kind = .Float
|
||||
cursor += 1
|
||||
for cursor < len(bytes) && bytes[cursor] >= '0' && bytes[cursor] <= '9' {
|
||||
cursor += 1
|
||||
}
|
||||
}
|
||||
append_token(&stream, source_file, kind, start, cursor)
|
||||
} else if is_identifier_start(value) {
|
||||
start := cursor
|
||||
for cursor < len(bytes) && is_identifier_continue(bytes[cursor]) {
|
||||
|
||||
+632
-70
@@ -3,6 +3,7 @@ package llvm
|
||||
import "../ir"
|
||||
import "../source"
|
||||
import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
@@ -21,11 +22,33 @@ Emitter :: struct {
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
llvm_type :: proc(value: types.Type) -> string {
|
||||
if value.kind == .Void {
|
||||
llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
|
||||
if types.is_void(value) {
|
||||
return "void"
|
||||
}
|
||||
switch value.bits {
|
||||
#partial switch types.kind(value, store) {
|
||||
case .Pointer:
|
||||
return "ptr"
|
||||
case .Slice:
|
||||
return "{ ptr, i64 }"
|
||||
case .Array:
|
||||
item, _ := types.node(store, value)
|
||||
return fmt.tprintf("[%d x %s]", types.physical_count(value, store), llvm_type(item.child, store))
|
||||
case .Optional:
|
||||
item, _ := types.node(store, value)
|
||||
if types.is_pointer(item.child, store) {
|
||||
return "ptr"
|
||||
}
|
||||
return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store))
|
||||
case .Struct:
|
||||
return fmt.tprintf("%%bro.type.%d", value)
|
||||
}
|
||||
selected := store.selected if store != nil else target.DEFAULT
|
||||
repr := types.representation(value, selected)
|
||||
if types.is_float(repr) {
|
||||
return "float" if types.bits(repr) == 32 else "double"
|
||||
}
|
||||
switch types.bits(repr) {
|
||||
case 8: return "i8"
|
||||
case 16: return "i16"
|
||||
case 32: return "i32"
|
||||
@@ -33,15 +56,37 @@ llvm_type :: proc(value: types.Type) -> string {
|
||||
}
|
||||
}
|
||||
|
||||
function_result_type :: proc(function: ir.Function) -> string {
|
||||
function_result_type :: proc(function: ir.Function, store: ^types.Store) -> string {
|
||||
if function.is_main {
|
||||
return "i32"
|
||||
}
|
||||
return llvm_type(function.result)
|
||||
return llvm_type(function.result, store)
|
||||
}
|
||||
|
||||
sentinel :: proc(value_type: types.Type) -> i64 {
|
||||
switch value_type.bits {
|
||||
c_abi_extension :: proc(value: types.Type, selected: target.Target) -> string {
|
||||
if !types.is_concrete_integer(value) {
|
||||
return ""
|
||||
}
|
||||
switch target.c_integer_extension(selected, types.bits(value, selected), types.is_signed(value, selected)) {
|
||||
case .Sign: return "signext"
|
||||
case .Zero: return "zeroext"
|
||||
case .None: return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
emit_function_result :: proc(builder: ^strings.Builder, function: ir.Function, store: ^types.Store) {
|
||||
if function.calling_convention == .C {
|
||||
extension := c_abi_extension(function.result, store.selected)
|
||||
if len(extension) > 0 {
|
||||
fmt.sbprintf(builder, "%s ", extension)
|
||||
}
|
||||
}
|
||||
strings.write_string(builder, function_result_type(function, store))
|
||||
}
|
||||
|
||||
sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 {
|
||||
switch types.bits(value_type, selected) {
|
||||
case 8: return -86
|
||||
case 16: return -21846
|
||||
case 32: return -1431655766
|
||||
@@ -53,29 +98,88 @@ valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: ir.Ins
|
||||
return instruction_id != ir.INVALID_INSTRUCTION && int(instruction_id) < len(instructions)
|
||||
}
|
||||
|
||||
valid_value :: proc(instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type) -> bool {
|
||||
valid_value :: proc(
|
||||
instructions: []ir.Instruction,
|
||||
value_id: ir.Instruction_Id,
|
||||
expected: types.Type,
|
||||
store: ^types.Store,
|
||||
) -> bool {
|
||||
if !valid_instruction(instructions, value_id) ||
|
||||
!types.is_concrete_integer(expected) ||
|
||||
!types.is_runtime_value(expected, store) ||
|
||||
!types.equal(instructions[value_id].type, expected) {
|
||||
return false
|
||||
}
|
||||
switch instructions[value_id].op {
|
||||
case .Param, .Const, .Load_Global, .Load, .Widen, .Neg_Checked, .Add_Checked, .Call:
|
||||
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
||||
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
||||
.Widen, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
|
||||
return true
|
||||
case .Alloca, .Store, .Trap, .Return, .Return_Void:
|
||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||
.Store, .Trap, .Return, .Return_Void:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type) {
|
||||
if !valid_value(instructions, value_id, expected) {
|
||||
fmt.sbprintf(builder, "%d", sentinel(expected))
|
||||
valid_address :: proc(
|
||||
instructions: []ir.Instruction,
|
||||
value_id: ir.Instruction_Id,
|
||||
pointee: types.Type,
|
||||
store: ^types.Store,
|
||||
) -> bool {
|
||||
if !valid_instruction(instructions, value_id) {
|
||||
return false
|
||||
}
|
||||
value := instructions[value_id]
|
||||
#partial switch value.op {
|
||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address:
|
||||
return types.equal(value.type, pointee)
|
||||
case:
|
||||
return types.is_pointer(value.type, store) &&
|
||||
types.equal(types.child_type(value.type, store), pointee) &&
|
||||
valid_value(instructions, value_id, value.type, store)
|
||||
}
|
||||
}
|
||||
|
||||
write_constant :: proc(builder: ^strings.Builder, value: i64, value_type: types.Type, store: ^types.Store = nil) {
|
||||
if !types.is_concrete_scalar(value_type) {
|
||||
strings.write_string(builder, "zeroinitializer")
|
||||
return
|
||||
}
|
||||
selected := store.selected if store != nil else target.DEFAULT
|
||||
if types.is_float(value_type, selected) {
|
||||
text := ""
|
||||
if types.bits(value_type, selected) == 32 {
|
||||
bits := u32(value)
|
||||
number := transmute(f32)bits
|
||||
text = fmt.tprintf("%.9g", number)
|
||||
} else {
|
||||
number := transmute(f64)value
|
||||
text = fmt.tprintf("%.17g", number)
|
||||
}
|
||||
strings.write_string(builder, text)
|
||||
if !strings.contains(text, ".") && !strings.contains(text, "e") && !strings.contains(text, "E") {
|
||||
strings.write_string(builder, ".0")
|
||||
}
|
||||
return
|
||||
}
|
||||
fmt.sbprintf(builder, "%d", value)
|
||||
}
|
||||
|
||||
write_operand :: proc(
|
||||
builder: ^strings.Builder,
|
||||
instructions: []ir.Instruction,
|
||||
value_id: ir.Instruction_Id,
|
||||
expected: types.Type,
|
||||
store: ^types.Store,
|
||||
) {
|
||||
if !valid_value(instructions, value_id, expected, store) {
|
||||
write_constant(builder, sentinel(expected, store.selected), expected, store)
|
||||
return
|
||||
}
|
||||
value := instructions[value_id]
|
||||
if value.op == .Const {
|
||||
fmt.sbprintf(builder, "%d", value.integer)
|
||||
write_constant(builder, value.integer, expected, store)
|
||||
} else {
|
||||
fmt.sbprintf(builder, "%%v%d", value_id)
|
||||
}
|
||||
@@ -126,24 +230,59 @@ emit_trap_call :: proc(emitter: ^Emitter, message_id: int) {
|
||||
emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction: ir.Instruction, fallback: string) {
|
||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback)
|
||||
emit_trap_call(emitter, message)
|
||||
if types.is_concrete_integer(instruction.type) {
|
||||
if types.is_runtime_value(instruction.type, &emitter.module.types) {
|
||||
if !types.is_float(instruction.type, emitter.module.target) {
|
||||
if !types.is_concrete_scalar(instruction.type) {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = freeze %s zeroinitializer\n",
|
||||
instruction_id,
|
||||
llvm_type(instruction.type, &emitter.module.types),
|
||||
)
|
||||
return
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = add %s 0, %d\n",
|
||||
instruction_id,
|
||||
llvm_type(instruction.type),
|
||||
sentinel(instruction.type),
|
||||
llvm_type(instruction.type, &emitter.module.types),
|
||||
sentinel(instruction.type, emitter.module.target),
|
||||
)
|
||||
return
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = select i1 true, %s ",
|
||||
instruction_id,
|
||||
llvm_type(instruction.type, &emitter.module.types),
|
||||
)
|
||||
write_constant(&emitter.builder, sentinel(instruction.type, emitter.module.target), instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(instruction.type, &emitter.module.types))
|
||||
write_constant(&emitter.builder, sentinel(instruction.type, emitter.module.target), instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []ir.Instruction_Id, param_types: []types.Type) {
|
||||
emit_call_args :: proc(
|
||||
builder: ^strings.Builder,
|
||||
instructions: []ir.Instruction,
|
||||
args: []ir.Instruction_Id,
|
||||
param_types: []types.Type,
|
||||
store: ^types.Store,
|
||||
c_abi := false,
|
||||
) {
|
||||
for arg, index in args {
|
||||
if index > 0 {
|
||||
strings.write_string(builder, ", ")
|
||||
}
|
||||
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index]))
|
||||
write_operand(builder, instructions, arg, param_types[index])
|
||||
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index], store))
|
||||
if c_abi {
|
||||
extension := c_abi_extension(param_types[index], store.selected)
|
||||
if len(extension) > 0 {
|
||||
fmt.sbprintf(builder, "%s ", extension)
|
||||
}
|
||||
}
|
||||
write_operand(builder, instructions, arg, param_types[index], store)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +302,102 @@ emit_instruction_stream :: proc(
|
||||
}
|
||||
switch instruction.op {
|
||||
case .Param, .Const:
|
||||
case .String:
|
||||
string_id := int(instruction.integer)
|
||||
if string_id < 0 || string_id >= len(emitter.module.strings) ||
|
||||
!types.is_slice(instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid string literal")
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%string_ptr%d = insertvalue %s poison, ptr @bro.str.%d, 0\n",
|
||||
instruction_index, type_name, string_id,
|
||||
)
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = insertvalue %s %%string_ptr%d, i64 %d, 1\n",
|
||||
instruction_index, type_name, instruction_index, len(emitter.module.strings[string_id]),
|
||||
)
|
||||
case .Aggregate:
|
||||
item, ok := types.node(&emitter.module.types, instruction.type)
|
||||
expected_count := 0
|
||||
if ok && item.kind == .Array {
|
||||
expected_count = int(item.count)
|
||||
} else if ok && item.kind == .Struct {
|
||||
expected_count = int(item.field_count)
|
||||
} else {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type")
|
||||
continue
|
||||
}
|
||||
if len(instruction.args) != expected_count {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate operands")
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
total := len(instruction.args) + (1 if item.kind == .Array && item.has_sentinel else 0)
|
||||
if total == 0 {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = freeze %s zeroinitializer\n", instruction_index, type_name)
|
||||
continue
|
||||
}
|
||||
for arg_index := 0; arg_index < total; arg_index += 1 {
|
||||
element_type := item.child
|
||||
if item.kind == .Struct {
|
||||
element_type = types.fields_for(&emitter.module.types, instruction.type)[arg_index].type
|
||||
}
|
||||
final := arg_index == total-1
|
||||
if final {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s ", instruction_index, type_name)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%aggregate%d_%d = insertvalue %s ", instruction_index, arg_index, type_name)
|
||||
}
|
||||
if arg_index == 0 {
|
||||
strings.write_string(&emitter.builder, "poison")
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, "%%aggregate%d_%d", instruction_index, arg_index-1)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(element_type, &emitter.module.types))
|
||||
if arg_index < len(instruction.args) {
|
||||
write_operand(&emitter.builder, instructions, instruction.args[arg_index], element_type, &emitter.module.types)
|
||||
} else {
|
||||
write_constant(&emitter.builder, i64(item.sentinel), element_type, &emitter.module.types)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, ", %d\n", arg_index)
|
||||
}
|
||||
case .None:
|
||||
item, ok := types.node(&emitter.module.types, instruction.type)
|
||||
if !ok || item.kind != .Optional {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid optional none")
|
||||
continue
|
||||
}
|
||||
if types.is_pointer(item.child, &emitter.module.types) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr null, ptr null\n", instruction_index)
|
||||
} else {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = insertvalue %s zeroinitializer, i1 false, 0\n",
|
||||
instruction_index, llvm_type(instruction.type, &emitter.module.types),
|
||||
)
|
||||
}
|
||||
case .Optional_Some:
|
||||
item, ok := types.node(&emitter.module.types, instruction.type)
|
||||
if !ok || item.kind != .Optional ||
|
||||
!valid_value(instructions, instruction.a, item.child, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid optional value")
|
||||
continue
|
||||
}
|
||||
if types.is_pointer(item.child, &emitter.module.types) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr ", instruction_index)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, item.child, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ", ptr null\n")
|
||||
} else {
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, " %%optional%d = insertvalue %s poison, i1 true, 0\n", instruction_index, type_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%optional%d, %s ", instruction_index, type_name, instruction_index, llvm_type(item.child, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.a, item.child, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ", 1\n")
|
||||
}
|
||||
case .Load_Global:
|
||||
global_id := ir.as_global(instruction.target)
|
||||
if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) {
|
||||
@@ -179,7 +414,7 @@ emit_instruction_stream :: proc(
|
||||
&emitter.builder,
|
||||
" %%v%d = load %s, ptr @bro.g.%d\n",
|
||||
instruction_index,
|
||||
llvm_type(global.type),
|
||||
llvm_type(global.type, &emitter.module.types),
|
||||
global_id,
|
||||
)
|
||||
} else {
|
||||
@@ -187,20 +422,115 @@ emit_instruction_stream :: proc(
|
||||
&emitter.builder,
|
||||
" %%v%d = call %s @bro.get.%d()\n",
|
||||
instruction_index,
|
||||
llvm_type(global.type),
|
||||
llvm_type(global.type, &emitter.module.types),
|
||||
global_id,
|
||||
)
|
||||
}
|
||||
case .Address_Global:
|
||||
global_id := ir.as_global(instruction.target)
|
||||
if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) ||
|
||||
!types.equal(instruction.type, emitter.module.globals[global_id].type) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid global address")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = getelementptr %s, ptr @bro.g.%d, i64 0\n",
|
||||
instruction_index, llvm_type(instruction.type, &emitter.module.types), global_id,
|
||||
)
|
||||
case .Address_Of:
|
||||
child := types.child_type(instruction.type, &emitter.module.types)
|
||||
if !types.is_pointer(instruction.type, &emitter.module.types) ||
|
||||
!valid_address(instructions, instruction.a, child, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid address operand")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = getelementptr %s, ptr %%v%d, i64 0\n",
|
||||
instruction_index, llvm_type(child, &emitter.module.types), instruction.a,
|
||||
)
|
||||
case .Alloca:
|
||||
if !types.is_concrete_integer(instruction.type) {
|
||||
if !types.is_runtime_value(instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type))
|
||||
case .Load:
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types))
|
||||
case .Index_Address:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
instructions[instruction.a].op != .Alloca ||
|
||||
!types.equal(instructions[instruction.a].type, instruction.type) {
|
||||
!valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid index operands")
|
||||
continue
|
||||
}
|
||||
container := instructions[instruction.a]
|
||||
container_node, container_ok := types.node(&emitter.module.types, container.type)
|
||||
if !container_ok {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid index container")
|
||||
continue
|
||||
}
|
||||
pointer_name := fmt.tprintf("%%v%d", instruction.a)
|
||||
length: u64
|
||||
bounded := false
|
||||
if container_node.kind == .Array {
|
||||
length = container_node.count
|
||||
if container_node.has_sentinel && instruction.integer != 0 {
|
||||
length += 1
|
||||
}
|
||||
bounded = true
|
||||
} else if container_node.kind == .Slice {
|
||||
fmt.sbprintf(&emitter.builder, " %%index_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
|
||||
fmt.sbprintf(&emitter.builder, " %%index_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
|
||||
pointer_name = fmt.tprintf("%%index_ptr%d", instruction_index)
|
||||
comparison := "ule" if container_node.has_sentinel && instruction.integer != 0 else "ult"
|
||||
fmt.sbprintf(&emitter.builder, " %%index_ok%d = icmp %s i64 ", instruction_index, comparison)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", %%index_len%d\n", instruction_index)
|
||||
bounded = true
|
||||
} else if container_node.kind != .Pointer || !container_node.many {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid index container")
|
||||
continue
|
||||
}
|
||||
if bounded {
|
||||
if container_node.kind == .Array {
|
||||
fmt.sbprintf(&emitter.builder, " %%index_ok%d = icmp ult i64 ", instruction_index)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", %d\n", length)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%index_ok%d, label %%index_continue%d, label %%index_trap%d\nindex_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "index out of bounds")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\nindex_continue%d:\n", instruction_index)
|
||||
}
|
||||
if container_node.kind == .Array {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(container.type, &emitter.module.types), pointer_name)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 ", instruction_index, llvm_type(instruction.type, &emitter.module.types), pointer_name)
|
||||
}
|
||||
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
case .Field_Address:
|
||||
if !valid_instruction(instructions, instruction.a) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid field base")
|
||||
continue
|
||||
}
|
||||
base_type := instructions[instruction.a].type
|
||||
if types.is_pointer(base_type, &emitter.module.types) {
|
||||
base_type = types.child_type(base_type, &emitter.module.types)
|
||||
}
|
||||
fields := types.fields_for(&emitter.module.types, base_type)
|
||||
field_index := int(instruction.integer)
|
||||
if field_index < 0 || field_index >= len(fields) ||
|
||||
!types.equal(fields[field_index].type, instruction.type) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n",
|
||||
instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index,
|
||||
)
|
||||
case .Load:
|
||||
if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot")
|
||||
continue
|
||||
}
|
||||
@@ -208,42 +538,191 @@ emit_instruction_stream :: proc(
|
||||
&emitter.builder,
|
||||
" %%v%d = load %s, ptr %%v%d\n",
|
||||
instruction_index,
|
||||
llvm_type(instruction.type),
|
||||
llvm_type(instruction.type, &emitter.module.types),
|
||||
instruction.a,
|
||||
)
|
||||
case .Store:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
instructions[instruction.a].op != .Alloca ||
|
||||
!types.equal(instructions[instruction.a].type, instruction.type) ||
|
||||
!valid_value(instructions, instruction.b, instruction.type) {
|
||||
if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid store operand")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type))
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type)
|
||||
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", ptr %%v%d\n", instruction.a)
|
||||
case .Slice:
|
||||
if !valid_instruction(instructions, instruction.a) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||
continue
|
||||
}
|
||||
container := instructions[instruction.a]
|
||||
item, ok := types.node(&emitter.module.types, container.type)
|
||||
if !ok || (item.kind != .Array && item.kind != .Slice) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||
continue
|
||||
}
|
||||
pointer_name := fmt.tprintf("%%v%d", instruction.a)
|
||||
length_name := fmt.tprintf("%d", item.count)
|
||||
if item.kind == .Array {
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
|
||||
pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index)
|
||||
} else if item.kind == .Slice {
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a)
|
||||
pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index)
|
||||
length_name = fmt.tprintf("%%slice_len%d", instruction_index)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_bound_start%d = add i64 0, ", instruction_index)
|
||||
if len(instruction.args) > 0 && instruction.args[0] != ir.INVALID_INSTRUCTION {
|
||||
write_operand(&emitter.builder, instructions, instruction.args[0], types.USIZE, &emitter.module.types)
|
||||
} else {
|
||||
strings.write_string(&emitter.builder, "0")
|
||||
}
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_bound_end%d = add i64 0, ", instruction_index)
|
||||
if len(instruction.args) > 1 && instruction.args[1] != ir.INVALID_INSTRUCTION {
|
||||
write_operand(&emitter.builder, instructions, instruction.args[1], types.USIZE, &emitter.module.types)
|
||||
} else {
|
||||
strings.write_string(&emitter.builder, length_name)
|
||||
}
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
start_name := fmt.tprintf("%%slice_bound_start%d", instruction_index)
|
||||
end_name := fmt.tprintf("%%slice_bound_end%d", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_order%d = icmp ule i64 %s, %s\n", instruction_index, start_name, end_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp ule i64 %s, %s\n", instruction_index, end_name, length_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_ok%d = and i1 %%slice_order%d, %%slice_end_ok%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%slice_ok%d, label %%slice_continue%d, label %%slice_trap%d\nslice_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice bounds out of range")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\nslice_continue%d:\n", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_start%d = getelementptr %s, ptr %s, i64 %s\n", instruction_index, llvm_type(item.child, &emitter.module.types), pointer_name, start_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_result%d = insertvalue %s poison, ptr %%slice_start%d, 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%slice_result_len%d = sub i64 %s, %s\n", instruction_index, end_name, start_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%slice_result%d, i64 %%slice_result_len%d, 1\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index, instruction_index)
|
||||
case .Length:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!types.is_slice(instructions[instruction.a].type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice length")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = extractvalue %s %%v%d, 1\n",
|
||||
instruction_index,
|
||||
llvm_type(instructions[instruction.a].type, &emitter.module.types),
|
||||
instruction.a,
|
||||
)
|
||||
case .Slice_Ptr:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!types.is_pointer(instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid container pointer")
|
||||
continue
|
||||
}
|
||||
container_type := instructions[instruction.a].type
|
||||
if types.is_array(container_type, &emitter.module.types) {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n",
|
||||
instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a,
|
||||
)
|
||||
} else if types.is_slice(container_type, &emitter.module.types) {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = extractvalue %s %%v%d, 0\n",
|
||||
instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a,
|
||||
)
|
||||
} else {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid container pointer")
|
||||
}
|
||||
case .Unwrap:
|
||||
optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
|
||||
item, ok := types.node(&emitter.module.types, optional_type)
|
||||
if !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid optional unwrap")
|
||||
continue
|
||||
}
|
||||
if types.is_pointer(item.child, &emitter.module.types) {
|
||||
fmt.sbprintf(&emitter.builder, " %%optional_ok%d = icmp ne ptr %%v%d, null\n", instruction_index, instruction.a)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%optional_ok%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%optional_ok%d, label %%optional_continue%d, label %%optional_trap%d\noptional_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "attempted to unwrap none")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\noptional_continue%d:\n", instruction_index)
|
||||
if types.is_pointer(item.child, &emitter.module.types) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a)
|
||||
}
|
||||
case .Orelse_Begin:
|
||||
optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
|
||||
item, ok := types.node(&emitter.module.types, optional_type)
|
||||
if !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) ||
|
||||
!valid_value(instructions, instruction.a, optional_type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid optional fallback")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%orelse_slot%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types))
|
||||
if types.is_pointer(item.child, &emitter.module.types) {
|
||||
fmt.sbprintf(&emitter.builder, " %%orelse_ok%d = icmp ne ptr %%v%d, null\n", instruction_index, instruction.a)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%orelse_ok%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a)
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" br i1 %%orelse_ok%d, label %%orelse_some%d, label %%orelse_fallback%d\norelse_fallback%d:\n",
|
||||
instruction_index, instruction_index, instruction_index, instruction_index,
|
||||
)
|
||||
case .Orelse:
|
||||
begin := instructions[instruction.a] if valid_instruction(instructions, instruction.a) else ir.Instruction{}
|
||||
optional_type := instructions[begin.a].type if valid_instruction(instructions, begin.a) else types.INVALID
|
||||
item, ok := types.node(&emitter.module.types, optional_type)
|
||||
if begin.op != .Orelse_Begin || !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) ||
|
||||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid optional fallback")
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, " store %s ", type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", ptr %%orelse_slot%d\n", instruction.a)
|
||||
fmt.sbprintf(&emitter.builder, " br label %%orelse_merge%d\norelse_some%d:\n", instruction.a, instruction.a)
|
||||
if types.is_pointer(item.child, &emitter.module.types) {
|
||||
fmt.sbprintf(&emitter.builder, " store ptr %%v%d, ptr %%orelse_slot%d\n", begin.a, instruction.a)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%orelse_value%d = extractvalue %s %%v%d, 1\n", instruction.a, llvm_type(optional_type, &emitter.module.types), begin.a)
|
||||
fmt.sbprintf(&emitter.builder, " store %s %%orelse_value%d, ptr %%orelse_slot%d\n", type_name, instruction.a, instruction.a)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " br label %%orelse_merge%d\norelse_merge%d:\n", instruction.a, instruction.a)
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%orelse_slot%d\n", instruction_index, type_name, instruction.a)
|
||||
case .Widen:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!types.is_concrete_integer(instructions[instruction.a].type) ||
|
||||
!types.is_concrete_integer(instruction.type) ||
|
||||
instructions[instruction.a].type.bits >= instruction.type.bits {
|
||||
!types.can_widen(instructions[instruction.a].type, instruction.type) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid widening operand")
|
||||
continue
|
||||
}
|
||||
from_type := instructions[instruction.a].type
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_index, llvm_type(from_type))
|
||||
write_operand(&emitter.builder, instructions, instruction.a, from_type)
|
||||
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type))
|
||||
operation := "fpext" if types.is_float(from_type, emitter.module.target) else ("sext" if types.is_signed(from_type, emitter.module.target) else "zext")
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
|
||||
case .Neg_Checked:
|
||||
if !valid_value(instructions, instruction.a, instruction.type) {
|
||||
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(instruction.type)
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
if types.is_float(instruction.type, emitter.module.target) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = fneg %s ", instruction_index, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.ssub.with.overflow.%s(%s 0, %s ", type_name, type_name, type_name, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ")\n")
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index)
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
@@ -263,18 +742,27 @@ emit_instruction_stream :: proc(
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
|
||||
case .Add_Checked:
|
||||
if !valid_value(instructions, instruction.a, instruction.type) ||
|
||||
!valid_value(instructions, instruction.b, instruction.type) {
|
||||
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand")
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(instruction.type)
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
if types.is_float(instruction.type, emitter.module.target) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = fadd %s ", instruction_index, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ", ")
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
continue
|
||||
}
|
||||
intrinsic := "uadd" if types.is_unsigned(instruction.type, emitter.module.target) else "sadd"
|
||||
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type)
|
||||
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s.with.overflow.%s(%s ", type_name, intrinsic, type_name, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ")\n")
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index)
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
@@ -290,9 +778,20 @@ emit_instruction_stream :: proc(
|
||||
instruction_index,
|
||||
)
|
||||
fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer addition overflow")
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "integer addition overflow")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
|
||||
case .Pointer_Add:
|
||||
item, ok := types.node(&emitter.module.types, instruction.type)
|
||||
if !ok || item.kind != .Pointer || !item.many ||
|
||||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
!valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer offset")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 ", instruction_index, llvm_type(item.child, &emitter.module.types), instruction.a)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
case .Call:
|
||||
function_id := ir.as_function(instruction.target)
|
||||
if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) {
|
||||
@@ -303,7 +802,7 @@ emit_instruction_stream :: proc(
|
||||
valid_args := len(instruction.args) == len(target.param_types)
|
||||
if valid_args {
|
||||
for arg, index in instruction.args {
|
||||
if !valid_value(instructions, arg, target.param_types[index]) {
|
||||
if !valid_value(instructions, arg, target.param_types[index], &emitter.module.types) {
|
||||
valid_args = false
|
||||
break
|
||||
}
|
||||
@@ -317,7 +816,7 @@ emit_instruction_stream :: proc(
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands")
|
||||
continue
|
||||
}
|
||||
if instruction.type.kind != .Void {
|
||||
if !types.is_void(instruction.type) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
|
||||
} else {
|
||||
strings.write_string(&emitter.builder, " ")
|
||||
@@ -326,8 +825,12 @@ emit_instruction_stream :: proc(
|
||||
if target.calling_convention == .Brolang {
|
||||
strings.write_string(&emitter.builder, "fastcc ")
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
|
||||
emit_call_args(&emitter.builder, instructions, instruction.args, target.param_types)
|
||||
emit_function_result(&emitter.builder, target, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, " @%s(", target.link_name)
|
||||
emit_call_args(
|
||||
&emitter.builder, instructions, instruction.args, target.param_types,
|
||||
&emitter.module.types, target.calling_convention == .C,
|
||||
)
|
||||
strings.write_string(&emitter.builder, ")\n")
|
||||
case .Trap:
|
||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
||||
@@ -337,8 +840,8 @@ emit_instruction_stream :: proc(
|
||||
return_value = instruction.a
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function))
|
||||
write_operand(&emitter.builder, instructions, instruction.a, function.result)
|
||||
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
after_return = true
|
||||
case .Return_Void:
|
||||
@@ -361,17 +864,18 @@ emit_globals :: proc(emitter: ^Emitter) {
|
||||
if global.is_static {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
"@bro.g.%d = internal constant %s %d\n",
|
||||
"@bro.g.%d = internal constant %s ",
|
||||
global_id,
|
||||
llvm_type(global.type),
|
||||
global.static_value,
|
||||
llvm_type(global.type, &emitter.module.types),
|
||||
)
|
||||
write_constant(&emitter.builder, global.static_value, global.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
} else {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
"@bro.g.%d = internal global %s 0\n@bro.gstate.%d = internal global i8 0\n",
|
||||
"@bro.g.%d = internal global %s zeroinitializer\n@bro.gstate.%d = internal global i8 0\n",
|
||||
global_id,
|
||||
llvm_type(global.type),
|
||||
llvm_type(global.type, &emitter.module.types),
|
||||
global_id,
|
||||
)
|
||||
}
|
||||
@@ -379,13 +883,53 @@ emit_globals :: proc(emitter: ^Emitter) {
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
}
|
||||
|
||||
emit_types :: proc(emitter: ^Emitter) {
|
||||
for item, index in emitter.module.types.nodes {
|
||||
if item.kind != .Struct {
|
||||
continue
|
||||
}
|
||||
id := types.DYNAMIC_START+types.Type(index)
|
||||
fmt.sbprintf(&emitter.builder, "%%bro.type.%d = type ", id)
|
||||
if item.opaque {
|
||||
strings.write_string(&emitter.builder, "opaque\n")
|
||||
continue
|
||||
}
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
for field, field_index in types.fields_for(&emitter.module.types, id) {
|
||||
if field_index > 0 {
|
||||
strings.write_string(&emitter.builder, ", ")
|
||||
}
|
||||
strings.write_string(&emitter.builder, llvm_type(field.type, &emitter.module.types))
|
||||
}
|
||||
strings.write_string(&emitter.builder, " }\n")
|
||||
}
|
||||
if len(emitter.module.types.nodes) > 0 {
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
emit_strings :: proc(emitter: ^Emitter) {
|
||||
for value, id in emitter.module.strings {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
"@bro.str.%d = private unnamed_addr constant [%d x i8] c\"",
|
||||
id, len(value)+1,
|
||||
)
|
||||
emit_escaped_bytes(&emitter.builder, value)
|
||||
strings.write_string(&emitter.builder, "\\00\"\n")
|
||||
}
|
||||
if len(emitter.module.strings) > 0 {
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
emit_global_accessors :: proc(emitter: ^Emitter) {
|
||||
placeholder_function := ir.Function{result=types.I64}
|
||||
for global, global_id in emitter.module.globals {
|
||||
if global.is_static {
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(global.type)
|
||||
type_name := llvm_type(global.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, "define internal %s @bro.get.%d() ", type_name, global_id)
|
||||
strings.write_string(&emitter.builder, "{\nentry:\n")
|
||||
fmt.sbprintf(
|
||||
@@ -407,11 +951,11 @@ emit_global_accessors :: proc(emitter: ^Emitter) {
|
||||
placeholder_function.result = global.type
|
||||
value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true)
|
||||
fmt.sbprintf(&emitter.builder, " store %s ", type_name)
|
||||
write_operand(&emitter.builder, global.initializer, value, global.type)
|
||||
write_operand(&emitter.builder, global.initializer, value, global.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", ptr @bro.g.%d\n", global_id)
|
||||
fmt.sbprintf(&emitter.builder, " store i8 2, ptr @bro.gstate.%d\n", global_id)
|
||||
fmt.sbprintf(&emitter.builder, " ret %s ", type_name)
|
||||
write_operand(&emitter.builder, global.initializer, value, global.type)
|
||||
write_operand(&emitter.builder, global.initializer, value, global.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\nready:\n")
|
||||
fmt.sbprintf(&emitter.builder, " %%value = load %s, ptr @bro.g.%d\n ret %s %%value\n}\n\n", type_name, global_id, type_name)
|
||||
}
|
||||
@@ -434,7 +978,7 @@ emit_constructor :: proc(emitter: ^Emitter) {
|
||||
strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
|
||||
for global, global_id in emitter.module.globals {
|
||||
if !global.is_static && !global.problematic {
|
||||
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type), global_id)
|
||||
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
||||
}
|
||||
}
|
||||
strings.write_string(&emitter.builder, " ret void\n}\n\n")
|
||||
@@ -453,15 +997,25 @@ emit_functions :: proc(emitter: ^Emitter) {
|
||||
if function.calling_convention == .Brolang {
|
||||
strings.write_string(&emitter.builder, "fastcc ")
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(function), function.link_name)
|
||||
emit_function_result(&emitter.builder, function, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, " @%s(", function.link_name)
|
||||
for param_type, index in function.param_types {
|
||||
if index > 0 {
|
||||
strings.write_string(&emitter.builder, ", ")
|
||||
}
|
||||
if function.implementation == .Declaration {
|
||||
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type))
|
||||
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types))
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, "%s %%v%d", llvm_type(param_type), index)
|
||||
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types))
|
||||
}
|
||||
if function.calling_convention == .C {
|
||||
extension := c_abi_extension(param_type, emitter.module.target)
|
||||
if len(extension) > 0 {
|
||||
fmt.sbprintf(&emitter.builder, " %s", extension)
|
||||
}
|
||||
}
|
||||
if function.implementation != .Declaration {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d", index)
|
||||
}
|
||||
}
|
||||
if function.implementation == .Declaration {
|
||||
@@ -503,6 +1057,10 @@ emit_declarations :: proc(emitter: ^Emitter) {
|
||||
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
|
||||
strings.write_string(&emitter.builder, "declare { i")
|
||||
fmt.sbprintf(&emitter.builder, "%d", bits)
|
||||
strings.write_string(&emitter.builder, ", i1 } @llvm.uadd.with.overflow.i")
|
||||
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
|
||||
strings.write_string(&emitter.builder, "declare { i")
|
||||
fmt.sbprintf(&emitter.builder, "%d", bits)
|
||||
strings.write_string(&emitter.builder, ", i1 } @llvm.ssub.with.overflow.i")
|
||||
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
|
||||
}
|
||||
@@ -534,7 +1092,11 @@ emit :: proc(
|
||||
strings.builder_destroy(&emitter.builder)
|
||||
}
|
||||
|
||||
strings.write_string(&emitter.builder, "; generated by brolang\n\n")
|
||||
strings.write_string(&emitter.builder, "; generated by brolang\n")
|
||||
fmt.sbprintf(&emitter.builder, "target datalayout = \"%s\"\n", target.llvm_data_layout(module.target))
|
||||
fmt.sbprintf(&emitter.builder, "target triple = \"%s\"\n\n", target.llvm_triple(module.target))
|
||||
emit_types(&emitter)
|
||||
emit_strings(&emitter)
|
||||
emit_globals(&emitter)
|
||||
emit_constructor(&emitter)
|
||||
emit_global_accessors(&emitter)
|
||||
|
||||
@@ -5,6 +5,7 @@ import "../lexer"
|
||||
import "../parser"
|
||||
import "../source"
|
||||
import "../symbol"
|
||||
import "../types"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:path/filepath"
|
||||
@@ -253,6 +254,92 @@ validate_imports :: proc(state: ^State) {
|
||||
}
|
||||
}
|
||||
|
||||
find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.Id) -> ast.Import_Id {
|
||||
for import_item, index in module.imports {
|
||||
if import_item.file == file && import_item.alias == alias {
|
||||
return ast.import_id(index)
|
||||
}
|
||||
}
|
||||
return ast.INVALID_IMPORT
|
||||
}
|
||||
|
||||
canonical_type :: proc(
|
||||
module: ^ast.Module,
|
||||
value: types.Type,
|
||||
mapping: []types.Type,
|
||||
visiting: []bool,
|
||||
) -> types.Type {
|
||||
if value < types.DYNAMIC_START {
|
||||
return value
|
||||
}
|
||||
index := int(value-types.DYNAMIC_START)
|
||||
if index < 0 || index >= len(mapping) {
|
||||
return value
|
||||
}
|
||||
if types.is_valid(mapping[index]) {
|
||||
return mapping[index]
|
||||
}
|
||||
if visiting[index] {
|
||||
return value
|
||||
}
|
||||
visiting[index] = true
|
||||
defer visiting[index] = false
|
||||
item := module.type_store.nodes[index]
|
||||
if item.kind == .Named {
|
||||
if item.qualifier != 0 {
|
||||
import_id := find_type_import(module, ast.File_Id(item.file), symbol.Id(item.qualifier))
|
||||
if import_id != ast.INVALID_IMPORT {
|
||||
module.imports[import_id].used = true
|
||||
import_item := module.imports[import_id]
|
||||
resolved := types.find_named(&module.type_store, u32(import_item.target), item.name)
|
||||
if types.is_valid(resolved) {
|
||||
mapping[index] = resolved
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
}
|
||||
mapping[index] = value
|
||||
return value
|
||||
}
|
||||
if item.kind == .Struct {
|
||||
mapping[index] = value
|
||||
fields := types.fields_for(&module.type_store, value)
|
||||
for &field in fields {
|
||||
field.type = canonical_type(module, field.type, mapping, visiting)
|
||||
}
|
||||
return value
|
||||
}
|
||||
if types.is_valid(item.child) {
|
||||
item.child = canonical_type(module, item.child, mapping, visiting)
|
||||
}
|
||||
resolved := types.intern(&module.type_store, item)
|
||||
mapping[index] = resolved
|
||||
return resolved
|
||||
}
|
||||
|
||||
canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
||||
original_count := len(module.type_store.nodes)
|
||||
mapping := make([]types.Type, original_count, allocator)
|
||||
visiting := make([]bool, original_count, allocator)
|
||||
defer delete(mapping, allocator)
|
||||
defer delete(visiting, allocator)
|
||||
for &function in module.functions {
|
||||
for ¶m in function.params {
|
||||
param.type = canonical_type(module, param.type, mapping, visiting)
|
||||
}
|
||||
function.result = canonical_type(module, function.result, mapping, visiting)
|
||||
}
|
||||
for &global in module.globals {
|
||||
global.type = canonical_type(module, global.type, mapping, visiting)
|
||||
}
|
||||
for &statement in module.statements {
|
||||
statement.type = canonical_type(module, statement.type, mapping, visiting)
|
||||
}
|
||||
for index := 0; index < original_count; index += 1 {
|
||||
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
|
||||
}
|
||||
}
|
||||
|
||||
load :: proc(
|
||||
root_path: string,
|
||||
sources: ^source.Store,
|
||||
@@ -275,5 +362,6 @@ load :: proc(
|
||||
state.root_failed = true
|
||||
}
|
||||
validate_imports(&state)
|
||||
canonicalize_types(&module, allocator)
|
||||
return module, !state.root_failed
|
||||
}
|
||||
|
||||
+231
-19
@@ -3,6 +3,7 @@ package lower
|
||||
import "../hir"
|
||||
import "../ir"
|
||||
import "../source"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
@@ -28,8 +29,11 @@ clone_args :: proc(values: []ir.Instruction_Id, allocator: mem.Allocator) -> []i
|
||||
return result
|
||||
}
|
||||
|
||||
sentinel :: proc(value_type: types.Type) -> i64 {
|
||||
switch value_type.bits {
|
||||
sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 {
|
||||
if types.is_float(value_type, selected) {
|
||||
return i64(0x7fc0_0000) if types.bits(value_type, selected) == 32 else transmute(i64)u64(0x7ff8_0000_0000_0000)
|
||||
}
|
||||
switch types.bits(value_type, selected) {
|
||||
case 8: return -86
|
||||
case 16: return -21846
|
||||
case 32: return -1431655766
|
||||
@@ -53,14 +57,14 @@ append_recovery_value :: proc(
|
||||
diagnostic=diagnostic,
|
||||
})
|
||||
fallback := value_type
|
||||
if !types.is_concrete_integer(fallback) {
|
||||
if !types.is_valid(fallback) {
|
||||
fallback = types.I64
|
||||
}
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Const,
|
||||
span=span,
|
||||
type=fallback,
|
||||
integer=sentinel(fallback),
|
||||
integer=sentinel(fallback, state.hir_module.target),
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
@@ -76,6 +80,187 @@ Lower_Expr_Frame :: struct {
|
||||
args: []ir.Instruction_Id,
|
||||
}
|
||||
|
||||
lower_nested_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
outer := state.expr_stack
|
||||
state.expr_stack = nil
|
||||
state.expr_stack.allocator = state.allocator
|
||||
result := lower_expr(state, expr_id)
|
||||
delete(state.expr_stack)
|
||||
state.expr_stack = outer
|
||||
return result
|
||||
}
|
||||
|
||||
lower_location :: proc(state: ^State, expr_id: hir.Expr_Id, for_write := false) -> ir.Instruction_Id {
|
||||
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(state.hir_module.exprs) {
|
||||
return ir.INVALID_INSTRUCTION
|
||||
}
|
||||
expr := state.hir_module.exprs[expr_id]
|
||||
#partial switch expr.kind {
|
||||
case .Local:
|
||||
local := hir.as_local(expr.target)
|
||||
if local != hir.INVALID_LOCAL && int(local) < len(state.local_slots) {
|
||||
return state.local_slots[local]
|
||||
}
|
||||
case .Global:
|
||||
global := hir.as_global(expr.target)
|
||||
if global != hir.INVALID_GLOBAL && int(global) < len(state.hir_module.globals) {
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Address_Global, span=expr.span, type=expr.type,
|
||||
target=ir.global_ref(ir.Global_Id(global)),
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
case .Deref:
|
||||
return lower_nested_expr(state, expr.left)
|
||||
case .Index:
|
||||
container_type := state.hir_module.exprs[expr.left].type
|
||||
container := lower_nested_expr(state, expr.left)
|
||||
if types.is_array(container_type, &state.hir_module.types) {
|
||||
container = lower_location(state, expr.left, for_write)
|
||||
}
|
||||
index := lower_nested_expr(state, expr.right)
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Index_Address, span=expr.span, type=expr.type,
|
||||
integer=0 if for_write else 1,
|
||||
target=ir.INVALID_REF, a=container, b=index,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Field:
|
||||
base_type := state.hir_module.exprs[expr.left].type
|
||||
base := lower_nested_expr(state, expr.left)
|
||||
if !types.is_pointer(base_type, &state.hir_module.types) {
|
||||
base = lower_location(state, expr.left, for_write)
|
||||
}
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Field_Address, span=expr.span, type=expr.type, integer=expr.integer,
|
||||
target=ir.INVALID_REF, a=base, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return ir.INVALID_INSTRUCTION
|
||||
}
|
||||
|
||||
lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
expr := state.hir_module.exprs[expr_id]
|
||||
#partial switch expr.kind {
|
||||
case .String:
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.String, span=expr.span, type=expr.type, integer=expr.integer,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Array, .Struct:
|
||||
args := make([]ir.Instruction_Id, len(expr.args), state.allocator)
|
||||
for arg, index in expr.args {
|
||||
args[index] = lower_nested_expr(state, arg)
|
||||
}
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Aggregate, span=expr.span, type=expr.type, args=args,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .None:
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.None, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Optional_Some:
|
||||
value := lower_nested_expr(state, expr.left)
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Optional_Some, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Address:
|
||||
location := lower_location(state, expr.left, types.is_mutable(expr.type, &state.hir_module.types))
|
||||
if location != ir.INVALID_INSTRUCTION {
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Address_Of, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=location, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||
case .Deref, .Index, .Field:
|
||||
location := lower_location(state, expr_id)
|
||||
if location == ir.INVALID_INSTRUCTION {
|
||||
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||
}
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Load, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=location, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Slice:
|
||||
container := lower_nested_expr(state, expr.left)
|
||||
if types.is_array(state.hir_module.exprs[expr.left].type, &state.hir_module.types) {
|
||||
container = lower_location(state, expr.left)
|
||||
}
|
||||
args := make([]ir.Instruction_Id, len(expr.args), state.allocator)
|
||||
for arg, index in expr.args {
|
||||
args[index] = ir.INVALID_INSTRUCTION
|
||||
if arg != hir.INVALID_EXPR {
|
||||
args[index] = lower_nested_expr(state, arg)
|
||||
}
|
||||
}
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Slice, span=expr.span, type=expr.type, args=args,
|
||||
target=ir.INVALID_REF, a=container, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Unwrap:
|
||||
value := lower_nested_expr(state, expr.left)
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Unwrap, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Length:
|
||||
container_type := state.hir_module.exprs[expr.left].type
|
||||
item, ok := types.node(&state.hir_module.types, container_type)
|
||||
if ok && item.kind == .Array {
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Const, span=expr.span, type=types.USIZE, integer=i64(item.count),
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
value := lower_nested_expr(state, expr.left)
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Length, span=expr.span, type=types.USIZE,
|
||||
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Slice_Ptr:
|
||||
container_type := state.hir_module.exprs[expr.left].type
|
||||
value := lower_nested_expr(state, expr.left)
|
||||
if types.is_array(container_type, &state.hir_module.types) {
|
||||
value = lower_location(state, expr.left)
|
||||
}
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Slice_Ptr, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Orelse:
|
||||
value := lower_nested_expr(state, expr.left)
|
||||
begin := append_instruction(state, ir.Instruction{
|
||||
op=.Orelse_Begin, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
fallback := lower_nested_expr(state, expr.right)
|
||||
return append_instruction(state, ir.Instruction{
|
||||
op=.Orelse, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=begin, b=fallback,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||
}
|
||||
|
||||
lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
stack := state.expr_stack
|
||||
clear_dynamic_array(&stack)
|
||||
@@ -98,16 +283,20 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
}
|
||||
expr := state.hir_module.exprs[frame.expr]
|
||||
if frame.stage == 0 {
|
||||
switch expr.kind {
|
||||
#partial switch expr.kind {
|
||||
case .Invalid:
|
||||
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||
_ = pop(&stack)
|
||||
case .Integer:
|
||||
case .Integer, .Float:
|
||||
last = append_instruction(state, ir.Instruction{
|
||||
op=.Const, span=expr.span, type=expr.type, integer=expr.integer,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
_ = pop(&stack)
|
||||
case .String, .Array, .Struct, .None, .Optional_Some, .Address, .Deref,
|
||||
.Index, .Slice, .Field, .Length, .Slice_Ptr, .Unwrap, .Orelse:
|
||||
last = lower_compound_expr(state, frame.expr)
|
||||
_ = pop(&stack)
|
||||
case .Local:
|
||||
last = ir.INVALID_INSTRUCTION
|
||||
local := hir.as_local(expr.target)
|
||||
@@ -141,7 +330,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
case .Negate:
|
||||
stack[frame_index].stage = 5
|
||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||
case .Add:
|
||||
case .Add, .Pointer_Add:
|
||||
stack[frame_index].stage = 2
|
||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||
case .Call:
|
||||
@@ -183,7 +372,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
}
|
||||
if frame.stage == 3 {
|
||||
last = append_instruction(state, ir.Instruction{
|
||||
op=.Add_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
||||
op=.Pointer_Add if expr.kind == .Pointer_Add else .Add_Checked,
|
||||
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
||||
a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
_ = pop(&stack)
|
||||
@@ -238,6 +428,20 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
||||
})
|
||||
state.local_values[local_id] = param
|
||||
}
|
||||
for local_id in function.params {
|
||||
slot := append_instruction(&state, ir.Instruction{
|
||||
op=.Alloca, type=function.locals[local_id].type,
|
||||
target=ir.local_ref(ir.Local_Id(local_id)),
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append_instruction(&state, ir.Instruction{
|
||||
op=.Store, type=function.locals[local_id].type,
|
||||
target=ir.INVALID_REF, a=slot, b=state.local_values[local_id],
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
state.local_slots[local_id] = slot
|
||||
}
|
||||
|
||||
for statement_id in function.body {
|
||||
statement := hir_module.statements[statement_id]
|
||||
@@ -252,7 +456,6 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
||||
continue
|
||||
}
|
||||
local := function.locals[statement.local]
|
||||
if local.mutable {
|
||||
slot := append_instruction(&state, ir.Instruction{
|
||||
op=.Alloca,
|
||||
span=statement.span,
|
||||
@@ -272,16 +475,20 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
||||
b=value,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
state.local_values[statement.local] = value
|
||||
}
|
||||
case .Assignment:
|
||||
value := lower_expr(&state, statement.expr)
|
||||
slot := ir.INVALID_INSTRUCTION
|
||||
if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) {
|
||||
slot = state.local_slots[statement.local]
|
||||
value_type := types.INVALID
|
||||
if statement.target != hir.INVALID_EXPR {
|
||||
slot = lower_location(&state, statement.target, true)
|
||||
if int(statement.target) < len(hir_module.exprs) {
|
||||
value_type = hir_module.exprs[statement.target].type
|
||||
}
|
||||
if slot == ir.INVALID_INSTRUCTION || statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(function.locals) {
|
||||
} else if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) {
|
||||
slot = state.local_slots[statement.local]
|
||||
value_type = function.locals[statement.local].type
|
||||
}
|
||||
if slot == ir.INVALID_INSTRUCTION || !types.is_valid(value_type) {
|
||||
append_instruction(&state, ir.Instruction{
|
||||
op=.Trap, span=statement.span, type=types.VOID,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
||||
@@ -291,7 +498,7 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
||||
append_instruction(&state, ir.Instruction{
|
||||
op=.Store,
|
||||
span=statement.span,
|
||||
type=function.locals[statement.local].type,
|
||||
type=value_type,
|
||||
target=ir.INVALID_REF,
|
||||
a=slot,
|
||||
b=value,
|
||||
@@ -337,13 +544,13 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
||||
if len(state.instructions) == 0 ||
|
||||
(state.instructions[len(state.instructions)-1].op != .Return &&
|
||||
state.instructions[len(state.instructions)-1].op != .Return_Void) {
|
||||
if function.result.kind == .Void {
|
||||
if types.is_void(function.result) {
|
||||
append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC})
|
||||
} else {
|
||||
value := append_instruction(&state, ir.Instruction{
|
||||
op=.Const,
|
||||
type=function.result,
|
||||
integer=sentinel(function.result),
|
||||
integer=sentinel(function.result, hir_module.target),
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
@@ -373,7 +580,12 @@ lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, al
|
||||
}
|
||||
|
||||
lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Module {
|
||||
module := ir.init_module(allocator)
|
||||
module := ir.init_module(hir_module.target, allocator)
|
||||
types.destroy_store(&module.types)
|
||||
module.types = types.clone_store(&hir_module.types, allocator)
|
||||
for value in hir_module.strings {
|
||||
append(&module.strings, fmt.aprintf("%s", value, allocator=allocator))
|
||||
}
|
||||
for global in hir_module.globals {
|
||||
_ = ir.global_id(len(module.globals))
|
||||
append(&module.globals, ir.Global{
|
||||
|
||||
+537
-28
@@ -4,9 +4,12 @@ import "../ast"
|
||||
import "../source"
|
||||
import "../symbol"
|
||||
import "../token"
|
||||
import "../types"
|
||||
import "base:intrinsics"
|
||||
import "core:fmt"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
Parser :: struct {
|
||||
tokens: ^token.Stream,
|
||||
@@ -80,36 +83,225 @@ invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> ast
|
||||
|
||||
is_type_token :: proc(kind: token.Kind) -> bool {
|
||||
#partial switch kind {
|
||||
case .Keyword_Int, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64, .Keyword_Void:
|
||||
case .Keyword_Int, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
|
||||
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
|
||||
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
||||
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
|
||||
.Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
|
||||
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
|
||||
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble,
|
||||
.Keyword_Void, .Identifier, .Question, .At, .Star, .Left_Bracket:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
decode_character :: proc(parser: ^Parser, tok: token.Token) -> (u64, bool) {
|
||||
text := token_text(parser, tok)
|
||||
if len(text) < 3 {
|
||||
return 0, false
|
||||
}
|
||||
contents := text[1:len(text)-1]
|
||||
if len(contents) == 2 && contents[0] == '\\' {
|
||||
switch contents[1] {
|
||||
case '0': return 0, true
|
||||
case 'n': return '\n', true
|
||||
case 'r': return '\r', true
|
||||
case 't': return '\t', true
|
||||
case '\\': return '\\', true
|
||||
case '\'': return '\'', true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
value, width := utf8.decode_rune_in_string(contents)
|
||||
return u64(value), width == len(contents)
|
||||
}
|
||||
|
||||
parse_type_constant :: proc(parser: ^Parser) -> (u64, bool) {
|
||||
negative := false
|
||||
if _, ok := allow(parser, .Minus); ok {
|
||||
negative = true
|
||||
}
|
||||
tok := current(parser)
|
||||
if tok.kind == .Integer {
|
||||
advance(parser)
|
||||
value, ok := parse_integer_magnitude(token_text(parser, tok))
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
if negative {
|
||||
return transmute(u64)-i64(value), true
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
if !negative && tok.kind == .Character {
|
||||
advance(parser)
|
||||
return decode_character(parser, tok)
|
||||
}
|
||||
source.add(parser.diagnostics, tok.span, "expected an integer or character constant")
|
||||
return 0, false
|
||||
}
|
||||
|
||||
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
tok := current(parser)
|
||||
if tok.kind == .Question {
|
||||
advance(parser)
|
||||
child := parse_type(parser)
|
||||
return types.intern(&parser.module.type_store, types.Node{kind=.Optional, child=child})
|
||||
}
|
||||
if tok.kind == .At || tok.kind == .Star {
|
||||
many := tok.kind == .Star
|
||||
advance(parser)
|
||||
_, mutable := allow(parser, .Keyword_Mut)
|
||||
child := parse_type(parser)
|
||||
return types.intern(&parser.module.type_store, types.Node{
|
||||
kind=.Pointer,
|
||||
child=child,
|
||||
mutable=mutable,
|
||||
many=many,
|
||||
})
|
||||
}
|
||||
if tok.kind == .Left_Bracket {
|
||||
advance(parser)
|
||||
node := types.Node{}
|
||||
if _, ok := allow(parser, .Right_Bracket); ok {
|
||||
node.kind = .Slice
|
||||
} else if _, ok := allow(parser, .Semicolon); ok {
|
||||
node.kind = .Slice
|
||||
node.has_sentinel = true
|
||||
node.sentinel, _ = parse_type_constant(parser)
|
||||
if _, ok = allow(parser, .Right_Bracket); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected ']' after sentinel slice type")
|
||||
}
|
||||
} else {
|
||||
node.kind = .Array
|
||||
if _, ok := allow(parser, .Underscore); ok {
|
||||
node.inferred_count = true
|
||||
} else {
|
||||
count, ok := parse_type_constant(parser)
|
||||
if ok {
|
||||
node.count = count
|
||||
}
|
||||
}
|
||||
if _, ok := allow(parser, .Semicolon); ok {
|
||||
node.has_sentinel = true
|
||||
node.sentinel, _ = parse_type_constant(parser)
|
||||
}
|
||||
if _, ok := allow(parser, .Right_Bracket); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected ']' after array type")
|
||||
}
|
||||
}
|
||||
_, node.mutable = allow(parser, .Keyword_Mut)
|
||||
node.child = parse_type(parser)
|
||||
return types.intern(&parser.module.type_store, node)
|
||||
}
|
||||
#partial switch tok.kind {
|
||||
case .Keyword_Int:
|
||||
advance(parser)
|
||||
return .Int
|
||||
return types.INT
|
||||
case .Keyword_I8:
|
||||
advance(parser)
|
||||
return .I8
|
||||
return types.I8
|
||||
case .Keyword_I16:
|
||||
advance(parser)
|
||||
return .I16
|
||||
return types.I16
|
||||
case .Keyword_I32:
|
||||
advance(parser)
|
||||
return .I32
|
||||
return types.I32
|
||||
case .Keyword_I64:
|
||||
advance(parser)
|
||||
return .I64
|
||||
return types.I64
|
||||
case .Keyword_U8:
|
||||
advance(parser)
|
||||
return types.U8
|
||||
case .Keyword_U16:
|
||||
advance(parser)
|
||||
return types.U16
|
||||
case .Keyword_U32:
|
||||
advance(parser)
|
||||
return types.U32
|
||||
case .Keyword_U64:
|
||||
advance(parser)
|
||||
return types.U64
|
||||
case .Keyword_Isize:
|
||||
advance(parser)
|
||||
return types.ISIZE
|
||||
case .Keyword_Usize:
|
||||
advance(parser)
|
||||
return types.USIZE
|
||||
case .Keyword_F32:
|
||||
advance(parser)
|
||||
return types.F32
|
||||
case .Keyword_F64:
|
||||
advance(parser)
|
||||
return types.F64
|
||||
case .Keyword_C_Char:
|
||||
advance(parser)
|
||||
return types.C_CHAR
|
||||
case .Keyword_C_Schar:
|
||||
advance(parser)
|
||||
return types.C_SCHAR
|
||||
case .Keyword_C_Uchar:
|
||||
advance(parser)
|
||||
return types.C_UCHAR
|
||||
case .Keyword_C_Short:
|
||||
advance(parser)
|
||||
return types.C_SHORT
|
||||
case .Keyword_C_Ushort:
|
||||
advance(parser)
|
||||
return types.C_USHORT
|
||||
case .Keyword_C_Int:
|
||||
advance(parser)
|
||||
return types.C_INT
|
||||
case .Keyword_C_Uint:
|
||||
advance(parser)
|
||||
return types.C_UINT
|
||||
case .Keyword_C_Long:
|
||||
advance(parser)
|
||||
return types.C_LONG
|
||||
case .Keyword_C_Ulong:
|
||||
advance(parser)
|
||||
return types.C_ULONG
|
||||
case .Keyword_C_Longlong:
|
||||
advance(parser)
|
||||
return types.C_LONGLONG
|
||||
case .Keyword_C_Ulonglong:
|
||||
advance(parser)
|
||||
return types.C_ULONGLONG
|
||||
case .Keyword_C_Float:
|
||||
advance(parser)
|
||||
return types.C_FLOAT
|
||||
case .Keyword_C_Double:
|
||||
advance(parser)
|
||||
return types.C_DOUBLE
|
||||
case .Keyword_C_Longdouble:
|
||||
advance(parser)
|
||||
return types.C_LONGDOUBLE
|
||||
case .Keyword_Void:
|
||||
advance(parser)
|
||||
return .Void
|
||||
return types.VOID
|
||||
case .Identifier:
|
||||
first := advance(parser)
|
||||
name := first
|
||||
qualifier := symbol.INVALID
|
||||
if _, ok := allow(parser, .Dot); ok {
|
||||
qualifier = first.symbol
|
||||
if current(parser).kind != .Identifier {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected a type name after '.'")
|
||||
return types.INVALID
|
||||
}
|
||||
name = advance(parser)
|
||||
}
|
||||
return types.named(
|
||||
&parser.module.type_store,
|
||||
u32(parser.pkg),
|
||||
u32(name.symbol),
|
||||
u32(qualifier),
|
||||
u32(parser.file),
|
||||
)
|
||||
}
|
||||
source.add(parser.diagnostics, tok.span, "expected a type")
|
||||
return .Invalid
|
||||
return types.INVALID
|
||||
}
|
||||
|
||||
skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
|
||||
@@ -168,6 +360,93 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok
|
||||
})
|
||||
}
|
||||
|
||||
parse_array_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
start := advance(parser)
|
||||
parser.delimiter_depth += 1
|
||||
defer parser.delimiter_depth -= 1
|
||||
args: [dynamic]ast.Expr_Id
|
||||
args.allocator = parser.module.allocator
|
||||
skip_newlines(parser)
|
||||
for current(parser).kind != .Right_Bracket && current(parser).kind != .Eof {
|
||||
append(&args, parse_expression_bp(parser, 0, nesting+1))
|
||||
skip_newlines(parser)
|
||||
if _, ok := allow(parser, .Comma); ok {
|
||||
skip_newlines(parser)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
end, ok := allow(parser, .Right_Bracket)
|
||||
if !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected ']' after array literal")
|
||||
end = start
|
||||
}
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Array,
|
||||
span=span_from(start.span, end.span),
|
||||
args=args[:],
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
parse_struct_literal :: proc(
|
||||
parser: ^Parser,
|
||||
qualifier: symbol.Id,
|
||||
first, name: token.Token,
|
||||
nesting: int,
|
||||
) -> ast.Expr_Id {
|
||||
left_brace := advance(parser)
|
||||
parser.delimiter_depth += 1
|
||||
defer parser.delimiter_depth -= 1
|
||||
args: [dynamic]ast.Expr_Id
|
||||
args.allocator = parser.module.allocator
|
||||
skip_newlines(parser)
|
||||
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
||||
field := current(parser)
|
||||
if field.kind != .Identifier {
|
||||
source.add(parser.diagnostics, field.span, "expected a keyed struct field initializer")
|
||||
break
|
||||
}
|
||||
advance(parser)
|
||||
if _, ok := allow(parser, .Equal); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '=' after struct field name")
|
||||
}
|
||||
skip_newlines(parser)
|
||||
value := parse_expression_bp(parser, 0, nesting+1)
|
||||
append(&args, add_expr(parser, ast.Expr{
|
||||
kind=.Keyed,
|
||||
span=span_from(field.span, parser.module.exprs[value].span),
|
||||
name=field.symbol,
|
||||
left=value,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}))
|
||||
skip_newlines(parser)
|
||||
if _, ok := allow(parser, .Comma); ok {
|
||||
skip_newlines(parser)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
right_brace, ok := allow(parser, .Right_Brace)
|
||||
if !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct literal")
|
||||
right_brace = left_brace
|
||||
}
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Struct_Literal,
|
||||
span=source.Span{file=name.span.file, start=first.span.start, end=right_brace.span.end},
|
||||
qualifier=qualifier,
|
||||
name=name.symbol,
|
||||
args=args[:],
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
|
||||
value: u64
|
||||
for byte in transmute([]byte)text {
|
||||
@@ -203,6 +482,58 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Character:
|
||||
advance(parser)
|
||||
value, ok := decode_character(parser, tok)
|
||||
if !ok {
|
||||
return invalid_expr(parser, tok.span, "character literal must contain one Unicode code point")
|
||||
}
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Integer,
|
||||
span=tok.span,
|
||||
integer=value,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Float:
|
||||
advance(parser)
|
||||
value, ok := strconv.parse_f64(token_text(parser, tok))
|
||||
if !ok {
|
||||
return invalid_expr(parser, tok.span, "invalid floating-point literal")
|
||||
}
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Float,
|
||||
span=tok.span,
|
||||
integer=transmute(u64)value,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .String:
|
||||
advance(parser)
|
||||
value := decode_import_path(parser, tok)
|
||||
id := u64(len(parser.module.strings))
|
||||
append(&parser.module.strings, value)
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.String,
|
||||
span=tok.span,
|
||||
integer=id,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Keyword_None:
|
||||
advance(parser)
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.None,
|
||||
span=tok.span,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Left_Bracket:
|
||||
return parse_array_literal(parser, nesting)
|
||||
case .Identifier:
|
||||
first := advance(parser)
|
||||
name := first
|
||||
@@ -217,6 +548,9 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
if current(parser).kind == .Left_Paren {
|
||||
return parse_call(parser, qualifier, first, name, nesting)
|
||||
}
|
||||
if current(parser).kind == .Left_Brace {
|
||||
return parse_struct_literal(parser, qualifier, first, name, nesting)
|
||||
}
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Name,
|
||||
span=span_from(first.span, name.span),
|
||||
@@ -262,6 +596,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
|
||||
infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
|
||||
#partial switch kind {
|
||||
case .Keyword_Orelse:
|
||||
return 2, 3, true
|
||||
case .Plus:
|
||||
return 10, 11, true
|
||||
}
|
||||
@@ -270,7 +606,7 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
|
||||
|
||||
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
|
||||
#partial switch kind {
|
||||
case .Minus:
|
||||
case .Minus, .Ampersand:
|
||||
return 20, true
|
||||
}
|
||||
return 0, false
|
||||
@@ -293,7 +629,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
operand := parse_expression_bp(parser, right_power, nesting+1)
|
||||
operand_expr := parser.module.exprs[operand]
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=.Negate,
|
||||
kind=.Address if operator.kind == .Ampersand else .Negate,
|
||||
span=span_from(operator.span, operand_expr.span),
|
||||
left=operand,
|
||||
right=ast.INVALID_EXPR,
|
||||
@@ -306,17 +642,99 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
skip_newlines(parser)
|
||||
}
|
||||
for {
|
||||
if current(parser).kind == .Caret || current(parser).kind == .Question {
|
||||
operator := advance(parser)
|
||||
left_expr := parser.module.exprs[left]
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=.Deref if operator.kind == .Caret else .Unwrap,
|
||||
span=span_from(left_expr.span, operator.span),
|
||||
left=left,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if current(parser).kind == .Dot {
|
||||
advance(parser)
|
||||
field := current(parser)
|
||||
if field.kind != .Identifier {
|
||||
left = invalid_expr(parser, field.span, "expected a field name after '.'")
|
||||
continue
|
||||
}
|
||||
advance(parser)
|
||||
left_expr := parser.module.exprs[left]
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=.Field,
|
||||
span=span_from(left_expr.span, field.span),
|
||||
name=field.symbol,
|
||||
left=left,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if current(parser).kind == .Left_Bracket {
|
||||
start_token := advance(parser)
|
||||
parser.delimiter_depth += 1
|
||||
skip_newlines(parser)
|
||||
start_expr := ast.INVALID_EXPR
|
||||
end_expr := ast.INVALID_EXPR
|
||||
slicing := false
|
||||
if _, ok := allow(parser, .Range); ok {
|
||||
slicing = true
|
||||
} else {
|
||||
start_expr = parse_expression_bp(parser, 0, nesting+1)
|
||||
skip_newlines(parser)
|
||||
if _, ok := allow(parser, .Range); ok {
|
||||
slicing = true
|
||||
}
|
||||
}
|
||||
skip_newlines(parser)
|
||||
if slicing && current(parser).kind != .Right_Bracket {
|
||||
end_expr = parse_expression_bp(parser, 0, nesting+1)
|
||||
skip_newlines(parser)
|
||||
}
|
||||
end_token, ok := allow(parser, .Right_Bracket)
|
||||
if !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected ']' after index or slice")
|
||||
end_token = start_token
|
||||
}
|
||||
parser.delimiter_depth -= 1
|
||||
left_expr := parser.module.exprs[left]
|
||||
if slicing {
|
||||
args := make([]ast.Expr_Id, 2, parser.module.allocator)
|
||||
args[0] = start_expr
|
||||
args[1] = end_expr
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=.Slice,
|
||||
span=span_from(left_expr.span, end_token.span),
|
||||
args=args,
|
||||
left=left,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=.Index,
|
||||
span=span_from(left_expr.span, end_token.span),
|
||||
left=left,
|
||||
right=start_expr,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
left_power, right_power, ok := infix_binding_power(current(parser).kind)
|
||||
if !ok || left_power < minimum_binding_power {
|
||||
break
|
||||
}
|
||||
advance(parser)
|
||||
operator := advance(parser)
|
||||
skip_newlines(parser)
|
||||
right := parse_expression_bp(parser, right_power, nesting+1)
|
||||
left_expr := parser.module.exprs[left]
|
||||
right_expr := parser.module.exprs[right]
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=.Add,
|
||||
kind=.Orelse if operator.kind == .Keyword_Orelse else .Add,
|
||||
span=span_from(left_expr.span, right_expr.span),
|
||||
left=left,
|
||||
right=right,
|
||||
@@ -381,6 +799,31 @@ parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
return id
|
||||
}
|
||||
|
||||
starts_declared_type :: proc(parser: ^Parser) -> bool {
|
||||
if current(parser).kind != .Left_Bracket {
|
||||
return is_type_token(current(parser).kind)
|
||||
}
|
||||
depth := 0
|
||||
cursor := parser.cursor
|
||||
for cursor < len(parser.tokens.items) {
|
||||
kind := parser.tokens.items[cursor].kind
|
||||
if kind == .Left_Bracket {
|
||||
depth += 1
|
||||
} else if kind == .Right_Bracket {
|
||||
depth -= 1
|
||||
if depth == 0 {
|
||||
cursor += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
cursor += 1
|
||||
}
|
||||
if cursor < len(parser.tokens.items) && parser.tokens.items[cursor].kind == .Keyword_Mut {
|
||||
cursor += 1
|
||||
}
|
||||
return cursor < len(parser.tokens.items) && is_type_token(parser.tokens.items[cursor].kind)
|
||||
}
|
||||
|
||||
parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
if current(parser).kind == .Keyword_Return {
|
||||
return parse_return(parser)
|
||||
@@ -389,9 +832,9 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
|
||||
start_cursor := parser.cursor
|
||||
name := advance(parser)
|
||||
type_syntax := ast.Type_Syntax.Invalid
|
||||
type_syntax := types.INVALID
|
||||
had_type := false
|
||||
if is_type_token(current(parser).kind) {
|
||||
if starts_declared_type(parser) {
|
||||
type_syntax = parse_type(parser)
|
||||
had_type = true
|
||||
}
|
||||
@@ -413,6 +856,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
name=name.symbol,
|
||||
type=type_syntax,
|
||||
immutable=immutable,
|
||||
target=ast.INVALID_EXPR,
|
||||
expr=expr,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
@@ -422,6 +866,19 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
}
|
||||
|
||||
expr := parse_expression(parser)
|
||||
if _, ok := allow(parser, .Equal); ok {
|
||||
skip_newlines(parser)
|
||||
value := parse_expression(parser)
|
||||
id := ast.stmt_id(len(parser.module.statements))
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Assignment,
|
||||
span=span_from(parser.module.exprs[expr].span, parser.module.exprs[value].span),
|
||||
target=expr,
|
||||
expr=value,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return id
|
||||
}
|
||||
id := ast.stmt_id(len(parser.module.statements))
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Expression,
|
||||
@@ -541,6 +998,57 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||
})
|
||||
}
|
||||
|
||||
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool) {
|
||||
start := advance(parser)
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
|
||||
ended_by_newline := current(parser).kind == .Newline
|
||||
skip_newlines(parser)
|
||||
if current(parser).kind != .Left_Brace {
|
||||
if !c_layout {
|
||||
source.add(parser.diagnostics, start.span, "native struct declarations require a body")
|
||||
}
|
||||
if !types.define_struct(&parser.module.type_store, id, nil, c_layout, true) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
}
|
||||
if !ended_by_newline {
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
return
|
||||
}
|
||||
advance(parser)
|
||||
fields: [dynamic]types.Field
|
||||
fields.allocator = parser.module.allocator
|
||||
defer delete(fields)
|
||||
skip_newlines(parser)
|
||||
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
||||
if current(parser).kind != .Identifier {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
|
||||
for current(parser).kind != .Newline &&
|
||||
current(parser).kind != .Right_Brace &&
|
||||
current(parser).kind != .Eof {
|
||||
advance(parser)
|
||||
}
|
||||
skip_newlines(parser)
|
||||
continue
|
||||
}
|
||||
field_name := advance(parser)
|
||||
field_type := parse_type(parser)
|
||||
append(&fields, types.Field{name=u32(field_name.symbol), type=field_type})
|
||||
if _, ok := allow(parser, .Comma); ok {
|
||||
skip_newlines(parser)
|
||||
continue
|
||||
}
|
||||
_ = finish_statement(parser, true)
|
||||
}
|
||||
if _, ok := allow(parser, .Right_Brace); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
|
||||
}
|
||||
if !types.define_struct(&parser.module.type_store, id, fields[:], c_layout, false) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
}
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
|
||||
decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
|
||||
text := token_text(parser, tok)
|
||||
if len(text) < 2 {
|
||||
@@ -553,6 +1061,13 @@ decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
|
||||
if value == '\\' && index+1 < len(text)-1 {
|
||||
index += 1
|
||||
value = text[index]
|
||||
switch value {
|
||||
case 'n': value = '\n'
|
||||
case 'r': value = '\r'
|
||||
case 't': value = '\t'
|
||||
case '0': value = 0
|
||||
case:
|
||||
}
|
||||
}
|
||||
strings.write_byte(&builder, value)
|
||||
}
|
||||
@@ -621,7 +1136,7 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
}
|
||||
parser.cursor = saved
|
||||
}
|
||||
type_syntax := ast.Type_Syntax.Invalid
|
||||
type_syntax := types.INVALID
|
||||
if is_type_token(current(parser).kind) {
|
||||
type_syntax = parse_type(parser)
|
||||
}
|
||||
@@ -634,23 +1149,17 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
advance(parser)
|
||||
skip_newlines(parser)
|
||||
|
||||
c_abi := false
|
||||
if operator.kind == .Colon_Colon &&
|
||||
current(parser).kind == .Identifier &&
|
||||
token_text(parser, current(parser)) == "c" {
|
||||
saved := parser.cursor
|
||||
c_abi = true
|
||||
advance(parser)
|
||||
skip_newlines(parser)
|
||||
if current(parser).kind != .Keyword_Func {
|
||||
c_abi = false
|
||||
parser.cursor = saved
|
||||
}
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Func {
|
||||
(current(parser).kind == .Keyword_Func || current(parser).kind == .Keyword_C_Func) {
|
||||
c_abi := current(parser).kind == .Keyword_C_Func
|
||||
parse_function(parser, name, c_abi)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon &&
|
||||
(current(parser).kind == .Keyword_Struct || current(parser).kind == .Keyword_C_Struct) {
|
||||
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct)
|
||||
return
|
||||
}
|
||||
|
||||
expr := parse_expression(parser)
|
||||
_ = ast.global_id(len(parser.module.globals))
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package target
|
||||
|
||||
Kind :: enum u8 {
|
||||
Aarch64_Macos,
|
||||
}
|
||||
|
||||
Target :: struct {
|
||||
kind: Kind,
|
||||
}
|
||||
|
||||
C_Primitive :: enum u8 {
|
||||
Char,
|
||||
Schar,
|
||||
Uchar,
|
||||
Short,
|
||||
Ushort,
|
||||
Int,
|
||||
Uint,
|
||||
Long,
|
||||
Ulong,
|
||||
Longlong,
|
||||
Ulonglong,
|
||||
Float,
|
||||
Double,
|
||||
Longdouble,
|
||||
}
|
||||
|
||||
Scalar_Kind :: enum u8 {
|
||||
Signed_Integer,
|
||||
Unsigned_Integer,
|
||||
Float,
|
||||
}
|
||||
|
||||
Scalar_Layout :: struct {
|
||||
bits: int,
|
||||
alignment: int,
|
||||
kind: Scalar_Kind,
|
||||
}
|
||||
|
||||
Integer_Extension :: enum u8 {
|
||||
None,
|
||||
Sign,
|
||||
Zero,
|
||||
}
|
||||
|
||||
DEFAULT :: Target{kind=.Aarch64_Macos}
|
||||
|
||||
parse :: proc(value: string) -> (Target, bool) {
|
||||
switch value {
|
||||
case "aarch64-macos", "arm64-macos":
|
||||
return DEFAULT, true
|
||||
case:
|
||||
return {}, false
|
||||
}
|
||||
}
|
||||
|
||||
name :: proc(value: Target) -> string {
|
||||
switch value.kind {
|
||||
case .Aarch64_Macos:
|
||||
return "aarch64-macos"
|
||||
}
|
||||
return "<unsupported>"
|
||||
}
|
||||
|
||||
pointer_bits :: proc(value: Target) -> int {
|
||||
switch value.kind {
|
||||
case .Aarch64_Macos:
|
||||
return 64
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
llvm_triple :: proc(value: Target) -> string {
|
||||
switch value.kind {
|
||||
case .Aarch64_Macos:
|
||||
return "arm64-apple-macosx13.0.0"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
llvm_data_layout :: proc(value: Target) -> string {
|
||||
switch value.kind {
|
||||
case .Aarch64_Macos:
|
||||
return "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
c_primitive_layout :: proc(value: Target, primitive: C_Primitive) -> Scalar_Layout {
|
||||
switch value.kind {
|
||||
case .Aarch64_Macos:
|
||||
switch primitive {
|
||||
case .Char, .Schar:
|
||||
return Scalar_Layout{bits=8, alignment=1, kind=.Signed_Integer}
|
||||
case .Uchar:
|
||||
return Scalar_Layout{bits=8, alignment=1, kind=.Unsigned_Integer}
|
||||
case .Short:
|
||||
return Scalar_Layout{bits=16, alignment=2, kind=.Signed_Integer}
|
||||
case .Ushort:
|
||||
return Scalar_Layout{bits=16, alignment=2, kind=.Unsigned_Integer}
|
||||
case .Int:
|
||||
return Scalar_Layout{bits=32, alignment=4, kind=.Signed_Integer}
|
||||
case .Uint:
|
||||
return Scalar_Layout{bits=32, alignment=4, kind=.Unsigned_Integer}
|
||||
case .Long, .Longlong:
|
||||
return Scalar_Layout{bits=64, alignment=8, kind=.Signed_Integer}
|
||||
case .Ulong, .Ulonglong:
|
||||
return Scalar_Layout{bits=64, alignment=8, kind=.Unsigned_Integer}
|
||||
case .Float:
|
||||
return Scalar_Layout{bits=32, alignment=4, kind=.Float}
|
||||
case .Double, .Longdouble:
|
||||
return Scalar_Layout{bits=64, alignment=8, kind=.Float}
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
c_integer_extension :: proc(value: Target, bits: int, signed: bool) -> Integer_Extension {
|
||||
switch value.kind {
|
||||
case .Aarch64_Macos:
|
||||
if bits < 32 {
|
||||
return .Sign if signed else .Zero
|
||||
}
|
||||
}
|
||||
return .None
|
||||
}
|
||||
@@ -9,27 +9,66 @@ Kind :: enum u8 {
|
||||
Newline,
|
||||
Identifier,
|
||||
Integer,
|
||||
Float,
|
||||
String,
|
||||
Character,
|
||||
Underscore,
|
||||
Colon_Colon,
|
||||
Equal,
|
||||
Plus,
|
||||
Minus,
|
||||
Dot,
|
||||
Range,
|
||||
At,
|
||||
Star,
|
||||
Ampersand,
|
||||
Caret,
|
||||
Question,
|
||||
Semicolon,
|
||||
Left_Bracket,
|
||||
Right_Bracket,
|
||||
Left_Paren,
|
||||
Right_Paren,
|
||||
Left_Brace,
|
||||
Right_Brace,
|
||||
Comma,
|
||||
Keyword_Func,
|
||||
Keyword_C_Func,
|
||||
Keyword_Struct,
|
||||
Keyword_C_Struct,
|
||||
Keyword_Import,
|
||||
Keyword_Return,
|
||||
Keyword_Mut,
|
||||
Keyword_None,
|
||||
Keyword_Orelse,
|
||||
Keyword_Void,
|
||||
Keyword_Int,
|
||||
Keyword_I8,
|
||||
Keyword_I16,
|
||||
Keyword_I32,
|
||||
Keyword_I64,
|
||||
Keyword_U8,
|
||||
Keyword_U16,
|
||||
Keyword_U32,
|
||||
Keyword_U64,
|
||||
Keyword_Isize,
|
||||
Keyword_Usize,
|
||||
Keyword_F32,
|
||||
Keyword_F64,
|
||||
Keyword_C_Char,
|
||||
Keyword_C_Schar,
|
||||
Keyword_C_Uchar,
|
||||
Keyword_C_Short,
|
||||
Keyword_C_Ushort,
|
||||
Keyword_C_Int,
|
||||
Keyword_C_Uint,
|
||||
Keyword_C_Long,
|
||||
Keyword_C_Ulong,
|
||||
Keyword_C_Longlong,
|
||||
Keyword_C_Ulonglong,
|
||||
Keyword_C_Float,
|
||||
Keyword_C_Double,
|
||||
Keyword_C_Longdouble,
|
||||
}
|
||||
|
||||
Token :: struct {
|
||||
|
||||
+602
-72
@@ -1,94 +1,619 @@
|
||||
package types
|
||||
|
||||
import "../target"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
|
||||
Numeric_Category :: enum {
|
||||
// Type is a compact ID. Builtin scalar types occupy the stable low range;
|
||||
// recursive and nominal types are interned in Store starting at DYNAMIC_START.
|
||||
Type :: distinct u32
|
||||
|
||||
INVALID :: Type(0)
|
||||
VOID :: Type(1)
|
||||
INT :: Type(2)
|
||||
|
||||
I8 :: Type(3)
|
||||
I16 :: Type(4)
|
||||
I32 :: Type(5)
|
||||
I64 :: Type(6)
|
||||
U8 :: Type(7)
|
||||
U16 :: Type(8)
|
||||
U32 :: Type(9)
|
||||
U64 :: Type(10)
|
||||
ISIZE :: Type(11)
|
||||
USIZE :: Type(12)
|
||||
F32 :: Type(13)
|
||||
F64 :: Type(14)
|
||||
|
||||
C_CHAR :: Type(15)
|
||||
C_SCHAR :: Type(16)
|
||||
C_UCHAR :: Type(17)
|
||||
C_SHORT :: Type(18)
|
||||
C_USHORT :: Type(19)
|
||||
C_INT :: Type(20)
|
||||
C_UINT :: Type(21)
|
||||
C_LONG :: Type(22)
|
||||
C_ULONG :: Type(23)
|
||||
C_LONGLONG :: Type(24)
|
||||
C_ULONGLONG :: Type(25)
|
||||
C_FLOAT :: Type(26)
|
||||
C_DOUBLE :: Type(27)
|
||||
C_LONGDOUBLE :: Type(28)
|
||||
|
||||
DYNAMIC_START :: Type(64)
|
||||
|
||||
Numeric_Category :: enum u8 {
|
||||
None,
|
||||
Signed_Integer,
|
||||
Unsigned_Integer,
|
||||
Float,
|
||||
}
|
||||
|
||||
Kind :: enum {
|
||||
Kind :: enum u8 {
|
||||
Invalid,
|
||||
Void,
|
||||
Int_Constraint,
|
||||
Concrete,
|
||||
Scalar,
|
||||
Array,
|
||||
Pointer,
|
||||
Slice,
|
||||
Optional,
|
||||
Named,
|
||||
Struct,
|
||||
}
|
||||
|
||||
Type :: struct {
|
||||
Node :: struct {
|
||||
kind: Kind,
|
||||
category: Numeric_Category,
|
||||
bits: int,
|
||||
child: Type,
|
||||
count: u64,
|
||||
sentinel: u64,
|
||||
field_start: u32,
|
||||
field_count: u32,
|
||||
pkg: u32,
|
||||
name: u32,
|
||||
qualifier: u32,
|
||||
file: u32,
|
||||
mutable: bool,
|
||||
many: bool,
|
||||
has_sentinel: bool,
|
||||
inferred_count: bool,
|
||||
c_layout: bool,
|
||||
opaque: bool,
|
||||
declared: bool,
|
||||
}
|
||||
|
||||
INVALID :: Type {
|
||||
kind = .Invalid,
|
||||
Field :: struct {
|
||||
name: u32,
|
||||
type: Type,
|
||||
}
|
||||
VOID :: Type {
|
||||
kind = .Void,
|
||||
|
||||
Store :: struct {
|
||||
nodes: [dynamic]Node,
|
||||
fields: [dynamic]Field,
|
||||
selected: target.Target,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
INT :: Type {
|
||||
kind = .Int_Constraint,
|
||||
category = .Signed_Integer,
|
||||
|
||||
init_store :: proc(allocator := context.allocator) -> Store {
|
||||
store: Store
|
||||
store.nodes.allocator = allocator
|
||||
store.fields.allocator = allocator
|
||||
store.selected = target.DEFAULT
|
||||
store.allocator = allocator
|
||||
return store
|
||||
}
|
||||
I8 :: Type {
|
||||
kind = .Concrete,
|
||||
category = .Signed_Integer,
|
||||
bits = 8,
|
||||
|
||||
destroy_store :: proc(store: ^Store) {
|
||||
delete(store.nodes)
|
||||
delete(store.fields)
|
||||
}
|
||||
I16 :: Type {
|
||||
kind = .Concrete,
|
||||
category = .Signed_Integer,
|
||||
bits = 16,
|
||||
|
||||
clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
|
||||
store := init_store(allocator)
|
||||
append(&store.nodes, ..source.nodes[:])
|
||||
append(&store.fields, ..source.fields[:])
|
||||
store.selected = source.selected
|
||||
return store
|
||||
}
|
||||
I32 :: Type {
|
||||
kind = .Concrete,
|
||||
category = .Signed_Integer,
|
||||
bits = 32,
|
||||
|
||||
intern :: proc(store: ^Store, candidate: Node) -> Type {
|
||||
if candidate.kind != .Struct && candidate.kind != .Named {
|
||||
for existing, index in store.nodes {
|
||||
if existing == candidate {
|
||||
return DYNAMIC_START+Type(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
id := DYNAMIC_START+Type(len(store.nodes))
|
||||
append(&store.nodes, candidate)
|
||||
return id
|
||||
}
|
||||
I64 :: Type {
|
||||
kind = .Concrete,
|
||||
category = .Signed_Integer,
|
||||
bits = 64,
|
||||
|
||||
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
|
||||
normalized_file := file if qualifier != 0 else u32(0)
|
||||
for existing, index in store.nodes {
|
||||
if (existing.kind == .Named || existing.kind == .Struct) &&
|
||||
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
|
||||
existing.file == normalized_file {
|
||||
return DYNAMIC_START+Type(index)
|
||||
}
|
||||
}
|
||||
return intern(store, Node{kind=.Named, pkg=pkg, name=name, qualifier=qualifier, file=normalized_file})
|
||||
}
|
||||
|
||||
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
|
||||
for existing, index in store.nodes {
|
||||
if (existing.kind == .Named || existing.kind == .Struct) &&
|
||||
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
|
||||
return DYNAMIC_START+Type(index)
|
||||
}
|
||||
}
|
||||
return INVALID
|
||||
}
|
||||
|
||||
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
|
||||
existing, ok := node(store, id)
|
||||
if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared {
|
||||
return false
|
||||
}
|
||||
index := int(id-DYNAMIC_START)
|
||||
store.nodes[index].kind = .Struct
|
||||
store.nodes[index].c_layout = c_layout
|
||||
store.nodes[index].opaque = opaque
|
||||
store.nodes[index].declared = true
|
||||
store.nodes[index].field_start = u32(len(store.fields))
|
||||
store.nodes[index].field_count = u32(len(fields))
|
||||
append(&store.fields, ..fields)
|
||||
return true
|
||||
}
|
||||
|
||||
fields_for :: proc(store: ^Store, value: Type) -> []Field {
|
||||
item, ok := node(store, value)
|
||||
if !ok || item.kind != .Struct {
|
||||
return nil
|
||||
}
|
||||
start := int(item.field_start)
|
||||
end := start+int(item.field_count)
|
||||
if start < 0 || end > len(store.fields) {
|
||||
return nil
|
||||
}
|
||||
return store.fields[start:end]
|
||||
}
|
||||
|
||||
kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
||||
switch value {
|
||||
case INVALID:
|
||||
return .Invalid
|
||||
case VOID:
|
||||
return .Void
|
||||
case INT:
|
||||
return .Int_Constraint
|
||||
}
|
||||
if value >= I8 && value <= C_LONGDOUBLE {
|
||||
return .Scalar
|
||||
}
|
||||
if store != nil && value >= DYNAMIC_START {
|
||||
index := int(value-DYNAMIC_START)
|
||||
if index >= 0 && index < len(store.nodes) {
|
||||
return store.nodes[index].kind
|
||||
}
|
||||
}
|
||||
return .Invalid
|
||||
}
|
||||
|
||||
node :: proc(store: ^Store, value: Type) -> (Node, bool) {
|
||||
if store == nil || value < DYNAMIC_START {
|
||||
return {}, false
|
||||
}
|
||||
index := int(value-DYNAMIC_START)
|
||||
if index < 0 || index >= len(store.nodes) {
|
||||
return {}, false
|
||||
}
|
||||
return store.nodes[index], true
|
||||
}
|
||||
|
||||
is_valid :: proc(value: Type) -> bool {
|
||||
return value.kind != .Invalid
|
||||
return value != INVALID
|
||||
}
|
||||
|
||||
is_void :: proc(value: Type) -> bool {
|
||||
return value == VOID
|
||||
}
|
||||
|
||||
is_constraint :: proc(value: Type) -> bool {
|
||||
return value == INT
|
||||
}
|
||||
|
||||
is_c :: proc(value: Type) -> bool {
|
||||
return value >= C_CHAR && value <= C_LONGDOUBLE
|
||||
}
|
||||
|
||||
as_c_primitive :: proc(value: Type) -> (target.C_Primitive, bool) {
|
||||
switch value {
|
||||
case C_CHAR: return .Char, true
|
||||
case C_SCHAR: return .Schar, true
|
||||
case C_UCHAR: return .Uchar, true
|
||||
case C_SHORT: return .Short, true
|
||||
case C_USHORT: return .Ushort, true
|
||||
case C_INT: return .Int, true
|
||||
case C_UINT: return .Uint, true
|
||||
case C_LONG: return .Long, true
|
||||
case C_ULONG: return .Ulong, true
|
||||
case C_LONGLONG: return .Longlong, true
|
||||
case C_ULONGLONG: return .Ulonglong, true
|
||||
case C_FLOAT: return .Float, true
|
||||
case C_DOUBLE: return .Double, true
|
||||
case C_LONGDOUBLE: return .Longdouble, true
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
|
||||
category :: proc(value: Type, selected := target.DEFAULT) -> Numeric_Category {
|
||||
switch value {
|
||||
case I8, I16, I32, I64, ISIZE:
|
||||
return .Signed_Integer
|
||||
case U8, U16, U32, U64, USIZE:
|
||||
return .Unsigned_Integer
|
||||
case F32, F64:
|
||||
return .Float
|
||||
case:
|
||||
primitive, ok := as_c_primitive(value)
|
||||
if !ok {
|
||||
return .None
|
||||
}
|
||||
switch target.c_primitive_layout(selected, primitive).kind {
|
||||
case .Signed_Integer: return .Signed_Integer
|
||||
case .Unsigned_Integer: return .Unsigned_Integer
|
||||
case .Float: return .Float
|
||||
}
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
bits :: proc(value: Type, selected := target.DEFAULT) -> int {
|
||||
switch value {
|
||||
case I8, U8:
|
||||
return 8
|
||||
case I16, U16:
|
||||
return 16
|
||||
case I32, U32, F32:
|
||||
return 32
|
||||
case I64, U64, F64:
|
||||
return 64
|
||||
case ISIZE, USIZE:
|
||||
return target.pointer_bits(selected)
|
||||
case:
|
||||
primitive, ok := as_c_primitive(value)
|
||||
return target.c_primitive_layout(selected, primitive).bits if ok else 0
|
||||
}
|
||||
}
|
||||
|
||||
alignment :: proc(value: Type, selected := target.DEFAULT) -> int {
|
||||
if primitive, ok := as_c_primitive(value); ok {
|
||||
return target.c_primitive_layout(selected, primitive).alignment
|
||||
}
|
||||
width := bits(value, selected)/8
|
||||
return min(max(width, 1), 8)
|
||||
}
|
||||
|
||||
representation :: proc(value: Type, selected := target.DEFAULT) -> Type {
|
||||
if value == ISIZE {
|
||||
return I64
|
||||
}
|
||||
if value == USIZE {
|
||||
return U64
|
||||
}
|
||||
primitive, ok := as_c_primitive(value)
|
||||
if !ok {
|
||||
return value
|
||||
}
|
||||
layout := target.c_primitive_layout(selected, primitive)
|
||||
if layout.kind == .Float {
|
||||
return F32 if layout.bits == 32 else F64
|
||||
}
|
||||
if layout.kind == .Signed_Integer {
|
||||
switch layout.bits {
|
||||
case 8: return I8
|
||||
case 16: return I16
|
||||
case 32: return I32
|
||||
case: return I64
|
||||
}
|
||||
}
|
||||
switch layout.bits {
|
||||
case 8: return U8
|
||||
case 16: return U16
|
||||
case 32: return U32
|
||||
case: return U64
|
||||
}
|
||||
}
|
||||
|
||||
is_concrete_scalar :: proc(value: Type) -> bool {
|
||||
return kind(value) == .Scalar
|
||||
}
|
||||
|
||||
is_concrete :: proc(value: Type, store: ^Store = nil) -> bool {
|
||||
value_kind := kind(value, store)
|
||||
if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer ||
|
||||
value_kind == .Slice || value_kind == .Optional {
|
||||
return true
|
||||
}
|
||||
if value_kind == .Struct {
|
||||
item, ok := node(store, value)
|
||||
return ok && item.declared
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
is_pointer :: proc(value: Type, store: ^Store) -> bool {
|
||||
return kind(value, store) == .Pointer
|
||||
}
|
||||
|
||||
is_array :: proc(value: Type, store: ^Store) -> bool {
|
||||
return kind(value, store) == .Array
|
||||
}
|
||||
|
||||
is_slice :: proc(value: Type, store: ^Store) -> bool {
|
||||
return kind(value, store) == .Slice
|
||||
}
|
||||
|
||||
is_optional :: proc(value: Type, store: ^Store) -> bool {
|
||||
return kind(value, store) == .Optional
|
||||
}
|
||||
|
||||
is_struct :: proc(value: Type, store: ^Store) -> bool {
|
||||
return kind(value, store) == .Struct
|
||||
}
|
||||
|
||||
is_optional_pointer :: proc(value: Type, store: ^Store) -> bool {
|
||||
item, ok := node(store, value)
|
||||
return ok && item.kind == .Optional && is_pointer(item.child, store)
|
||||
}
|
||||
|
||||
is_runtime_value :: proc(value: Type, store: ^Store) -> bool {
|
||||
value_kind := kind(value, store)
|
||||
if value_kind == .Scalar || value_kind == .Pointer {
|
||||
return true
|
||||
}
|
||||
if value_kind == .Slice || value_kind == .Array || value_kind == .Optional {
|
||||
return !contains_c_struct_by_value(value, store)
|
||||
}
|
||||
if value_kind == .Struct {
|
||||
item, ok := node(store, value)
|
||||
return ok && item.declared && !item.opaque && !contains_c_struct_by_value(value, store)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
|
||||
if depth > 256 {
|
||||
return true
|
||||
}
|
||||
item, ok := node(store, value)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if item.kind == .Pointer {
|
||||
return false
|
||||
}
|
||||
if item.kind == .Struct {
|
||||
if item.c_layout {
|
||||
return true
|
||||
}
|
||||
for field in fields_for(store, value) {
|
||||
if contains_c_struct_by_value(field.type, store, depth+1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if item.kind == .Array || item.kind == .Slice || item.kind == .Optional {
|
||||
return contains_c_struct_by_value(item.child, store, depth+1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) -> bool {
|
||||
if allow_void && is_void(value) {
|
||||
return true
|
||||
}
|
||||
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store)
|
||||
}
|
||||
|
||||
child_type :: proc(value: Type, store: ^Store) -> Type {
|
||||
item, ok := node(store, value)
|
||||
return item.child if ok else INVALID
|
||||
}
|
||||
|
||||
logical_count :: proc(value: Type, store: ^Store) -> u64 {
|
||||
item, ok := node(store, value)
|
||||
return item.count if ok else 0
|
||||
}
|
||||
|
||||
physical_count :: proc(value: Type, store: ^Store) -> u64 {
|
||||
item, ok := node(store, value)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return item.count + (u64(1) if item.has_sentinel else u64(0))
|
||||
}
|
||||
|
||||
is_mutable :: proc(value: Type, store: ^Store) -> bool {
|
||||
item, ok := node(store, value)
|
||||
return ok && item.mutable
|
||||
}
|
||||
|
||||
is_many_pointer :: proc(value: Type, store: ^Store) -> bool {
|
||||
item, ok := node(store, value)
|
||||
return ok && item.kind == .Pointer && item.many
|
||||
}
|
||||
|
||||
is_c_struct :: proc(value: Type, store: ^Store) -> bool {
|
||||
item, ok := node(store, value)
|
||||
return ok && item.kind == .Struct && item.c_layout
|
||||
}
|
||||
|
||||
pointer :: proc(store: ^Store, child: Type, mutable, many: bool) -> Type {
|
||||
return intern(store, Node{kind=.Pointer, child=child, mutable=mutable, many=many})
|
||||
}
|
||||
|
||||
slice :: proc(store: ^Store, child: Type, mutable: bool, has_sentinel := false, sentinel: u64 = 0) -> Type {
|
||||
return intern(store, Node{
|
||||
kind=.Slice,
|
||||
child=child,
|
||||
mutable=mutable,
|
||||
has_sentinel=has_sentinel,
|
||||
sentinel=sentinel,
|
||||
})
|
||||
}
|
||||
|
||||
array :: proc(
|
||||
store: ^Store,
|
||||
child: Type,
|
||||
count: u64,
|
||||
mutable: bool,
|
||||
has_sentinel := false,
|
||||
sentinel: u64 = 0,
|
||||
) -> Type {
|
||||
return intern(store, Node{
|
||||
kind=.Array,
|
||||
child=child,
|
||||
count=count,
|
||||
mutable=mutable,
|
||||
has_sentinel=has_sentinel,
|
||||
sentinel=sentinel,
|
||||
})
|
||||
}
|
||||
|
||||
optional :: proc(store: ^Store, child: Type) -> Type {
|
||||
return intern(store, Node{kind=.Optional, child=child})
|
||||
}
|
||||
|
||||
with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type {
|
||||
item, ok := node(store, value)
|
||||
if !ok || item.kind != .Array {
|
||||
return value
|
||||
}
|
||||
item.count = count
|
||||
item.inferred_count = false
|
||||
return intern(store, item)
|
||||
}
|
||||
|
||||
can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool {
|
||||
from_node, from_ok := node(store, from)
|
||||
to_node, to_ok := node(store, to)
|
||||
return from_ok && to_ok &&
|
||||
from_node.kind == .Pointer && to_node.kind == .Pointer &&
|
||||
from_node.child == to_node.child && from_node.many == to_node.many &&
|
||||
from_node.mutable && !to_node.mutable
|
||||
}
|
||||
|
||||
is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {
|
||||
item, ok := node(store, value)
|
||||
return ok && item.kind == .Struct && item.opaque
|
||||
}
|
||||
|
||||
size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
||||
#partial switch kind(value, store) {
|
||||
case .Scalar:
|
||||
return u64(bits(value, selected)/8)
|
||||
case .Pointer:
|
||||
return u64(target.pointer_bits(selected)/8)
|
||||
case .Slice:
|
||||
return u64(target.pointer_bits(selected)/8*2)
|
||||
case .Array:
|
||||
item, _ := node(store, value)
|
||||
return physical_count(value, store)*size(item.child, store, selected)
|
||||
case .Optional:
|
||||
item, _ := node(store, value)
|
||||
if is_pointer(item.child, store) {
|
||||
return u64(target.pointer_bits(selected)/8)
|
||||
}
|
||||
child_size := size(item.child, store, selected)
|
||||
child_align := u64(alignment_of(item.child, store, selected))
|
||||
return ((child_size+1+child_align-1)/child_align)*child_align
|
||||
case .Struct:
|
||||
offset: u64
|
||||
max_align: u64 = 1
|
||||
for field in fields_for(store, value) {
|
||||
field_align := u64(alignment_of(field.type, store, selected))
|
||||
offset = (offset+field_align-1)/field_align*field_align
|
||||
offset += size(field.type, store, selected)
|
||||
max_align = max(max_align, field_align)
|
||||
}
|
||||
return (offset+max_align-1)/max_align*max_align
|
||||
case:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> int {
|
||||
#partial switch kind(value, store) {
|
||||
case .Scalar:
|
||||
return alignment(value, selected)
|
||||
case .Pointer, .Slice:
|
||||
return target.pointer_bits(selected)/8
|
||||
case .Array, .Optional:
|
||||
return alignment_of(child_type(value, store), store, selected)
|
||||
case .Struct:
|
||||
result := 1
|
||||
for field in fields_for(store, value) {
|
||||
result = max(result, alignment_of(field.type, store, selected))
|
||||
}
|
||||
return result
|
||||
case:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
is_concrete_integer :: proc(value: Type) -> bool {
|
||||
return(
|
||||
value.kind == .Concrete &&
|
||||
(value.category == .Signed_Integer || value.category == .Unsigned_Integer) \
|
||||
)
|
||||
category := category(value)
|
||||
return kind(value) == .Scalar &&
|
||||
(category == .Signed_Integer || category == .Unsigned_Integer)
|
||||
}
|
||||
|
||||
is_signed :: proc(value: Type) -> bool {
|
||||
return value.kind == .Concrete && value.category == .Signed_Integer
|
||||
is_float :: proc(value: Type, selected := target.DEFAULT) -> bool {
|
||||
return kind(value) == .Scalar && category(value, selected) == .Float
|
||||
}
|
||||
|
||||
is_signed :: proc(value: Type, selected := target.DEFAULT) -> bool {
|
||||
return kind(value) == .Scalar && category(value, selected) == .Signed_Integer
|
||||
}
|
||||
|
||||
is_unsigned :: proc(value: Type, selected := target.DEFAULT) -> bool {
|
||||
return kind(value) == .Scalar && category(value, selected) == .Unsigned_Integer
|
||||
}
|
||||
|
||||
equal :: proc(a, b: Type) -> bool {
|
||||
return a.kind == b.kind && a.category == b.category && a.bits == b.bits
|
||||
return a == b
|
||||
}
|
||||
|
||||
same_numeric_family :: proc(a, b: Type) -> bool {
|
||||
if category(a) != category(b) {
|
||||
return false
|
||||
}
|
||||
// C primitives are intentionally distinct semantic types. Exact-width
|
||||
// Brolang scalars may widen only to other Brolang scalars.
|
||||
return !is_c(a) && !is_c(b)
|
||||
}
|
||||
|
||||
can_widen :: proc(from, to: Type) -> bool {
|
||||
if equal(from, to) {
|
||||
return true
|
||||
}
|
||||
return(
|
||||
from.kind == .Concrete &&
|
||||
to.kind == .Concrete &&
|
||||
from.category == to.category &&
|
||||
from.bits < to.bits \
|
||||
)
|
||||
return is_concrete_scalar(from) &&
|
||||
is_concrete_scalar(to) &&
|
||||
same_numeric_family(from, to) &&
|
||||
bits(from) < bits(to)
|
||||
}
|
||||
|
||||
widest :: proc(a, b: Type) -> Type {
|
||||
if a.kind != .Concrete || b.kind != .Concrete || a.category != b.category {
|
||||
if equal(a, b) && is_concrete_scalar(a) {
|
||||
return a
|
||||
}
|
||||
if !is_concrete_scalar(a) || !is_concrete_scalar(b) || !same_numeric_family(a, b) {
|
||||
return INVALID
|
||||
}
|
||||
if a.bits >= b.bits {
|
||||
if bits(a) >= bits(b) {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
@@ -108,32 +633,37 @@ smallest_signed_for_literal :: proc(value: i64) -> Type {
|
||||
}
|
||||
|
||||
name :: proc(value: Type) -> string {
|
||||
switch value.kind {
|
||||
case .Invalid:
|
||||
return "<invalid>"
|
||||
case .Void:
|
||||
return "void"
|
||||
case .Int_Constraint:
|
||||
return "int"
|
||||
case .Concrete:
|
||||
switch value.category {
|
||||
case .Signed_Integer:
|
||||
switch value.bits {
|
||||
case 8:
|
||||
return "i8"
|
||||
case 16:
|
||||
return "i16"
|
||||
case 32:
|
||||
return "i32"
|
||||
case 64:
|
||||
return "i64"
|
||||
switch value {
|
||||
case INVALID: return "<invalid>"
|
||||
case VOID: return "void"
|
||||
case INT: return "int"
|
||||
case I8: return "i8"
|
||||
case I16: return "i16"
|
||||
case I32: return "i32"
|
||||
case I64: return "i64"
|
||||
case U8: return "u8"
|
||||
case U16: return "u16"
|
||||
case U32: return "u32"
|
||||
case U64: return "u64"
|
||||
case ISIZE: return "isize"
|
||||
case USIZE: return "usize"
|
||||
case F32: return "f32"
|
||||
case F64: return "f64"
|
||||
case C_CHAR: return "c_char"
|
||||
case C_SCHAR: return "c_schar"
|
||||
case C_UCHAR: return "c_uchar"
|
||||
case C_SHORT: return "c_short"
|
||||
case C_USHORT: return "c_ushort"
|
||||
case C_INT: return "c_int"
|
||||
case C_UINT: return "c_uint"
|
||||
case C_LONG: return "c_long"
|
||||
case C_ULONG: return "c_ulong"
|
||||
case C_LONGLONG: return "c_longlong"
|
||||
case C_ULONGLONG: return "c_ulonglong"
|
||||
case C_FLOAT: return "c_float"
|
||||
case C_DOUBLE: return "c_double"
|
||||
case C_LONGDOUBLE: return "c_longdouble"
|
||||
case:
|
||||
return fmt.tprintf("<type %d>", value)
|
||||
}
|
||||
case .Unsigned_Integer:
|
||||
return fmt.tprintf("u%d", value.bits)
|
||||
case .Float:
|
||||
return fmt.tprintf("f%d", value.bits)
|
||||
case .None:
|
||||
}
|
||||
}
|
||||
return "<invalid>"
|
||||
}
|
||||
|
||||
+159
-22
@@ -14,6 +14,7 @@ import "./compiler/lower"
|
||||
import "./compiler/parser"
|
||||
import "./compiler/source"
|
||||
import "./compiler/symbol"
|
||||
import "./compiler/target"
|
||||
import "./compiler/token"
|
||||
import "./compiler/types"
|
||||
import "core:fmt"
|
||||
@@ -86,9 +87,10 @@ main :: func() void { _ = value }
|
||||
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, size_of(source.Span), 12)
|
||||
testing.expect_value(t, size_of(token.Token), 24)
|
||||
testing.expect_value(t, size_of(ast.Expr), 64)
|
||||
testing.expect_value(t, size_of(hir.Expr), 88)
|
||||
testing.expect_value(t, size_of(ir.Instruction), 88)
|
||||
testing.expect(t, size_of(ast.Expr) <= 64)
|
||||
testing.expect(t, size_of(hir.Expr) <= 88)
|
||||
testing.expect(t, size_of(ir.Instruction) <= 88)
|
||||
testing.expect_value(t, size_of(types.Type), 4)
|
||||
|
||||
source_index, source_ok := source.source_index(source.Source_Id(0), 1)
|
||||
testing.expect_value(t, source_index, 0)
|
||||
@@ -195,8 +197,8 @@ main :: func() void { _ = give() }
|
||||
|
||||
@(test)
|
||||
parser_distinguishes_bodyless_declarations_and_definitions :: proc(t: ^testing.T) {
|
||||
text := `foreign :: c func(value i32) i32
|
||||
defined :: c func(value i32) i32
|
||||
text := `foreign :: c_func(value i32) i32
|
||||
defined :: c_func(value i32) i32
|
||||
{
|
||||
return value
|
||||
}
|
||||
@@ -229,7 +231,7 @@ main :: func() void {}
|
||||
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
|
||||
text := `c :: 5
|
||||
x :: c
|
||||
foreign :: c func() i32
|
||||
foreign :: c_func() i32
|
||||
broken :: c 5
|
||||
main :: func() void {}
|
||||
`
|
||||
@@ -403,6 +405,8 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
|
||||
"thing",
|
||||
"--link",
|
||||
"helper.o",
|
||||
"--target",
|
||||
"aarch64-macos",
|
||||
})
|
||||
defer delete(options.link_arguments)
|
||||
|
||||
@@ -415,15 +419,18 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
|
||||
testing.expect_value(t, options.link_arguments[1].kind, linker.Kind.Library_Path)
|
||||
testing.expect_value(t, options.link_arguments[2].kind, linker.Kind.Library)
|
||||
testing.expect_value(t, options.link_arguments[3].value, "helper.o")
|
||||
testing.expect_value(t, target.name(options.target), "aarch64-macos")
|
||||
|
||||
_, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"})
|
||||
_, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"})
|
||||
_, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"})
|
||||
_, duplicate_empty_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "", "-o", "two"})
|
||||
_, invalid_target := parse_cli_args([]string{"brolang", "app", "-o", "out", "--target", "x86_64-linux"})
|
||||
testing.expect(t, !unknown_valid)
|
||||
testing.expect(t, !incomplete_valid)
|
||||
testing.expect(t, !duplicate_output_valid)
|
||||
testing.expect(t, !duplicate_empty_output_valid)
|
||||
testing.expect(t, !invalid_target)
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -453,7 +460,7 @@ main :: func() void {}
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_rejects_chained_package_access :: proc(t: ^testing.T) {
|
||||
parser_accepts_chained_field_access :: proc(t: ^testing.T) {
|
||||
text := `main :: func() void {
|
||||
_ = first.second.value
|
||||
}
|
||||
@@ -468,7 +475,7 @@ parser_rejects_chained_package_access :: proc(t: ^testing.T) {
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
testing.expect(t, len(diagnostics.items) > 0)
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -566,7 +573,7 @@ main :: func() void {
|
||||
|
||||
@(test)
|
||||
pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) {
|
||||
text := `sum_c :: c func(a, b int) int {
|
||||
text := `sum_c :: c_func(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
sum_bro :: func(a, b int) int {
|
||||
@@ -597,16 +604,16 @@ main :: func() void {
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, llvm_text, second_llvm_text)
|
||||
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p0__sum_c__i8__i8"))
|
||||
testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p0__sum_c__i8__i8"))
|
||||
testing.expect(t, strings.contains(llvm_text, "define internal fastcc i8 @bro__p0__sum_bro__i8__i8"))
|
||||
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
pipeline_emits_only_referenced_foreign_declarations_with_exact_names :: proc(t: ^testing.T) {
|
||||
text := `used :: c func(a, b i32) i32
|
||||
unused :: c func() i32
|
||||
bodyful :: c func(value i32) i32 {
|
||||
text := `used :: c_func(a, b i32) i32
|
||||
unused :: c_func() i32
|
||||
bodyful :: c_func(value i32) i32 {
|
||||
return value
|
||||
}
|
||||
main :: func() void {
|
||||
@@ -637,9 +644,126 @@ main :: func() void {
|
||||
testing.expect(t, strings.contains(llvm_text, "define i32 @bro_c__p0__bodyful__i32"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
c_primitives_remain_distinct_with_apple_silicon_representations :: proc(t: ^testing.T) {
|
||||
testing.expect(t, types.C_CHAR != types.C_SCHAR)
|
||||
testing.expect(t, types.C_SCHAR != types.C_UCHAR)
|
||||
testing.expect(t, types.C_INT != types.I32)
|
||||
testing.expect(t, types.C_ULONG != types.U64)
|
||||
testing.expect_value(t, types.representation(types.C_CHAR), types.I8)
|
||||
testing.expect_value(t, types.representation(types.C_SCHAR), types.I8)
|
||||
testing.expect_value(t, types.representation(types.C_UCHAR), types.U8)
|
||||
testing.expect_value(t, types.representation(types.C_SHORT), types.I16)
|
||||
testing.expect_value(t, types.representation(types.C_USHORT), types.U16)
|
||||
testing.expect_value(t, types.representation(types.C_INT), types.I32)
|
||||
testing.expect_value(t, types.representation(types.C_UINT), types.U32)
|
||||
testing.expect_value(t, types.representation(types.C_LONG), types.I64)
|
||||
testing.expect_value(t, types.representation(types.C_ULONG), types.U64)
|
||||
testing.expect_value(t, types.representation(types.C_LONGLONG), types.I64)
|
||||
testing.expect_value(t, types.representation(types.C_ULONGLONG), types.U64)
|
||||
testing.expect_value(t, types.representation(types.C_FLOAT), types.F32)
|
||||
testing.expect_value(t, types.representation(types.C_DOUBLE), types.F64)
|
||||
testing.expect_value(t, types.representation(types.C_LONGDOUBLE), types.F64)
|
||||
testing.expect_value(t, target.llvm_triple(target.DEFAULT), "arm64-apple-macosx13.0.0")
|
||||
}
|
||||
|
||||
@(test)
|
||||
interop_foundation_emits_compounds_and_narrow_c_abi_attributes :: proc(t: ^testing.T) {
|
||||
text := `Point :: struct {
|
||||
x i32
|
||||
y i32
|
||||
}
|
||||
signed :: c_func(value c_char) c_char
|
||||
unsigned :: c_func(value c_uchar) c_uchar
|
||||
exact :: c_func(value u32) u32
|
||||
fallback :: func() i32 {
|
||||
return 9
|
||||
}
|
||||
main :: func() void {
|
||||
c :: 1
|
||||
values [2;0]mut u8 = [1, 2]
|
||||
point Point :: Point{x = 3, y = 4}
|
||||
maybe ?i32 = 5
|
||||
_ = c
|
||||
_ = values[2]
|
||||
_ = values.ptr + 1
|
||||
_ = values.len
|
||||
_ = values[0..2]
|
||||
_ = "hello".ptr
|
||||
_ = "hello".len
|
||||
_ = point.x
|
||||
_ = maybe?
|
||||
_ = maybe orelse 0
|
||||
_ = maybe orelse fallback()
|
||||
_ = signed(1)
|
||||
_ = unsigned(1)
|
||||
_ = exact(1)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, strings.contains(llvm_text, "target triple = \"arm64-apple-macosx13.0.0\""))
|
||||
testing.expect(t, strings.contains(llvm_text, "declare signext i8 @signed(i8 signext)"))
|
||||
testing.expect(t, strings.contains(llvm_text, "declare zeroext i8 @unsigned(i8 zeroext)"))
|
||||
testing.expect(t, strings.contains(llvm_text, "declare i32 @exact(i32)"))
|
||||
testing.expect(t, strings.contains(llvm_text, "@bro.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\""))
|
||||
testing.expect(t, strings.contains(llvm_text, "getelementptr [3 x i8]"))
|
||||
testing.expect(t, strings.contains(llvm_text, "attempted to unwrap none"))
|
||||
testing.expect(t, strings.contains(llvm_text, "orelse_fallback"))
|
||||
testing.expect(t, strings.contains(llvm_text, "orelse_some"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) {
|
||||
text := `Defined :: c_struct {
|
||||
value c_int
|
||||
}
|
||||
Opaque :: c_struct
|
||||
read :: c_func(value @Defined) c_int
|
||||
bad_param :: c_func(value Defined) void
|
||||
bad_result :: c_func() Defined
|
||||
main :: func() void {}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found_param := false
|
||||
found_result := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_param = found_param || strings.contains(diagnostic.message, "cannot be passed by value")
|
||||
found_result = found_result || strings.contains(diagnostic.message, "cannot be returned by value")
|
||||
}
|
||||
testing.expect(t, found_param)
|
||||
testing.expect(t, found_result)
|
||||
}
|
||||
|
||||
@(test)
|
||||
invalid_foreign_declarations_are_eagerly_diagnosed_and_calls_trap :: proc(t: ^testing.T) {
|
||||
text := `bad :: c func(value int) int
|
||||
text := `bad :: c_func(value int) int
|
||||
native :: func() i32
|
||||
main :: func() void {
|
||||
_ = bad(1)
|
||||
@@ -667,7 +791,7 @@ main :: func() void {
|
||||
for diagnostic in diagnostics.items {
|
||||
found_parameter = found_parameter || strings.contains(diagnostic.message, "requires concrete parameter types")
|
||||
found_result = found_result || strings.contains(diagnostic.message, "requires a concrete or void result type")
|
||||
found_native = found_native || strings.contains(diagnostic.message, "must use 'c func'")
|
||||
found_native = found_native || strings.contains(diagnostic.message, "must use 'c_func'")
|
||||
}
|
||||
testing.expect(t, found_parameter)
|
||||
testing.expect(t, found_result)
|
||||
@@ -707,7 +831,7 @@ duplicate_foreign_symbols_across_packages_are_poisoned :: proc(t: ^testing.T) {
|
||||
|
||||
@(test)
|
||||
bodyless_root_main_recovers_as_a_trap_definition :: proc(t: ^testing.T) {
|
||||
text := "main :: c func() i32\n"
|
||||
text := "main :: c_func() i32\n"
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
@@ -974,7 +1098,7 @@ eager_global_calls_root_specializations :: proc(t: ^testing.T) {
|
||||
unused_native :: func() i32 {
|
||||
return 9
|
||||
}
|
||||
unused_foreign :: c func() i32
|
||||
unused_foreign :: c_func() i32
|
||||
value i32 :: make()
|
||||
main :: func() void {}
|
||||
`
|
||||
@@ -1040,16 +1164,16 @@ long_generic_call_chain_reaches_a_fixed_point :: proc(t: ^testing.T) {
|
||||
builder := strings.builder_make()
|
||||
defer strings.builder_destroy(&builder)
|
||||
for index in 0 ..< 70 {
|
||||
fmt.sbprintf(&builder, "f%d :: func(value int) int ", index)
|
||||
fmt.sbprintf(&builder, "fn%d :: func(value int) int ", index)
|
||||
strings.write_string(&builder, "{ return ")
|
||||
if index == 69 {
|
||||
strings.write_string(&builder, "value")
|
||||
} else {
|
||||
fmt.sbprintf(&builder, "f%d(value)", index+1)
|
||||
fmt.sbprintf(&builder, "fn%d(value)", index+1)
|
||||
}
|
||||
strings.write_string(&builder, " }\n")
|
||||
}
|
||||
strings.write_string(&builder, "main :: func() i32 { return f0(1) }\n")
|
||||
strings.write_string(&builder, "main :: func() i32 { return fn0(1) }\n")
|
||||
|
||||
source_file := source.Source{path="test.bro", text=strings.to_string(builder)}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
@@ -1195,6 +1319,17 @@ foreign_function_links_from_c_source :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, state.exit_code, 42)
|
||||
}
|
||||
|
||||
@(test)
|
||||
interop_foundation_matches_zig_compiled_apple_silicon_c_fixture :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-interop-foundation"
|
||||
defer _ = os.remove(output)
|
||||
arguments := []linker.Argument{{kind=.Input, value="examples/interop/foundation/native.c"}}
|
||||
status := compiler_core.compile_package("examples/interop/foundation", output, arguments)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 1)
|
||||
}
|
||||
|
||||
@(test)
|
||||
foreign_function_links_from_object :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-foreign-object"
|
||||
@@ -1442,6 +1577,8 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
|
||||
"/usr/bin/env",
|
||||
"zig",
|
||||
"cc",
|
||||
"-target",
|
||||
"aarch64-macos",
|
||||
"-Wno-override-module",
|
||||
"module.ll",
|
||||
"native.c",
|
||||
@@ -2127,8 +2264,8 @@ package_llvm_is_deterministic_and_symbols_include_package_ids :: proc(t: ^testin
|
||||
testing.expect(t, loaded)
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, llvm_text, second_llvm_text)
|
||||
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p1__same"))
|
||||
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p2__same"))
|
||||
testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p1__same"))
|
||||
testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p2__same"))
|
||||
testing.expect(t, strings.contains(llvm_text, "define i32 @main()"))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
Buffer :: c_struct {
|
||||
data *c_char
|
||||
length c_ulong
|
||||
}
|
||||
|
||||
get_buffer :: c_func() @Buffer
|
||||
verify :: c_func(
|
||||
char_value c_char,
|
||||
schar_value c_schar,
|
||||
uchar_value c_uchar,
|
||||
short_value c_short,
|
||||
ushort_value c_ushort,
|
||||
int_value c_int,
|
||||
uint_value c_uint,
|
||||
long_value c_long,
|
||||
ulong_value c_ulong,
|
||||
longlong_value c_longlong,
|
||||
ulonglong_value c_ulonglong,
|
||||
float_value c_float,
|
||||
double_value c_double,
|
||||
longdouble_value c_longdouble,
|
||||
) i32
|
||||
|
||||
main :: func() i32 {
|
||||
buffer @Buffer :: get_buffer()
|
||||
_ = buffer^.data
|
||||
_ = buffer^.length
|
||||
return verify(1, -2, 3, -4, 5, -6, 7, -8, 9, -10, 11, 12.0, 13.0, 14.0)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
struct Buffer {
|
||||
const char *data;
|
||||
unsigned long length;
|
||||
};
|
||||
|
||||
static const struct Buffer buffer = {"ok", 2};
|
||||
|
||||
const struct Buffer *get_buffer(void) {
|
||||
return &buffer;
|
||||
}
|
||||
|
||||
int verify(
|
||||
char char_value,
|
||||
signed char schar_value,
|
||||
unsigned char uchar_value,
|
||||
short short_value,
|
||||
unsigned short ushort_value,
|
||||
int int_value,
|
||||
unsigned int uint_value,
|
||||
long long_value,
|
||||
unsigned long ulong_value,
|
||||
long long longlong_value,
|
||||
unsigned long long ulonglong_value,
|
||||
float float_value,
|
||||
double double_value,
|
||||
long double longdouble_value
|
||||
) {
|
||||
return char_value == 1 &&
|
||||
schar_value == -2 &&
|
||||
uchar_value == 3 &&
|
||||
short_value == -4 &&
|
||||
ushort_value == 5 &&
|
||||
int_value == -6 &&
|
||||
uint_value == 7 &&
|
||||
long_value == -8 &&
|
||||
ulong_value == 9 &&
|
||||
longlong_value == -10 &&
|
||||
ulonglong_value == 11 &&
|
||||
float_value == 12.0f &&
|
||||
double_value == 13.0 &&
|
||||
longdouble_value == 14.0L;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
foreign_add :: c func(a, b i32) i32
|
||||
foreign_add :: c_func(a, b i32) i32
|
||||
|
||||
main :: func() i32 {
|
||||
return foreign_add(20, 22)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
same :: c func() int {
|
||||
same :: c_func() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
same :: c func() int {
|
||||
same :: c_func() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
same :: c func() i32
|
||||
same :: c_func() i32
|
||||
|
||||
@@ -1 +1 @@
|
||||
same :: c func() i32
|
||||
same :: c_func() i32
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
x int :: 2
|
||||
|
||||
sum_c :: c func(a, b int) int {
|
||||
sum_c :: c_func(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import "./compiler"
|
||||
import "./compiler/linker"
|
||||
import "./compiler/target"
|
||||
import "core:fmt"
|
||||
import "core:os/os2"
|
||||
|
||||
@@ -9,13 +10,14 @@ Cli_Options :: struct {
|
||||
input_path: string,
|
||||
output_path: string,
|
||||
link_arguments: []linker.Argument,
|
||||
target: target.Target,
|
||||
}
|
||||
|
||||
parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_Options, bool) {
|
||||
if len(args) < 4 {
|
||||
return {}, false
|
||||
}
|
||||
options := Cli_Options{input_path=args[1]}
|
||||
options := Cli_Options{input_path=args[1], target=target.DEFAULT}
|
||||
link_arguments: [dynamic]linker.Argument
|
||||
link_arguments.allocator = allocator
|
||||
output_set := false
|
||||
@@ -43,6 +45,13 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
|
||||
append(&link_arguments, linker.Argument{kind=.Library_Path, value=value})
|
||||
case "--library":
|
||||
append(&link_arguments, linker.Argument{kind=.Library, value=value})
|
||||
case "--target":
|
||||
selected, ok := target.parse(value)
|
||||
if !ok {
|
||||
delete(link_arguments)
|
||||
return {}, false
|
||||
}
|
||||
options.target = selected
|
||||
case:
|
||||
delete(link_arguments)
|
||||
return {}, false
|
||||
@@ -58,7 +67,7 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
|
||||
|
||||
print_usage :: proc() {
|
||||
fmt.eprintln(
|
||||
"usage: brolang <package-directory> -o <executable> [--link <path> | --library-path <dir> | --library <name>]...",
|
||||
"usage: brolang <package-directory> -o <executable> [--target aarch64-macos] [--link <path> | --library-path <dir> | --library <name>]...",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,7 +78,7 @@ main :: proc() {
|
||||
os2.exit(2)
|
||||
}
|
||||
defer delete(options.link_arguments)
|
||||
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments)
|
||||
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target)
|
||||
if status != 0 {
|
||||
os2.exit(status)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user