diff --git a/LANGUAGE.md b/LANGUAGE.md index 0a87de4..d6555f5 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -12,17 +12,24 @@ ### types and expressions -- `i8`, `i16`, `i32`, `i64`, `void`, and inferred integer-constrained `int` +- exact-width `i8` through `i64`, `u8` through `u64`, `f32`, `f64`, `isize`, `usize`, `void`, and inferred integer-constrained `int` +- target-dependent atomic C primitives from `c_char` through `c_longdouble` +- C primitives remain semantically distinct from exact-width Brolang primitives until target lowering - contextual integer literals and constant folding of addition and negation trees -- checked signed integer addition and unary negation -- function calls, assignments, and returns +- strict numeric conversions, checked integer addition, and unary negation +- arrays `[N]T`, sentinel arrays `[N;S]T`, indexing, pointers, pointer offsets, slices, and explicit slicing +- immutable UTF-8 sentinel-slice strings and Unicode code-point character literals +- optionals with trapping postfix `?`, `orelse`, and nullable pointer representation +- source-order native structs, keyed literals, and defined or opaque pointer-only `c_struct` +- postfix pointer dereference, explicit `.ptr`/`.len`, general writable locations, function calls, assignments, and returns ### functions and packages - demand-monomorphized functions -- bodyful `c func` definitions using the c calling convention -- bodyless `c func` declarations with exact, globally unique external symbol names +- bodyful `c_func` definitions using the c calling convention +- bodyless `c_func` declarations with exact, globally unique external symbol names - concrete-only foreign signatures +- Apple Silicon C ABI scalar and pointer lowering, including narrow integer extension attributes - directory packages with merged declarations - file-local relative imports, aliases, and qualified member access @@ -43,20 +50,12 @@ ### scalar and compound types -- unsigned integers, floats, characters, and target-dependent c scalar types -- arrays `[N]T` and sentinel arrays `[N; S]T` -- single-item pointers `@T` and many-item pointers `*T` -- element and pointee mutability through `mut`, separate from binding mutability -- slices `[]T` and sentinel slices `[; S]T` -- string literals as immutable sentinel slices backed by static arrays -- character literals -- optionals with trapping `?` unwrap, `orelse` fallback, and nullable pointers -- data-only native structs and target-layout `c struct` types - tuples and native variadic functions +- C unions, C enums, and by-value C record ABI lowering ### c imports - c headers imported as synthetic package namespaces - typedefs, enums, opaque records, and external variables - function pointers, callbacks, macros, and static inline functions -- target-specific by-value c record and union ABI lowering +- target-specific by-value C record and union ABI lowering diff --git a/README.md b/README.md index 25989a7..b4b6ed7 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,12 @@ odin build . -out:build/brolang ./build/prototype ``` -Bodyless `c func` declarations bind exact external symbols and require concrete -types: +Bodyless `c_func` declarations bind exact external symbols and require concrete +types. C primitives use atomic target-dependent names and remain semantically +distinct from exact-width Brolang primitives: ```bro -foreign_add :: c func(a, b i32) i32 +strlen :: c_func(value *c_char) c_ulong ``` Additional native inputs and libraries are passed to the final `zig cc` @@ -56,7 +57,10 @@ Current prototype features: - Newline-terminated, multiline statements; `}` may terminate a block's final statement - `#` comments - Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks -- `i8`, `i16`, `i32`, `i64`, and loose integer-constrained `int` +- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int` +- Target-dependent atomic `c_*` primitive types, `c_func`, and pointer-only `c_struct` +- Arrays, sentinel arrays, pointers, slices, sentinel slices, strings, character literals, optionals, and native structs +- Explicit `.ptr`/`.len`, slicing, postfix pointer dereference and optional unwrap, and keyed struct literals - Contextual integer constants and compile-time folding of addition and unary negation trees - Directory packages with merged declarations and file-local relative imports - Qualified imported globals and functions with package-aware symbol mangling diff --git a/TODO.md b/TODO.md index 1343bc4..dc17cc6 100644 --- a/TODO.md +++ b/TODO.md @@ -4,8 +4,10 @@ # milestones -1. interop type foundation +1. interop type foundation (implemented) - unsigned integers, floats, and target-dependent c scalar types + - atomic `c_*` primitive types remain distinct until target-aware lowering + - `c_func` and pointer-only `c_struct`; `c` remains an ordinary identifier - keep binding mutability (`::` / `=`) separate from element or pointee mutability (`mut`) - arrays and indexing - `[N]T`: array with `N` logical elements @@ -13,7 +15,7 @@ - pointers - `@T` / `@mut T`: non-null single-item pointer without arithmetic - `*T` / `*mut T`: non-null many-item pointer with arithmetic - - optional pointers represent nullable pointers + - optional pointers represent nullable pointers (i.e. `?@T` / `?@mut T`, `?*T` / `?*mut T`) - slices and slicing - `[]T`: pointer and length - `[;S]T`: pointer and length with a sentinel invariant @@ -22,9 +24,9 @@ - character literals - optionals with trapping unwrap and fallback operations - native structs with compiler-controlled layout - - pointer-only `c struct` support with target c layout - - `Some :: c struct { ... }`: defined c-layout struct - - `Some :: c struct`: opaque c-layout struct + - pointer-only `c_struct` support with target c layout + - `Some :: c_struct { ... }`: defined c-layout struct + - `Some :: c_struct`: opaque c-layout struct - defer passing c structs by value until target ABI classification exists 2. restricted c header imports diff --git a/compiler/ast/ast.odin b/compiler/ast/ast.odin index 3bd9dad..bbbe126 100644 --- a/compiler/ast/ast.odin +++ b/compiler/ast/ast.odin @@ -2,6 +2,7 @@ package ast import "../source" import "../symbol" +import "../types" import "core:mem" Expr_Id :: distinct u32 @@ -60,20 +61,25 @@ index :: proc(id: $T, invalid: T, count: int) -> (int, bool) { return value, id != invalid && value < count } -Type_Syntax :: enum u8 { - Invalid, - Int, - I8, - I16, - I32, - I64, - Void, -} +Type_Syntax :: types.Type Expr_Kind :: enum u8 { Invalid, Integer, + Float, + String, + Array, + None, Name, + Address, + Deref, + Index, + Slice, + Field, + Unwrap, + Orelse, + Struct_Literal, + Keyed, Negate, Add, Call, @@ -111,6 +117,7 @@ Stmt :: struct { name: symbol.Id, type: Type_Syntax, immutable: bool, + target: Expr_Id, expr: Expr_Id, diagnostic: source.Diagnostic_Id, } @@ -170,12 +177,15 @@ Module :: struct { imports: [dynamic]Import, files: [dynamic]File, packages: [dynamic]Package, + strings: [dynamic]string, + type_store: types.Store, allocator: mem.Allocator, } init_module :: proc(allocator := context.allocator) -> Module { module: Module module.allocator = allocator + module.type_store = types.init_store(allocator) module.exprs.allocator = allocator module.statements.allocator = allocator module.functions.allocator = allocator @@ -183,6 +193,7 @@ init_module :: proc(allocator := context.allocator) -> Module { module.imports.allocator = allocator module.files.allocator = allocator module.packages.allocator = allocator + module.strings.allocator = allocator return module } @@ -200,6 +211,9 @@ destroy_module :: proc(module: ^Module) { for pkg in module.packages { delete(pkg.path, module.allocator) } + for value in module.strings { + delete(value, module.allocator) + } delete(module.exprs) delete(module.statements) delete(module.functions) @@ -207,4 +221,6 @@ destroy_module :: proc(module: ^Module) { delete(module.imports) delete(module.files) delete(module.packages) + delete(module.strings) + types.destroy_store(&module.type_store) } diff --git a/compiler/backend/backend.odin b/compiler/backend/backend.odin index cae10bd..e3b5805 100644 --- a/compiler/backend/backend.odin +++ b/compiler/backend/backend.odin @@ -1,6 +1,7 @@ package backend import "../linker" +import "../target" import "core:fmt" import "core:mem" import "core:os" @@ -14,6 +15,7 @@ append_owned :: proc(command: ^[dynamic]string, value: string, allocator: mem.Al build_command :: proc( llvm_path, output_path: string, link_arguments: []linker.Argument, + selected := target.DEFAULT, allocator := context.allocator, ) -> []string { command: [dynamic]string @@ -21,6 +23,8 @@ build_command :: proc( append_owned(&command, "/usr/bin/env", allocator) append_owned(&command, "zig", allocator) append_owned(&command, "cc", allocator) + append_owned(&command, "-target", allocator) + append_owned(&command, target.name(selected), allocator) append_owned(&command, "-Wno-override-module", allocator) append_owned(&command, llvm_path, allocator) for argument in link_arguments { @@ -45,12 +49,16 @@ destroy_command :: proc(command: []string, allocator := context.allocator) { delete(command, allocator) } -compile :: proc(llvm_path, output_path: string, link_arguments: []linker.Argument = nil) -> bool { +compile :: proc( + llvm_path, output_path: string, + link_arguments: []linker.Argument = nil, + selected := target.DEFAULT, +) -> bool { pid := os2.get_pid() temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid) defer _ = os.remove(temporary_output) - command := build_command(llvm_path, temporary_output, link_arguments) + command := build_command(llvm_path, temporary_output, link_arguments, selected) defer destroy_command(command) state, stdout, stderr, err := os2.process_exec( os2.Process_Desc{command=command}, diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 8ad95ef..0b9e5e2 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -4,6 +4,7 @@ import "../ast" import "../hir" import "../source" import "../symbol" +import "../target" import "../types" import "base:intrinsics" import "core:fmt" @@ -93,6 +94,7 @@ Checker :: struct { cycle_stack: [dynamic]Cycle_Frame, main_symbol: symbol.Id, sink_symbol: symbol.Id, + target: target.Target, allocator: mem.Allocator, } @@ -187,36 +189,49 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant { return checker.constants[expr_id] } -fits_signed_type :: proc(value: i128, target: types.Type) -> bool { - if !types.is_signed(target) { +fits_signed_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool { + if !types.is_signed(value_type, selected) { return false } - limit := i128(1) << u32(target.bits - 1) + limit := i128(1) << u32(types.bits(value_type, selected) - 1) return value >= -limit && value < limit } +fits_unsigned_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool { + if !types.is_unsigned(value_type, selected) || value < 0 { + return false + } + limit := i128(1) << u32(types.bits(value_type, selected)) + return value < limit +} + +fits_integer_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool { + return fits_signed_type(value, value_type, selected) || fits_unsigned_type(value, value_type, selected) +} + fits_i64 :: proc(value: i128) -> bool { return fits_signed_type(value, types.I64) } type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type { - switch value { - case .Int: - return types.INT - case .I8: - return types.I8 - case .I16: - return types.I16 - case .I32: - return types.I32 - case .I64: - return types.I64 - case .Void: - return types.VOID - case .Invalid: - return types.INVALID + return value +} + +is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool { + return types.is_runtime_value(value, &checker.module.types) +} + +resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: ast.Expr_Id) -> types.Type { + item, ok := types.node(&checker.module.types, value) + if !ok || item.kind != .Array || !item.inferred_count || + expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) { + return value } - return types.INVALID + expr := checker.ast_module.exprs[expr_id] + if expr.kind != .Array { + return value + } + return types.with_array_count(&checker.module.types, value, u64(len(expr.args))) } function_index_less :: proc(left, right: Function_Index_Entry) -> bool { @@ -423,11 +438,16 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as switch expr.kind { case .Call: append(&stack, ..expr.args) - case .Negate: + case .Array, .Struct_Literal, .Slice: + append(&stack, ..expr.args) + if expr.left != ast.INVALID_EXPR { + append(&stack, expr.left) + } + case .Negate, .Address, .Deref, .Field, .Unwrap, .Keyed: append(&stack, expr.left) - case .Add: + case .Add, .Index, .Orelse: append(&stack, expr.left, expr.right) - case .Invalid, .Integer, .Name: + case .Invalid, .Integer, .Float, .String, .None, .Name: } } } @@ -437,7 +457,7 @@ validate_declarations :: proc(checker: ^Checker) { locals: [dynamic]symbol.Id locals.allocator = checker.allocator for param in function.params { - if param.type == .Void { + if param.type == types.VOID { source.add( checker.diagnostics, param.span, @@ -453,18 +473,34 @@ validate_declarations :: proc(checker: ^Checker) { ) } append(&locals, param.name) + if types.contains_c_struct_by_value(type_from_syntax(param.type), &checker.module.types) { + checker.template_diagnostics[function_id] = source.addf( + checker.diagnostics, + param.span, + "C records cannot be passed by value to '%s'", + symbol_text(checker, function.name), + ) + } + } + if types.contains_c_struct_by_value(type_from_syntax(function.result), &checker.module.types) { + checker.template_diagnostics[function_id] = source.addf( + checker.diagnostics, + function.span, + "C records cannot be returned by value from '%s'", + symbol_text(checker, function.name), + ) } if !function.has_body && !function.c_abi { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, function.span, - "bodyless function '%s' must use 'c func'", + "bodyless function '%s' must use 'c_func'", symbol_text(checker, function.name), ) } if !function.has_body && function.c_abi { for param in function.params { - if type_from_syntax(param.type).kind != .Concrete { + if !types.is_c_signature_type(type_from_syntax(param.type), &checker.module.types) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, param.span, @@ -474,7 +510,7 @@ validate_declarations :: proc(checker: ^Checker) { } } result := type_from_syntax(function.result) - if result.kind != .Concrete && result.kind != .Void { + if !types.is_c_signature_type(result, &checker.module.types, true) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, function.span, @@ -495,6 +531,9 @@ validate_declarations :: proc(checker: ^Checker) { switch statement.kind { case .Declaration, .Assignment, .Return, .Expression: mark_expr_imports_used(checker, statement.expr, function.file) + if statement.target != ast.INVALID_EXPR { + mark_expr_imports_used(checker, statement.target, function.file) + } case .Invalid: } } @@ -519,6 +558,37 @@ validate_declarations :: proc(checker: ^Checker) { } } +validate_type_nodes :: proc(checker: ^Checker) { + for item, index in checker.module.types.nodes { + id := types.DYNAMIC_START+types.Type(index) + if item.has_sentinel { + value := i128(item.sentinel) + if types.is_signed(item.child, checker.target) { + value = i128(i64(item.sentinel)) + } + if !types.is_concrete_integer(item.child) || !fits_integer_type(value, item.child, checker.target) { + source.addf( + checker.diagnostics, + source.Span{}, + "sentinel value does not fit array or slice element type %s", + types.name(item.child), + ) + } + } + if item.kind == .Struct { + for field in types.fields_for(&checker.module.types, id) { + if types.contains_c_struct_by_value(field.type, &checker.module.types) { + source.add( + checker.diagnostics, + source.Span{}, + "C records may only appear behind pointers", + ) + } + } + } + } +} + find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type { for index := len(locals) - 1; index >= 0; index -= 1 { if locals[index].name == name { @@ -554,19 +624,19 @@ find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []t specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> types.Type { declared := type_from_syntax(syntax) - if declared.kind == .Int_Constraint { + if types.is_constraint(declared) { return actual } return declared } -can_specialize :: proc(function: ast.Function, actual_args: []types.Type) -> bool { +can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: []types.Type) -> bool { for param, index in function.params { actual := types.INVALID if index < len(actual_args) { actual = actual_args[index] } - if !types.is_concrete_integer(specialized_param_type(param.type, actual)) { + if !is_runtime_type(checker, specialized_param_type(param.type, actual)) { return false } } @@ -588,7 +658,7 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [ append(&signature, specialized_param_type(param.type, actual)) } result := type_from_syntax(function.result) - if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int { + if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT { result = types.I32 } index := spec_id(len(checker.specs)) @@ -616,6 +686,111 @@ Infer_Frame :: struct { template: ast.Function_Id, } +infer_nested_expr :: proc( + checker: ^Checker, + expr_id: ast.Expr_Id, + locals: []Infer_Local, + pkg: ast.Package_Id, + file: ast.File_Id, + demanded: ^[dynamic]Spec_Id, +) -> types.Type { + outer := checker.infer_stack + checker.infer_stack = nil + checker.infer_stack.allocator = checker.allocator + result := infer_expr(checker, expr_id, locals, pkg, file, demanded) + delete(checker.infer_stack) + checker.infer_stack = outer + return result +} + +infer_compound_expr :: proc( + checker: ^Checker, + expr: ast.Expr, + locals: []Infer_Local, + pkg: ast.Package_Id, + file: ast.File_Id, + demanded: ^[dynamic]Spec_Id, +) -> types.Type { + store := &checker.module.types + #partial switch expr.kind { + case .String: + return types.slice(store, types.U8, false, true, 0) + case .Array: + element := types.INVALID + for arg in expr.args { + actual := infer_nested_expr(checker, arg, locals, pkg, file, demanded) + if !types.is_valid(element) { + element = actual + } else if !types.equal(element, actual) { + element = types.widest(element, actual) + } + } + if !types.is_valid(element) { + element = types.I64 + } + return types.array(store, element, u64(len(expr.args)), false) + case .None: + return types.INVALID + case .Address: + child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + return types.pointer(store, child, false, false) + case .Deref: + value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + return types.child_type(value, store) if types.is_pointer(value, store) else types.INVALID + case .Index: + value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + _ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded) + return types.child_type(value, store) + case .Slice: + value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + item, ok := types.node(store, value) + if !ok || (item.kind != .Array && item.kind != .Slice) { + return types.INVALID + } + for bound in expr.args { + if bound != ast.INVALID_EXPR { + _ = infer_nested_expr(checker, bound, locals, pkg, file, demanded) + } + } + preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR + return types.slice(store, item.child, item.mutable, preserve, item.sentinel) + case .Field: + value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + item, has_item := types.node(store, value) + field_name := symbol_text(checker, expr.name) + if has_item && (item.kind == .Array || item.kind == .Slice) { + if field_name == "len" { + return types.USIZE + } + if field_name == "ptr" { + return types.pointer(store, item.child, item.mutable, true) + } + } + if types.is_pointer(value, store) { + value = types.child_type(value, store) + } + _, field, ok := find_struct_field(checker, value, expr.name) + return field.type if ok else types.INVALID + case .Unwrap: + value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID + case .Orelse: + value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + _ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded) + return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID + case .Struct_Literal: + for keyed in expr.args { + _ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded) + } + target_pkg, available := expr_package(checker, expr, pkg, file) + return types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID + case .Keyed: + return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + case: + return types.INVALID + } +} + infer_expr :: proc( checker: ^Checker, expr_id: ast.Expr_Id, @@ -667,10 +842,37 @@ infer_expr :: proc( last = types.smallest_signed_for_literal(i64(expr.integer)) } _ = pop(&stack) + case .Float: + last = types.F64 + _ = pop(&stack) + case .String, .Array, .None, .Address, .Deref, .Index, .Slice, + .Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed: + last = infer_compound_expr(checker, expr, locals, pkg, file, demanded) + _ = pop(&stack) case .Name: last = types.INVALID if !symbol.is_valid(expr.qualifier) { last = find_infer_local(locals, expr.name) + } else { + base_type := find_infer_local(locals, expr.qualifier) + item, has_item := types.node(&checker.module.types, base_type) + field_name := symbol_text(checker, expr.name) + if has_item && (item.kind == .Array || item.kind == .Slice) { + if field_name == "len" { + last = types.USIZE + } else if field_name == "ptr" { + last = types.pointer(&checker.module.types, item.child, item.mutable, true) + } + } + if types.is_pointer(base_type, &checker.module.types) { + base_type = types.child_type(base_type, &checker.module.types) + } + if !types.is_valid(last) { + _, field, ok := find_struct_field(checker, base_type, expr.name) + if ok { + last = field.type + } + } } if !types.is_valid(last) { target_pkg, available := expr_package(checker, expr, pkg, file) @@ -701,7 +903,7 @@ infer_expr :: proc( } if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC { declared := type_from_syntax(checker.ast_module.functions[template].result) - last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID + last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID _ = pop(&stack) continue } @@ -715,7 +917,7 @@ infer_expr :: proc( continue } if frame.stage == 5 { - if !types.is_signed(last) { + if !types.is_signed(last, checker.target) && !types.is_float(last, checker.target) { last = types.INVALID } _ = pop(&stack) @@ -728,7 +930,11 @@ infer_expr :: proc( continue } if frame.stage == 2 { - last = types.widest(frame.left, last) + if types.is_many_pointer(frame.left, &checker.module.types) && types.is_concrete_integer(last) { + last = frame.left + } else { + last = types.widest(frame.left, last) + } _ = pop(&stack) continue } @@ -742,7 +948,7 @@ infer_expr :: proc( } } function := checker.ast_module.functions[frame.template] - if can_specialize(function, stack[frame_index].args) { + if can_specialize(checker, function, stack[frame_index].args) { spec := INVALID_SPEC if demanded == nil { spec = ensure_spec(checker, frame.template, stack[frame_index].args) @@ -754,14 +960,14 @@ infer_expr :: proc( last = checker.specs[spec].result } else { declared := type_from_syntax(function.result) - last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID + last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID } } else { declared := type_from_syntax(function.result) - if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int { + if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT { last = types.I32 } else { - last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID + last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID } } delete(stack[frame_index].args, checker.allocator) @@ -776,7 +982,7 @@ infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Sp spec := checker.specs[id] function := checker.ast_module.functions[spec.template] declared := type_from_syntax(function.result) - if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int { + if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT { declared = types.I32 } @@ -798,11 +1004,14 @@ infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Sp case .Declaration: value_type := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded) declared_local := type_from_syntax(statement.type) - if declared_local.kind == .Concrete { + if is_runtime_type(checker, declared_local) { value_type = declared_local } append(&locals, Infer_Local{name = statement.name, type = value_type}) case .Assignment, .Expression: + if statement.target != ast.INVALID_EXPR { + _ = infer_expr(checker, statement.target, locals[:], function.pkg, function.file, demanded) + } _ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded) case .Return: if statement.expr != ast.INVALID_EXPR { @@ -815,22 +1024,25 @@ infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Sp } } } - if declared.kind == .Int_Constraint { + if types.is_constraint(declared) { return result } return declared } -merge_inferred_type :: proc(current: ^types.Type, inferred: types.Type) -> bool { - if !types.is_concrete_integer(inferred) { +merge_inferred_type :: proc(store: ^types.Store, current: ^types.Type, inferred: types.Type) -> bool { + if !types.is_runtime_value(inferred, store) { return false } - if !types.is_concrete_integer(current^) { + if !types.is_runtime_value(current^, store) { current^ = inferred return true } + if types.equal(current^, inferred) { + return false + } merged := types.widest(current^, inferred) - if types.is_concrete_integer(merged) && !types.equal(current^, merged) { + if types.is_concrete_scalar(merged) && !types.equal(current^, merged) { current^ = merged return true } @@ -840,7 +1052,7 @@ merge_inferred_type :: proc(current: ^types.Type, inferred: types.Type) -> bool infer_all :: proc(checker: ^Checker) { for global, index in checker.ast_module.globals { declared := type_from_syntax(global.type) - if declared.kind == .Concrete { + if is_runtime_type(checker, declared) { checker.global_types[index] = declared } } @@ -855,15 +1067,15 @@ infer_all :: proc(checker: ^Checker) { spec_count := len(checker.specs) for global, index in checker.ast_module.globals { inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file) - if type_from_syntax(global.type).kind == .Concrete { + if is_runtime_type(checker, type_from_syntax(global.type)) { continue } - changed = merge_inferred_type(&checker.global_types[index], inferred) || changed + changed = merge_inferred_type(&checker.module.types, &checker.global_types[index], inferred) || changed } for index := 0; index < len(checker.specs); index += 1 { id := spec_id(index) inferred := infer_spec_result(checker, id) - changed = merge_inferred_type(&checker.specs[id].result, inferred) || changed + changed = merge_inferred_type(&checker.module.types, &checker.specs[id].result, inferred) || changed } if len(checker.specs) != spec_count { changed = true @@ -972,6 +1184,27 @@ coerce_expr :: proc( if types.equal(actual, expected) { return expr_id } + if types.can_weaken_pointer(actual, expected, &checker.module.types) { + checker.module.exprs[expr_id].type = expected + return expr_id + } + if types.is_optional(expected, &checker.module.types) { + child := types.child_type(expected, &checker.module.types) + if types.equal(actual, child) || + types.can_widen(actual, child) || + types.can_weaken_pointer(actual, child, &checker.module.types) { + value := coerce_expr(checker, expr_id, child, span) + return add_hir_expr(checker, hir.Expr{ + kind=.Optional_Some, + span=span, + type=expected, + left=value, + target=hir.INVALID_REF, + right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + } if types.can_widen(actual, expected) { return add_hir_expr( checker, @@ -1003,10 +1236,11 @@ build_constant_expr :: proc( expected: types.Type, ) -> hir.Expr_Id { recovery_type := types.I64 - if types.is_signed(expected) { + if types.is_concrete_integer(expected) { recovery_type = expected } - if constant.kind == .Overflow || !fits_i64(constant.value) { + if constant.kind == .Overflow || + (!types.is_concrete_integer(expected) && !fits_i64(constant.value)) { id := source.add( checker.diagnostics, expr.span, @@ -1016,9 +1250,12 @@ build_constant_expr :: proc( } value := i64(constant.value) + if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) { + value = transmute(i64)u64(constant.value) + } result_type := types.smallest_signed_for_literal(value) - if types.is_signed(expected) { - if !fits_signed_type(constant.value, expected) { + if types.is_concrete_integer(expected) { + if !fits_integer_type(constant.value, expected, checker.target) { id := source.addf( checker.diagnostics, expr.span, @@ -1045,6 +1282,36 @@ build_constant_expr :: proc( ) } +build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type) -> hir.Expr_Id { + result_type := types.F64 + if types.is_float(expected, checker.target) { + result_type = expected + } else if types.is_valid(expected) { + id := source.addf( + checker.diagnostics, + expr.span, + "cannot implicitly convert f64 to %s", + types.name(expected), + ) + return invalid_hir_expr(checker, expr.span, id, expected) + } + value := transmute(f64)expr.integer + bits := transmute(i64)value + if types.bits(result_type, checker.target) == 32 { + bits = i64(transmute(u32)f32(value)) + } + return add_hir_expr(checker, hir.Expr{ + kind=.Float, + span=expr.span, + type=result_type, + integer=bits, + target=hir.INVALID_REF, + left=hir.INVALID_EXPR, + right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) +} + Build_Expr_Frame :: struct { expr: ast.Expr_Id, expected: types.Type, @@ -1056,6 +1323,329 @@ Build_Expr_Frame :: struct { template: ast.Function_Id, } +hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool { + if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) { + return false + } + expr := checker.module.exprs[expr_id] + #partial switch expr.kind { + case .Local: + id := hir.as_local(expr.target) + for local in locals { + if local.id == id { + return local.mutable + } + } + case .Deref: + pointer_type := checker.module.exprs[expr.left].type + return types.is_mutable(pointer_type, &checker.module.types) + case .Index: + container_type := checker.module.exprs[expr.left].type + return types.is_mutable(container_type, &checker.module.types) + case .Field: + base_type := checker.module.exprs[expr.left].type + if types.is_pointer(base_type, &checker.module.types) { + return types.is_mutable(base_type, &checker.module.types) + } + return hir_location_writable(checker, expr.left, locals) + case: + } + return false +} + +hir_is_location :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool { + if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) { + return false + } + #partial switch checker.module.exprs[expr_id].kind { + case .Local, .Global, .Deref, .Index, .Field: + return true + } + return false +} + +find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symbol.Id) -> (int, types.Field, bool) { + for field, index in types.fields_for(&checker.module.types, struct_type) { + if field.name == u32(name) { + return index, field, true + } + } + return 0, {}, false +} + +build_nested_expr :: proc( + checker: ^Checker, + expr_id: ast.Expr_Id, + locals: []Build_Local, + global_reads: ^[dynamic]hir.Global_Id, + calls: ^[dynamic]hir.Function_Id, + expected: types.Type, + pkg: ast.Package_Id, + file: ast.File_Id, +) -> hir.Expr_Id { + outer := checker.build_stack + checker.build_stack = nil + checker.build_stack.allocator = checker.allocator + result := build_expr(checker, expr_id, locals, global_reads, calls, expected, pkg, file) + delete(checker.build_stack) + checker.build_stack = outer + return result +} + +build_compound_expr :: proc( + checker: ^Checker, + expr: ast.Expr, + locals: []Build_Local, + global_reads: ^[dynamic]hir.Global_Id, + calls: ^[dynamic]hir.Function_Id, + expected: types.Type, + pkg: ast.Package_Id, + file: ast.File_Id, +) -> hir.Expr_Id { + store := &checker.module.types + #partial switch expr.kind { + case .String: + string_type := types.slice(store, types.U8, false, true, 0) + return add_hir_expr(checker, hir.Expr{ + kind=.String, span=expr.span, type=string_type, integer=i64(expr.integer), + target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Array: + element_type := types.INVALID + result_type := expected + expected_node, has_expected := types.node(store, expected) + if !has_expected || expected_node.kind != .Array { + has_expected = false + result_type = types.INVALID + } else { + element_type = expected_node.child + if !expected_node.inferred_count && expected_node.count != u64(len(expr.args)) { + id := source.addf( + checker.diagnostics, expr.span, + "array literal expects %d elements, got %d", + expected_node.count, len(expr.args), + ) + return invalid_hir_expr(checker, expr.span, id, expected) + } + if expected_node.inferred_count { + result_type = types.with_array_count(store, expected, u64(len(expr.args))) + } + } + if !has_expected { + infer_locals := make([]Infer_Local, len(locals), checker.allocator) + defer delete(infer_locals, checker.allocator) + for local, index in locals { + infer_locals[index] = Infer_Local{name=local.name, type=local.type} + } + for arg in expr.args { + actual := infer_nested_expr(checker, arg, infer_locals, pkg, file, nil) + if !types.is_valid(element_type) { + element_type = actual + } else { + element_type = types.widest(element_type, actual) + } + } + if !types.is_valid(element_type) { + element_type = types.I64 + } + result_type = types.array(store, element_type, u64(len(expr.args)), false) + } + args := make([]hir.Expr_Id, len(expr.args), checker.allocator) + for arg, index in expr.args { + args[index] = build_nested_expr( + checker, arg, locals, global_reads, calls, element_type, pkg, file, + ) + args[index] = coerce_expr(checker, args[index], element_type, checker.module.exprs[args[index]].span) + } + return add_hir_expr(checker, hir.Expr{ + kind=.Array, span=expr.span, type=result_type, args=args, + target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .None: + if !types.is_optional(expected, store) { + id := source.add(checker.diagnostics, expr.span, "'none' requires an optional context") + return invalid_hir_expr(checker, expr.span, id, expected) + } + return add_hir_expr(checker, hir.Expr{ + kind=.None, span=expr.span, type=expected, target=hir.INVALID_REF, + left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Address: + value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) + if !hir_is_location(checker, value) { + id := source.add(checker.diagnostics, expr.span, "'&' requires an addressable location") + return invalid_hir_expr(checker, expr.span, id) + } + value_type := checker.module.exprs[value].type + writable := hir_location_writable(checker, value, locals) + result_type := types.pointer(store, value_type, writable, false) + if types.is_pointer(expected, store) && + types.equal(types.child_type(expected, store), value_type) && + (!types.is_mutable(expected, store) || writable) { + result_type = expected + } + return add_hir_expr(checker, hir.Expr{ + kind=.Address, span=expr.span, type=result_type, left=value, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Deref: + pointer := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) + pointer_type := checker.module.exprs[pointer].type + if !types.is_pointer(pointer_type, store) { + id := source.add(checker.diagnostics, expr.span, "postfix '^' requires a pointer") + return invalid_hir_expr(checker, expr.span, id) + } + return add_hir_expr(checker, hir.Expr{ + kind=.Deref, span=expr.span, type=types.child_type(pointer_type, store), left=pointer, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Index: + container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) + index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file) + container_type := checker.module.exprs[container].type + item, ok := types.node(store, container_type) + if !ok || (item.kind != .Array && item.kind != .Slice && !(item.kind == .Pointer && item.many)) { + id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, or many-item pointer") + return invalid_hir_expr(checker, expr.span, id) + } + return add_hir_expr(checker, hir.Expr{ + kind=.Index, span=expr.span, type=item.child, left=container, right=index, + target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Slice: + container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) + container_type := checker.module.exprs[container].type + item, ok := types.node(store, container_type) + if !ok || (item.kind != .Array && item.kind != .Slice) { + id := source.add(checker.diagnostics, expr.span, "slicing requires an array or slice") + return invalid_hir_expr(checker, expr.span, id) + } + bounds := make([]hir.Expr_Id, 2, checker.allocator) + bounds[0] = hir.INVALID_EXPR + bounds[1] = hir.INVALID_EXPR + for bound, index in expr.args { + if bound != ast.INVALID_EXPR { + bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.USIZE, pkg, file) + } + } + preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR + result_type := types.slice(store, item.child, item.mutable, preserve_sentinel, item.sentinel) + return add_hir_expr(checker, hir.Expr{ + kind=.Slice, span=expr.span, type=result_type, args=bounds, left=container, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Field: + base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) + base_type := checker.module.exprs[base].type + item, has_item := types.node(store, base_type) + field_name := symbol_text(checker, expr.name) + if has_item && (item.kind == .Array || item.kind == .Slice) { + if field_name == "len" { + return add_hir_expr(checker, hir.Expr{ + kind=.Length, span=expr.span, type=types.USIZE, left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + if field_name == "ptr" { + return add_hir_expr(checker, hir.Expr{ + kind=.Slice_Ptr, span=expr.span, + type=types.pointer(store, item.child, item.mutable, true), left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + } + if types.is_pointer(base_type, store) { + base_type = types.child_type(base_type, store) + } + index, field, ok := find_struct_field(checker, base_type, expr.name) + if !ok { + id := source.addf(checker.diagnostics, expr.span, "unknown struct field '%s'", symbol_text(checker, expr.name)) + return invalid_hir_expr(checker, expr.span, id) + } + return add_hir_expr(checker, hir.Expr{ + kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Unwrap: + optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) + optional_type := checker.module.exprs[optional].type + if !types.is_optional(optional_type, store) { + id := source.add(checker.diagnostics, expr.span, "postfix '?' requires an optional") + return invalid_hir_expr(checker, expr.span, id) + } + return add_hir_expr(checker, hir.Expr{ + kind=.Unwrap, span=expr.span, type=types.child_type(optional_type, store), left=optional, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Orelse: + optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) + optional_type := checker.module.exprs[optional].type + if !types.is_optional(optional_type, store) { + id := source.add(checker.diagnostics, expr.span, "'orelse' requires an optional left operand") + return invalid_hir_expr(checker, expr.span, id) + } + child := types.child_type(optional_type, store) + fallback := build_nested_expr(checker, expr.right, locals, global_reads, calls, child, pkg, file) + fallback = coerce_expr(checker, fallback, child, checker.module.exprs[fallback].span) + return add_hir_expr(checker, hir.Expr{ + kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback, + target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Struct_Literal: + target_pkg, available := expr_package(checker, expr, pkg, file, true) + struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID + if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) { + id := source.addf(checker.diagnostics, expr.span, "unknown or opaque struct type '%s'", symbol_text(checker, expr.name)) + return invalid_hir_expr(checker, expr.span, id) + } + if types.is_c_struct(struct_type, store) { + id := source.add(checker.diagnostics, expr.span, "c_struct values cannot be constructed by value") + return invalid_hir_expr(checker, expr.span, id) + } + fields := types.fields_for(store, struct_type) + values := make([]hir.Expr_Id, len(fields), checker.allocator) + initialized := make([]bool, len(fields), checker.allocator) + defer delete(initialized, checker.allocator) + for &value in values { + value = hir.INVALID_EXPR + } + for keyed in expr.args { + keyed_expr := checker.ast_module.exprs[keyed] + index, field, ok := find_struct_field(checker, struct_type, keyed_expr.name) + if !ok { + source.addf(checker.diagnostics, keyed_expr.span, "unknown struct field '%s'", symbol_text(checker, keyed_expr.name)) + continue + } + if initialized[index] { + source.addf(checker.diagnostics, keyed_expr.span, "duplicate initializer for struct field '%s'", symbol_text(checker, keyed_expr.name)) + continue + } + initialized[index] = true + values[index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file) + values[index] = coerce_expr(checker, values[index], field.type, keyed_expr.span) + } + for field, index in fields { + if values[index] == hir.INVALID_EXPR { + id := source.addf(checker.diagnostics, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name))) + delete(values, checker.allocator) + return invalid_hir_expr(checker, expr.span, id, struct_type) + } + } + return add_hir_expr(checker, hir.Expr{ + kind=.Struct, span=expr.span, type=struct_type, args=values, + target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Keyed: + id := source.add(checker.diagnostics, expr.span, "keyed initializer is only valid inside a struct literal") + return invalid_hir_expr(checker, expr.span, id) + case: + return invalid_hir_expr(checker, expr.span, expr.diagnostic) + } +} + build_expr :: proc( checker: ^Checker, expr_id: ast.Expr_Id, @@ -1097,9 +1687,18 @@ build_expr :: proc( continue } switch expr.kind { + case .String, .Array, .None, .Address, .Deref, .Index, .Slice, + .Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed: + last = build_compound_expr( + checker, expr, locals, global_reads, calls, frame.expected, pkg, file, + ) + _ = pop(&stack) case .Invalid, .Integer: last = invalid_hir_expr(checker, expr.span, expr.diagnostic) _ = pop(&stack) + case .Float: + last = build_float_expr(checker, expr, frame.expected) + _ = pop(&stack) case .Name: last = hir.INVALID_EXPR if !symbol.is_valid(expr.qualifier) { @@ -1109,6 +1708,38 @@ build_expr :: proc( left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC, }) } + } else if local, ok := find_build_local(locals, expr.qualifier); ok { + base := add_hir_expr(checker, hir.Expr{ + kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id), + left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + base_type := local.type + item, has_item := types.node(&checker.module.types, base_type) + field_name := symbol_text(checker, expr.name) + if has_item && (item.kind == .Array || item.kind == .Slice) { + if field_name == "len" { + last = add_hir_expr(checker, hir.Expr{ + kind=.Length, span=expr.span, type=types.USIZE, left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + } else if field_name == "ptr" { + last = add_hir_expr(checker, hir.Expr{ + kind=.Slice_Ptr, span=expr.span, + type=types.pointer(&checker.module.types, item.child, item.mutable, true), left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + } + if types.is_pointer(base_type, &checker.module.types) { + base_type = types.child_type(base_type, &checker.module.types) + } + index, field, found := find_struct_field(checker, base_type, expr.name) + if last == hir.INVALID_EXPR && found { + last = add_hir_expr(checker, hir.Expr{ + kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base, + target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + } } if last == hir.INVALID_EXPR { target_pkg, available := expr_package(checker, expr, pkg, file, true) @@ -1173,7 +1804,7 @@ build_expr :: proc( stack[frame_index].stage = 3 if len(expr.args) > 0 { arg_expected := type_from_syntax(checker.ast_module.functions[template].params[0].type) - if arg_expected.kind != .Concrete { + if !is_runtime_type(checker, arg_expected) { arg_expected = types.INVALID } append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION}) @@ -1184,15 +1815,15 @@ build_expr :: proc( if frame.stage == 5 { operand := last operand_type := checker.module.exprs[operand].type - if !types.is_signed(operand_type) { - id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer") + if !types.is_signed(operand_type, checker.target) && !types.is_float(operand_type, checker.target) { + id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer or float") last = invalid_hir_expr(checker, expr.span, id) } else { last = add_hir_expr(checker, hir.Expr{ kind=.Negate, span=expr.span, type=operand_type, left=operand, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) - if types.is_signed(frame.expected) { + if types.is_signed(frame.expected, checker.target) || types.is_float(frame.expected, checker.target) { last = coerce_expr(checker, last, frame.expected, expr.span) } } @@ -1202,15 +1833,28 @@ build_expr :: proc( if frame.stage == 1 { stack[frame_index].left = last stack[frame_index].stage = 2 - append(&stack, Build_Expr_Frame{expr=expr.right, expected=types.INVALID, template=ast.INVALID_FUNCTION}) + right_expected := types.INVALID + if types.is_many_pointer(checker.module.exprs[last].type, &checker.module.types) { + right_expected = types.USIZE + } + append(&stack, Build_Expr_Frame{expr=expr.right, expected=right_expected, template=ast.INVALID_FUNCTION}) continue } if frame.stage == 2 { left := frame.left right := last + if types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) && + types.equal(checker.module.exprs[right].type, types.USIZE) { + last = add_hir_expr(checker, hir.Expr{ + kind=.Pointer_Add, span=expr.span, type=checker.module.exprs[left].type, + left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC, + }) + _ = pop(&stack) + continue + } result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type) - if !types.is_signed(result) { - id := source.add(checker.diagnostics, expr.span, "addition requires compatible signed integers") + if !types.is_concrete_scalar(result) { + id := source.add(checker.diagnostics, expr.span, "addition requires compatible numeric operands") last = invalid_hir_expr(checker, expr.span, id) } else { left = coerce_expr(checker, left, result, checker.module.exprs[left].span) @@ -1231,7 +1875,7 @@ build_expr :: proc( if frame.arg_index+1 < len(expr.args) { next := frame.arg_index+1 arg_expected := type_from_syntax(checker.ast_module.functions[frame.template].params[next].type) - if arg_expected.kind != .Concrete { + if !is_runtime_type(checker, arg_expected) { arg_expected = types.INVALID } append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION}) @@ -1305,7 +1949,11 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string { strings.write_string(&builder, symbol_text(checker, function.name)) for arg in spec.args { strings.write_string(&builder, "__") - strings.write_string(&builder, types.name(arg)) + if arg >= types.DYNAMIC_START { + fmt.sbprintf(&builder, "t%d", arg) + } else { + strings.write_string(&builder, types.name(arg)) + } } return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator) } @@ -1314,7 +1962,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { spec := checker.specs[id] function := checker.ast_module.functions[spec.template] signature_diagnostic := source.INVALID_DIAGNOSTIC - if spec.result.kind != .Void && !types.is_concrete_integer(spec.result) { + if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) { checker.specs[id].result = types.I64 spec.result = types.I64 signature_diagnostic = source.addf( @@ -1325,7 +1973,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { ) } for arg in spec.args { - if !types.is_concrete_integer(arg) { + if !is_runtime_type(checker, arg) { signature_diagnostic = source.addf( checker.diagnostics, function.span, @@ -1406,9 +2054,9 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { statement := checker.ast_module.statements[statement_id] switch statement.kind { case .Declaration: - declared := type_from_syntax(statement.type) + declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr) expected := types.INVALID - if declared.kind == .Concrete { + if is_runtime_type(checker, declared) { expected = declared } value := build_expr( @@ -1422,10 +2070,10 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { function.file, ) value_type := checker.module.exprs[value].type - if declared.kind == .Concrete { + if is_runtime_type(checker, declared) { value = coerce_expr(checker, value, declared, statement.span) value_type = checker.module.exprs[value].type - } else if declared.kind == .Void { + } else if types.is_void(declared) { id := source.add( checker.diagnostics, statement.span, @@ -1486,9 +2134,38 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { ) problematic = problematic || checker.module.exprs[value].kind == .Invalid case .Assignment: + if statement.target != ast.INVALID_EXPR { + target_expr := build_expr( + checker, statement.target, locals[:], &global_reads, &calls, + types.INVALID, function.pkg, function.file, + ) + target_type := checker.module.exprs[target_expr].type + if !hir_location_writable(checker, target_expr, locals[:]) { + id := source.add(checker.diagnostics, statement.span, "assignment target is not writable") + append(&body, hir.stmt_id(len(checker.module.statements))) + append(&checker.module.statements, hir.Stmt{ + kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL, + target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id, + }) + problematic = true + continue + } + value := build_expr( + checker, statement.expr, locals[:], &global_reads, &calls, + target_type, function.pkg, function.file, + ) + value = coerce_expr(checker, value, target_type, statement.span) + append(&body, hir.stmt_id(len(checker.module.statements))) + append(&checker.module.statements, hir.Stmt{ + kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL, + target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC, + }) + problematic = problematic || checker.module.exprs[value].kind == .Invalid + continue + } if statement.name == checker.sink_symbol { value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file) - if checker.module.exprs[value].type.kind == .Void { + if types.is_void(checker.module.exprs[value].type) { id := source.add( checker.diagnostics, statement.span, @@ -1583,6 +2260,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { span = statement.span, expr = value, local = local.id, + target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC, }, ) @@ -1590,7 +2268,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { case .Return: has_return = true if statement.expr == ast.INVALID_EXPR { - if spec.result.kind != .Void { + if !types.is_void(spec.result) { id := source.add( checker.diagnostics, statement.span, @@ -1623,7 +2301,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { } continue } - if spec.result.kind == .Void { + if types.is_void(spec.result) { id := source.add( checker.diagnostics, statement.span, @@ -1668,7 +2346,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { problematic = problematic || checker.module.exprs[value].kind == .Invalid case .Expression: value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file) - if checker.module.exprs[value].type.kind != .Void { + if !types.is_void(checker.module.exprs[value].type) { id := source.add( checker.diagnostics, statement.span, @@ -1715,7 +2393,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) { } } - if spec.result.kind != .Void && !has_return { + if !types.is_void(spec.result) && !has_return { id := source.addf( checker.diagnostics, function.span, @@ -1788,21 +2466,21 @@ build_globals :: proc(checker: ^Checker) { dependencies.allocator = checker.allocator calls: [dynamic]hir.Function_Id calls.allocator = checker.allocator - declared := type_from_syntax(global.type) + declared := resolve_inferred_array(checker, type_from_syntax(global.type), global.expr) expected := types.INVALID - if declared.kind == .Concrete { + if is_runtime_type(checker, declared) { expected = declared } expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file) global_type := checker.global_types[global_index] - if declared.kind == .Concrete { + if is_runtime_type(checker, declared) { expr = coerce_expr(checker, expr, declared, global.span) global_type = checker.module.exprs[expr].type - } else if types.is_concrete_integer(checker.module.exprs[expr].type) { + } else if is_runtime_type(checker, checker.module.exprs[expr].type) { global_type = checker.module.exprs[expr].type } diagnostic := source.INVALID_DIAGNOSTIC - if !types.is_concrete_integer(global_type) { + if !is_runtime_type(checker, global_type) { diagnostic = source.addf( checker.diagnostics, global.span, @@ -1812,7 +2490,7 @@ build_globals :: proc(checker: ^Checker) { global_type = types.I64 expr = invalid_hir_expr(checker, global.span, diagnostic, global_type) } - if global.type == .Void { + if global.type == types.VOID { diagnostic = source.add( checker.diagnostics, global.span, @@ -2057,18 +2735,26 @@ check :: proc( ast_module: ^ast.Module, diagnostics: ^source.Diagnostics, symbols: ^symbol.Table, + selected := target.DEFAULT, allocator := context.allocator, ) -> hir.Module { checker := Checker { ast_module = ast_module, diagnostics = diagnostics, symbols = symbols, - module = hir.init_module(allocator), + module = hir.init_module(selected, allocator), main_symbol = symbol.intern(symbols, "main"), sink_symbol = symbol.intern(symbols, "_"), + target = selected, allocator = allocator, } checker.specs.allocator = allocator + types.destroy_store(&checker.module.types) + checker.module.types = types.clone_store(&ast_module.type_store, allocator) + checker.module.types.selected = selected + for value in ast_module.strings { + append(&checker.module.strings, strings.clone(value, allocator)) + } checker.constant_stack.allocator = allocator checker.ast_expr_stack.allocator = allocator checker.hir_expr_stack.allocator = allocator @@ -2121,6 +2807,7 @@ check :: proc( } } + validate_type_nodes(&checker) validate_declarations(&checker) infer_all(&checker) prune_specs(&checker) @@ -2144,7 +2831,7 @@ check :: proc( if main_declarations != 1 || !template.has_body || len(template.params) != 0 || - !(template.result == .Void || template.result == .I32 || template.result == .Int) { + !(template.result == types.VOID || template.result == types.I32 || template.result == types.INT) { id := checker.template_diagnostics[main_template] if id == source.INVALID_DIAGNOSTIC { id = source.add( diff --git a/compiler/compiler.odin b/compiler/compiler.odin index dda05ed..7b787c8 100644 --- a/compiler/compiler.odin +++ b/compiler/compiler.odin @@ -9,12 +9,17 @@ import "./lower" import "./opt" import "./source" import "./symbol" +import "./target" import "core:fmt" import vmem "core:mem/virtual" import "core:os" import "core:os/os2" -compile_package :: proc(input_path, output_path: string, link_arguments: []linker.Argument = nil) -> int { +compile_package :: proc( + input_path, output_path: string, + link_arguments: []linker.Argument = nil, + selected := target.DEFAULT, +) -> int { sources := source.init_store() defer source.destroy_store(&sources) diagnostics := source.init_store_diagnostics(&sources) @@ -61,7 +66,7 @@ compile_package :: proc(input_path, output_path: string, link_arguments: []linke return 2 } vmem.arena_free_all(&lexer_arena) - hir_module := checker.check(&ast_module, &diagnostics, &symbols, vmem.arena_allocator(&checker_arena)) + hir_module := checker.check(&ast_module, &diagnostics, &symbols, selected, vmem.arena_allocator(&checker_arena)) vmem.arena_free_all(&parser_arena) ir_module := lower.lower(&hir_module, vmem.arena_allocator(&lower_arena)) vmem.arena_free_all(&checker_arena) @@ -78,7 +83,7 @@ compile_package :: proc(input_path, output_path: string, link_arguments: []linke } source.print_all(&diagnostics) - if !backend.compile(llvm_path, output_path, link_arguments) { + if !backend.compile(llvm_path, output_path, link_arguments, selected) { return 2 } if len(diagnostics.items) > 0 { diff --git a/compiler/hir/hir.odin b/compiler/hir/hir.odin index d4c1730..7df3941 100644 --- a/compiler/hir/hir.odin +++ b/compiler/hir/hir.odin @@ -2,6 +2,7 @@ package hir import "../source" import "../symbol" +import "../target" import "../types" import "core:mem" @@ -74,11 +75,27 @@ Linkage :: enum u8 { Expr_Kind :: enum u8 { Invalid, Integer, + Float, + String, + Array, + Struct, + None, + Optional_Some, Local, Global, + Address, + Deref, + Index, + Slice, + Field, + Length, + Slice_Ptr, + Unwrap, + Orelse, Widen, Negate, Add, + Pointer_Add, Call, } @@ -114,6 +131,7 @@ Stmt :: struct { kind: Stmt_Kind, span: source.Span, local: Local_Id, + target: Expr_Id, expr: Expr_Id, diagnostic: source.Diagnostic_Id, } @@ -153,16 +171,23 @@ Module :: struct { statements: [dynamic]Stmt, functions: [dynamic]Function, globals: [dynamic]Global, + strings: [dynamic]string, + types: types.Store, + target: target.Target, allocator: mem.Allocator, } -init_module :: proc(allocator := context.allocator) -> Module { +init_module :: proc(selected := target.DEFAULT, allocator := context.allocator) -> Module { module: Module + module.target = selected + module.types = types.init_store(allocator) + module.types.selected = selected module.allocator = allocator module.exprs.allocator = allocator module.statements.allocator = allocator module.functions.allocator = allocator module.globals.allocator = allocator + module.strings.allocator = allocator return module } @@ -182,8 +207,13 @@ destroy_module :: proc(module: ^Module) { delete(global.dependencies) delete(global.calls, module.allocator) } + for value in module.strings { + delete(value, module.allocator) + } delete(module.exprs) delete(module.statements) delete(module.functions) delete(module.globals) + delete(module.strings) + types.destroy_store(&module.types) } diff --git a/compiler/ir/ir.odin b/compiler/ir/ir.odin index de736e9..1e9f320 100644 --- a/compiler/ir/ir.odin +++ b/compiler/ir/ir.odin @@ -2,6 +2,7 @@ package ir import "../source" import "../symbol" +import "../target" import "../types" import "core:mem" @@ -67,13 +68,28 @@ Linkage :: enum u8 { Opcode :: enum u8 { Param, Const, + String, + Aggregate, + None, + Optional_Some, Load_Global, + Address_Global, + Address_Of, Alloca, + Index_Address, + Field_Address, Load, Store, + Slice, + Length, + Slice_Ptr, + Unwrap, + Orelse_Begin, + Orelse, Widen, Neg_Checked, Add_Checked, + Pointer_Add, Call, Trap, Return, @@ -117,13 +133,20 @@ Global :: struct { Module :: struct { functions: [dynamic]Function, globals: [dynamic]Global, + strings: [dynamic]string, + types: types.Store, + target: target.Target, allocator: mem.Allocator, } -init_module :: proc(allocator := context.allocator) -> Module { +init_module :: proc(selected := target.DEFAULT, allocator := context.allocator) -> Module { module: Module + module.target = selected + module.types = types.init_store(allocator) + module.types.selected = selected module.functions.allocator = allocator module.globals.allocator = allocator + module.strings.allocator = allocator module.allocator = allocator return module } @@ -144,6 +167,11 @@ destroy_module :: proc(module: ^Module) { for global in module.globals { destroy_instructions(global.initializer, module.allocator) } + for value in module.strings { + delete(value, module.allocator) + } delete(module.functions) delete(module.globals) + delete(module.strings) + types.destroy_store(&module.types) } diff --git a/compiler/lexer/lexer.odin b/compiler/lexer/lexer.odin index f88088a..e8e306a 100644 --- a/compiler/lexer/lexer.odin +++ b/compiler/lexer/lexer.odin @@ -14,16 +14,44 @@ is_identifier_continue :: proc(value: byte) -> bool { keyword_kind :: proc(text: string) -> token.Kind { switch text { - case "func": return .Keyword_Func - case "import": return .Keyword_Import - case "return": return .Keyword_Return - 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 "_": return .Underscore + case "func": return .Keyword_Func + case "c_func": return .Keyword_C_Func + case "struct": return .Keyword_Struct + case "c_struct": return .Keyword_C_Struct + case "import": return .Keyword_Import + case "return": return .Keyword_Return + case "mut": return .Keyword_Mut + case "none": return .Keyword_None + case "orelse": return .Keyword_Orelse + case "void": return .Keyword_Void + case "int": return .Keyword_Int + case "i8": return .Keyword_I8 + case "i16": return .Keyword_I16 + case "i32": return .Keyword_I32 + case "i64": return .Keyword_I64 + case "u8": return .Keyword_U8 + case "u16": return .Keyword_U16 + case "u32": return .Keyword_U32 + case "u64": return .Keyword_U64 + case "isize": return .Keyword_Isize + case "usize": return .Keyword_Usize + case "f32": return .Keyword_F32 + case "f64": return .Keyword_F64 + case "c_char": return .Keyword_C_Char + case "c_schar": return .Keyword_C_Schar + case "c_uchar": return .Keyword_C_Uchar + case "c_short": return .Keyword_C_Short + case "c_ushort": return .Keyword_C_Ushort + case "c_int": return .Keyword_C_Int + case "c_uint": return .Keyword_C_Uint + case "c_long": return .Keyword_C_Long + case "c_ulong": return .Keyword_C_Ulong + case "c_longlong": return .Keyword_C_Longlong + case "c_ulonglong": return .Keyword_C_Ulonglong + case "c_float": return .Keyword_C_Float + case "c_double": return .Keyword_C_Double + case "c_longdouble": return .Keyword_C_Longdouble + case "_": return .Underscore } return .Identifier } @@ -87,7 +115,34 @@ lex :: proc( append_token(&stream, source_file, .Minus, cursor, cursor+1) cursor += 1 case '.': - append_token(&stream, source_file, .Dot, cursor, cursor+1) + start := cursor + cursor += 1 + if cursor < len(bytes) && bytes[cursor] == '.' { + cursor += 1 + append_token(&stream, source_file, .Range, start, cursor) + } else { + append_token(&stream, source_file, .Dot, start, cursor) + } + case '@': + append_token(&stream, source_file, .At, cursor, cursor+1) + cursor += 1 + case '*': + append_token(&stream, source_file, .Star, cursor, cursor+1) + cursor += 1 + case '&': + append_token(&stream, source_file, .Ampersand, cursor, cursor+1) + cursor += 1 + case '^': + append_token(&stream, source_file, .Caret, cursor, cursor+1) + cursor += 1 + case '?': + append_token(&stream, source_file, .Question, cursor, cursor+1) + cursor += 1 + case '[': + append_token(&stream, source_file, .Left_Bracket, cursor, cursor+1) + cursor += 1 + case ']': + append_token(&stream, source_file, .Right_Bracket, cursor, cursor+1) cursor += 1 case '(': append_token(&stream, source_file, .Left_Paren, cursor, cursor+1) @@ -111,11 +166,13 @@ lex :: proc( for cursor < len(bytes) && bytes[cursor] != '"' && bytes[cursor] != '\n' { if bytes[cursor] == '\\' { cursor += 1 - if cursor >= len(bytes) || (bytes[cursor] != '\\' && bytes[cursor] != '"') { + if cursor >= len(bytes) || + (bytes[cursor] != '\\' && bytes[cursor] != '"' && bytes[cursor] != 'n' && + bytes[cursor] != 'r' && bytes[cursor] != 't' && bytes[cursor] != '0') { source.add( diagnostics, source.Span{file=source_file.id, start=source.Offset(max(cursor-1, start)), end=source.Offset(min(cursor+1, len(bytes)))}, - "import strings only support '\\\\' and '\\\"' escapes", + "strings only support '\\\\', '\\\"', '\\n', '\\r', '\\t', and '\\0' escapes", ) valid = false } @@ -136,20 +193,44 @@ lex :: proc( append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id) } case ';': - id := source.add( - diagnostics, - source.Span{file=source_file.id, start=source.Offset(cursor), end=source.Offset(cursor+1)}, - "semicolons are invalid; terminate statements with a newline", - ) - append_token(&stream, source_file, .Invalid, cursor, cursor+1, diagnostic=id) + append_token(&stream, source_file, .Semicolon, cursor, cursor+1) cursor += 1 + case '\'': + start := cursor + cursor += 1 + for cursor < len(bytes) && bytes[cursor] != '\'' && bytes[cursor] != '\n' { + if bytes[cursor] == '\\' && cursor+1 < len(bytes) { + cursor += 1 + } + cursor += 1 + } + if cursor < len(bytes) && bytes[cursor] == '\'' { + cursor += 1 + append_token(&stream, source_file, .Character, start, cursor) + } else { + id := source.add( + diagnostics, + source.Span{file=source_file.id, start=source.Offset(start), end=source.Offset(cursor)}, + "unterminated character literal", + ) + append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id) + } case: if value >= '0' && value <= '9' { start := cursor for cursor < len(bytes) && bytes[cursor] >= '0' && bytes[cursor] <= '9' { cursor += 1 } - append_token(&stream, source_file, .Integer, start, cursor) + kind := token.Kind.Integer + if cursor+1 < len(bytes) && bytes[cursor] == '.' && + bytes[cursor+1] != '.' && bytes[cursor+1] >= '0' && bytes[cursor+1] <= '9' { + kind = .Float + cursor += 1 + for cursor < len(bytes) && bytes[cursor] >= '0' && bytes[cursor] <= '9' { + cursor += 1 + } + } + append_token(&stream, source_file, kind, start, cursor) } else if is_identifier_start(value) { start := cursor for cursor < len(bytes) && is_identifier_continue(bytes[cursor]) { diff --git a/compiler/llvm/llvm.odin b/compiler/llvm/llvm.odin index a929d30..18af1f7 100644 --- a/compiler/llvm/llvm.odin +++ b/compiler/llvm/llvm.odin @@ -3,6 +3,7 @@ package llvm import "../ir" import "../source" import "../symbol" +import "../target" import "../types" import "core:fmt" import "core:mem" @@ -21,11 +22,33 @@ Emitter :: struct { allocator: mem.Allocator, } -llvm_type :: proc(value: types.Type) -> string { - if value.kind == .Void { +llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string { + if types.is_void(value) { return "void" } - switch value.bits { + #partial switch types.kind(value, store) { + case .Pointer: + return "ptr" + case .Slice: + return "{ ptr, i64 }" + case .Array: + item, _ := types.node(store, value) + return fmt.tprintf("[%d x %s]", types.physical_count(value, store), llvm_type(item.child, store)) + case .Optional: + item, _ := types.node(store, value) + if types.is_pointer(item.child, store) { + return "ptr" + } + return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store)) + case .Struct: + return fmt.tprintf("%%bro.type.%d", value) + } + selected := store.selected if store != nil else target.DEFAULT + repr := types.representation(value, selected) + if types.is_float(repr) { + return "float" if types.bits(repr) == 32 else "double" + } + switch types.bits(repr) { case 8: return "i8" case 16: return "i16" case 32: return "i32" @@ -33,15 +56,37 @@ llvm_type :: proc(value: types.Type) -> string { } } -function_result_type :: proc(function: ir.Function) -> string { +function_result_type :: proc(function: ir.Function, store: ^types.Store) -> string { if function.is_main { return "i32" } - return llvm_type(function.result) + return llvm_type(function.result, store) } -sentinel :: proc(value_type: types.Type) -> i64 { - switch value_type.bits { +c_abi_extension :: proc(value: types.Type, selected: target.Target) -> string { + if !types.is_concrete_integer(value) { + return "" + } + switch target.c_integer_extension(selected, types.bits(value, selected), types.is_signed(value, selected)) { + case .Sign: return "signext" + case .Zero: return "zeroext" + case .None: return "" + } + return "" +} + +emit_function_result :: proc(builder: ^strings.Builder, function: ir.Function, store: ^types.Store) { + if function.calling_convention == .C { + extension := c_abi_extension(function.result, store.selected) + if len(extension) > 0 { + fmt.sbprintf(builder, "%s ", extension) + } + } + strings.write_string(builder, function_result_type(function, store)) +} + +sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 { + switch types.bits(value_type, selected) { case 8: return -86 case 16: return -21846 case 32: return -1431655766 @@ -53,29 +98,88 @@ valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: ir.Ins return instruction_id != ir.INVALID_INSTRUCTION && int(instruction_id) < len(instructions) } -valid_value :: proc(instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type) -> bool { +valid_value :: proc( + instructions: []ir.Instruction, + value_id: ir.Instruction_Id, + expected: types.Type, + store: ^types.Store, +) -> bool { if !valid_instruction(instructions, value_id) || - !types.is_concrete_integer(expected) || + !types.is_runtime_value(expected, store) || !types.equal(instructions[value_id].type, expected) { return false } switch instructions[value_id].op { - case .Param, .Const, .Load_Global, .Load, .Widen, .Neg_Checked, .Add_Checked, .Call: + case .Param, .Const, .String, .Aggregate, .None, .Optional_Some, + .Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse, + .Widen, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call: return true - case .Alloca, .Store, .Trap, .Return, .Return_Void: + case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin, + .Store, .Trap, .Return, .Return_Void: return false } return false } -write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type) { - if !valid_value(instructions, value_id, expected) { - fmt.sbprintf(builder, "%d", sentinel(expected)) +valid_address :: proc( + instructions: []ir.Instruction, + value_id: ir.Instruction_Id, + pointee: types.Type, + store: ^types.Store, +) -> bool { + if !valid_instruction(instructions, value_id) { + return false + } + value := instructions[value_id] + #partial switch value.op { + case .Address_Global, .Alloca, .Index_Address, .Field_Address: + return types.equal(value.type, pointee) + case: + return types.is_pointer(value.type, store) && + types.equal(types.child_type(value.type, store), pointee) && + valid_value(instructions, value_id, value.type, store) + } +} + +write_constant :: proc(builder: ^strings.Builder, value: i64, value_type: types.Type, store: ^types.Store = nil) { + if !types.is_concrete_scalar(value_type) { + strings.write_string(builder, "zeroinitializer") + return + } + selected := store.selected if store != nil else target.DEFAULT + if types.is_float(value_type, selected) { + text := "" + if types.bits(value_type, selected) == 32 { + bits := u32(value) + number := transmute(f32)bits + text = fmt.tprintf("%.9g", number) + } else { + number := transmute(f64)value + text = fmt.tprintf("%.17g", number) + } + strings.write_string(builder, text) + if !strings.contains(text, ".") && !strings.contains(text, "e") && !strings.contains(text, "E") { + strings.write_string(builder, ".0") + } + return + } + fmt.sbprintf(builder, "%d", value) +} + +write_operand :: proc( + builder: ^strings.Builder, + instructions: []ir.Instruction, + value_id: ir.Instruction_Id, + expected: types.Type, + store: ^types.Store, +) { + if !valid_value(instructions, value_id, expected, store) { + write_constant(builder, sentinel(expected, store.selected), expected, store) return } value := instructions[value_id] if value.op == .Const { - fmt.sbprintf(builder, "%d", value.integer) + write_constant(builder, value.integer, expected, store) } else { fmt.sbprintf(builder, "%%v%d", value_id) } @@ -126,24 +230,59 @@ emit_trap_call :: proc(emitter: ^Emitter, message_id: int) { emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction: ir.Instruction, fallback: string) { message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback) emit_trap_call(emitter, message) - if types.is_concrete_integer(instruction.type) { + if types.is_runtime_value(instruction.type, &emitter.module.types) { + if !types.is_float(instruction.type, emitter.module.target) { + if !types.is_concrete_scalar(instruction.type) { + fmt.sbprintf( + &emitter.builder, + " %%v%d = freeze %s zeroinitializer\n", + instruction_id, + llvm_type(instruction.type, &emitter.module.types), + ) + return + } + fmt.sbprintf( + &emitter.builder, + " %%v%d = add %s 0, %d\n", + instruction_id, + llvm_type(instruction.type, &emitter.module.types), + sentinel(instruction.type, emitter.module.target), + ) + return + } fmt.sbprintf( &emitter.builder, - " %%v%d = add %s 0, %d\n", + " %%v%d = select i1 true, %s ", instruction_id, - llvm_type(instruction.type), - sentinel(instruction.type), + llvm_type(instruction.type, &emitter.module.types), ) + write_constant(&emitter.builder, sentinel(instruction.type, emitter.module.target), instruction.type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(instruction.type, &emitter.module.types)) + write_constant(&emitter.builder, sentinel(instruction.type, emitter.module.target), instruction.type, &emitter.module.types) + strings.write_string(&emitter.builder, "\n") } } -emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []ir.Instruction_Id, param_types: []types.Type) { +emit_call_args :: proc( + builder: ^strings.Builder, + instructions: []ir.Instruction, + args: []ir.Instruction_Id, + param_types: []types.Type, + store: ^types.Store, + c_abi := false, +) { for arg, index in args { if index > 0 { strings.write_string(builder, ", ") } - fmt.sbprintf(builder, "%s ", llvm_type(param_types[index])) - write_operand(builder, instructions, arg, param_types[index]) + fmt.sbprintf(builder, "%s ", llvm_type(param_types[index], store)) + if c_abi { + extension := c_abi_extension(param_types[index], store.selected) + if len(extension) > 0 { + fmt.sbprintf(builder, "%s ", extension) + } + } + write_operand(builder, instructions, arg, param_types[index], store) } } @@ -163,6 +302,102 @@ emit_instruction_stream :: proc( } switch instruction.op { case .Param, .Const: + case .String: + string_id := int(instruction.integer) + if string_id < 0 || string_id >= len(emitter.module.strings) || + !types.is_slice(instruction.type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid string literal") + continue + } + type_name := llvm_type(instruction.type, &emitter.module.types) + fmt.sbprintf( + &emitter.builder, + " %%string_ptr%d = insertvalue %s poison, ptr @bro.str.%d, 0\n", + instruction_index, type_name, string_id, + ) + fmt.sbprintf( + &emitter.builder, + " %%v%d = insertvalue %s %%string_ptr%d, i64 %d, 1\n", + instruction_index, type_name, instruction_index, len(emitter.module.strings[string_id]), + ) + case .Aggregate: + item, ok := types.node(&emitter.module.types, instruction.type) + expected_count := 0 + if ok && item.kind == .Array { + expected_count = int(item.count) + } else if ok && item.kind == .Struct { + expected_count = int(item.field_count) + } else { + emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type") + continue + } + if len(instruction.args) != expected_count { + emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate operands") + continue + } + type_name := llvm_type(instruction.type, &emitter.module.types) + total := len(instruction.args) + (1 if item.kind == .Array && item.has_sentinel else 0) + if total == 0 { + fmt.sbprintf(&emitter.builder, " %%v%d = freeze %s zeroinitializer\n", instruction_index, type_name) + continue + } + for arg_index := 0; arg_index < total; arg_index += 1 { + element_type := item.child + if item.kind == .Struct { + element_type = types.fields_for(&emitter.module.types, instruction.type)[arg_index].type + } + final := arg_index == total-1 + if final { + fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s ", instruction_index, type_name) + } else { + fmt.sbprintf(&emitter.builder, " %%aggregate%d_%d = insertvalue %s ", instruction_index, arg_index, type_name) + } + if arg_index == 0 { + strings.write_string(&emitter.builder, "poison") + } else { + fmt.sbprintf(&emitter.builder, "%%aggregate%d_%d", instruction_index, arg_index-1) + } + fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(element_type, &emitter.module.types)) + if arg_index < len(instruction.args) { + write_operand(&emitter.builder, instructions, instruction.args[arg_index], element_type, &emitter.module.types) + } else { + write_constant(&emitter.builder, i64(item.sentinel), element_type, &emitter.module.types) + } + fmt.sbprintf(&emitter.builder, ", %d\n", arg_index) + } + case .None: + item, ok := types.node(&emitter.module.types, instruction.type) + if !ok || item.kind != .Optional { + emit_recovery_value(emitter, instruction_index, instruction, "invalid optional none") + continue + } + if types.is_pointer(item.child, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr null, ptr null\n", instruction_index) + } else { + fmt.sbprintf( + &emitter.builder, + " %%v%d = insertvalue %s zeroinitializer, i1 false, 0\n", + instruction_index, llvm_type(instruction.type, &emitter.module.types), + ) + } + case .Optional_Some: + item, ok := types.node(&emitter.module.types, instruction.type) + if !ok || item.kind != .Optional || + !valid_value(instructions, instruction.a, item.child, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid optional value") + continue + } + if types.is_pointer(item.child, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr ", instruction_index) + write_operand(&emitter.builder, instructions, instruction.a, item.child, &emitter.module.types) + strings.write_string(&emitter.builder, ", ptr null\n") + } else { + type_name := llvm_type(instruction.type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, " %%optional%d = insertvalue %s poison, i1 true, 0\n", instruction_index, type_name) + fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%optional%d, %s ", instruction_index, type_name, instruction_index, llvm_type(item.child, &emitter.module.types)) + write_operand(&emitter.builder, instructions, instruction.a, item.child, &emitter.module.types) + strings.write_string(&emitter.builder, ", 1\n") + } case .Load_Global: global_id := ir.as_global(instruction.target) if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) { @@ -179,7 +414,7 @@ emit_instruction_stream :: proc( &emitter.builder, " %%v%d = load %s, ptr @bro.g.%d\n", instruction_index, - llvm_type(global.type), + llvm_type(global.type, &emitter.module.types), global_id, ) } else { @@ -187,20 +422,115 @@ emit_instruction_stream :: proc( &emitter.builder, " %%v%d = call %s @bro.get.%d()\n", instruction_index, - llvm_type(global.type), + llvm_type(global.type, &emitter.module.types), global_id, ) } + case .Address_Global: + global_id := ir.as_global(instruction.target) + if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) || + !types.equal(instruction.type, emitter.module.globals[global_id].type) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid global address") + continue + } + fmt.sbprintf( + &emitter.builder, + " %%v%d = getelementptr %s, ptr @bro.g.%d, i64 0\n", + instruction_index, llvm_type(instruction.type, &emitter.module.types), global_id, + ) + case .Address_Of: + child := types.child_type(instruction.type, &emitter.module.types) + if !types.is_pointer(instruction.type, &emitter.module.types) || + !valid_address(instructions, instruction.a, child, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid address operand") + continue + } + fmt.sbprintf( + &emitter.builder, + " %%v%d = getelementptr %s, ptr %%v%d, i64 0\n", + instruction_index, llvm_type(child, &emitter.module.types), instruction.a, + ) case .Alloca: - if !types.is_concrete_integer(instruction.type) { + if !types.is_runtime_value(instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type") continue } - fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type)) - case .Load: + fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types)) + case .Index_Address: if !valid_instruction(instructions, instruction.a) || - instructions[instruction.a].op != .Alloca || - !types.equal(instructions[instruction.a].type, instruction.type) { + !valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid index operands") + continue + } + container := instructions[instruction.a] + container_node, container_ok := types.node(&emitter.module.types, container.type) + if !container_ok { + emit_recovery_value(emitter, instruction_index, instruction, "invalid index container") + continue + } + pointer_name := fmt.tprintf("%%v%d", instruction.a) + length: u64 + bounded := false + if container_node.kind == .Array { + length = container_node.count + if container_node.has_sentinel && instruction.integer != 0 { + length += 1 + } + bounded = true + } else if container_node.kind == .Slice { + fmt.sbprintf(&emitter.builder, " %%index_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) + fmt.sbprintf(&emitter.builder, " %%index_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) + pointer_name = fmt.tprintf("%%index_ptr%d", instruction_index) + comparison := "ule" if container_node.has_sentinel && instruction.integer != 0 else "ult" + fmt.sbprintf(&emitter.builder, " %%index_ok%d = icmp %s i64 ", instruction_index, comparison) + write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", %%index_len%d\n", instruction_index) + bounded = true + } else if container_node.kind != .Pointer || !container_node.many { + emit_recovery_value(emitter, instruction_index, instruction, "invalid index container") + continue + } + if bounded { + if container_node.kind == .Array { + fmt.sbprintf(&emitter.builder, " %%index_ok%d = icmp ult i64 ", instruction_index) + write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", %d\n", length) + } + fmt.sbprintf(&emitter.builder, " br i1 %%index_ok%d, label %%index_continue%d, label %%index_trap%d\nindex_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index) + message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "index out of bounds") + emit_trap_call(emitter, message) + fmt.sbprintf(&emitter.builder, " unreachable\nindex_continue%d:\n", instruction_index) + } + if container_node.kind == .Array { + fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(container.type, &emitter.module.types), pointer_name) + } else { + fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 ", instruction_index, llvm_type(instruction.type, &emitter.module.types), pointer_name) + } + write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) + strings.write_string(&emitter.builder, "\n") + case .Field_Address: + if !valid_instruction(instructions, instruction.a) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid field base") + continue + } + base_type := instructions[instruction.a].type + if types.is_pointer(base_type, &emitter.module.types) { + base_type = types.child_type(base_type, &emitter.module.types) + } + fields := types.fields_for(&emitter.module.types, base_type) + field_index := int(instruction.integer) + if field_index < 0 || field_index >= len(fields) || + !types.equal(fields[field_index].type, instruction.type) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference") + continue + } + fmt.sbprintf( + &emitter.builder, + " %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n", + instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index, + ) + case .Load: + if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot") continue } @@ -208,42 +538,191 @@ emit_instruction_stream :: proc( &emitter.builder, " %%v%d = load %s, ptr %%v%d\n", instruction_index, - llvm_type(instruction.type), + llvm_type(instruction.type, &emitter.module.types), instruction.a, ) case .Store: - if !valid_instruction(instructions, instruction.a) || - instructions[instruction.a].op != .Alloca || - !types.equal(instructions[instruction.a].type, instruction.type) || - !valid_value(instructions, instruction.b, instruction.type) { + if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) || + !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid store operand") continue } - fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type)) - write_operand(&emitter.builder, instructions, instruction.b, instruction.type) + fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type, &emitter.module.types)) + write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr %%v%d\n", instruction.a) + case .Slice: + if !valid_instruction(instructions, instruction.a) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container") + continue + } + container := instructions[instruction.a] + item, ok := types.node(&emitter.module.types, container.type) + if !ok || (item.kind != .Array && item.kind != .Slice) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container") + continue + } + pointer_name := fmt.tprintf("%%v%d", instruction.a) + length_name := fmt.tprintf("%d", item.count) + if item.kind == .Array { + fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) + pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index) + } else if item.kind == .Slice { + fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) + fmt.sbprintf(&emitter.builder, " %%slice_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) + pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index) + length_name = fmt.tprintf("%%slice_len%d", instruction_index) + } + fmt.sbprintf(&emitter.builder, " %%slice_bound_start%d = add i64 0, ", instruction_index) + if len(instruction.args) > 0 && instruction.args[0] != ir.INVALID_INSTRUCTION { + write_operand(&emitter.builder, instructions, instruction.args[0], types.USIZE, &emitter.module.types) + } else { + strings.write_string(&emitter.builder, "0") + } + strings.write_string(&emitter.builder, "\n") + fmt.sbprintf(&emitter.builder, " %%slice_bound_end%d = add i64 0, ", instruction_index) + if len(instruction.args) > 1 && instruction.args[1] != ir.INVALID_INSTRUCTION { + write_operand(&emitter.builder, instructions, instruction.args[1], types.USIZE, &emitter.module.types) + } else { + strings.write_string(&emitter.builder, length_name) + } + strings.write_string(&emitter.builder, "\n") + start_name := fmt.tprintf("%%slice_bound_start%d", instruction_index) + end_name := fmt.tprintf("%%slice_bound_end%d", instruction_index) + fmt.sbprintf(&emitter.builder, " %%slice_order%d = icmp ule i64 %s, %s\n", instruction_index, start_name, end_name) + fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp ule i64 %s, %s\n", instruction_index, end_name, length_name) + fmt.sbprintf(&emitter.builder, " %%slice_ok%d = and i1 %%slice_order%d, %%slice_end_ok%d\n", instruction_index, instruction_index, instruction_index) + fmt.sbprintf(&emitter.builder, " br i1 %%slice_ok%d, label %%slice_continue%d, label %%slice_trap%d\nslice_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index) + message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice bounds out of range") + emit_trap_call(emitter, message) + fmt.sbprintf(&emitter.builder, " unreachable\nslice_continue%d:\n", instruction_index) + fmt.sbprintf(&emitter.builder, " %%slice_start%d = getelementptr %s, ptr %s, i64 %s\n", instruction_index, llvm_type(item.child, &emitter.module.types), pointer_name, start_name) + fmt.sbprintf(&emitter.builder, " %%slice_result%d = insertvalue %s poison, ptr %%slice_start%d, 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index) + fmt.sbprintf(&emitter.builder, " %%slice_result_len%d = sub i64 %s, %s\n", instruction_index, end_name, start_name) + fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%slice_result%d, i64 %%slice_result_len%d, 1\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index, instruction_index) + case .Length: + if !valid_instruction(instructions, instruction.a) || + !types.is_slice(instructions[instruction.a].type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid slice length") + continue + } + fmt.sbprintf( + &emitter.builder, + " %%v%d = extractvalue %s %%v%d, 1\n", + instruction_index, + llvm_type(instructions[instruction.a].type, &emitter.module.types), + instruction.a, + ) + case .Slice_Ptr: + if !valid_instruction(instructions, instruction.a) || + !types.is_pointer(instruction.type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid container pointer") + continue + } + container_type := instructions[instruction.a].type + if types.is_array(container_type, &emitter.module.types) { + fmt.sbprintf( + &emitter.builder, + " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", + instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a, + ) + } else if types.is_slice(container_type, &emitter.module.types) { + fmt.sbprintf( + &emitter.builder, + " %%v%d = extractvalue %s %%v%d, 0\n", + instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a, + ) + } else { + emit_recovery_value(emitter, instruction_index, instruction, "invalid container pointer") + } + case .Unwrap: + optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID + item, ok := types.node(&emitter.module.types, optional_type) + if !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid optional unwrap") + continue + } + if types.is_pointer(item.child, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " %%optional_ok%d = icmp ne ptr %%v%d, null\n", instruction_index, instruction.a) + } else { + fmt.sbprintf(&emitter.builder, " %%optional_ok%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a) + } + fmt.sbprintf(&emitter.builder, " br i1 %%optional_ok%d, label %%optional_continue%d, label %%optional_trap%d\noptional_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index) + message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "attempted to unwrap none") + emit_trap_call(emitter, message) + fmt.sbprintf(&emitter.builder, " unreachable\noptional_continue%d:\n", instruction_index) + if types.is_pointer(item.child, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a) + } else { + fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a) + } + case .Orelse_Begin: + optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID + item, ok := types.node(&emitter.module.types, optional_type) + if !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) || + !valid_value(instructions, instruction.a, optional_type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid optional fallback") + continue + } + fmt.sbprintf(&emitter.builder, " %%orelse_slot%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types)) + if types.is_pointer(item.child, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " %%orelse_ok%d = icmp ne ptr %%v%d, null\n", instruction_index, instruction.a) + } else { + fmt.sbprintf(&emitter.builder, " %%orelse_ok%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a) + } + fmt.sbprintf( + &emitter.builder, + " br i1 %%orelse_ok%d, label %%orelse_some%d, label %%orelse_fallback%d\norelse_fallback%d:\n", + instruction_index, instruction_index, instruction_index, instruction_index, + ) + case .Orelse: + begin := instructions[instruction.a] if valid_instruction(instructions, instruction.a) else ir.Instruction{} + optional_type := instructions[begin.a].type if valid_instruction(instructions, begin.a) else types.INVALID + item, ok := types.node(&emitter.module.types, optional_type) + if begin.op != .Orelse_Begin || !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) || + !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid optional fallback") + continue + } + type_name := llvm_type(instruction.type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, " store %s ", type_name) + write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", ptr %%orelse_slot%d\n", instruction.a) + fmt.sbprintf(&emitter.builder, " br label %%orelse_merge%d\norelse_some%d:\n", instruction.a, instruction.a) + if types.is_pointer(item.child, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " store ptr %%v%d, ptr %%orelse_slot%d\n", begin.a, instruction.a) + } else { + fmt.sbprintf(&emitter.builder, " %%orelse_value%d = extractvalue %s %%v%d, 1\n", instruction.a, llvm_type(optional_type, &emitter.module.types), begin.a) + fmt.sbprintf(&emitter.builder, " store %s %%orelse_value%d, ptr %%orelse_slot%d\n", type_name, instruction.a, instruction.a) + } + fmt.sbprintf(&emitter.builder, " br label %%orelse_merge%d\norelse_merge%d:\n", instruction.a, instruction.a) + fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%orelse_slot%d\n", instruction_index, type_name, instruction.a) case .Widen: if !valid_instruction(instructions, instruction.a) || - !types.is_concrete_integer(instructions[instruction.a].type) || - !types.is_concrete_integer(instruction.type) || - instructions[instruction.a].type.bits >= instruction.type.bits { + !types.can_widen(instructions[instruction.a].type, instruction.type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid widening operand") continue } from_type := instructions[instruction.a].type - fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_index, llvm_type(from_type)) - write_operand(&emitter.builder, instructions, instruction.a, from_type) - fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type)) + operation := "fpext" if types.is_float(from_type, emitter.module.target) else ("sext" if types.is_signed(from_type, emitter.module.target) else "zext") + fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types)) + write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types)) case .Neg_Checked: - if !valid_value(instructions, instruction.a, instruction.type) { + if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand") continue } - type_name := llvm_type(instruction.type) + type_name := llvm_type(instruction.type, &emitter.module.types) + if types.is_float(instruction.type, emitter.module.target) { + fmt.sbprintf(&emitter.builder, " %%v%d = fneg %s ", instruction_index, type_name) + write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) + strings.write_string(&emitter.builder, "\n") + continue + } fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index) strings.write_string(&emitter.builder, "{ ") fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.ssub.with.overflow.%s(%s 0, %s ", type_name, type_name, type_name, type_name) - write_operand(&emitter.builder, instructions, instruction.a, instruction.type) + write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ")\n") fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index) strings.write_string(&emitter.builder, "{ ") @@ -263,18 +742,27 @@ emit_instruction_stream :: proc( emit_trap_call(emitter, message) fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index) case .Add_Checked: - if !valid_value(instructions, instruction.a, instruction.type) || - !valid_value(instructions, instruction.b, instruction.type) { + if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) || + !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand") continue } - type_name := llvm_type(instruction.type) + type_name := llvm_type(instruction.type, &emitter.module.types) + if types.is_float(instruction.type, emitter.module.target) { + fmt.sbprintf(&emitter.builder, " %%v%d = fadd %s ", instruction_index, type_name) + write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) + strings.write_string(&emitter.builder, ", ") + write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) + strings.write_string(&emitter.builder, "\n") + continue + } + intrinsic := "uadd" if types.is_unsigned(instruction.type, emitter.module.target) else "sadd" fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index) strings.write_string(&emitter.builder, "{ ") - fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name) - write_operand(&emitter.builder, instructions, instruction.a, instruction.type) + fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s.with.overflow.%s(%s ", type_name, intrinsic, type_name, type_name) + write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %s ", type_name) - write_operand(&emitter.builder, instructions, instruction.b, instruction.type) + write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ")\n") fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index) strings.write_string(&emitter.builder, "{ ") @@ -290,9 +778,20 @@ emit_instruction_stream :: proc( instruction_index, ) fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index) - message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer addition overflow") + message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "integer addition overflow") emit_trap_call(emitter, message) fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index) + case .Pointer_Add: + item, ok := types.node(&emitter.module.types, instruction.type) + if !ok || item.kind != .Pointer || !item.many || + !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) || + !valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer offset") + continue + } + fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 ", instruction_index, llvm_type(item.child, &emitter.module.types), instruction.a) + write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) + strings.write_string(&emitter.builder, "\n") case .Call: function_id := ir.as_function(instruction.target) if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) { @@ -303,7 +802,7 @@ emit_instruction_stream :: proc( valid_args := len(instruction.args) == len(target.param_types) if valid_args { for arg, index in instruction.args { - if !valid_value(instructions, arg, target.param_types[index]) { + if !valid_value(instructions, arg, target.param_types[index], &emitter.module.types) { valid_args = false break } @@ -317,7 +816,7 @@ emit_instruction_stream :: proc( emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands") continue } - if instruction.type.kind != .Void { + if !types.is_void(instruction.type) { fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index) } else { strings.write_string(&emitter.builder, " ") @@ -326,8 +825,12 @@ emit_instruction_stream :: proc( if target.calling_convention == .Brolang { strings.write_string(&emitter.builder, "fastcc ") } - fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name) - emit_call_args(&emitter.builder, instructions, instruction.args, target.param_types) + emit_function_result(&emitter.builder, target, &emitter.module.types) + fmt.sbprintf(&emitter.builder, " @%s(", target.link_name) + emit_call_args( + &emitter.builder, instructions, instruction.args, target.param_types, + &emitter.module.types, target.calling_convention == .C, + ) strings.write_string(&emitter.builder, ")\n") case .Trap: message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source") @@ -337,8 +840,8 @@ emit_instruction_stream :: proc( return_value = instruction.a continue } - fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function)) - write_operand(&emitter.builder, instructions, instruction.a, function.result) + fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types)) + write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) strings.write_string(&emitter.builder, "\n") after_return = true case .Return_Void: @@ -361,17 +864,18 @@ emit_globals :: proc(emitter: ^Emitter) { if global.is_static { fmt.sbprintf( &emitter.builder, - "@bro.g.%d = internal constant %s %d\n", + "@bro.g.%d = internal constant %s ", global_id, - llvm_type(global.type), - global.static_value, + llvm_type(global.type, &emitter.module.types), ) + write_constant(&emitter.builder, global.static_value, global.type, &emitter.module.types) + strings.write_string(&emitter.builder, "\n") } else { fmt.sbprintf( &emitter.builder, - "@bro.g.%d = internal global %s 0\n@bro.gstate.%d = internal global i8 0\n", + "@bro.g.%d = internal global %s zeroinitializer\n@bro.gstate.%d = internal global i8 0\n", global_id, - llvm_type(global.type), + llvm_type(global.type, &emitter.module.types), global_id, ) } @@ -379,13 +883,53 @@ emit_globals :: proc(emitter: ^Emitter) { strings.write_string(&emitter.builder, "\n") } +emit_types :: proc(emitter: ^Emitter) { + for item, index in emitter.module.types.nodes { + if item.kind != .Struct { + continue + } + id := types.DYNAMIC_START+types.Type(index) + fmt.sbprintf(&emitter.builder, "%%bro.type.%d = type ", id) + if item.opaque { + strings.write_string(&emitter.builder, "opaque\n") + continue + } + strings.write_string(&emitter.builder, "{ ") + for field, field_index in types.fields_for(&emitter.module.types, id) { + if field_index > 0 { + strings.write_string(&emitter.builder, ", ") + } + strings.write_string(&emitter.builder, llvm_type(field.type, &emitter.module.types)) + } + strings.write_string(&emitter.builder, " }\n") + } + if len(emitter.module.types.nodes) > 0 { + strings.write_string(&emitter.builder, "\n") + } +} + +emit_strings :: proc(emitter: ^Emitter) { + for value, id in emitter.module.strings { + fmt.sbprintf( + &emitter.builder, + "@bro.str.%d = private unnamed_addr constant [%d x i8] c\"", + id, len(value)+1, + ) + emit_escaped_bytes(&emitter.builder, value) + strings.write_string(&emitter.builder, "\\00\"\n") + } + if len(emitter.module.strings) > 0 { + strings.write_string(&emitter.builder, "\n") + } +} + emit_global_accessors :: proc(emitter: ^Emitter) { placeholder_function := ir.Function{result=types.I64} for global, global_id in emitter.module.globals { if global.is_static { continue } - type_name := llvm_type(global.type) + type_name := llvm_type(global.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, "define internal %s @bro.get.%d() ", type_name, global_id) strings.write_string(&emitter.builder, "{\nentry:\n") fmt.sbprintf( @@ -407,11 +951,11 @@ emit_global_accessors :: proc(emitter: ^Emitter) { placeholder_function.result = global.type value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true) fmt.sbprintf(&emitter.builder, " store %s ", type_name) - write_operand(&emitter.builder, global.initializer, value, global.type) + write_operand(&emitter.builder, global.initializer, value, global.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr @bro.g.%d\n", global_id) fmt.sbprintf(&emitter.builder, " store i8 2, ptr @bro.gstate.%d\n", global_id) fmt.sbprintf(&emitter.builder, " ret %s ", type_name) - write_operand(&emitter.builder, global.initializer, value, global.type) + write_operand(&emitter.builder, global.initializer, value, global.type, &emitter.module.types) strings.write_string(&emitter.builder, "\nready:\n") fmt.sbprintf(&emitter.builder, " %%value = load %s, ptr @bro.g.%d\n ret %s %%value\n}\n\n", type_name, global_id, type_name) } @@ -434,7 +978,7 @@ emit_constructor :: proc(emitter: ^Emitter) { strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n") for global, global_id in emitter.module.globals { if !global.is_static && !global.problematic { - fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type), global_id) + fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id) } } strings.write_string(&emitter.builder, " ret void\n}\n\n") @@ -453,15 +997,25 @@ emit_functions :: proc(emitter: ^Emitter) { if function.calling_convention == .Brolang { strings.write_string(&emitter.builder, "fastcc ") } - fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(function), function.link_name) + emit_function_result(&emitter.builder, function, &emitter.module.types) + fmt.sbprintf(&emitter.builder, " @%s(", function.link_name) for param_type, index in function.param_types { if index > 0 { strings.write_string(&emitter.builder, ", ") } if function.implementation == .Declaration { - fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type)) + fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types)) } else { - fmt.sbprintf(&emitter.builder, "%s %%v%d", llvm_type(param_type), index) + fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types)) + } + if function.calling_convention == .C { + extension := c_abi_extension(param_type, emitter.module.target) + if len(extension) > 0 { + fmt.sbprintf(&emitter.builder, " %s", extension) + } + } + if function.implementation != .Declaration { + fmt.sbprintf(&emitter.builder, " %%v%d", index) } } if function.implementation == .Declaration { @@ -503,6 +1057,10 @@ emit_declarations :: proc(emitter: ^Emitter) { fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits) strings.write_string(&emitter.builder, "declare { i") fmt.sbprintf(&emitter.builder, "%d", bits) + strings.write_string(&emitter.builder, ", i1 } @llvm.uadd.with.overflow.i") + fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits) + strings.write_string(&emitter.builder, "declare { i") + fmt.sbprintf(&emitter.builder, "%d", bits) strings.write_string(&emitter.builder, ", i1 } @llvm.ssub.with.overflow.i") fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits) } @@ -534,7 +1092,11 @@ emit :: proc( strings.builder_destroy(&emitter.builder) } - strings.write_string(&emitter.builder, "; generated by brolang\n\n") + strings.write_string(&emitter.builder, "; generated by brolang\n") + fmt.sbprintf(&emitter.builder, "target datalayout = \"%s\"\n", target.llvm_data_layout(module.target)) + fmt.sbprintf(&emitter.builder, "target triple = \"%s\"\n\n", target.llvm_triple(module.target)) + emit_types(&emitter) + emit_strings(&emitter) emit_globals(&emitter) emit_constructor(&emitter) emit_global_accessors(&emitter) diff --git a/compiler/loader/loader.odin b/compiler/loader/loader.odin index 92e1dfb..2ce0242 100644 --- a/compiler/loader/loader.odin +++ b/compiler/loader/loader.odin @@ -5,6 +5,7 @@ import "../lexer" import "../parser" import "../source" import "../symbol" +import "../types" import "core:mem" import "core:os" import "core:path/filepath" @@ -253,6 +254,92 @@ validate_imports :: proc(state: ^State) { } } +find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.Id) -> ast.Import_Id { + for import_item, index in module.imports { + if import_item.file == file && import_item.alias == alias { + return ast.import_id(index) + } + } + return ast.INVALID_IMPORT +} + +canonical_type :: proc( + module: ^ast.Module, + value: types.Type, + mapping: []types.Type, + visiting: []bool, +) -> types.Type { + if value < types.DYNAMIC_START { + return value + } + index := int(value-types.DYNAMIC_START) + if index < 0 || index >= len(mapping) { + return value + } + if types.is_valid(mapping[index]) { + return mapping[index] + } + if visiting[index] { + return value + } + visiting[index] = true + defer visiting[index] = false + item := module.type_store.nodes[index] + if item.kind == .Named { + if item.qualifier != 0 { + import_id := find_type_import(module, ast.File_Id(item.file), symbol.Id(item.qualifier)) + if import_id != ast.INVALID_IMPORT { + module.imports[import_id].used = true + import_item := module.imports[import_id] + resolved := types.find_named(&module.type_store, u32(import_item.target), item.name) + if types.is_valid(resolved) { + mapping[index] = resolved + return resolved + } + } + } + mapping[index] = value + return value + } + if item.kind == .Struct { + mapping[index] = value + fields := types.fields_for(&module.type_store, value) + for &field in fields { + field.type = canonical_type(module, field.type, mapping, visiting) + } + return value + } + if types.is_valid(item.child) { + item.child = canonical_type(module, item.child, mapping, visiting) + } + resolved := types.intern(&module.type_store, item) + mapping[index] = resolved + return resolved +} + +canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) { + original_count := len(module.type_store.nodes) + mapping := make([]types.Type, original_count, allocator) + visiting := make([]bool, original_count, allocator) + defer delete(mapping, allocator) + defer delete(visiting, allocator) + for &function in module.functions { + for ¶m in function.params { + param.type = canonical_type(module, param.type, mapping, visiting) + } + function.result = canonical_type(module, function.result, mapping, visiting) + } + for &global in module.globals { + global.type = canonical_type(module, global.type, mapping, visiting) + } + for &statement in module.statements { + statement.type = canonical_type(module, statement.type, mapping, visiting) + } + for index := 0; index < original_count; index += 1 { + _ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting) + } +} + load :: proc( root_path: string, sources: ^source.Store, @@ -275,5 +362,6 @@ load :: proc( state.root_failed = true } validate_imports(&state) + canonicalize_types(&module, allocator) return module, !state.root_failed } diff --git a/compiler/lower/lower.odin b/compiler/lower/lower.odin index d1bc0b8..1750590 100644 --- a/compiler/lower/lower.odin +++ b/compiler/lower/lower.odin @@ -3,6 +3,7 @@ package lower import "../hir" import "../ir" import "../source" +import "../target" import "../types" import "core:fmt" import "core:mem" @@ -28,8 +29,11 @@ clone_args :: proc(values: []ir.Instruction_Id, allocator: mem.Allocator) -> []i return result } -sentinel :: proc(value_type: types.Type) -> i64 { - switch value_type.bits { +sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 { + if types.is_float(value_type, selected) { + return i64(0x7fc0_0000) if types.bits(value_type, selected) == 32 else transmute(i64)u64(0x7ff8_0000_0000_0000) + } + switch types.bits(value_type, selected) { case 8: return -86 case 16: return -21846 case 32: return -1431655766 @@ -53,14 +57,14 @@ append_recovery_value :: proc( diagnostic=diagnostic, }) fallback := value_type - if !types.is_concrete_integer(fallback) { + if !types.is_valid(fallback) { fallback = types.I64 } return append_instruction(state, ir.Instruction{ op=.Const, span=span, type=fallback, - integer=sentinel(fallback), + integer=sentinel(fallback, state.hir_module.target), target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, @@ -76,6 +80,187 @@ Lower_Expr_Frame :: struct { args: []ir.Instruction_Id, } +lower_nested_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { + outer := state.expr_stack + state.expr_stack = nil + state.expr_stack.allocator = state.allocator + result := lower_expr(state, expr_id) + delete(state.expr_stack) + state.expr_stack = outer + return result +} + +lower_location :: proc(state: ^State, expr_id: hir.Expr_Id, for_write := false) -> ir.Instruction_Id { + if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(state.hir_module.exprs) { + return ir.INVALID_INSTRUCTION + } + expr := state.hir_module.exprs[expr_id] + #partial switch expr.kind { + case .Local: + local := hir.as_local(expr.target) + if local != hir.INVALID_LOCAL && int(local) < len(state.local_slots) { + return state.local_slots[local] + } + case .Global: + global := hir.as_global(expr.target) + if global != hir.INVALID_GLOBAL && int(global) < len(state.hir_module.globals) { + return append_instruction(state, ir.Instruction{ + op=.Address_Global, span=expr.span, type=expr.type, + target=ir.global_ref(ir.Global_Id(global)), + a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + case .Deref: + return lower_nested_expr(state, expr.left) + case .Index: + container_type := state.hir_module.exprs[expr.left].type + container := lower_nested_expr(state, expr.left) + if types.is_array(container_type, &state.hir_module.types) { + container = lower_location(state, expr.left, for_write) + } + index := lower_nested_expr(state, expr.right) + return append_instruction(state, ir.Instruction{ + op=.Index_Address, span=expr.span, type=expr.type, + integer=0 if for_write else 1, + target=ir.INVALID_REF, a=container, b=index, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Field: + base_type := state.hir_module.exprs[expr.left].type + base := lower_nested_expr(state, expr.left) + if !types.is_pointer(base_type, &state.hir_module.types) { + base = lower_location(state, expr.left, for_write) + } + return append_instruction(state, ir.Instruction{ + op=.Field_Address, span=expr.span, type=expr.type, integer=expr.integer, + target=ir.INVALID_REF, a=base, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + return ir.INVALID_INSTRUCTION +} + +lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { + expr := state.hir_module.exprs[expr_id] + #partial switch expr.kind { + case .String: + return append_instruction(state, ir.Instruction{ + op=.String, span=expr.span, type=expr.type, integer=expr.integer, + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Array, .Struct: + args := make([]ir.Instruction_Id, len(expr.args), state.allocator) + for arg, index in expr.args { + args[index] = lower_nested_expr(state, arg) + } + return append_instruction(state, ir.Instruction{ + op=.Aggregate, span=expr.span, type=expr.type, args=args, + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .None: + return append_instruction(state, ir.Instruction{ + op=.None, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Optional_Some: + value := lower_nested_expr(state, expr.left) + return append_instruction(state, ir.Instruction{ + op=.Optional_Some, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Address: + location := lower_location(state, expr.left, types.is_mutable(expr.type, &state.hir_module.types)) + if location != ir.INVALID_INSTRUCTION { + return append_instruction(state, ir.Instruction{ + op=.Address_Of, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=location, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + return append_recovery_value(state, expr.span, expr.type, expr.diagnostic) + case .Deref, .Index, .Field: + location := lower_location(state, expr_id) + if location == ir.INVALID_INSTRUCTION { + return append_recovery_value(state, expr.span, expr.type, expr.diagnostic) + } + return append_instruction(state, ir.Instruction{ + op=.Load, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=location, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Slice: + container := lower_nested_expr(state, expr.left) + if types.is_array(state.hir_module.exprs[expr.left].type, &state.hir_module.types) { + container = lower_location(state, expr.left) + } + args := make([]ir.Instruction_Id, len(expr.args), state.allocator) + for arg, index in expr.args { + args[index] = ir.INVALID_INSTRUCTION + if arg != hir.INVALID_EXPR { + args[index] = lower_nested_expr(state, arg) + } + } + return append_instruction(state, ir.Instruction{ + op=.Slice, span=expr.span, type=expr.type, args=args, + target=ir.INVALID_REF, a=container, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Unwrap: + value := lower_nested_expr(state, expr.left) + return append_instruction(state, ir.Instruction{ + op=.Unwrap, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Length: + container_type := state.hir_module.exprs[expr.left].type + item, ok := types.node(&state.hir_module.types, container_type) + if ok && item.kind == .Array { + return append_instruction(state, ir.Instruction{ + op=.Const, span=expr.span, type=types.USIZE, integer=i64(item.count), + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + value := lower_nested_expr(state, expr.left) + return append_instruction(state, ir.Instruction{ + op=.Length, span=expr.span, type=types.USIZE, + target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Slice_Ptr: + container_type := state.hir_module.exprs[expr.left].type + value := lower_nested_expr(state, expr.left) + if types.is_array(container_type, &state.hir_module.types) { + value = lower_location(state, expr.left) + } + return append_instruction(state, ir.Instruction{ + op=.Slice_Ptr, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Orelse: + value := lower_nested_expr(state, expr.left) + begin := append_instruction(state, ir.Instruction{ + op=.Orelse_Begin, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + fallback := lower_nested_expr(state, expr.right) + return append_instruction(state, ir.Instruction{ + op=.Orelse, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=begin, b=fallback, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + return append_recovery_value(state, expr.span, expr.type, expr.diagnostic) +} + lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { stack := state.expr_stack clear_dynamic_array(&stack) @@ -98,16 +283,20 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { } expr := state.hir_module.exprs[frame.expr] if frame.stage == 0 { - switch expr.kind { + #partial switch expr.kind { case .Invalid: last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) _ = pop(&stack) - case .Integer: + case .Integer, .Float: last = append_instruction(state, ir.Instruction{ op=.Const, span=expr.span, type=expr.type, integer=expr.integer, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, }) _ = pop(&stack) + case .String, .Array, .Struct, .None, .Optional_Some, .Address, .Deref, + .Index, .Slice, .Field, .Length, .Slice_Ptr, .Unwrap, .Orelse: + last = lower_compound_expr(state, frame.expr) + _ = pop(&stack) case .Local: last = ir.INVALID_INSTRUCTION local := hir.as_local(expr.target) @@ -141,7 +330,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { case .Negate: stack[frame_index].stage = 5 append(&stack, Lower_Expr_Frame{expr=expr.left}) - case .Add: + case .Add, .Pointer_Add: stack[frame_index].stage = 2 append(&stack, Lower_Expr_Frame{expr=expr.left}) case .Call: @@ -183,7 +372,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { } if frame.stage == 3 { last = append_instruction(state, ir.Instruction{ - op=.Add_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF, + op=.Pointer_Add if expr.kind == .Pointer_Add else .Add_Checked, + span=expr.span, type=expr.type, target=ir.INVALID_REF, a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC, }) _ = pop(&stack) @@ -238,6 +428,20 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m }) state.local_values[local_id] = param } + for local_id in function.params { + slot := append_instruction(&state, ir.Instruction{ + op=.Alloca, type=function.locals[local_id].type, + target=ir.local_ref(ir.Local_Id(local_id)), + a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + append_instruction(&state, ir.Instruction{ + op=.Store, type=function.locals[local_id].type, + target=ir.INVALID_REF, a=slot, b=state.local_values[local_id], + diagnostic=source.INVALID_DIAGNOSTIC, + }) + state.local_slots[local_id] = slot + } for statement_id in function.body { statement := hir_module.statements[statement_id] @@ -252,36 +456,39 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m continue } local := function.locals[statement.local] - if local.mutable { - slot := append_instruction(&state, ir.Instruction{ - op=.Alloca, - span=statement.span, - type=local.type, - target=ir.local_ref(ir.Local_Id(statement.local)), - a=ir.INVALID_INSTRUCTION, - b=ir.INVALID_INSTRUCTION, - diagnostic=source.INVALID_DIAGNOSTIC, - }) - state.local_slots[statement.local] = slot - append_instruction(&state, ir.Instruction{ - op=.Store, - span=statement.span, - type=local.type, - target=ir.INVALID_REF, - a=slot, - b=value, - diagnostic=source.INVALID_DIAGNOSTIC, - }) - } else { - state.local_values[statement.local] = value - } + slot := append_instruction(&state, ir.Instruction{ + op=.Alloca, + span=statement.span, + type=local.type, + target=ir.local_ref(ir.Local_Id(statement.local)), + a=ir.INVALID_INSTRUCTION, + b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + state.local_slots[statement.local] = slot + append_instruction(&state, ir.Instruction{ + op=.Store, + span=statement.span, + type=local.type, + target=ir.INVALID_REF, + a=slot, + b=value, + diagnostic=source.INVALID_DIAGNOSTIC, + }) case .Assignment: value := lower_expr(&state, statement.expr) slot := ir.INVALID_INSTRUCTION - if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) { + 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] + 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{ op=.Trap, span=statement.span, type=types.VOID, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic, @@ -291,7 +498,7 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m append_instruction(&state, ir.Instruction{ op=.Store, span=statement.span, - type=function.locals[statement.local].type, + type=value_type, target=ir.INVALID_REF, a=slot, b=value, @@ -337,13 +544,13 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m if len(state.instructions) == 0 || (state.instructions[len(state.instructions)-1].op != .Return && state.instructions[len(state.instructions)-1].op != .Return_Void) { - if function.result.kind == .Void { + if types.is_void(function.result) { append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}) } else { value := append_instruction(&state, ir.Instruction{ op=.Const, type=function.result, - integer=sentinel(function.result), + integer=sentinel(function.result, hir_module.target), target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, @@ -373,7 +580,12 @@ lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, al } lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Module { - module := ir.init_module(allocator) + module := ir.init_module(hir_module.target, allocator) + types.destroy_store(&module.types) + module.types = types.clone_store(&hir_module.types, allocator) + for value in hir_module.strings { + append(&module.strings, fmt.aprintf("%s", value, allocator=allocator)) + } for global in hir_module.globals { _ = ir.global_id(len(module.globals)) append(&module.globals, ir.Global{ diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index 5b145a9..d3f98d7 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -4,9 +4,12 @@ import "../ast" import "../source" import "../symbol" import "../token" +import "../types" import "base:intrinsics" import "core:fmt" +import "core:strconv" import "core:strings" +import "core:unicode/utf8" Parser :: struct { tokens: ^token.Stream, @@ -80,36 +83,225 @@ invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> ast is_type_token :: proc(kind: token.Kind) -> bool { #partial switch kind { - case .Keyword_Int, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64, .Keyword_Void: + case .Keyword_Int, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64, + .Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64, + .Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64, + .Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar, + .Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint, + .Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong, + .Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble, + .Keyword_Void, .Identifier, .Question, .At, .Star, .Left_Bracket: return true } return false } +decode_character :: proc(parser: ^Parser, tok: token.Token) -> (u64, bool) { + text := token_text(parser, tok) + if len(text) < 3 { + return 0, false + } + contents := text[1:len(text)-1] + if len(contents) == 2 && contents[0] == '\\' { + switch contents[1] { + case '0': return 0, true + case 'n': return '\n', true + case 'r': return '\r', true + case 't': return '\t', true + case '\\': return '\\', true + case '\'': return '\'', true + } + return 0, false + } + value, width := utf8.decode_rune_in_string(contents) + return u64(value), width == len(contents) +} + +parse_type_constant :: proc(parser: ^Parser) -> (u64, bool) { + negative := false + if _, ok := allow(parser, .Minus); ok { + negative = true + } + tok := current(parser) + if tok.kind == .Integer { + advance(parser) + value, ok := parse_integer_magnitude(token_text(parser, tok)) + if !ok { + return 0, false + } + if negative { + return transmute(u64)-i64(value), true + } + return value, true + } + if !negative && tok.kind == .Character { + advance(parser) + return decode_character(parser, tok) + } + source.add(parser.diagnostics, tok.span, "expected an integer or character constant") + return 0, false +} + parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax { tok := current(parser) + if tok.kind == .Question { + advance(parser) + child := parse_type(parser) + return types.intern(&parser.module.type_store, types.Node{kind=.Optional, child=child}) + } + if tok.kind == .At || tok.kind == .Star { + many := tok.kind == .Star + advance(parser) + _, mutable := allow(parser, .Keyword_Mut) + child := parse_type(parser) + return types.intern(&parser.module.type_store, types.Node{ + kind=.Pointer, + child=child, + mutable=mutable, + many=many, + }) + } + if tok.kind == .Left_Bracket { + advance(parser) + node := types.Node{} + if _, ok := allow(parser, .Right_Bracket); ok { + node.kind = .Slice + } else if _, ok := allow(parser, .Semicolon); ok { + node.kind = .Slice + node.has_sentinel = true + node.sentinel, _ = parse_type_constant(parser) + if _, ok = allow(parser, .Right_Bracket); !ok { + source.add(parser.diagnostics, current(parser).span, "expected ']' after sentinel slice type") + } + } else { + node.kind = .Array + if _, ok := allow(parser, .Underscore); ok { + node.inferred_count = true + } else { + count, ok := parse_type_constant(parser) + if ok { + node.count = count + } + } + if _, ok := allow(parser, .Semicolon); ok { + node.has_sentinel = true + node.sentinel, _ = parse_type_constant(parser) + } + if _, ok := allow(parser, .Right_Bracket); !ok { + source.add(parser.diagnostics, current(parser).span, "expected ']' after array type") + } + } + _, node.mutable = allow(parser, .Keyword_Mut) + node.child = parse_type(parser) + return types.intern(&parser.module.type_store, node) + } #partial switch tok.kind { case .Keyword_Int: advance(parser) - return .Int + return types.INT case .Keyword_I8: advance(parser) - return .I8 + return types.I8 case .Keyword_I16: advance(parser) - return .I16 + return types.I16 case .Keyword_I32: advance(parser) - return .I32 + return types.I32 case .Keyword_I64: advance(parser) - return .I64 + return types.I64 + case .Keyword_U8: + advance(parser) + return types.U8 + case .Keyword_U16: + advance(parser) + return types.U16 + case .Keyword_U32: + advance(parser) + return types.U32 + case .Keyword_U64: + advance(parser) + return types.U64 + case .Keyword_Isize: + advance(parser) + return types.ISIZE + case .Keyword_Usize: + advance(parser) + return types.USIZE + case .Keyword_F32: + advance(parser) + return types.F32 + case .Keyword_F64: + advance(parser) + return types.F64 + case .Keyword_C_Char: + advance(parser) + return types.C_CHAR + case .Keyword_C_Schar: + advance(parser) + return types.C_SCHAR + case .Keyword_C_Uchar: + advance(parser) + return types.C_UCHAR + case .Keyword_C_Short: + advance(parser) + return types.C_SHORT + case .Keyword_C_Ushort: + advance(parser) + return types.C_USHORT + case .Keyword_C_Int: + advance(parser) + return types.C_INT + case .Keyword_C_Uint: + advance(parser) + return types.C_UINT + case .Keyword_C_Long: + advance(parser) + return types.C_LONG + case .Keyword_C_Ulong: + advance(parser) + return types.C_ULONG + case .Keyword_C_Longlong: + advance(parser) + return types.C_LONGLONG + case .Keyword_C_Ulonglong: + advance(parser) + return types.C_ULONGLONG + case .Keyword_C_Float: + advance(parser) + return types.C_FLOAT + case .Keyword_C_Double: + advance(parser) + return types.C_DOUBLE + case .Keyword_C_Longdouble: + advance(parser) + return types.C_LONGDOUBLE case .Keyword_Void: advance(parser) - return .Void + return types.VOID + case .Identifier: + first := advance(parser) + name := first + qualifier := symbol.INVALID + if _, ok := allow(parser, .Dot); ok { + qualifier = first.symbol + if current(parser).kind != .Identifier { + source.add(parser.diagnostics, current(parser).span, "expected a type name after '.'") + return types.INVALID + } + name = advance(parser) + } + return types.named( + &parser.module.type_store, + u32(parser.pkg), + u32(name.symbol), + u32(qualifier), + u32(parser.file), + ) } source.add(parser.diagnostics, tok.span, "expected a type") - return .Invalid + return types.INVALID } skip_parenthesized :: proc(parser: ^Parser) -> source.Span { @@ -168,6 +360,93 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok }) } +parse_array_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { + start := advance(parser) + parser.delimiter_depth += 1 + defer parser.delimiter_depth -= 1 + args: [dynamic]ast.Expr_Id + args.allocator = parser.module.allocator + skip_newlines(parser) + for current(parser).kind != .Right_Bracket && current(parser).kind != .Eof { + append(&args, parse_expression_bp(parser, 0, nesting+1)) + skip_newlines(parser) + if _, ok := allow(parser, .Comma); ok { + skip_newlines(parser) + continue + } + break + } + end, ok := allow(parser, .Right_Bracket) + if !ok { + source.add(parser.diagnostics, current(parser).span, "expected ']' after array literal") + end = start + } + return add_expr(parser, ast.Expr{ + kind=.Array, + span=span_from(start.span, end.span), + args=args[:], + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) +} + +parse_struct_literal :: proc( + parser: ^Parser, + qualifier: symbol.Id, + first, name: token.Token, + nesting: int, +) -> ast.Expr_Id { + left_brace := advance(parser) + parser.delimiter_depth += 1 + defer parser.delimiter_depth -= 1 + args: [dynamic]ast.Expr_Id + args.allocator = parser.module.allocator + skip_newlines(parser) + for current(parser).kind != .Right_Brace && current(parser).kind != .Eof { + field := current(parser) + if field.kind != .Identifier { + source.add(parser.diagnostics, field.span, "expected a keyed struct field initializer") + break + } + advance(parser) + if _, ok := allow(parser, .Equal); !ok { + source.add(parser.diagnostics, current(parser).span, "expected '=' after struct field name") + } + skip_newlines(parser) + value := parse_expression_bp(parser, 0, nesting+1) + append(&args, add_expr(parser, ast.Expr{ + kind=.Keyed, + span=span_from(field.span, parser.module.exprs[value].span), + name=field.symbol, + left=value, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + })) + skip_newlines(parser) + if _, ok := allow(parser, .Comma); ok { + skip_newlines(parser) + continue + } + break + } + right_brace, ok := allow(parser, .Right_Brace) + if !ok { + source.add(parser.diagnostics, current(parser).span, "expected '}' after struct literal") + right_brace = left_brace + } + return add_expr(parser, ast.Expr{ + kind=.Struct_Literal, + span=source.Span{file=name.span.file, start=first.span.start, end=right_brace.span.end}, + qualifier=qualifier, + name=name.symbol, + args=args[:], + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) +} + parse_integer_magnitude :: proc(text: string) -> (u64, bool) { value: u64 for byte in transmute([]byte)text { @@ -203,6 +482,58 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) + case .Character: + advance(parser) + value, ok := decode_character(parser, tok) + if !ok { + return invalid_expr(parser, tok.span, "character literal must contain one Unicode code point") + } + return add_expr(parser, ast.Expr{ + kind=.Integer, + span=tok.span, + integer=value, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Float: + advance(parser) + value, ok := strconv.parse_f64(token_text(parser, tok)) + if !ok { + return invalid_expr(parser, tok.span, "invalid floating-point literal") + } + return add_expr(parser, ast.Expr{ + kind=.Float, + span=tok.span, + integer=transmute(u64)value, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .String: + advance(parser) + value := decode_import_path(parser, tok) + id := u64(len(parser.module.strings)) + append(&parser.module.strings, value) + return add_expr(parser, ast.Expr{ + kind=.String, + span=tok.span, + integer=id, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Keyword_None: + advance(parser) + return add_expr(parser, ast.Expr{ + kind=.None, + span=tok.span, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + case .Left_Bracket: + return parse_array_literal(parser, nesting) case .Identifier: first := advance(parser) name := first @@ -217,6 +548,9 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { if current(parser).kind == .Left_Paren { return parse_call(parser, qualifier, first, name, nesting) } + if current(parser).kind == .Left_Brace { + return parse_struct_literal(parser, qualifier, first, name, nesting) + } return add_expr(parser, ast.Expr{ kind=.Name, span=span_from(first.span, name.span), @@ -262,6 +596,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) { #partial switch kind { + case .Keyword_Orelse: + return 2, 3, true case .Plus: return 10, 11, true } @@ -270,7 +606,7 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) { prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) { #partial switch kind { - case .Minus: + case .Minus, .Ampersand: return 20, true } return 0, false @@ -293,7 +629,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int operand := parse_expression_bp(parser, right_power, nesting+1) operand_expr := parser.module.exprs[operand] left = add_expr(parser, ast.Expr{ - kind=.Negate, + kind=.Address if operator.kind == .Ampersand else .Negate, span=span_from(operator.span, operand_expr.span), left=operand, right=ast.INVALID_EXPR, @@ -306,17 +642,99 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int skip_newlines(parser) } for { + if current(parser).kind == .Caret || current(parser).kind == .Question { + operator := advance(parser) + left_expr := parser.module.exprs[left] + left = add_expr(parser, ast.Expr{ + kind=.Deref if operator.kind == .Caret else .Unwrap, + span=span_from(left_expr.span, operator.span), + left=left, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + continue + } + if current(parser).kind == .Dot { + advance(parser) + field := current(parser) + if field.kind != .Identifier { + left = invalid_expr(parser, field.span, "expected a field name after '.'") + continue + } + advance(parser) + left_expr := parser.module.exprs[left] + left = add_expr(parser, ast.Expr{ + kind=.Field, + span=span_from(left_expr.span, field.span), + name=field.symbol, + left=left, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + continue + } + if current(parser).kind == .Left_Bracket { + start_token := advance(parser) + parser.delimiter_depth += 1 + skip_newlines(parser) + start_expr := ast.INVALID_EXPR + end_expr := ast.INVALID_EXPR + slicing := false + if _, ok := allow(parser, .Range); ok { + slicing = true + } else { + start_expr = parse_expression_bp(parser, 0, nesting+1) + skip_newlines(parser) + if _, ok := allow(parser, .Range); ok { + slicing = true + } + } + skip_newlines(parser) + if slicing && current(parser).kind != .Right_Bracket { + end_expr = parse_expression_bp(parser, 0, nesting+1) + skip_newlines(parser) + } + end_token, ok := allow(parser, .Right_Bracket) + if !ok { + source.add(parser.diagnostics, current(parser).span, "expected ']' after index or slice") + end_token = start_token + } + parser.delimiter_depth -= 1 + left_expr := parser.module.exprs[left] + if slicing { + args := make([]ast.Expr_Id, 2, parser.module.allocator) + args[0] = start_expr + args[1] = end_expr + left = add_expr(parser, ast.Expr{ + kind=.Slice, + span=span_from(left_expr.span, end_token.span), + args=args, + left=left, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } else { + left = add_expr(parser, ast.Expr{ + kind=.Index, + span=span_from(left_expr.span, end_token.span), + left=left, + right=start_expr, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + continue + } left_power, right_power, ok := infix_binding_power(current(parser).kind) if !ok || left_power < minimum_binding_power { break } - advance(parser) + operator := advance(parser) skip_newlines(parser) right := parse_expression_bp(parser, right_power, nesting+1) left_expr := parser.module.exprs[left] right_expr := parser.module.exprs[right] left = add_expr(parser, ast.Expr{ - kind=.Add, + kind=.Orelse if operator.kind == .Keyword_Orelse else .Add, span=span_from(left_expr.span, right_expr.span), left=left, right=right, @@ -381,6 +799,31 @@ parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id { return id } +starts_declared_type :: proc(parser: ^Parser) -> bool { + if current(parser).kind != .Left_Bracket { + return is_type_token(current(parser).kind) + } + depth := 0 + cursor := parser.cursor + for cursor < len(parser.tokens.items) { + kind := parser.tokens.items[cursor].kind + if kind == .Left_Bracket { + depth += 1 + } else if kind == .Right_Bracket { + depth -= 1 + if depth == 0 { + cursor += 1 + break + } + } + cursor += 1 + } + if cursor < len(parser.tokens.items) && parser.tokens.items[cursor].kind == .Keyword_Mut { + cursor += 1 + } + return cursor < len(parser.tokens.items) && is_type_token(parser.tokens.items[cursor].kind) +} + parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id { if current(parser).kind == .Keyword_Return { return parse_return(parser) @@ -389,9 +832,9 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id { if current(parser).kind == .Identifier || current(parser).kind == .Underscore { start_cursor := parser.cursor name := advance(parser) - type_syntax := ast.Type_Syntax.Invalid + type_syntax := types.INVALID had_type := false - if is_type_token(current(parser).kind) { + if starts_declared_type(parser) { type_syntax = parse_type(parser) had_type = true } @@ -413,6 +856,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id { name=name.symbol, type=type_syntax, immutable=immutable, + target=ast.INVALID_EXPR, expr=expr, diagnostic=source.INVALID_DIAGNOSTIC, }) @@ -422,6 +866,19 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id { } expr := parse_expression(parser) + if _, ok := allow(parser, .Equal); ok { + skip_newlines(parser) + value := parse_expression(parser) + id := ast.stmt_id(len(parser.module.statements)) + append(&parser.module.statements, ast.Stmt{ + kind=.Assignment, + span=span_from(parser.module.exprs[expr].span, parser.module.exprs[value].span), + target=expr, + expr=value, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + return id + } id := ast.stmt_id(len(parser.module.statements)) append(&parser.module.statements, ast.Stmt{ kind=.Expression, @@ -541,6 +998,57 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) { }) } +parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool) { + start := advance(parser) + id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol)) + ended_by_newline := current(parser).kind == .Newline + skip_newlines(parser) + if current(parser).kind != .Left_Brace { + if !c_layout { + source.add(parser.diagnostics, start.span, "native struct declarations require a body") + } + if !types.define_struct(&parser.module.type_store, id, nil, c_layout, true) { + source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) + } + if !ended_by_newline { + _ = finish_statement(parser) + } + return + } + advance(parser) + fields: [dynamic]types.Field + fields.allocator = parser.module.allocator + defer delete(fields) + skip_newlines(parser) + for current(parser).kind != .Right_Brace && current(parser).kind != .Eof { + if current(parser).kind != .Identifier { + source.add(parser.diagnostics, current(parser).span, "expected a struct field name") + for current(parser).kind != .Newline && + current(parser).kind != .Right_Brace && + current(parser).kind != .Eof { + advance(parser) + } + skip_newlines(parser) + continue + } + field_name := advance(parser) + field_type := parse_type(parser) + append(&fields, types.Field{name=u32(field_name.symbol), type=field_type}) + if _, ok := allow(parser, .Comma); ok { + skip_newlines(parser) + continue + } + _ = finish_statement(parser, true) + } + if _, ok := allow(parser, .Right_Brace); !ok { + source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields") + } + if !types.define_struct(&parser.module.type_store, id, fields[:], c_layout, false) { + source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) + } + _ = finish_statement(parser) +} + decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string { text := token_text(parser, tok) if len(text) < 2 { @@ -553,6 +1061,13 @@ decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string { if value == '\\' && index+1 < len(text)-1 { index += 1 value = text[index] + switch value { + case 'n': value = '\n' + case 'r': value = '\r' + case 't': value = '\t' + case '0': value = 0 + case: + } } strings.write_byte(&builder, value) } @@ -621,7 +1136,7 @@ parse_top_level :: proc(parser: ^Parser) { } parser.cursor = saved } - type_syntax := ast.Type_Syntax.Invalid + type_syntax := types.INVALID if is_type_token(current(parser).kind) { type_syntax = parse_type(parser) } @@ -634,23 +1149,17 @@ parse_top_level :: proc(parser: ^Parser) { advance(parser) skip_newlines(parser) - c_abi := false if operator.kind == .Colon_Colon && - current(parser).kind == .Identifier && - token_text(parser, current(parser)) == "c" { - saved := parser.cursor - c_abi = true - advance(parser) - skip_newlines(parser) - if current(parser).kind != .Keyword_Func { - c_abi = false - parser.cursor = saved - } - } - if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Func { + (current(parser).kind == .Keyword_Func || current(parser).kind == .Keyword_C_Func) { + c_abi := current(parser).kind == .Keyword_C_Func parse_function(parser, name, c_abi) return } + if operator.kind == .Colon_Colon && + (current(parser).kind == .Keyword_Struct || current(parser).kind == .Keyword_C_Struct) { + parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct) + return + } expr := parse_expression(parser) _ = ast.global_id(len(parser.module.globals)) diff --git a/compiler/target/target.odin b/compiler/target/target.odin new file mode 100644 index 0000000..1bdd787 --- /dev/null +++ b/compiler/target/target.odin @@ -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 "" +} + +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 +} diff --git a/compiler/token/token.odin b/compiler/token/token.odin index 41f008a..484a54c 100644 --- a/compiler/token/token.odin +++ b/compiler/token/token.odin @@ -9,27 +9,66 @@ Kind :: enum u8 { Newline, Identifier, Integer, + Float, String, + Character, Underscore, Colon_Colon, Equal, Plus, Minus, Dot, + Range, + At, + Star, + Ampersand, + Caret, + Question, + Semicolon, + Left_Bracket, + Right_Bracket, Left_Paren, Right_Paren, Left_Brace, Right_Brace, Comma, Keyword_Func, + Keyword_C_Func, + Keyword_Struct, + Keyword_C_Struct, Keyword_Import, Keyword_Return, + Keyword_Mut, + Keyword_None, + Keyword_Orelse, Keyword_Void, Keyword_Int, Keyword_I8, Keyword_I16, Keyword_I32, Keyword_I64, + Keyword_U8, + Keyword_U16, + Keyword_U32, + Keyword_U64, + Keyword_Isize, + Keyword_Usize, + Keyword_F32, + Keyword_F64, + Keyword_C_Char, + Keyword_C_Schar, + Keyword_C_Uchar, + Keyword_C_Short, + Keyword_C_Ushort, + Keyword_C_Int, + Keyword_C_Uint, + Keyword_C_Long, + Keyword_C_Ulong, + Keyword_C_Longlong, + Keyword_C_Ulonglong, + Keyword_C_Float, + Keyword_C_Double, + Keyword_C_Longdouble, } Token :: struct { diff --git a/compiler/types/types.odin b/compiler/types/types.odin index 149270d..b14439e 100644 --- a/compiler/types/types.odin +++ b/compiler/types/types.odin @@ -1,94 +1,619 @@ package types +import "../target" import "core:fmt" +import "core:mem" -Numeric_Category :: enum { +// Type is a compact ID. Builtin scalar types occupy the stable low range; +// recursive and nominal types are interned in Store starting at DYNAMIC_START. +Type :: distinct u32 + +INVALID :: Type(0) +VOID :: Type(1) +INT :: Type(2) + +I8 :: Type(3) +I16 :: Type(4) +I32 :: Type(5) +I64 :: Type(6) +U8 :: Type(7) +U16 :: Type(8) +U32 :: Type(9) +U64 :: Type(10) +ISIZE :: Type(11) +USIZE :: Type(12) +F32 :: Type(13) +F64 :: Type(14) + +C_CHAR :: Type(15) +C_SCHAR :: Type(16) +C_UCHAR :: Type(17) +C_SHORT :: Type(18) +C_USHORT :: Type(19) +C_INT :: Type(20) +C_UINT :: Type(21) +C_LONG :: Type(22) +C_ULONG :: Type(23) +C_LONGLONG :: Type(24) +C_ULONGLONG :: Type(25) +C_FLOAT :: Type(26) +C_DOUBLE :: Type(27) +C_LONGDOUBLE :: Type(28) + +DYNAMIC_START :: Type(64) + +Numeric_Category :: enum u8 { None, Signed_Integer, Unsigned_Integer, Float, } -Kind :: enum { +Kind :: enum u8 { Invalid, Void, Int_Constraint, - Concrete, + Scalar, + Array, + Pointer, + Slice, + Optional, + Named, + Struct, } -Type :: struct { - kind: Kind, - category: Numeric_Category, - bits: int, +Node :: struct { + kind: Kind, + child: Type, + count: u64, + sentinel: u64, + field_start: u32, + field_count: u32, + pkg: u32, + name: u32, + qualifier: u32, + file: u32, + mutable: bool, + many: bool, + has_sentinel: bool, + inferred_count: bool, + c_layout: bool, + opaque: bool, + declared: bool, } -INVALID :: Type { - kind = .Invalid, +Field :: struct { + name: u32, + type: Type, } -VOID :: Type { - kind = .Void, + +Store :: struct { + nodes: [dynamic]Node, + fields: [dynamic]Field, + selected: target.Target, + allocator: mem.Allocator, } -INT :: Type { - kind = .Int_Constraint, - category = .Signed_Integer, + +init_store :: proc(allocator := context.allocator) -> Store { + store: Store + store.nodes.allocator = allocator + store.fields.allocator = allocator + store.selected = target.DEFAULT + store.allocator = allocator + return store } -I8 :: Type { - kind = .Concrete, - category = .Signed_Integer, - bits = 8, + +destroy_store :: proc(store: ^Store) { + delete(store.nodes) + delete(store.fields) } -I16 :: Type { - kind = .Concrete, - category = .Signed_Integer, - bits = 16, + +clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store { + store := init_store(allocator) + append(&store.nodes, ..source.nodes[:]) + append(&store.fields, ..source.fields[:]) + store.selected = source.selected + return store } -I32 :: Type { - kind = .Concrete, - category = .Signed_Integer, - bits = 32, + +intern :: proc(store: ^Store, candidate: Node) -> Type { + if candidate.kind != .Struct && candidate.kind != .Named { + for existing, index in store.nodes { + if existing == candidate { + return DYNAMIC_START+Type(index) + } + } + } + id := DYNAMIC_START+Type(len(store.nodes)) + append(&store.nodes, candidate) + return id } -I64 :: Type { - kind = .Concrete, - category = .Signed_Integer, - bits = 64, + +named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type { + normalized_file := file if qualifier != 0 else u32(0) + for existing, index in store.nodes { + if (existing.kind == .Named || existing.kind == .Struct) && + existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && + existing.file == normalized_file { + return DYNAMIC_START+Type(index) + } + } + return intern(store, Node{kind=.Named, pkg=pkg, name=name, qualifier=qualifier, file=normalized_file}) +} + +find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type { + for existing, index in store.nodes { + if (existing.kind == .Named || existing.kind == .Struct) && + existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier { + return DYNAMIC_START+Type(index) + } + } + return INVALID +} + +define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool { + existing, ok := node(store, id) + if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared { + return false + } + index := int(id-DYNAMIC_START) + store.nodes[index].kind = .Struct + store.nodes[index].c_layout = c_layout + store.nodes[index].opaque = opaque + store.nodes[index].declared = true + store.nodes[index].field_start = u32(len(store.fields)) + store.nodes[index].field_count = u32(len(fields)) + append(&store.fields, ..fields) + return true +} + +fields_for :: proc(store: ^Store, value: Type) -> []Field { + item, ok := node(store, value) + if !ok || item.kind != .Struct { + return nil + } + start := int(item.field_start) + end := start+int(item.field_count) + if start < 0 || end > len(store.fields) { + return nil + } + return store.fields[start:end] +} + +kind :: proc(value: Type, store: ^Store = nil) -> Kind { + switch value { + case INVALID: + return .Invalid + case VOID: + return .Void + case INT: + return .Int_Constraint + } + if value >= I8 && value <= C_LONGDOUBLE { + return .Scalar + } + if store != nil && value >= DYNAMIC_START { + index := int(value-DYNAMIC_START) + if index >= 0 && index < len(store.nodes) { + return store.nodes[index].kind + } + } + return .Invalid +} + +node :: proc(store: ^Store, value: Type) -> (Node, bool) { + if store == nil || value < DYNAMIC_START { + return {}, false + } + index := int(value-DYNAMIC_START) + if index < 0 || index >= len(store.nodes) { + return {}, false + } + return store.nodes[index], true } is_valid :: proc(value: Type) -> bool { - return value.kind != .Invalid + return value != INVALID +} + +is_void :: proc(value: Type) -> bool { + return value == VOID +} + +is_constraint :: proc(value: Type) -> bool { + return value == INT +} + +is_c :: proc(value: Type) -> bool { + return value >= C_CHAR && value <= C_LONGDOUBLE +} + +as_c_primitive :: proc(value: Type) -> (target.C_Primitive, bool) { + switch value { + case C_CHAR: return .Char, true + case C_SCHAR: return .Schar, true + case C_UCHAR: return .Uchar, true + case C_SHORT: return .Short, true + case C_USHORT: return .Ushort, true + case C_INT: return .Int, true + case C_UINT: return .Uint, true + case C_LONG: return .Long, true + case C_ULONG: return .Ulong, true + case C_LONGLONG: return .Longlong, true + case C_ULONGLONG: return .Ulonglong, true + case C_FLOAT: return .Float, true + case C_DOUBLE: return .Double, true + case C_LONGDOUBLE: return .Longdouble, true + } + return {}, false +} + +category :: proc(value: Type, selected := target.DEFAULT) -> Numeric_Category { + switch value { + case I8, I16, I32, I64, ISIZE: + return .Signed_Integer + case U8, U16, U32, U64, USIZE: + return .Unsigned_Integer + case F32, F64: + return .Float + case: + primitive, ok := as_c_primitive(value) + if !ok { + return .None + } + switch target.c_primitive_layout(selected, primitive).kind { + case .Signed_Integer: return .Signed_Integer + case .Unsigned_Integer: return .Unsigned_Integer + case .Float: return .Float + } + } + return .None +} + +bits :: proc(value: Type, selected := target.DEFAULT) -> int { + switch value { + case I8, U8: + return 8 + case I16, U16: + return 16 + case I32, U32, F32: + return 32 + case I64, U64, F64: + return 64 + case ISIZE, USIZE: + return target.pointer_bits(selected) + case: + primitive, ok := as_c_primitive(value) + return target.c_primitive_layout(selected, primitive).bits if ok else 0 + } +} + +alignment :: proc(value: Type, selected := target.DEFAULT) -> int { + if primitive, ok := as_c_primitive(value); ok { + return target.c_primitive_layout(selected, primitive).alignment + } + width := bits(value, selected)/8 + return min(max(width, 1), 8) +} + +representation :: proc(value: Type, selected := target.DEFAULT) -> Type { + if value == ISIZE { + return I64 + } + if value == USIZE { + return U64 + } + primitive, ok := as_c_primitive(value) + if !ok { + return value + } + layout := target.c_primitive_layout(selected, primitive) + if layout.kind == .Float { + return F32 if layout.bits == 32 else F64 + } + if layout.kind == .Signed_Integer { + switch layout.bits { + case 8: return I8 + case 16: return I16 + case 32: return I32 + case: return I64 + } + } + switch layout.bits { + case 8: return U8 + case 16: return U16 + case 32: return U32 + case: return U64 + } +} + +is_concrete_scalar :: proc(value: Type) -> bool { + return kind(value) == .Scalar +} + +is_concrete :: proc(value: Type, store: ^Store = nil) -> bool { + value_kind := kind(value, store) + if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer || + value_kind == .Slice || value_kind == .Optional { + return true + } + if value_kind == .Struct { + item, ok := node(store, value) + return ok && item.declared + } + return false +} + +is_pointer :: proc(value: Type, store: ^Store) -> bool { + return kind(value, store) == .Pointer +} + +is_array :: proc(value: Type, store: ^Store) -> bool { + return kind(value, store) == .Array +} + +is_slice :: proc(value: Type, store: ^Store) -> bool { + return kind(value, store) == .Slice +} + +is_optional :: proc(value: Type, store: ^Store) -> bool { + return kind(value, store) == .Optional +} + +is_struct :: proc(value: Type, store: ^Store) -> bool { + return kind(value, store) == .Struct +} + +is_optional_pointer :: proc(value: Type, store: ^Store) -> bool { + item, ok := node(store, value) + return ok && item.kind == .Optional && is_pointer(item.child, store) +} + +is_runtime_value :: proc(value: Type, store: ^Store) -> bool { + value_kind := kind(value, store) + if value_kind == .Scalar || value_kind == .Pointer { + return true + } + if value_kind == .Slice || value_kind == .Array || value_kind == .Optional { + return !contains_c_struct_by_value(value, store) + } + if value_kind == .Struct { + item, ok := node(store, value) + return ok && item.declared && !item.opaque && !contains_c_struct_by_value(value, store) + } + return false +} + +contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bool { + if depth > 256 { + return true + } + item, ok := node(store, value) + if !ok { + return false + } + if item.kind == .Pointer { + return false + } + if item.kind == .Struct { + if item.c_layout { + return true + } + for field in fields_for(store, value) { + if contains_c_struct_by_value(field.type, store, depth+1) { + return true + } + } + return false + } + if item.kind == .Array || item.kind == .Slice || item.kind == .Optional { + return contains_c_struct_by_value(item.child, store, depth+1) + } + return false +} + +is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) -> bool { + if allow_void && is_void(value) { + return true + } + return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) +} + +child_type :: proc(value: Type, store: ^Store) -> Type { + item, ok := node(store, value) + return item.child if ok else INVALID +} + +logical_count :: proc(value: Type, store: ^Store) -> u64 { + item, ok := node(store, value) + return item.count if ok else 0 +} + +physical_count :: proc(value: Type, store: ^Store) -> u64 { + item, ok := node(store, value) + if !ok { + return 0 + } + return item.count + (u64(1) if item.has_sentinel else u64(0)) +} + +is_mutable :: proc(value: Type, store: ^Store) -> bool { + item, ok := node(store, value) + return ok && item.mutable +} + +is_many_pointer :: proc(value: Type, store: ^Store) -> bool { + item, ok := node(store, value) + return ok && item.kind == .Pointer && item.many +} + +is_c_struct :: proc(value: Type, store: ^Store) -> bool { + item, ok := node(store, value) + return ok && item.kind == .Struct && item.c_layout +} + +pointer :: proc(store: ^Store, child: Type, mutable, many: bool) -> Type { + return intern(store, Node{kind=.Pointer, child=child, mutable=mutable, many=many}) +} + +slice :: proc(store: ^Store, child: Type, mutable: bool, has_sentinel := false, sentinel: u64 = 0) -> Type { + return intern(store, Node{ + kind=.Slice, + child=child, + mutable=mutable, + has_sentinel=has_sentinel, + sentinel=sentinel, + }) +} + +array :: proc( + store: ^Store, + child: Type, + count: u64, + mutable: bool, + has_sentinel := false, + sentinel: u64 = 0, +) -> Type { + return intern(store, Node{ + kind=.Array, + child=child, + count=count, + mutable=mutable, + has_sentinel=has_sentinel, + sentinel=sentinel, + }) +} + +optional :: proc(store: ^Store, child: Type) -> Type { + return intern(store, Node{kind=.Optional, child=child}) +} + +with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type { + item, ok := node(store, value) + if !ok || item.kind != .Array { + return value + } + item.count = count + item.inferred_count = false + return intern(store, item) +} + +can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool { + from_node, from_ok := node(store, from) + to_node, to_ok := node(store, to) + return from_ok && to_ok && + from_node.kind == .Pointer && to_node.kind == .Pointer && + from_node.child == to_node.child && from_node.many == to_node.many && + from_node.mutable && !to_node.mutable +} + +is_opaque_struct :: proc(value: Type, store: ^Store) -> bool { + item, ok := node(store, value) + return ok && item.kind == .Struct && item.opaque +} + +size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 { + #partial switch kind(value, store) { + case .Scalar: + return u64(bits(value, selected)/8) + case .Pointer: + return u64(target.pointer_bits(selected)/8) + case .Slice: + return u64(target.pointer_bits(selected)/8*2) + case .Array: + item, _ := node(store, value) + return physical_count(value, store)*size(item.child, store, selected) + case .Optional: + item, _ := node(store, value) + if is_pointer(item.child, store) { + return u64(target.pointer_bits(selected)/8) + } + child_size := size(item.child, store, selected) + child_align := u64(alignment_of(item.child, store, selected)) + return ((child_size+1+child_align-1)/child_align)*child_align + case .Struct: + offset: u64 + max_align: u64 = 1 + for field in fields_for(store, value) { + field_align := u64(alignment_of(field.type, store, selected)) + offset = (offset+field_align-1)/field_align*field_align + offset += size(field.type, store, selected) + max_align = max(max_align, field_align) + } + return (offset+max_align-1)/max_align*max_align + case: + return 0 + } +} + +alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> int { + #partial switch kind(value, store) { + case .Scalar: + return alignment(value, selected) + case .Pointer, .Slice: + return target.pointer_bits(selected)/8 + case .Array, .Optional: + return alignment_of(child_type(value, store), store, selected) + case .Struct: + result := 1 + for field in fields_for(store, value) { + result = max(result, alignment_of(field.type, store, selected)) + } + return result + case: + return 1 + } } is_concrete_integer :: proc(value: Type) -> bool { - return( - value.kind == .Concrete && - (value.category == .Signed_Integer || value.category == .Unsigned_Integer) \ - ) + category := category(value) + return kind(value) == .Scalar && + (category == .Signed_Integer || category == .Unsigned_Integer) } -is_signed :: proc(value: Type) -> bool { - return value.kind == .Concrete && value.category == .Signed_Integer +is_float :: proc(value: Type, selected := target.DEFAULT) -> bool { + return kind(value) == .Scalar && category(value, selected) == .Float +} + +is_signed :: proc(value: Type, selected := target.DEFAULT) -> bool { + return kind(value) == .Scalar && category(value, selected) == .Signed_Integer +} + +is_unsigned :: proc(value: Type, selected := target.DEFAULT) -> bool { + return kind(value) == .Scalar && category(value, selected) == .Unsigned_Integer } equal :: proc(a, b: Type) -> bool { - return a.kind == b.kind && a.category == b.category && a.bits == b.bits + return a == b +} + +same_numeric_family :: proc(a, b: Type) -> bool { + if category(a) != category(b) { + return false + } + // C primitives are intentionally distinct semantic types. Exact-width + // Brolang scalars may widen only to other Brolang scalars. + return !is_c(a) && !is_c(b) } can_widen :: proc(from, to: Type) -> bool { if equal(from, to) { return true } - return( - from.kind == .Concrete && - to.kind == .Concrete && - from.category == to.category && - from.bits < to.bits \ - ) + return is_concrete_scalar(from) && + is_concrete_scalar(to) && + same_numeric_family(from, to) && + bits(from) < bits(to) } widest :: proc(a, b: Type) -> Type { - if a.kind != .Concrete || b.kind != .Concrete || a.category != b.category { + if equal(a, b) && is_concrete_scalar(a) { + return a + } + if !is_concrete_scalar(a) || !is_concrete_scalar(b) || !same_numeric_family(a, b) { return INVALID } - if a.bits >= b.bits { + if bits(a) >= bits(b) { return a } return b @@ -108,32 +633,37 @@ smallest_signed_for_literal :: proc(value: i64) -> Type { } name :: proc(value: Type) -> string { - switch value.kind { - case .Invalid: - return "" - case .Void: - return "void" - case .Int_Constraint: - return "int" - case .Concrete: - switch value.category { - case .Signed_Integer: - switch value.bits { - case 8: - return "i8" - case 16: - return "i16" - case 32: - return "i32" - case 64: - return "i64" - } - case .Unsigned_Integer: - return fmt.tprintf("u%d", value.bits) - case .Float: - return fmt.tprintf("f%d", value.bits) - case .None: - } + switch value { + case INVALID: return "" + case VOID: return "void" + case INT: return "int" + case I8: return "i8" + case I16: return "i16" + case I32: return "i32" + case I64: return "i64" + case U8: return "u8" + case U16: return "u16" + case U32: return "u32" + case U64: return "u64" + case ISIZE: return "isize" + case USIZE: return "usize" + case F32: return "f32" + case F64: return "f64" + case C_CHAR: return "c_char" + case C_SCHAR: return "c_schar" + case C_UCHAR: return "c_uchar" + case C_SHORT: return "c_short" + case C_USHORT: return "c_ushort" + case C_INT: return "c_int" + case C_UINT: return "c_uint" + case C_LONG: return "c_long" + case C_ULONG: return "c_ulong" + case C_LONGLONG: return "c_longlong" + case C_ULONGLONG: return "c_ulonglong" + case C_FLOAT: return "c_float" + case C_DOUBLE: return "c_double" + case C_LONGDOUBLE: return "c_longdouble" + case: + return fmt.tprintf("", value) } - return "" } diff --git a/compiler_tests.odin b/compiler_tests.odin index c698000..5d80262 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -14,6 +14,7 @@ import "./compiler/lower" import "./compiler/parser" import "./compiler/source" import "./compiler/symbol" +import "./compiler/target" import "./compiler/token" import "./compiler/types" import "core:fmt" @@ -86,9 +87,10 @@ main :: func() void { _ = value } compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) { testing.expect_value(t, size_of(source.Span), 12) testing.expect_value(t, size_of(token.Token), 24) - testing.expect_value(t, size_of(ast.Expr), 64) - testing.expect_value(t, size_of(hir.Expr), 88) - testing.expect_value(t, size_of(ir.Instruction), 88) + testing.expect(t, size_of(ast.Expr) <= 64) + testing.expect(t, size_of(hir.Expr) <= 88) + testing.expect(t, size_of(ir.Instruction) <= 88) + testing.expect_value(t, size_of(types.Type), 4) source_index, source_ok := source.source_index(source.Source_Id(0), 1) testing.expect_value(t, source_index, 0) @@ -195,8 +197,8 @@ main :: func() void { _ = give() } @(test) parser_distinguishes_bodyless_declarations_and_definitions :: proc(t: ^testing.T) { - text := `foreign :: c func(value i32) i32 -defined :: c func(value i32) i32 + text := `foreign :: c_func(value i32) i32 +defined :: c_func(value i32) i32 { return value } @@ -229,7 +231,7 @@ main :: func() void {} parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) { text := `c :: 5 x :: c -foreign :: c func() i32 +foreign :: c_func() i32 broken :: c 5 main :: func() void {} ` @@ -403,6 +405,8 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T) "thing", "--link", "helper.o", + "--target", + "aarch64-macos", }) defer delete(options.link_arguments) @@ -415,15 +419,18 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T) testing.expect_value(t, options.link_arguments[1].kind, linker.Kind.Library_Path) testing.expect_value(t, options.link_arguments[2].kind, linker.Kind.Library) testing.expect_value(t, options.link_arguments[3].value, "helper.o") + testing.expect_value(t, target.name(options.target), "aarch64-macos") _, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"}) _, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"}) _, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"}) _, duplicate_empty_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "", "-o", "two"}) + _, invalid_target := parse_cli_args([]string{"brolang", "app", "-o", "out", "--target", "x86_64-linux"}) testing.expect(t, !unknown_valid) testing.expect(t, !incomplete_valid) testing.expect(t, !duplicate_output_valid) testing.expect(t, !duplicate_empty_output_valid) + testing.expect(t, !invalid_target) } @(test) @@ -453,7 +460,7 @@ main :: func() void {} } @(test) -parser_rejects_chained_package_access :: proc(t: ^testing.T) { +parser_accepts_chained_field_access :: proc(t: ^testing.T) { text := `main :: func() void { _ = first.second.value } @@ -468,7 +475,7 @@ parser_rejects_chained_package_access :: proc(t: ^testing.T) { module := parser.parse(&stream, &source_file, &diagnostics) defer ast.destroy_module(&module) - testing.expect(t, len(diagnostics.items) > 0) + testing.expect_value(t, len(diagnostics.items), 0) } @(test) @@ -566,7 +573,7 @@ main :: func() void { @(test) pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) { - text := `sum_c :: c func(a, b int) int { + text := `sum_c :: c_func(a, b int) int { return a + b } sum_bro :: func(a, b int) int { @@ -597,16 +604,16 @@ main :: func() void { testing.expect_value(t, len(diagnostics.items), 0) testing.expect_value(t, llvm_text, second_llvm_text) - testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p0__sum_c__i8__i8")) + testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p0__sum_c__i8__i8")) testing.expect(t, strings.contains(llvm_text, "define internal fastcc i8 @bro__p0__sum_bro__i8__i8")) testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8")) } @(test) pipeline_emits_only_referenced_foreign_declarations_with_exact_names :: proc(t: ^testing.T) { - text := `used :: c func(a, b i32) i32 -unused :: c func() i32 -bodyful :: c func(value i32) i32 { + text := `used :: c_func(a, b i32) i32 +unused :: c_func() i32 +bodyful :: c_func(value i32) i32 { return value } main :: func() void { @@ -637,9 +644,126 @@ main :: func() void { testing.expect(t, strings.contains(llvm_text, "define i32 @bro_c__p0__bodyful__i32")) } +@(test) +c_primitives_remain_distinct_with_apple_silicon_representations :: proc(t: ^testing.T) { + testing.expect(t, types.C_CHAR != types.C_SCHAR) + testing.expect(t, types.C_SCHAR != types.C_UCHAR) + testing.expect(t, types.C_INT != types.I32) + testing.expect(t, types.C_ULONG != types.U64) + testing.expect_value(t, types.representation(types.C_CHAR), types.I8) + testing.expect_value(t, types.representation(types.C_SCHAR), types.I8) + testing.expect_value(t, types.representation(types.C_UCHAR), types.U8) + testing.expect_value(t, types.representation(types.C_SHORT), types.I16) + testing.expect_value(t, types.representation(types.C_USHORT), types.U16) + testing.expect_value(t, types.representation(types.C_INT), types.I32) + testing.expect_value(t, types.representation(types.C_UINT), types.U32) + testing.expect_value(t, types.representation(types.C_LONG), types.I64) + testing.expect_value(t, types.representation(types.C_ULONG), types.U64) + testing.expect_value(t, types.representation(types.C_LONGLONG), types.I64) + testing.expect_value(t, types.representation(types.C_ULONGLONG), types.U64) + testing.expect_value(t, types.representation(types.C_FLOAT), types.F32) + testing.expect_value(t, types.representation(types.C_DOUBLE), types.F64) + testing.expect_value(t, types.representation(types.C_LONGDOUBLE), types.F64) + testing.expect_value(t, target.llvm_triple(target.DEFAULT), "arm64-apple-macosx13.0.0") +} + +@(test) +interop_foundation_emits_compounds_and_narrow_c_abi_attributes :: proc(t: ^testing.T) { + text := `Point :: struct { + x i32 + y i32 +} +signed :: c_func(value c_char) c_char +unsigned :: c_func(value c_uchar) c_uchar +exact :: c_func(value u32) u32 +fallback :: func() i32 { + return 9 +} +main :: func() void { + c :: 1 + values [2;0]mut u8 = [1, 2] + point Point :: Point{x = 3, y = 4} + maybe ?i32 = 5 + _ = c + _ = values[2] + _ = values.ptr + 1 + _ = values.len + _ = values[0..2] + _ = "hello".ptr + _ = "hello".len + _ = point.x + _ = maybe? + _ = maybe orelse 0 + _ = maybe orelse fallback() + _ = signed(1) + _ = unsigned(1) + _ = exact(1) +} +` + source_file := source.Source{path="test.bro", text=text} + diagnostics := source.init_diagnostics(&source_file) + defer source.destroy_diagnostics(&diagnostics) + symbols := symbol.init_table() + defer symbol.destroy_table(&symbols) + stream := lexer.lex(&source_file, &diagnostics, &symbols) + defer delete(stream.items) + ast_module := parser.parse(&stream, &source_file, &diagnostics) + defer ast.destroy_module(&ast_module) + hir_module := checker.check(&ast_module, &diagnostics, &symbols) + defer hir.destroy_module(&hir_module) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols) + defer delete(llvm_text) + + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect(t, strings.contains(llvm_text, "target triple = \"arm64-apple-macosx13.0.0\"")) + testing.expect(t, strings.contains(llvm_text, "declare signext i8 @signed(i8 signext)")) + testing.expect(t, strings.contains(llvm_text, "declare zeroext i8 @unsigned(i8 zeroext)")) + testing.expect(t, strings.contains(llvm_text, "declare i32 @exact(i32)")) + testing.expect(t, strings.contains(llvm_text, "@bro.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\"")) + testing.expect(t, strings.contains(llvm_text, "getelementptr [3 x i8]")) + testing.expect(t, strings.contains(llvm_text, "attempted to unwrap none")) + testing.expect(t, strings.contains(llvm_text, "orelse_fallback")) + testing.expect(t, strings.contains(llvm_text, "orelse_some")) +} + +@(test) +c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) { + text := `Defined :: c_struct { + value c_int +} +Opaque :: c_struct +read :: c_func(value @Defined) c_int +bad_param :: c_func(value Defined) void +bad_result :: c_func() Defined +main :: func() void {} +` + source_file := source.Source{path="test.bro", text=text} + diagnostics := source.init_diagnostics(&source_file) + defer source.destroy_diagnostics(&diagnostics) + symbols := symbol.init_table() + defer symbol.destroy_table(&symbols) + stream := lexer.lex(&source_file, &diagnostics, &symbols) + defer delete(stream.items) + ast_module := parser.parse(&stream, &source_file, &diagnostics) + defer ast.destroy_module(&ast_module) + hir_module := checker.check(&ast_module, &diagnostics, &symbols) + defer hir.destroy_module(&hir_module) + + found_param := false + found_result := false + for diagnostic in diagnostics.items { + found_param = found_param || strings.contains(diagnostic.message, "cannot be passed by value") + found_result = found_result || strings.contains(diagnostic.message, "cannot be returned by value") + } + testing.expect(t, found_param) + testing.expect(t, found_result) +} + @(test) invalid_foreign_declarations_are_eagerly_diagnosed_and_calls_trap :: proc(t: ^testing.T) { - text := `bad :: c func(value int) int + text := `bad :: c_func(value int) int native :: func() i32 main :: func() void { _ = bad(1) @@ -667,7 +791,7 @@ main :: func() void { for diagnostic in diagnostics.items { found_parameter = found_parameter || strings.contains(diagnostic.message, "requires concrete parameter types") found_result = found_result || strings.contains(diagnostic.message, "requires a concrete or void result type") - found_native = found_native || strings.contains(diagnostic.message, "must use 'c func'") + found_native = found_native || strings.contains(diagnostic.message, "must use 'c_func'") } testing.expect(t, found_parameter) testing.expect(t, found_result) @@ -707,7 +831,7 @@ duplicate_foreign_symbols_across_packages_are_poisoned :: proc(t: ^testing.T) { @(test) bodyless_root_main_recovers_as_a_trap_definition :: proc(t: ^testing.T) { - text := "main :: c func() i32\n" + text := "main :: c_func() i32\n" source_file := source.Source{path="test.bro", text=text} diagnostics := source.init_diagnostics(&source_file) defer source.destroy_diagnostics(&diagnostics) @@ -974,7 +1098,7 @@ eager_global_calls_root_specializations :: proc(t: ^testing.T) { unused_native :: func() i32 { return 9 } -unused_foreign :: c func() i32 +unused_foreign :: c_func() i32 value i32 :: make() main :: func() void {} ` @@ -1040,16 +1164,16 @@ long_generic_call_chain_reaches_a_fixed_point :: proc(t: ^testing.T) { builder := strings.builder_make() defer strings.builder_destroy(&builder) for index in 0 ..< 70 { - fmt.sbprintf(&builder, "f%d :: func(value int) int ", index) + fmt.sbprintf(&builder, "fn%d :: func(value int) int ", index) strings.write_string(&builder, "{ return ") if index == 69 { strings.write_string(&builder, "value") } else { - fmt.sbprintf(&builder, "f%d(value)", index+1) + fmt.sbprintf(&builder, "fn%d(value)", index+1) } strings.write_string(&builder, " }\n") } - strings.write_string(&builder, "main :: func() i32 { return f0(1) }\n") + strings.write_string(&builder, "main :: func() i32 { return fn0(1) }\n") source_file := source.Source{path="test.bro", text=strings.to_string(builder)} diagnostics := source.init_diagnostics(&source_file) @@ -1195,6 +1319,17 @@ foreign_function_links_from_c_source :: proc(t: ^testing.T) { testing.expect_value(t, state.exit_code, 42) } +@(test) +interop_foundation_matches_zig_compiled_apple_silicon_c_fixture :: proc(t: ^testing.T) { + output := "/tmp/brolang-test-interop-foundation" + defer _ = os.remove(output) + arguments := []linker.Argument{{kind=.Input, value="examples/interop/foundation/native.c"}} + status := compiler_core.compile_package("examples/interop/foundation", output, arguments) + testing.expect_value(t, status, 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 1) +} + @(test) foreign_function_links_from_object :: proc(t: ^testing.T) { output := "/tmp/brolang-test-foreign-object" @@ -1442,6 +1577,8 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T) "/usr/bin/env", "zig", "cc", + "-target", + "aarch64-macos", "-Wno-override-module", "module.ll", "native.c", @@ -2127,8 +2264,8 @@ package_llvm_is_deterministic_and_symbols_include_package_ids :: proc(t: ^testin testing.expect(t, loaded) testing.expect_value(t, len(diagnostics.items), 0) testing.expect_value(t, llvm_text, second_llvm_text) - testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p1__same")) - testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p2__same")) + testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p1__same")) + testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p2__same")) testing.expect(t, strings.contains(llvm_text, "define i32 @main()")) } diff --git a/examples/interop/foundation/main.bro b/examples/interop/foundation/main.bro new file mode 100644 index 0000000..2f262c7 --- /dev/null +++ b/examples/interop/foundation/main.bro @@ -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) +} diff --git a/examples/interop/foundation/native.c b/examples/interop/foundation/native.c new file mode 100644 index 0000000..60a1e8d --- /dev/null +++ b/examples/interop/foundation/native.c @@ -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; +} diff --git a/examples/interop/manual/main.bro b/examples/interop/manual/main.bro index 9287eec..8d69cd4 100644 --- a/examples/interop/manual/main.bro +++ b/examples/interop/manual/main.bro @@ -1,4 +1,4 @@ -foreign_add :: c func(a, b i32) i32 +foreign_add :: c_func(a, b i32) i32 main :: func() i32 { return foreign_add(20, 22) diff --git a/examples/packages/c_symbols/left/left.bro b/examples/packages/c_symbols/left/left.bro index b2f7c92..1f12483 100644 --- a/examples/packages/c_symbols/left/left.bro +++ b/examples/packages/c_symbols/left/left.bro @@ -1,3 +1,3 @@ -same :: c func() int { +same :: c_func() int { return 1 } diff --git a/examples/packages/c_symbols/right/right.bro b/examples/packages/c_symbols/right/right.bro index 1983aae..d34099d 100644 --- a/examples/packages/c_symbols/right/right.bro +++ b/examples/packages/c_symbols/right/right.bro @@ -1,3 +1,3 @@ -same :: c func() int { +same :: c_func() int { return 2 } diff --git a/examples/packages/foreign_duplicate/left/left.bro b/examples/packages/foreign_duplicate/left/left.bro index 7eb4b87..ff9aed7 100644 --- a/examples/packages/foreign_duplicate/left/left.bro +++ b/examples/packages/foreign_duplicate/left/left.bro @@ -1 +1 @@ -same :: c func() i32 +same :: c_func() i32 diff --git a/examples/packages/foreign_duplicate/right/right.bro b/examples/packages/foreign_duplicate/right/right.bro index 7eb4b87..ff9aed7 100644 --- a/examples/packages/foreign_duplicate/right/right.bro +++ b/examples/packages/foreign_duplicate/right/right.bro @@ -1 +1 @@ -same :: c func() i32 +same :: c_func() i32 diff --git a/examples/programs/prototype/main.bro b/examples/programs/prototype/main.bro index 15bec4d..3973a93 100644 --- a/examples/programs/prototype/main.bro +++ b/examples/programs/prototype/main.bro @@ -2,7 +2,7 @@ x int :: 2 -sum_c :: c func(a, b int) int { +sum_c :: c_func(a, b int) int { return a + b } diff --git a/main.odin b/main.odin index 7800563..8489dbd 100644 --- a/main.odin +++ b/main.odin @@ -2,6 +2,7 @@ package main import "./compiler" import "./compiler/linker" +import "./compiler/target" import "core:fmt" import "core:os/os2" @@ -9,13 +10,14 @@ Cli_Options :: struct { input_path: string, output_path: string, link_arguments: []linker.Argument, + target: target.Target, } parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_Options, bool) { if len(args) < 4 { return {}, false } - options := Cli_Options{input_path=args[1]} + options := Cli_Options{input_path=args[1], target=target.DEFAULT} link_arguments: [dynamic]linker.Argument link_arguments.allocator = allocator output_set := false @@ -43,6 +45,13 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O append(&link_arguments, linker.Argument{kind=.Library_Path, value=value}) case "--library": append(&link_arguments, linker.Argument{kind=.Library, value=value}) + case "--target": + selected, ok := target.parse(value) + if !ok { + delete(link_arguments) + return {}, false + } + options.target = selected case: delete(link_arguments) return {}, false @@ -58,7 +67,7 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O print_usage :: proc() { fmt.eprintln( - "usage: brolang -o [--link | --library-path | --library ]...", + "usage: brolang -o [--target aarch64-macos] [--link | --library-path | --library ]...", ) } @@ -69,7 +78,7 @@ main :: proc() { os2.exit(2) } defer delete(options.link_arguments) - status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments) + status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target) if status != 0 { os2.exit(status) }