c interop type foundation

This commit is contained in:
2026-06-12 18:10:52 +02:00
parent d2f0d16795
commit 4e860b033e
27 changed files with 3523 additions and 380 deletions
+14 -15
View File
@@ -12,17 +12,24 @@
### types and expressions ### 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 - contextual integer literals and constant folding of addition and negation trees
- checked signed integer addition and unary negation - strict numeric conversions, checked integer addition, and unary negation
- function calls, assignments, and returns - 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 ### functions and packages
- demand-monomorphized functions - demand-monomorphized functions
- bodyful `c func` definitions using the c calling convention - bodyful `c_func` definitions using the c calling convention
- bodyless `c func` declarations with exact, globally unique external symbol names - bodyless `c_func` declarations with exact, globally unique external symbol names
- concrete-only foreign signatures - concrete-only foreign signatures
- Apple Silicon C ABI scalar and pointer lowering, including narrow integer extension attributes
- directory packages with merged declarations - directory packages with merged declarations
- file-local relative imports, aliases, and qualified member access - file-local relative imports, aliases, and qualified member access
@@ -43,20 +50,12 @@
### scalar and compound types ### 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 - tuples and native variadic functions
- C unions, C enums, and by-value C record ABI lowering
### c imports ### c imports
- c headers imported as synthetic package namespaces - c headers imported as synthetic package namespaces
- typedefs, enums, opaque records, and external variables - typedefs, enums, opaque records, and external variables
- function pointers, callbacks, macros, and static inline functions - 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
+8 -4
View File
@@ -9,11 +9,12 @@ odin build . -out:build/brolang
./build/prototype ./build/prototype
``` ```
Bodyless `c func` declarations bind exact external symbols and require concrete Bodyless `c_func` declarations bind exact external symbols and require concrete
types: types. C primitives use atomic target-dependent names and remain semantically
distinct from exact-width Brolang primitives:
```bro ```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` 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 - Newline-terminated, multiline statements; `}` may terminate a block's final statement
- `#` comments - `#` comments
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks - 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 - Contextual integer constants and compile-time folding of addition and unary negation trees
- Directory packages with merged declarations and file-local relative imports - Directory packages with merged declarations and file-local relative imports
- Qualified imported globals and functions with package-aware symbol mangling - Qualified imported globals and functions with package-aware symbol mangling
+7 -5
View File
@@ -4,8 +4,10 @@
# milestones # milestones
1. interop type foundation 1. interop type foundation (implemented)
- unsigned integers, floats, and target-dependent c scalar types - 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`) - keep binding mutability (`::` / `=`) separate from element or pointee mutability (`mut`)
- arrays and indexing - arrays and indexing
- `[N]T`: array with `N` logical elements - `[N]T`: array with `N` logical elements
@@ -13,7 +15,7 @@
- pointers - pointers
- `@T` / `@mut T`: non-null single-item pointer without arithmetic - `@T` / `@mut T`: non-null single-item pointer without arithmetic
- `*T` / `*mut T`: non-null many-item pointer with 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 - slices and slicing
- `[]T`: pointer and length - `[]T`: pointer and length
- `[;S]T`: pointer and length with a sentinel invariant - `[;S]T`: pointer and length with a sentinel invariant
@@ -22,9 +24,9 @@
- character literals - character literals
- optionals with trapping unwrap and fallback operations - optionals with trapping unwrap and fallback operations
- native structs with compiler-controlled layout - native structs with compiler-controlled layout
- pointer-only `c struct` support with target c layout - pointer-only `c_struct` support with target c layout
- `Some :: c struct { ... }`: defined c-layout struct - `Some :: c_struct { ... }`: defined c-layout struct
- `Some :: c struct`: opaque c-layout struct - `Some :: c_struct`: opaque c-layout struct
- defer passing c structs by value until target ABI classification exists - defer passing c structs by value until target ABI classification exists
2. restricted c header imports 2. restricted c header imports
+25 -9
View File
@@ -2,6 +2,7 @@ package ast
import "../source" import "../source"
import "../symbol" import "../symbol"
import "../types"
import "core:mem" import "core:mem"
Expr_Id :: distinct u32 Expr_Id :: distinct u32
@@ -60,20 +61,25 @@ index :: proc(id: $T, invalid: T, count: int) -> (int, bool) {
return value, id != invalid && value < count return value, id != invalid && value < count
} }
Type_Syntax :: enum u8 { Type_Syntax :: types.Type
Invalid,
Int,
I8,
I16,
I32,
I64,
Void,
}
Expr_Kind :: enum u8 { Expr_Kind :: enum u8 {
Invalid, Invalid,
Integer, Integer,
Float,
String,
Array,
None,
Name, Name,
Address,
Deref,
Index,
Slice,
Field,
Unwrap,
Orelse,
Struct_Literal,
Keyed,
Negate, Negate,
Add, Add,
Call, Call,
@@ -111,6 +117,7 @@ Stmt :: struct {
name: symbol.Id, name: symbol.Id,
type: Type_Syntax, type: Type_Syntax,
immutable: bool, immutable: bool,
target: Expr_Id,
expr: Expr_Id, expr: Expr_Id,
diagnostic: source.Diagnostic_Id, diagnostic: source.Diagnostic_Id,
} }
@@ -170,12 +177,15 @@ Module :: struct {
imports: [dynamic]Import, imports: [dynamic]Import,
files: [dynamic]File, files: [dynamic]File,
packages: [dynamic]Package, packages: [dynamic]Package,
strings: [dynamic]string,
type_store: types.Store,
allocator: mem.Allocator, allocator: mem.Allocator,
} }
init_module :: proc(allocator := context.allocator) -> Module { init_module :: proc(allocator := context.allocator) -> Module {
module: Module module: Module
module.allocator = allocator module.allocator = allocator
module.type_store = types.init_store(allocator)
module.exprs.allocator = allocator module.exprs.allocator = allocator
module.statements.allocator = allocator module.statements.allocator = allocator
module.functions.allocator = allocator module.functions.allocator = allocator
@@ -183,6 +193,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
module.imports.allocator = allocator module.imports.allocator = allocator
module.files.allocator = allocator module.files.allocator = allocator
module.packages.allocator = allocator module.packages.allocator = allocator
module.strings.allocator = allocator
return module return module
} }
@@ -200,6 +211,9 @@ destroy_module :: proc(module: ^Module) {
for pkg in module.packages { for pkg in module.packages {
delete(pkg.path, module.allocator) delete(pkg.path, module.allocator)
} }
for value in module.strings {
delete(value, module.allocator)
}
delete(module.exprs) delete(module.exprs)
delete(module.statements) delete(module.statements)
delete(module.functions) delete(module.functions)
@@ -207,4 +221,6 @@ destroy_module :: proc(module: ^Module) {
delete(module.imports) delete(module.imports)
delete(module.files) delete(module.files)
delete(module.packages) delete(module.packages)
delete(module.strings)
types.destroy_store(&module.type_store)
} }
+10 -2
View File
@@ -1,6 +1,7 @@
package backend package backend
import "../linker" import "../linker"
import "../target"
import "core:fmt" import "core:fmt"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
@@ -14,6 +15,7 @@ append_owned :: proc(command: ^[dynamic]string, value: string, allocator: mem.Al
build_command :: proc( build_command :: proc(
llvm_path, output_path: string, llvm_path, output_path: string,
link_arguments: []linker.Argument, link_arguments: []linker.Argument,
selected := target.DEFAULT,
allocator := context.allocator, allocator := context.allocator,
) -> []string { ) -> []string {
command: [dynamic]string command: [dynamic]string
@@ -21,6 +23,8 @@ build_command :: proc(
append_owned(&command, "/usr/bin/env", allocator) append_owned(&command, "/usr/bin/env", allocator)
append_owned(&command, "zig", allocator) append_owned(&command, "zig", allocator)
append_owned(&command, "cc", 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, "-Wno-override-module", allocator)
append_owned(&command, llvm_path, allocator) append_owned(&command, llvm_path, allocator)
for argument in link_arguments { for argument in link_arguments {
@@ -45,12 +49,16 @@ destroy_command :: proc(command: []string, allocator := context.allocator) {
delete(command, 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() pid := os2.get_pid()
temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid) temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid)
defer _ = os.remove(temporary_output) 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) defer destroy_command(command)
state, stdout, stderr, err := os2.process_exec( state, stdout, stderr, err := os2.process_exec(
os2.Process_Desc{command=command}, os2.Process_Desc{command=command},
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -9,12 +9,17 @@ import "./lower"
import "./opt" import "./opt"
import "./source" import "./source"
import "./symbol" import "./symbol"
import "./target"
import "core:fmt" import "core:fmt"
import vmem "core:mem/virtual" import vmem "core:mem/virtual"
import "core:os" import "core:os"
import "core:os/os2" 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() sources := source.init_store()
defer source.destroy_store(&sources) defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources) diagnostics := source.init_store_diagnostics(&sources)
@@ -61,7 +66,7 @@ compile_package :: proc(input_path, output_path: string, link_arguments: []linke
return 2 return 2
} }
vmem.arena_free_all(&lexer_arena) 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) vmem.arena_free_all(&parser_arena)
ir_module := lower.lower(&hir_module, vmem.arena_allocator(&lower_arena)) ir_module := lower.lower(&hir_module, vmem.arena_allocator(&lower_arena))
vmem.arena_free_all(&checker_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) 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 return 2
} }
if len(diagnostics.items) > 0 { if len(diagnostics.items) > 0 {
+31 -1
View File
@@ -2,6 +2,7 @@ package hir
import "../source" import "../source"
import "../symbol" import "../symbol"
import "../target"
import "../types" import "../types"
import "core:mem" import "core:mem"
@@ -74,11 +75,27 @@ Linkage :: enum u8 {
Expr_Kind :: enum u8 { Expr_Kind :: enum u8 {
Invalid, Invalid,
Integer, Integer,
Float,
String,
Array,
Struct,
None,
Optional_Some,
Local, Local,
Global, Global,
Address,
Deref,
Index,
Slice,
Field,
Length,
Slice_Ptr,
Unwrap,
Orelse,
Widen, Widen,
Negate, Negate,
Add, Add,
Pointer_Add,
Call, Call,
} }
@@ -114,6 +131,7 @@ Stmt :: struct {
kind: Stmt_Kind, kind: Stmt_Kind,
span: source.Span, span: source.Span,
local: Local_Id, local: Local_Id,
target: Expr_Id,
expr: Expr_Id, expr: Expr_Id,
diagnostic: source.Diagnostic_Id, diagnostic: source.Diagnostic_Id,
} }
@@ -153,16 +171,23 @@ Module :: struct {
statements: [dynamic]Stmt, statements: [dynamic]Stmt,
functions: [dynamic]Function, functions: [dynamic]Function,
globals: [dynamic]Global, globals: [dynamic]Global,
strings: [dynamic]string,
types: types.Store,
target: target.Target,
allocator: mem.Allocator, allocator: mem.Allocator,
} }
init_module :: proc(allocator := context.allocator) -> Module { init_module :: proc(selected := target.DEFAULT, allocator := context.allocator) -> Module {
module: Module module: Module
module.target = selected
module.types = types.init_store(allocator)
module.types.selected = selected
module.allocator = allocator module.allocator = allocator
module.exprs.allocator = allocator module.exprs.allocator = allocator
module.statements.allocator = allocator module.statements.allocator = allocator
module.functions.allocator = allocator module.functions.allocator = allocator
module.globals.allocator = allocator module.globals.allocator = allocator
module.strings.allocator = allocator
return module return module
} }
@@ -182,8 +207,13 @@ destroy_module :: proc(module: ^Module) {
delete(global.dependencies) delete(global.dependencies)
delete(global.calls, module.allocator) delete(global.calls, module.allocator)
} }
for value in module.strings {
delete(value, module.allocator)
}
delete(module.exprs) delete(module.exprs)
delete(module.statements) delete(module.statements)
delete(module.functions) delete(module.functions)
delete(module.globals) delete(module.globals)
delete(module.strings)
types.destroy_store(&module.types)
} }
+29 -1
View File
@@ -2,6 +2,7 @@ package ir
import "../source" import "../source"
import "../symbol" import "../symbol"
import "../target"
import "../types" import "../types"
import "core:mem" import "core:mem"
@@ -67,13 +68,28 @@ Linkage :: enum u8 {
Opcode :: enum u8 { Opcode :: enum u8 {
Param, Param,
Const, Const,
String,
Aggregate,
None,
Optional_Some,
Load_Global, Load_Global,
Address_Global,
Address_Of,
Alloca, Alloca,
Index_Address,
Field_Address,
Load, Load,
Store, Store,
Slice,
Length,
Slice_Ptr,
Unwrap,
Orelse_Begin,
Orelse,
Widen, Widen,
Neg_Checked, Neg_Checked,
Add_Checked, Add_Checked,
Pointer_Add,
Call, Call,
Trap, Trap,
Return, Return,
@@ -117,13 +133,20 @@ Global :: struct {
Module :: struct { Module :: struct {
functions: [dynamic]Function, functions: [dynamic]Function,
globals: [dynamic]Global, globals: [dynamic]Global,
strings: [dynamic]string,
types: types.Store,
target: target.Target,
allocator: mem.Allocator, allocator: mem.Allocator,
} }
init_module :: proc(allocator := context.allocator) -> Module { init_module :: proc(selected := target.DEFAULT, allocator := context.allocator) -> Module {
module: Module module: Module
module.target = selected
module.types = types.init_store(allocator)
module.types.selected = selected
module.functions.allocator = allocator module.functions.allocator = allocator
module.globals.allocator = allocator module.globals.allocator = allocator
module.strings.allocator = allocator
module.allocator = allocator module.allocator = allocator
return module return module
} }
@@ -144,6 +167,11 @@ destroy_module :: proc(module: ^Module) {
for global in module.globals { for global in module.globals {
destroy_instructions(global.initializer, module.allocator) destroy_instructions(global.initializer, module.allocator)
} }
for value in module.strings {
delete(value, module.allocator)
}
delete(module.functions) delete(module.functions)
delete(module.globals) delete(module.globals)
delete(module.strings)
types.destroy_store(&module.types)
} }
+101 -20
View File
@@ -14,16 +14,44 @@ is_identifier_continue :: proc(value: byte) -> bool {
keyword_kind :: proc(text: string) -> token.Kind { keyword_kind :: proc(text: string) -> token.Kind {
switch text { switch text {
case "func": return .Keyword_Func case "func": return .Keyword_Func
case "import": return .Keyword_Import case "c_func": return .Keyword_C_Func
case "return": return .Keyword_Return case "struct": return .Keyword_Struct
case "void": return .Keyword_Void case "c_struct": return .Keyword_C_Struct
case "int": return .Keyword_Int case "import": return .Keyword_Import
case "i8": return .Keyword_I8 case "return": return .Keyword_Return
case "i16": return .Keyword_I16 case "mut": return .Keyword_Mut
case "i32": return .Keyword_I32 case "none": return .Keyword_None
case "i64": return .Keyword_I64 case "orelse": return .Keyword_Orelse
case "_": return .Underscore 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 return .Identifier
} }
@@ -87,7 +115,34 @@ lex :: proc(
append_token(&stream, source_file, .Minus, cursor, cursor+1) append_token(&stream, source_file, .Minus, cursor, cursor+1)
cursor += 1 cursor += 1
case '.': 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 cursor += 1
case '(': case '(':
append_token(&stream, source_file, .Left_Paren, cursor, cursor+1) 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' { for cursor < len(bytes) && bytes[cursor] != '"' && bytes[cursor] != '\n' {
if bytes[cursor] == '\\' { if bytes[cursor] == '\\' {
cursor += 1 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( source.add(
diagnostics, diagnostics,
source.Span{file=source_file.id, start=source.Offset(max(cursor-1, start)), end=source.Offset(min(cursor+1, len(bytes)))}, 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 valid = false
} }
@@ -136,20 +193,44 @@ lex :: proc(
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id) append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
} }
case ';': case ';':
id := source.add( append_token(&stream, source_file, .Semicolon, cursor, cursor+1)
diagnostics,
source.Span{file=source_file.id, start=source.Offset(cursor), end=source.Offset(cursor+1)},
"semicolons are invalid; terminate statements with a newline",
)
append_token(&stream, source_file, .Invalid, cursor, cursor+1, diagnostic=id)
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(start), end=source.Offset(cursor)},
"unterminated character literal",
)
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
}
case: case:
if value >= '0' && value <= '9' { if value >= '0' && value <= '9' {
start := cursor start := cursor
for cursor < len(bytes) && bytes[cursor] >= '0' && bytes[cursor] <= '9' { for cursor < len(bytes) && bytes[cursor] >= '0' && bytes[cursor] <= '9' {
cursor += 1 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) { } else if is_identifier_start(value) {
start := cursor start := cursor
for cursor < len(bytes) && is_identifier_continue(bytes[cursor]) { for cursor < len(bytes) && is_identifier_continue(bytes[cursor]) {
+633 -71
View File
@@ -3,6 +3,7 @@ package llvm
import "../ir" import "../ir"
import "../source" import "../source"
import "../symbol" import "../symbol"
import "../target"
import "../types" import "../types"
import "core:fmt" import "core:fmt"
import "core:mem" import "core:mem"
@@ -21,11 +22,33 @@ Emitter :: struct {
allocator: mem.Allocator, allocator: mem.Allocator,
} }
llvm_type :: proc(value: types.Type) -> string { llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
if value.kind == .Void { if types.is_void(value) {
return "void" 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 8: return "i8"
case 16: return "i16" case 16: return "i16"
case 32: return "i32" 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 { if function.is_main {
return "i32" return "i32"
} }
return llvm_type(function.result) return llvm_type(function.result, store)
} }
sentinel :: proc(value_type: types.Type) -> i64 { c_abi_extension :: proc(value: types.Type, selected: target.Target) -> string {
switch value_type.bits { 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 8: return -86
case 16: return -21846 case 16: return -21846
case 32: return -1431655766 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) 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) || if !valid_instruction(instructions, value_id) ||
!types.is_concrete_integer(expected) || !types.is_runtime_value(expected, store) ||
!types.equal(instructions[value_id].type, expected) { !types.equal(instructions[value_id].type, expected) {
return false return false
} }
switch instructions[value_id].op { 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 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
} }
return false return false
} }
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type) { valid_address :: proc(
if !valid_value(instructions, value_id, expected) { instructions: []ir.Instruction,
fmt.sbprintf(builder, "%d", sentinel(expected)) 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 return
} }
value := instructions[value_id] value := instructions[value_id]
if value.op == .Const { if value.op == .Const {
fmt.sbprintf(builder, "%d", value.integer) write_constant(builder, value.integer, expected, store)
} else { } else {
fmt.sbprintf(builder, "%%v%d", value_id) 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) { emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction: ir.Instruction, fallback: string) {
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback) message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback)
emit_trap_call(emitter, message) 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, &emitter.module.types),
sentinel(instruction.type, emitter.module.target),
)
return
}
fmt.sbprintf( fmt.sbprintf(
&emitter.builder, &emitter.builder,
" %%v%d = add %s 0, %d\n", " %%v%d = select i1 true, %s ",
instruction_id, instruction_id,
llvm_type(instruction.type), llvm_type(instruction.type, &emitter.module.types),
sentinel(instruction.type),
) )
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 { for arg, index in args {
if index > 0 { if index > 0 {
strings.write_string(builder, ", ") strings.write_string(builder, ", ")
} }
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index])) fmt.sbprintf(builder, "%s ", llvm_type(param_types[index], store))
write_operand(builder, instructions, arg, param_types[index]) 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 { switch instruction.op {
case .Param, .Const: 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: case .Load_Global:
global_id := ir.as_global(instruction.target) global_id := ir.as_global(instruction.target)
if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) { if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) {
@@ -179,7 +414,7 @@ emit_instruction_stream :: proc(
&emitter.builder, &emitter.builder,
" %%v%d = load %s, ptr @bro.g.%d\n", " %%v%d = load %s, ptr @bro.g.%d\n",
instruction_index, instruction_index,
llvm_type(global.type), llvm_type(global.type, &emitter.module.types),
global_id, global_id,
) )
} else { } else {
@@ -187,20 +422,115 @@ emit_instruction_stream :: proc(
&emitter.builder, &emitter.builder,
" %%v%d = call %s @bro.get.%d()\n", " %%v%d = call %s @bro.get.%d()\n",
instruction_index, instruction_index,
llvm_type(global.type), llvm_type(global.type, &emitter.module.types),
global_id, 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: 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") emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type")
continue continue
} }
fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type)) fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types))
case .Load: case .Index_Address:
if !valid_instruction(instructions, instruction.a) || if !valid_instruction(instructions, instruction.a) ||
instructions[instruction.a].op != .Alloca || !valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) {
!types.equal(instructions[instruction.a].type, instruction.type) { 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") emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot")
continue continue
} }
@@ -208,42 +538,191 @@ emit_instruction_stream :: proc(
&emitter.builder, &emitter.builder,
" %%v%d = load %s, ptr %%v%d\n", " %%v%d = load %s, ptr %%v%d\n",
instruction_index, instruction_index,
llvm_type(instruction.type), llvm_type(instruction.type, &emitter.module.types),
instruction.a, instruction.a,
) )
case .Store: case .Store:
if !valid_instruction(instructions, instruction.a) || if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) ||
instructions[instruction.a].op != .Alloca || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
!types.equal(instructions[instruction.a].type, instruction.type) ||
!valid_value(instructions, instruction.b, instruction.type) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid store operand") emit_recovery_value(emitter, instruction_index, instruction, "invalid store operand")
continue continue
} }
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type)) fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type, &emitter.module.types))
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, ", ptr %%v%d\n", instruction.a) 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: case .Widen:
if !valid_instruction(instructions, instruction.a) || if !valid_instruction(instructions, instruction.a) ||
!types.is_concrete_integer(instructions[instruction.a].type) || !types.can_widen(instructions[instruction.a].type, instruction.type) {
!types.is_concrete_integer(instruction.type) ||
instructions[instruction.a].type.bits >= instruction.type.bits {
emit_recovery_value(emitter, instruction_index, instruction, "invalid widening operand") emit_recovery_value(emitter, instruction_index, instruction, "invalid widening operand")
continue continue
} }
from_type := instructions[instruction.a].type from_type := instructions[instruction.a].type
fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_index, llvm_type(from_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")
write_operand(&emitter.builder, instructions, instruction.a, from_type) fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type)) 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: 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") emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
continue 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) fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
strings.write_string(&emitter.builder, "{ ") 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) 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, ")\n")
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index) fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index)
strings.write_string(&emitter.builder, "{ ") strings.write_string(&emitter.builder, "{ ")
@@ -263,18 +742,27 @@ emit_instruction_stream :: proc(
emit_trap_call(emitter, message) emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index) fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
case .Add_Checked: case .Add_Checked:
if !valid_value(instructions, instruction.a, instruction.type) || if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type) { !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand") emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand")
continue 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) fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
strings.write_string(&emitter.builder, "{ ") strings.write_string(&emitter.builder, "{ ")
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name) 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) write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %s ", type_name) 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, ")\n")
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index) fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index)
strings.write_string(&emitter.builder, "{ ") strings.write_string(&emitter.builder, "{ ")
@@ -290,9 +778,20 @@ emit_instruction_stream :: proc(
instruction_index, instruction_index,
) )
fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", 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) emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index) 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: case .Call:
function_id := ir.as_function(instruction.target) function_id := ir.as_function(instruction.target)
if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) { 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) valid_args := len(instruction.args) == len(target.param_types)
if valid_args { if valid_args {
for arg, index in instruction.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 valid_args = false
break break
} }
@@ -317,7 +816,7 @@ emit_instruction_stream :: proc(
emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands") emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands")
continue continue
} }
if instruction.type.kind != .Void { if !types.is_void(instruction.type) {
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index) fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
} else { } else {
strings.write_string(&emitter.builder, " ") strings.write_string(&emitter.builder, " ")
@@ -326,8 +825,12 @@ emit_instruction_stream :: proc(
if target.calling_convention == .Brolang { if target.calling_convention == .Brolang {
strings.write_string(&emitter.builder, "fastcc ") strings.write_string(&emitter.builder, "fastcc ")
} }
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name) emit_function_result(&emitter.builder, target, &emitter.module.types)
emit_call_args(&emitter.builder, instructions, instruction.args, target.param_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") strings.write_string(&emitter.builder, ")\n")
case .Trap: case .Trap:
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source") message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
@@ -337,8 +840,8 @@ emit_instruction_stream :: proc(
return_value = instruction.a return_value = instruction.a
continue continue
} }
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function)) fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, function.result) write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types)
strings.write_string(&emitter.builder, "\n") strings.write_string(&emitter.builder, "\n")
after_return = true after_return = true
case .Return_Void: case .Return_Void:
@@ -361,17 +864,18 @@ emit_globals :: proc(emitter: ^Emitter) {
if global.is_static { if global.is_static {
fmt.sbprintf( fmt.sbprintf(
&emitter.builder, &emitter.builder,
"@bro.g.%d = internal constant %s %d\n", "@bro.g.%d = internal constant %s ",
global_id, global_id,
llvm_type(global.type), llvm_type(global.type, &emitter.module.types),
global.static_value,
) )
write_constant(&emitter.builder, global.static_value, global.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
} else { } else {
fmt.sbprintf( fmt.sbprintf(
&emitter.builder, &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, global_id,
llvm_type(global.type), llvm_type(global.type, &emitter.module.types),
global_id, global_id,
) )
} }
@@ -379,13 +883,53 @@ emit_globals :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, "\n") 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) { emit_global_accessors :: proc(emitter: ^Emitter) {
placeholder_function := ir.Function{result=types.I64} placeholder_function := ir.Function{result=types.I64}
for global, global_id in emitter.module.globals { for global, global_id in emitter.module.globals {
if global.is_static { if global.is_static {
continue 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) fmt.sbprintf(&emitter.builder, "define internal %s @bro.get.%d() ", type_name, global_id)
strings.write_string(&emitter.builder, "{\nentry:\n") strings.write_string(&emitter.builder, "{\nentry:\n")
fmt.sbprintf( fmt.sbprintf(
@@ -407,11 +951,11 @@ emit_global_accessors :: proc(emitter: ^Emitter) {
placeholder_function.result = global.type placeholder_function.result = global.type
value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true) value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true)
fmt.sbprintf(&emitter.builder, " store %s ", type_name) 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, ", 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, " store i8 2, ptr @bro.gstate.%d\n", global_id)
fmt.sbprintf(&emitter.builder, " ret %s ", type_name) 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") 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) 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") strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
for global, global_id in emitter.module.globals { for global, global_id in emitter.module.globals {
if !global.is_static && !global.problematic { 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") strings.write_string(&emitter.builder, " ret void\n}\n\n")
@@ -453,15 +997,25 @@ emit_functions :: proc(emitter: ^Emitter) {
if function.calling_convention == .Brolang { if function.calling_convention == .Brolang {
strings.write_string(&emitter.builder, "fastcc ") 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 { for param_type, index in function.param_types {
if index > 0 { if index > 0 {
strings.write_string(&emitter.builder, ", ") strings.write_string(&emitter.builder, ", ")
} }
if function.implementation == .Declaration { 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 { } 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 { 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) fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
strings.write_string(&emitter.builder, "declare { i") strings.write_string(&emitter.builder, "declare { i")
fmt.sbprintf(&emitter.builder, "%d", bits) 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") strings.write_string(&emitter.builder, ", i1 } @llvm.ssub.with.overflow.i")
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits) 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.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_globals(&emitter)
emit_constructor(&emitter) emit_constructor(&emitter)
emit_global_accessors(&emitter) emit_global_accessors(&emitter)
+88
View File
@@ -5,6 +5,7 @@ import "../lexer"
import "../parser" import "../parser"
import "../source" import "../source"
import "../symbol" import "../symbol"
import "../types"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
import "core:path/filepath" 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 &param 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( load :: proc(
root_path: string, root_path: string,
sources: ^source.Store, sources: ^source.Store,
@@ -275,5 +362,6 @@ load :: proc(
state.root_failed = true state.root_failed = true
} }
validate_imports(&state) validate_imports(&state)
canonicalize_types(&module, allocator)
return module, !state.root_failed return module, !state.root_failed
} }
+249 -37
View File
@@ -3,6 +3,7 @@ package lower
import "../hir" import "../hir"
import "../ir" import "../ir"
import "../source" import "../source"
import "../target"
import "../types" import "../types"
import "core:fmt" import "core:fmt"
import "core:mem" import "core:mem"
@@ -28,8 +29,11 @@ clone_args :: proc(values: []ir.Instruction_Id, allocator: mem.Allocator) -> []i
return result return result
} }
sentinel :: proc(value_type: types.Type) -> i64 { sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 {
switch value_type.bits { 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 8: return -86
case 16: return -21846 case 16: return -21846
case 32: return -1431655766 case 32: return -1431655766
@@ -53,14 +57,14 @@ append_recovery_value :: proc(
diagnostic=diagnostic, diagnostic=diagnostic,
}) })
fallback := value_type fallback := value_type
if !types.is_concrete_integer(fallback) { if !types.is_valid(fallback) {
fallback = types.I64 fallback = types.I64
} }
return append_instruction(state, ir.Instruction{ return append_instruction(state, ir.Instruction{
op=.Const, op=.Const,
span=span, span=span,
type=fallback, type=fallback,
integer=sentinel(fallback), integer=sentinel(fallback, state.hir_module.target),
target=ir.INVALID_REF, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
@@ -76,6 +80,187 @@ Lower_Expr_Frame :: struct {
args: []ir.Instruction_Id, 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 { lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
stack := state.expr_stack stack := state.expr_stack
clear_dynamic_array(&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] expr := state.hir_module.exprs[frame.expr]
if frame.stage == 0 { if frame.stage == 0 {
switch expr.kind { #partial switch expr.kind {
case .Invalid: case .Invalid:
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
_ = pop(&stack) _ = pop(&stack)
case .Integer: case .Integer, .Float:
last = append_instruction(state, ir.Instruction{ last = append_instruction(state, ir.Instruction{
op=.Const, span=expr.span, type=expr.type, integer=expr.integer, 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, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
_ = pop(&stack) _ = 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: case .Local:
last = ir.INVALID_INSTRUCTION last = ir.INVALID_INSTRUCTION
local := hir.as_local(expr.target) 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: case .Negate:
stack[frame_index].stage = 5 stack[frame_index].stage = 5
append(&stack, Lower_Expr_Frame{expr=expr.left}) append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Add: case .Add, .Pointer_Add:
stack[frame_index].stage = 2 stack[frame_index].stage = 2
append(&stack, Lower_Expr_Frame{expr=expr.left}) append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Call: case .Call:
@@ -183,7 +372,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
} }
if frame.stage == 3 { if frame.stage == 3 {
last = append_instruction(state, ir.Instruction{ 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, a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
_ = pop(&stack) _ = pop(&stack)
@@ -238,6 +428,20 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
}) })
state.local_values[local_id] = param 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 { for statement_id in function.body {
statement := hir_module.statements[statement_id] statement := hir_module.statements[statement_id]
@@ -252,36 +456,39 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
continue continue
} }
local := function.locals[statement.local] local := function.locals[statement.local]
if local.mutable { slot := append_instruction(&state, ir.Instruction{
slot := append_instruction(&state, ir.Instruction{ op=.Alloca,
op=.Alloca, span=statement.span,
span=statement.span, type=local.type,
type=local.type, target=ir.local_ref(ir.Local_Id(statement.local)),
target=ir.local_ref(ir.Local_Id(statement.local)), a=ir.INVALID_INSTRUCTION,
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
diagnostic=source.INVALID_DIAGNOSTIC, })
}) state.local_slots[statement.local] = slot
state.local_slots[statement.local] = slot append_instruction(&state, ir.Instruction{
append_instruction(&state, ir.Instruction{ op=.Store,
op=.Store, span=statement.span,
span=statement.span, type=local.type,
type=local.type, target=ir.INVALID_REF,
target=ir.INVALID_REF, a=slot,
a=slot, b=value,
b=value, diagnostic=source.INVALID_DIAGNOSTIC,
diagnostic=source.INVALID_DIAGNOSTIC, })
})
} else {
state.local_values[statement.local] = value
}
case .Assignment: case .Assignment:
value := lower_expr(&state, statement.expr) value := lower_expr(&state, statement.expr)
slot := ir.INVALID_INSTRUCTION slot := ir.INVALID_INSTRUCTION
if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) { 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
}
} else if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) {
slot = state.local_slots[statement.local] slot = state.local_slots[statement.local]
value_type = function.locals[statement.local].type
} }
if slot == ir.INVALID_INSTRUCTION || statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(function.locals) { if slot == ir.INVALID_INSTRUCTION || !types.is_valid(value_type) {
append_instruction(&state, ir.Instruction{ append_instruction(&state, ir.Instruction{
op=.Trap, span=statement.span, type=types.VOID, op=.Trap, span=statement.span, type=types.VOID,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic, 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{ append_instruction(&state, ir.Instruction{
op=.Store, op=.Store,
span=statement.span, span=statement.span,
type=function.locals[statement.local].type, type=value_type,
target=ir.INVALID_REF, target=ir.INVALID_REF,
a=slot, a=slot,
b=value, b=value,
@@ -337,13 +544,13 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
if len(state.instructions) == 0 || if len(state.instructions) == 0 ||
(state.instructions[len(state.instructions)-1].op != .Return && (state.instructions[len(state.instructions)-1].op != .Return &&
state.instructions[len(state.instructions)-1].op != .Return_Void) { 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}) 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 { } else {
value := append_instruction(&state, ir.Instruction{ value := append_instruction(&state, ir.Instruction{
op=.Const, op=.Const,
type=function.result, type=function.result,
integer=sentinel(function.result), integer=sentinel(function.result, hir_module.target),
target=ir.INVALID_REF, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, a=ir.INVALID_INSTRUCTION,
b=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 { 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 { for global in hir_module.globals {
_ = ir.global_id(len(module.globals)) _ = ir.global_id(len(module.globals))
append(&module.globals, ir.Global{ append(&module.globals, ir.Global{
+537 -28
View File
@@ -4,9 +4,12 @@ import "../ast"
import "../source" import "../source"
import "../symbol" import "../symbol"
import "../token" import "../token"
import "../types"
import "base:intrinsics" import "base:intrinsics"
import "core:fmt" import "core:fmt"
import "core:strconv"
import "core:strings" import "core:strings"
import "core:unicode/utf8"
Parser :: struct { Parser :: struct {
tokens: ^token.Stream, 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 { is_type_token :: proc(kind: token.Kind) -> bool {
#partial switch kind { #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 true
} }
return false 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 { parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
tok := current(parser) 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 { #partial switch tok.kind {
case .Keyword_Int: case .Keyword_Int:
advance(parser) advance(parser)
return .Int return types.INT
case .Keyword_I8: case .Keyword_I8:
advance(parser) advance(parser)
return .I8 return types.I8
case .Keyword_I16: case .Keyword_I16:
advance(parser) advance(parser)
return .I16 return types.I16
case .Keyword_I32: case .Keyword_I32:
advance(parser) advance(parser)
return .I32 return types.I32
case .Keyword_I64: case .Keyword_I64:
advance(parser) 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: case .Keyword_Void:
advance(parser) 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") source.add(parser.diagnostics, tok.span, "expected a type")
return .Invalid return types.INVALID
} }
skip_parenthesized :: proc(parser: ^Parser) -> source.Span { 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) { parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
value: u64 value: u64
for byte in transmute([]byte)text { for byte in transmute([]byte)text {
@@ -203,6 +482,58 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
right=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC, 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: case .Identifier:
first := advance(parser) first := advance(parser)
name := first name := first
@@ -217,6 +548,9 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
if current(parser).kind == .Left_Paren { if current(parser).kind == .Left_Paren {
return parse_call(parser, qualifier, first, name, nesting) 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{ return add_expr(parser, ast.Expr{
kind=.Name, kind=.Name,
span=span_from(first.span, name.span), 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) { infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
#partial switch kind { #partial switch kind {
case .Keyword_Orelse:
return 2, 3, true
case .Plus: case .Plus:
return 10, 11, true 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) { prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
#partial switch kind { #partial switch kind {
case .Minus: case .Minus, .Ampersand:
return 20, true return 20, true
} }
return 0, false 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 := parse_expression_bp(parser, right_power, nesting+1)
operand_expr := parser.module.exprs[operand] operand_expr := parser.module.exprs[operand]
left = add_expr(parser, ast.Expr{ left = add_expr(parser, ast.Expr{
kind=.Negate, kind=.Address if operator.kind == .Ampersand else .Negate,
span=span_from(operator.span, operand_expr.span), span=span_from(operator.span, operand_expr.span),
left=operand, left=operand,
right=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
@@ -306,17 +642,99 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
skip_newlines(parser) skip_newlines(parser)
} }
for { 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) left_power, right_power, ok := infix_binding_power(current(parser).kind)
if !ok || left_power < minimum_binding_power { if !ok || left_power < minimum_binding_power {
break break
} }
advance(parser) operator := advance(parser)
skip_newlines(parser) skip_newlines(parser)
right := parse_expression_bp(parser, right_power, nesting+1) right := parse_expression_bp(parser, right_power, nesting+1)
left_expr := parser.module.exprs[left] left_expr := parser.module.exprs[left]
right_expr := parser.module.exprs[right] right_expr := parser.module.exprs[right]
left = add_expr(parser, ast.Expr{ 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), span=span_from(left_expr.span, right_expr.span),
left=left, left=left,
right=right, right=right,
@@ -381,6 +799,31 @@ parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
return 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 { parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
if current(parser).kind == .Keyword_Return { if current(parser).kind == .Keyword_Return {
return parse_return(parser) 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 { if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
start_cursor := parser.cursor start_cursor := parser.cursor
name := advance(parser) name := advance(parser)
type_syntax := ast.Type_Syntax.Invalid type_syntax := types.INVALID
had_type := false had_type := false
if is_type_token(current(parser).kind) { if starts_declared_type(parser) {
type_syntax = parse_type(parser) type_syntax = parse_type(parser)
had_type = true had_type = true
} }
@@ -413,6 +856,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
name=name.symbol, name=name.symbol,
type=type_syntax, type=type_syntax,
immutable=immutable, immutable=immutable,
target=ast.INVALID_EXPR,
expr=expr, expr=expr,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
@@ -422,6 +866,19 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
} }
expr := parse_expression(parser) 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)) id := ast.stmt_id(len(parser.module.statements))
append(&parser.module.statements, ast.Stmt{ append(&parser.module.statements, ast.Stmt{
kind=.Expression, 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 { decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
text := token_text(parser, tok) text := token_text(parser, tok)
if len(text) < 2 { 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 { if value == '\\' && index+1 < len(text)-1 {
index += 1 index += 1
value = text[index] 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) strings.write_byte(&builder, value)
} }
@@ -621,7 +1136,7 @@ parse_top_level :: proc(parser: ^Parser) {
} }
parser.cursor = saved parser.cursor = saved
} }
type_syntax := ast.Type_Syntax.Invalid type_syntax := types.INVALID
if is_type_token(current(parser).kind) { if is_type_token(current(parser).kind) {
type_syntax = parse_type(parser) type_syntax = parse_type(parser)
} }
@@ -634,23 +1149,17 @@ parse_top_level :: proc(parser: ^Parser) {
advance(parser) advance(parser)
skip_newlines(parser) skip_newlines(parser)
c_abi := false
if operator.kind == .Colon_Colon && if operator.kind == .Colon_Colon &&
current(parser).kind == .Identifier && (current(parser).kind == .Keyword_Func || current(parser).kind == .Keyword_C_Func) {
token_text(parser, current(parser)) == "c" { c_abi := current(parser).kind == .Keyword_C_Func
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 {
parse_function(parser, name, c_abi) parse_function(parser, name, c_abi)
return 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) expr := parse_expression(parser)
_ = ast.global_id(len(parser.module.globals)) _ = ast.global_id(len(parser.module.globals))
+126
View File
@@ -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
}
+39
View File
@@ -9,27 +9,66 @@ Kind :: enum u8 {
Newline, Newline,
Identifier, Identifier,
Integer, Integer,
Float,
String, String,
Character,
Underscore, Underscore,
Colon_Colon, Colon_Colon,
Equal, Equal,
Plus, Plus,
Minus, Minus,
Dot, Dot,
Range,
At,
Star,
Ampersand,
Caret,
Question,
Semicolon,
Left_Bracket,
Right_Bracket,
Left_Paren, Left_Paren,
Right_Paren, Right_Paren,
Left_Brace, Left_Brace,
Right_Brace, Right_Brace,
Comma, Comma,
Keyword_Func, Keyword_Func,
Keyword_C_Func,
Keyword_Struct,
Keyword_C_Struct,
Keyword_Import, Keyword_Import,
Keyword_Return, Keyword_Return,
Keyword_Mut,
Keyword_None,
Keyword_Orelse,
Keyword_Void, Keyword_Void,
Keyword_Int, Keyword_Int,
Keyword_I8, Keyword_I8,
Keyword_I16, Keyword_I16,
Keyword_I32, Keyword_I32,
Keyword_I64, 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 { Token :: struct {
+603 -73
View File
@@ -1,94 +1,619 @@
package types package types
import "../target"
import "core:fmt" 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, None,
Signed_Integer, Signed_Integer,
Unsigned_Integer, Unsigned_Integer,
Float, Float,
} }
Kind :: enum { Kind :: enum u8 {
Invalid, Invalid,
Void, Void,
Int_Constraint, Int_Constraint,
Concrete, Scalar,
Array,
Pointer,
Slice,
Optional,
Named,
Struct,
} }
Type :: struct { Node :: struct {
kind: Kind, kind: Kind,
category: Numeric_Category, child: Type,
bits: int, 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 { Field :: struct {
kind = .Invalid, 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, init_store :: proc(allocator := context.allocator) -> Store {
category = .Signed_Integer, store: Store
store.nodes.allocator = allocator
store.fields.allocator = allocator
store.selected = target.DEFAULT
store.allocator = allocator
return store
} }
I8 :: Type {
kind = .Concrete, destroy_store :: proc(store: ^Store) {
category = .Signed_Integer, delete(store.nodes)
bits = 8, delete(store.fields)
} }
I16 :: Type {
kind = .Concrete, clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
category = .Signed_Integer, store := init_store(allocator)
bits = 16, append(&store.nodes, ..source.nodes[:])
append(&store.fields, ..source.fields[:])
store.selected = source.selected
return store
} }
I32 :: Type {
kind = .Concrete, intern :: proc(store: ^Store, candidate: Node) -> Type {
category = .Signed_Integer, if candidate.kind != .Struct && candidate.kind != .Named {
bits = 32, 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, named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
category = .Signed_Integer, normalized_file := file if qualifier != 0 else u32(0)
bits = 64, 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 { 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 { is_concrete_integer :: proc(value: Type) -> bool {
return( category := category(value)
value.kind == .Concrete && return kind(value) == .Scalar &&
(value.category == .Signed_Integer || value.category == .Unsigned_Integer) \ (category == .Signed_Integer || category == .Unsigned_Integer)
)
} }
is_signed :: proc(value: Type) -> bool { is_float :: proc(value: Type, selected := target.DEFAULT) -> bool {
return value.kind == .Concrete && value.category == .Signed_Integer 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 { 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 { can_widen :: proc(from, to: Type) -> bool {
if equal(from, to) { if equal(from, to) {
return true return true
} }
return( return is_concrete_scalar(from) &&
from.kind == .Concrete && is_concrete_scalar(to) &&
to.kind == .Concrete && same_numeric_family(from, to) &&
from.category == to.category && bits(from) < bits(to)
from.bits < to.bits \
)
} }
widest :: proc(a, b: Type) -> Type { 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 return INVALID
} }
if a.bits >= b.bits { if bits(a) >= bits(b) {
return a return a
} }
return b return b
@@ -108,32 +633,37 @@ smallest_signed_for_literal :: proc(value: i64) -> Type {
} }
name :: proc(value: Type) -> string { name :: proc(value: Type) -> string {
switch value.kind { switch value {
case .Invalid: case INVALID: return "<invalid>"
return "<invalid>" case VOID: return "void"
case .Void: case INT: return "int"
return "void" case I8: return "i8"
case .Int_Constraint: case I16: return "i16"
return "int" case I32: return "i32"
case .Concrete: case I64: return "i64"
switch value.category { case U8: return "u8"
case .Signed_Integer: case U16: return "u16"
switch value.bits { case U32: return "u32"
case 8: case U64: return "u64"
return "i8" case ISIZE: return "isize"
case 16: case USIZE: return "usize"
return "i16" case F32: return "f32"
case 32: case F64: return "f64"
return "i32" case C_CHAR: return "c_char"
case 64: case C_SCHAR: return "c_schar"
return "i64" case C_UCHAR: return "c_uchar"
} case C_SHORT: return "c_short"
case .Unsigned_Integer: case C_USHORT: return "c_ushort"
return fmt.tprintf("u%d", value.bits) case C_INT: return "c_int"
case .Float: case C_UINT: return "c_uint"
return fmt.tprintf("f%d", value.bits) case C_LONG: return "c_long"
case .None: 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)
} }
return "<invalid>"
} }
+159 -22
View File
@@ -14,6 +14,7 @@ import "./compiler/lower"
import "./compiler/parser" import "./compiler/parser"
import "./compiler/source" import "./compiler/source"
import "./compiler/symbol" import "./compiler/symbol"
import "./compiler/target"
import "./compiler/token" import "./compiler/token"
import "./compiler/types" import "./compiler/types"
import "core:fmt" import "core:fmt"
@@ -86,9 +87,10 @@ main :: func() void { _ = value }
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) { 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(source.Span), 12)
testing.expect_value(t, size_of(token.Token), 24) testing.expect_value(t, size_of(token.Token), 24)
testing.expect_value(t, size_of(ast.Expr), 64) testing.expect(t, size_of(ast.Expr) <= 64)
testing.expect_value(t, size_of(hir.Expr), 88) testing.expect(t, size_of(hir.Expr) <= 88)
testing.expect_value(t, size_of(ir.Instruction), 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) source_index, source_ok := source.source_index(source.Source_Id(0), 1)
testing.expect_value(t, source_index, 0) testing.expect_value(t, source_index, 0)
@@ -195,8 +197,8 @@ main :: func() void { _ = give() }
@(test) @(test)
parser_distinguishes_bodyless_declarations_and_definitions :: proc(t: ^testing.T) { parser_distinguishes_bodyless_declarations_and_definitions :: proc(t: ^testing.T) {
text := `foreign :: c func(value i32) i32 text := `foreign :: c_func(value i32) i32
defined :: c func(value i32) i32 defined :: c_func(value i32) i32
{ {
return value return value
} }
@@ -229,7 +231,7 @@ main :: func() void {}
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) { parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
text := `c :: 5 text := `c :: 5
x :: c x :: c
foreign :: c func() i32 foreign :: c_func() i32
broken :: c 5 broken :: c 5
main :: func() void {} main :: func() void {}
` `
@@ -403,6 +405,8 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
"thing", "thing",
"--link", "--link",
"helper.o", "helper.o",
"--target",
"aarch64-macos",
}) })
defer delete(options.link_arguments) 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[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[2].kind, linker.Kind.Library)
testing.expect_value(t, options.link_arguments[3].value, "helper.o") 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"}) _, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"})
_, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"}) _, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"})
_, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"}) _, 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"}) _, 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, !unknown_valid)
testing.expect(t, !incomplete_valid) testing.expect(t, !incomplete_valid)
testing.expect(t, !duplicate_output_valid) testing.expect(t, !duplicate_output_valid)
testing.expect(t, !duplicate_empty_output_valid) testing.expect(t, !duplicate_empty_output_valid)
testing.expect(t, !invalid_target)
} }
@(test) @(test)
@@ -453,7 +460,7 @@ main :: func() void {}
} }
@(test) @(test)
parser_rejects_chained_package_access :: proc(t: ^testing.T) { parser_accepts_chained_field_access :: proc(t: ^testing.T) {
text := `main :: func() void { text := `main :: func() void {
_ = first.second.value _ = first.second.value
} }
@@ -468,7 +475,7 @@ parser_rejects_chained_package_access :: proc(t: ^testing.T) {
module := parser.parse(&stream, &source_file, &diagnostics) module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module) defer ast.destroy_module(&module)
testing.expect(t, len(diagnostics.items) > 0) testing.expect_value(t, len(diagnostics.items), 0)
} }
@(test) @(test)
@@ -566,7 +573,7 @@ main :: func() void {
@(test) @(test)
pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) { 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 return a + b
} }
sum_bro :: func(a, b int) int { 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, len(diagnostics.items), 0)
testing.expect_value(t, llvm_text, second_llvm_text) 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, "define internal fastcc i8 @bro__p0__sum_bro__i8__i8"))
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8")) testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
} }
@(test) @(test)
pipeline_emits_only_referenced_foreign_declarations_with_exact_names :: proc(t: ^testing.T) { pipeline_emits_only_referenced_foreign_declarations_with_exact_names :: proc(t: ^testing.T) {
text := `used :: c func(a, b i32) i32 text := `used :: c_func(a, b i32) i32
unused :: c func() i32 unused :: c_func() i32
bodyful :: c func(value i32) i32 { bodyful :: c_func(value i32) i32 {
return value return value
} }
main :: func() void { main :: func() void {
@@ -637,9 +644,126 @@ main :: func() void {
testing.expect(t, strings.contains(llvm_text, "define i32 @bro_c__p0__bodyful__i32")) 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) @(test)
invalid_foreign_declarations_are_eagerly_diagnosed_and_calls_trap :: proc(t: ^testing.T) { 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 native :: func() i32
main :: func() void { main :: func() void {
_ = bad(1) _ = bad(1)
@@ -667,7 +791,7 @@ main :: func() void {
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
found_parameter = found_parameter || strings.contains(diagnostic.message, "requires concrete parameter types") 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_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_parameter)
testing.expect(t, found_result) testing.expect(t, found_result)
@@ -707,7 +831,7 @@ duplicate_foreign_symbols_across_packages_are_poisoned :: proc(t: ^testing.T) {
@(test) @(test)
bodyless_root_main_recovers_as_a_trap_definition :: proc(t: ^testing.T) { 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} source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file) diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics) defer source.destroy_diagnostics(&diagnostics)
@@ -974,7 +1098,7 @@ eager_global_calls_root_specializations :: proc(t: ^testing.T) {
unused_native :: func() i32 { unused_native :: func() i32 {
return 9 return 9
} }
unused_foreign :: c func() i32 unused_foreign :: c_func() i32
value i32 :: make() value i32 :: make()
main :: func() void {} main :: func() void {}
` `
@@ -1040,16 +1164,16 @@ long_generic_call_chain_reaches_a_fixed_point :: proc(t: ^testing.T) {
builder := strings.builder_make() builder := strings.builder_make()
defer strings.builder_destroy(&builder) defer strings.builder_destroy(&builder)
for index in 0 ..< 70 { 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 ") strings.write_string(&builder, "{ return ")
if index == 69 { if index == 69 {
strings.write_string(&builder, "value") strings.write_string(&builder, "value")
} else { } 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, " }\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)} source_file := source.Source{path="test.bro", text=strings.to_string(builder)}
diagnostics := source.init_diagnostics(&source_file) 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) 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) @(test)
foreign_function_links_from_object :: proc(t: ^testing.T) { foreign_function_links_from_object :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-foreign-object" output := "/tmp/brolang-test-foreign-object"
@@ -1442,6 +1577,8 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
"/usr/bin/env", "/usr/bin/env",
"zig", "zig",
"cc", "cc",
"-target",
"aarch64-macos",
"-Wno-override-module", "-Wno-override-module",
"module.ll", "module.ll",
"native.c", "native.c",
@@ -2127,8 +2264,8 @@ package_llvm_is_deterministic_and_symbols_include_package_ids :: proc(t: ^testin
testing.expect(t, loaded) testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0) testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, llvm_text, second_llvm_text) 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 signext 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__p2__same"))
testing.expect(t, strings.contains(llvm_text, "define i32 @main()")) testing.expect(t, strings.contains(llvm_text, "define i32 @main()"))
} }
+29
View File
@@ -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)
}
+42
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
foreign_add :: c func(a, b i32) i32 foreign_add :: c_func(a, b i32) i32
main :: func() i32 { main :: func() i32 {
return foreign_add(20, 22) return foreign_add(20, 22)
+1 -1
View File
@@ -1,3 +1,3 @@
same :: c func() int { same :: c_func() int {
return 1 return 1
} }
+1 -1
View File
@@ -1,3 +1,3 @@
same :: c func() int { same :: c_func() int {
return 2 return 2
} }
@@ -1 +1 @@
same :: c func() i32 same :: c_func() i32
@@ -1 +1 @@
same :: c func() i32 same :: c_func() i32
+1 -1
View File
@@ -2,7 +2,7 @@
x int :: 2 x int :: 2
sum_c :: c func(a, b int) int { sum_c :: c_func(a, b int) int {
return a + b return a + b
} }
+12 -3
View File
@@ -2,6 +2,7 @@ package main
import "./compiler" import "./compiler"
import "./compiler/linker" import "./compiler/linker"
import "./compiler/target"
import "core:fmt" import "core:fmt"
import "core:os/os2" import "core:os/os2"
@@ -9,13 +10,14 @@ Cli_Options :: struct {
input_path: string, input_path: string,
output_path: string, output_path: string,
link_arguments: []linker.Argument, link_arguments: []linker.Argument,
target: target.Target,
} }
parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_Options, bool) { parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_Options, bool) {
if len(args) < 4 { if len(args) < 4 {
return {}, false 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: [dynamic]linker.Argument
link_arguments.allocator = allocator link_arguments.allocator = allocator
output_set := false 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}) append(&link_arguments, linker.Argument{kind=.Library_Path, value=value})
case "--library": case "--library":
append(&link_arguments, linker.Argument{kind=.Library, value=value}) 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: case:
delete(link_arguments) delete(link_arguments)
return {}, false return {}, false
@@ -58,7 +67,7 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
print_usage :: proc() { print_usage :: proc() {
fmt.eprintln( 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) os2.exit(2)
} }
defer delete(options.link_arguments) 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 { if status != 0 {
os2.exit(status) os2.exit(status)
} }