From 395bdd4cc46ba5238a462175ab5c4d13fbae7e19 Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Fri, 26 Jan 2024 18:54:03 +0100 Subject: [PATCH] Pass first test with LLVM. Ditch C transpiler --- .github/workflows/ci.yml | 11 +- bootstrap/Compilation.zig | 4698 ++++++++++++----- bootstrap/backend/c_transpiler.zig | 2055 ------- bootstrap/backend/llvm.cpp | 834 +++ bootstrap/backend/llvm.zig | 3489 ++++++++++++ bootstrap/backend/llvm_bindings.zig | 118 + bootstrap/data_structures.zig | 269 +- bootstrap/frontend/lexer.zig | 435 ++ bootstrap/frontend/lexical_analyzer.zig | 265 - .../{syntactic_analyzer.zig => parser.zig} | 1549 +++--- bootstrap/frontend/semantic_analyzer.zig | 4376 --------------- bootstrap/main.zig | 6 +- build.zig | 231 +- ci.sh | 85 +- lib/std/build.nat | 2 +- lib/std/builtin.nat | 1 - lib/std/os.nat | 6 +- lib/std/os/linux.nat | 2 +- lib/std/start.nat | 6 +- lib/std/std.nat | 51 + {test => todo_test}/integral/first/.gitignore | 0 {test => todo_test}/integral/first/build.nat | 0 .../integral/first/src/main.nat | 0 .../integral/link_libc/.gitignore | 0 .../integral/link_libc/build.nat | 0 .../integral/link_libc/src/main.nat | 0 .../standalone/add_sub/main.nat | 0 {test => todo_test}/standalone/and/main.nat | 0 todo_test/standalone/break/main.nat | 12 + {test => todo_test}/standalone/div/main.nat | 0 todo_test/standalone/first/main.nat | 3 + .../standalone/foreach/main.nat | 0 todo_test/standalone/foreach_slice/main.nat | 30 + {test => todo_test}/standalone/fork/main.nat | 0 .../standalone/fork_exec/main.nat | 0 .../standalone/hello_world/main.nat | 0 {test => todo_test}/standalone/imul/main.nat | 0 .../standalone/loop_break/main.nat | 0 todo_test/standalone/optional_wrap/main.nat | 8 + {test => todo_test}/standalone/or/main.nat | 3 +- .../standalone/self_exe_path/main.nat | 0 .../standalone/shifts/main.nat | 0 .../standalone/simple_bool/main.nat | 0 todo_test/standalone/slice/main.nat | 14 + todo_test/standalone/slice2/main.nat | 6 + .../standalone/slice_expression/main.nat | 10 + {test => todo_test}/standalone/stack/main.nat | 0 .../standalone/virtual_memory/main.nat | 0 {test => todo_test}/standalone/xor/main.nat | 0 49 files changed, 9605 insertions(+), 8970 deletions(-) delete mode 100644 bootstrap/backend/c_transpiler.zig create mode 100644 bootstrap/backend/llvm.cpp create mode 100644 bootstrap/backend/llvm.zig create mode 100644 bootstrap/backend/llvm_bindings.zig create mode 100644 bootstrap/frontend/lexer.zig delete mode 100644 bootstrap/frontend/lexical_analyzer.zig rename bootstrap/frontend/{syntactic_analyzer.zig => parser.zig} (55%) delete mode 100644 bootstrap/frontend/semantic_analyzer.zig rename {test => todo_test}/integral/first/.gitignore (100%) rename {test => todo_test}/integral/first/build.nat (100%) rename {test => todo_test}/integral/first/src/main.nat (100%) rename {test => todo_test}/integral/link_libc/.gitignore (100%) rename {test => todo_test}/integral/link_libc/build.nat (100%) rename {test => todo_test}/integral/link_libc/src/main.nat (100%) rename {test => todo_test}/standalone/add_sub/main.nat (100%) rename {test => todo_test}/standalone/and/main.nat (100%) create mode 100644 todo_test/standalone/break/main.nat rename {test => todo_test}/standalone/div/main.nat (100%) create mode 100644 todo_test/standalone/first/main.nat rename {test => todo_test}/standalone/foreach/main.nat (100%) create mode 100644 todo_test/standalone/foreach_slice/main.nat rename {test => todo_test}/standalone/fork/main.nat (100%) rename {test => todo_test}/standalone/fork_exec/main.nat (100%) rename {test => todo_test}/standalone/hello_world/main.nat (100%) rename {test => todo_test}/standalone/imul/main.nat (100%) rename {test => todo_test}/standalone/loop_break/main.nat (100%) create mode 100644 todo_test/standalone/optional_wrap/main.nat rename {test => todo_test}/standalone/or/main.nat (66%) rename {test => todo_test}/standalone/self_exe_path/main.nat (100%) rename {test => todo_test}/standalone/shifts/main.nat (100%) rename {test => todo_test}/standalone/simple_bool/main.nat (100%) create mode 100644 todo_test/standalone/slice/main.nat create mode 100644 todo_test/standalone/slice2/main.nat create mode 100644 todo_test/standalone/slice_expression/main.nat rename {test => todo_test}/standalone/stack/main.nat (100%) rename {test => todo_test}/standalone/virtual_memory/main.nat (100%) rename {test => todo_test}/standalone/xor/main.nat (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a59204b..ca94ebe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,13 +13,8 @@ concurrency: cancel-in-progress: true jobs: - build: - strategy: - matrix: - os: [ - ubuntu-latest, - ] - runs-on: ${{ matrix.os }} + build_and_test: + runs-on: [ self-hosted, Linux, x64 ] timeout-minutes: 15 steps: - name: Checkout @@ -29,4 +24,4 @@ jobs: with: version: master - name: Test - run: ./ci.sh + run: ./ci.sh ../todo_foo_debug_path ../../../../../dev/llvm-static-release-zen4-17.0.6/out/x86_64-linux-musl-native diff --git a/bootstrap/Compilation.zig b/bootstrap/Compilation.zig index a7ace66..643871b 100644 --- a/bootstrap/Compilation.zig +++ b/bootstrap/Compilation.zig @@ -1,5 +1,3 @@ -const Compilation = @This(); - const std = @import("std"); const assert = std.debug.assert; const equal = std.mem.eql; @@ -9,6 +7,7 @@ const Allocator = std.mem.Allocator; const data_structures = @import("data_structures.zig"); const ArrayList = data_structures.ArrayList; const AutoHashMap = data_structures.AutoHashMap; +const AutoArrayHashMap = data_structures.AutoArrayHashMap; const BlockList = data_structures.BlockList; const HashMap = data_structures.HashMap; const SegmentedList = data_structures.SegmentedList; @@ -16,25 +15,10 @@ const StringKeyMap = data_structures.StringKeyMap; const StringHashMap = data_structures.StringHashMap; const StringArrayHashMap = data_structures.StringArrayHashMap; -const lexical_analyzer = @import("frontend/lexical_analyzer.zig"); -const Token = lexical_analyzer.Token; -const syntactic_analyzer = @import("frontend/syntactic_analyzer.zig"); -const Node = syntactic_analyzer.Node; -const semantic_analyzer = @import("frontend/semantic_analyzer.zig"); -const c_transpiler = @import("backend/c_transpiler.zig"); - -test { - _ = lexical_analyzer; - _ = syntactic_analyzer; - _ = semantic_analyzer; - _ = data_structures; -} - -base_allocator: Allocator, -cwd_absolute_path: []const u8, -directory_absolute_path: []const u8, -executable_absolute_path: []const u8, -build_directory: std.fs.Dir, +const lexer = @import("frontend/lexer.zig"); +const parser = @import("frontend/parser.zig"); +const Node = parser.Node; +const llvm = @import("backend/llvm.zig"); const cache_dir_name = "cache"; const installation_dir_name = "installation"; @@ -48,8 +32,8 @@ fn reportUnterminatedArgumentError(string: []const u8) noreturn { std.debug.panic("Unterminated argument: {s}", .{string}); } -fn parseArguments(compilation: *const Compilation) !Compilation.Module.Descriptor { - const allocator = compilation.base_allocator; +fn parseArguments(context: *const Context) !Descriptor { + const allocator = context.allocator; const arguments = (try std.process.argsAlloc(allocator))[1..]; var maybe_executable_path: ?[]const u8 = null; @@ -59,9 +43,11 @@ fn parseArguments(compilation: *const Compilation) !Compilation.Module.Descripto var maybe_only_parse: ?bool = null; var link_libc = false; var maybe_executable_name: ?[]const u8 = null; + const generate_debug_information = true; + var is_build = false; if (arguments.len == 0) { - // foo + is_build = true; } else if (equal(u8, arguments[0], "init")) { if (arguments.len == 1) { unreachable; @@ -192,11 +178,10 @@ fn parseArguments(compilation: *const Compilation) !Compilation.Module.Descripto } const cross_target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = target_triplet }); - const target = cross_target.toTarget(); - const transpile_to_c = should_transpile_to_c orelse true; + const target = try std.zig.system.resolveTargetQuery(cross_target); + const transpile_to_c = should_transpile_to_c orelse false; const only_parse = maybe_only_parse orelse false; - var is_build = false; const main_package_path = if (maybe_main_package_path) |path| blk: { const file = std.fs.cwd().openFile(path, .{}) catch return error.main_source_file_not_found; file.close(); @@ -211,13 +196,14 @@ fn parseArguments(compilation: *const Compilation) !Compilation.Module.Descripto break :blk build_file; }; + const executable_name = if (is_build) b: { + assert(maybe_executable_name == null); + break :b "build"; + } else b: { + break :b if (maybe_executable_name) |name| name else std.fs.path.basename(main_package_path[0 .. main_package_path.len - "/main.nat".len]); + }; + const executable_path = maybe_executable_path orelse blk: { - const executable_name = if (is_build) b: { - assert(maybe_executable_name == null); - break :b "build"; - } else b: { - break :b if (maybe_executable_name) |name| name else std.fs.path.basename(main_package_path[0 .. main_package_path.len - "/main.nat".len]); - }; assert(executable_name.len > 0); const result = try std.mem.concat(allocator, u8, &.{ "nat/", executable_name }); break :blk result; @@ -231,618 +217,137 @@ fn parseArguments(compilation: *const Compilation) !Compilation.Module.Descripto .is_build = is_build, .only_parse = only_parse, .link_libc = link_libc, + .generate_debug_information = generate_debug_information, + .name = executable_name, }; } pub fn init(allocator: Allocator) !void { - const compilation: *Compilation = try allocator.create(Compilation); + const context: *Context = try allocator.create(Context); const self_exe_path = try std.fs.selfExePathAlloc(allocator); const self_exe_dir_path = std.fs.path.dirname(self_exe_path).?; - compilation.* = .{ - .base_allocator = allocator, + context.* = .{ + .allocator = allocator, .cwd_absolute_path = try realpathAlloc(allocator, "."), .executable_absolute_path = self_exe_path, .directory_absolute_path = self_exe_dir_path, .build_directory = try std.fs.cwd().makeOpenPath("nat", .{}), }; - try compilation.build_directory.makePath(cache_dir_name); - try compilation.build_directory.makePath(installation_dir_name); + try context.build_directory.makePath(cache_dir_name); + try context.build_directory.makePath(installation_dir_name); - const compilation_descriptor = try parseArguments(compilation); - - try compilation.compileModule(compilation_descriptor); + const unit = try context.allocator.create(Unit); + unit.* = .{ + .descriptor = try parseArguments(context), + }; + try unit.compile(context); } -pub const Struct = struct { - scope: Scope.Index, - fields: ArrayList(ContainerField.Index) = .{}, - backing_type: Type.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const ContainerField = struct { - name: u32, - type: Type.Index, - default_value: Value.Index, - parent: Type.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const ContainerInitialization = struct { - field_initializations: ArrayList(Value.Index), - type: Type.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Type = union(enum) { - any, - void, - noreturn, - bool, - type, - comptime_int, - integer: Type.Integer, - slice: Type.Slice, - pointer: Pointer, - @"struct": Struct.Index, - @"enum": Enum.Index, - function: Function.Prototype.Index, - array: Array, - optional: Optional, - - pub const Optional = struct { - element_type: Type.Index, - }; - - pub const Termination = enum { - none, - null, - zero, - }; - - pub const Array = struct { - element_type: Type.Index, - element_count: usize, - termination: Termination, - }; - - pub const Slice = struct { - element_type: Type.Index, - @"const": bool, - termination: Termination, - }; - - pub const Pointer = struct { - element_type: Type.Index, - many: bool, - @"const": bool, - termination: Termination, - }; - - pub const List = BlockList(@This()); - pub const Index = List.Index; - - pub const Enum = struct { - scope: Scope.Index, - fields: ArrayList(Enum.Field.Index) = .{}, - backing_type: Type.Index, - - pub const Field = struct { - name: u32, - value: Value.Index, - parent: Type.Index, - - pub const List = BlockList(@This()); - pub const Index = Enum.Field.List.Index; - }; - - pub const List = BlockList(@This()); - pub const Index = Enum.List.Index; - }; - - pub const Integer = struct { - bit_count: u16, - signedness: Signedness, - pub const Signedness = enum(u1) { - unsigned = 0, - signed = 1, - }; - - pub fn getSize(integer: Integer) u64 { - return integer.bit_count / @bitSizeOf(u8) + @intFromBool(integer.bit_count % @bitSizeOf(u8) != 0); - } - - pub fn getIndex(integer: Integer) Compilation.Type.Index { - return .{ - .block = 0, - .element = @ctz(integer.bit_count) - @ctz(@as(u8, 8)) + @as(u6, switch (integer.signedness) { - .signed => Compilation.HardwareSignedIntegerType.offset, - .unsigned => Compilation.HardwareUnsignedIntegerType.offset, - }), - }; - } - - // pub fn get(bit_count: u16, comptime signedness: Signedness) @This().Type(signedness) { - // _ = bit_count; - // } - - fn Type(comptime signedness: Signedness) type { - return switch (signedness) { - .unsigned => HardwareUnsignedIntegerType, - .signed => HardwareSignedIntegerType, - }; - } - }; - - pub fn getSize(type_info: Type) u64 { - return switch (type_info) { - .integer => |integer| integer.getSize(), - .pointer => 8, - .comptime_int => @panic("This call should never happen"), - else => |t| @panic(@tagName(t)), - }; - } - - pub fn getBitSize(type_info: Type) u64 { - return switch (type_info) { - .integer => |integer| integer.bit_count, - .pointer => 8, - .bool => 1, - .comptime_int => @panic("This call should never happen"), - else => |t| @panic(@tagName(t)), - }; - } - - pub fn getAlignment(type_info: Type) u64 { - return switch (type_info) { - .integer => |integer| @min(16, integer.getSize()), - .pointer => 8, - else => |t| @panic(@tagName(t)), - }; - } - - pub const any = FixedTypeKeyword.any.toType(); - pub const @"void" = FixedTypeKeyword.void.toType(); - pub const boolean = FixedTypeKeyword.bool.toType(); - pub const ssize = FixedTypeKeyword.ssize.toType(); - pub const @"usize" = FixedTypeKeyword.usize.toType(); - pub const @"noreturn" = FixedTypeKeyword.noreturn.toType(); - pub const @"type" = FixedTypeKeyword.type.toType(); - pub const @"comptime_int" = FixedTypeKeyword.comptime_int.toType(); - pub const string_literal = ExtraCommonType.string_literal.toType(); - pub const @"u8" = Type.Integer.getIndex(.{ - .bit_count = 8, - .signedness = .unsigned, - }); - pub const @"u16" = Type.Integer.getIndex(.{ - .bit_count = 16, - .signedness = .unsigned, - }); - pub const @"u32" = Type.Integer.getIndex(.{ - .bit_count = 32, - .signedness = .unsigned, - }); - pub const @"u64" = Type.Integer.getIndex(.{ - .bit_count = 64, - .signedness = .unsigned, - }); -}; - -pub const Intrinsic = struct { +pub const Intrinsic = struct{ kind: Kind, type: Type.Index, - pub const Kind = union(enum) { + pub const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; + + // Each time an enum is added here, a corresponding insertion in the initialization must be made + pub const Kind = union(Id) { + @"asm", //this is processed separately as it need special parsing cast: Value.Index, - array_coerce_to_slice: Value.Index, - min: Binary, - optional_wrap: Value.Index, - optional_unwrap: Value.Index, - optional_check: Value.Index, + enum_to_int: Value.Index, + @"error", + int_to_pointer: Value.Index, + import, + min, + size, sign_extend: Value.Index, - syscall: ArrayList(Value.Index), + syscall: []const Value.Index, zero_extend: Value.Index, - - pub const Binary = struct { - left: Value.Index, - right: Value.Index, - }; }; - pub const List = BlockList(@This()); - pub const Index = @This().List.Index; -}; - -// Each time an enum is added here, a corresponding insertion in the initialization must be made -pub const ValidIntrinsic = enum { - //@"asm", this is processed separately as it need special parsing - cast, - @"error", - import, - min, - size, - syscall, -}; - -pub const FixedTypeKeyword = enum { - void, - noreturn, - bool, - usize, - ssize, - type, - comptime_int, - any, - - const offset = 0; - - fn toType(fixed_type_keyword: FixedTypeKeyword) Type.Index { - return Type.Index.fromInteger(offset + @intFromEnum(fixed_type_keyword)); - } -}; - -pub const HardwareUnsignedIntegerType = enum { - u8, - u16, - u32, - u64, - - pub const offset = @typeInfo(FixedTypeKeyword).Enum.fields.len; -}; - -pub const HardwareSignedIntegerType = enum { - s8, - s16, - s32, - s64, - - pub const offset = HardwareUnsignedIntegerType.offset + @typeInfo(HardwareUnsignedIntegerType).Enum.fields.len; -}; - -pub const ExtraCommonType = enum { - string_literal, - pub const offset = HardwareSignedIntegerType.offset + @typeInfo(HardwareSignedIntegerType).Enum.fields.len; - - fn toType(t: ExtraCommonType) Type.Index { - return Type.Index.fromInteger(offset + @intFromEnum(t)); - } -}; - -pub const extra_common_type_data = blk: { - var result: [@typeInfo(ExtraCommonType).Enum.fields.len]Type = undefined; - result[@intFromEnum(ExtraCommonType.string_literal)] = .{ - .pointer = .{ - .many = true, - .@"const" = true, - .element_type = Type.u8, - .termination = .null, - }, + pub const Id = enum{ + @"asm", //this is processed separately as it need special parsing + cast, + enum_to_int, + @"error", + int_to_pointer, + import, + min, + size, + sign_extend, + syscall, + zero_extend, }; - - break :blk result; }; -/// A scope contains a bunch of declarations -pub const Scope = struct { - declarations: data_structures.AutoArrayHashMap(u32, Declaration.Index) = .{}, - parent: Scope.Index, - file: File.Index, - token: Token.Index, - type: Type.Index = Type.Index.invalid, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const ScopeType = enum(u1) { - local = 0, - global = 1, -}; - -pub const Mutability = enum(u1) { - @"const", - @"var", -}; - -pub const Declaration = struct { - scope_type: ScopeType, - mutability: Mutability, - init_value: Value.Index, - name: u32, - argument_index: ?u32, - // A union is needed here because of global lazy declarations - type: Declaration.Type, - scope: Scope.Index, - - pub const Reference = struct { - value: Declaration.Index, - - pub fn getType(reference: Reference, module: *Module) Compilation.Type.Index { - return module.values.declarations.get(reference.value).getType(); - } - }; - - pub const Type = union(enum) { - resolved: Compilation.Type.Index, - inferred: Compilation.Type.Index, - unresolved: Node.Index, - }; - - pub fn getType(declaration: *Declaration) Compilation.Type.Index { - const type_index: Compilation.Type.Index = switch (declaration.type) { - .resolved => |resolved| resolved, - .inferred => |inferred| inferred, - .unresolved => unreachable, - }; - - assert(!type_index.invalid); - - return type_index; - } - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Function = struct { - body: Block.Index, - prototype: Type.Index, - - pub const Prototype = struct { - arguments: ArrayList(Declaration.Index), - return_type: Type.Index, - attributes: Attributes = .{}, - scope: Scope.Index, - - pub const List = BlockList(@This()); - pub const Index = Prototype.List.Index; - - pub const Attributes = struct { - @"extern": bool = false, - @"export": bool = false, - @"inline": Inline = .none, - calling_convention: CallingConvention = .system_v, - - pub const Inline = enum { - none, - suggestion_optimizer, - force_semantic, - force_optimizer, - }; - }; - }; - - pub fn getBodyBlock(function: Function, module: *Module) *Block { - return module.blocks.get(function.body); - } - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Block = struct { - statements: ArrayList(Value.Index) = .{}, - reaches_end: bool, - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Loop = struct { - pre: Value.Index, - condition: Value.Index, - body: Value.Index, - post: Value.Index, - reaches_end: bool, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -const Unresolved = struct { - node_index: Node.Index, -}; - -pub const Assignment = struct { - destination: Value.Index, - source: Value.Index, - operation: Operation, - - const Operation = enum { - none, - add, - }; - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Syscall = struct { - number: Value.Index, - arguments: [6]Value.Index, - argument_count: u8, - - pub fn getArguments(syscall: *const Syscall) []const Value.Index { - return syscall.arguments[0..syscall.argument_count]; - } - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Call = struct { - value: Value.Index, - arguments: ArgumentList.Index, - type: Type.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const ArgumentList = struct { - array: ArrayList(Value.Index), - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Return = struct { - value: Value.Index, - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Cast = struct { - value: Value.Index, - type: Type.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const BinaryOperation = struct { +pub const BinaryOperation = struct{ left: Value.Index, right: Value.Index, type: Type.Index, id: Id, - pub const List = BlockList(@This()); - pub const Index = List.Index; - pub const Id = enum { add, sub, + mul, + div, + mod, bit_and, bit_xor, bit_or, - multiply, - divide, shift_left, shift_right, compare_equal, - compare_greater_than, - compare_greater_or_equal, - compare_less_than, - compare_less_or_equal, compare_not_equal, + compare_greater, + compare_greater_equal, + compare_less, + compare_less_equal, }; + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; }; -pub const UnaryOperation = struct { +pub const Assignment = struct { + destination: Value.Index, + source: Value.Index, + operation: ?BinaryOperation.Id, + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; +}; + +pub const Call = struct { value: Value.Index, - type: Type.Index, - id: Id, - - pub const List = BlockList(@This()); - pub const Index = List.Index; - - pub const Id = enum { - boolean_not, - negation, - address_of, - pointer_dereference, - }; -}; - -pub const CallingConvention = enum { - system_v, - naked, -}; - -pub const Branch = struct { - expression: Value.Index, - taken_expression: Value.Index, - not_taken_expression: Value.Index, - reaches_end: bool, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Switch = struct { - value: Value.Index, - groups: ArrayList(Group), - - pub const Group = struct { - conditions: ArrayList(Value.Index), - expression: Value.Index, - }; - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const FieldAccess = struct { - declaration_reference: Value.Index, - field: ContainerField.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Range = struct { - start: Value.Index, - end: Value.Index, -}; - -pub const Slice = struct { - sliceable: Value.Index, - range: Range, + arguments: []const Value.Index, type: Type.Index, - pub const Access = struct { - value: Value.Index, - field: Field, - type: Type.Index, - - pub const List = BlockList(@This()); - pub const Index = Slice.Access.List.Index; - }; - - pub const Field = enum { - ptr, - len, - }; - - pub const List = BlockList(@This()); - pub const Index = List.Index; + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; }; -pub const IndexedAccess = struct { - indexed_expression: Value.Index, - index_expression: Value.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const OptionalCheck = struct { +pub const Return = struct { value: Value.Index, - pub const List = BlockList(@This()); - pub const Index = List.Index; + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; }; -pub const OptionalUnwrap = struct { - value: Value.Index, +pub const InlineAssembly = struct { + instructions: []const InlineAssembly.Instruction.Index, - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; -pub const Assembly = struct { pub const Instruction = struct { id: u32, operands: []const Operand, - pub const List = BlockList(@This()); - pub const Index = List.Index; + pub const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; }; pub const Operand = union(enum) { @@ -851,13 +356,6 @@ pub const Assembly = struct { value_index: Value.Index, }; - pub const Block = struct { - instructions: []const Assembly.Instruction.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; - }; - pub const x86_64 = struct { pub const Instruction = enum { @"and", @@ -874,586 +372,12 @@ pub const Assembly = struct { }; }; -pub const StringLiteral = struct { - hash: u32, - type: Type.Index, -}; - -pub const Value = union(enum) { - void, - @"break", - undefined, - @"unreachable", - bool: bool, - pointer_null_literal, - optional_null_literal, - unresolved: Unresolved, - intrinsic: Intrinsic.Index, - declaration: Declaration.Index, - declaration_reference: Declaration.Reference, - loop: Loop.Index, - function_definition: Function.Index, - function_declaration: Function.Index, - block: Block.Index, - assign: Assignment.Index, - type: Type.Index, - integer: Integer, - call: Call.Index, - argument_list: ArgumentList, - @"return": Return.Index, - argument: Declaration.Index, - string_literal: StringLiteral, - enum_field: Type.Enum.Field.Index, - extern_function: Function.Prototype.Index, - binary_operation: BinaryOperation.Index, - unary_operation: UnaryOperation.Index, - branch: Branch.Index, - container_initialization: ContainerInitialization.Index, - array_initialization: ContainerInitialization.Index, - field_access: FieldAccess.Index, - slice_access: Slice.Access.Index, - indexed_access: IndexedAccess.Index, - optional_check: OptionalCheck.Index, - optional_unwrap: OptionalUnwrap.Index, - array_coerce_to_slice: Cast.Index, - slice: Slice.Index, - assembly_block: Assembly.Block.Index, - switch_expression: Switch.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; - - pub const Integer = struct { - value: u64, - type: Type.Index, - signedness: Type.Integer.Signedness, - - pub fn getBitCount(integer: Integer, module: *Module) u16 { - return module.types.get(integer.type).integer.bit_count; - } - }; - - pub fn isComptime(value: *Value, module: *Module) bool { - _ = module; - - return switch (value.*) { - .bool, - .void, - .function_definition, - .function_declaration, - .type, - .enum_field, - .string_literal, - => true, - .integer => |integer| integer.type.eq(Type.comptime_int), - .declaration_reference => false, - // TODO: - .call, - // .syscall, - .binary_operation, - .container_initialization, - // .cast, - .optional_unwrap, - .pointer_null_literal, - .indexed_access, - .slice, - .array_initialization, - .undefined, - .intrinsic, - .field_access, - => false, - // TODO: - else => |t| @panic(@tagName(t)), - }; - } - - pub fn getType(value: Value, module: *Module) Type.Index { - const result = switch (value) { - .call => |call_index| module.values.calls.get(call_index).type, - .integer => |integer| integer.type, - .declaration_reference => |declaration_reference| declaration_reference.getType(module), - .string_literal => |string_literal| string_literal.type, - .type => Type.type, - .enum_field => |enum_field_index| module.types.enum_fields.get(enum_field_index).parent, - .function_definition => |function_index| module.types.function_definitions.get(function_index).prototype, - .function_declaration => |function_index| module.types.function_declarations.get(function_index).prototype, - .binary_operation => |binary_operation| module.values.binary_operations.get(binary_operation).type, - .bool => Type.boolean, - // .declaration => Type.void, - .container_initialization, - .array_initialization, - => |initialization| module.values.container_initializations.get(initialization).type, - .intrinsic => |intrinsic_index| module.values.intrinsics.get(intrinsic_index).type, - .unary_operation => |unary_operation_index| module.values.unary_operations.get(unary_operation_index).type, - .pointer_null_literal => semantic_analyzer.optional_pointer_to_any_type, - .optional_null_literal => semantic_analyzer.optional_any, - .field_access => |field_access_index| module.types.container_fields.get(module.values.field_accesses.get(field_access_index).field).type, - // .cast, - // .optional_cast, - // .array_coerce_to_slice => |cast_index| module.values.casts.get(cast_index).type, - .slice => |slice_index| module.values.slices.get(slice_index).type, - .slice_access => |slice_access_index| module.values.slice_accesses.get(slice_access_index).type, - .optional_check => Type.boolean, - .indexed_access => |indexed_access_index| blk: { - const indexed_expression = module.values.array.get(module.values.indexed_accesses.get(indexed_access_index).indexed_expression); - const indexed_expression_type_index = indexed_expression.getType(module); - const indexed_expression_type = module.types.array.get(indexed_expression_type_index); - - break :blk switch (indexed_expression_type.*) { - .slice => |slice| slice.element_type, - .array => |array| array.element_type, - .pointer => |pointer| switch (pointer.many) { - true => pointer.element_type, - false => unreachable, - }, - else => |t| @panic(@tagName(t)), - }; - }, - .undefined => Type.any, - .optional_unwrap => |optional_unwrap_index| blk: { - const optional_unwrap = module.values.optional_unwraps.get(optional_unwrap_index); - const optional_value = module.values.array.get(optional_unwrap.value); - const expected_optional_type_index = optional_value.getType(module); - const expected_optional_type = module.types.array.get(expected_optional_type_index); - - break :blk switch (expected_optional_type.*) { - .optional => |optional| optional.element_type, - else => |t| @panic(@tagName(t)), - }; - }, - .branch => |branch_index| { - // TODO - const branch = module.values.branches.get(branch_index); - const taken = module.values.array.get(branch.taken_expression); - const not_taken = module.values.array.get(branch.not_taken_expression); - const taken_type = taken.getType(module); - const not_taken_type = not_taken.getType(module); - _ = not_taken_type; - return taken_type; - }, - else => |t| @panic(@tagName(t)), - }; - - return result; - } - - const TypeCheckError = error{ - integer_size, - pointer_many_differ, - pointer_element_type_differ, - }; -}; - -pub const Module = struct { - values: struct { - field_accesses: BlockList(FieldAccess) = .{}, - array: BlockList(Value) = .{}, - declarations: BlockList(Declaration) = .{}, - scopes: BlockList(Scope) = .{}, - files: BlockList(File) = .{}, - blocks: BlockList(Block) = .{}, - loops: BlockList(Loop) = .{}, - assignments: BlockList(Assignment) = .{}, - intrinsics: BlockList(Intrinsic) = .{}, - // syscalls: BlockList(Syscall) = .{}, - calls: BlockList(Call) = .{}, - argument_lists: BlockList(ArgumentList) = .{}, - returns: BlockList(Return) = .{}, - container_initializations: BlockList(ContainerInitialization) = .{}, - // casts: BlockList(Cast) = .{}, - branches: BlockList(Branch) = .{}, - binary_operations: BlockList(BinaryOperation) = .{}, - unary_operations: BlockList(UnaryOperation) = .{}, - slices: BlockList(Slice) = .{}, - slice_accesses: BlockList(Slice.Access) = .{}, - indexed_accesses: BlockList(IndexedAccess) = .{}, - optional_checks: BlockList(OptionalCheck) = .{}, - optional_unwraps: BlockList(OptionalUnwrap) = .{}, - assembly_blocks: BlockList(Assembly.Block) = .{}, - assembly_instructions: BlockList(Assembly.Instruction) = .{}, - switches: BlockList(Switch) = .{}, - } = .{}, - types: struct { - array: BlockList(Type) = .{}, - enums: BlockList(Type.Enum) = .{}, - structs: BlockList(Struct) = .{}, - container_fields: BlockList(ContainerField) = .{}, - enum_fields: BlockList(Type.Enum.Field) = .{}, - function_definitions: BlockList(Function) = .{}, - function_declarations: BlockList(Function) = .{}, - function_prototypes: BlockList(Function.Prototype) = .{}, - } = .{}, - map: struct { - function_definitions: data_structures.AutoArrayHashMap(Function.Index, Declaration.Index) = .{}, - function_declarations: data_structures.AutoArrayHashMap(Function.Index, Declaration.Index) = .{}, - strings: StringKeyMap([]const u8) = .{}, - imports: StringArrayHashMap(File.Index) = .{}, - types: data_structures.AutoArrayHashMap(Type.Index, Declaration.Index) = .{}, - non_primitive_integer: data_structures.AutoArrayHashMap(Type.Integer, Type.Index) = .{}, - slices: data_structures.AutoArrayHashMap(Type.Slice, Type.Index) = .{}, - pointers: data_structures.AutoArrayHashMap(Type.Pointer, Type.Index) = .{}, - optionals: data_structures.AutoArrayHashMap(Type.Index, Type.Index) = .{}, - arrays: data_structures.AutoArrayHashMap(Type.Array, Type.Index) = .{}, - libraries: data_structures.StringArrayHashMap(void) = .{}, - } = .{}, - main_package: *Package, - entry_point: Function.Index = Function.Index.invalid, - descriptor: Descriptor, - - pub const Descriptor = struct { - main_package_path: []const u8, - executable_path: []const u8, - target: std.Target, - transpile_to_c: bool, - is_build: bool, - only_parse: bool, - link_libc: bool, - }; - - const ImportFileResult = struct { - index: File.Index, - is_new: bool, - }; - - const ImportPackageResult = struct { - file: ImportFileResult, - is_package: bool, - }; - - pub fn importFile(module: *Module, allocator: Allocator, current_file_index: File.Index, import_name: []const u8) !ImportPackageResult { - logln(.compilation, .import, "import: '{s}'\n", .{import_name}); - if (equal(u8, import_name, "std")) { - return module.importPackage(allocator, module.main_package.dependencies.get("std").?); - } - - if (equal(u8, import_name, "builtin")) { - return module.importPackage(allocator, module.main_package.dependencies.get("builtin").?); - } - - if (equal(u8, import_name, "main")) { - return module.importPackage(allocator, module.main_package); - } - - const current_file = module.values.files.get(current_file_index); - if (current_file.package.dependencies.get(import_name)) |package| { - return module.importPackage(allocator, package); - } - - if (!std.mem.endsWith(u8, import_name, ".nat")) { - unreachable; - } - - const current_file_relative_path_to_package_directory = std.fs.path.dirname(current_file.relative_path) orelse ""; - const import_file_relative_path = try std.fs.path.join(allocator, &.{ current_file_relative_path_to_package_directory, import_name }); - const full_path = try std.fs.path.join(allocator, &.{ current_file.package.directory.path, import_file_relative_path }); - const file_relative_path = import_file_relative_path; - const package = current_file.package; - const import_file = try module.getFile(allocator, full_path, file_relative_path, package); - - try module.values.files.get(import_file.index).file_references.append(allocator, current_file); - - const result = ImportPackageResult{ - .file = import_file, - .is_package = false, - }; - - return result; - } - - fn getFile(module: *Module, allocator: Allocator, full_path: []const u8, relative_path: []const u8, package: *Package) !ImportFileResult { - const path_lookup = try module.map.imports.getOrPut(allocator, full_path); - const index = switch (path_lookup.found_existing) { - true => path_lookup.value_ptr.*, - false => blk: { - const file_index = try module.values.files.append(allocator, File{ - .relative_path = relative_path, - .package = package, - }); - logln(.compilation, .new_file, "Adding file #{}: {s}\n", .{ file_index.uniqueInteger(), full_path }); - path_lookup.value_ptr.* = file_index; - // break :blk file; - break :blk file_index; - }, - }; - - return .{ - .index = index, - .is_new = !path_lookup.found_existing, - }; - } - - pub fn importPackage(module: *Module, allocator: Allocator, package: *Package) !ImportPackageResult { - const full_path = try std.fs.path.resolve(allocator, &.{ package.directory.path, package.source_path }); - logln(.compilation, .import, "Import full path: {s}\n", .{full_path}); - const import_file = try module.getFile(allocator, full_path, package.source_path, package); - const file = module.values.files.get(import_file.index); - try file.addPackageReference(allocator, package); - - return .{ - .file = import_file, - .is_package = true, - }; - } - - pub fn generateAbstractSyntaxTreeForFile(module: *Module, allocator: Allocator, file_index: File.Index) !void { - const file = module.values.files.get(file_index); - const source_file = file.package.directory.handle.openFile(file.relative_path, .{}) catch |err| { - std.debug.panic("Can't find file {s} in directory {s} for error {s}", .{ file.relative_path, file.package.directory.path, @errorName(err) }); - }; - - const file_size = try source_file.getEndPos(); - var file_buffer = try allocator.alloc(u8, file_size); - - const read_byte_count = try source_file.readAll(file_buffer); - assert(read_byte_count == file_size); - source_file.close(); - - //TODO: adjust file maximum size - file.source_code = file_buffer[0..read_byte_count]; - file.status = .loaded_into_memory; - - try file.lex(allocator, file_index); - try file.parse(allocator, file_index); - } - - fn getString(map: *StringKeyMap([]const u8), key: u32) ?[]const u8 { - return map.getValue(key); - } - - pub fn addString(map: *StringKeyMap([]const u8), allocator: Allocator, string: []const u8) !u32 { - const lookup_result = try map.getOrPut(allocator, string, string); - return lookup_result.key; - } - - pub fn getName(module: *Module, key: u32) ?[]const u8 { - return getString(&module.map.strings, key); - } - - pub fn addName(module: *Module, allocator: Allocator, name: []const u8) !u32 { - return addString(&module.map.strings, allocator, name); - } -}; - -fn pathFromCwd(compilation: *const Compilation, relative_path: []const u8) ![]const u8 { - return std.fs.path.join(compilation.base_allocator, &.{ compilation.cwd_absolute_path, relative_path }); -} - -fn pathFromCompiler(compilation: *const Compilation, relative_path: []const u8) ![]const u8 { - return std.fs.path.join(compilation.base_allocator, &.{ compilation.directory_absolute_path, relative_path }); -} - fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]const u8 { var path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; const realpathInStack = try std.os.realpath(pathname, &path_buffer); return allocator.dupe(u8, realpathInStack); } -pub fn compileModule(compilation: *Compilation, descriptor: Module.Descriptor) !void { - const module: *Module = try compilation.base_allocator.create(Module); - module.* = Module{ - .main_package = blk: { - const result = try compilation.base_allocator.create(Package); - const main_package_absolute_directory_path = b: { - const relative_path = if (std.fs.path.dirname(descriptor.main_package_path)) |dirname| dirname else "."; - break :b try compilation.pathFromCwd(relative_path); - }; - result.* = .{ - .directory = .{ - .handle = try std.fs.openDirAbsolute(main_package_absolute_directory_path, .{}), - .path = main_package_absolute_directory_path, - }, - .source_path = try compilation.base_allocator.dupe(u8, std.fs.path.basename(descriptor.main_package_path)), - }; - break :blk result; - }, - .descriptor = descriptor, - }; - - const builtin_file_name = "builtin.nat"; - var cache_dir = try compilation.build_directory.openDir("cache", .{}); - const builtin_file = try cache_dir.createFile(builtin_file_name, .{}); - try builtin_file.writer().print( - \\const builtin = #import("std").builtin; - \\const cpu = builtin.Cpu.{s}; - \\const os = builtin.Os.{s}; - \\const abi = builtin.Abi.{s}; - \\const link_libc = {}; - \\ - , .{ - @tagName(module.descriptor.target.cpu.arch), - @tagName(module.descriptor.target.os.tag), - @tagName(module.descriptor.target.abi), - module.descriptor.link_libc, - }); - builtin_file.close(); - - const std_package_dir = "lib/std"; - - const package_descriptors = [2]struct { - name: []const u8, - directory_path: []const u8, - }{ - .{ - .name = "std", - .directory_path = try compilation.pathFromCompiler(std_package_dir), - }, - .{ - .name = "builtin", - .directory_path = blk: { - const result = try cache_dir.realpathAlloc(compilation.base_allocator, "."); - cache_dir.close(); - break :blk result; - }, - }, - }; - - var packages: [package_descriptors.len]*Package = undefined; - for (package_descriptors, &packages) |package_descriptor, *package_ptr| { - const package = try compilation.base_allocator.create(Package); - package.* = .{ - .directory = .{ - .path = package_descriptor.directory_path, - .handle = try std.fs.openDirAbsolute(package_descriptor.directory_path, .{}), - }, - .source_path = try std.mem.concat(compilation.base_allocator, u8, &.{ package_descriptor.name, ".nat" }), - }; - - try module.main_package.addDependency(compilation.base_allocator, package_descriptor.name, package); - - package_ptr.* = package; - } - - assert(module.main_package.dependencies.size == 2); - - if (!module.descriptor.only_parse) { - _ = try module.importPackage(compilation.base_allocator, module.main_package.dependencies.get("std").?); - } else { - _ = try module.importPackage(compilation.base_allocator, module.main_package); - } - - for (module.map.imports.values()) |import| { - try module.generateAbstractSyntaxTreeForFile(compilation.base_allocator, import); - } - - if (!module.descriptor.only_parse) { - inline for (@typeInfo(FixedTypeKeyword).Enum.fields) |enum_field| { - _ = try module.types.array.append(compilation.base_allocator, switch (@field(FixedTypeKeyword, enum_field.name)) { - .usize => @unionInit(Type, "integer", .{ - .bit_count = 64, - .signedness = .unsigned, - }), - .ssize => @unionInit(Type, "integer", .{ - .bit_count = 64, - .signedness = .signed, - }), - else => @unionInit(Type, enum_field.name, {}), - }); - } - - inline for (@typeInfo(HardwareUnsignedIntegerType).Enum.fields) |enum_field| { - _ = try module.types.array.append(compilation.base_allocator, .{ - .integer = .{ - .signedness = .unsigned, - .bit_count = switch (@field(HardwareUnsignedIntegerType, enum_field.name)) { - .u8 => 8, - .u16 => 16, - .u32 => 32, - .u64 => 64, - }, - }, - }); - } - - inline for (@typeInfo(HardwareSignedIntegerType).Enum.fields) |enum_field| { - _ = try module.types.array.append(compilation.base_allocator, .{ - .integer = .{ - .signedness = .signed, - .bit_count = switch (@field(HardwareSignedIntegerType, enum_field.name)) { - .s8 => 8, - .s16 => 16, - .s32 => 32, - .s64 => 64, - }, - }, - }); - } - - for (extra_common_type_data) |type_data| { - _ = try module.types.array.append(compilation.base_allocator, type_data); - } - semantic_analyzer.pointer_to_any_type = try module.types.array.append(compilation.base_allocator, .{ - .pointer = .{ - .element_type = Type.any, - .many = false, - .@"const" = true, - .termination = .none, - }, - }); - semantic_analyzer.optional_pointer_to_any_type = try module.types.array.append(compilation.base_allocator, .{ - .optional = .{ - .element_type = semantic_analyzer.pointer_to_any_type, - }, - }); - semantic_analyzer.optional_any = try module.types.array.append(compilation.base_allocator, .{ - .optional = .{ - .element_type = Type.any, - }, - }); - - semantic_analyzer.unreachable_index = try module.values.array.append(compilation.base_allocator, .@"unreachable"); - semantic_analyzer.pointer_null_index = try module.values.array.append(compilation.base_allocator, .pointer_null_literal); - semantic_analyzer.optional_null_index = try module.values.array.append(compilation.base_allocator, .optional_null_literal); - semantic_analyzer.undefined_index = try module.values.array.append(compilation.base_allocator, .undefined); - semantic_analyzer.boolean_false = try module.values.array.append(compilation.base_allocator, .{ - .bool = false, - }); - semantic_analyzer.boolean_true = try module.values.array.append(compilation.base_allocator, .{ - .bool = true, - }); - - const value_index = try module.values.array.append(compilation.base_allocator, .{ - .unresolved = .{ - .node_index = .{ .value = 0 }, - }, - }); - - try semantic_analyzer.initialize(compilation, module, packages[0], value_index); - - if (module.descriptor.transpile_to_c) { - try c_transpiler.initialize(compilation, module); - if (module.descriptor.is_build) { - const argv = [_][]const u8{ module.descriptor.executable_path, "--compiler", compilation.executable_absolute_path }; - const process_result = try std.ChildProcess.run(.{ - .allocator = compilation.base_allocator, - .argv = &argv, - }); - - switch (process_result.term) { - .Exited => |exit_code| { - if (exit_code != 0) { - for (argv) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("exited with failure: {}\n", .{exit_code}); - std.debug.print("STDOUT:\n```\n{s}\n```\n", .{process_result.stdout}); - std.debug.print("STDERR:\n```\n{s}\n```\n", .{process_result.stderr}); - @panic("Internal error"); - } - }, - else => @panic("Unexpected program state"), - } - } - } else { - unreachable; - // const ir = try intermediate_representation.initialize(compilation, module); - // - // switch (descriptor.target.cpu.arch) { - // inline else => |arch| try emit.get(arch).initialize(compilation.base_allocator, ir, descriptor), - // } - } - } -} - pub const ContainerType = enum { @"struct", @"enum", @@ -1476,85 +400,31 @@ pub const Package = struct { } }; -pub const File = struct { - status: Status = .not_loaded, - source_code: []const u8 = &.{}, - lexical_analyzer_result: lexical_analyzer.Result = undefined, - syntactic_analyzer_result: syntactic_analyzer.Result = undefined, - package_references: ArrayList(*Package) = .{}, - file_references: ArrayList(*File) = .{}, - type: Type.Index = Type.Index.invalid, - relative_path: []const u8, - package: *Package, - - pub const List = BlockList(@This()); - pub const Index = List.Index; - - const Status = enum { - not_loaded, - loaded_into_memory, - lexed, - parsed, - }; - - fn addPackageReference(file: *File, allocator: Allocator, package: *Package) !void { - for (file.package_references.items) |other| { - if (other == package) return; - } - - try file.package_references.insert(allocator, 0, package); - } - - fn addFileReference(file: *File, allocator: Allocator, affected: *File) !void { - try file.file_references.append(allocator, affected); - } - - fn lex(file: *File, allocator: Allocator, file_index: File.Index) !void { - assert(file.status == .loaded_into_memory); - file.lexical_analyzer_result = try lexical_analyzer.analyze(allocator, file.source_code, file_index); - // if (!@import("builtin").is_test) { - // print("[LEXICAL ANALYSIS] {} ns\n", .{file.lexical_analyzer_result.time}); - // } - file.status = .lexed; - } - - fn parse(file: *File, allocator: Allocator, file_index: File.Index) !void { - assert(file.status == .lexed); - file.syntactic_analyzer_result = try syntactic_analyzer.analyze(allocator, file.lexical_analyzer_result.tokens.items, file.source_code, file_index); - - // if (file_index.uniqueInteger() == 5) { - // assert(file.syntactic_analyzer_result.nodes.items[1356].id != .node_list); - // } - // if (!@import("builtin").is_test) { - // print("[SYNTACTIC ANALYSIS] {} ns\n", .{file.syntactic_analyzer_result.time}); - // } - file.status = .parsed; - } -}; - const LoggerScope = enum { compilation, lexer, parser, - sema, - c, }; const Logger = enum { import, new_file, arguments, - var bitset = std.EnumSet(Logger).initEmpty(); + token_bytes, + identifier, + ir, + + var bitset = std.EnumSet(Logger).initMany(&.{ + .ir, + }); }; fn getLoggerScopeType(comptime logger_scope: LoggerScope) type { comptime { return switch (logger_scope) { .compilation => @This(), - .lexer => lexical_analyzer, - .parser => syntactic_analyzer, - .sema => semantic_analyzer, - .c => c_transpiler, + .lexer => lexer, + .parser => parser, }; } } @@ -1593,3 +463,3373 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, return_ }, } } + +const TypeCheckSwitchEnums = struct { + switch_case_groups: ArrayList(ArrayList(Enum.Field.Index)), + else_switch_case_group_index: ?usize = null, +}; + +const ImportFileResult = struct { + index: File.Index, + is_new: bool, +}; + +const ImportPackageResult = struct { + file: ImportFileResult, + is_package: bool, +}; + +pub const Value = union(enum) { + undefined, + @"unreachable", + unresolved: Node.Index, + copy: Value.Index, + type: Type.Index, + declaration: Declaration.Index, + bool: bool, + reference: Declaration.Index, + intrinsic: Intrinsic.Index, + unary_operation: UnaryOperation.Index, + binary_operation: BinaryOperation.Index, + assignment: Assignment.Index, + integer: Integer, + enum_field: Enum.Field.Index, + block: Block.Index, + function_definition: Function.Definition.Index, + @"return": Return.Index, + call: Call.Index, + inline_assembly: InlineAssembly.Index, + + pub const List = BlockList(@This(), Common); + pub usingnamespace List.Index; + + const Common = enum{ + undefined, + @"unreachable", + bool_false, + bool_true, + + const map = std.EnumArray(@This(), Value).init(.{ + .undefined = .undefined, + .@"unreachable" = .@"unreachable", + .bool_false = .{ + .bool = false, + }, + .bool_true = .{ + .bool = true, + }, + }); + }; + + pub const Integer = struct { + value: u64, + type: Type.Index, + }; + + pub fn isComptime(value: *const Value, unit: *Unit) bool { + return Compilation.isComptime(value, unit); + } + + pub fn getType(value: *const Value, unit: *Unit) Type.Index { + return Compilation.getType(value, unit); + } +}; + +fn isComptime(value: *const Value, unit: *Unit) bool { + const result: bool = switch (value.*) { + .type => true, + .bool => true, + .copy => true, + .enum_field => true, + .function_definition => true, + .intrinsic => |intrinsic_index| switch (unit.intrinsics.get(intrinsic_index).kind) { + .cast => |value_index| Compilation.isComptime(unit.values.get(value_index), unit), + .int_to_pointer => false, + else => |t| @panic(@tagName(t)), + }, + .reference => false, + .call => false, + else => |t| @panic(@tagName(t)), + }; + + return result; +} + +fn getType(value: *const Value, unit: *Unit) Type.Index{ + return switch (value.*) { + .type => Type.Index.type, + .bool => Type.Index.bool, + .copy => |value_index| b: { + const v = unit.values.get(value_index); + break :b getType(v, unit); + }, + .reference => |declaration_index| b: { + const declaration = unit.declarations.get(declaration_index); + assert(declaration.type != .null); + const declaration_type = unit.unwrapTypeCopy(declaration.type); + assert(unit.types.get(declaration_type).* != .unresolved); + assert(unit.types.get(declaration_type).* != .copy); + break :b declaration_type; + }, + .integer => |integer| integer.type, + .enum_field => |enum_field_index| unit.enum_fields.get(enum_field_index).parent, + .function_definition => |function_index| unit.function_definitions.get(function_index).type, + .call => |call_index| unit.calls.get(call_index).type, + else => |t| @panic(@tagName(t)), + }; +} + +pub const UnaryOperation = struct { + value: Value.Index, + type: Type.Index, + id: Id, + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; + + pub const Id = enum { + boolean_not, + negation, + address_of, + pointer_dereference, + }; +}; + +pub const Block = struct{ + scope: Scope, + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; +}; + +const Compilation = @This(); + +pub const Type = union(enum) { + void, + noreturn, + type, + comptime_int, + bool, + unresolved: Node.Index, + copy: Type.Index, + @"struct": Struct.Index, + @"enum": Enum.Index, + function: Function.Prototype.Index, + integer: Integer, + pointer: Pointer, + + fn getByteSize(ty: *Type, unit: *Unit) u32 { + _ = unit; // autofix + return switch (ty.*) { + .integer => |integer| integer.bit_count, + else => |t| @panic(@tagName(t)), + }; + } + + fn getScope(ty: *Type, unit: *Unit) *Scope { + return switch (ty.*) { + .@"struct" => |struct_index| &unit.structs.get(struct_index).scope, + .@"enum" => |enum_index| &unit.enums.get(enum_index).scope, + else => |t| @panic(@tagName(t)), + }; + } + + const Expect = union(enum){ + none, + type: Type.Index, + }; + + const Integer = struct { + bit_count: u16, + signedness: Signedness, + + pub const Signedness = enum(u1) { + unsigned = 0, + signed = 1, + }; + }; + + + const Pointer = struct{ + type: Type.Index, + termination: Termination, + mutability: Mutability, + many: bool, + nullable: bool, + }; + + pub const Termination = enum { + none, + null, + zero, + }; + + const Common = enum{ + void, + noreturn, + type, + comptime_int, + bool, + u1, + u8, + u16, + u32, + u64, + s8, + s16, + s32, + s64, + usize, + ssize, + + const map = std.EnumArray(@This(), Type).init(.{ + .void = .void, + .noreturn = .noreturn, + .type = .type, + .bool = .bool, + .comptime_int = .comptime_int, + .u1 = .{ + .integer = .{ + .bit_count = 1, + .signedness = .unsigned, + }, + }, + .u8 = .{ + .integer = .{ + .bit_count = 8, + .signedness = .unsigned, + }, + }, + .u16 = .{ + .integer = .{ + .bit_count = 16, + .signedness = .unsigned, + }, + }, + .u32 = .{ + .integer = .{ + .bit_count = 32, + .signedness = .unsigned, + }, + }, + .u64 = .{ + .integer = .{ + .bit_count = 64, + .signedness = .unsigned, + }, + }, + .s8 = .{ + .integer = .{ + .bit_count = 8, + .signedness = .signed, + }, + }, + .s16 = .{ + .integer = .{ + .bit_count = 16, + .signedness = .signed, + }, + }, + .s32 = .{ + .integer = .{ + .bit_count = 32, + .signedness = .signed, + }, + }, + .s64 = .{ + .integer = .{ + .bit_count = 64, + .signedness = .signed, + }, + }, + .ssize = .{ + .integer = .{ + .bit_count = 64, + .signedness = .signed, + }, + }, + .@"usize" = .{ + .integer = .{ + .bit_count = 64, + .signedness = .unsigned, + }, + }, + }); + }; + + pub const List = BlockList(@This(), Common); + pub usingnamespace List.Index; +}; + +pub const Instruction = union(enum) { + argument_declaration: ArgumentDeclaration, + // TODO + call: Instruction.Call, + cast: Cast, + // TODO: remove? + constant_int: Value.Integer, + debug_checkpoint: DebugCheckPoint, + global_variable: GlobalVariable.Index, + inline_assembly: InlineAssembly.Index, + integer_binary_operation: Instruction.IntegerBinaryOperation, + // TODO: delete + load: Load, + load_instruction: LoadInstruction, + load_global: LoadGlobal, + pop_scope: Instruction.Scope, + push_scope: Instruction.Scope, + ret: Instruction.Index, + ret_void, + stack_slot: Instruction.StackSlot, + store: Store, + syscall: Syscall, + @"unreachable", + + const Syscall = struct{ + arguments: []const Instruction.Index, + }; + + const Callable = union(enum) { + function_definition: Function.Definition.Index, + }; + + const Call = struct{ + callable: Callable, + function_type: Type.Index, + arguments: []const Instruction.Index, + }; + + const IntegerBinaryOperation = struct{ + left: Instruction.Index, + right: Instruction.Index, + id: Id, + signedness: Type.Integer.Signedness, + + const Id = enum{ + add, + mul, + }; + }; + + const Scope = struct { + old: *Compilation.Scope, + new: *Compilation.Scope, + }; + + const ArgumentDeclaration = struct{ + name: u32, + type: Type.Index, + }; + + const Cast = struct { + id: Cast.Id, + value: Value.Index, + type: Type.Index, + + const Id = enum{ + enum_to_int, + int_to_pointer, + sign_extend, + zero_extend, + }; + }; + + const DebugCheckPoint = struct{ + scope: *Compilation.Scope, + line: u32, + column: u32, + }; + + const Load = struct{ + value: Value.Index, + }; + + const LoadGlobal = struct{ + value: GlobalVariable.Index, + }; + + const LoadInstruction = struct{ + value: Instruction.Index, + }; + + const StackSlot = struct{ + type: Type.Index, + }; + + const Store = struct{ + // TODO: + destination: Instruction.Index, + source: Instruction.Index, + }; + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; +}; + +pub const BasicBlock = struct{ + instructions: ArrayList(Instruction.Index) = .{}, + predecessor: BasicBlock.Index = .null, + // TODO: not use a bool + terminated: bool = false, + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; +}; + +pub const Function = struct{ + pub const Attribute = enum{ + cc, + naked, + @"export", + @"extern", + }; + + pub const Definition = struct{ + scope: Scope, + symbol: GlobalSymbol, + basic_blocks: ArrayList(BasicBlock.Index) = .{}, + // TODO: make this more efficient + declarations: AutoArrayHashMap(Declaration.Index, Instruction.Index) = .{}, + argument_map: AutoArrayHashMap(Declaration.Index, Instruction.Index) = .{}, + type: Type.Index, + body: Value.Index, + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; + }; + + pub const Prototype = struct { + argument_types: []const Type.Index, + return_type: Type.Index, + attributes: Attributes, + + const Attributes = struct{ + @"export": bool, + naked: bool, + }; + + const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; + }; + +}; + +const Struct = struct{ + fields: ArrayList(Field) = .{}, + scope: Scope, + backing_type: Type.Index, + + const Field = struct{ + name: u32, + type: u32, + value: Value.Index, + }; + + const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; +}; + +pub const GlobalSymbol = struct{ + name: u32, + type: Type.Index, + kind: Kind, + + const Kind = enum{ + code, + data, + }; +}; + +pub const GlobalVariable = struct{ + symbol: GlobalSymbol, + mutability: Mutability, + value: Value.Index, + + const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; +}; + +pub const Context = struct { + allocator: Allocator, + cwd_absolute_path: []const u8, + directory_absolute_path: []const u8, + executable_absolute_path: []const u8, + build_directory: std.fs.Dir, + + fn pathFromCwd(context: *const Context, relative_path: []const u8) ![]const u8 { + return std.fs.path.join(context.allocator, &.{ context.cwd_absolute_path, relative_path }); + } + + fn pathFromCompiler(context: *const Context, relative_path: []const u8) ![]const u8 { + return std.fs.path.join(context.allocator, &.{ context.directory_absolute_path, relative_path }); + } +}; + +pub const File = struct { + relative_path: []const u8, + package: *Package, + source_code: []const u8 = &.{}, + status: Status = .not_loaded, + lexer: lexer.Result = undefined, + parser: parser.Result = undefined, + value: Value.Index = .null, + type: Type.Index = .null, + scope: Scope, + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; + + pub const Status = enum { + not_loaded, + loaded_into_memory, + lexed, + parsed, + }; +}; + +pub const Scope = struct{ + declarations: AutoArrayHashMap(u32, Declaration.Index) =.{}, + parent: ?*Scope = null, + file: File.Index, + line: u32, + column: u32, + kind: Kind, + local: bool, + level: u8, + + const Lookup = struct{ + scope: *Scope, + declaration: Declaration.Index, + }; + + fn lookupDeclaration(s: *Scope, name: u32, look_in_parent_scopes: bool) ?Lookup{ + var scope_it: ?*Scope = s; + while (scope_it) |scope| : (scope_it = scope.parent) { + if (scope.declarations.get(name)) |declaration| { + return Lookup{ + .declaration = declaration, + .scope = scope, + }; + } + + if (!look_in_parent_scopes) break; + } + + return null; + } + + fn getFile(scope: *Scope, unit: *Unit) File.Index { + var scope_it: ?*Scope = scope; + while (scope_it) |s| : (scope_it = s.parent) { + if (s.kind == .file) { + const file = @fieldParentPtr(File, "scope", s); + const file_index = unit.files.indexOf(file); + return file_index; + } + } else @panic("No parent file scope"); + + } + + const Kind = enum{ + compilation_unit, + file, + file_container, + container, + function,// Arguments + block, + }; +}; + +pub const Declaration = struct{ + scope: *Scope, + value: Value.Index, + type: Type.Index, + line: u32, + column: u32, + name: u32, + mutability: Mutability, + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; +}; + +pub const Mutability = enum(u1) { + @"const", + @"var", +}; + +pub const Builder = struct { + current_scope: *Scope, + current_file: File.Index = .null, + current_function: Function.Definition.Index = .null, + current_basic_block: BasicBlock.Index = .null, + last_check_point: struct{ + line: u32 = 0, + column: u32 = 0, + scope: ?*Scope = null, + } = .{}, + generate_debug_info: bool, + emit_ir: bool, + + + fn pushScope(builder: *Builder, unit: *Unit, context: *const Context, new_scope: *Scope) !void { + const old_scope = builder.current_scope; + + if (builder.current_basic_block != .null) { + const instruction = try unit.instructions.append(context.allocator, .{ + .push_scope = .{ + .old = old_scope, + .new = new_scope, + }, + }); + try builder.appendInstruction(unit, context, instruction); + } + + new_scope.parent = old_scope; + builder.current_scope = new_scope; + } + + fn popScope(builder: *Builder, unit: *Unit, context: *const Context) !void { + const old_scope = builder.current_scope; + const new_scope = old_scope.parent.?; + + if (builder.current_basic_block != .null) { + const instruction = try unit.instructions.append(context.allocator, .{ + .pop_scope = .{ + .old = old_scope, + .new = new_scope, + }, + }); + try builder.appendInstruction(unit, context, instruction); + } + + builder.current_scope = new_scope; + } + + fn analyzePackage(builder: *Builder, unit: *Unit, context: *const Context, package: *Package) !void { + const package_import = try unit.importPackage(context, package); + assert(!package_import.file.is_new); + const file_index = package_import.file.index; + + _ = try builder.analyzeFile(unit, context, file_index); + } + + fn analyzeFile(builder: *Builder, unit: *Unit, context: *const Context, file_index: File.Index) !void { + const old_scope = builder.current_scope; + builder.current_scope = &unit.scope; + defer builder.current_scope = old_scope; + + const file = unit.files.get(file_index); + assert(file.status == .parsed); + assert(file.value == .null); + + const previous_file = builder.current_file; + builder.current_file = file_index; + defer builder.current_file = previous_file; + + try builder.pushScope(unit, context, &file.scope); + defer builder.popScope(unit, context) catch unreachable; + + const main_node_index = file.parser.main_node_index; + const main_node = unit.node_buffer.get(main_node_index); + const main_node_list = unit.getNodeList(main_node.left); + + + // Dummy + file.type = try unit.types.append(context.allocator, .{ + .unresolved = main_node_index, + }); + file.value = try unit.values.append(context.allocator, .{ + .type = file.type, + }); + + try builder.resolveContainerType(unit, context, main_node_list, main_node_index, .@"struct", file.type); + } + + fn resolveValueAllocate(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, node_index: Node.Index) !Value.Index { + const value_index = try unit.values.append(context.allocator, .{ + .unresolved = node_index, + }); + try builder.resolveValue(unit, context, type_expect, value_index); + return value_index; + } + + const TypeCheckResult = enum { + success, + zero_extend, + sign_extend, + take_source, + take_expected, + optional_wrap, + array_coerce_to_slice, + }; + + const CastResult = enum{ + int_to_pointer, + enum_to_int, + sign_extend, + zero_extend, + }; + + fn getCastType(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, value_index: Value.Index) !CastResult { + _ = builder; // autofix + _ = context; // autofix + const value_type_index = unit.values.get(value_index).getType(unit); + const value_type = unit.types.get(value_type_index); + + return switch (type_expect) { + .type => |type_index| { + const destination_type = unit.types.get(type_index); + switch (destination_type.*) { + .pointer => |destination_pointer| { + _ = destination_pointer; // autofix + switch (value_type.*) { + .integer => |source_integer| { + _ = source_integer; // autofix + // TODO: + return .int_to_pointer; + }, + else => |t| @panic(@tagName(t)), + } + }, + .integer => |destination_integer| { + switch (value_type.*) { + .@"enum" => { + return .enum_to_int; + }, + .integer => |source_integer| { + if (destination_integer.bit_count < source_integer.bit_count) { + unreachable; + } else if (destination_integer.bit_count > source_integer.bit_count) { + assert(destination_integer.signedness != source_integer.signedness); + return switch (destination_integer.signedness) { + .signed => .sign_extend, + .unsigned => .sign_extend, + }; + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + }; + } + + const TokenDebugInfo = struct{ + line: u32, + column: u32, + }; + + fn getTokenDebugInfo(builder: *Builder, unit: *Unit, token: Token.Index) TokenDebugInfo{ + const file = unit.files.get(builder.current_file); + const line_offset_index = unit.token_buffer.tokens.items(.line)[Token.unwrap(token)]; + const line = line_offset_index - file.lexer.line_offset; + const offset = unit.token_buffer.tokens.items(.offset)[Token.unwrap(token)]; + const line_offset = unit.token_buffer.line_offsets.items[line_offset_index]; + const column = offset - line_offset; + + return .{ + .line = line, + .column = column, + }; + } + + fn insertDebugCheckPoint(builder: *Builder, unit: *Unit, context: *const Context, token: Token.Index) !void { + if (builder.generate_debug_info and builder.current_scope.local) { + const basic_block = unit.basic_blocks.get(builder.current_basic_block); + assert(!basic_block.terminated); + + const debug_info = builder.getTokenDebugInfo(unit, token); + + if (debug_info.line != builder.last_check_point.line or debug_info.column != builder.last_check_point.column or builder.current_scope != builder.last_check_point.scope) { + const instruction = try unit.instructions.append(context.allocator, .{ + .debug_checkpoint = .{ + .scope = builder.current_scope, + .line = debug_info.line, + .column = debug_info.column, + }, + }); + try basic_block.instructions.append(context.allocator, instruction); + + builder.last_check_point = .{ + .scope = builder.current_scope, + .line = debug_info.line, + .column = debug_info.column, + }; + } + } + } + + fn appendInstruction(builder: *Builder, unit: *Unit, context: *const Context, instruction_index: Instruction.Index) !void { + const basic_block = unit.basic_blocks.get(builder.current_basic_block); + assert(!basic_block.terminated); + try basic_block.instructions.append(context.allocator, instruction_index); + } + + const If = struct{ + condition: Condition, + const Condition = union(enum){ + true: Value.Index, + false, + runtime: Value.Index, + }; + }; + + fn resolveIf(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index) !If { + const if_node = unit.getNode(node_index); + assert(if_node.id == .@"if"); + + const condition = try builder.resolveValueAllocate(unit, context, Type.Expect{ + .type = .bool, + }, if_node.left); + assert(condition != .null); + + if (unit.evaluateBooleanAtComptime(condition)) |comptime_condition| { + if (comptime_condition == true) { + unreachable; + } else { + return If{ + .condition = .false, + }; + } + } else { + try builder.insertDebugCheckPoint(unit, context, if_node.token); + unreachable; + } + + unreachable; + } + + fn referenceDeclaration(builder: *Builder, unit: *Unit, context: *const Context, scope: *Scope, identifier: []const u8, declaration_index: Declaration.Index, value_index: Value.Index) !void { + _ = identifier; // autofix + const old_file = builder.current_file; + builder.current_file = scope.getFile(unit); + defer builder.current_file = old_file; + + const old_scope = builder.current_scope; + builder.current_scope = scope; + defer builder.current_scope = old_scope; + + const old_basic_block = builder.current_basic_block; + defer builder.current_basic_block = old_basic_block; + + // We need to switch the current file so that the correct node pointers are picked by the indices into the file buffer + const declaration = unit.declarations.get(declaration_index); + const declaration_value_index = declaration.value; + + const is_comptime = if (declaration_value_index != .null) b: { + const declaration_value = unit.values.get(declaration_value_index); + + switch (declaration_value.*) { + .unresolved => { + switch (declaration.type) { + .null => {}, + else => { + if (scope.kind == .function) { + @panic("arguments are not supposed to be here"); + } + + try builder.resolveType(unit, context, declaration.type); + }, + } + + const type_expect = switch (declaration.type) { + .null => .none, + else => Type.Expect{ + .type = declaration.type, + }, + }; + + try builder.resolveValue(unit, context, type_expect, declaration_value_index); + + switch (declaration.type) { + .null => declaration.type = declaration_value.getType(unit), + else => { + const declaration_type = unit.types.get(declaration.type); + assert(declaration_type.* != .unresolved); + }, + } + + switch (unit.values.get(declaration_value_index).*) { + .unresolved => unreachable, + .function_definition => |function_definition_index| { + const function_definition = unit.function_definitions.get(function_definition_index); + function_definition.symbol = .{ + .type = declaration.type, + .name = declaration.name, + .kind = .code, + }; + + try unit.function_declaration_map.putNoClobber(context.allocator, function_definition_index, declaration_index); + }, + .type => |type_index| { + switch (unit.types.get(type_index).*) { + .unresolved, .copy => unreachable, + else => {}, + } + try unit.type_declaration_map.putNoClobber(context.allocator, type_index, declaration_index); + }, + else => {}, + } + }, + else => {}, + } + + assert(declaration_value.* != .declaration); + assert(declaration_value.* != .unresolved); + + const result = if (declaration.mutability == .@"const" and declaration_value.isComptime(unit)) blk: { + if (declaration_value_index != value_index) { + unit.copyValue(value_index, declaration.value); + } else { + std.debug.print("Same value, not copying...\n", .{}); + } + + break :blk true; + } else blk: { + // Here we need to declare global variables that go into the executable + switch (scope.kind) { + .block, + .function, + => {}, + .file_container => { + if (unit.global_variable_map.get(declaration_index)) |_| { + // + } else { + switch (unit.values.get(declaration.value).*) { + .integer => {}, + .undefined => {}, + else => |t| @panic(@tagName(t)), + } + const global_variable = try unit.global_variables.append(context.allocator, .{ + .symbol = .{ + .name = declaration.name, + .type = declaration.type, + .kind = .data, + }, + .mutability = declaration.mutability, + .value = declaration.value, + }); + try unit.global_variable_map.putNoClobber(context.allocator, declaration_index, global_variable); + } + }, + else => |t| @panic(@tagName(t)), + } + + break :blk false; + }; + + break :b result; + } else false; + + if (!is_comptime) { + if (declaration.value != value_index) { + const value = unit.values.get(value_index); + value.* = .{ + .reference = declaration_index, + }; + + // Map the value to an instruction + switch (declaration.scope.kind) { + .function => { + const function = unit.function_definitions.get( builder.current_function); + const declaration_instruction = function.argument_map.get(declaration_index).?; + const load = try unit.instructions.append(context.allocator, .{ + .load_instruction = .{ + .value = declaration_instruction, + }, + }); + try builder.appendInstruction(unit, context, load); + + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, load); + }, + .block => { + const function = unit.function_definitions.get( builder.current_function); + const declaration_instruction = function.declarations.get(declaration_index).?; + const load = try unit.instructions.append(context.allocator, .{ + .load_instruction = .{ + .value = declaration_instruction, + }, + }); + try builder.appendInstruction(unit, context, load); + + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, load); + }, + .file_container => { + const global_variable_index = unit.global_variable_map.get(declaration_index).?; + const load = try unit.instructions.append(context.allocator, .{ + .load_global = .{ + .value = global_variable_index, + }, + }); + try builder.appendInstruction(unit, context, load); + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, load); + }, + else => |t| @panic(@tagName(t)), + } + } else unreachable; + } + } + + fn resolveValue(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, value_index: Value.Index) anyerror!void { + const value = unit.values.get(value_index); + const node_index = switch (value.*) { + .unresolved => |ni| ni, + else => |t| @panic(@tagName(t)), + }; + + const node = unit.getNode(node_index); + + switch (node.id) { + .block => { + const token_debug_info = builder.getTokenDebugInfo(unit, node.token); + const block_index = try unit.blocks.append(context.allocator, .{ + .scope = .{ + .line = token_debug_info.line, + .column = token_debug_info.column, + .kind = .block, + .level = builder.current_scope.level + 1, + .local = builder.current_scope.local, + .file = builder.current_file, + }, + }); + + value.* = .{ + .block = block_index, + }; + + const block = unit.blocks.get(block_index); + try builder.pushScope(unit, context, &block.scope); + defer builder.popScope(unit, context) catch unreachable; + + const statement_node_list = unit.getNodeList(node.left); + + for (statement_node_list) |statement_node_index| { + const statement_node = unit.getNode(statement_node_index); + + try builder.insertDebugCheckPoint(unit, context, statement_node.token); + + switch (statement_node.id) { + .assign, + .add_assign, + .if_else, + .@"return", + .intrinsic, + .constant_symbol_declaration, + .variable_symbol_declaration, + .call, + .@"switch", + .@"unreachable", + => { + const statement_value_index = try builder.resolveValueAllocate(unit, context, Type.Expect { .type = .void }, statement_node_index); + assert(unit.values.get(statement_value_index).* != .unresolved); + }, + else => |t| @panic(@tagName(t)), + } + } + }, + .assign, .add_assign => { + switch (type_expect) { + .none => {}, + .type => |original_type_index| { + const type_index = unit.unwrapTypeCopy(original_type_index); + assert(type_index == .void); + }, + } + + if (unit.getNode(node.left).id == .discard) { + value.* = .{ + .unresolved = node.right, + }; + + try builder.resolveValue(unit, context, Type.Expect.none, value_index); + } else { + const left = try builder.resolveValueAllocate(unit, context, Type.Expect.none, node.left); + const left_value = unit.values.get(left); + const is_left_comptime = left_value.isComptime(unit); + const left_type = left_value.getType(unit); + + switch (is_left_comptime) { + true => unreachable, + false => { + const right = try builder.resolveValueAllocate(unit, context, Type.Expect{ .type = left_type }, node.right); + const right_value = unit.values.get(right); + _ = right_value; // autofix + const right_i = unit.value_to_instruction_map.get(right).?; + + const assignment = Assignment{ + .destination = left, + .source = right, + .operation = switch (node.id) { + .assign => null, + .add_assign => .add, + else => |t| @panic(@tagName(t)), + }, + }; + const assignment_index = try unit.assignments.append(context.allocator, assignment); + value.* = .{ + .assignment = assignment_index, + }; + // TODO: get better at this + const store = try unit.instructions.append(context.allocator, .{ + .store = .{ + // TODO: do this better in a more efficient way + .destination = switch (left_value.*) { + .reference => |declaration_index| b: { + const declaration = unit.declarations.get(declaration_index); + switch (declaration.scope.kind) { + .file_container => { + const global_variable_index = unit.global_variable_map.get(declaration_index).?; + const instruction = try unit.instructions.append(context.allocator, .{ + .global_variable = global_variable_index, + }); + break :b instruction; + }, + .block => { + const function_definition = unit.function_definitions.get(builder.current_function); + const stack_slot_instruction = function_definition.declarations.get(declaration_index).?; + break :b stack_slot_instruction; + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + }, + .source = right_i, + }, + }); + + try builder.appendInstruction(unit, context, store); + }, + } + } + }, + .identifier => { + const identifier = unit.getExpectedTokenBytes(node.token, .identifier); + + const hash = try unit.processIdentifier(context, identifier); + + const look_in_parent_scopes = true; + if (builder.current_scope.lookupDeclaration(hash, look_in_parent_scopes)) |lookup| { + const scope = lookup.scope; + const declaration_index = lookup.declaration; + try builder.referenceDeclaration(unit, context, scope, + identifier, // Field inserted for debugging purposes + declaration_index, value_index); + } else { + var scope_it: ?*Scope = builder.current_scope; + const indentation_size = 4; + var indentation: u32 = 0; + + var file_path: []const u8 = ""; + while (scope_it) |scope| : (scope_it = scope.parent) { + for (0..indentation * indentation_size) |_|{ + std.debug.print(" ", .{}); + } + std.debug.print("> Scope {s} ", .{@tagName(scope.kind)}); + switch (scope.kind) { + .compilation_unit => {}, + .file_container, .container => {}, + .function => {}, + .file => { + const file = @fieldParentPtr(File, "scope", scope); + std.debug.print("{s}", .{file.relative_path}); + file_path = file.relative_path; + }, + .block => {}, + } + + std.debug.print("\n", .{}); + indentation += 1; + } + + std.debug.panic("Identifier '{s}' not found in file {s}", .{identifier, file_path}); + } + + assert(value.* != .unresolved); + }, + .intrinsic => { + const intrinsic_id: Intrinsic.Id = @enumFromInt(Node.unwrap(node.right)); + const argument_node_list = unit.getNodeList(node.left); + + switch (intrinsic_id) { + .import => { + if (argument_node_list.len != 1) { + @panic("Import argument mismatch"); + } + + const argument_node_index = argument_node_list[0]; + const argument_node = unit.getNode(argument_node_index); + if (argument_node.id != .string_literal) { + @panic("Import expected a string literal as an argument"); + } + + const string_literal_bytes = unit.tokenStringLiteral(argument_node.token); + + const import_file = try unit.importFile(context, builder.current_file, string_literal_bytes); + + if (import_file.file.is_new) { + const new_file_index = import_file.file.index; + try unit.generateAbstractSyntaxTreeForFile(context, new_file_index); + try builder.analyzeFile(unit, context, new_file_index); + logln(.compilation, .import, "Done analyzing {s}!", .{string_literal_bytes}); + } + + const file = unit.files.get(import_file.file.index); + const file_type = unit.types.get(file.type); + assert(file_type.* != .unresolved); + unit.copyValue(value_index, file.value); + }, + .@"asm" => { + const architecture = InlineAssembly.x86_64; + + var instructions = try ArrayList(InlineAssembly.Instruction.Index).initCapacity(context.allocator, argument_node_list.len); + + for (argument_node_list) |assembly_statement_node_index| { + const assembly_statement_node = unit.getNode(assembly_statement_node_index); + const instruction_name = unit.getExpectedTokenBytes(assembly_statement_node.token, .identifier); + const instruction = inline for (@typeInfo(architecture.Instruction).Enum.fields) |instruction_enum_field| { + if (equal(u8, instruction_name, instruction_enum_field.name)) { + break @field(architecture.Instruction, instruction_enum_field.name); + } + } else unreachable; + const operand_nodes = unit.getNodeList(assembly_statement_node.left); + + var operands = try ArrayList(InlineAssembly.Operand).initCapacity(context.allocator, operand_nodes.len); + + for (operand_nodes) |operand_node_index| { + const operand_node = unit.getNode(operand_node_index); + const operand: InlineAssembly.Operand = switch (operand_node.id) { + .assembly_register => blk: { + const register_name = unit.getExpectedTokenBytes(operand_node.token, .identifier); + + const register = inline for (@typeInfo(architecture.Register).Enum.fields) |register_enum_field| { + if (equal(u8, register_name, register_enum_field.name)) { + break @field(architecture.Register, register_enum_field.name); + } + } else unreachable; + break :blk .{ + .register = @intFromEnum(register), + }; + }, + .number_literal => switch (std.zig.parseNumberLiteral(unit.getExpectedTokenBytes(operand_node.token, .number_literal))) { + .int => |integer| .{ + .number_literal = integer, + }, + else => |t| @panic(@tagName(t)), + }, + .identifier => .{ + .value_index = try builder.resolveValueAllocate(unit, context, Type.Expect.none, operand_node_index), + }, + else => |t| @panic(@tagName(t)), + }; + + operands.appendAssumeCapacity(operand); + } + + const instruction_index = try unit.assembly_instructions.append(context.allocator, .{ + .id = @intFromEnum(instruction), + .operands = operands.items, + }); + + instructions.appendAssumeCapacity(instruction_index); + } + + const inline_assembly = try unit.inline_assembly.append(context.allocator, .{ + .instructions = instructions.items, + }); + + const inline_asm = try unit.instructions.append(context.allocator, .{ + .inline_assembly = inline_assembly, + }); + try builder.appendInstruction(unit, context, inline_asm); + + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, inline_asm); + + value.* = .{ + .inline_assembly = inline_assembly, + }; + }, + .cast => { + assert(argument_node_list.len == 1); + const value_to_cast_index = try builder.resolveValueAllocate(unit, context, Type.Expect.none, argument_node_list[0]); + + const cast_type = try builder.getCastType(unit, context, type_expect, value_to_cast_index); + switch (cast_type) { + .int_to_pointer => { + const intrinsic = try unit.intrinsics.append(context.allocator, .{ + .kind = .{ + .int_to_pointer = value_to_cast_index, + }, + .type = type_expect.type, + }); + + const instruction = try unit.instructions.append(context.allocator, .{ + .cast = .{ + .id = .int_to_pointer, + .value = value_to_cast_index, + .type = type_expect.type, + }, + }); + + try builder.appendInstruction(unit, context, instruction); + + value.* = .{ + .intrinsic = intrinsic, + }; + + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, instruction); + }, + .enum_to_int => { + const intrinsic = try unit.intrinsics.append(context.allocator, .{ + .kind = .{ + .enum_to_int = value_to_cast_index, + }, + .type = type_expect.type, + }); + + const instruction = try unit.instructions.append(context.allocator, .{ + .cast = .{ + .id = .enum_to_int, + .value = value_to_cast_index, + .type = type_expect.type, + }, + }); + + try builder.appendInstruction(unit, context, instruction); + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, instruction); + + value.* = .{ + .intrinsic = intrinsic, + }; + }, + .sign_extend => { + const intrinsic = try unit.intrinsics.append(context.allocator, .{ + .kind = .{ + .sign_extend = value_to_cast_index, + }, + .type = type_expect.type, + }); + + const instruction = try unit.instructions.append(context.allocator, .{ + .cast = .{ + .id = .sign_extend, + .value = value_to_cast_index, + .type = type_expect.type, + }, + }); + + try builder.appendInstruction(unit, context, instruction); + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, instruction); + + value.* = .{ + .intrinsic = intrinsic, + }; + }, + else => |t| @panic(@tagName(t)), + } + }, + .size => { + assert(argument_node_list.len == 1); + const argument_type_index = unit.unwrapTypeCopy(try builder.resolveTypeAllocate(unit, context, argument_node_list[0])); + const argument_type = unit.types.get(argument_type_index); + const argument_size = argument_type.getByteSize(unit); + + const integer = Value.Integer{ + .value = argument_size, + .type = switch (type_expect) { + .none => .comptime_int, + .type => |type_index| b: { + const ty = unit.types.get(type_index); + break :b switch (ty.*) { + .integer => type_index, + else => |t| @panic(@tagName(t)), + }; + }, + }, + }; + + value.* = .{ + .integer = integer, + }; + + const const_int = try unit.getConstantInt(context, integer); + + try builder.appendInstruction(unit, context, const_int); + // We dont mind clobbering the value + try unit.value_to_instruction_map.put(context.allocator, value_index, const_int); + }, + .syscall => { + if (argument_node_list.len > 0 and argument_node_list.len <= 6 + 1) { + var argument_list = try ArrayList(Value.Index).initCapacity(context.allocator, argument_node_list.len); + var instruction_list = try ArrayList(Instruction.Index).initCapacity(context.allocator, argument_node_list.len); + // TODO + const arg_type_expect = Type.Expect{ + .type = Type.Index.usize, + }; + + for (argument_node_list) |argument_node_index| { + const argument_value_index = try builder.resolveValueAllocate(unit, context, arg_type_expect, argument_node_index); + + const argument_value = unit.values.get(argument_value_index); + _ = argument_value; // autofix + const instruction_index = unit.value_to_instruction_map.get(argument_value_index).?; + + argument_list.appendAssumeCapacity(argument_value_index); + instruction_list.appendAssumeCapacity(instruction_index); + } + + const intrinsic = try unit.intrinsics.append(context.allocator, .{ + .kind = .{ + .syscall = argument_list.items, + }, + .type = Type.Index.usize, + }); + + const syscall = try unit.instructions.append(context.allocator, .{ + .syscall = .{ + .arguments = instruction_list.items, + }, + }); + + try builder.appendInstruction(unit, context, syscall); + + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, syscall); + + value.* = .{ + .intrinsic = intrinsic, + }; + } else { + @panic("Syscall argument mismatch"); + } + }, + else => |t| @panic(@tagName(t)), + } + + assert(value.* != .unresolved); + }, + .if_else => { + assert(node.left != .null); + assert(node.right != .null); + + const if_result = try builder.resolveIf(unit, context, node.left); + switch (if_result.condition) { + .false => { + value.* = .{ + .unresolved = node.right, + }; + try builder.resolveValue(unit, context, Type.Expect{ + .type = .void, + }, value_index); + }, + else => |t| @panic(@tagName(t)), + } + }, + .field_access => { + const right_node = unit.getNode(node.right); + assert(right_node.id == .identifier); + const identifier = unit.getExpectedTokenBytes(right_node.token,.identifier); + + const lvi = try builder.resolveValueAllocate(unit, context, Type.Expect.none, node.left); + const left_value_index = unit.unwrapValueCopy(lvi); + const left_value = unit.values.get(left_value_index); + + switch (left_value.*) { + .type => |type_index| { + const left_type = unit.types.get(type_index); + const scope = left_type.getScope(unit); + const identifier_hash = try unit.processIdentifier(context, identifier); + const look_in_parent_scopes = false; + + if (scope.lookupDeclaration(identifier_hash, look_in_parent_scopes)) |lookup| { + try builder.referenceDeclaration(unit,context, lookup.scope, identifier, // Argument inserted for debugging + lookup.declaration, value_index); + if (value.isComptime(unit)) { + // + } else { + unreachable; + } + } else { + switch (left_type.*) { + .@"enum" => |enum_index| { + const enum_type = unit.enums.get(enum_index); + const field_index = for (enum_type.fields.items) |enum_field_index| { + const enum_field = unit.enum_fields.get(enum_field_index); + if (enum_field.name == identifier_hash) { + break enum_field_index; + } + } else @panic("identifier not found"); + value.* = .{ + .enum_field = field_index, + }; + }, + else => |t| @panic(@tagName(t)), + } + } + }, + else => |t| @panic(@tagName(t)), + } + }, + .keyword_false => unit.copyValue(value_index, Value.Index.bool_false), + .keyword_true => unit.copyValue(value_index, Value.Index.bool_true), + .function_definition => { + const current_basic_block = builder.current_basic_block; + defer builder.current_basic_block = current_basic_block; + builder.current_basic_block = .null; + const function_prototype_node_index = node.left; + const body_node_index = node.right; + + const function_prototype_index = try builder.resolveFunctionPrototype(unit, context, function_prototype_node_index); + const function_prototype_type_index = try unit.types.append(context.allocator, .{ + .function = function_prototype_index, + }); + const body_value_index = try unit.values.append(context.allocator, .{ + .unresolved = body_node_index, + }); + + const old_function = builder.current_function; + const token_debug_info = builder.getTokenDebugInfo(unit, node.token); + builder.current_function = try unit.function_definitions.append(context.allocator, .{ + .type = function_prototype_type_index, + .body = body_value_index, + .scope = Scope{ + .line = token_debug_info.line, + .column = token_debug_info.column, + .kind = .function, + .local = true, + .level = builder.current_scope.level + 1, + .file = builder.current_file, + }, + // This data assignment when the symbol is resolved in an upper step in the callstack + .symbol = undefined, + }); + defer builder.current_function = old_function; + + const function = unit.function_definitions.get(builder.current_function); + value.* = .{ + .function_definition = builder.current_function, + }; + + builder.last_check_point = .{}; + + try builder.pushScope(unit, context, &function.scope); + defer builder.popScope(unit, context) catch unreachable; + + const entry_basic_block = try builder.newBasicBlock(unit, context); + builder.current_basic_block = entry_basic_block; + defer builder.current_basic_block = .null; + + // Get argument declarations into scope + const function_prototype_node = unit.getNode(function_prototype_node_index); + if (function_prototype_node.left != .null) { + const argument_node_list = unit.getNodeList(function_prototype_node.left); + const function_prototype = unit.function_prototypes.get(function_prototype_index); + const argument_types = function_prototype.argument_types; + + for (argument_node_list, argument_types) |argument_node_index, argument_type_index| { + const argument_node = unit.getNode(argument_node_index); + assert(argument_node.id == .argument_declaration); + + const argument_name = unit.getExpectedTokenBytes(argument_node.token, .identifier); + const argument_name_hash = try unit.processIdentifier(context, argument_name); + + const look_in_parent_scopes = true; + if (builder.current_scope.lookupDeclaration(argument_name_hash, look_in_parent_scopes)) |_| { + std.debug.panic("Symbol with name '{s}' already declarared on scope", .{argument_name}); + } + + const argument_token_debug_info = builder.getTokenDebugInfo(unit, argument_node.token); + const argument_declaration_index = try unit.declarations.append(context.allocator, .{ + .scope = builder.current_scope, + .name = argument_name_hash, + .type = argument_type_index, + .value = .null, + .mutability = .@"const", + .line = argument_token_debug_info.line, + .column = argument_token_debug_info.column, + }); + + try builder.current_scope.declarations.putNoClobber(context.allocator, argument_name_hash, argument_declaration_index); + } + + for (builder.current_scope.declarations.values()) |declaration_index| { + const argument_declaration = unit.declarations.get(declaration_index); + + const argument_instruction = try unit.instructions.append(context.allocator, .{ + .argument_declaration = .{ + .name = argument_declaration.name, + .type = argument_declaration.type, + }, + }); + + try builder.appendInstruction(unit, context, argument_instruction); + + try function.argument_map.putNoClobber(context.allocator, declaration_index, argument_instruction); + } + } + + const body_node = unit.getNode(body_node_index); + + if (body_node.id == .block) { + try builder.resolveValue(unit, context, Type.Expect{ .type = .void }, body_value_index); + + logln(.compilation, .ir, "Function #{}", .{Function.Definition.unwrap(builder.current_function)}); + + for (function.basic_blocks.items) |basic_block_index| { + const basic_block = unit.basic_blocks.get(basic_block_index); + logln(.compilation, .ir, "[#{}]:", .{BasicBlock.unwrap(basic_block_index)}); + + for (basic_block.instructions.items) |instruction_index| { + const instruction = unit.instructions.get(instruction_index); + log(.compilation, .ir, " %{}: {s} ", .{Instruction.unwrap(instruction_index), @tagName(instruction.*)}); + + switch (instruction.*) { + .debug_checkpoint => |checkpoint| { + log(.compilation, .ir, "{}, {}", .{checkpoint.line, checkpoint.column}); + }, + .argument_declaration => |arg|{ + log(.compilation, .ir, "\"{s}\"", .{unit.getIdentifier(arg.name)}); + }, + .cast => |cast| { + log(.compilation, .ir, "{s}", .{@tagName(cast.id)}); + }, + // .binary_operation => |binary_operation| { + // log(.compilation, .ir, "{s}", .{@tagName(binary_operation.id)}); + // }, + else => {} + } + logln(.compilation, .ir, "", .{}); + } + } + } else { + @panic("Function body is expected to be a block"); + } + }, + .constant_symbol_declaration, + .variable_symbol_declaration, => { + // All variables here are local + assert(builder.current_scope.local); + const expected_identifier_token_index = Token.addInt(node.token, 1); + const identifier = unit.getExpectedTokenBytes(expected_identifier_token_index, .identifier); + logln(.compilation, .identifier, "Analyzing local declaration {s}", .{identifier}); + const identifier_hash = try unit.processIdentifier(context, identifier); + + const look_in_parent_scopes = true; + if (builder.current_scope.lookupDeclaration(identifier_hash, look_in_parent_scopes)) |lookup| { + _ = lookup; // autofix + std.debug.panic("Identifier '{s}' already declarared on scope", .{identifier}); + } + + const type_node_index = node.left; + const value_node_index = node.right; + assert(value_node_index != .null); + const init_value_index = try unit.values.append(context.allocator, .{ + .unresolved = value_node_index, + }); + + const declaration_type_index: Type.Index = switch (type_node_index) { + .null => blk: { + try builder.resolveValue(unit, context, Type.Expect.none, init_value_index); + const init_value = unit.values.get(init_value_index); + const init_type_index = init_value.getType(unit); + break :blk init_type_index; + }, + else => blk: { + const type_index = try builder.resolveTypeAllocate(unit, context, type_node_index); + try builder.resolveValue(unit, context, Type.Expect{ + .type = type_index, + }, init_value_index); + break :blk type_index; + }, + }; + + const token_debug_info = builder.getTokenDebugInfo(unit, node.token); + + const mutability: Mutability = switch (node.id) { + .constant_symbol_declaration => .@"const", + .variable_symbol_declaration => .@"var", + else => unreachable, + }; + + const declaration_index = try unit.declarations.append(context.allocator, .{ + .scope = builder.current_scope, + .name = identifier_hash, + .type = declaration_type_index, + .value = init_value_index, + .mutability = mutability, + .line = token_debug_info.line, + .column = token_debug_info.column, + }); + + try builder.current_scope.declarations.putNoClobber(context.allocator, identifier_hash, declaration_index); + + if (!(mutability == .@"const" and unit.values.get(init_value_index).isComptime(unit))) { + const instruction = try unit.instructions.append(context.allocator, .{ + .stack_slot = .{ + .type = unit.unwrapTypeCopy(declaration_type_index), + }, + }); + try builder.appendInstruction(unit, context, instruction); + + const current_function = unit.function_definitions.get(builder.current_function); + try current_function.declarations.putNoClobber(context.allocator, declaration_index, instruction); + + const init_value = unit.values.get(init_value_index); + switch (init_value.*) { + .intrinsic => |intrinsic_index| { + const intrinsic = unit.intrinsics.get(intrinsic_index); + _ = intrinsic; // autofix + // @breakpoint(); + }, + else => {}, + } + const init_value_instruction = unit.value_to_instruction_map.get(init_value_index).?; + + // TODO: store initial value + const initialization = try unit.instructions.append(context.allocator, .{ + .store = .{ + .destination = instruction, + .source = init_value_instruction, + }, + }); + try builder.appendInstruction(unit, context, initialization); + } + + value.* = .{ + .declaration = declaration_index, + }; + }, + .pointer_dereference => { + // TODO: + const pointer_type_expect = switch (type_expect) { + .none => unreachable,//type_expect, + .type => |type_index| b: { + const pointer_type = try unit.types.append(context.allocator, .{ + .unresolved = undefined, + }); + try unit.getPointerType(context, .{ + .type = type_index, + .mutability = .@"const", + .many = false, // TODO + .termination = .none, // TODO + .nullable = false, + }, pointer_type); + const result = Type.Expect{ + .type = pointer_type, + }; + break :b result; + }, + }; + const pointer_value_index = try builder.resolveValueAllocate(unit, context, pointer_type_expect, node.left); + + const load = try unit.instructions.append(context.allocator, .{ + .load = .{ + .value = pointer_value_index, + }, + }); + try builder.appendInstruction(unit, context, load); + + const unary_operation = try unit.unary_operations.append(context.allocator, .{ + .value = pointer_value_index, + .type = switch (type_expect) { + .none => unreachable, + .type => |type_index| type_index, + }, + .id = .pointer_dereference, + }); + + value.* = .{ + .unary_operation = unary_operation, + }; + + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, load); + }, + .number_literal => switch (std.zig.parseNumberLiteral(unit.getExpectedTokenBytes(node.token, .number_literal))) { + .int => |integer| { + const type_index = switch (type_expect) { + .type => |original_type_index| b: { + const type_index = unit.unwrapTypeCopy(original_type_index); + const ty = unit.types.get(type_index); + break :b switch (ty.*) { + .integer => type_index, + else => |t| @panic(@tagName(t)), + }; + }, + .none => Type.Index.comptime_int, + //else => |t| @panic(@tagName(t)), + }; + + const integer_value = Value.Integer{ + .value = integer, + .type = type_index, + }; + + const const_int = try unit.getConstantInt(context, integer_value); + + if (builder.current_basic_block != .null) { + try builder.appendInstruction(unit, context, const_int); + } + + value.* = .{ + .integer = integer_value, + }; + + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, const_int); + }, + else => |t| @panic(@tagName(t)), + }, + .undefined => { + value.* = .undefined; + }, + .add, .mul => { + const binary_operation_id: BinaryOperation.Id = switch (node.id) { + .add => .add, + .sub => .sub, + .bit_and => .bit_and, + .bit_xor => .bit_xor, + .bit_or => .bit_or, + .mul => .mul, + .div => .div, + .mod => .mod, + .shift_left => .shift_left, + .shift_right => .shift_right, + .compare_equal => .compare_equal, + .compare_not_equal => .compare_not_equal, + .compare_greater => .compare_greater, + .compare_greater_equal => .compare_greater_equal, + .compare_less => .compare_less, + .compare_less_equal => .compare_less_equal, + else => |t| @panic(@tagName(t)), + }; + const left_expect_type: Type.Expect = switch (binary_operation_id) { + .compare_equal, + .compare_not_equal, + .compare_less, + .compare_less_equal, + .compare_greater, + .compare_greater_equal, + => Type.Expect.none, + else => type_expect, + }; + const left_value_index = try builder.resolveValueAllocate(unit, context, left_expect_type, node.left); + const left_value = unit.values.get(left_value_index); + const left_instruction = unit.value_to_instruction_map.get(left_value_index).?; + const left_type = left_value.getType(unit); + + const right_expect_type: Type.Expect = switch (type_expect) { + .none => Type.Expect{ + .type = left_type, + }, + .type => switch (binary_operation_id) { + .add, + .sub, + .bit_and, + .bit_xor, + .bit_or, + .mul, + .div, + .mod, + .shift_left, + .shift_right, + => type_expect, + // .shift_left, + // .shift_right, + // => ExpectType{ + // .type_index = Type.u8, + // }, + .compare_equal, + .compare_not_equal, + .compare_less, + .compare_greater, + .compare_greater_equal, + .compare_less_equal, + => Type.Expect{ + .type = left_type, + }, + }, + // else => |t| @panic(@tagName(t)), + }; + const right_value_index = try builder.resolveValueAllocate(unit, context, right_expect_type, node.right); + const right_instruction = unit.value_to_instruction_map.get(right_value_index).?; + + const binary_operation = BinaryOperation{ + .left = left_value_index, + .right = right_value_index, + .type = switch (type_expect) { + .none => switch (binary_operation_id) { + .bit_and, + .bit_or, + .shift_right, + .add, + .sub, + .mul, + .div, + .mod, + => left_type, + else => |t| @panic(@tagName(t)), + }, + .type => |type_index| type_index, + // else => |t| @panic(@tagName(t)), + }, + .id = binary_operation_id, + }; + const binary_operation_index = try unit.binary_operations.append(context.allocator, binary_operation); + + { + const instruction = switch (unit.types.get(left_type).*) { + .integer => |integer| b: { + const id: Instruction.IntegerBinaryOperation.Id = switch (binary_operation_id) { + .add => .add, + .mul => .mul, + else => |t| @panic(@tagName(t)), + }; + + const i = try unit.instructions.append(context.allocator, .{ + .integer_binary_operation = .{ + .left = left_instruction, + .right = right_instruction, + .id = id, + .signedness = integer.signedness, + }, + }); + break :b i; + }, + else => |t| @panic(@tagName(t)), + }; + + try builder.appendInstruction(unit, context, instruction); + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, instruction); + } + + value.* = .{ + .binary_operation = binary_operation_index, + }; + }, + .call => { + assert(node.left != .null); + assert(node.right != .null); + const left_node = unit.getNode(node.left); + const is_field_access = switch (left_node.id) { + .field_access => true, + else => false, + }; + _ = is_field_access; // autofix + const left_value_index = unit.unwrapValueCopy( try builder.resolveValueAllocate(unit, context, Type.Expect.none, node.left)); + const left_value = unit.values.get(left_value_index); + + switch (left_value.*) { + .function_definition => |function_definition_index| { + const function = unit.function_definitions.get(function_definition_index); + const function_type = unit.types.get( function.type); + const function_prototype = unit.function_prototypes.get(function_type.function); + const argument_nodes = unit.getNodeList(node.right); + const argument_declaration_count = function.scope.declarations.count(); + + if (argument_nodes.len != argument_declaration_count) { + @panic("Argument count mismatch"); + } + + var list = try ArrayList(Value.Index).initCapacity(context.allocator, argument_declaration_count); + var instruction_list = try ArrayList(Instruction.Index).initCapacity(context.allocator, argument_declaration_count); + + for (argument_nodes, function.scope.declarations.values()) |arg_ni, argument_declaration_index| { + const argument_node = unit.getNode(arg_ni); + const argument_declaration = unit.declarations.get(argument_declaration_index); + const arg_type_expect = Type.Expect{ + .type = argument_declaration.type, + }; + const argument_node_index = switch (argument_node.id) { + .named_argument => argument_node.right, + else => arg_ni, + }; + const argument_value_index = try builder.resolveValueAllocate(unit, context, arg_type_expect, argument_node_index); + list.appendAssumeCapacity(argument_value_index); + instruction_list.appendAssumeCapacity(unit.value_to_instruction_map.get(argument_value_index).?); + } + + const call = Call{ + .value = left_value_index, + .arguments = list.items, + .type = switch (type_expect) { + .none => function_prototype.return_type, + .type => |type_index| b: { + const rt_index = unit.unwrapTypeCopy( function_prototype.return_type); + const rt = unit.types.get(rt_index); + _ = rt; // autofix + if (rt_index != .noreturn) { + assert(type_index == rt_index); + } + break :b rt_index; + }, + // else => |t| @panic(@tagName(t)), + }, + }; + + const instruction = try unit.instructions.append(context.allocator, .{ + .call = .{ + .callable = .{ + .function_definition = function_definition_index, + }, + .function_type = function.type, + .arguments = instruction_list.items, + }, + }); + try builder.appendInstruction(unit, context, instruction); + + try unit.value_to_instruction_map.putNoClobber(context.allocator, value_index, instruction); + + // TODO: type check return type + const call_index = try unit.calls.append(context.allocator, call); + + value.* = .{ + .call = call_index, + }; + }, + else => |t| @panic(@tagName(t)), + } + }, + .@"return" => { + assert(node.left != .null); + assert(node.right == .null); + const return_value_node_index = node.left; + const return_value_index = try builder.resolveValueAllocate(unit, context, Type.Expect{ + .type = unit.getReturnType(builder.current_function), + },return_value_node_index); + const instruction_index = unit.value_to_instruction_map.get(return_value_index).?; + const return_index = try unit.returns.append(context.allocator,.{ + .value = return_value_index, + }); + value.* = .{ + .@"return" = return_index, + }; + + const ret = try unit.instructions.append(context.allocator, .{ + .ret = instruction_index, + }); + try builder.appendInstruction(unit, context, ret); + }, + .@"switch" => { + const switch_expression_value_index = try builder.resolveValueAllocate(unit, context, Type.Expect.none, node.left); + const case_nodes = unit.getNodeList(node.right); + + const comptime_switch_expression = unit.evaluateAtComptime(switch_expression_value_index); + if (comptime_switch_expression != .null) { + const switch_expression = unit.values.get(comptime_switch_expression); + switch (switch_expression.*) { + .enum_field => |enum_field_index| { + const enum_field = unit.enum_fields.get(enum_field_index); + const enum_type_general = unit.types.get(enum_field.parent); + const enum_type = unit.enums.get(enum_type_general.@"enum"); + const typecheck_enum_result = try unit.typecheckSwitchEnums(context, enum_type.*, case_nodes); + + const group_index = for (typecheck_enum_result.switch_case_groups.items, 0..) |switch_case_group, switch_case_group_index| { + break for (switch_case_group.items) |field_index| { + if (enum_field_index == field_index) { + break switch_case_group_index; + } + } else { + continue; + }; + } else typecheck_enum_result.else_switch_case_group_index orelse unreachable; + const true_switch_case_node = unit.getNode(case_nodes[group_index]); + value.* = .{ + .unresolved = true_switch_case_node.right, + }; + try builder.resolveValue(unit, context, type_expect, value_index); + }, + else => |t| @panic(@tagName(t)), + } + } else { + unreachable; + } + }, + .enum_type => { + const node_list = unit.getNodeList(node.left); + + const type_index = try unit.types.append(context.allocator,.{ + .unresolved = node_index, + }); + value.* = .{ + .type = type_index, + }; + + try builder.resolveContainerType(unit, context, node_list, node_index, .@"enum", type_index); + }, + .@"unreachable" => { + unit.copyValue(value_index, .@"unreachable"); + const instruction = try unit.instructions.append(context.allocator, .@"unreachable"); + try builder.appendInstruction(unit, context, instruction); + }, + else => |t| @panic(@tagName(t)), + } + + assert(value.* != .unresolved); + } + + fn newBasicBlock(builder: *Builder, unit: *Unit, context: *const Context) !BasicBlock.Index{ + const function = unit.function_definitions.get(builder.current_function); + const entry_basic_block = try unit.basic_blocks.append(context.allocator, .{}); + try function.basic_blocks.append(context.allocator, entry_basic_block); + + return entry_basic_block; + } + + fn resolveTypeAllocate(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index) !Type.Index { + const type_index = try unit.types.append(context.allocator, .{ + .unresolved = node_index, + }); + try builder.resolveType(unit, context, type_index); + return type_index; + } + + fn resolveType(builder: *Builder, unit: *Unit, context: *const Context, type_index: Type.Index) anyerror!void { + const ty = unit.types.get(type_index); + const node_index = switch (ty.*) { + .unresolved => |ni| ni, + else => |t| @panic(@tagName(t)), + }; + const node = unit.getNode(node_index); + + switch (node.id) { + .signed_integer_type, .unsigned_integer_type, => { + const token_bytes = unit.getExpectedTokenBytes(node.token, switch (node.id) { + .signed_integer_type => .keyword_signed_integer, + .unsigned_integer_type => .keyword_unsigned_integer, + else => unreachable, + }); + + const number_chunk = token_bytes[1..]; + // + try unit.getIntegerType(context, .{ + .bit_count = try std.fmt.parseInt(u16, number_chunk, 10), + .signedness = switch (node.id) { + .signed_integer_type => .signed, + .unsigned_integer_type => .unsigned, + else => unreachable, + }, + }, type_index); + }, + .pointer_type => { + const attribute_node_list = unit.getNodeList(node.left); + var mutability = Mutability.@"var"; + var element_type_index = Type.Index.null; + var termination = Type.Termination.none; + var many = false; + + for (attribute_node_list) |element_node_index| { + const element_node = unit.getNode(element_node_index); + switch (element_node.id) { + .function_prototype, + .identifier, + .unsigned_integer_type, + .signed_integer_type, + .optional_type, + .array_type, + .usize_type, + .pointer_type, + => { + if (element_type_index != .null) { + unreachable; + } + + element_type_index = try builder.resolveTypeAllocate(unit, context, element_node_index); + }, + .const_expression => mutability = .@"const", + .many_pointer_expression => many = true, + .zero_terminated => { + assert(many); + assert(termination == .none); + termination = .zero; + }, + .null_terminated => { + assert(many); + assert(termination == .none); + termination = .null; + }, + else => |t| @panic(@tagName(t)), + } + } + + assert(element_type_index != .null); + + try unit.getPointerType(context, .{ + .mutability = mutability, + .many = many, + .type = element_type_index, + .termination = termination, + .nullable = false, + }, type_index); + }, + .optional_type => { + const element_type_index = try builder.resolveTypeAllocate(unit, context, node.left); + const element_type = unit.types.get(element_type_index); + switch (element_type.*) { + .pointer => |pointer| { + var nullable_pointer = pointer; + assert(!nullable_pointer.nullable); + nullable_pointer.nullable = true; + try unit.getPointerType(context, nullable_pointer, type_index); + }, + else => unreachable, + } + }, + .keyword_noreturn => unit.copyType(type_index, Type.Index.noreturn), + .usize_type => unit.copyType(type_index, Type.Index.usize), + else => |t| @panic(@tagName(t)), + } + + assert(unit.types.get(type_index).* != .unresolved); + } + + fn resolveFunctionPrototype(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index) !Function.Prototype.Index { + const node = unit.getNode(node_index); + assert(node.id == .function_prototype); + const attribute_and_return_type_node_list = unit.getNodeList(node.right); + assert(attribute_and_return_type_node_list.len >= 1); + const attribute_node_list = attribute_and_return_type_node_list[0..attribute_and_return_type_node_list.len - 1]; + const return_type_node_index = attribute_and_return_type_node_list[attribute_and_return_type_node_list.len - 1]; + + const function_prototype_index = try unit.function_prototypes.append(context.allocator, .{ + .argument_types = &.{}, + .return_type = .null, + .attributes = .{ + .@"export" = false, + .naked = false, + }, + }); + + + var is_export: bool = false; + var is_naked: bool = false; + + // Resolve attributes + for (attribute_node_list) |attribute_node_index| { + const attribute_node = unit.getNode(attribute_node_index); + switch (attribute_node.id) { + .attribute_export => is_export = true, + .attribute_naked => is_naked = true, + else => |t| @panic(@tagName(t)), + } + } + + const function_prototype = unit.function_prototypes.get(function_prototype_index); + + if (node.left != .null) { + const argument_node_list = unit.getNodeList(node.left); + var argument_types = try ArrayList(Type.Index).initCapacity(context.allocator, argument_node_list.len); + + for (argument_node_list) |argument_node_index| { + const argument_node = unit.getNode(argument_node_index); + assert(argument_node.id == .argument_declaration); + + const argument_type_index = try builder.resolveTypeAllocate(unit, context, argument_node.left); + assert(unit.types.get(argument_type_index).* != .unresolved); + argument_types.appendAssumeCapacity(argument_type_index); + } + + function_prototype.argument_types = argument_types.items; + } + + function_prototype.attributes = .{ + .@"export" = is_export, + .naked = is_naked, + }; + + function_prototype.return_type = try builder.resolveTypeAllocate(unit, context, return_type_node_index); + + return function_prototype_index; + } + + fn resolveContainerType(builder: *Builder, unit: *Unit, context: *const Context, container_nodes: []const Node.Index, container_node_index: Node.Index, container_type: ContainerType, type_index: Type.Index) !void { + const current_basic_block = builder.current_basic_block; + defer builder.current_basic_block = current_basic_block; + builder.current_basic_block = .null; + + const container_node = unit.getNode(container_node_index); + + const Data = struct{ + scope: *Scope, + type: Type.Index, + }; + + const backing_type: Type.Index = switch (container_node.right) { + .null => .null, + else => |backing_type_node_index| b: { + switch (builder.current_scope.kind) { + .file => unreachable, + else => { + const backing_type_index = try builder.resolveTypeAllocate(unit, context, backing_type_node_index); + const backing_type = unit.types.get(unit.unwrapTypeCopy(backing_type_index)); + switch (backing_type.*) { + .integer => |integer| { + switch (integer.bit_count) { + 8, 16, 32, 64 => {}, + else => @panic("Invalid integer backing type bit count"), + } + }, + else => |t| @panic(@tagName(t)), + } + + break :b backing_type_index; + }, + } + }, + // if (true) unreachable; + // break :blk switch (container_type) { + // else => |t| @panic(@tagName(t)), + // }; + }; + + const token_debug_info = builder.getTokenDebugInfo(unit, container_node.token); + const data: Data = switch (container_type) { + .@"struct" => b: { + assert(container_node.id == .struct_type); + const struct_index = try unit.structs.append(context.allocator, .{ + .scope = .{ + .kind = switch (builder.current_scope.kind) { + .file => .file_container, + else => .container, + }, + .line = token_debug_info.line, + .column = token_debug_info.column, + .level = builder.current_scope.level + 1, + .local = false, + .file = builder.current_file, + }, + .backing_type = backing_type, + }); + const struct_type = unit.structs.get(struct_index); + + const ty = unit.types.get(type_index); + ty.* = .{ + .@"struct" = struct_index, + }; + + try unit.struct_type_map.putNoClobber(context.allocator, struct_index, type_index); + + break :b .{ + .scope = &struct_type.scope, + .type = type_index, + }; + }, + .@"enum" => b: { + assert(container_node.id == .enum_type); + const enum_index = try unit.enums.append(context.allocator, .{ + .scope = .{ + .kind = .container, + .line = token_debug_info.line, + .column = token_debug_info.column, + .level = builder.current_scope.level + 1, + .local = false, + .file = builder.current_file, + }, + .backing_type = backing_type, + }); + + const enum_type = unit.enums.get(enum_index); + const ty = unit.types.get(type_index); + ty.* = .{ + .@"enum" = enum_index, + }; + break :b .{ + .scope = &enum_type.scope, + .type = type_index, + }; + }, + }; + + const scope = data.scope; + try builder.pushScope(unit, context, scope); + defer builder.popScope(unit, context) catch unreachable; + + const count = blk: { + var result: struct { + fields: u32 = 0, + declarations: u32 = 0, + comptime_blocks: u32 = 0, + } = .{}; + + for (container_nodes) |member_index| { + const member = unit.getNode(member_index); + switch (container_type) { + .@"struct" => assert(member.id != .enum_field), + .@"enum" => assert(member.id != .container_field), + } + // const token_offset = file.lexer.token_offsets.items[Token.unwrap(member.token)]; + // const slice = file.source_code[token_offset..@min(token_offset + 100, file.source_code.len)]; + // std.debug.print("Member: `{s}`\n", .{slice}); + const member_type = getContainerMemberType(member.id); + + switch (member_type) { + .declaration => result.declarations += 1, + .field => result.fields += 1, + .comptime_block => result.comptime_blocks += 1, + } + } + + break :blk result; + }; + + var declaration_nodes = try ArrayList(Node.Index).initCapacity(context.allocator, count.declarations); + var field_nodes = try ArrayList(Node.Index).initCapacity(context.allocator, count.fields); + var comptime_block_nodes = try ArrayList(Node.Index).initCapacity(context.allocator, count.comptime_blocks); + + for (container_nodes) |member_index| { + const member_node = unit.getNode(member_index); + const member_type = getContainerMemberType(member_node.id); + const array_list = switch (member_type) { + .comptime_block => &comptime_block_nodes, + .declaration => &declaration_nodes, + .field => &field_nodes, + }; + array_list.appendAssumeCapacity(member_index); + } + + if (count.declarations > 0) { + for (declaration_nodes.items) |declaration_node_index| { + const declaration_node = unit.getNode(declaration_node_index); + + switch (declaration_node.id) { + .constant_symbol_declaration, + .variable_symbol_declaration, + => { + const expected_identifier_token_index = Token.addInt(declaration_node.token, 1); + const identifier = unit.getExpectedTokenBytes(expected_identifier_token_index, .identifier); + logln(.compilation, .identifier, "Analyzing global declaration {s}", .{identifier}); + const identifier_hash = try unit.processIdentifier(context, identifier); + + const look_in_parent_scopes = true; + if (builder.current_scope.lookupDeclaration(identifier_hash, look_in_parent_scopes)) |lookup_result| { + _ = lookup_result; // autofix + std.debug.panic("Symbol {s} already on scope", .{identifier}); + } + + assert(declaration_node.right != .null); + const type_node_index = declaration_node.left; + const value_node_index = declaration_node.right; + const unresolved_initial_value = try unit.values.append(context.allocator, .{ + .unresolved = value_node_index, + }); + + const declaration_type_index: Type.Index = switch (type_node_index) { + .null => .null, + else => try unit.types.append(context.allocator, .{ + .unresolved = type_node_index, + }), + }; + + const declaration_token_debug_info = builder.getTokenDebugInfo(unit, declaration_node.token); + + const declaration = try unit.declarations.append(context.allocator, .{ + .scope = builder.current_scope, + .name = identifier_hash, + .type = declaration_type_index, + .value = unresolved_initial_value, + .mutability = switch (declaration_node.id) { + .constant_symbol_declaration => .@"const", + .variable_symbol_declaration => .@"var", + else => unreachable, + }, + .line = declaration_token_debug_info.line, + .column = declaration_token_debug_info.column, + }); + + try builder.current_scope.declarations.putNoClobber(context.allocator, identifier_hash, declaration); + }, + else => unreachable, + } + } + } + + if (count.fields > 0) { + switch (container_type) { + .@"enum" => { + const ty = unit.types.get(type_index); + const enum_type = unit.enums.get(ty.@"enum"); + const field_count = field_nodes.items.len; + try enum_type.fields.ensureTotalCapacity(context.allocator, field_count); + + if (enum_type.backing_type == .null) { + const bit_count = @bitSizeOf(@TypeOf(field_nodes.items.len)) - @clz(field_nodes.items.len); + const real_bit_count: u16 = if (bit_count <= 8) 8 else if (bit_count <= 16) 16 else if (bit_count <= 32) 32 else if (bit_count <= 64) 64 else unreachable; + const backing_type_index = try unit.types.append(context.allocator, .{ + .unresolved = undefined, + }); + + try unit.getIntegerType(context, .{ + .bit_count = real_bit_count, + .signedness = .unsigned, + }, backing_type_index); + + enum_type.backing_type = backing_type_index; + } + }, + else => |t| @panic(@tagName(t)), + } + + // // TODO: bit-sized integers + // const expect_type = ExpectType{ + // .type_index = switch (backing_type.invalid) { + // true => blk: { + // break :blk type_index; + // }, + // false => backing_type, + // }, + // }; + + for (field_nodes.items, 0..) |field_node_index, index| { + const field_node = unit.getNode(field_node_index); + switch (container_type) { + .@"enum" => { + assert(field_node.id == .@"enum_field"); + const ty = unit.types.get(type_index); + const enum_type = unit.enums.get(ty.@"enum"); + + const identifier = unit.getExpectedTokenBytes(field_node.token, .identifier); + const hash = try unit.processIdentifier(context, identifier); + + const enum_value: usize = switch (field_node.left) { + .null => index, + else => b: { + const enum_value_index = try builder.resolveValueAllocate(unit, context, Type.Expect.none, field_node.left); + const comptime_enum_value_index = unit.evaluateAtComptime(enum_value_index); + if (comptime_enum_value_index == .null) @panic("Enum value must be known at compile time"); + const enum_value = unit.values.get(comptime_enum_value_index); + + break :b switch (enum_value.*) { + .integer => |integer| integer.value, + else => |t| @panic(@tagName(t)), + }; + }, + }; + const enum_field_index = try unit.enum_fields.append(context.allocator, .{ + .name = hash, + .value = enum_value, + .parent = type_index, + }); + enum_type.fields.appendAssumeCapacity(enum_field_index); + }, + else => |t| @panic(@tagName(t)), + } + } + } + + if (count.comptime_blocks > 0) { + const emit_ir = builder.emit_ir; + builder.emit_ir = false; + defer builder.emit_ir = emit_ir; + + for (comptime_block_nodes.items) |comptime_node_index| { + const comptime_node = unit.getNode(comptime_node_index); + assert(comptime_node.id == .@"comptime"); + + const comptime_block_value = try unit.values.append(context.allocator, .{ + .unresolved = comptime_node.left, + }); + try builder.resolveValue(unit, context, Type.Expect{ + .type = .void, + }, comptime_block_value); + } + } + } +}; + +pub const Enum = struct { + scope: Scope, + fields: ArrayList(Enum.Field.Index) = .{}, + backing_type: Type.Index, + + pub const Field = struct { + value: usize, + name: u32, + parent: Type.Index, + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; + }; + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; +}; + +pub const Unit = struct { + node_buffer: Node.List = .{}, + files: File.List = .{}, + values: Value.List = .{}, + types: Type.List = .{}, + structs: Struct.List = .{}, + enums: Enum.List = .{}, + enum_fields: Enum.Field.List = .{}, + function_definitions: Function.Definition.List = .{}, + blocks: Block.List = .{}, + declarations: Declaration.List = .{}, + assembly_instructions: InlineAssembly.Instruction.List = .{}, + function_prototypes: Function.Prototype.List = .{}, + intrinsics: Intrinsic.List = .{}, + unary_operations: UnaryOperation.List = .{}, + binary_operations: BinaryOperation.List = .{}, + assignments: Assignment.List = .{}, + returns: Return.List = .{}, + calls: Call.List = .{}, + inline_assembly: InlineAssembly.List = .{}, + instructions: Instruction.List = .{}, + basic_blocks: BasicBlock.List = .{}, + global_variables: GlobalVariable.List = .{}, + global_variable_map: AutoHashMap(Declaration.Index, GlobalVariable.Index) = .{}, + token_buffer: Token.Buffer = .{}, + node_lists: ArrayList(ArrayList(Node.Index)) = .{}, + file_token_offsets: AutoArrayHashMap(Token.Range, File.Index) = .{}, + file_map: StringArrayHashMap(File.Index) = .{}, + identifiers: StringKeyMap([]const u8) = .{}, + pointers: AutoHashMap(Type.Pointer, Type.Index) = .{}, + function_declaration_map: AutoHashMap(Function.Definition.Index, Declaration.Index) = .{}, + type_declaration_map: AutoHashMap(Type.Index, Declaration.Index) = .{}, + // TODO + value_to_instruction_map: AutoHashMap(Value.Index, Instruction.Index) = .{}, + constant_int_map: AutoHashMap(Value.Integer, Instruction.Index) = .{}, + struct_type_map: AutoHashMap(Struct.Index, Type.Index) = .{}, + scope: Scope = .{ + .file = .null, + .kind = .compilation_unit, + .line = 0, + .column = 0, + .level = 0, + .local = false, + }, + main_package: *Package = undefined, + descriptor: Descriptor, + + fn getReturnType(unit: *Unit, function_index: Function.Definition.Index) Type.Index{ + const function = unit.function_definitions.get(function_index); + const function_type = unit.types.get(function.type); + const function_prototype = unit.function_prototypes.get(function_type.function); + return function_prototype.return_type; + } + + fn typecheckSwitchEnums(unit: *Unit, context: *const Context, enum_type: Enum, switch_case_node_list: []const Node.Index) !TypeCheckSwitchEnums { + var result = TypeCheckSwitchEnums{ + .switch_case_groups = try ArrayList(ArrayList(Enum.Field.Index)).initCapacity(context.allocator, switch_case_node_list.len), + }; + + var existing_enums = ArrayList(Enum.Field.Index){}; + + for (switch_case_node_list, 0..) |switch_case_node_index, index| { + const switch_case_node = unit.getNode(switch_case_node_index); + + switch (switch_case_node.left) { + else => { + const switch_case_condition_node = unit.getNode(switch_case_node.left); + var switch_case_group = ArrayList(Enum.Field.Index){}; + + switch (switch_case_condition_node.id) { + .enum_literal => { + if (try unit.typeCheckEnumLiteral(context, Token.addInt(switch_case_condition_node.token, 1), enum_type)) |enum_field_index| { + for (existing_enums.items) |existing| { + if (enum_field_index == existing) { + // Duplicate case + unreachable; + } + } + + try switch_case_group.append(context.allocator, enum_field_index); + try existing_enums.append(context.allocator, enum_field_index); + } else { + unreachable; + } + }, + .node_list => { + const node_list = unit.getNodeListFromNode(switch_case_condition_node); + try switch_case_group.ensureTotalCapacity(context.allocator, node_list.len); + + for (node_list) |case_condition_node_index| { + const case_condition_node = unit.getNode(case_condition_node_index); + switch (case_condition_node.id) { + .enum_literal => { + if (try unit.typeCheckEnumLiteral(context, Token.addInt(case_condition_node.token, 1), enum_type)) |enum_field_index| { + for (existing_enums.items) |existing| { + if (enum_field_index == existing) { + // Duplicate case + unreachable; + } + } + + try existing_enums.append(context.allocator, enum_field_index); + switch_case_group.appendAssumeCapacity(enum_field_index); + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + } + } + }, + else => |t| @panic(@tagName(t)), + } + + result.switch_case_groups.appendAssumeCapacity(switch_case_group); + }, + .null => { + result.else_switch_case_group_index = index; + }, + } + } + + return result; + } + + fn typeCheckEnumLiteral(unit: *Unit, context: *const Context, token_index: Token.Index, enum_type: Enum) !?Enum.Field.Index { + const enum_name = unit.getExpectedTokenBytes(token_index, .identifier); + const enum_name_hash = try unit.processIdentifier(context, enum_name); + for (enum_type.fields.items) |enum_field_index| { + const enum_field = unit.enum_fields.get(enum_field_index); + if (enum_field.name == enum_name_hash) { + return enum_field_index; + } + } else { + return null; + } + } + + pub fn unwrapValueCopy(unit: *Unit, value_index: Value.Index) Value.Index{ + var i = value_index; + while (true) { + const value = unit.values.get(i); + switch (value.*) { + .copy => |new_value_index| { + i = new_value_index; + }, + else => return i, + } + } + } + + pub fn unwrapTypeCopy(unit: *Unit, type_index: Type.Index) Type.Index{ + var i = type_index; + while (true) { + const ty = unit.types.get(i); + switch (ty.*) { + .copy => |new_type_index| { + i = new_type_index; + }, + else => return i, + } + } + } + + fn getNode(unit: *Unit, node_index: Node.Index) *const Node { + const node = unit.node_buffer.get(node_index); + return node; + } + + fn getNodeList(unit: *Unit, node_index: Node.Index) []const Node.Index { + const node_list_node = unit.getNode(node_index); + const list = unit.getNodeListFromNode(node_list_node); + return list; + } + + fn getNodeListFromNode(unit: *Unit, node: *const Node) []const Node.Index { + assert(node.id == .node_list); + const list_index = node.left; + const node_list = unit.node_lists.items[Node.unwrap(list_index)]; + return node_list.items; + } + + // TODO: make this fast + fn findTokenFile(unit: *Unit, token_index: Token.Index) File.Index{ + const ti = @intFromEnum(token_index); + for (unit.file_token_offsets.keys(), unit.file_token_offsets.values()) |range, file_index| { + const i = @intFromEnum(range.start); + if (ti >= i and ti < i + range.count) { + return file_index; + } + } + + unreachable; + } + + fn getExpectedTokenBytes(unit: *Unit, token_index: Token.Index, expected_id: Token.Id) []const u8 { + const index = Token.unwrap(token_index); + const id = unit.token_buffer.tokens.items(.id)[index]; + logln(.compilation, .token_bytes, "trying to get {s} from token of id {s}", .{ @tagName(expected_id), @tagName(id) }); + if (id != expected_id) @panic("Unexpected token"); + const offset = unit.token_buffer.tokens.items(.offset)[index]; + const len = unit.token_buffer.tokens.items(.length)[index]; + const file_index = unit.findTokenFile(token_index); + const file = unit.files.get(file_index); + const bytes = file.source_code[offset..][0..len]; + return bytes; + } + + fn tokenStringLiteral(unit: *Unit, token_index: Token.Index) []const u8 { + const bytes = unit.getExpectedTokenBytes(token_index, .string_literal); + // Eat double quotes + const string_literal_bytes = bytes[1..][0 .. bytes.len - 2]; + return string_literal_bytes; + } + + fn evaluateAtComptime(unit: *Unit, original_value_index: Value.Index) Value.Index{ + const value_index = unit.unwrapValueCopy(original_value_index); + const value = unit.values.get(value_index); + return switch (value.*) { + .bool => value_index, + .enum_field => value_index, + .integer => value_index, + else => |t| @panic(@tagName(t)), + }; + } + + fn evaluateBooleanAtComptime(unit: *Unit, value_index: Value.Index) ?bool { + const comptime_condition = unit.evaluateAtComptime(value_index); + if (comptime_condition != .null) { + const value = unit.values.get(comptime_condition); + return value.bool; + } else { + return null; + } + } + + fn getPointerType(unit: *Unit, context: *const Context, pointer: Type.Pointer, type_index: Type.Index) !void { + if (unit.pointers.get(pointer)) |existing_type_index| { + unit.copyType(type_index, existing_type_index); + } else { + assert(unit.types.get(type_index).* == .unresolved); + unit.types.get(type_index).* = .{ + .pointer = pointer, + }; + try unit.pointers.putNoClobber(context.allocator, pointer, type_index); + assert(unit.types.get(type_index).* != .copy); + assert(unit.types.get(type_index).* != .unresolved); + } + } + + fn getIntegerType(unit: *Unit, context: *const Context, integer: Type.Integer, type_index: Type.Index) !void { + _ = context; // autofix + const existing_type_index: Type.Index = switch (integer.bit_count) { + 8 => switch (integer.signedness) { + .unsigned => .u8, + .signed => .s8, + }, + 16 => switch (integer.signedness) { + .unsigned => .u16, + .signed => .s16, + }, + 32 => switch (integer.signedness) { + .unsigned => .u32, + .signed => .s32, + }, + 64 => switch (integer.signedness) { + .unsigned => .u64, + .signed => .s64, + }, + else => unreachable, + }; + + unit.copyType(type_index, existing_type_index); + } + + fn copyType(unit: *Unit, destination: Type.Index, source: Type.Index) void { + if (destination != source) { + unit.types.get(destination).* = .{ + .copy = source, + }; + } else unreachable; + } + + fn copyValue(unit: *Unit, destination: Value.Index, source: Value.Index) void { + if (destination != source) { + unit.values.get(destination).* = .{ + .copy = source, + }; + } + } + + fn processIdentifier(unit: *Unit, context: *const Context, string: []const u8) !u32 { + const lookup_result = try unit.identifiers.getOrPut(context.allocator, string, string); + return lookup_result.key; + } + + pub fn getIdentifier(unit: *Unit, hash: u32) []const u8 { + return unit.identifiers.getValue(hash).?; + } + + pub fn analyze(unit: *Unit, context: *const Context, main_package: *Package) !void { + const builder = try context.allocator.create(Builder); + builder.* = .{ + .generate_debug_info = unit.descriptor.generate_debug_information, + .emit_ir = true, + .current_scope = &unit.scope, + }; + + inline for (@typeInfo(Type.Common).Enum.fields) |enum_field| { + const e = @field(Type.Common, enum_field.name); + const type_value = Type.Common.map.get(e); + _ = try unit.types.append(context.allocator, type_value); + } + + inline for (@typeInfo(Value.Common).Enum.fields) |enum_field| { + const e = @field(Value.Common, enum_field.name); + const value = Value.Common.map.get(e); + _ = try unit.values.append(context.allocator, value); + } + + + try builder.analyzePackage(unit, context, main_package); + } + + pub fn generateAbstractSyntaxTreeForFile(unit: *Unit, context: *const Context, file_index: File.Index) !void { + const file = unit.files.get(file_index); + const source_file = file.package.directory.handle.openFile(file.relative_path, .{}) catch |err| { + std.debug.panic("Can't find file {s} in directory {s} for error {s}", .{ file.relative_path, file.package.directory.path, @errorName(err) }); + }; + + const file_size = try source_file.getEndPos(); + var file_buffer = try context.allocator.alloc(u8, file_size); + + const read_byte_count = try source_file.readAll(file_buffer); + assert(read_byte_count == file_size); + source_file.close(); + + //TODO: adjust file maximum size + file.source_code = file_buffer[0..read_byte_count]; + file.status = .loaded_into_memory; + + assert(file.status == .loaded_into_memory); + file.lexer = try lexer.analyze(context.allocator, file.source_code, &unit.token_buffer); + assert(file.status == .loaded_into_memory); + file.status = .lexed; + try unit.file_token_offsets.putNoClobber(context.allocator, .{ + .start = file.lexer.offset, + .count = file.lexer.count, + }, file_index); + + logln(.parser, .file, "[START PARSING FILE #{} {s}]", .{ file_index, file.package.source_path }); + file.parser = try parser.analyze(context.allocator, file.lexer, file.source_code, file_index, &unit.token_buffer, &unit.node_buffer, &unit.node_lists); + logln(.parser, .file, "[END PARSING FILE #{} {s}]", .{ file_index, file.package.source_path }); + assert(file.status == .lexed); + file.status = .parsed; + } + + fn importPackage(unit: *Unit, context: *const Context, package: *Package) !ImportPackageResult { + const full_path = try std.fs.path.resolve(context.allocator, &.{ package.directory.path, package.source_path }); + logln(.compilation, .import, "Import full path: {s}\n", .{full_path}); + const import_file = try unit.getFile(context, full_path, package.source_path, package); + + return .{ + .file = import_file, + .is_package = true, + }; + } + + pub fn importFile(unit: *Unit, context:*const Context, current_file_index: File.Index, import_name: []const u8) !ImportPackageResult { + logln(.compilation, .import, "import: '{s}'\n", .{import_name}); + + if (equal(u8, import_name, "std")) { + return unit.importPackage(context, unit.main_package.dependencies.get("std").?); + } + + if (equal(u8, import_name, "builtin")) { + return unit.importPackage(context, unit.main_package.dependencies.get("builtin").?); + } + + if (equal(u8, import_name, "main")) { + return unit.importPackage(context, unit.main_package); + } + + const current_file = unit.files.get(current_file_index); + if (current_file.package.dependencies.get(import_name)) |package| { + return unit.importPackage(context, package); + } + + if (!std.mem.endsWith(u8, import_name, ".nat")) { + unreachable; + } + + const current_file_relative_path_to_package_directory = std.fs.path.dirname(current_file.relative_path) orelse ""; + const import_file_relative_path = try std.fs.path.join(context.allocator, &.{ current_file_relative_path_to_package_directory, import_name }); + const full_path = try std.fs.path.join(context.allocator, &.{ current_file.package.directory.path, import_file_relative_path }); + const file_relative_path = import_file_relative_path; + const package = current_file.package; + const import_file = try unit.getFile(context, full_path, file_relative_path, package); + _ = @intFromPtr(unit.files.get(import_file.index).package); + + // try unit.files.get(import_file.index).file_references.append(context.allocator, current_file); + + const result = ImportPackageResult{ + .file = import_file, + .is_package = false, + }; + + return result; + } + + fn getFile(unit: *Unit, context: *const Context, full_path: []const u8, relative_path: []const u8, package: *Package) !ImportFileResult { + const path_lookup = try unit.file_map.getOrPut(context.allocator, full_path); + const index = switch (path_lookup.found_existing) { + true => path_lookup.value_ptr.*, + false => blk: { + const file_index = try unit.files.append(context.allocator, File{ + .relative_path = relative_path, + .package = package, + .scope = .{ + .file = .null, + .kind = .file, + .line = 0, + .column = 0, + .local = false, + .level = 1, + }, + }); + logln(.compilation, .new_file, "Adding file #{}: {s}\n", .{ file_index, full_path }); + path_lookup.value_ptr.* = file_index; + // break :blk file; + break :blk file_index; + }, + }; + + return .{ + .index = index, + .is_new = !path_lookup.found_existing, + }; + } + + fn compile(unit: *Unit, context: *const Context) !void { + const builtin_file_name = "builtin.nat"; + var cache_dir = try context.build_directory.openDir("cache", .{}); + + // Write the builtin file to the filesystem + { + const builtin_file = try cache_dir.createFile(builtin_file_name, .{}); + try builtin_file.writer().print( + \\const builtin = #import("std").builtin; + \\const cpu = builtin.Cpu.{s}; + \\const os = builtin.Os.{s}; + \\const abi = builtin.Abi.{s}; + \\const link_libc = {}; + \\ + , .{ + @tagName(unit.descriptor.target.cpu.arch), + @tagName(unit.descriptor.target.os.tag), + @tagName(unit.descriptor.target.abi), + unit.descriptor.link_libc, + }); + builtin_file.close(); + } + + unit.main_package = blk: { + const result = try context.allocator.create(Package); + const main_package_absolute_directory_path = b: { + const relative_path = if (std.fs.path.dirname(unit.descriptor.main_package_path)) |dirname| dirname else "."; + break :b try context.pathFromCwd(relative_path); + }; + result.* = .{ + .directory = .{ + .handle = try std.fs.openDirAbsolute(main_package_absolute_directory_path, .{}), + .path = main_package_absolute_directory_path, + }, + .source_path = try context.allocator.dupe(u8, std.fs.path.basename(unit.descriptor.main_package_path)), + }; + break :blk result; + }; + const std_package_dir = "lib/std"; + + const package_descriptors = [2]struct { + name: []const u8, + directory_path: []const u8, + }{ + .{ + .name = "std", + .directory_path = try context.pathFromCompiler(std_package_dir), + }, + .{ + .name = "builtin", + .directory_path = blk: { + const result = try cache_dir.realpathAlloc(context.allocator, "."); + cache_dir.close(); + break :blk result; + }, + }, + }; + + var packages: [package_descriptors.len]*Package = undefined; + for (package_descriptors, &packages) |package_descriptor, *package_ptr| { + const package = try context.allocator.create(Package); + package.* = .{ + .directory = .{ + .path = package_descriptor.directory_path, + .handle = try std.fs.openDirAbsolute(package_descriptor.directory_path, .{}), + }, + .source_path = try std.mem.concat(context.allocator, u8, &.{ package_descriptor.name, ".nat" }), + }; + + try unit.main_package.addDependency(context.allocator, package_descriptor.name, package); + + package_ptr.* = package; + } + + assert(unit.main_package.dependencies.size == 2); + + if (!unit.descriptor.only_parse) { + _ = try unit.importPackage(context, unit.main_package.dependencies.get("std").?); + } else { + _ = try unit.importPackage(context, unit.main_package); + } + + for (unit.file_map.values()) |import| { + try unit.generateAbstractSyntaxTreeForFile(context, import); + } + + if (!unit.descriptor.only_parse) { + try unit.analyze(context, packages[0]); + + try llvm.codegen(unit, context); + } + } + + fn getConstantInt(unit: *Unit, context: *const Context, integer: Value.Integer) !Instruction.Index{ + if (unit.constant_int_map.get(integer)) |r| return r else { + const const_int = try unit.instructions.append(context.allocator, .{ + .constant_int = integer, + }); + try unit.constant_int_map.putNoClobber(context.allocator, integer, const_int); + return const_int; + } + } +}; + +pub const FixedKeyword = enum { + @"comptime", + @"const", + @"var", + void, + noreturn, + function, + @"while", + bool, + true, + false, + @"fn", + @"unreachable", + @"return", + ssize, + usize, + @"switch", + @"if", + @"else", + @"struct", + @"enum", + @"union", + null, + @"align", + @"for", + undefined, + @"break", +}; + +pub const Descriptor = struct { + main_package_path: []const u8, + executable_path: []const u8, + target: std.Target, + transpile_to_c: bool, + is_build: bool, + only_parse: bool, + link_libc: bool, + generate_debug_information: bool, + name: []const u8, +}; + +fn getContainerMemberType(member_id: Node.Id) MemberType { + return switch (member_id) { + .@"comptime" => .comptime_block, + .constant_symbol_declaration, + .variable_symbol_declaration, + => .declaration, + .enum_field, + .container_field, + => .field, + else => |t| @panic(@tagName(t)), + }; +} + +const MemberType = enum { + declaration, + field, + comptime_block, +}; + +// TODO: switch to packed struct when speed is important +pub const Token = struct { + line: u32, + offset: u32, + length: u32, + id: Token.Id, + + pub const Buffer = struct{ + tokens: std.MultiArrayList(Token) = .{}, + line_offsets: ArrayList(u32) = .{}, + + pub fn getOffset(buffer: *const Buffer) Token.Index { + return @enumFromInt(buffer.tokens.len); + } + + pub fn getLineOffset(buffer: *const Buffer) u32 { + return @intCast(buffer.line_offsets.items.len); + } + }; + + pub const Range = struct{ + start: Token.Index, + count: u32, + }; + + pub const Id = enum { + keyword_unsigned_integer, + keyword_signed_integer, + identifier, + number_literal, + string_literal, + character_literal, + intrinsic, + discard, + // Operators + operator_left_parenthesis, + operator_right_parenthesis, + operator_left_brace, + operator_right_brace, + operator_left_bracket, + operator_right_bracket, + operator_semicolon, + operator_at, + operator_comma, + operator_dot, + operator_colon, + operator_bang, + operator_optional, + operator_dollar, + operator_switch_case, + // Binary + operator_assign, + operator_add, + operator_minus, + operator_asterisk, + operator_div, + operator_mod, + operator_bar, + operator_ampersand, + operator_xor, + operator_shift_left, + operator_shift_right, + operator_add_assign, + operator_sub_assign, + operator_mul_assign, + operator_div_assign, + operator_mod_assign, + operator_or_assign, + operator_and_assign, + operator_xor_assign, + operator_shift_left_assign, + operator_shift_right_assign, + operator_compare_equal, + operator_compare_not_equal, + operator_compare_less, + operator_compare_less_equal, + operator_compare_greater, + operator_compare_greater_equal, + // Fixed keywords + fixed_keyword_function, + fixed_keyword_const, + fixed_keyword_var, + fixed_keyword_void, + fixed_keyword_noreturn, + fixed_keyword_comptime, + fixed_keyword_while, + fixed_keyword_bool, + fixed_keyword_true, + fixed_keyword_false, + fixed_keyword_fn, + fixed_keyword_unreachable, + fixed_keyword_return, + fixed_keyword_ssize, + fixed_keyword_usize, + fixed_keyword_switch, + fixed_keyword_if, + fixed_keyword_else, + fixed_keyword_struct, + fixed_keyword_enum, + fixed_keyword_union, + fixed_keyword_null, + fixed_keyword_align, + fixed_keyword_for, + fixed_keyword_undefined, + fixed_keyword_break, + unused0, + unused1, + unused2, + unused3, + unused4, + unused5, + unused6, + unused7, + unused8, + unused9, + unused20, + unused21, + unused22, + unused23, + unused24, + unused25, + unused26, + unused27, + unused28, + unused29, + unused30, + unused31, + unused32, + unused33, + unused34, + unused35, + unused36, + unused37, + unused38, + unused39, + unused40, + unused41, + unused42, + unused43, + unused44, + unused45, + unused46, + unused47, + unused48, + unused49, + unused50, + unused51, + unused52, + unused53, + unused54, + unused55, + unused56, + unused57, + unused58, + unused59, + unused60, + unused61, + unused62, + unused63, + unused64, + unused65, + unused66, + unused67, + unused68, + unused69, + + comptime { + assert(@bitSizeOf(@This()) == @bitSizeOf(u8)); + } + }; + + pub usingnamespace data_structures.getIndexForType(@This(), enum{}); +}; diff --git a/bootstrap/backend/c_transpiler.zig b/bootstrap/backend/c_transpiler.zig deleted file mode 100644 index b1a6b57..0000000 --- a/bootstrap/backend/c_transpiler.zig +++ /dev/null @@ -1,2055 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; -const equal = std.mem.eql; - -const Compilation = @import("../Compilation.zig"); -const logln = Compilation.logln; -const Module = Compilation.Module; -const Type = Compilation.Type; -const Value = Compilation.Value; -const data_structures = @import("../data_structures.zig"); -const ArrayList = data_structures.ArrayList; -const AutoArrayHashMap = data_structures.AutoArrayHashMap; -const StringArrayHashMap = data_structures.StringArrayHashMap; - -pub const Logger = enum { - g, - - pub var bitset = std.EnumSet(Logger).initMany(&.{ - .g, - }); -}; - -const margin_width = 4; - -const TypeSet = struct { - map: AutoArrayHashMap(Type.Index, TypeValue) = .{}, - - const TypeValue = struct { - underscore: []const u8, - space: []const u8, - }; - - fn get(type_set: *const TypeSet, type_index: Type.Index, character: u8) ?[]const u8 { - if (type_set.map.get(type_index)) |value| { - const result = switch (character) { - '_' => value.underscore, - ' ' => value.space, - else => unreachable, - }; - return result; - } else { - return null; - } - } - - fn put(type_set: *TypeSet, allocator: Allocator, type_index: Type.Index, value: TypeValue) !void { - try type_set.map.putNoClobber(allocator, type_index, value); - } - - fn getOrPutValue(type_set: *TypeSet, allocator: Allocator, type_index: Type.Index, value: TypeValue) !TypeValue { - const gop = try type_set.map.getOrPutValue(allocator, type_index, value); - return gop.value_ptr.*; - } -}; - -pub const TranslationUnit = struct { - string_literals: ArrayList(u8) = .{}, - primitive_type_declarations: ArrayList(u8) = .{}, - type_forward_declarations: ArrayList(u8) = .{}, - macros: ArrayList(u8) = .{}, - type_declarations: ArrayList(u8) = .{}, - function_declarations: ArrayList(u8) = .{}, - global_variable_declarations: ArrayList(u8) = .{}, - function_definitions: ArrayList(u8) = .{}, - syscall_bitset: SyscallBitset = SyscallBitset.initEmpty(), - function_definition_set: AutoArrayHashMap(Compilation.Function.Index, []const u8) = .{}, - function_declaration_set: AutoArrayHashMap(Compilation.Function.Index, []const u8) = .{}, - macro_set: std.EnumSet(Macro) = std.EnumSet(Macro).initEmpty(), - struct_type_set: TypeSet = .{}, - optional_type_set: TypeSet = .{}, - slice_type_set: TypeSet = .{}, - array_type_set: TypeSet = .{}, - enum_type_set: TypeSet = .{}, - pointer_type_set: TypeSet = .{}, - declaration_set: AutoArrayHashMap(Compilation.Declaration.Index, []const u8) = .{}, - - const SyscallBitset = std.StaticBitSet(7); - - fn create(module: *Module, allocator: Allocator) !*TranslationUnit { - var unit = try allocator.create(TranslationUnit); - unit.* = .{}; - try unit.primitive_type_declarations.appendSlice(allocator, - \\typedef unsigned char u8; - \\typedef unsigned short u16; - \\typedef unsigned int u32; - \\typedef unsigned long u64; - \\typedef u64 usize; - \\static_assert(sizeof(u8) == 1); - \\static_assert(sizeof(u16) == 2); - \\static_assert(sizeof(u32) == 4); - \\static_assert(sizeof(u64) == 8); - \\typedef signed char s8; - \\typedef signed short s16; - \\typedef signed int s32; - \\typedef signed long s64; - \\typedef s64 ssize; - \\static_assert(sizeof(s8) == 1); - \\static_assert(sizeof(s16) == 2); - \\static_assert(sizeof(s32) == 4); - \\static_assert(sizeof(s64) == 8); - \\ - \\ - ); - - { - var function_declarations = module.types.function_declarations.iterator(); - while (function_declarations.nextIndex()) |function_declaration_index| { - _ = try unit.writeFunctionDeclaration(module, allocator, function_declaration_index); - } - } - - { - var function_definitions = module.types.function_definitions.iterator(); - while (function_definitions.nextIndex()) |function_definition_index| { - _ = try unit.writeFunctionDefinition(module, allocator, function_definition_index); - } - } - - return unit; - } - - fn writeFunctionDeclaration(unit: *TranslationUnit, module: *Module, allocator: Allocator, function_declaration_index: Compilation.Function.Index) ![]const u8 { - if (unit.function_declaration_set.getIndex(function_declaration_index)) |index| { - return unit.function_declaration_set.values()[index]; - } else { - const function_name = try unit.renderFunctionDeclarationName(module, allocator, function_declaration_index); - try unit.writeFunctionHeader(module, &unit.function_declarations, allocator, module.types.function_declarations.get(function_declaration_index), function_name); - try unit.function_declaration_set.putNoClobber(allocator, function_declaration_index, function_name); - - try unit.function_declarations.appendSlice(allocator, ";\n\n"); - - return function_name; - } - } - - fn writeFunctionDefinition(unit: *TranslationUnit, module: *Module, allocator: Allocator, function_definition_index: Compilation.Function.Index) ![]const u8 { - if (unit.function_definition_set.getIndex(function_definition_index)) |index| { - return unit.function_definition_set.values()[index]; - } else { - const function_definition = module.types.function_definitions.get(function_definition_index); - const function_prototype_type = function_definition.prototype; - const function_prototype = module.types.function_prototypes.get(module.types.array.get(function_prototype_type).function); - - const function_name = try unit.renderFunctionDefinitionName(module, allocator, function_definition_index); - try unit.writeFunctionHeader(module, &unit.function_declarations, allocator, function_definition, function_name); - try unit.function_definition_set.putNoClobber(allocator, function_definition_index, function_name); - - try unit.writeFunctionHeader(module, &unit.function_definitions, allocator, function_definition, function_name); - try unit.function_declarations.appendSlice(allocator, ";\n\n"); - - try unit.function_definitions.append(allocator, ' '); - try unit.writeBlock(module, &unit.function_definitions, allocator, function_definition.body, function_prototype.return_type, 0); - try unit.function_definitions.append(allocator, '\n'); - - return function_name; - } - } - - fn writeDeclaration(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, declaration_index: Compilation.Declaration.Index, indentation: usize, separation_character: u8) !void { - const declaration = module.values.declarations.get(declaration_index); - const mangle = false; - const name = try unit.renderDeclarationName(module, allocator, declaration_index, mangle); - // if (equal(u8, name, "pointer")) { - // @breakpoint(); - // } - - if (declaration.mutability == .@"const") { - switch (module.types.array.get(declaration.getType()).*) { - .optional => |optional| switch (module.types.array.get(optional.element_type).*) { - .pointer => {}, - else => { - try list.appendSlice(allocator, "const "); - }, - }, - .pointer => {}, - .integer, - .@"struct", - .slice, - .bool, - .array, - => { - try list.appendSlice(allocator, "const "); - }, - else => |t| @panic(@tagName(t)), - //else => try list.appendSlice(allocator, "const "), - } - } - - try unit.writeType(module, list, allocator, declaration.getType(), separation_character); - - try list.append(allocator, ' '); - - try list.appendSlice(allocator, name); - - try list.appendSlice(allocator, " = "); - - try unit.writeValue(module, list, allocator, Type.Index.invalid, indentation, .{ - .value_index = declaration.init_value, - .type_index = declaration.getType(), - }); - } - - fn writeAssignment(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, assignment_index: Compilation.Assignment.Index, function_return_type: Type.Index, indentation: usize) !void { - const assignment = module.values.assignments.get(assignment_index); - const left_type = module.values.array.get(assignment.destination).getType(module); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = assignment.destination, - .type_index = left_type, - }); - try list.append(allocator, ' '); - switch (assignment.operation) { - .none => {}, - .add => try list.append(allocator, '+'), - } - try list.appendSlice(allocator, "= "); - - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = assignment.source, - .type_index = left_type, - }); - } - - fn writeReturn(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, return_index: Compilation.Return.Index, function_return_type: Type.Index, indentation: usize) !void { - const return_expr = module.values.returns.get(return_index); - try list.appendSlice(allocator, "return "); - const return_value = module.values.array.get(return_expr.value); - const return_value_type_index = return_value.getType(module); - // _ = return_value_type_index; - switch (module.types.array.get(function_return_type).*) { - .optional => switch (module.types.array.get(return_value_type_index).*) { - .optional => try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = return_expr.value, - .type_index = function_return_type, - }), - else => { - try list.append(allocator, '('); - try unit.writeType(module, list, allocator, function_return_type, '_'); - try list.appendSlice(allocator, ") {\n"); - - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, ".value = "); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = return_expr.value, - .type_index = return_value_type_index, - }); - try list.appendSlice(allocator, ",\n"); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, ".is_null = false,\n"); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.append(allocator, '}'); - }, - }, - else => try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = return_expr.value, - .type_index = function_return_type, - }), - } - } - - fn writeBlock(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, block_index: Compilation.Block.Index, function_return_type: Type.Index, old_indentation: usize) !void { - try list.appendSlice(allocator, "{\n"); - const block = module.values.blocks.get(block_index); - - const indentation = old_indentation + 1; - - for (block.statements.items) |statement_index| { - try list.appendNTimes(allocator, ' ', indentation * margin_width); - - const statement = module.values.array.get(statement_index); - switch (statement.*) { - .declaration => |declaration_index| { - try unit.writeDeclaration(module, list, allocator, declaration_index, indentation, ' '); - try list.append(allocator, ';'); - }, - .assign => |assignment_index| { - try unit.writeAssignment(module, list, allocator, assignment_index, function_return_type, indentation); - try list.append(allocator, ';'); - }, - .@"return" => |return_index| { - try unit.writeReturn(module, list, allocator, return_index, function_return_type, indentation); - try list.append(allocator, ';'); - }, - .@"unreachable" => { - try writeUnreachable(list, allocator); - try list.append(allocator, ';'); - }, - .call => |call_index| { - try unit.writeCall(module, list, allocator, call_index, function_return_type, indentation); - try list.append(allocator, ';'); - }, - .branch => |branch_index| { - if (!try unit.writeBranch(module, list, allocator, branch_index, function_return_type, indentation)) { - continue; - } - }, - .assembly_block => |assembly_block_index| { - try unit.writeAssembly(module, list, allocator, assembly_block_index, indentation); - try list.append(allocator, ';'); - }, - .loop => |loop_index| { - const loop = module.values.loops.get(loop_index); - try list.appendSlice(allocator, "for ("); - if (!loop.pre.invalid) { - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = loop.pre, - .type_index = Type.Index.invalid, - }); - } - try list.appendSlice(allocator, "; "); - - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = loop.condition, - .type_index = Type.boolean, - }); - - try list.appendSlice(allocator, "; "); - - if (!loop.post.invalid) { - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = loop.post, - .type_index = Type.Index.invalid, - }); - } - - try list.appendSlice(allocator, ") "); - - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = loop.body, - .type_index = Type.Index.invalid, - }); - }, - .block => |new_block_index| { - try unit.writeBlock(module, list, allocator, new_block_index, function_return_type, indentation); - }, - .switch_expression => |switch_index| { - const switch_expression = module.values.switches.get(switch_index); - try list.appendSlice(allocator, "switch ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = switch_expression.value, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ") {\n"); - - for (switch_expression.groups.items) |switch_case_group| { - for (switch_case_group.conditions.items) |condition_value_index| { - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.appendSlice(allocator, "case "); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = condition_value_index, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ":\n"); - } - - _ = list.pop(); - - switch (module.values.array.get(switch_case_group.expression).*) { - .block => { - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = switch_case_group.expression, - .type_index = Type.Index.invalid, - }); - }, - else => { - try list.appendSlice(allocator, " {\n"); - - try unit.writeValue(module, list, allocator, function_return_type, indentation + 2, .{ - .value_index = switch_case_group.expression, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ";\n"); - - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, "}\n"); - }, - } - - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, " break;\n"); - } - - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, "};\n"); - }, - .intrinsic => |intrinsic_index| { - try unit.writeIntrinsic(module, list, allocator, intrinsic_index, function_return_type, indentation, .statement); - try list.append(allocator, ';'); - }, - .@"break" => try list.appendSlice(allocator, "break;"), - else => |t| @panic(@tagName(t)), - } - - try list.append(allocator, '\n'); - } - - try list.appendNTimes(allocator, ' ', old_indentation * margin_width); - try list.appendSlice(allocator, "}\n"); - } - - const FunctionHeaderType = enum { - pointer, - header, - }; - - fn renderTypeName(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Type.Index) ![]const u8 { - const declaration_index = module.map.types.get(type_index).?; - const mangle = true; - const result = try unit.renderDeclarationName(module, allocator, declaration_index, mangle); - return result; - } - - fn renderFunctionDefinitionName(unit: *TranslationUnit, module: *Module, allocator: Allocator, function_definition_index: Compilation.Function.Index) ![]const u8 { - const function_definition = module.types.function_definitions.get(function_definition_index); - const function_prototype_type = module.types.array.get(function_definition.prototype); - const function_prototype_index = function_prototype_type.function; - const function_prototype = module.types.function_prototypes.get(function_prototype_index); - const mangle = !(function_prototype.attributes.@"export" or function_prototype.attributes.@"extern"); - const function_declaration_index = module.map.function_definitions.get(function_definition_index).?; - const name = try unit.renderDeclarationName(module, allocator, function_declaration_index, mangle); - return name; - } - - fn renderFunctionDeclarationName(unit: *TranslationUnit, module: *Module, allocator: Allocator, function_declaration_index: Compilation.Function.Index) ![]const u8 { - const function_declaration = module.types.function_declarations.get(function_declaration_index); - const function_prototype_type = module.types.array.get(function_declaration.prototype); - const function_prototype_index = function_prototype_type.function; - const function_prototype = module.types.function_prototypes.get(function_prototype_index); - const mangle = !(function_prototype.attributes.@"export" or function_prototype.attributes.@"extern"); - const declaration_index = module.map.function_declarations.get(function_declaration_index).?; - const name = try unit.renderDeclarationName(module, allocator, declaration_index, mangle); - return name; - } - - fn renderDeclarationName(unit: *TranslationUnit, module: *Module, allocator: Allocator, declaration_index: Compilation.Declaration.Index, mangle: bool) anyerror![]const u8 { - if (unit.declaration_set.getIndex(declaration_index)) |index| { - return unit.declaration_set.values()[index]; - } else { - const declaration = module.values.declarations.get(declaration_index); - const base_declaration_name = blk: { - const name = module.getName(declaration.name).?; - if (mangle and declaration.scope_type == .global and equal(u8, name, "main")) { - break :blk "user_entry_point"; - } else { - break :blk name; - } - }; - var list = ArrayList(u8){}; - - try list.insertSlice(allocator, 0, base_declaration_name); - - if (mangle) { - var scope_index = declaration.scope; - - var iterations: usize = 0; - switch (declaration.scope_type) { - .global => { - while (!scope_index.invalid) { - const scope = module.values.scopes.get(scope_index); - - if (module.map.types.get(scope.type)) |type_declaration| { - const scope_type_declaration = module.values.declarations.get(type_declaration); - const scope_type_declaration_name = module.getName(scope_type_declaration.name).?; - try list.insert(allocator, 0, '_'); - try list.insertSlice(allocator, 0, scope_type_declaration_name); - - scope_index = scope.parent; - } else { - break; - } - - iterations += 1; - } - }, - .local => {}, - } - } - - // TODO: enhance declaration name rendering with file scope name - // const scope = declaration.scope; - try unit.declaration_set.putNoClobber(allocator, declaration_index, list.items); - - switch (declaration.scope_type) { - .global => switch (module.types.array.get(declaration.getType()).*) { - .function, - .type, - => {}, - .integer, - .@"struct", - .pointer, - => { - const new_separation_character: u8 = switch (module.types.array.get(declaration.getType()).*) { - .integer, - .@"struct", - => '_', - .pointer => ' ', - else => unreachable, - }; - try unit.writeDeclaration(module, &unit.global_variable_declarations, allocator, declaration_index, 0, new_separation_character); - try unit.global_variable_declarations.append(allocator, ';'); - try unit.global_variable_declarations.appendNTimes(allocator, '\n', 2); - }, - else => |t| @panic(@tagName(t)), - }, - .local => {}, - } - - return list.items; - } - } - - fn writeFunctionPrototype(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, function_prototype_index: Compilation.Function.Prototype.Index, name: []const u8) !void { - const is_main = equal(u8, "main", name); - if (is_main) { - try list.appendSlice(allocator, "int main(int argc, char** argv, char** envp)"); - } else { - const function_prototype = module.types.function_prototypes.get(function_prototype_index); - - if (function_prototype.attributes.@"extern") { - try list.appendSlice(allocator, "extern "); - } - - switch (function_prototype.attributes.calling_convention) { - .system_v => {}, - .naked => try list.appendSlice(allocator, "[[gnu::naked]] "), - } - - try unit.writeType(module, list, allocator, function_prototype.return_type, ' '); - - try list.append(allocator, ' '); - - try list.appendSlice(allocator, name); - - try list.append(allocator, '('); - - if (function_prototype.arguments.items.len > 0) { - for (function_prototype.arguments.items, 0..) |argument_index, index| { - _ = index; - - const arg_declaration = module.values.declarations.get(argument_index); - if (is_main) {} else { - try unit.writeType(module, list, allocator, arg_declaration.getType(), ' '); - } - try list.append(allocator, ' '); - const arg_name = module.getName(arg_declaration.name).?; - try list.appendSlice(allocator, arg_name); - try list.appendSlice(allocator, ", "); - } - _ = list.pop(); - _ = list.pop(); - } - - try list.append(allocator, ')'); - } - } - - fn writeFunctionHeader(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, function: *const Compilation.Function, name: []const u8) !void { - const function_prototype_type = module.types.array.get(function.prototype); - const function_prototype_index = function_prototype_type.function; - - try unit.writeFunctionPrototype(module, list, allocator, function_prototype_index, name); - } - - fn writeType(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, type_index: Type.Index, separation_character: u8) anyerror!void { - const sema_type = module.types.array.get(type_index); - - switch (sema_type.*) { - .void => try list.appendSlice(allocator, "void"), - .noreturn => try list.appendSlice(allocator, "[[noreturn]] void"), - .bool => try list.appendSlice(allocator, "bool"), - .integer => |integer| { - if (type_index.eq(Type.usize)) { - try list.appendSlice(allocator, "usize"); - } else if (type_index.eq(Type.ssize)) { - try list.appendSlice(allocator, "ssize"); - } else { - try list.append(allocator, switch (integer.signedness) { - .signed => 's', - .unsigned => 'u', - }); - try list.writer(allocator).print("{}", .{integer.bit_count}); - } - }, - .pointer => { - const name = try unit.cachePointerType(module, allocator, type_index, separation_character); - try list.appendSlice(allocator, name); - }, - .@"struct" => { - const name = try unit.cacheStructType(module, allocator, type_index, separation_character); - try list.appendSlice(allocator, name); - }, - .optional => { - const name = try unit.cacheOptionalType(module, allocator, type_index, separation_character); - try list.appendSlice(allocator, name); - }, - .slice => { - const name = try unit.cacheSliceType(module, allocator, type_index, separation_character); - try list.appendSlice(allocator, name); - }, - .array => { - const name = try unit.cacheArrayType(module, allocator, type_index, separation_character); - try list.appendSlice(allocator, name); - }, - .any => @panic("Internal compiler error: 'any' made it to the backend"), - .@"enum" => { - const name = try unit.cacheEnumType(module, allocator, type_index, separation_character); - try list.appendSlice(allocator, name); - }, - else => |t| @panic(@tagName(t)), - } - } - - fn writeCDeclaration(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, name: []const u8, type_index: Type.Index, separation_character: u8) !void { - const declaration_type = module.types.array.get(type_index); - switch (declaration_type.*) { - .pointer => |pointer| { - switch (module.types.array.get(pointer.element_type).*) { - .function => |function| return try unit.writeFunctionPrototype(module, list, allocator, function, try std.mem.concat(allocator, u8, &.{ "(*", name, ")" })), - .optional => {}, - else => |t| @panic(@tagName(t)), - } - }, - else => {}, - } - - try unit.writeType(module, list, allocator, type_index, separation_character); - try list.append(allocator, ' '); - try list.appendSlice(allocator, name); - } - - fn writeAssembly(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, assembly_block_index: Compilation.Assembly.Block.Index, indentation: usize) !void { - const assembly_block = module.values.assembly_blocks.get(assembly_block_index); - - try list.appendSlice(allocator, "__asm__ __volatile__(\n"); - - var operand_values = ArrayList(Value.Index){}; - - for (assembly_block.instructions) |instruction_index| { - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.append(allocator, '"'); - - const generic_instruction = module.values.assembly_instructions.get(instruction_index); - - switch (module.descriptor.target.cpu.arch) { - .x86_64 => { - const architecture = @field(Compilation.Assembly, "x86_64"); - - const instruction: architecture.Instruction = @enumFromInt(generic_instruction.id); - const instruction_name = switch (instruction) { - .@"and" => "andq", - .mov => "movq", - .xor => @tagName(instruction), - .call => "callq", - }; - try list.appendSlice(allocator, instruction_name); - - assert(generic_instruction.operands.len <= 2); - - if (generic_instruction.operands.len > 0) { - try list.append(allocator, ' '); - - var skip = false; - - var operand_iterator = std.mem.reverseIterator(generic_instruction.operands); - while (operand_iterator.next()) |operand| { - switch (operand) { - .register => |generic_register| { - const register: architecture.Register = @enumFromInt(generic_register); - try list.append(allocator, '%'); - try list.appendSlice(allocator, @tagName(register)); - }, - .number_literal => |number_literal| { - try list.writer(allocator).print("$0x{x}", .{number_literal}); - }, - .value_index => |value_index| { - const operand_value = module.values.array.get(value_index); - - switch (operand_value.*) { - .unary_operation => |unary_operation_index| { - const unary_operation = module.values.unary_operations.get(unary_operation_index); - switch (unary_operation.id) { - .address_of => try list.writer(allocator).print("%{}", .{operand_values.items.len}), - else => |t| @panic(@tagName(t)), - } - }, - .function_definition => { - try unit.writeValue(module, list, allocator, Type.Index.invalid, indentation + 1, .{ - .value_index = value_index, - .type_index = Type.Index.invalid, - }); - skip = true; - }, - else => |t| @panic(@tagName(t)), - } - - if (!skip) { - try operand_values.append(allocator, value_index); - } - }, - } - - try list.appendSlice(allocator, ", "); - } - - _ = list.pop(); - _ = list.pop(); - } - }, - else => unreachable, - } - - try list.appendSlice(allocator, "\\n\\t\"\n"); - } - - if (operand_values.items.len > 0) { - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, ":\n"); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, ": "); - - for (operand_values.items) |operand_value_index| { - try list.appendSlice(allocator, "\"x\"("); - - try unit.writeValue(module, list, allocator, Type.Index.invalid, indentation + 1, .{ - .value_index = operand_value_index, - .type_index = Type.Index.invalid, - }); - - try list.appendSlice(allocator, "), "); - } - - _ = list.pop(); - _ = list.pop(); - - try list.append(allocator, '\n'); - } - - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.append(allocator, ')'); - } - - fn cacheStructType(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Type.Index, separation_character: u8) ![]const u8 { - const t = module.types.array.get(type_index); - assert(t.* == .@"struct"); - const result = if (unit.struct_type_set.get(type_index, separation_character)) |r| r else blk: { - const type_name = try unit.renderTypeName(module, allocator, type_index); - // if (std.mem.containsAtLeast(u8, type_name, 1, "Process")) @breakpoint(); - logln(.c, .g, "Registering struct {s}: #{}", .{ type_name, type_index.uniqueInteger() }); - try unit.struct_type_set.put(allocator, type_index, .{ - .underscore = type_name, - .space = type_name, - }); - - try unit.forwardDeclareContainerType(allocator, .@"struct", type_name); - - const struct_type = module.types.structs.get(t.@"struct"); - // Actually declare the struct - { - var list = ArrayList(u8){}; - try list.appendSlice(allocator, "typedef struct "); - try list.appendSlice(allocator, type_name); - try list.appendSlice(allocator, " {\n"); - - for (struct_type.fields.items) |struct_field_index| { - try list.appendNTimes(allocator, ' ', margin_width); - - const struct_field = module.types.container_fields.get(struct_field_index); - const struct_field_name = module.getName(struct_field.name).?; - - switch (struct_type.backing_type.invalid) { - false => { - try unit.writeType(module, &list, allocator, struct_type.backing_type, '_'); - try list.append(allocator, ' '); - try list.appendSlice(allocator, struct_field_name); - try list.appendSlice(allocator, " : "); - try list.writer(allocator).print("{}", .{module.types.array.get(struct_field.type).getBitSize()}); - }, - true => try unit.writeCDeclaration(module, &list, allocator, struct_field_name, struct_field.type, ' '), - } - - try list.appendSlice(allocator, ";\n"); - } - - try list.appendSlice(allocator, "} "); - try list.appendSlice(allocator, type_name); - try list.appendSlice(allocator, ";\n\n"); - - try unit.type_declarations.appendSlice(allocator, list.items); - } - - break :blk type_name; - }; - - return result; - } - - fn forwardDeclareContainerType(unit: *TranslationUnit, allocator: Allocator, container_type: Compilation.ContainerType, type_name: []const u8) !void { - try unit.type_forward_declarations.appendSlice(allocator, "typedef "); - try unit.type_forward_declarations.appendSlice(allocator, @tagName(container_type)); - try unit.type_forward_declarations.append(allocator, ' '); - try unit.type_forward_declarations.appendSlice(allocator, type_name); - try unit.type_forward_declarations.append(allocator, ' '); - try unit.type_forward_declarations.appendSlice(allocator, type_name); - try unit.type_forward_declarations.appendSlice(allocator, ";\n"); - } - - fn cacheEnumType(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Type.Index, separation_character: u8) ![]const u8 { - const result = if (unit.array_type_set.get(type_index, separation_character)) |r| r else blk: { - const type_name = try unit.renderTypeName(module, allocator, type_index); - logln(.c, .g, "Registering enum {s}: #{}", .{ type_name, type_index.uniqueInteger() }); - try unit.array_type_set.put(allocator, type_index, .{ - .underscore = type_name, - .space = type_name, - }); - - try unit.forwardDeclareContainerType(allocator, .@"enum", type_name); - - const t = module.types.array.get(type_index); - const enum_type = module.types.enums.get(t.@"enum"); - - var list = ArrayList(u8){}; - - try list.appendSlice(allocator, "typedef enum "); - try list.appendSlice(allocator, type_name); - try list.appendSlice(allocator, " {\n"); - - for (enum_type.fields.items) |enum_field_index| { - try list.appendNTimes(allocator, ' ', margin_width); - - const enum_field = module.types.enum_fields.get(enum_field_index); - const enum_field_name = module.getName(enum_field.name).?; - try list.appendSlice(allocator, type_name); - try list.append(allocator, '_'); - try list.appendSlice(allocator, enum_field_name); - - if (!enum_field.value.invalid) { - try list.appendSlice(allocator, " = "); - - try unit.writeValue(module, &list, allocator, Type.Index.invalid, 0, .{ - .value_index = enum_field.value, - .type_index = Type.usize, - }); - } - - try list.appendSlice(allocator, ",\n"); - } - - try list.appendSlice(allocator, "} "); - try list.appendSlice(allocator, type_name); - try list.appendSlice(allocator, ";\n\n"); - - try unit.type_declarations.appendSlice(allocator, list.items); - - break :blk type_name; - }; - - return result; - } - - fn cacheOptionalType(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Type.Index, separation_character: u8) ![]const u8 { - const optional_type = module.types.array.get(type_index); - assert(optional_type.* == .optional); - const optional = optional_type.optional; - - const result = if (unit.optional_type_set.get(optional.element_type, separation_character)) |r| r else { - const optional_element_type = module.types.array.get(optional.element_type); - - switch (optional_element_type.*) { - .pointer => { - const name = try unit.cachePointerType(module, allocator, optional.element_type, separation_character); - return name; - }, - else => { - var type_name = ArrayList(u8){}; - try type_name.appendSlice(allocator, "Optional_"); - try unit.writeType(module, &type_name, allocator, optional.element_type, '_'); - logln(.c, .g, "Registering optional {s}: #{}", .{ type_name.items, type_index.uniqueInteger() }); - try unit.optional_type_set.put(allocator, optional.element_type, .{ - .underscore = type_name.items, - .space = type_name.items, - }); - - try unit.forwardDeclareContainerType(allocator, .@"struct", type_name.items); - - var list = ArrayList(u8){}; - - try list.appendSlice(allocator, "typedef struct "); - try list.appendSlice(allocator, type_name.items); - try list.appendSlice(allocator, " {\n"); - - try list.appendNTimes(allocator, ' ', margin_width); - try unit.writeCDeclaration(module, &list, allocator, "value", optional.element_type, separation_character); - try list.appendSlice(allocator, ";\n"); - - try list.appendNTimes(allocator, ' ', margin_width); - try unit.writeCDeclaration(module, &list, allocator, "is_null", Type.boolean, separation_character); - try list.appendSlice(allocator, ";\n"); - - try list.appendSlice(allocator, "} "); - try list.appendSlice(allocator, type_name.items); - try list.appendSlice(allocator, ";\n\n"); - - try unit.type_declarations.appendSlice(allocator, list.items); - - return type_name.items; - }, - } - }; - - return result; - } - - fn cacheSliceType(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Type.Index, separation_character: u8) ![]const u8 { - const slice = module.types.array.get(type_index).slice; - - const result = if (unit.slice_type_set.get(slice.element_type, separation_character)) |r| r else blk: { - var type_name = ArrayList(u8){}; - try type_name.appendSlice(allocator, "Slice_"); - try unit.writeType(module, &type_name, allocator, slice.element_type, separation_character); - logln(.c, .g, "Registering slice {s}: #{}", .{ type_name.items, type_index.uniqueInteger() }); - try unit.slice_type_set.put(allocator, slice.element_type, .{ - .underscore = type_name.items, - .space = type_name.items, - }); - - try unit.forwardDeclareContainerType(allocator, .@"struct", type_name.items); - - var list = ArrayList(u8){}; - - try list.appendSlice(allocator, "typedef struct "); - try list.appendSlice(allocator, type_name.items); - try list.appendSlice(allocator, " {\n"); - - try list.appendNTimes(allocator, ' ', margin_width); - try unit.writeType(module, &list, allocator, slice.element_type, '_'); - try list.appendSlice(allocator, "* ptr;\n"); - - try list.appendNTimes(allocator, ' ', margin_width); - try list.appendSlice(allocator, "usize len;\n"); - - try list.appendSlice(allocator, "} "); - try list.appendSlice(allocator, type_name.items); - try list.appendSlice(allocator, ";\n\n"); - - try unit.type_declarations.appendSlice(allocator, list.items); - - break :blk type_name.items; - }; - - return result; - } - - fn cachePointerType(unit: *TranslationUnit, module: *Module, allocator: Allocator, pointer_type_index: Type.Index, separation_character: u8) ![]const u8 { - const result = if (unit.pointer_type_set.get(pointer_type_index, separation_character)) |r| r else blk: { - var underscore_type_name = ArrayList(u8){}; - var space_type_name = ArrayList(u8){}; - const pointer_type = module.types.array.get(pointer_type_index).pointer; - try underscore_type_name.appendSlice(allocator, "Pointer_"); - if (pointer_type.@"const") { - try underscore_type_name.appendSlice(allocator, "const_"); - } - if (pointer_type.many) { - try underscore_type_name.appendSlice(allocator, "many_"); - } - try unit.writeType(module, &underscore_type_name, allocator, pointer_type.element_type, '_'); - try unit.writeType(module, &space_type_name, allocator, pointer_type.element_type, ' '); - if (pointer_type.@"const") { - try space_type_name.appendSlice(allocator, " const"); - } - try space_type_name.append(allocator, '*'); - - const result = try unit.pointer_type_set.getOrPutValue(allocator, pointer_type_index, .{ - .underscore = underscore_type_name.items, - .space = space_type_name.items, - }); - - break :blk switch (separation_character) { - '_' => result.underscore, - ' ' => result.space, - else => unreachable, - }; - }; - - return result; - } - - fn cacheArrayType(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Type.Index, separation_character: u8) ![]const u8 { - const array = module.types.array.get(type_index).array; - - const result = if (unit.array_type_set.get(array.element_type, separation_character)) |r| r else blk: { - var type_name = ArrayList(u8){}; - try type_name.appendSlice(allocator, "Array_"); - try unit.writeType(module, &type_name, allocator, array.element_type, '_'); - try type_name.writer(allocator).print("_{}", .{array.element_count}); - var terminated = false; - switch (array.termination) { - .none => {}, - .zero, - .null, - => { - terminated = true; - try type_name.append(allocator, '_'); - try type_name.writer(allocator).writeAll(@tagName(array.termination)); - try type_name.appendSlice(allocator, "_terminated"); - }, - } - logln(.c, .g, "Registering array {s}: #{}", .{ type_name.items, type_index.uniqueInteger() }); - - try unit.array_type_set.put(allocator, array.element_type, .{ - .underscore = type_name.items, - .space = type_name.items, - }); - - try unit.forwardDeclareContainerType(allocator, .@"struct", type_name.items); - - var list = ArrayList(u8){}; - - try list.appendSlice(allocator, "typedef struct "); - try list.appendSlice(allocator, type_name.items); - try list.appendSlice(allocator, " {\n"); - - try list.appendNTimes(allocator, ' ', margin_width); - try unit.writeType(module, &list, allocator, array.element_type, ' '); - try list.appendSlice(allocator, " value"); - - try list.writer(allocator).print("[{}];\n", .{array.element_count + @intFromBool(terminated)}); - - try list.appendSlice(allocator, "} "); - try list.appendSlice(allocator, type_name.items); - try list.appendSlice(allocator, ";\n\n"); - - try unit.type_declarations.appendSlice(allocator, list.items); - - break :blk type_name.items; - }; - - return result; - } - - const ValueType = enum { - statement, - expression, - }; - - fn writeIntrinsic(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, intrinsic_index: Compilation.Intrinsic.Index, function_return_type: Type.Index, indentation: usize, value_type: ValueType) !void { - const intrinsic = module.values.intrinsics.get(intrinsic_index); - switch (intrinsic.kind) { - .syscall => |syscall_arguments| { - const syscall_argument_count = syscall_arguments.items.len; - if (!unit.syscall_bitset.isSet(syscall_argument_count - 1)) { - try unit.function_declarations.appendSlice(allocator, "u64 syscall"); - try unit.function_declarations.writer(allocator).print("{}(", .{syscall_argument_count}); - for (0..syscall_argument_count) |arg_i| { - try unit.function_declarations.writer(allocator).print("u64 arg{}, ", .{arg_i}); - } - _ = unit.function_declarations.pop(); - _ = unit.function_declarations.pop(); - try unit.function_declarations.appendSlice(allocator, - \\) { - \\ - ); - - const simple_register_argument_count = @min(syscall_argument_count, 4); - const complex_register_argument_count = syscall_argument_count - simple_register_argument_count; - const simple_argument_registers = [_]u8{ 'a', 'D', 'S', 'd' }; - const complex_argument_registers = [_]u8{ 10, 8, 9 }; - - for (0..complex_register_argument_count) |i| { - try unit.function_declarations.appendNTimes(allocator, ' ', indentation * margin_width); - try unit.function_declarations.writer(allocator).print("register unsigned long r{} __asm__(\"r{}\") = arg{};\n", .{ complex_argument_registers[i], complex_argument_registers[i], simple_argument_registers.len + i }); - } - - try unit.function_declarations.appendSlice(allocator, - \\ unsigned long ret; - \\ - \\ __asm__ __volatile__("syscall" - \\ : "=a"(ret) - \\ : - ); - - for (0..simple_register_argument_count, simple_argument_registers[0..simple_register_argument_count]) |arg_i, arg_register| { - try unit.function_declarations.writer(allocator).print("\"{c}\"(arg{}), ", .{ arg_register, arg_i }); - } - - for (complex_argument_registers[0..complex_register_argument_count]) |arg_register| { - try unit.function_declarations.writer(allocator).print("\"r\"(r{}), ", .{arg_register}); - } - - _ = unit.function_declarations.pop(); - _ = unit.function_declarations.pop(); - - try unit.function_declarations.appendSlice(allocator, - \\ - \\ : "rcx", "r11", "memory" - \\ ); - \\ - \\ return ret; - \\} - \\ - \\ - ); - - unit.syscall_bitset.set(syscall_argument_count - 1); - } - - try list.writer(allocator).print("syscall{}(", .{syscall_argument_count}); - - for (syscall_arguments.items) |argument_index| { - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = argument_index, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ", "); - } - - _ = list.pop(); - _ = list.pop(); - try list.append(allocator, ')'); - }, - .cast => |cast_value_index| { - if (value_type == .statement) unreachable; - - const cast_type = intrinsic.type; - const cast_value = module.values.array.get(cast_value_index); - - try list.append(allocator, '('); - try unit.writeType(module, list, allocator, cast_type, ' '); - try list.append(allocator, ')'); - - const cast_value_type = module.types.array.get(cast_value.getType(module)); - - switch (cast_value_type.*) { - .@"struct" => |struct_index| { - const struct_type = module.types.structs.get(struct_index); - switch (struct_type.backing_type.invalid) { - false => { - try list.appendSlice(allocator, "*("); - try unit.writeType(module, list, allocator, struct_type.backing_type, '_'); - try list.appendSlice(allocator, "*)&("); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = cast_value_index, - .type_index = function_return_type, - }); - try list.append(allocator, ')'); - }, - true => @panic("Unable to bitcast non-packed struct"), - } - }, - else => try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = cast_value_index, - .type_index = Type.Index.invalid, - }), - } - }, - .optional_wrap => |value_to_wrap_index| { - const optional_type = module.types.array.get(intrinsic.type); - switch (optional_type.*) { - .optional => |optional| { - const optional_element_type = module.types.array.get(optional.element_type); - switch (optional_element_type.*) { - .pointer => try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = value_to_wrap_index, - .type_index = optional.element_type, - }), - else => { - try list.append(allocator, '('); - try unit.writeType(module, list, allocator, intrinsic.type, '_'); - try list.appendSlice(allocator, ") {\n"); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, ".value = "); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = value_to_wrap_index, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ",\n"); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, ".is_null = false,\n"); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.append(allocator, '}'); - }, - } - }, - else => |t| @panic(@tagName(t)), - } - }, - .array_coerce_to_slice => |array_to_coerce_value_index| { - try list.append(allocator, '('); - try unit.writeType(module, list, allocator, intrinsic.type, '_'); - try list.appendSlice(allocator, ") {\n"); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.appendSlice(allocator, ".ptr = "); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = array_to_coerce_value_index, - .type_index = Type.Index.invalid, - }); - switch (module.values.array.get(array_to_coerce_value_index).*) { - .string_literal => {}, - else => try list.appendSlice(allocator, ".value"), - } - try list.appendSlice(allocator, ",\n"); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - const array_value = module.values.array.get(array_to_coerce_value_index); - const array_type = module.types.array.get(array_value.getType(module)); - const array_length = switch (array_type.*) { - .array => |array| array.element_count, - .pointer => |pointer| switch (module.types.array.get(pointer.element_type).*) { - .array => |array| array.element_count, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }; - try list.writer(allocator).print(".len = {},\n", .{array_length}); - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.append(allocator, '}'); - }, - .sign_extend, .zero_extend => |value_index| { - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = value_index, - .type_index = intrinsic.type, - }); - }, - .min => |binary| { - try list.appendSlice(allocator, try unit.cacheMacro(allocator, .MIN)); - try list.append(allocator, '('); - - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = binary.left, - .type_index = intrinsic.type, - }); - - try list.appendSlice(allocator, ", "); - - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = binary.right, - .type_index = intrinsic.type, - }); - - try list.append(allocator, ')'); - }, - else => |t| @panic(@tagName(t)), - } - } - - const Macro = enum { - MIN, - }; - - fn cacheMacro(unit: *TranslationUnit, allocator: Allocator, macro: Macro) ![]const u8 { - if (!unit.macro_set.contains(macro)) { - switch (macro) { - .MIN => { - try unit.macros.appendSlice(allocator, - \\#define MIN(a, b) \ - \\ ({ __typeof__ (a) _a = (a); \ - \\ __typeof__ (b) _b = (b); \ - \\ _a < _b ? _a : _b; }) - \\ - \\ - ); - }, - } - - unit.macro_set.setPresent(macro, true); - } - - return @tagName(macro); - } - - fn writeUnreachable(list: *ArrayList(u8), allocator: Allocator) !void { - try list.appendSlice(allocator, "__builtin_unreachable()"); - } - - fn writeCall(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, call_index: Compilation.Call.Index, function_return_type: Type.Index, indentation: usize) !void { - const call = module.values.calls.get(call_index); - const call_value = module.values.array.get(call.value); - var argument_declarations = ArrayList(Compilation.Declaration.Index){}; - - const callable_name = switch (call_value.*) { - .function_definition => |function_definition_index| blk: { - const name = try unit.renderFunctionDefinitionName(module, allocator, function_definition_index); - const function_definition = module.types.function_definitions.get(function_definition_index); - const function_prototype_type = module.types.array.get(function_definition.prototype); - const function_prototype = module.types.function_prototypes.get(function_prototype_type.function); - argument_declarations = function_prototype.arguments; - - try list.appendSlice(allocator, name); - try list.append(allocator, '('); - - break :blk name; - }, - .function_declaration => |function_declaration_index| blk: { - const name = try unit.renderFunctionDeclarationName(module, allocator, function_declaration_index); - const function_declaration = module.types.function_declarations.get(function_declaration_index); - const function_prototype_type = module.types.array.get(function_declaration.prototype); - const function_prototype = module.types.function_prototypes.get(function_prototype_type.function); - argument_declarations = function_prototype.arguments; - - try list.appendSlice(allocator, name); - try list.append(allocator, '('); - - break :blk name; - }, - .field_access => |field_access_index| blk: { - const field_access = module.values.field_accesses.get(field_access_index); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = field_access.declaration_reference, - .type_index = function_return_type, - }); - - const left_type = module.types.array.get(module.values.array.get(field_access.declaration_reference).declaration_reference.getType(module)); - // const is_pointer = switch (left_type) { - // .pointer => |pointer| b: { - // break :b true; - // }, - // }; - const is_pointer = switch (left_type.*) { - .pointer => true, - else => false, - }; - const container_field = module.types.container_fields.get(module.values.field_accesses.get(field_access_index).field); - argument_declarations = switch (module.types.array.get(container_field.type).*) { - .pointer => |pointer| switch (module.types.array.get(pointer.element_type).*) { - .function => |function| module.types.function_prototypes.get(function).arguments, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }; - - if (is_pointer) { - try list.appendSlice(allocator, "->"); - } else { - try list.append(allocator, '.'); - } - - const field = module.types.container_fields.get(field_access.field); - const field_name = module.getName(field.name).?; - try list.appendSlice(allocator, field_name); - try list.append(allocator, '('); - break :blk "field_access"; - }, - else => |t| @panic(@tagName(t)), - }; - _ = callable_name; - - if (!call.arguments.invalid) { - const argument_list = module.values.argument_lists.get(call.arguments); - assert(argument_declarations.items.len == argument_list.array.items.len); - - if (argument_list.array.items.len > 0) { - for (argument_list.array.items, argument_declarations.items, 0..) |argument_index, argument_declaration_index, index| { - _ = index; - - const argument_declaration = module.values.declarations.get(argument_declaration_index); - - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = argument_index, - .type_index = argument_declaration.getType(), - }); - try list.appendSlice(allocator, ", "); - } - - _ = list.pop(); - _ = list.pop(); - } - } - - try list.append(allocator, ')'); - } - - fn writeBranch(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, branch_index: Compilation.Branch.Index, function_return_type: Type.Index, indentation: usize) !bool { - const branch = module.values.branches.get(branch_index); - const classic = switch (module.values.array.get(branch.taken_expression).*) { - .block => true, - else => false, - }; - - if (classic) { - try list.appendSlice(allocator, "if ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = branch.expression, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ") "); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = branch.taken_expression, - .type_index = function_return_type, - }); - - if (!branch.not_taken_expression.invalid) { - if (module.values.array.get(branch.taken_expression).* == .block) { - _ = list.pop(); - try list.appendSlice(allocator, " else "); - } else { - unreachable; - } - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = branch.not_taken_expression, - .type_index = function_return_type, - }); - - if (module.values.array.get(branch.not_taken_expression).* == .block) { - return false; - } - } - - return true; - } else { - assert(!branch.not_taken_expression.invalid); - - try list.appendSlice(allocator, "("); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = branch.expression, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ") ? ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = branch.taken_expression, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ") : ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = branch.not_taken_expression, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ")"); - - return true; - } - } - - const ValueArguments = struct { - value_index: Value.Index, - type_index: Type.Index, - }; - - fn writeValue(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, function_return_type: Type.Index, indentation: usize, arguments: ValueArguments) anyerror!void { - const value_index = arguments.value_index; - const type_index = arguments.type_index; - const value = module.values.array.get(value_index); - //logln(.c, .g, "Generating C code for {s}", .{@tagName(value.*)}); - switch (value.*) { - .declaration => |declaration_index| { - try unit.writeDeclaration(module, list, allocator, declaration_index, indentation, '.'); - }, - .assign => |assignment_index| { - try unit.writeAssignment(module, list, allocator, assignment_index, function_return_type, indentation); - }, - .integer => |integer| { - try list.writer(allocator).print("{}", .{integer.value}); - }, - .declaration_reference => |declaration_reference| { - const mangle = true; - const name = try unit.renderDeclarationName(module, allocator, declaration_reference.value, mangle); - try list.appendSlice(allocator, name); - }, - .binary_operation => |binary_operation_index| { - const binary_operation = module.values.binary_operations.get(binary_operation_index); - - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = binary_operation.left, - .type_index = binary_operation.type, - }); - try list.append(allocator, ' '); - - switch (binary_operation.id) { - .add => try list.append(allocator, '+'), - .sub => try list.append(allocator, '-'), - .bit_and => try list.append(allocator, '&'), - .bit_or => try list.append(allocator, '|'), - .bit_xor => try list.append(allocator, '^'), - .multiply => try list.append(allocator, '*'), - .divide => try list.append(allocator, '/'), - .compare_greater_than => try list.append(allocator, '>'), - .compare_less_than => try list.append(allocator, '<'), - .shift_left => try list.appendSlice(allocator, "<<"), - .shift_right => try list.appendSlice(allocator, ">>"), - .compare_equal => try list.appendSlice(allocator, "=="), - .compare_not_equal => try list.appendSlice(allocator, "!="), - .compare_greater_or_equal => try list.appendSlice(allocator, ">="), - .compare_less_or_equal => try list.appendSlice(allocator, "<="), - } - - try list.append(allocator, ' '); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = binary_operation.right, - .type_index = binary_operation.type, - }); - try list.append(allocator, ')'); - }, - .string_literal => |string_literal_descriptor| { - try list.appendSlice(allocator, "(u8 *)"); - const string_literal = module.getName(string_literal_descriptor.hash) orelse unreachable; - try list.append(allocator, '"'); - try list.appendSlice(allocator, string_literal); - try list.append(allocator, '"'); - }, - .@"unreachable" => { - try writeUnreachable(list, allocator); - }, - .call => |call_index| try unit.writeCall(module, list, allocator, call_index, function_return_type, indentation), - .bool => |boolean| try list.appendSlice(allocator, if (boolean) "true" else "false"), - .block => |block_index| try unit.writeBlock(module, list, allocator, block_index, function_return_type, indentation), - .unary_operation => |unary_operation_index| { - const unary_operation = module.values.unary_operations.get(unary_operation_index); - const expression_character: u8 = switch (unary_operation.id) { - .boolean_not => '!', - .negation => '-', - .address_of => '&', - .pointer_dereference => '*', - }; - - const emit_expression_character = blk: { - var emit = true; - if (unary_operation.id == .address_of) { - const unary_value_type = module.types.array.get(module.values.array.get(unary_operation.value).getType(module)); - switch (unary_value_type.*) { - .array => |source_array| if (!arguments.type_index.invalid) switch (module.types.array.get(arguments.type_index).*) { - .pointer => |pointer| switch (module.types.array.get(pointer.element_type).*) { - .array => {}, - .optional => switch (module.types.array.get(source_array.element_type).*) { - .optional => emit = false, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }, - .integer, - .@"struct", - .function, - .pointer, - => {}, - else => |t| @panic(@tagName(t)), - } - } - - break :blk emit; - }; - - if (emit_expression_character) { - try list.append(allocator, expression_character); - } - - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = unary_operation.value, - .type_index = unary_operation.type, - }); - try list.append(allocator, ')'); - - if (unary_operation.id == .address_of) { - if (!arguments.type_index.invalid) { - switch (module.types.array.get(arguments.type_index).*) { - .pointer => |pointer| switch (module.types.array.get(module.values.array.get(unary_operation.value).getType(module)).*) { - .array => switch (module.types.array.get(pointer.element_type).*) { - .array => {}, - .optional => try list.appendSlice(allocator, ".value"), - else => |t| @panic(@tagName(t)), - }, - .integer, - .@"struct", - .function, - => {}, - // .array, - // .optional, - // .integer, - // .@"struct", - // .function, - // => {}, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - } - } - } - }, - .container_initialization => |container_initialization_index| { - const container_initialization = module.values.container_initializations.get(container_initialization_index); - - // TODO: obliterate this crap and handle this in sema perfectly (optional_wrap) - const additional_indentation: usize = if (!type_index.invalid) blk: { - switch (module.types.array.get(type_index).*) { - .@"struct" => break :blk 0, - .optional => |optional| switch (module.types.array.get(optional.element_type).*) { - .pointer => break :blk 0, - else => { - try list.append(allocator, '('); - try unit.writeType(module, list, allocator, type_index, '_'); - try list.appendSlice(allocator, ") {\n"); - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.appendSlice(allocator, ".value = "); - break :blk 1; - }, - }, - else => |t| @panic(@tagName(t)), - } - } else 0; - - try list.append(allocator, '('); - try unit.writeType(module, list, allocator, container_initialization.type, '_'); - try list.appendSlice(allocator, ") {\n"); - - const container_type = module.types.array.get(container_initialization.type); - switch (container_type.*) { - .@"struct" => { - const struct_type = module.types.structs.get(container_type.@"struct"); - logln(.c, .g, "Struct type: 0x{x}", .{@intFromPtr(struct_type)}); - - for (container_initialization.field_initializations.items, struct_type.fields.items) |field_initialization_index, container_field_index| { - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.append(allocator, '.'); - const container_field = module.types.container_fields.get(container_field_index); - const field_name = module.getName(container_field.name).?; - try list.appendSlice(allocator, field_name); - try list.appendSlice(allocator, " = "); - logln(.c, .g, "Name: {s}. Value: #{}. Field #{}", .{ field_name, field_initialization_index.uniqueInteger(), container_field_index.uniqueInteger() }); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = field_initialization_index, - .type_index = container_field.type, - }); - try list.appendSlice(allocator, ",\n"); - } - - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.append(allocator, '}'); - }, - .array => |array_type| { - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.appendSlice(allocator, ".value = {\n"); - - for (container_initialization.field_initializations.items, 0..) |field_initialization_index, array_index| { - try list.appendNTimes(allocator, ' ', (indentation + 2) * margin_width); - try list.writer(allocator).print("[{}] = ", .{array_index}); - - try unit.writeValue(module, list, allocator, function_return_type, indentation + 2, .{ - .value_index = field_initialization_index, - .type_index = Type.Index.invalid, - }); - - try list.appendSlice(allocator, " ,\n"); - } - - switch (array_type.termination) { - .none => {}, - .null, .zero => { - try list.appendNTimes(allocator, ' ', (indentation + 2) * margin_width); - const termination: []const u8 = switch (array_type.termination) { - .null => "nullptr", - .zero => "0", - else => unreachable, - }; - try list.writer(allocator).print("[{}] = {s},\n", .{ container_initialization.field_initializations.items.len, termination }); - }, - } - - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.appendSlice(allocator, "},\n"); - - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.append(allocator, '}'); - }, - else => |t| @panic(@tagName(t)), - } - - if (additional_indentation > 0) { - try list.appendSlice(allocator, ",\n"); - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.appendSlice(allocator, "}"); - } - }, - .array_initialization => |array_initialization_index| { - const array_initialization = module.values.container_initializations.get(array_initialization_index); - try list.append(allocator, '('); - try unit.writeType(module, list, allocator, array_initialization.type, '_'); - try list.appendSlice(allocator, ") { "); - - if (array_initialization.field_initializations.items.len > 0) { - for (array_initialization.field_initializations.items) |initialization_index| { - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = initialization_index, - .type_index = module.values.array.get(initialization_index).getType(module), - }); - try list.appendSlice(allocator, ", "); - // const container_field = module.types.container_fields.get(initialization_index); - // const field_name = module.getName(container_field.name).?; - } - - _ = list.pop(); - } - _ = list.pop(); - list.appendSliceAssumeCapacity(" }"); - }, - .field_access => |field_access_index| { - const field_access = module.values.field_accesses.get(field_access_index); - const left = module.values.array.get(field_access.declaration_reference); - const left_type = module.types.array.get(left.getType(module)); - const right_field = module.types.container_fields.get(field_access.field); - const right_field_name = module.getName(right_field.name).?; - const is_pointer = switch (left_type.*) { - .@"struct" => false, - .pointer => true, - else => |t| @panic(@tagName(t)), - }; - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = field_access.declaration_reference, - .type_index = right_field.type, - }); - - if (is_pointer) { - try list.appendSlice(allocator, "->"); - } else { - try list.append(allocator, '.'); - } - - try list.appendSlice(allocator, right_field_name); - }, - .pointer_null_literal => try list.appendSlice(allocator, "nullptr"), - .optional_null_literal => { - assert(!arguments.type_index.invalid); - try list.append(allocator, '('); - try unit.writeType(module, list, allocator, arguments.type_index, '_'); - try list.appendSlice(allocator, ") { .is_null = true }"); - }, - .slice => |slice_index| { - const slice = module.values.slices.get(slice_index); - const sliceable = module.values.array.get(slice.sliceable); - - const sliceable_type_index = switch (sliceable.*) { - .declaration_reference => |declaration_reference| declaration_reference.getType(module), - else => |t| @panic(@tagName(t)), - }; - const sliceable_type = module.types.array.get(sliceable_type_index); - const sliceable_element_type = switch (sliceable_type.*) { - .pointer => |pointer| pointer.element_type, - .slice => |slice_type| slice_type.element_type, - .array => |array_type| array_type.element_type, - else => |t| @panic(@tagName(t)), - }; - - try list.appendSlice(allocator, "(Slice_"); - try unit.writeType(module, list, allocator, sliceable_element_type, '_'); - try list.appendSlice(allocator, ") {\n"); - - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.appendSlice(allocator, ".ptr = "); - - switch (sliceable_type.*) { - .pointer => { - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.sliceable, - .type_index = sliceable_type_index, - }); - try list.appendSlice(allocator, ") + ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.range.start, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, "),\n"); - }, - .slice => { - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.sliceable, - .type_index = sliceable_type_index, - }); - try list.appendSlice(allocator, ").ptr + ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.range.start, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, "),\n"); - }, - .array => { - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.sliceable, - .type_index = sliceable_type_index, - }); - try list.appendSlice(allocator, ").value + ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.range.start, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, "),\n"); - }, - else => |t| @panic(@tagName(t)), - } - - try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); - try list.appendSlice(allocator, ".len = "); - - switch (sliceable_type.*) { - .pointer => { - switch (slice.range.end.invalid) { - false => { - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.range.end, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ") - ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.range.start, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ")\n"); - }, - true => { - unreachable; - }, - } - }, - .slice, .array => { - try list.append(allocator, '('); - switch (slice.range.end.invalid) { - false => { - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.range.end, - .type_index = Type.Index.invalid, - }); - }, - true => { - switch (sliceable_type.*) { - .slice => { - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.sliceable, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ").len"); - }, - .array => |array| { - try list.writer(allocator).print("{}", .{array.element_count}); - }, - else => |t| @panic(@tagName(t)), - } - }, - } - - try list.appendSlice(allocator, ") - ("); - try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ - .value_index = slice.range.start, - .type_index = Type.Index.invalid, - }); - try list.appendSlice(allocator, ")\n"); - }, - else => |t| @panic(@tagName(t)), - } - - try list.appendNTimes(allocator, ' ', indentation * margin_width); - try list.append(allocator, '}'); - }, - .function_definition => |function_definition_index| { - const function_name = try unit.writeFunctionDefinition(module, allocator, function_definition_index); - try list.appendSlice(allocator, function_name); - }, - .optional_check => |optional_check_index| { - const optional_check = module.values.optional_checks.get(optional_check_index); - const optional_type = module.types.array.get(module.values.array.get(optional_check.value).getType(module)); - assert(optional_type.* == .optional); - const optional_element_type = module.types.array.get(optional_type.optional.element_type); - const is_null_suffix_expression = switch (optional_element_type.*) { - .pointer => false, - else => true, - }; - if (is_null_suffix_expression) { - try list.append(allocator, '!'); - } - - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = optional_check.value, - .type_index = Type.Index.invalid, - }); - try list.append(allocator, ')'); - - if (is_null_suffix_expression) { - try list.appendSlice(allocator, ".is_null"); - } - }, - .optional_unwrap => |optional_unwrap_index| { - const optional_unwrap = module.values.optional_unwraps.get(optional_unwrap_index); - const optional_value = module.values.array.get(optional_unwrap.value); - const optional_type = module.types.array.get(optional_value.getType(module)); - assert(optional_type.* == .optional); - const optional_element_type_index = optional_type.optional.element_type; - const optional_element_type = module.types.array.get(optional_element_type_index); - - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = optional_unwrap.value, - .type_index = optional_element_type_index, - }); - try list.append(allocator, ')'); - - switch (optional_element_type.*) { - .pointer => {}, - else => try list.appendSlice(allocator, ".value"), - } - }, - .slice_access => |slice_access_index| { - const slice_access = module.values.slice_accesses.get(slice_access_index); - try list.append(allocator, '('); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = slice_access.value, - .type_index = slice_access.type, - }); - try list.appendSlice(allocator, ")."); - try list.appendSlice(allocator, @tagName(slice_access.field)); - }, - .indexed_access => |indexed_access_index| { - const indexed_access = module.values.indexed_accesses.get(indexed_access_index); - try list.append(allocator, '('); - const indexed_expression_index = indexed_access.indexed_expression; - const indexed_expression = module.values.array.get(indexed_expression_index); - const indexed_expression_type_index = indexed_expression.getType(module); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = indexed_expression_index, - .type_index = indexed_expression_type_index, - }); - - const indexed_expression_type = module.types.array.get(indexed_expression_type_index); - switch (indexed_expression_type.*) { - .slice => { - try list.appendSlice(allocator, ".ptr"); - }, - .array => try list.appendSlice(allocator, ".value"), - .pointer => |pointer| { - _ = pointer; - // logln(.c, .g, "TODO FIXME This looks wrong", .{}); - // try list.appendSlice(allocator, ".value"); - - // const element_type = module.types.array.get(pointer.element_type); - // switch (element_type.*) { - // .optional => |optional| { - // switch (module.types.array.get(optional.element_type).*) { - // else => |t| @panic(@tagName(t)), - // } - // }, - // else => |t| @panic(@tagName(t)), - // } - }, - else => |t| @panic(@tagName(t)), - } - - try list.appendSlice(allocator, ")["); - try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ - .value_index = indexed_access.index_expression, - .type_index = Type.Index.invalid, - }); - try list.append(allocator, ']'); - }, - .undefined => { - // Assuming this is only done at initialization - _ = list.pop(); - _ = list.pop(); - _ = list.pop(); - }, - .enum_field => |enum_field_index| { - const enum_field = module.types.enum_fields.get(enum_field_index); - try unit.writeType(module, list, allocator, enum_field.parent, '_'); - try list.append(allocator, '_'); - const enum_field_name = module.getName(enum_field.name).?; - try list.appendSlice(allocator, enum_field_name); - }, - .intrinsic => |intrinsic_index| try unit.writeIntrinsic(module, list, allocator, intrinsic_index, function_return_type, indentation, .expression), - .@"return" => |return_index| { - try unit.writeReturn(module, list, allocator, return_index, function_return_type, indentation); - }, - .branch => |branch_index| { - _ = try unit.writeBranch(module, list, allocator, branch_index, function_return_type, indentation); - }, - else => |t| @panic(@tagName(t)), - } - } -}; - -pub fn initialize(compilation: *Compilation, module: *Module) !void { - const allocator = compilation.base_allocator; - var unit = try TranslationUnit.create(module, allocator); - const c_source_file_path = try std.mem.concat(allocator, u8, &.{ module.descriptor.executable_path, ".c" }); - const c_source_file = try std.fs.cwd().createFile(c_source_file_path, .{}); - - try unit.type_forward_declarations.append(allocator, '\n'); - - var offset: u64 = 0; - const slices = [_][]const u8{ unit.primitive_type_declarations.items, unit.type_forward_declarations.items, unit.macros.items, unit.type_declarations.items, unit.function_declarations.items, unit.global_variable_declarations.items, unit.string_literals.items, unit.function_definitions.items }; - for (slices) |slice| { - try c_source_file.pwriteAll(slice, offset); - offset += slice.len; - } - - c_source_file.close(); - const c_source_file_realpath = try std.fs.cwd().realpathAlloc(allocator, c_source_file_path); - const c_flags = [_][]const u8{ - "-std=c2x", - "-g", - "-funsigned-char", - }; - - var zig_command_line = ArrayList([]const u8){}; - try zig_command_line.append(allocator, "zig"); - try zig_command_line.append(allocator, "build-exe"); - - if (module.descriptor.link_libc) { - try zig_command_line.append(allocator, "-lc"); - } - - for (module.map.libraries.keys()) |library_name| { - const library_argument = try std.mem.concat(allocator, u8, &.{ "-l", library_name }); - try zig_command_line.append(allocator, library_argument); - } - - const local_cache_dir = std.fs.cwd().realpathAlloc(allocator, "zig-cache") catch b: { - std.fs.cwd().makeDir("nat/zig-cache") catch {}; - break :b try std.fs.cwd().realpathAlloc(allocator, "nat/zig-cache"); - }; - const home_directory = std.os.getenv("HOME") orelse @panic("Unable to get HOME environment variable. Did you forget to pass it to the process?"); - const global_cache_dir = try std.mem.concat(allocator, u8, &.{ home_directory, "/.cache/zig" }); - try zig_command_line.append(allocator, "--cache-dir"); - try zig_command_line.append(allocator, local_cache_dir); - try zig_command_line.append(allocator, "--global-cache-dir"); - try zig_command_line.append(allocator, global_cache_dir); - - try zig_command_line.append(allocator, try std.mem.concat(allocator, u8, &.{ "-femit-bin=", module.descriptor.executable_path })); - try zig_command_line.append(allocator, "-cflags"); - - for (c_flags) |c_flag| { - try zig_command_line.append(allocator, c_flag); - } - - try zig_command_line.append(allocator, "--"); - try zig_command_line.append(allocator, c_source_file_realpath); - - const run_result = try std.ChildProcess.run(.{ - .allocator = allocator, - .argv = zig_command_line.items, - }); - switch (run_result.term) { - .Exited => |exit_code| { - if (exit_code != 0) { - std.debug.print("\nERROR: Zig command exited with code {}:\n", .{exit_code}); - for (zig_command_line.items) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("\n\n{s}", .{run_result.stderr}); - - @panic("Internal error"); - } - }, - else => |t| @panic(@tagName(t)), - } -} diff --git a/bootstrap/backend/llvm.cpp b/bootstrap/backend/llvm.cpp new file mode 100644 index 0000000..b8a4b82 --- /dev/null +++ b/bootstrap/backend/llvm.cpp @@ -0,0 +1,834 @@ +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InlineAsm.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Verifier.h" +#include "llvm/IR/DIBuilder.h" + +#include "llvm/MC/TargetRegistry.h" + +#include "llvm/TargetParser/Host.h" +#include "llvm/Target/TargetOptions.h" +#include "llvm/Target/TargetMachine.h" + +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/FileSystem.h" + +#include "lld/Common/CommonLinkerContext.h" + +using namespace llvm; + +extern "C" LLVMContext* NativityLLVMCreateContext() +{ + auto* context = new LLVMContext(); + return context; +} + +extern "C" Module* NativityLLVMCreateModule(const char* name_ptr, size_t name_len, LLVMContext& context) +{ + auto name = StringRef(name_ptr, name_len); + auto* module = new Module(name, context); + return module; +} + +extern "C" IRBuilder<>* NativityLLVMCreateBuilder(LLVMContext& Context) +{ + auto* builder = new IRBuilder<>(Context); + return builder; +} + +extern "C" DIBuilder* NativityLLVMModuleCreateDebugInfoBuilder(Module& module) +{ + DIBuilder* builder = new DIBuilder(module); + return builder; +} + +extern "C" DIFile* NativityLLVMDebugInfoBuilderCreateFile(DIBuilder& builder, const char* filename_ptr, size_t filename_len, const char* directory_ptr, size_t directory_len) +{ + auto filename = StringRef(filename_ptr, filename_len); + auto directory = StringRef(directory_ptr, directory_len); + auto* file = builder.createFile(filename, directory); + return file; +} + +extern "C" DICompileUnit* NativityLLVMDebugInfoBuilderCreateCompileUnit(DIBuilder& builder, unsigned language, DIFile* file, const char* producer_ptr, size_t producer_len, bool is_optimized, const char* flags_ptr, size_t flags_len, unsigned runtime_version, const char* split_name_ptr, size_t split_name_len, DICompileUnit::DebugEmissionKind debug_emission_kind, uint64_t DWOId, bool split_debug_inlining, bool debug_info_for_profiling, DICompileUnit::DebugNameTableKind debug_name_table_kind, bool ranges_base_address, const char* sysroot_ptr, size_t sysroot_len, const char* sdk_ptr, size_t sdk_len) +{ + auto producer = StringRef(producer_ptr, producer_len); + auto flags = StringRef(flags_ptr, flags_len); + auto split_name = StringRef(split_name_ptr, split_name_len); + auto sysroot = StringRef(sysroot_ptr, sysroot_len); + auto sdk = StringRef(sdk_ptr, sdk_len); + auto* compile_unit = builder.createCompileUnit(language, file, producer, is_optimized, flags, runtime_version, split_name, debug_emission_kind, DWOId, split_debug_inlining, debug_info_for_profiling, debug_name_table_kind, ranges_base_address, sysroot, sdk); + return compile_unit; +} + +extern "C" DISubprogram* NativityLLVMDebugInfoBuilderCreateFunction(DIBuilder& builder, DIScope* scope, const char* name_ptr, size_t name_len, const char* linkage_name_ptr, size_t linkage_name_len, DIFile* file, unsigned line_number, DISubroutineType* type, unsigned scope_line, DINode::DIFlags flags, DISubprogram::DISPFlags subprogram_flags, DISubprogram* declaration) +{ + auto name = StringRef(name_ptr, name_len); + auto linkage_name = StringRef(linkage_name_ptr, linkage_name_len); + DITemplateParameterArray template_parameters = nullptr; + DITypeArray thrown_types = nullptr; + DINodeArray annotations = nullptr; + StringRef target_function_name = ""; + + auto* function = builder.createFunction(scope, name, linkage_name, file, line_number, type, scope_line, flags, subprogram_flags, template_parameters, declaration, thrown_types, annotations, target_function_name); + return function; +} + +extern "C" DISubroutineType* NativityLLVMDebugInfoBuilderCreateSubroutineType(DIBuilder& builder, DIType** parameter_types_ptr, size_t parameter_type_count, DINode::DIFlags flags, dwarf::CallingConvention calling_convention) +{ + auto metadata_list = ArrayRef(reinterpret_cast( parameter_types_ptr), parameter_type_count); + auto parameter_types = builder.getOrCreateTypeArray(metadata_list); + auto* subroutine_type = builder.createSubroutineType(parameter_types, flags, calling_convention); + return subroutine_type; +} + +extern "C" DILexicalBlock* NativityLLVMDebugInfoBuilderCreateLexicalBlock(DIBuilder& builder, DIScope* parent_scope, DIFile* parent_file, unsigned line, unsigned column) +{ + assert(isa(parent_scope)); + auto* block = builder.createLexicalBlock(parent_scope, parent_file, line, column); + return block; +} + +extern "C" void NativityLLVMBuilderSetCurrentDebugLocation(IRBuilder<>& builder, LLVMContext& context, unsigned line, unsigned column, DIScope* scope, Function* function) +{ + auto debug_location = DILocation::get(context, line, column, scope); + builder.SetCurrentDebugLocation(debug_location); +} + +extern "C" DILocalVariable* NativityLLVMDebugInfoBuilderCreateParameterVariable(DIBuilder& builder, DIScope* scope, const char* name_ptr, size_t name_len, unsigned argument_index, DIFile* file, unsigned line_number, DIType* type, bool always_preserve, DINode::DIFlags flags) +{ + assert(isa(scope)); + auto name = StringRef(name_ptr, name_len); + auto* parameter_variable = builder.createParameterVariable(scope, name, argument_index, file, line_number, type, always_preserve, flags); + return parameter_variable; +} + +extern "C" DILocalVariable* NativityLLVMDebugInfoBuilderCreateAutoVariable(DIBuilder& builder, DIScope* scope, const char* name_ptr, size_t name_len, DIFile* file, unsigned line_number, DIType* type, bool always_preserve, DINode::DIFlags flags, uint32_t alignment) // 0 means 1 << 0 (alignment of 1) +{ + auto name = StringRef(name_ptr, name_len); + auto* auto_variable = builder.createAutoVariable(scope, name, file, line_number, type, always_preserve, flags, alignment); + return auto_variable; +} + +extern "C" Instruction* NativityLLVMDebugInfoBuilderInsertDeclare(DIBuilder& builder, Value* pointer, DILocalVariable* local_variable, LLVMContext& context, unsigned line, unsigned column, DIScope* scope, BasicBlock* basic_block) +{ + auto debug_location = DILocation::get(context, line, column, scope); + auto* expression = builder.createExpression(); + auto* instruction = builder.insertDeclare(pointer, local_variable, expression, debug_location, basic_block); + return instruction; +} + +extern "C" DIType* NativityLLVMDebugInfoBuilderCreateBasicType(DIBuilder& builder, const char* name_ptr, size_t name_len, uint64_t bit_count, unsigned dwarf_encoding, DINode::DIFlags flags) +{ + auto name = StringRef(name_ptr, name_len); + auto* type = builder.createBasicType(name, bit_count, dwarf_encoding, flags); + return type; +} + +extern "C" DIDerivedType* NativityLLVMDebugInfoBuilderCreatePointerType(DIBuilder& builder, DIType* element_type, uint64_t pointer_bit_count, uint32_t alignment, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + std::optional DWARFAddressSpace = std::nullopt; + DINodeArray annotations = nullptr; + auto* pointer_type = builder.createPointerType(element_type, pointer_bit_count, alignment, DWARFAddressSpace, name, annotations); + return pointer_type; +} + +extern "C" DICompositeType* NativityLLVMDebugInfoBuilderCreateStructType(DIBuilder& builder, DIScope* scope, const char* name_ptr, size_t name_len, DIFile* file, unsigned line_number, uint64_t bit_count, uint32_t alignment, DINode::DIFlags flags, DIType* derived_from, DIType** element_type_ptr, size_t element_type_count) +{ + auto name = StringRef(name_ptr, name_len); + auto type_array = ArrayRef(reinterpret_cast(element_type_ptr), element_type_count); + + auto* struct_type = builder.createStructType(scope, name, file, line_number, bit_count, alignment, flags, derived_from, builder.getOrCreateArray(type_array)); + return struct_type; +} + +extern "C" DICompositeType* NativityLLVMDebugInfoBuilderCreateArrayType(DIBuilder& builder, uint64_t bit_size, uint32_t alignment, DIType* type, size_t element_count) +{ + Metadata* subranges[1] = { + builder.getOrCreateSubrange(0, element_count), + }; + DINodeArray subscripts = builder.getOrCreateArray(ArrayRef(subranges, sizeof(subranges) / sizeof(subranges[0]))); + + auto* array_type = builder.createArrayType(bit_size, alignment, type, subscripts); + return array_type; +} + +extern "C" DIEnumerator* NativityLLVMDebugInfoBuilderCreateEnumerator(DIBuilder& builder, const char* name_ptr, size_t name_len, uint64_t value, bool is_unsigned) +{ + + // DIEnumerator *DIBuilder::createEnumerator(StringRef Name, uint64_t Val, + auto name = StringRef(name_ptr, name_len); + auto* enumerator = builder.createEnumerator(name, value, is_unsigned); + return enumerator; +} + +extern "C" DICompositeType* NativityLLVMDebugInfoBuilderCreateEnumerationType(DIBuilder& builder, DIScope* scope, const char* name_ptr, size_t name_len, DIFile* file, unsigned line, uint64_t bit_size, uint32_t alignment, DIEnumerator** enumerator_ptr, size_t enumerator_count, DIType* underlying_type) +{ + auto name = StringRef(name_ptr, name_len); + DINodeArray enumerators = builder.getOrCreateArray(ArrayRef(reinterpret_cast(enumerator_ptr), enumerator_count)); + auto* enumeration_type = builder.createEnumerationType(scope, name, file, line, bit_size, alignment, enumerators, underlying_type); + return enumeration_type; +} + +extern "C" DICompositeType* NativityLLVMDebugInfoBuilderCreateReplaceableCompositeType(DIBuilder& builder, unsigned tag, const char* name_ptr, size_t name_len, DIScope* scope, DIFile* file, unsigned line) +{ + auto name = StringRef(name_ptr, name_len); + auto* composite_type = builder.createReplaceableCompositeType(tag, name, scope, file, line); + return composite_type; +} +extern "C" DISubprogram* NativityLLVMDebugInfoScopeToSubprogram(DIScope* scope) +{ + auto* subprogram = dyn_cast(scope); + return subprogram; +} + +extern "C" void NativityLLVMDebugInfoBuilderFinalizeSubprogram(DIBuilder& builder, DISubprogram* subprogram, const Function* function) +{ + assert(subprogram->describes(function)); + builder.finalizeSubprogram(subprogram); +} + +extern "C" void NativityLLVMDebugInfoBuilderFinalize(DIBuilder& builder) +{ + builder.finalize(); +} + +extern "C" DIFile* NativityLLVMDebugInfoSubprogramGetFile(DISubprogram& subprogram) +{ + auto* file = subprogram.getFile(); + return file; +} + +extern "C" DIType* NativityLLVMDebugInfoSubprogramGetArgumentType(DISubprogram& subprogram, size_t argument_index) +{ + auto* argument_type = subprogram.getType()->getTypeArray()[argument_index]; + return argument_type; +} + +extern "C" unsigned NativityLLVMArgumentGetIndex(Argument& argument) +{ + unsigned argument_index = argument.getArgNo(); + return argument_index; +} + +extern "C" FunctionType* NativityLLVMGetFunctionType(Type* return_type, Type** type_ptr, size_t type_count, bool var_args) +{ + auto types = ArrayRef(type_ptr, type_count); + auto* function_type = FunctionType::get(return_type, types, var_args); + return function_type; +} + +extern "C" IntegerType* NativityLLVMGetIntegerType(LLVMContext& context, unsigned bit_count) +{ + auto integer_type = IntegerType::get(context, bit_count); + return integer_type; +} + +extern "C" PointerType* NativityLLVMGetPointerType(LLVMContext& context, unsigned address_space) +{ + auto pointer_type = PointerType::get(context, address_space); + return pointer_type; +} + +extern "C" ArrayType* NativityLLVMGetArrayType(Type* element_type, uint64_t element_count) +{ + auto* array_type = ArrayType::get(element_type, element_count); + return array_type; +} + +extern "C" StructType* NativityLLVMCreateStructType(LLVMContext& context, Type** type_ptr, size_t type_count, const char* name_ptr, size_t name_len, bool is_packed) +{ + auto types = ArrayRef(type_ptr, type_count); + auto name = StringRef(name_ptr, name_len); + + auto* struct_type = StructType::create(context, types, name, is_packed); + return struct_type; +} +extern "C" Function* NativityLLVMModuleGetFunction(Module& module, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* function = module.getFunction(name); + return function; +} + +extern "C" void NativityLLVMFunctionAddAttributeKey(Function& function, Attribute::AttrKind attribute) +{ + function.addFnAttr(attribute); +} + +extern "C" Function* NativityLLVModuleCreateFunction(Module* module, FunctionType* function_type, GlobalValue::LinkageTypes linkage_type, unsigned address_space, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* function = Function::Create(function_type, linkage_type, address_space, name, module); + return function; +} + +extern "C" BasicBlock* NativityLLVMCreateBasicBlock(LLVMContext& context, const char* name_ptr, size_t name_len, Function* parent, BasicBlock* insert_before) +{ + auto name = StringRef(name_ptr, name_len); + auto* basic_block = BasicBlock::Create(context, name, parent, insert_before); + return basic_block; +} + +extern "C" PHINode* NativityLLVMCreatePhiNode(Type* type, unsigned reserved_value_count, const char* name_ptr, size_t name_len, BasicBlock* basic_block) +{ + auto name = StringRef(name_ptr, name_len); + auto* phi_node = PHINode::Create(type, reserved_value_count, name, basic_block); + return phi_node; +} + +extern "C" void NativityLLVMBasicBlockRemoveFromParent(BasicBlock* basic_block) +{ + basic_block->eraseFromParent(); +} + +extern "C" void NativityLLVMBuilderSetInsertPoint(IRBuilder<>& builder, BasicBlock* basic_block) +{ + builder.SetInsertPoint(basic_block); +} + +extern "C" void NativityLLVMValueSetName(Value* value, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + value->setName(name); +} + +extern "C" Type* NativityLLVMValueGetType(Value* value) +{ + auto* type = value->getType(); + return type; +} + +extern "C" void NativityLLVMFunctionGetArguments(Function& function, Argument** argument_ptr, size_t* argument_count) +{ + auto actual_argument_count = function.arg_size(); + assert(actual_argument_count <= *argument_count); + *argument_count = actual_argument_count; + size_t arg_i = 0; + for (auto& Arg : function.args()) { + argument_ptr[arg_i] = &Arg; + arg_i += 1; + } +} + +extern "C" void NativityLLVMFunctionSetSubprogram(Function& function, DISubprogram* subprogram) +{ + function.setSubprogram(subprogram); +} + +extern "C" DISubprogram* NativityLLVMFunctionGetSubprogram(Function& function) +{ + auto* subprogram = function.getSubprogram(); + return subprogram; +} + +extern "C" Constant* NativityLLVMConstantStruct(StructType* struct_type, Constant** constant_ptr, size_t constant_count) +{ + auto constants = ArrayRef(constant_ptr, constant_count); + auto* constant_struct = ConstantStruct::get(struct_type, constants); + return constant_struct; +} + +extern "C" StoreInst* NativityLLVMBuilderCreateStore(IRBuilder<>& builder, Value* value, Value* pointer, bool is_volatile) +{ + auto* store = builder.CreateStore(value, pointer, is_volatile); + return store; +} + +extern "C" AllocaInst* NativityLLVMBuilderCreateAlloca(IRBuilder<>& builder, Type* type, unsigned address_space, Value* array_size, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* alloca = builder.CreateAlloca(type, address_space, array_size, name); + return alloca; +} + +extern "C" Type* NativityLLVMGetVoidType(LLVMContext& context) +{ + auto* void_type = Type::getVoidTy(context); + return void_type; +} + +extern "C" Value* NativityLLVMBuilderCreateICmp(IRBuilder<>& builder, CmpInst::Predicate comparation, Value* left, Value* right, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* icmp = builder.CreateICmp(comparation, left, right, name); + return icmp; +} + +extern "C" LoadInst* NativityLLVMBuilderCreateLoad(IRBuilder<>& builder, Type* type, Value* value, bool is_volatile, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* load = builder.CreateLoad(type, value, is_volatile, name); + return load; +} + +extern "C" ReturnInst* NativityLLVMBuilderCreateRet(IRBuilder<>& builder, Value* value) +{ + auto* ret = builder.CreateRet(value); + return ret; +} + +extern "C" InlineAsm* NativityLLVMGetInlineAssembly(FunctionType* function_type, const char* assembly_ptr, size_t assembly_len, const char* constraints_ptr, size_t constrains_len, bool has_side_effects, bool is_align_stack, InlineAsm::AsmDialect dialect, bool can_throw) +{ + auto assembly = StringRef(assembly_ptr, assembly_len); + auto constraints = StringRef(constraints_ptr, constrains_len); + auto* inline_asm = InlineAsm::get(function_type, assembly, constraints, has_side_effects, is_align_stack, dialect, can_throw); + return inline_asm; +} + +extern "C" Value* NativityLLVMBuilderCreateCast(IRBuilder<>& builder, Instruction::CastOps cast_type, Value* value, Type* type, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* cast = builder.CreateCast(cast_type, value, type, name); + return cast; +} + +extern "C" CallInst* NativityLLVMBuilderCreateCall(IRBuilder<>& builder, FunctionType* function_type, Value* callee, Value** argument_ptr, size_t argument_count, const char* name_ptr, size_t name_len, MDNode* fp_math_tag) +{ + if (auto* foo = static_cast(callee->getType())) { + int k = 0; + } + auto arguments = ArrayRef(argument_ptr, argument_count); + auto name = StringRef(name_ptr, name_len); + auto* call = builder.CreateCall(function_type, callee, arguments, name, fp_math_tag); + return call; +} + +extern "C" UnreachableInst* NativityLLVMBuilderCreateUnreachable(IRBuilder<>& builder) +{ + auto* unreachable = builder.CreateUnreachable(); + return unreachable; +} + +extern "C" GlobalVariable* NativityLLVMModuleAddGlobalVariable(Module& module, Type* type, bool is_constant, GlobalValue::LinkageTypes linkage_type, Constant* initializer, const char* name_ptr, size_t name_len, GlobalVariable* insert_before, GlobalValue::ThreadLocalMode thread_local_mode, unsigned address_space, bool externally_initialized) +{ + auto name = StringRef(name_ptr, name_len); + auto* global_variable = new GlobalVariable(module, type, is_constant, linkage_type, initializer, name, insert_before, thread_local_mode, address_space, externally_initialized); + return global_variable; +} + +extern "C" Value* NativityLLVMBuilderCreateAdd(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len, bool no_unsigned_wrapping, bool no_signed_wrapping) +{ + auto name = StringRef(name_ptr, name_len); + auto* add = builder.CreateAdd(left, right, name, no_unsigned_wrapping, no_signed_wrapping); + return add; +} + +extern "C" Value* NativityLLVMBuilderCreateSub(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len, bool no_unsigned_wrapping, bool no_signed_wrapping) +{ + auto name = StringRef(name_ptr, name_len); + auto* add = builder.CreateSub(left, right, name, no_unsigned_wrapping, no_signed_wrapping); + return add; +} + +extern "C" Value* NativityLLVMBuilderCreateMultiply(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len, bool no_unsigned_wrapping, bool no_signed_wrapping) +{ + auto name = StringRef(name_ptr, name_len); + auto* multiply = builder.CreateMul(left, right, name, no_unsigned_wrapping, no_signed_wrapping); + return multiply; +} + +extern "C" Value* NativityLLVMBuilderCreateUDiv(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len, bool is_exact) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateUDiv(left, right, name, is_exact); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateSDiv(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len, bool is_exact) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateSDiv(left, right, name, is_exact); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateURem(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateURem(left, right, name); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateSRem(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateSRem(left, right, name); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateXor(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateXor(left, right, name); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateAnd(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateAnd(left, right, name); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateOr(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateOr(left, right, name); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateShiftLeft(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len, bool no_unsigned_wrapping, bool no_signed_wrapping) +{ + auto name = StringRef(name_ptr, name_len); + auto* shl = builder.CreateShl(left, right, name, no_unsigned_wrapping, no_signed_wrapping); + return shl; +} + +extern "C" Value* NativityLLVMBuilderCreateLogicalShiftRight(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len, bool is_exact) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateLShr(left, right, name, is_exact); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateArithmeticShiftRight(IRBuilder<>& builder, Value* left, Value* right, const char* name_ptr, size_t name_len, bool is_exact) +{ + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateAShr(left, right, name, is_exact); + return result; +} + +extern "C" Value* NativityLLVMBuilderCreateGEP(IRBuilder<>& builder, Type* type, Value* pointer, Value** index_ptr, size_t index_count, const char* name_ptr, size_t name_len, bool in_bounds) +{ + auto index_list = ArrayRef(index_ptr, index_count); + auto name = StringRef(name_ptr, name_len); + auto* GEP = builder.CreateGEP(type, pointer, index_list, name, in_bounds); + return GEP; +} + +extern "C" BranchInst* NativityLLVMBuilderCreateBranch(IRBuilder<>& builder, BasicBlock* basic_block) +{ + auto* conditional_branch = builder.CreateBr(basic_block); + return conditional_branch; +} + +extern "C" BranchInst* NativityLLVMBuilderCreateConditionalBranch(IRBuilder<>& builder, Value* condition, BasicBlock* true_block, BasicBlock* false_block, MDNode* branch_weights, MDNode* unpredictable) +{ + auto* conditional_branch = builder.CreateCondBr(condition, true_block, false_block, branch_weights, unpredictable); + return conditional_branch; +} + +extern "C" Intrinsic::ID NativityLLVMLookupIntrinsic(const char* name_ptr, size_t name_len) +{ + auto name = StringRef(name_ptr, name_len); + Intrinsic::ID id = Function::lookupIntrinsicID(name); + return id; +} + +extern "C" FunctionType* NativityLLVMContextGetIntrinsicType(LLVMContext& context, Intrinsic::ID intrinsic_id, Type** parameter_type_ptr, size_t parameter_type_count) +{ + assert(intrinsic_id < Intrinsic::num_intrinsics); + auto parameter_types = ArrayRef(parameter_type_ptr, parameter_type_count); + auto* function_type = Intrinsic::getType(context, intrinsic_id, parameter_types); + return function_type; +} + +extern "C" Function* NativityLLVMModuleGetIntrinsicDeclaration(Module* module, Intrinsic::ID intrinsic_id, Type** parameter_types_ptr, size_t parameter_type_count) +{ + auto parameter_types = ArrayRef(parameter_types_ptr, parameter_type_count); + assert(intrinsic_id < Intrinsic::num_intrinsics); + Function* function = Intrinsic::getDeclaration(module, intrinsic_id, parameter_types); + return function; +} + + +extern "C" Value* NativityLLVMBuilderCreateExtractValue(IRBuilder<>& builder, Value* aggregate, unsigned* indices_ptr, size_t indices_len, const char* name_ptr, size_t name_len) +{ + auto indices = ArrayRef(indices_ptr, indices_len); + auto name = StringRef(name_ptr, name_len); + auto* value = builder.CreateExtractValue(aggregate, indices, name); + return value; +} + +extern "C" Value* NativityLLVMBuilderCreateInsertValue(IRBuilder<>& builder, Value* aggregate, Value* value, unsigned* indices_ptr, size_t indices_len, const char* name_ptr, size_t name_len) +{ + auto indices = ArrayRef(indices_ptr, indices_len); + auto name = StringRef(name_ptr, name_len); + auto* result = builder.CreateInsertValue(aggregate, value, indices, name); + return result; +} + +extern "C" ConstantInt* NativityLLVMContextGetConstantInt(LLVMContext& context, unsigned bit_count, uint64_t value, bool is_signed) +{ + auto int_type = APInt(bit_count, value, is_signed); + auto constant_int = ConstantInt::get(context, int_type); + return constant_int; +} + +extern "C" Constant* NativityLLVMContextGetConstString(LLVMContext& context, const char* string_ptr, size_t string_len, bool null_terminate) +{ + auto string = StringRef(string_ptr, string_len); + auto* constant = ConstantDataArray::getString(context, string, null_terminate); + return constant; +} + +extern "C" Constant* NativityLLVMContextGetConstArray(ArrayType* array_type, Constant** value_ptr, size_t value_count) +{ + auto values = ArrayRef(value_ptr, value_count); + auto* constant_array = ConstantArray::get(array_type, values); + return constant_array; +} + +extern "C" Constant* NativityLLVMContextCreateGlobalStringPointer(IRBuilder<>& builder, const char* string_ptr, size_t string_len, const char* name_ptr, size_t name_len, unsigned address_space, Module* module) +{ + auto string = StringRef(string_ptr, string_len); + auto name = StringRef(name_ptr, name_len); + Constant* constant = builder.CreateGlobalStringPtr(string, name, address_space, module); + return constant; +} + +extern "C" bool NativityLLVMVerifyFunction(Function& function, const char** message_ptr, size_t* message_len) +{ + std::string message_buffer; + raw_string_ostream message_stream(message_buffer); + + bool result = verifyFunction(function, &message_stream); + message_stream.flush(); + auto size = message_stream.str().size(); + *message_ptr = strndup(message_stream.str().c_str(), size); + *message_len = size; + + // We invert the condition because LLVM conventions are just stupid + return !result; +} + +extern "C" bool NativityLLVMVerifyModule(const Module& module, const char** message_ptr, size_t* message_len) +{ + std::string message_buffer; + raw_string_ostream message_stream(message_buffer); + + bool result = verifyModule(module, &message_stream); + message_stream.flush(); + auto size = message_stream.str().size(); + *message_ptr = strndup(message_stream.str().c_str(), size); + *message_len = size; + + // We invert the condition because LLVM conventions are just stupid + return !result; +} + +extern "C" Type* NativityLLVMFunctionGetReturnType(const Function& function) +{ + auto* return_type = function.getReturnType(); + return return_type; +} + +extern "C" const char* NativityLLVMFunctionToString(const Function& function, size_t* len) +{ + std::string buf; + raw_string_ostream os(buf); + function.print(os); + os.flush(); + *len = buf.size(); + auto* result = strdup(buf.c_str()); + return result; +} + +extern "C" Type* NativityLLVMAllocatGetAllocatedType(AllocaInst& alloca) +{ + auto* type = alloca.getAllocatedType(); + return type; +} + +extern "C" AllocaInst* NativityLLVMValueToAlloca(Value* value) +{ + assert(value); + auto* alloca = dyn_cast(value); + return alloca; +} + +extern "C" Constant* NativityLLVMValueToConstant(Value* value) +{ + assert(value); + auto* constant = dyn_cast(value); + return constant; +} + +extern "C" Function* NativityLLVMValueToFunction(Value* value) +{ + assert(value); + auto* function = dyn_cast(value); + return function; +} + +extern "C" bool NativityLLVMTypeIsPointer(Type* type) +{ + bool is_pointer = type->isPointerTy(); + return is_pointer; +} + +extern "C" bool NativityLLVMTypeIsInteger(Type* type) +{ + bool is_integer = type->isIntegerTy(); + return is_integer; +} + +extern "C" StructType* NativityLLVMTypeToStruct(Type* type) +{ + auto* struct_type = dyn_cast(type); + return struct_type; +} + +extern "C" FunctionType* NativityLLVMTypeToFunction(Type* type) +{ + auto* function_type = dyn_cast(type); + return function_type; +} + +extern "C" ArrayType* NativityLLVMTypeToArray(Type* type) +{ + auto* array_type = dyn_cast(type); + return array_type; +} + +extern "C" Type* NativityLLVMArrayTypeGetElementType(ArrayType* array_type) +{ + auto* element_type = array_type->getElementType(); + return element_type; +} + +extern "C" const char* NativityLLVMModuleToString(const Module& module, size_t* len) +{ + std::string buf; + raw_string_ostream os(buf); + module.print(os, nullptr); + os.flush(); + *len = buf.size(); + auto* result = strdup(buf.c_str()); + return result; +} + +extern "C" BasicBlock* NativityLLVMBuilderGetInsertBlock(IRBuilder<>& builder) +{ + return builder.GetInsertBlock(); +} + +extern "C" bool NativityLLVMBuilderIsCurrentBlockTerminated(IRBuilder<>& builder) +{ + return builder.GetInsertBlock()->getTerminator() != nullptr; +} + +extern "C" UndefValue* NativityLLVMGetUndefined(Type* type) +{ + auto* undefined_value = UndefValue::get(type); + return undefined_value; +} + +extern "C" void NativityLLVMFunctionSetCallingConvention(Function& function, CallingConv::ID calling_convention) +{ + function.setCallingConv(calling_convention); +} + +extern "C" CallingConv::ID NativityLLVMFunctionGetCallingConvention(Function& function) +{ + auto calling_convention = function.getCallingConv(); + return calling_convention; +} + +extern "C" void NativityLLVMCallSetCallingConvention(CallBase& call_instruction, CallingConv::ID calling_convention) +{ + call_instruction.setCallingConv(calling_convention); +} + +extern "C" Constant* NativityLLVMGetStruct(StructType* struct_type, Constant** constants_ptr, size_t constant_count) +{ + auto constants = ArrayRef(constants_ptr, constant_count); + auto* named_struct = ConstantStruct::get(struct_type, constants); + return named_struct; +} + +namespace lld { + namespace coff { + bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); + } + namespace elf { + bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); + } + namespace wasm { + bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); + } +} + +extern "C" bool NativityLLVMGenerateMachineCode(Module& module, const char* object_file_path_ptr, size_t object_file_path_len, const char* file_path_ptr, size_t file_path_len) +{ + InitializeAllTargetInfos(); + InitializeAllTargets(); + InitializeAllTargetMCs(); InitializeAllAsmParsers(); + InitializeAllAsmPrinters(); + + auto target_triple = "x86_64-linux-none"; + auto cpu = "generic"; + auto features = ""; + TargetOptions target_options; + + std::string error; + auto* target = TargetRegistry::lookupTarget(target_triple, error); + assert(target); + + auto target_machine = target->createTargetMachine(target_triple, cpu, features, target_options, Reloc::Static); + assert(target_machine); + + module.setDataLayout(target_machine->createDataLayout()); + module.setTargetTriple(target_triple); + +#if 0 + SmallVector bytes; + raw_svector_ostream message_stream(bytes); + auto stream = buffer_ostream(message_stream); +#else + std::error_code EC; + auto object_file_path = StringRef(object_file_path_ptr, object_file_path_len); + raw_fd_ostream stream(object_file_path, EC, sys::fs::OF_None); + if (EC) { + return false; + } + +#endif + legacy::PassManager pass; + bool result = target_machine->addPassesToEmitFile(pass, stream, nullptr, llvm::CGFT_ObjectFile, false); + if (result) { + // We invert the condition because LLVM conventions are just stupid + return false; + } + + pass.run(module); + stream.flush(); + + std::vector args; + args.push_back("ld.lld"); + args.push_back(object_file_path_ptr); + args.push_back("-o"); + args.push_back(file_path_ptr); + + lld::elf::link(args, llvm::outs(), llvm::errs(), true, false); + + return true; +} + +extern "C" bool NativityLLVMCompareTypes(Type* a, Type* b) +{ + if (auto* int_a = dyn_cast(a)) { + auto* int_b = dyn_cast(b); + assert(int_b); + auto a_bit_count = int_a->getBitWidth(); + auto b_bit_count = int_b->getBitWidth(); + assert(a_bit_count == b_bit_count); + } + + return a == b; +} diff --git a/bootstrap/backend/llvm.zig b/bootstrap/backend/llvm.zig new file mode 100644 index 0000000..c19aca6 --- /dev/null +++ b/bootstrap/backend/llvm.zig @@ -0,0 +1,3489 @@ +const std = @import("std"); +const equal = std.mem.eql; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const Compilation = @import("../Compilation.zig"); +const Module = Compilation.Module; +const data_structures = @import("../data_structures.zig"); +const ArrayList = data_structures.ArrayList; +const AutoHashMap = data_structures.AutoHashMap; + +const bindings = @import("llvm_bindings.zig"); + +pub const LLVM = struct { + context: *LLVM.Context, + module: *LLVM.Module, + builder: *LLVM.Builder, + debug_info_builder: ?*LLVM.DebugInfo.Builder, + debug_info_file_map: AutoHashMap(Compilation.File.Index, *LLVM.DebugInfo.File) = .{}, + debug_type_map: AutoHashMap(Compilation.Type.Index, *LLVM.DebugInfo.Type) = .{}, + type_name_map: AutoHashMap(Compilation.Type.Index, []const u8) = .{}, + type_map: AutoHashMap(Compilation.Type.Index, *LLVM.Type) = .{}, + function_definition_map: AutoHashMap(Compilation.Function.Definition.Index, *LLVM.Value.Function) = .{}, + llvm_value_map: AutoHashMap(Compilation.Value.Index, *LLVM.Value) = .{}, + llvm_instruction_map: AutoHashMap(Compilation.Instruction.Index, *LLVM.Value) = .{}, + global_variable_map: AutoHashMap(Compilation.GlobalVariable.Index, *LLVM.Value.Constant.GlobalVariable) = .{}, + scope_map: AutoHashMap(*Compilation.Scope, *LLVM.DebugInfo.Scope) = .{}, + pointer_type: ?*LLVM.Type.Pointer = null, + function: *LLVM.Value.Function = undefined, + exit_block: *LLVM.Value.BasicBlock = undefined, + sema_function: Compilation.Function.Definition.Index = .null, + alloca_map: AutoHashMap(Compilation.Instruction.Index, *LLVM.Value) = .{}, + argument_allocas: AutoHashMap(Compilation.Instruction.Index, *LLVM.Value) = .{}, + declaration_names: AutoHashMap(Compilation.Declaration.Index, []const u8) = .{}, + return_phi_node: ?*LLVM.Value.Instruction.PhiNode = null, + scope: *LLVM.DebugInfo.Scope = undefined, + file: *LLVM.DebugInfo.File = undefined, + arg_index: u32 = 0, + inside_branch: bool = false, + + pub const Linkage = enum(c_uint) { + @"extern" = 0, + }; + + pub const ThreadLocalMode = enum(c_uint) { + not_thread_local = 0, + }; + + pub const Context = opaque { + const create = bindings.NativityLLVMCreateContext; + const createBasicBlock = bindings.NativityLLVMCreateBasicBlock; + const getConstantInt = bindings.NativityLLVMContextGetConstantInt; + const getConstString = bindings.NativityLLVMContextGetConstString; + const getVoidType = bindings.NativityLLVMGetVoidType; + const getIntegerType = bindings.NativityLLVMGetIntegerType; + const getFunctionType = bindings.NativityLLVMGetFunctionType; + const getPointerType = bindings.NativityLLVMGetPointerType; + const createStructType = bindings.NativityLLVMCreateStructType; + const getIntrinsicType = bindings.NativityLLVMContextGetIntrinsicType; + }; + + pub const Module = opaque { + const addGlobalVariable = bindings.NativityLLVMModuleAddGlobalVariable; + const create = bindings.NativityLLVMCreateModule; + const getFunction = bindings.NativityLLVMModuleGetFunction; + const createFunction = bindings.NativityLLVModuleCreateFunction; + const verify = bindings.NativityLLVMVerifyModule; + const toString = bindings.NativityLLVMModuleToString; + const getIntrinsicDeclaration = bindings.NativityLLVMModuleGetIntrinsicDeclaration; + const createDebugInfoBuilder = bindings.NativityLLVMModuleCreateDebugInfoBuilder; + }; + + pub const Builder = opaque { + const create = bindings.NativityLLVMCreateBuilder; + const setInsertPoint = bindings.NativityLLVMBuilderSetInsertPoint; + const createAdd = bindings.NativityLLVMBuilderCreateAdd; + const createAlloca = bindings.NativityLLVMBuilderCreateAlloca; + const createAnd = bindings.NativityLLVMBuilderCreateAnd; + const createOr = bindings.NativityLLVMBuilderCreateOr; + const createCall = bindings.NativityLLVMBuilderCreateCall; + const createCast = bindings.NativityLLVMBuilderCreateCast; + const createBranch = bindings.NativityLLVMBuilderCreateBranch; + const createConditionalBranch = bindings.NativityLLVMBuilderCreateConditionalBranch; + const createGEP = bindings.NativityLLVMBuilderCreateGEP; + const createICmp = bindings.NativityLLVMBuilderCreateICmp; + const createLoad = bindings.NativityLLVMBuilderCreateLoad; + const createMultiply = bindings.NativityLLVMBuilderCreateMultiply; + const createRet = bindings.NativityLLVMBuilderCreateRet; + const createShiftLeft = bindings.NativityLLVMBuilderCreateShiftLeft; + const createArithmeticShiftRight = bindings.NativityLLVMBuilderCreateArithmeticShiftRight; + const createLogicalShiftRight = bindings.NativityLLVMBuilderCreateLogicalShiftRight; + const createStore = bindings.NativityLLVMBuilderCreateStore; + const createSub = bindings.NativityLLVMBuilderCreateSub; + const createUnreachable = bindings.NativityLLVMBuilderCreateUnreachable; + const createXor = bindings.NativityLLVMBuilderCreateXor; + const createUDiv = bindings.NativityLLVMBuilderCreateUDiv; + const createSDiv = bindings.NativityLLVMBuilderCreateSDiv; + const createURem = bindings.NativityLLVMBuilderCreateURem; + const createSRem = bindings.NativityLLVMBuilderCreateSRem; + const createExtractValue = bindings.NativityLLVMBuilderCreateExtractValue; + const createInsertValue = bindings.NativityLLVMBuilderCreateInsertValue; + const createGlobalStringPointer = bindings.NativityLLVMContextCreateGlobalStringPointer; + + const getInsertBlock = bindings.NativityLLVMBuilderGetInsertBlock; + const isCurrentBlockTerminated = bindings.NativityLLVMBuilderIsCurrentBlockTerminated; + const setCurrentDebugLocation = bindings.NativityLLVMBuilderSetCurrentDebugLocation; + }; + + pub const DebugInfo = struct { + pub const AttributeType = enum(c_uint) { + address = 0x01, + boolean = 0x02, + complex_float = 0x03, + float = 0x04, + signed = 0x05, + signed_char = 0x06, + unsigned = 0x07, + unsigned_char = 0x08, + imaginary_float = 0x09, + packed_decimal = 0x0a, + numeric_string = 0x0b, + edited = 0x0c, + signed_fixed = 0x0d, + unsigned_fixed = 0x0e, + decimal_float = 0x0f, + UTF = 0x10, + UCS = 0x11, + ASCII = 0x12, + }; + + pub const CallingConvention = enum(c_uint) { + none = 0, + normal = 0x01, + program = 0x02, + nocall = 0x03, + pass_by_reference = 0x04, + pass_by_value = 0x05, + // Vendor extensions + GNU_renesas_sh = 0x40, + GNU_borland_fastcall_i386 = 0x41, + BORLAND_safecall = 0xb0, + BORLAND_stdcall = 0xb1, + BORLAND_pascal = 0xb2, + BORLAND_msfastcall = 0xb3, + BORLAND_msreturn = 0xb4, + BORLAND_thiscall = 0xb5, + BORLAND_fastcall = 0xb6, + LLVM_vectorcall = 0xc0, + LLVM_Win64 = 0xc1, + LLVM_X86_64SysV = 0xc2, + LLVM_AAPCS = 0xc3, + LLVM_AAPCS_VFP = 0xc4, + LLVM_IntelOclBicc = 0xc5, + LLVM_SpirFunction = 0xc6, + LLVM_OpenCLKernel = 0xc7, + LLVM_Swift = 0xc8, + LLVM_PreserveMost = 0xc9, + LLVM_PreserveAll = 0xca, + LLVM_X86RegCall = 0xcb, + GDB_IBM_OpenCL = 0xff, + }; + + pub const Builder = opaque { + const createCompileUnit = bindings.NativityLLVMDebugInfoBuilderCreateCompileUnit; + const createFile = bindings.NativityLLVMDebugInfoBuilderCreateFile; + const createFunction = bindings.NativityLLVMDebugInfoBuilderCreateFunction; + const createSubroutineType = bindings.NativityLLVMDebugInfoBuilderCreateSubroutineType; + const createLexicalBlock = bindings.NativityLLVMDebugInfoBuilderCreateLexicalBlock; + const createParameterVariable = bindings.NativityLLVMDebugInfoBuilderCreateParameterVariable; + const createAutoVariable = bindings.NativityLLVMDebugInfoBuilderCreateAutoVariable; + const createBasicType = bindings.NativityLLVMDebugInfoBuilderCreateBasicType; + const createPointerType = bindings.NativityLLVMDebugInfoBuilderCreatePointerType; + const createStructType = bindings.NativityLLVMDebugInfoBuilderCreateStructType; + const createArrayType = bindings.NativityLLVMDebugInfoBuilderCreateArrayType; + const createEnumerationType = bindings.NativityLLVMDebugInfoBuilderCreateEnumerationType; + const createEnumerator = bindings.NativityLLVMDebugInfoBuilderCreateEnumerator; + const createReplaceableCompositeType = bindings.NativityLLVMDebugInfoBuilderCreateReplaceableCompositeType; + const insertDeclare = bindings.NativityLLVMDebugInfoBuilderInsertDeclare; + const finalizeSubprogram = bindings.NativityLLVMDebugInfoBuilderFinalizeSubprogram; + const finalize = bindings.NativityLLVMDebugInfoBuilderFinalize; + }; + + pub const CompileUnit = opaque { + fn toScope(this: *@This()) *Scope { + return @ptrCast(this); + } + + pub const EmissionKind = enum(c_uint) { + no_debug = 0, + full_debug = 1, + line_tables_only = 2, + debug_directives_only = 3, + }; + + pub const NameTableKind = enum(c_uint) { + default = 0, + gnu = 1, + none = 2, + }; + }; + + pub const LocalVariable = opaque {}; + pub const LexicalBlock = opaque { + fn toScope(this: *@This()) *Scope { + return @ptrCast(this); + } + }; + + pub const Node = opaque { + pub const Flags = packed struct(u32) { + visibility: Visibility, + forward_declaration: bool, + apple_block: bool, + block_by_ref_struct: bool, + virtual: bool, + artificial: bool, + explicit: bool, + prototyped: bool, + objective_c_class_complete: bool, + object_pointer: bool, + vector: bool, + static_member: bool, + lvalue_reference: bool, + rvalue_reference: bool, + reserved: bool = false, + inheritance: Inheritance, + introduced_virtual: bool, + bit_field: bool, + no_return: bool, + type_pass_by_value: bool, + type_pass_by_reference: bool, + enum_class: bool, + thunk: bool, + non_trivial: bool, + big_endian: bool, + little_endian: bool, + all_calls_described: bool, + _: u3 = 0, + + const Visibility = enum(u2) { + none = 0, + private = 1, + protected = 2, + public = 3, + }; + const Inheritance = enum(u2) { + none = 0, + single = 1, + multiple = 2, + virtual = 3, + }; + }; + }; + + pub const File = opaque { + fn toScope(this: *@This()) *Scope { + return @ptrCast(this); + } + }; + + pub const Language = enum(c_uint) { + c = 0x02, + }; + + pub const Scope = opaque { + const toSubprogram = bindings.NativityLLVMDebugInfoScopeToSubprogram; + }; + pub const LocalScope = opaque { + fn toScope(this: *@This()) *Scope { + return @ptrCast(this); + } + }; + pub const Subprogram = opaque { + const getFile = bindings.NativityLLVMDebugInfoSubprogramGetFile; + const getArgumentType = bindings.NativityLLVMDebugInfoSubprogramGetArgumentType; + fn toLocalScope(this: *@This()) *LocalScope { + return @ptrCast(this); + } + + pub const Flags = packed struct(u32) { + virtuality: Virtuality, + local_to_unit: bool, + definition: bool, + optimized: bool, + pure: bool, + elemental: bool, + recursive: bool, + main_subprogram: bool, + deleted: bool, + reserved: bool = false, + object_c_direct: bool, + _: u20 = 0, + + const Virtuality = enum(u2) { + none = 0, + virtual = 1, + pure_virtual = 2, + }; + }; + }; + + pub const SubroutineType = opaque {}; + pub const Type = opaque { + pub const Derived = opaque { + fn toType(this: *@This()) *LLVM.DebugInfo.Type { + return @ptrCast(this); + } + }; + + pub const Composite = opaque { + fn toType(this: *@This()) *LLVM.DebugInfo.Type { + return @ptrCast(this); + } + }; + + pub const Enumerator = opaque {}; + }; + }; + + const lookupIntrinsic = bindings.NativityLLVMLookupIntrinsic; + const newPhiNode = bindings.NativityLLVMCreatePhiNode; + + pub const Metadata = opaque { + pub const Node = opaque {}; + }; + + pub const Attribute = enum(u32) { + AllocAlign = 1, + AllocatedPointer = 2, + AlwaysInline = 3, + Builtin = 4, + Cold = 5, + Convergent = 6, + DisableSanitizerInstrumentation = 7, + FnRetThunkExtern = 8, + Hot = 9, + ImmArg = 10, + InReg = 11, + InlineHint = 12, + JumpTable = 13, + MinSize = 14, + MustProgress = 15, + Naked = 16, + Nest = 17, + NoAlias = 18, + NoBuiltin = 19, + NoCallback = 20, + NoCapture = 21, + NoCfCheck = 22, + NoDuplicate = 23, + NoFree = 24, + NoImplicitFloat = 25, + NoInline = 26, + NoMerge = 27, + NoProfile = 28, + NoRecurse = 29, + NoRedZone = 30, + NoReturn = 31, + NoSanitizeBounds = 32, + NoSanitizeCoverage = 33, + NoSync = 34, + NoUndef = 35, + NoUnwind = 36, + NonLazyBind = 37, + NonNull = 38, + NullPointerIsValid = 39, + OptForFuzzing = 40, + OptimizeForSize = 41, + OptimizeNone = 42, + PresplitCoroutine = 43, + ReadNone = 44, + ReadOnly = 45, + Returned = 46, + ReturnsTwice = 47, + SExt = 48, + SafeStack = 49, + SanitizeAddress = 50, + SanitizeHWAddress = 51, + SanitizeMemTag = 52, + SanitizeMemory = 53, + SanitizeThread = 54, + ShadowCallStack = 55, + SkipProfile = 56, + Speculatable = 57, + SpeculativeLoadHardening = 58, + StackProtect = 59, + StackProtectReq = 60, + StackProtectStrong = 61, + StrictFP = 62, + SwiftAsync = 63, + SwiftError = 64, + SwiftSelf = 65, + WillReturn = 66, + WriteOnly = 67, + ZExt = 68, + ByRef = 69, + ByVal = 70, + ElementType = 71, + InAlloca = 72, + Preallocated = 73, + StructRet = 74, + Alignment = 75, + AllocKind = 76, + AllocSize = 77, + Dereferenceable = 78, + DereferenceableOrNull = 79, + Memory = 80, + StackAlignment = 81, + UWTable = 82, + VScaleRange = 83, + }; + + pub const Type = opaque { + const compare = bindings.NativityLLVMCompareTypes; + const toStruct = bindings.NativityLLVMTypeToStruct; + const toFunction = bindings.NativityLLVMTypeToFunction; + const toArray = bindings.NativityLLVMTypeToArray; + const isPointer = bindings.NativityLLVMTypeIsPointer; + const isInteger = bindings.NativityLLVMTypeIsInteger; + + pub const Array = opaque { + fn toType(integer: *@This()) *Type { + return @ptrCast(integer); + } + const get = bindings.NativityLLVMGetArrayType; + const getConstant = bindings.NativityLLVMContextGetConstArray; + const getElementType = bindings.NativityLLVMArrayTypeGetElementType; + }; + + pub const Integer = opaque { + fn toType(integer: *@This()) *Type { + return @ptrCast(integer); + } + }; + + pub const Function = opaque { + fn toType(integer: *@This()) *Type { + return @ptrCast(integer); + } + }; + + pub const Pointer = opaque { + fn toType(integer: *@This()) *Type { + return @ptrCast(integer); + } + }; + + pub const Struct = opaque { + const instantiate = bindings.NativityLLVMGetStruct; + const instantiateConstant = bindings.NativityLLVMConstantStruct; + fn toType(integer: *@This()) *Type { + return @ptrCast(integer); + } + }; + + pub const Error = error{ + void, + function, + integer, + pointer, + @"struct", + intrinsic, + array, + }; + + const getUndefined = bindings.NativityLLVMGetUndefined; + }; + + pub const Value = opaque { + const setName = bindings.NativityLLVMValueSetName; + const getType = bindings.NativityLLVMValueGetType; + const toConstant = bindings.NativityLLVMValueToConstant; + const toFunction = bindings.NativityLLVMValueToFunction; + const toAlloca = bindings.NativityLLVMValueToAlloca; + + const IntrinsicID = enum(u32) { + none = 0, + _, + }; + + pub const Function = opaque { + const getArguments = bindings.NativityLLVMFunctionGetArguments; + const getReturnType = bindings.NativityLLVMFunctionGetReturnType; + const addAttributeKey = bindings.NativityLLVMFunctionAddAttributeKey; + const verify = bindings.NativityLLVMVerifyFunction; + const toString = bindings.NativityLLVMFunctionToString; + const setCallingConvention = bindings.NativityLLVMFunctionSetCallingConvention; + const getCallingConvention = bindings.NativityLLVMFunctionGetCallingConvention; + const setSubprogram = bindings.NativityLLVMFunctionSetSubprogram; + const getSubprogram = bindings.NativityLLVMFunctionGetSubprogram; + + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + + pub const CallingConvention = enum(c_uint) { + /// The default llvm calling convention, compatible with C. This convention + /// is the only one that supports varargs calls. As with typical C calling + /// conventions, the callee/caller have to tolerate certain amounts of + /// prototype mismatch. + C = 0, + + // Generic LLVM calling conventions. None of these support varargs calls, + // and all assume that the caller and callee prototype exactly match. + + /// Attempts to make calls as fast as possible (e.g. by passing things in + /// registers). + Fast = 8, + + /// Attempts to make code in the caller as efficient as possible under the + /// assumption that the call is not commonly executed. As such, these calls + /// often preserve all registers so that the call does not break any live + /// ranges in the caller side. + Cold = 9, + + /// Used by the Glasgow Haskell Compiler (GHC). + GHC = 10, + + /// Used by the High-Performance Erlang Compiler (HiPE). + HiPE = 11, + + /// Used for stack based JavaScript calls + WebKit_JS = 12, + + /// Used for dynamic register based calls (e.g. stackmap and patchpoint + /// intrinsics). + AnyReg = 13, + + /// Used for runtime calls that preserves most registers. + PreserveMost = 14, + + /// Used for runtime calls that preserves (almost) all registers. + PreserveAll = 15, + + /// Calling convention for Swift. + Swift = 16, + + /// Used for access functions. + CXX_FAST_TLS = 17, + + /// Attemps to make calls as fast as possible while guaranteeing that tail + /// call optimization can always be performed. + Tail = 18, + + /// Special calling convention on Windows for calling the Control Guard + /// Check ICall funtion. The function takes exactly one argument (address of + /// the target function) passed in the first argument register, and has no + /// return value. All register values are preserved. + CFGuard_Check = 19, + + /// This follows the Swift calling convention in how arguments are passed + /// but guarantees tail calls will be made by making the callee clean up + /// their stack. + SwiftTail = 20, + + /// This is the start of the target-specific calling conventions, e.g. + /// fastcall and thiscall on X86. + // FirstTargetCC = 64, + + /// stdcall is mostly used by the Win32 API. It is basically the same as the + /// C convention with the difference in that the callee is responsible for + /// popping the arguments from the stack. + X86_StdCall = 64, + + /// 'fast' analog of X86_StdCall. Passes first two arguments in ECX:EDX + /// registers, others - via stack. Callee is responsible for stack cleaning. + X86_FastCall = 65, + + /// ARM Procedure Calling Standard (obsolete, but still used on some + /// targets). + ARM_APCS = 66, + + /// ARM Architecture Procedure Calling Standard calling convention (aka + /// EABI). Soft float variant. + ARM_AAPCS = 67, + + /// Same as ARM_AAPCS, but uses hard floating point ABI. + ARM_AAPCS_VFP = 68, + + /// Used for MSP430 interrupt routines. + MSP430_INTR = 69, + + /// Similar to X86_StdCall. Passes first argument in ECX, others via stack. + /// Callee is responsible for stack cleaning. MSVC uses this by default for + /// methods in its ABI. + X86_ThisCall = 70, + + /// Call to a PTX kernel. Passes all arguments in parameter space. + PTX_Kernel = 71, + + /// Call to a PTX device function. Passes all arguments in register or + /// parameter space. + PTX_Device = 72, + + /// Used for SPIR non-kernel device functions. No lowering or expansion of + /// arguments. Structures are passed as a pointer to a struct with the + /// byval attribute. Functions can only call SPIR_FUNC and SPIR_KERNEL + /// functions. Functions can only have zero or one return values. Variable + /// arguments are not allowed, except for printf. How arguments/return + /// values are lowered are not specified. Functions are only visible to the + /// devices. + SPIR_FUNC = 75, + + /// Used for SPIR kernel functions. Inherits the restrictions of SPIR_FUNC, + /// except it cannot have non-void return values, it cannot have variable + /// arguments, it can also be called by the host or it is externally + /// visible. + SPIR_KERNEL = 76, + + /// Used for Intel OpenCL built-ins. + Intel_OCL_BI = 77, + + /// The C convention as specified in the x86-64 supplement to the System V + /// ABI, used on most non-Windows systems. + X86_64_SysV = 78, + + /// The C convention as implemented on Windows/x86-64 and AArch64. It + /// differs from the more common \c X86_64_SysV convention in a number of + /// ways, most notably in that XMM registers used to pass arguments are + /// shadowed by GPRs, and vice versa. On AArch64, this is identical to the + /// normal C (AAPCS) calling convention for normal functions, but floats are + /// passed in integer registers to variadic functions. + Win64 = 79, + + /// MSVC calling convention that passes vectors and vector aggregates in SSE + /// registers. + X86_VectorCall = 80, + + /// Used by HipHop Virtual Machine (HHVM) to perform calls to and from + /// translation cache, and for calling PHP functions. HHVM calling + /// convention supports tail/sibling call elimination. + HHVM = 81, + + /// HHVM calling convention for invoking C/C++ helpers. + HHVM_C = 82, + + /// x86 hardware interrupt context. Callee may take one or two parameters, + /// where the 1st represents a pointer to hardware context frame and the 2nd + /// represents hardware error code, the presence of the later depends on the + /// interrupt vector taken. Valid for both 32- and 64-bit subtargets. + X86_INTR = 83, + + /// Used for AVR interrupt routines. + AVR_INTR = 84, + + /// Used for AVR signal routines. + AVR_SIGNAL = 85, + + /// Used for special AVR rtlib functions which have an "optimized" + /// convention to preserve registers. + AVR_BUILTIN = 86, + + /// Used for Mesa vertex shaders, or AMDPAL last shader stage before + /// rasterization (vertex shader if tessellation and geometry are not in + /// use, or otherwise copy shader if one is needed). + AMDGPU_VS = 87, + + /// Used for Mesa/AMDPAL geometry shaders. + AMDGPU_GS = 88, + + /// Used for Mesa/AMDPAL pixel shaders. + AMDGPU_PS = 89, + + /// Used for Mesa/AMDPAL compute shaders. + AMDGPU_CS = 90, + + /// Used for AMDGPU code object kernels. + AMDGPU_KERNEL = 91, + + /// Register calling convention used for parameters transfer optimization + X86_RegCall = 92, + + /// Used for Mesa/AMDPAL hull shaders (= tessellation control shaders). + AMDGPU_HS = 93, + + /// Used for special MSP430 rtlib functions which have an "optimized" + /// convention using additional registers. + MSP430_BUILTIN = 94, + + /// Used for AMDPAL vertex shader if tessellation is in use. + AMDGPU_LS = 95, + + /// Used for AMDPAL shader stage before geometry shader if geometry is in + /// use. So either the domain (= tessellation evaluation) shader if + /// tessellation is in use, or otherwise the vertex shader. + AMDGPU_ES = 96, + + /// Used between AArch64 Advanced SIMD functions + AArch64_VectorCall = 97, + + /// Used between AArch64 SVE functions + AArch64_SVE_VectorCall = 98, + + /// For emscripten __invoke_* functions. The first argument is required to + /// be the function ptr being indirectly called. The remainder matches the + /// regular calling convention. + WASM_EmscriptenInvoke = 99, + + /// Used for AMD graphics targets. + AMDGPU_Gfx = 100, + + /// Used for M68k interrupt routines. + M68k_INTR = 101, + + /// Preserve X0-X13, X19-X29, SP, Z0-Z31, P0-P15. + AArch64_SME_ABI_Support_Routines_PreserveMost_From_X0 = 102, + + /// Preserve X2-X15, X19-X29, SP, Z0-Z31, P0-P15. + AArch64_SME_ABI_Support_Routines_PreserveMost_From_X2 = 103, + + /// The highest possible ID. Must be some 2^k - 1. + MaxID = 1023, + }; + }; + + pub const BasicBlock = opaque { + const remove = bindings.NativityLLVMBasicBlockRemoveFromParent; + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Argument = opaque { + const getIndex = bindings.NativityLLVMArgumentGetIndex; + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Instruction = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + + pub const Alloca = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + + const getAllocatedType = bindings.NativityLLVMAllocatGetAllocatedType; + }; + + pub const Branch = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Call = opaque { + const setCallingConvention = bindings.NativityLLVMCallSetCallingConvention; + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Cast = opaque { + pub const Type = enum(c_uint) { + truncate = 38, + zero_extend = 39, + sign_extend = 40, + float_to_unsigned_integer = 41, + float_to_signed_integer = 42, + unsigned_integer_to_float = 43, + signed_integer_to_float = 44, + float_truncate = 45, + float_extend = 46, + pointer_to_int = 47, + int_to_pointer = 48, + bitcast = 49, + address_space_cast = 50, + }; + + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const ICmp = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + const Kind = enum(c_uint) { + eq = 32, // equal + ne = 33, // not equal + ugt = 34, // unsigned greater than + uge = 35, // unsigned greater or equal + ult = 36, // unsigned less than + ule = 37, // unsigned less or equal + sgt = 38, // signed greater than + sge = 39, // signed greater or equal + slt = 40, // signed less than + sle = 41, // signed less or equal + }; + }; + + pub const Load = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const PhiNode = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Store = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Ret = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Unreachable = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Error = error{ + add, + alloca, + @"and", + arithmetic_shift_right, + call, + cast, + conditional_branch, + extract_value, + gep, + icmp, + insert_value, + load, + logical_shift_right, + multiply, + @"or", + ret, + sdiv, + shift_left, + store, + udiv, + @"unreachable", + xor, + }; + }; + + pub const Constant = opaque { + pub const Int = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + fn toConstant(this: *@This()) *Constant { + return @ptrCast(this); + } + }; + + pub const GlobalVariable = opaque { + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + fn toConstant(this: *@This()) *Constant { + return @ptrCast(this); + } + }; + + pub const Undefined = opaque { + fn toConstant(this: *@This()) *Constant { + return @ptrCast(this); + } + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const InlineAssembly = opaque { + pub const Dialect = enum(c_uint) { + @"at&t", + intel, + }; + const get = bindings.NativityLLVMGetInlineAssembly; + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + + pub const Error = error{ + constant_struct, + constant_int, + constant_array, + inline_assembly, + global_variable, + intrinsic, + }; + }; + + fn getType(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, original_type_index: Compilation.Type.Index) !*LLVM.Type { + const type_index = unit.unwrapTypeCopy(original_type_index); + if (llvm.type_map.get(type_index)) |llvm_type| { + return llvm_type; + } else { + const sema_type = unit.types.get(type_index); + + const llvm_type: *LLVM.Type = switch (sema_type.*) { + .function => |function_prototype_index| blk: { + const sema_function_prototype = unit.function_prototypes.get(function_prototype_index); + const llvm_return_type = try llvm.getType(unit, context, sema_function_prototype.return_type); + var parameter_types = try ArrayList(*LLVM.Type).initCapacity(context.allocator, sema_function_prototype.argument_types.len); + + for (sema_function_prototype.argument_types) |sema_argument_type_index| { + const argument_type_index = unit.unwrapTypeCopy(sema_argument_type_index); + switch (unit.types.get(argument_type_index).*) { + .integer, .pointer, .@"enum" => try parameter_types.append(context.allocator, try llvm.getType(unit, context, sema_argument_type_index)), + // .slice => |slice| { + // const pointer_type = try llvm.getType(llvm.sema.map.pointers.get(.{ + // .many = true, + // .@"const" = slice.@"const", + // .termination = slice.termination, + // .element_type = slice.element_type, + // }).?); + // const usize_type = try llvm.getType(Compilation.Type.usize); + // try parameter_types.append(context.allocator, pointer_type); + // try parameter_types.append(context.allocator, usize_type); + // }, + // .@"struct" => |struct_index| { + // const struct_type = llvm.sema.types.structs.get(struct_index); + // if (!struct_type.backing_type.invalid) { + // unreachable; + // } else { + // for (struct_type.fields.items) |field_index| { + // const field = llvm.sema.types.container_fields.get(field_index); + // const debug_type = try llvm.getType(field.type); + // try parameter_types.append(context.allocator, debug_type); + // } + // } + // }, + else => |t| @panic(@tagName(t)), + } + // arg_types.appendAssumeCapacity(llvm_argument_type); + } + + const is_var_args = false; + const llvm_function_type = LLVM.Context.getFunctionType(llvm_return_type, parameter_types.items.ptr, parameter_types.items.len, is_var_args) orelse return Type.Error.function; + break :blk llvm_function_type.toType(); + }, + .bool => blk: { + const bit_count = 1; + const llvm_integer_type = llvm.context.getIntegerType(bit_count) orelse return Type.Error.integer; + break :blk llvm_integer_type.toType(); + }, + .integer => |integer| blk: { + const llvm_integer_type = llvm.context.getIntegerType(integer.bit_count) orelse return Type.Error.integer; + break :blk llvm_integer_type.toType(); + }, + .pointer => { + if (llvm.pointer_type) |pointer_type| { + return pointer_type.toType(); + } else { + const pointer_type = llvm.context.getPointerType(address_space) orelse return Type.Error.pointer; + llvm.pointer_type = pointer_type; + return pointer_type.toType(); + } + }, + .noreturn, + .void, + => blk: { + const void_type = llvm.context.getVoidType() orelse return Type.Error.void; + break :blk void_type; + }, + .@"enum" => |enum_index| blk: { + const enum_type = unit.enums.get(enum_index); + // switch (enum_type.backing_type.invalid) { + // true => { + const field_count = enum_type.fields.items.len; + const bit_count = @bitSizeOf(@TypeOf(field_count)) - @clz(field_count); + const real_bit_count: u32 = if (bit_count <= 8) 8 else if (bit_count <= 16) 16 else if (bit_count <= 32) 32 else if (bit_count <= 64) 64 else unreachable; + const llvm_integer_type = llvm.context.getIntegerType(real_bit_count) orelse return Type.Error.integer; + break :blk llvm_integer_type.toType(); + // }, + // false => break :blk try llvm.getType(enum_type.backing_type), + // } + }, + // .slice => |slice| blk: { + // const sema_slice_pointer = Compilation.Type.Pointer{ + // .element_type = slice.element_type, + // .many = true, + // .@"const" = slice.@"const", + // .termination = slice.termination, + // }; + // const sema_pointer_type = llvm.sema.map.pointers.get(sema_slice_pointer).?; + // const llvm_pointer_type = try llvm.getType(sema_pointer_type); + // const llvm_usize_type = try llvm.getType(Compilation.Type.usize); + // const slice_types = [_]*Type{ llvm_pointer_type, llvm_usize_type }; + // const name = [_]u8{}; + // const is_packed = false; + // const struct_type = llvm.context.createStructType(&slice_types, slice_types.len, &name, name.len, is_packed) orelse return Type.Error.@"struct"; + // break :blk struct_type.toType(); + // }, + // .@"struct" => |struct_type_index| blk: { + // const sema_struct_type = llvm.sema.types.structs.get(struct_type_index); + // switch (sema_struct_type.backing_type.invalid) { + // true => { + // var field_type_list = try ArrayList(*LLVM.Type).initCapacity(context.allocator, sema_struct_type.fields.items.len); + // for (sema_struct_type.fields.items) |sema_field_index| { + // const sema_field = llvm.sema.types.container_fields.get(sema_field_index); + // const llvm_type = try llvm.getType(sema_field.type); + // field_type_list.appendAssumeCapacity(llvm_type); + // } + // + // const struct_name: []const u8 = if (llvm.sema.map.types.get(type_index)) |declaration_index| b: { + // const declaration = llvm.sema.values.declarations.get(declaration_index); + // const name = llvm.sema.getName(declaration.name).?; + // break :b name; + // } else "anonymous_struct"; + // const is_packed = true; + // const struct_type = llvm.context.createStructType(field_type_list.items.ptr, field_type_list.items.len, struct_name.ptr, struct_name.len, is_packed) orelse return Type.Error.@"struct"; + // + // break :blk struct_type.toType(); + // }, + // else => |b| @panic(if (b) "true" else "false"), + // } + // }, + // .optional => |optional| blk: { + // switch (llvm.sema.types.array.get(optional.element_type).*) { + // .pointer => |pointer| { + // _ = pointer; + // + // unreachable; + // }, + // else => { + // const element_type = try llvm.getType(optional.element_type); + // const selector_type = try llvm.getType(Compilation.Type.boolean); + // const optional_types = [2]*LLVM.Type{ element_type, selector_type }; + // const name = "optional_type"; + // const is_packed = false; + // const struct_type = llvm.context.createStructType(&optional_types, optional_types.len, name, name.len, is_packed) orelse return Type.Error.@"struct"; + // break :blk struct_type.toType(); + // }, + // } + // }, + // .array => |array| blk: { + // const element_type = try llvm.getType(array.element_type); + // const array_type = LLVM.Type.Array.get(element_type, array.element_count) orelse return Type.Error.array; + // break :blk array_type.toType(); + // }, + else => |t| @panic(@tagName(t)), + }; + + try llvm.type_map.putNoClobber(context.allocator, type_index, llvm_type); + + return llvm_type; + } + } + + // fn emitDeclaration(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, declaration_index: Compilation.Declaration.Index, maybe_argument: ?*LLVM.Value.Argument) !*LLVM.Value { + // _ = unit; // autofix + // const sema_declaration = llvm.sema.values.declarations.get(declaration_index); + // const declaration_name = llvm.sema.getName(sema_declaration.name).?; + // + // const sema_declaration_type_index = sema_declaration.getType(); + // + // const declaration_type = if (maybe_argument) |argument| blk: { + // const argument_value: *LLVM.Value = argument.toValue(); + // break :blk argument_value.getType(); + // } else try llvm.getType(sema_declaration_type_index); + // const is_volatile = false; + // + // const initial_value: ?*LLVM.Value = if (maybe_argument) |argument| blk: { + // assert(sema_declaration.init_value.invalid); + // const argument_value: *LLVM.Value = argument.toValue(); + // argument_value.setName(declaration_name.ptr, declaration_name.len); + // break :blk argument_value; + // } else blk: { + // if (!sema_declaration.init_value.invalid) { + // assert(maybe_argument == null); + // break :blk switch (llvm.sema.values.array.get(sema_declaration.init_value).*) { + // .undefined => null, + // else => try llvm.emitValue(sema_declaration.init_value, sema_declaration.scope_type), + // }; + // } else { + // assert(maybe_argument != null); + // break :blk null; + // } + // }; + // + // const declaration_value: *Value = switch (sema_declaration.scope_type) { + // .local => blk: { + // const sema_declaration_type = llvm.sema.types.array.get(sema_declaration_type_index); + // const alloca_array_size: ?*LLVM.Value = switch (sema_declaration_type.*) { + // else => null, + // .array => |array| b: { + // const is_signed = false; + // const array_len = llvm.context.getConstantInt(64, array.element_count, is_signed) orelse unreachable; + // break :b array_len.toValue(); + // }, + // }; + // + // const declaration_alloca = llvm.builder.createAlloca(declaration_type, address_space, alloca_array_size, declaration_name.ptr, declaration_name.len) orelse return LLVM.Value.Instruction.Error.alloca; + // const alloca_value: *LLVM.Value = declaration_alloca.toValue(); + // + // const debug_declaration_type = try llvm.getDebugType(sema_declaration.getType()); + // const always_preserve = true; + // const flags = LLVM.DebugInfo.Node.Flags{ + // .visibility = .none, + // .forward_declaration = false, + // .apple_block = false, + // .block_by_ref_struct = false, + // .virtual = false, + // .artificial = false, + // .explicit = false, + // .prototyped = false, + // .objective_c_class_complete = false, + // .object_pointer = false, + // .vector = false, + // .static_member = false, + // .lvalue_reference = false, + // .rvalue_reference = false, + // .reserved = false, + // .inheritance = .none, + // .introduced_virtual = false, + // .bit_field = false, + // .no_return = false, + // .type_pass_by_value = false, + // .type_pass_by_reference = false, + // .enum_class = false, + // .thunk = false, + // .non_trivial = false, + // .big_endian = false, + // .little_endian = false, + // .all_calls_described = false, + // }; + // const local_variable = if (maybe_argument) |argument| b: { + // const argument_index = argument.getIndex(); + // const parameter_variable = llvm.debug_info_builder.createParameterVariable(llvm.scope, declaration_name.ptr, declaration_name.len, argument_index + 1, llvm.file, sema_declaration.line, debug_declaration_type, always_preserve, flags) orelse unreachable; + // break :b parameter_variable; + // } else b: { + // // TODO: + // const alignment = 0; + // const auto_variable = llvm.debug_info_builder.createAutoVariable(llvm.scope, declaration_name.ptr, declaration_name.len, llvm.file, sema_declaration.line, debug_declaration_type, always_preserve, flags, alignment) orelse unreachable; + // break :b auto_variable; + // }; + // + // const insert_declare = llvm.debug_info_builder.insertDeclare(alloca_value, local_variable, llvm.context, sema_declaration.line, sema_declaration.column, (llvm.function.getSubprogram() orelse unreachable).toLocalScope().toScope(), llvm.builder.getInsertBlock() orelse unreachable); + // _ = insert_declare; + // + // if (initial_value) |init_value| { + // const store = llvm.builder.createStore(init_value, alloca_value, is_volatile) orelse return LLVM.Value.Instruction.Error.store; + // _ = store; + // } + // + // break :blk alloca_value; + // }, + // .global => blk: { + // const is_constant = switch (sema_declaration.mutability) { + // .@"const" => true, + // .@"var" => false, + // }; + // // TODO: + // const linkage = LLVM.Linkage.@"extern"; + // const initializer: *LLVM.Value.Constant = if (initial_value) |value| value.toConstant() orelse unreachable else (declaration_type.getUndefined() orelse unreachable).toConstant(); + // const thread_local_mode = LLVM.ThreadLocalMode.not_thread_local; + // const externally_initialized = false; + // const global_variable = llvm.module.addGlobalVariable(declaration_type, is_constant, linkage, initializer, declaration_name.ptr, declaration_name.len, null, thread_local_mode, address_space, externally_initialized) orelse return LLVM.Value.Error.constant_int; + // break :blk global_variable.toValue(); + // }, + // else => unreachable, + // }; + // + // try llvm.declaration_map.putNoClobber(context.allocator, declaration_index, declaration_value); + // + // return declaration_value; + // } + + fn emitLValue(llvm: *LLVM, sema_value_index: Compilation.Value.Index, context: Compilation.ScopeType) anyerror!*LLVM.Value { + const sema_value = llvm.sema.values.array.get(sema_value_index); + + switch (sema_value.*) { + .declaration_reference => |declaration_reference| { + const declaration_index = declaration_reference.value; + return try llvm.getDeclaration(declaration_index); + }, + .indexed_access => |indexed_access_index| { + const indexed_access = llvm.sema.values.indexed_accesses.get(indexed_access_index); + const indexed = try llvm.emitValue(indexed_access.indexed_expression, context); + const index = try llvm.emitValue(indexed_access.index_expression, context); + const indexed_value = llvm.sema.values.array.get(indexed_access.indexed_expression); + const indexed_type = indexed_value.getType(llvm.sema); + switch (llvm.sema.types.array.get(indexed_type).*) { + .pointer => |pointer| { + const element_type = try llvm.getType(pointer.element_type); + const is_signed = false; + const index_zero = llvm.context.getConstantInt(32, 0, is_signed) orelse unreachable; + const indices = [2]*LLVM.Value{ index_zero.toValue(), index }; + const in_bounds = true; + const gep = llvm.builder.createGEP(element_type, indexed, &indices, indices.len, "gep", "gep".len, in_bounds) orelse return LLVM.Value.Instruction.Error.gep; + return gep; + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + } + } + + fn getDeclaration(llvm: *LLVM, declaration_index: Compilation.Declaration.Index) anyerror!*LLVM.Value { + if (llvm.declaration_map.get(declaration_index)) |declaration_value| { + return declaration_value; + } else { + // This is a global variable + const declaration_value = try llvm.emitDeclaration(declaration_index, null); + return declaration_value; + } + } + + fn emitInteger(llvm: *LLVM, unit: *Compilation.Unit, integer: Compilation.Value.Integer) !*LLVM.Value.Constant.Int { + const t = unit.types.get(integer.type); + const integer_type = t.integer; + const bit_count = integer_type.bit_count; + const signed = switch (integer_type.signedness) { + .signed => true, + .unsigned => false, + }; + const constant_integer = llvm.context.getConstantInt(bit_count, integer.value, signed) orelse return LLVM.Value.Error.constant_int; + return constant_integer; + } + + // fn emitValue(llvm: *LLVM, sema_value_index: Compilation.Value.Index, context: Compilation.ScopeType) anyerror!*LLVM.Value { + // const sema_value = llvm.sema.values.array.get(sema_value_index); + // const sema_type = sema_value.getType(llvm.sema); + // + // switch (sema_value.*) { + // .integer => |integer| { + // const bit_count = llvm.sema.types.array.get(integer.type).integer.bit_count; + // const constant_int = llvm.context.getConstantInt(bit_count, integer.value, switch (integer.signedness) { + // .unsigned => false, + // .signed => true, + // }) orelse return LLVM.Value.Error.constant_int; + // return constant_int.toValue(); + // }, + // .binary_operation => |binary_operation_index| { + // const binary_operation = llvm.sema.values.binary_operations.get(binary_operation_index); + // + // const sema_left_value = llvm.sema.values.array.get(binary_operation.left); + // const sema_left_type_index = sema_left_value.getType(llvm.sema); + // const sema_right_type_index = llvm.sema.values.array.get(binary_operation.right).getType(llvm.sema); + // assert(sema_left_type_index.eq(sema_right_type_index)); + // const sema_left_type = llvm.sema.types.array.get(sema_left_type_index); + // + // const expected_left_type = try llvm.getType(sema_left_type_index); + // + // const left = try llvm.emitValue(binary_operation.left, context); + // assert(left.getType() == expected_left_type); + // const right = try llvm.emitValue(binary_operation.right, context); + // assert(right.getType() == expected_left_type); + // + // switch (binary_operation.id) { + // .compare_less_than, + // .compare_greater_or_equal, + // .compare_equal, + // => { + // switch (sema_left_type.*) { + // .integer => |integer| { + // const integer_comparison: LLVM.Value.Instruction.ICmp.Kind = switch (integer.signedness) { + // .signed => switch (binary_operation.id) { + // .compare_less_than => .slt, + // .compare_greater_or_equal => .sge, + // .compare_equal => .eq, + // else => |t| @panic(@tagName(t)), + // }, + // .unsigned => switch (binary_operation.id) { + // .compare_less_than => .ult, + // .compare_greater_or_equal => .uge, + // .compare_equal => .eq, + // else => |t| @panic(@tagName(t)), + // }, + // }; + // const icmp = llvm.builder.createICmp(integer_comparison, left, right, "cmp", "cmp".len) orelse return LLVM.Value.Instruction.Error.icmp; + // return icmp; + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .add, + // .sub, + // .mul, + // .shift_left, + // => { + // switch (sema_left_type.*) { + // .integer => |integer_type| { + // const result = try llvm.arithmeticIntegerBinaryOperation(left, right, binary_operation.id, integer_type, "binary_operation"); + // return result; + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .div => { + // switch (sema_left_type.*) { + // .integer => |integer_type| { + // const is_exact = false; + // const result = switch (integer_type.signedness) { + // .unsigned => llvm.builder.createUDiv(left, right, "udiv", "udiv".len, is_exact) orelse return LLVM.Value.Instruction.Error.udiv, + // .signed => llvm.builder.createSDiv(left, right, "sdiv", "sdiv".len, is_exact) orelse return LLVM.Value.Instruction.Error.sdiv, + // }; + // + // return result; + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .mod => { + // switch (sema_left_type.*) { + // .integer => |integer_type| { + // const result = switch (integer_type.signedness) { + // .unsigned => llvm.builder.createURem(left, right, "urem", "urem".len) orelse return LLVM.Value.Instruction.Error.udiv, + // .signed => llvm.builder.createSRem(left, right, "srem", "srem".len) orelse return LLVM.Value.Instruction.Error.sdiv, + // }; + // + // return result; + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .shift_right => { + // switch (sema_left_type.*) { + // .integer => |integer_type| { + // const is_exact = false; + // const result = switch (integer_type.signedness) { + // .unsigned => llvm.builder.createLogicalShiftRight(left, right, "logical_shift_right", "logical_shift_right".len, is_exact) orelse return LLVM.Value.Instruction.Error.logical_shift_right, + // .signed => llvm.builder.createArithmeticShiftRight(left, right, "arithmetic_shift_right", "arithmetic_shift_right".len, is_exact) orelse return LLVM.Value.Instruction.Error.arithmetic_shift_right, + // }; + // + // return result; + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .bit_xor => { + // const xor = llvm.builder.createXor(left, right, "xor", "xor".len) orelse return LLVM.Value.Instruction.Error.xor; + // return xor; + // }, + // .bit_and => { + // const result = llvm.builder.createAnd(left, right, "and", "and".len) orelse return LLVM.Value.Instruction.Error.@"and"; + // return result; + // }, + // .bit_or => { + // const result = llvm.builder.createOr(left, right, "or", "or".len) orelse return LLVM.Value.Instruction.Error.@"or"; + // return result; + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .declaration_reference => |declaration_reference| { + // const declaration_alloca = try llvm.getDeclaration(declaration_reference.value); + // const is_volatile = false; + // const load_type = try llvm.getType(declaration_reference.getType(llvm.sema)); + // const load = llvm.builder.createLoad(load_type, declaration_alloca, is_volatile, "declaration_reference", "declaration_reference".len) orelse return LLVM.Value.Instruction.Error.load; + // return load.toValue(); + // }, + // .intrinsic => |intrinsic_index| return try llvm.emitIntrinsic(intrinsic_index, context), + // .enum_field => |enum_field_index| { + // const enum_field = llvm.sema.types.enum_fields.get(enum_field_index); + // switch (llvm.sema.types.array.get(enum_field.parent).*) { + // .@"enum" => |enum_index| { + // const enum_type = llvm.sema.types.enums.get(enum_index); + // const backing_type = llvm.sema.types.array.get(enum_type.backing_type); + // switch (backing_type.*) { + // .integer => |integer| { + // const is_signed = switch (integer.signedness) { + // .signed => true, + // .unsigned => false, + // }; + // assert(!is_signed); + // const enum_value = llvm.context.getConstantInt(integer.bit_count, enum_field.value, is_signed) orelse unreachable; + // return enum_value.toValue(); + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .unary_operation => |unary_operation_index| { + // const unary_operation = llvm.sema.values.unary_operations.get(unary_operation_index); + // switch (unary_operation.id) { + // .pointer_dereference => { + // const value = try llvm.emitValue(unary_operation.value, context); + // const is_volatile = false; + // const load = llvm.builder.createLoad(try llvm.getType(unary_operation.type), value, is_volatile, "pointer_dereference", "pointer_dereference".len) orelse return LLVM.Value.Instruction.Error.load; + // return load.toValue(); + // }, + // .address_of => { + // const pointer = try llvm.emitLValue(unary_operation.value, context); + // return pointer; + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .call => |call_index| { + // assert(context == .local); + // return try llvm.emitCall(call_index, context); + // }, + // .function_definition => |function_definition_index| { + // return llvm.function_definition_map.get(function_definition_index).?.toValue(); + // }, + // .container_initialization => |container_initialization_index| { + // const container_initialization = llvm.sema.values.container_initializations.get(container_initialization_index); + // const container_type = llvm.sema.types.array.get(container_initialization.type); + // const llvm_type = try llvm.getType(container_initialization.type); + // + // switch (container_type.*) { + // .@"struct" => |struct_index| { + // const struct_type = llvm.sema.types.structs.get(struct_index); + // switch (struct_type.backing_type.invalid) { + // true => { + // switch (context) { + // .global => { + // var initialization_values = try ArrayList(*LLVM.Value.Constant).initCapacity(context.allocator, container_initialization.field_initializations.items.len); + // + // for (container_initialization.field_initializations.items) |field_value_index| { + // const value = try llvm.emitValue(field_value_index, context); + // initialization_values.appendAssumeCapacity(value.toConstant() orelse unreachable); + // } + // + // // TODO: fix + // const llvm_struct_type = llvm_type.toStruct() orelse unreachable; + // const type_declaration = llvm.sema.values.declarations.get(llvm.sema.map.types.get(container_initialization.type).?); + // const name = llvm.sema.getName(type_declaration.name).?; + // std.debug.print("Type: {s}\n", .{name}); + // const constant_struct = llvm_struct_type.instantiate(initialization_values.items.ptr, initialization_values.items.len) orelse return LLVM.Value.Error.constant_struct; + // return constant_struct.toValue(); + // }, + // .local => { + // const alloca = llvm.builder.createAlloca(llvm_type, address_space, null, "struct_initialization", "struct initialization".len) orelse return LLVM.Value.Instruction.Error.alloca; + // + // const is_signed = false; + // + // for (struct_type.fields.items, container_initialization.field_initializations.items, 0..) |struct_field_index, field_initialization_value_index, index| { + // const struct_field = llvm.sema.types.container_fields.get(struct_field_index); + // const field_initialization = llvm.sema.values.array.get(field_initialization_value_index); + // const field_initialization_type = field_initialization.getType(llvm.sema); + // assert(field_initialization_type.eq(struct_field.type)); + // const llvm_field_type = try llvm.getType(struct_field.type); + // const index_value = llvm.context.getConstantInt(32, index, is_signed) orelse unreachable; + // const indices = [_]*LLVM.Value{index_value.toValue()}; + // const in_bounds = true; + // const gep = llvm.builder.createGEP(llvm_field_type, alloca.toValue(), &indices, indices.len, "gep", "gep".len, in_bounds) orelse return LLVM.Value.Instruction.Error.gep; + // const load = try llvm.emitValue(field_initialization_value_index, context); + // const is_volatile = false; + // const store = llvm.builder.createStore(load, gep, is_volatile) orelse return LLVM.Value.Instruction.Error.store; + // _ = store; + // } + // + // const is_volatile = false; + // const load = llvm.builder.createLoad(llvm_type, alloca.toValue(), is_volatile, "struct_init_load", "struct_init_load".len) orelse return LLVM.Value.Instruction.Error.load; + // return load.toValue(); + // }, + // else => unreachable, + // } + // }, + // false => switch (llvm.sema.types.array.get(struct_type.backing_type).*) { + // else => |t| @panic(@tagName(t)), + // }, + // } + // }, + // else => |t| @panic(@tagName(t)), + // } + // // container_initialization.field_initializations.items + // }, + // .slice_access => |slice_access_index| { + // const slice_access = llvm.sema.values.slice_accesses.get(slice_access_index); + // switch (llvm.sema.types.array.get(llvm.sema.values.array.get(slice_access.value).getType(llvm.sema)).*) { + // .slice => |slice| { + // _ = slice; + // + // const slice_access_value = try llvm.emitValue(slice_access.value, context); + // const index: c_uint = switch (slice_access.field) { + // .ptr => 0, + // .len => 1, + // }; + // const name = switch (slice_access.field) { + // .ptr => "slice_access_ptr", + // .len => "slice_access_len", + // }; + // const indices = [1]c_uint{index}; + // const len_value = llvm.builder.createExtractValue(slice_access_value, &indices, indices.len, name.ptr, name.len) orelse return LLVM.Value.Instruction.Error.extract_value; + // return len_value; + // }, + // else => |t| @panic(@tagName(t)), + // } + // // const llvm_type = try llvm.getType(slice_access.type); + // }, + // .field_access => |field_access_index| { + // const field_access = llvm.sema.values.field_accesses.get(field_access_index); + // const sema_field = llvm.sema.types.container_fields.get(field_access.field); + // const result_type = try llvm.getType(sema_field.type); + // const value = try llvm.emitValue(field_access.declaration_reference, context); + // _ = result_type; + // // extern fn bindings.NativityLLVMBuilderCreateGEP(builder: *LLVM.Builder, type: *LLVM.Type, pointer: *LLVM.Value, index_ptr: [*]const *LLVM.Value, index_count: usize, name_ptr: [*]const u8, name_len: usize, in_bounds: bool) ?*LLVM.Value; + // const indices = [1]u32{sema_field.index}; + // const result = llvm.builder.createExtractValue(value, &indices, indices.len, "field_access", "field_access".len) orelse return LLVM.Value.Instruction.Error.extract_value; + // return result; + // }, + // .optional_check => |optional_check_index| { + // const optional_check = llvm.sema.values.optional_checks.get(optional_check_index); + // const sema_optional_value = llvm.sema.values.array.get(optional_check.value); + // const optional_type_index = sema_optional_value.getType(llvm.sema); + // + // switch (llvm.sema.types.array.get(optional_type_index).*) { + // .optional => |optional| switch (llvm.sema.types.array.get(optional.element_type).*) { + // .pointer => |pointer| { + // _ = pointer; + // + // @panic("TODO: optional check for pointer"); + // }, + // else => { + // const optional_value = try llvm.emitValue(optional_check.value, context); + // const indices = [1]c_uint{1}; + // const result = llvm.builder.createExtractValue(optional_value, &indices, indices.len, "optional_check", "optional_check".len) orelse return LLVM.Value.Instruction.Error.extract_value; + // return result; + // }, + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .optional_unwrap => |optional_unwrap_index| { + // const optional_unwrap = llvm.sema.values.optional_unwraps.get(optional_unwrap_index); + // const sema_optional_value = llvm.sema.values.array.get(optional_unwrap.value); + // const optional_type_index = sema_optional_value.getType(llvm.sema); + // switch (llvm.sema.types.array.get(optional_type_index).*) { + // .optional => |optional| switch (llvm.sema.types.array.get(optional.element_type).*) { + // .pointer => |pointer| { + // _ = pointer; + // + // @panic("TODO: optional check for pointer"); + // }, + // else => { + // const optional_value = try llvm.emitValue(optional_unwrap.value, context); + // const indices = [1]c_uint{0}; + // const result = llvm.builder.createExtractValue(optional_value, &indices, indices.len, "optional_unwrap", "optional_unwrap".len) orelse return LLVM.Value.Instruction.Error.extract_value; + // return result; + // }, + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .optional_null_literal => |optional_type_index| { + // const optional_type = try llvm.getType(optional_type_index); + // const optional_undefined = optional_type.getUndefined() orelse unreachable; + // + // const indices = [1]c_uint{1}; + // const is_signed = false; + // const null_value = llvm.context.getConstantInt(1, 0, is_signed) orelse unreachable; + // const insert_value = llvm.builder.createInsertValue(optional_undefined.toValue(), null_value.toValue(), &indices, indices.len, "optional_null_literal", "optional_null_literal".len) orelse return LLVM.Value.Instruction.Error.insert_value; + // _ = insert_value; + // + // return optional_undefined.toValue(); + // }, + // .slice => |slice_expression_index| { + // const slice_expression = llvm.sema.values.slices.get(slice_expression_index); + // const sliceable = try llvm.emitValue(slice_expression.sliceable, context); + // const slice_expression_type = try llvm.getType(slice_expression.type); + // const sema_sliceable = llvm.sema.values.array.get(slice_expression.sliceable); + // const sema_sliceable_type_index = sema_sliceable.getType(llvm.sema); + // const sema_sliceable_type = llvm.sema.types.array.get(sema_sliceable_type_index); + // const start_value = try llvm.emitValue(slice_expression.range.start, context); + // const result = slice_expression_type.getUndefined() orelse unreachable; + // + // switch (sema_sliceable_type.*) { + // .slice => |slice| { + // const indices = [1]c_uint{0}; + // const sliceable_ptr = llvm.builder.createExtractValue(sliceable, &indices, indices.len, "sliceable_ptr", "sliceable_ptr".len) orelse return LLVM.Value.Instruction.Error.extract_value; + // const element_type = try llvm.getType(slice.element_type); + // const ptr_indices = [1]*LLVM.Value{start_value}; + // const in_bounds = true; + // const offset_ptr = llvm.builder.createGEP(element_type, sliceable_ptr, &ptr_indices, ptr_indices.len, "offset_ptr", "offset_ptr".len, in_bounds) orelse unreachable; + // const insert_slice_ptr = llvm.builder.createInsertValue(result.toValue(), offset_ptr, &indices, indices.len, "insert_slice_ptr", "insert_slice_ptr".len) orelse unreachable; + // _ = insert_slice_ptr; + // + // switch (slice_expression.range.end.invalid) { + // true => { + // const no_unsigned_wrapping = true; + // const no_signed_wrapping = false; + // const len_indices = [1]c_uint{1}; + // const sliceable_len = llvm.builder.createExtractValue(sliceable, &len_indices, len_indices.len, "sliceable_len", "sliceable_len".len) orelse return LLVM.Value.Instruction.Error.extract_value; + // const len_sub = llvm.builder.createSub(sliceable_len, start_value, "slice_len_arithmetic", "slice_len_arithmetic".len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add; + // const insert_slice_len = llvm.builder.createInsertValue(result.toValue(), len_sub, &len_indices, len_indices.len, "insert_slice_len", "insert_slice_len".len) orelse unreachable; + // _ = insert_slice_len; + // return result.toValue(); + // }, + // false => unreachable, + // } + // }, + // .pointer => |pointer| { + // const offset_indices = [1]*LLVM.Value{start_value}; + // const ptr_indices = [1]c_uint{0}; + // const element_type = try llvm.getType(pointer.element_type); + // const in_bounds = true; + // const offset_ptr = llvm.builder.createGEP(element_type, sliceable, &offset_indices, offset_indices.len, "offset_ptr", "offset_ptr".len, in_bounds) orelse unreachable; + // const insert_slice_ptr = llvm.builder.createInsertValue(result.toValue(), offset_ptr, &ptr_indices, ptr_indices.len, "insert_slice_ptr", "insert_slice_ptr".len) orelse unreachable; + // _ = insert_slice_ptr; + // + // switch (slice_expression.range.end.invalid) { + // true => { + // switch (pointer.many) { + // true => @panic("Only pointer to array"), + // false => { + // switch (llvm.sema.types.array.get(pointer.element_type).*) { + // .array => |array| { + // const len_indices = [1]c_uint{1}; + // const is_signed = false; + // const constant_len = llvm.context.getConstantInt(64, array.element_count, is_signed) orelse unreachable; + // const no_unsigned_wrapping = true; + // const no_signed_wrapping = false; + // const len_sub = llvm.builder.createSub(constant_len.toValue(), start_value, "slice_len_arithmetic", "slice_len_arithmetic".len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add; + // const insert_slice_len = llvm.builder.createInsertValue(result.toValue(), len_sub, &len_indices, len_indices.len, "insert_slice_len", "insert_slice_len".len) orelse unreachable; + // _ = insert_slice_len; + // return result.toValue(); + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // } + // }, + // false => unreachable, + // } + // unreachable; + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .bool => |boolean| { + // const is_signed = false; + // const boolean_constant = llvm.context.getConstantInt(1, @intFromBool(boolean), is_signed) orelse unreachable; + // return boolean_constant.toValue(); + // }, + // .string_literal => |sema_string_literal| { + // const string = llvm.sema.getName(sema_string_literal.hash) orelse unreachable; + // const llvm_const_string = llvm.builder.createGlobalStringPointer(string.ptr, string.len, "string_literal", "string_literal".len, address_space, llvm.module) orelse unreachable; + // return llvm_const_string.toValue(); + // }, + // .indexed_access => |indexed_access_index| { + // const indexed_access = llvm.sema.values.indexed_accesses.get(indexed_access_index); + // const indexed_value = llvm.sema.values.array.get(indexed_access.indexed_expression); + // const indexed_type = indexed_value.getType(llvm.sema); + // const index = try llvm.emitValue(indexed_access.index_expression, context); + // const indexed = try llvm.emitValue(indexed_access.indexed_expression, context); + // + // switch (llvm.sema.types.array.get(indexed_type).*) { + // .pointer => |pointer| { + // switch (pointer.many) { + // true => { + // const element_type = try llvm.getType(pointer.element_type); + // const indices = [_]*LLVM.Value{index}; + // const in_bounds = true; + // const pointer_access = llvm.builder.createGEP(element_type, indexed, &indices, indices.len, "indexed_pointer_access", "indexed_pointer_access".len, in_bounds) orelse unreachable; + // + // const result_type = try llvm.getType(sema_type); + // const is_volatile = false; + // const name = "indexed_pointer_load"; + // const load = llvm.builder.createLoad(result_type, pointer_access, is_volatile, name, name.len) orelse return LLVM.Value.Instruction.Error.load; + // const load_value = load.toValue(); + // return load_value; + // }, + // false => unreachable, + // } + // }, + // .slice => |slice| { + // const slice_indices = [1]c_uint{0}; + // const slice_ptr = llvm.builder.createExtractValue(indexed, &slice_indices, slice_indices.len, "slice_ptr", "slice_ptr".len) orelse return LLVM.Value.Instruction.Error.extract_value; + // const element_type = try llvm.getType(slice.element_type); + // const indices = [_]*LLVM.Value{index}; + // const in_bounds = true; + // const pointer_access = llvm.builder.createGEP(element_type, slice_ptr, &indices, indices.len, "indexed_pointer_access", "indexed_pointer_access".len, in_bounds) orelse unreachable; + // const result_type = try llvm.getType(sema_type); + // const is_volatile = false; + // const name = "indexed_pointer_load"; + // const load = llvm.builder.createLoad(result_type, pointer_access, is_volatile, name, name.len) orelse return LLVM.Value.Instruction.Error.load; + // + // const load_value = load.toValue(); + // assert(load_value.getType() == result_type); + // return load.toValue(); + // }, + // else => |t| @panic(@tagName(t)), + // } + // }, + // .character_literal => |ch| { + // const is_signed = false; + // const constant = llvm.context.getConstantInt(8, ch, is_signed) orelse unreachable; + // return constant.toValue(); + // }, + // .array_initialization => |array_initialization_index| { + // const array_initialization = llvm.sema.values.container_initializations.get(array_initialization_index); + // const initialization_type = try llvm.getType(array_initialization.type); + // const array_type = initialization_type.toArray() orelse unreachable; + // const array_element_type = array_type.getElementType() orelse unreachable; + // const sema_array_element_type = switch (llvm.sema.types.array.get(array_initialization.type).*) { + // .array => |array| array.element_type, + // else => |t| @panic(@tagName(t)), + // }; + // + // if (array_initialization.is_comptime) { + // var list = try ArrayList(*LLVM.Value.Constant).initCapacity(context.allocator, array_initialization.field_initializations.items.len); + // + // for (array_initialization.field_initializations.items) |element_initialization| { + // const value = try llvm.emitValue(element_initialization, context); + // const sema_value_type = llvm.sema.values.array.get(element_initialization).getType(llvm.sema); + // assert(sema_value_type.eq(sema_array_element_type)); + // const value_type = value.getType(); + // if (!value_type.compare(array_element_type)) { + // unreachable; + // } + // const constant = value.toConstant() orelse unreachable; + // list.appendAssumeCapacity(constant); + // } + // + // const constant_array = array_type.getConstant(list.items.ptr, list.items.len) orelse unreachable; + // return constant_array.toValue(); + // } else { + // const array_undefined = initialization_type.getUndefined() orelse unreachable; + // + // for (array_initialization.field_initializations.items, 0..) |element_initialization, index| { + // const value = try llvm.emitValue(element_initialization, context); + // const indices = [1]c_uint{@intCast(index)}; + // const insert_array_element = llvm.builder.createInsertValue(array_undefined.toValue(), value, &indices, indices.len, "insert_array_element", "insert_array_element".len) orelse unreachable; + // _ = insert_array_element; + // } + // + // return array_undefined.toValue(); + // } + // }, + // else => |t| @panic(@tagName(t)), + // } + // } + + // fn emitCall(llvm: *LLVM, call_index: Compilation.Call.Index, context: Compilation.ScopeType) !*LLVM.Value { + // assert(context == .local); + // var argument_buffer: [32]*LLVM.Value = undefined; + // const sema_call = llvm.sema.values.calls.get(call_index); + // const sema_call_arguments = llvm.sema.values.argument_lists.get(sema_call.arguments).array.items; + // const argument_count = sema_call_arguments.len; + // const arguments = argument_buffer[0..argument_count]; + // + // const sema_type = llvm.sema.values.array.get(sema_call.value).getType(llvm.sema); + // + // switch (llvm.sema.values.array.get(sema_call.value).*) { + // .function_definition => |function_definition_index| { + // const function_definition = llvm.sema.types.function_definitions.get(function_definition_index); + // assert(function_definition.prototype.eq(sema_type)); + // + // const function_prototype_type = llvm.sema.types.array.get(function_definition.prototype); + // const function_prototype = llvm.sema.types.function_prototypes.get(function_prototype_type.function); + // const declaration_index = llvm.sema.map.function_definitions.get(function_definition_index).?; + // const declaration = llvm.sema.values.declarations.get(declaration_index); + // const declaration_name = llvm.sema.getName(declaration.name).?; + // std.debug.print("Call to {s}\n", .{declaration_name}); + // if (equal(u8, declaration_name, "count_slice_byte_count")) { + // //@breakpoint(); + // } + // + // const callee = try llvm.emitValue(sema_call.value, context); + // + // for (function_prototype.arguments.items, sema_call_arguments, arguments) |argument_declaration_index, sema_call_value_index, *argument| { + // const argument_declaration = llvm.sema.values.declarations.get(argument_declaration_index); + // const argument_type = argument_declaration.getType(); + // switch (llvm.sema.types.array.get(argument_type).*) { + // .integer => |_| { + // argument.* = try llvm.emitValue(sema_call_value_index, context); + // }, + // .@"struct" => |struct_index| { + // const struct_type = llvm.sema.types.structs.get(struct_index); + // if (!struct_type.backing_type.invalid) { + // unreachable; + // } else { + // unreachable; + // } + // }, + // else => |t| @panic(@tagName(t)), + // } + // + // unreachable; + // // _ = argument_declaration_index; + // // const call_argument = llvm.sema.values.array.get(sema_call_value_index); + // // const call_argument_type = call_argument.getType(llvm.sema); + // // const cat = llvm.sema.types.array.get(call_argument_type); + // // const argument_declaration = llvm.sema.values.declarations.get(argument_declaration_index); + // // const argument_declaration_type = argument_declaration.getType(); + // // const argument_type = try llvm.getType(argument_declaration_type); + // + // // if (!call_argument_type.eq(argument_declaration_type)) { + // // switch (llvm.sema.types.array.get(argument_declaration_type).*) { + // // .slice => |slice| { + // // _ = slice; + // // + // // const result = argument_type.getUndefined() orelse unreachable; + // // + // // const ptr_indices = [1]c_uint{0}; + // // const extract_slice_ptr = llvm.builder.createExtractValue(argument.*, &ptr_indices, ptr_indices.len, "extract_slice_ptr", "extract_slice_ptr".len) orelse unreachable; + // // const insert_slice_ptr = llvm.builder.createInsertValue(result.toValue(), extract_slice_ptr, &ptr_indices, ptr_indices.len, "insert_slice_ptr", "insert_slice_ptr".len) orelse unreachable; + // // _ = insert_slice_ptr; + // // const len_indices = [1]c_uint{1}; + // // const extract_slice_len = llvm.builder.createExtractValue(argument.*, &len_indices, len_indices.len, "extract_slice_ptr", "extract_slice_ptr".len) orelse unreachable; + // // const insert_slice_len = llvm.builder.createInsertValue(result.toValue(), extract_slice_len, &len_indices, len_indices.len, "insert_slice_len", "insert_slice_len".len) orelse unreachable; + // // _ = insert_slice_len; + // // argument.* = result.toValue(); + // // }, + // // else => |t| @panic(@tagName(t)), + // // } + // // // argument.* = llvm.builder.createCast(.bitcast, argument.*, argument_type, "arg_bitcast", "arg_bitcast".len) orelse unreachable; + // // } + // } + // + // // const function = callee.toFunction() orelse unreachable; + // // const llvm_calling_convention = function.getCallingConvention(); + // // const name = if (sema_call.type.eq(Compilation.Type.void) or sema_call.type.eq(Compilation.Type.noreturn)) "" else "call"; + // // const callee_type = try llvm.getType(sema_type); + // // const function_type = callee_type.toFunction() orelse unreachable; + // // const call = llvm.builder.createCall(function_type, callee, arguments.ptr, arguments.len, name.ptr, name.len, null) orelse return LLVM.Value.Instruction.Error.call; + // // call.setCallingConvention(llvm_calling_convention); + // // + // // return call.toValue(); + // // + // }, + // else => |t| @panic(@tagName(t)), + // } + // } + + fn arithmeticIntegerBinaryOperation(llvm: *LLVM, left: *LLVM.Value, right: *LLVM.Value, binary_operation: Compilation.BinaryOperation.Id, sema_integer_type: Compilation.Type.Integer, name: []const u8) !*LLVM.Value { + var no_signed_wrapping = false; + var no_unsigned_wrapping = false; + + switch (sema_integer_type.signedness) { + .signed => no_signed_wrapping = true, + .unsigned => no_unsigned_wrapping = true, + } + + assert(left.getType().isInteger()); + assert(right.getType().isInteger()); + + return switch (binary_operation) { + .add => llvm.builder.createAdd(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add, + .sub => llvm.builder.createSub(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add, + .mul => llvm.builder.createMultiply(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.multiply, + .shift_left => llvm.builder.createShiftLeft(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.shift_left, + else => |t| @panic(@tagName(t)), + }; + } + + fn emitStatement(llvm: *LLVM, sema_statement_index: Compilation.Statement.Index, context: Compilation.ScopeType) anyerror!void { + const sema_statement = llvm.sema.values.statements.get(sema_statement_index); + const sema_statement_value = llvm.sema.values.array.get(sema_statement.value); + + llvm.builder.setCurrentDebugLocation(llvm.context, sema_statement.line + 1, sema_statement.column + 1, llvm.scope, llvm.function); + + switch (sema_statement_value.*) { + .declaration => |sema_declaration_index| { + _ = try llvm.emitDeclaration(sema_declaration_index, null); + }, + .@"return" => |return_index| { + const return_expression = llvm.sema.values.returns.get(return_index); + const sema_value_return_type = llvm.sema.values.array.get(return_expression.value).getType(llvm.sema); + const sema_function = llvm.sema.types.function_definitions.get(llvm.sema_function); + const sema_function_prototype = llvm.sema.types.function_prototypes.get(llvm.sema.types.array.get(sema_function.prototype).function); + const sema_return_type = sema_function_prototype.return_type; + assert(sema_value_return_type.eq(sema_return_type)); + const return_value = try llvm.emitValue(return_expression.value, context); + + // if (!llvm.inside_branch) { + const function_return_type = llvm.function.getReturnType() orelse unreachable; + _ = function_return_type; + const ret = llvm.builder.createRet(return_value) orelse return LLVM.Value.Instruction.Error.ret; + _ = ret; + // } else { + // if (llvm.return_phi_node) |return_phi_node| { + // _ = return_phi_node; + // + // unreachable; + // } else { + // const phi_node_count_hint = 2; + // const insert_basic_block = null; + // _ = insert_basic_block; + // const phi_node = LLVM.newPhiNode(return_type, phi_node_count_hint, "phi", "phi".len, null) orelse unreachable; + // const go_to_exit = llvm.builder.createBranch(llvm.exit_block) orelse unreachable; + // _ = go_to_exit; + // llvm.return_phi_node = phi_node; + // } + // } + }, + .intrinsic => |intrinsic_index| { + _ = try llvm.emitIntrinsic(intrinsic_index, context); + }, + .@"unreachable" => { + const unreachable_instruction = llvm.builder.createUnreachable() orelse return LLVM.Value.Instruction.Error.@"unreachable"; + _ = unreachable_instruction; + }, + .assign => |assignment_index| { + const assignment = llvm.sema.values.assignments.get(assignment_index); + const right = try llvm.emitValue(assignment.source, context); + const pointer = try llvm.emitLValue(assignment.destination, context); + const value: *LLVM.Value = if (assignment.operation) |operation| switch (operation) { + .add, .sub, .mul, .div => blk: { + const is_volatile = false; + const name = "compound_assign"; + const destination_type = try llvm.getType(llvm.sema.values.array.get(assignment.destination).getType(llvm.sema)); + const load = llvm.builder.createLoad(destination_type, pointer, is_volatile, name, name.len) orelse return LLVM.Value.Instruction.Error.load; + const right_value = llvm.sema.values.array.get(assignment.source); + const right_type = llvm.sema.types.array.get(right_value.getType(llvm.sema)); + + break :blk switch (right_type.*) { + .integer => |integer_type| switch (operation) { + .add, .sub, .mul => try llvm.arithmeticIntegerBinaryOperation(load.toValue(), right, operation, integer_type, "compound_assign"), + .div => b: { + const is_exact = false; + const result = switch (integer_type.signedness) { + .unsigned => llvm.builder.createUDiv(load.toValue(), right, "udiv", "udiv".len, is_exact) orelse return LLVM.Value.Instruction.Error.udiv, + .signed => llvm.builder.createSDiv(load.toValue(), right, "sdiv", "sdiv".len, is_exact) orelse return LLVM.Value.Instruction.Error.sdiv, + }; + + break :b result; + }, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + }; + }, + else => |t| @panic(@tagName(t)), + } else right; + + const is_volatile = false; + const store = llvm.builder.createStore(value, pointer, is_volatile) orelse return LLVM.Value.Instruction.Error.store; + _ = store; + }, + .call => |call_index| { + assert(context == .local); + _ = try llvm.emitCall(call_index, context); + }, + .assembly_block => |assembly_block_index| { + const assembly_block = llvm.sema.values.assembly_blocks.get(assembly_block_index); + + var assembly_statements = ArrayList(u8){}; + var constraints = ArrayList(u8){}; + var operand_values = ArrayList(*LLVM.Value){}; + var operand_types = ArrayList(*LLVM.Type){}; + + switch (llvm.sema.descriptor.target.cpu.arch) { + .x86_64 => { + for (assembly_block.instructions) |instruction_index| { + const instruction = llvm.sema.values.assembly_instructions.get(instruction_index); + const instruction_id: Compilation.Assembly.x86_64.Instruction = @enumFromInt(instruction.id); + + try assembly_statements.appendSlice(context.allocator, switch (instruction_id) { + .xor => "xorl", + .mov => "movq", + .@"and" => "andq", + .call => "callq", + }); + try assembly_statements.append(context.allocator, ' '); + + if (instruction.operands.len > 0) { + var reverse_operand_iterator = std.mem.reverseIterator(instruction.operands); + + while (reverse_operand_iterator.next()) |operand| { + switch (operand) { + .register => |register_value| { + const register: Compilation.Assembly.x86_64.Register = @enumFromInt(register_value); + try assembly_statements.append(context.allocator, '%'); + try assembly_statements.appendSlice(context.allocator, @tagName(register)); + }, + .number_literal => |literal| { + try assembly_statements.writer(context.allocator).print("$$0x{x}", .{literal}); + }, + .value_index => |value_index| { + switch (llvm.sema.values.array.get(value_index).*) { + .function_definition => { + const value = try llvm.emitValue(value_index, context); + try assembly_statements.writer(context.allocator).print("${{{}:P}}", .{operand_values.items.len}); + try operand_values.append(context.allocator, value); + try constraints.append(context.allocator, 'X'); + const value_type = value.getType(); + try operand_types.append(context.allocator, value_type); + }, + else => |t| @panic(@tagName(t)), + } + // try assembly_statements.writer(context.allocator).print("%{}", .{operand_values.items.len}); + }, + } + + try assembly_statements.appendSlice(context.allocator, ", "); + } + + _ = assembly_statements.pop(); + _ = assembly_statements.pop(); + } + + try assembly_statements.appendSlice(context.allocator, "\n\t"); + } + + try constraints.appendSlice(context.allocator, ",~{dirflag},~{fpsr},~{flags}"); + }, + else => |t| @panic(@tagName(t)), + } + + const is_var_args = false; + const function_type = LLVM.Context.getFunctionType(try llvm.getType(Compilation.Type.void), operand_types.items.ptr, operand_types.items.len, is_var_args) orelse unreachable; + const has_side_effects = true; + const is_align_stack = true; + const dialect = LLVM.Value.InlineAssembly.Dialect.@"at&t"; + const can_throw = false; + + const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly_statements.items.ptr, assembly_statements.items.len, constraints.items.ptr, constraints.items.len, has_side_effects, is_align_stack, dialect, can_throw) orelse return LLVM.Value.Error.inline_assembly; + const call = llvm.builder.createCall(function_type, inline_assembly.toValue(), operand_values.items.ptr, operand_values.items.len, "", "".len, null) orelse return LLVM.Value.Instruction.Error.call; + _ = call; + }, + .block => |block_index| { + assert(context == .local); + const emit_arguments = false; + try llvm.emitBlock(block_index, context, emit_arguments); + }, + .branch => |branch_index| { + const branch = llvm.sema.values.branches.get(branch_index); + const branch_type = llvm.sema.values.array.get(branch.expression).getType(llvm.sema); + assert(branch_type.eq(Compilation.Type.boolean)); + const condition_value = try llvm.emitValue(branch.expression, context); + const previous_inside_branch = llvm.inside_branch; + llvm.inside_branch = true; + const taken_basic_block = llvm.context.createBasicBlock("branch_taken", "branch_taken".len, llvm.function, null) orelse return Error.basic_block; + var not_taken_basic_block: ?*LLVM.Value.BasicBlock = null; + var fuse_basic_block: ?*LLVM.Value.BasicBlock = null; + const acting_fuse_block = switch (branch.not_taken_expression.invalid) { + true => b: { + const block = llvm.context.createBasicBlock("branch_fuse", "branch_fuse".len, llvm.function, null) orelse return Error.basic_block; + fuse_basic_block = block; + break :b block; + }, + false => b: { + const block = llvm.context.createBasicBlock("branch_not_taken", "branch_fuse".len, llvm.function, null) orelse return Error.basic_block; + not_taken_basic_block = block; + break :b block; + }, + }; + + const branch_weights = null; + const unpredictable = null; + const conditional_branch = llvm.builder.createConditionalBranch(condition_value, taken_basic_block, acting_fuse_block, branch_weights, unpredictable) orelse return LLVM.Value.Instruction.Error.conditional_branch; + _ = conditional_branch; + + var taken_reaches_end = true; + { + llvm.builder.setInsertPoint(taken_basic_block); + + switch (llvm.sema.values.array.get(branch.taken_expression).*) { + .block => |block_index| { + taken_reaches_end = llvm.sema.values.blocks.get(block_index).reaches_end; + const emit_arguments = false; + try llvm.emitBlock(block_index, context, emit_arguments); + }, + else => |t| @panic(@tagName(t)), + } + } + + if (taken_reaches_end) { + assert(!llvm.builder.isCurrentBlockTerminated()); + if (fuse_basic_block == null) { + const fuse_block = llvm.context.createBasicBlock("branch_fuse", "branch_fuse".len, llvm.function, null) orelse return Error.basic_block; + fuse_basic_block = fuse_block; + } + const merge_br = llvm.builder.createBranch(fuse_basic_block orelse unreachable); + _ = merge_br; + } + + var not_taken_reaches_end = true; + if (!branch.not_taken_expression.invalid) { + llvm.builder.setInsertPoint(not_taken_basic_block orelse unreachable); + + switch (llvm.sema.values.array.get(branch.not_taken_expression).*) { + .block => |block_index| { + const emit_arguments = false; + try llvm.emitBlock(block_index, context, emit_arguments); + not_taken_reaches_end = llvm.sema.values.blocks.get(block_index).reaches_end; + }, + else => |t| @panic(@tagName(t)), + } + + if (not_taken_reaches_end) { + assert(!llvm.builder.isCurrentBlockTerminated()); + if (fuse_basic_block == null) { + const fuse_block = llvm.context.createBasicBlock("branch_fuse", "branch_fuse".len, llvm.function, null) orelse return Error.basic_block; + fuse_basic_block = fuse_block; + } + + const merge_br = llvm.builder.createBranch(fuse_basic_block orelse unreachable); + _ = merge_br; + } + } + + if (fuse_basic_block) |end_block| { + llvm.builder.setInsertPoint(end_block); + } + + llvm.inside_branch = previous_inside_branch; + }, + .loop => |loop_index| { + const loop = llvm.sema.values.loops.get(loop_index); + assert(context == .local); + const previous_inside_branch = llvm.inside_branch; + llvm.inside_branch = true; + for (loop.pre.items) |pre_statement_value_index| { + try llvm.emitStatement(pre_statement_value_index, context); + } + const header_basic_block = llvm.context.createBasicBlock("loop_header", "loop_header".len, llvm.function, null) orelse return Error.basic_block; + const jump_to_loop = llvm.builder.createBranch(header_basic_block) orelse unreachable; + _ = jump_to_loop; + const body_basic_block = llvm.context.createBasicBlock("loop_body", "loop_body".len, llvm.function, null) orelse return Error.basic_block; + const previous_exit_block = llvm.exit_block; + const exit_basic_block = llvm.context.createBasicBlock("loop_exit", "loop_exit".len, llvm.function, null) orelse return Error.basic_block; + llvm.exit_block = exit_basic_block; + + llvm.builder.setInsertPoint(header_basic_block); + + const condition = try llvm.emitValue(loop.condition, context); + const branch_weights = null; + const unpredictable = null; + const conditional_branch = llvm.builder.createConditionalBranch(condition, body_basic_block, exit_basic_block, branch_weights, unpredictable) orelse unreachable; + _ = conditional_branch; + llvm.builder.setInsertPoint(body_basic_block); + try llvm.emitStatement(loop.body, context); + + for (loop.post.items) |post_statement_value_index| { + try llvm.emitStatement(post_statement_value_index, context); + } + + // if (!llvm.builder.isCurrentBlockTerminated()) { + const jump_to_header = llvm.builder.createBranch(header_basic_block) orelse unreachable; + _ = jump_to_header; + // } + + llvm.builder.setInsertPoint(exit_basic_block); + llvm.exit_block = previous_exit_block; + llvm.inside_branch = previous_inside_branch; + + // if (llvm.inside_branch and !llvm.builder.isCurrentBlockTerminated()) { + // unreachable; + // } + }, + .@"break" => { + const jump_to_exit_block = llvm.builder.createBranch(llvm.exit_block) orelse unreachable; + _ = jump_to_exit_block; + }, + else => |t| @panic(@tagName(t)), + } + } + + fn emitIntrinsic(llvm: *LLVM, intrinsic_index: Compilation.Intrinsic.Index, context: Compilation.ScopeType) !*LLVM.Value { + const intrinsic = llvm.sema.values.intrinsics.get(intrinsic_index); + switch (intrinsic.kind) { + .cast => |sema_cast_value| { + const value = try llvm.emitValue(sema_cast_value, context); + const destination_type = llvm.sema.types.array.get(intrinsic.type); + const source_value = llvm.sema.values.array.get(sema_cast_value); + const source_type = llvm.sema.types.array.get(source_value.getType(llvm.sema)); + + switch (destination_type.*) { + .integer => |destination_integer| { + switch (source_type.*) { + .@"enum" => return value, + .integer => |source_integer| { + if (source_integer.bit_count == destination_integer.bit_count) { + return value; + } else if (source_integer.bit_count < destination_integer.bit_count) { + assert(source_integer.signedness != destination_integer.signedness); + const cast_type: LLVM.Value.Instruction.Cast.Type = switch (destination_integer.signedness) { + .signed => .sign_extend, + .unsigned => .zero_extend, + }; + const name = @tagName(cast_type); + const cast = llvm.builder.createCast(cast_type, value, try llvm.getType(intrinsic.type), name.ptr, name.len) orelse return LLVM.Value.Instruction.Error.cast; + return cast; + } else if (source_integer.bit_count > destination_integer.bit_count) { + const cast = llvm.builder.createCast(.truncate, value, try llvm.getType(intrinsic.type), "truncate", "truncate".len) orelse return LLVM.Value.Instruction.Error.cast; + return cast; + } else unreachable; + }, + .pointer => |pointer| { + _ = pointer; + assert(destination_integer.bit_count == 64); + const cast = llvm.builder.createCast(.pointer_to_int, value, try llvm.getType(intrinsic.type), "pointer_to_int", "pointer_to_int".len) orelse return LLVM.Value.Instruction.Error.cast; + return cast; + }, + else => |t| @panic(@tagName(t)), + } + }, + .pointer => |pointer| { + _ = pointer; + + switch (source_type.*) { + .integer => { + const cast = llvm.builder.createCast(.int_to_pointer, value, try llvm.getType(intrinsic.type), "int_to_pointer", "int_to_pointer".len) orelse return LLVM.Value.Instruction.Error.cast; + return cast; + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + } + }, + .sign_extend => |sema_value| { + const value = try llvm.emitValue(sema_value, context); + + const sign_extend = llvm.builder.createCast(.sign_extend, value, try llvm.getType(intrinsic.type), "sign_extend", "sign_extend".len) orelse return LLVM.Value.Instruction.Error.cast; + return sign_extend; + }, + .zero_extend => |sema_value| { + const value = try llvm.emitValue(sema_value, context); + + const zero_extend = llvm.builder.createCast(.zero_extend, value, try llvm.getType(intrinsic.type), "zero_extend", "zero_extend".len) orelse return LLVM.Value.Instruction.Error.cast; + return zero_extend; + }, + .optional_wrap => |sema_value| { + const optional_type = try llvm.getType(intrinsic.type); + switch (llvm.sema.types.array.get(intrinsic.type).*) { + .optional => |optional| switch (llvm.sema.types.array.get(optional.element_type).*) { + .integer => { + const alloca = llvm.builder.createAlloca(optional_type, address_space, null, "optional_wrap_alloca", "optional_wrap_alloca".len) orelse return LLVM.Value.Instruction.Error.alloca; + const is_signed = false; + const index_zero = llvm.context.getConstantInt(32, 0, is_signed) orelse unreachable; + const index_one = llvm.context.getConstantInt(32, 1, is_signed) orelse unreachable; + + const optional_element_type = try llvm.getType(optional.element_type); + const boolean_type = try llvm.getType(Compilation.Type.boolean); + + const indices0 = [_]*LLVM.Value{index_zero.toValue()}; + const in_bounds = true; + const gep0 = llvm.builder.createGEP(optional_element_type, alloca.toValue(), &indices0, indices0.len, "gep", "gep".len, in_bounds) orelse return LLVM.Value.Instruction.Error.gep; + const load0 = try llvm.emitValue(sema_value, context); + const is_volatile = false; + const store0 = llvm.builder.createStore(load0, gep0, is_volatile) orelse return LLVM.Value.Instruction.Error.store; + _ = store0; + + const indices1 = [_]*LLVM.Value{index_one.toValue()}; + const gep1 = llvm.builder.createGEP(boolean_type, alloca.toValue(), &indices1, indices1.len, "gep", "gep".len, in_bounds) orelse return LLVM.Value.Instruction.Error.gep; + const load1 = llvm.context.getConstantInt(1, 1, is_signed) orelse unreachable; + const store1 = llvm.builder.createStore(load1.toValue(), gep1, is_volatile) orelse return LLVM.Value.Instruction.Error.store; + _ = store1; + + const load = llvm.builder.createLoad(optional_type, alloca.toValue(), is_volatile, "struct_init_load", "struct_init_load".len) orelse return LLVM.Value.Instruction.Error.load; + return load.toValue(); + }, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + } + }, + .min => |sema_values| { + switch (llvm.sema.types.array.get(intrinsic.type).*) { + .integer => |integer_type| { + const intrinsic_name = switch (integer_type.signedness) { + .unsigned => "llvm.umin", + .signed => "llvm.smin", + }; + const intrinsic_id = lookupIntrinsic(intrinsic_name.ptr, intrinsic_name.len); + assert(intrinsic_id != .none); + + const left_type = llvm.sema.values.array.get(sema_values.left).getType(llvm.sema); + const right_type = llvm.sema.values.array.get(sema_values.right).getType(llvm.sema); + assert(left_type.eq(right_type)); + assert(left_type.eq(intrinsic.type)); + const intrinsic_return_type = try llvm.getType(intrinsic.type); + const types = [_]*LLVM.Type{intrinsic_return_type}; + const intrinsic_function = llvm.module.getIntrinsicDeclaration(intrinsic_id, &types, types.len) orelse return LLVM.Value.Error.intrinsic; + const intrinsic_function_type = llvm.context.getIntrinsicType(intrinsic_id, &types, types.len) orelse return LLVM.Type.Error.intrinsic; + + const left = try llvm.emitValue(sema_values.left, context); + const right = try llvm.emitValue(sema_values.right, context); + const arguments = [_]*LLVM.Value{ left, right }; + + const call = llvm.builder.createCall(intrinsic_function_type, intrinsic_function.toValue(), &arguments, arguments.len, "min".ptr, "min".len, null) orelse return LLVM.Value.Instruction.Error.call; + return call.toValue(); + }, + else => |t| @panic(@tagName(t)), + } + }, + .array_coerce_to_slice => |sema_value| { + const result_type = try llvm.getType(intrinsic.type); + const slice_type = result_type.toStruct() orelse unreachable; + const appointee_value = try llvm.emitValue(sema_value, context); + switch (llvm.sema.values.array.get(sema_value).*) { + .array_initialization => |array_initialization_index| { + const array_initialization = llvm.sema.values.container_initializations.get(array_initialization_index); + if (array_initialization.is_comptime) { + const constant_array = appointee_value.toConstant() orelse unreachable; + const array_type = try llvm.getType(array_initialization.type); + const is_constant = true; + const linkage = LLVM.Linkage.@"extern"; + const thread_local_mode = LLVM.ThreadLocalMode.not_thread_local; + const externally_initialized = false; + const global_variable = llvm.module.addGlobalVariable(array_type, is_constant, linkage, constant_array, "", "".len, null, thread_local_mode, address_space, externally_initialized) orelse return LLVM.Value.Error.constant_array; + const is_signed = false; + const len_constant = llvm.context.getConstantInt(@bitSizeOf(usize), array_initialization.field_initializations.items.len, is_signed) orelse unreachable; + const slice_values = [2]*LLVM.Value.Constant{ global_variable.toConstant(), len_constant.toConstant() }; + const constant = slice_type.instantiateConstant(&slice_values, slice_values.len) orelse unreachable; + return constant.toValue(); + } else { + unreachable; + } + // const ptr_indices = [1]c_uint{0}; + // const is_signed = false; + // const len_indices = [1]c_uint{1}; + // const insert_slice_len = llvm.builder.createInsertValue(result.toValue(), len_constant.toValue(), &len_indices, len_indices.len, "insert_slice_len", "insert_slice_len".len) orelse unreachable; + // _ = insert_slice_len; + // _ = insert_slice_ptr; + // return result.toValue(); + // } else { + // unreachable; + // } + }, + else => |t| @panic(@tagName(t)), + } + + // switch (llvm.sema.values.array.get(sema_value).*) { + // .string_literal => |string_literal| { + // const name = llvm.sema.getName(string_literal.hash).?; + // const expected_type = try llvm.getType(string_literal.type); + // assert(expected_type.compare(appointee_value.getType() orelse unreachable)); + // const ptr_indices = [1]c_uint{0}; + // const insert_slice_ptr = llvm.builder.createInsertValue(result.toValue(), appointee_value, &ptr_indices, ptr_indices.len, "insert_slice_ptr", "insert_slice_ptr".len) orelse unreachable; + // const is_signed = false; + // const len_constant = llvm.context.getConstantInt(@bitSizeOf(usize), name.len, is_signed) orelse unreachable; + // const len_indices = [1]c_uint{1}; + // const insert_slice_len = llvm.builder.createInsertValue(result.toValue(), len_constant.toValue(), &len_indices, len_indices.len, "insert_slice_len", "insert_slice_len".len) orelse unreachable; + // _ = insert_slice_len; + // _ = insert_slice_ptr; + // return result.toValue(); + // }, + // else => |t| @panic(@tagName(t)), + // } + unreachable; + }, + else => |t| @panic(@tagName(t)), + } + } + + fn renderDeclarationName(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, declaration_index: Compilation.Declaration.Index, mangle: bool) anyerror![]const u8 { + if (llvm.declaration_names.get(declaration_index)) |name| { + return name; + } else { + const declaration = unit.declarations.get(declaration_index); + const base_declaration_name = unit.getIdentifier(declaration.name); + var list = ArrayList(u8){}; + + try list.insertSlice(context.allocator, 0, base_declaration_name); + + if (mangle) { + switch (declaration.scope.kind) { + .compilation_unit, .file, .file_container, .container => { + var scope_it: ?*Compilation.Scope = declaration.scope; + while (scope_it) |scope| : (scope_it = scope.parent) { + const type_index = switch (scope.kind) { + .compilation_unit => break, + .file => b: { + const file = @fieldParentPtr(Compilation.File, "scope", scope); + break :b file.type; + }, + else => break, + }; + + if (unit.type_declaration_map.get(type_index)) |scope_declaration_index| { + const scope_declaration = unit.declarations.get(scope_declaration_index); + const declaration_name = unit.getIdentifier( scope_declaration.name); + try list.insert(context.allocator, 0, '.'); + try list.insertSlice(context.allocator, 0, declaration_name); + } + } + }, + .function, .block => {}, + } + } + + // TODO: enhance declaration name rendering with file scope name + // const scope = declaration.scope; + try llvm.declaration_names.putNoClobber(context.allocator, declaration_index, list.items); + + return list.items; + } + } + + fn emitBlock(llvm: *LLVM, block_index: Compilation.Block.Index, context: Compilation.ScopeType, emit_arguments: bool) !void { + const block = llvm.sema.values.blocks.get(block_index); + const previous_scope = llvm.scope; + const lexical_block = llvm.debug_info_builder.createLexicalBlock(previous_scope, llvm.file, block.line + 1, block.column + 1) orelse unreachable; + llvm.scope = lexical_block.toScope(); + llvm.builder.setCurrentDebugLocation(llvm.context, block.line + 1, block.column + 1, llvm.scope, llvm.function); + + if (emit_arguments) { + const sema_function = llvm.sema.types.function_definitions.get(llvm.sema_function); + const function_prototype = llvm.sema.types.function_prototypes.get(llvm.sema.types.array.get(sema_function.prototype).function); + _ = function_prototype; + // TODO: rewrite + var argument_buffer: [16]*LLVM.Value.Argument = undefined; + var argument_count: usize = argument_buffer.len; + llvm.function.getArguments(&argument_buffer, &argument_count); + const arguments = argument_buffer[0..argument_count]; + + for (arguments) |arg| { + const argument_value = arg.toValue(); + const alloca_array_size = null; + const declaration_alloca = llvm.builder.createAlloca(argument_value.getType(), address_space, alloca_array_size, "", "".len) orelse return LLVM.Value.Instruction.Error.alloca; + const is_volatile = false; + const store = llvm.builder.createStore(argument_value, declaration_alloca.toValue(), is_volatile) orelse return LLVM.Value.Instruction.Error.store; + _ = store; + } + } + + for (block.statements.items) |sema_statement_value_index| { + try llvm.emitStatement(sema_statement_value_index, context); + } + + llvm.scope = previous_scope; + } + + fn getDebugInfoFile(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, sema_file_index: Compilation.File.Index) !*DebugInfo.File { + if (llvm.debug_info_file_map.get(sema_file_index)) |file| { + return file; + } else { + const sema_file = unit.files.get(sema_file_index); + const sub_path = std.fs.path.dirname(sema_file.relative_path) orelse ""; + const file_path = std.fs.path.basename(sema_file.relative_path); + const directory_path = try std.fs.path.join(context.allocator, &.{ sema_file.package.directory.path, sub_path }); + const debug_file = llvm.debug_info_builder.?.createFile(file_path.ptr, file_path.len, directory_path.ptr, directory_path.len) orelse unreachable; + try llvm.debug_info_file_map.putNoClobber(context.allocator, sema_file_index, debug_file); + return debug_file; + } + } + + fn renderTypeName(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, sema_type_index: Compilation.Type.Index) ![]const u8 { + if (llvm.type_name_map.get(sema_type_index)) |typename| { + return typename; + } else { + const sema_type = unit.types.get(sema_type_index); + const result: []const u8 = switch (sema_type.*) { + .integer => |integer| b: { + const signedness_char: u8 = switch (integer.signedness) { + .signed => 's', + .unsigned => 'u', + }; + const name = try std.fmt.allocPrint(context.allocator, "{c}{}", .{ signedness_char, integer.bit_count }); + break :b name; + }, + .bool => "bool", + .pointer => |pointer| b: { + var name = ArrayList(u8){}; + try name.appendSlice(context.allocator, "&"); + if (pointer.mutability == .@"const") { + try name.appendSlice(context.allocator, "const"); + } + try name.appendSlice(context.allocator, " "); + const element_type_name = try llvm.renderTypeName(unit, context, pointer.type); + try name.appendSlice(context.allocator, element_type_name); + break :b name.items; + }, + .@"enum", + .@"struct", + => b: { + if (unit.type_declaration_map.get(sema_type_index)) |type_declaration_index| { + const declaration = unit.declarations.get(type_declaration_index); + const name = unit.getIdentifier(declaration.name); + break :b name; + } else { + unreachable; + } + }, + // .optional => |optional| b: { + // var name = ArrayList(u8){}; + // const element_type_name = try llvm.renderTypeName(optional.element_type); + // try name.writer(context.allocator).print("?{s}", .{element_type_name}); + // break :b name.items; + // }, + // .array => |array| b: { + // var name = ArrayList(u8){}; + // const element_type_name = try llvm.renderTypeName(array.element_type); + // try name.writer(context.allocator).print("[{}]{s}", .{ array.element_count, element_type_name }); + // break :b name.items; + // }, + // .slice => |slice| b: { + // var name = ArrayList(u8){}; + // try name.appendSlice(context.allocator, "[] "); + // if (slice.@"const") { + // try name.appendSlice(context.allocator, "const "); + // } + // const element_type_name = try llvm.renderTypeName(slice.element_type); + // try name.appendSlice(context.allocator, element_type_name); + // break :b name.items; + // }, + else => |t| @panic(@tagName(t)), + }; + + try llvm.type_name_map.putNoClobber(context.allocator, sema_type_index, result); + return result; + } + } + + fn createDebugStructType(llvm: *LLVM, arguments: struct { + scope: ?*LLVM.DebugInfo.Scope, + name: []const u8, + file: ?*LLVM.DebugInfo.File, + line: u32, + bitsize: u64, + alignment: u32, + field_types: []const *LLVM.DebugInfo.Type, + }) *LLVM.DebugInfo.Type.Composite { + const flags = LLVM.DebugInfo.Node.Flags{ + .visibility = .none, + .forward_declaration = false, + .apple_block = false, + .block_by_ref_struct = false, + .virtual = false, + .artificial = false, + .explicit = false, + .prototyped = false, + .objective_c_class_complete = false, + .object_pointer = false, + .vector = false, + .static_member = false, + .lvalue_reference = false, + .rvalue_reference = false, + .reserved = false, + .inheritance = .none, + .introduced_virtual = false, + .bit_field = false, + .no_return = false, + .type_pass_by_value = false, + .type_pass_by_reference = false, + .enum_class = false, + .thunk = false, + .non_trivial = false, + .big_endian = false, + .little_endian = false, + .all_calls_described = false, + }; + + const struct_type = llvm.debug_info_builder.createStructType(arguments.scope, arguments.name.ptr, arguments.name.len, arguments.file, arguments.line, arguments.bitsize, arguments.alignment, flags, null, arguments.field_types.ptr, arguments.field_types.len) orelse unreachable; + return struct_type; + } + + fn getDebugType(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, sema_type_index: Compilation.Type.Index) !*LLVM.DebugInfo.Type { + if (llvm.debug_info_builder) |di_builder| { + if (llvm.debug_type_map.get(sema_type_index)) |t| { + return t; + } else { + const name = try llvm.renderTypeName(unit, context, sema_type_index); + const sema_type = unit.types.get(sema_type_index); + const result: *LLVM.DebugInfo.Type = switch (sema_type.*) { + .integer => |integer| b: { + const dwarf_encoding: LLVM.DebugInfo.AttributeType = switch (integer.signedness) { + .unsigned => .unsigned, + .signed => .signed, + }; + const flags = LLVM.DebugInfo.Node.Flags{ + .visibility = .none, + .forward_declaration = false, + .apple_block = false, + .block_by_ref_struct = false, + .virtual = false, + .artificial = false, + .explicit = false, + .prototyped = false, + .objective_c_class_complete = false, + .object_pointer = false, + .vector = false, + .static_member = false, + .lvalue_reference = false, + .rvalue_reference = false, + .reserved = false, + .inheritance = .none, + .introduced_virtual = false, + .bit_field = false, + .no_return = false, + .type_pass_by_value = false, + .type_pass_by_reference = false, + .enum_class = false, + .thunk = false, + .non_trivial = false, + .big_endian = false, + .little_endian = false, + .all_calls_described = false, + }; + const integer_type = di_builder.createBasicType(name.ptr, name.len, integer.bit_count, dwarf_encoding, flags) orelse unreachable; + break :b integer_type; + }, + .pointer => |pointer| b: { + const element_type = try llvm.getDebugType(unit, context, pointer.type); + const pointer_width = @bitSizeOf(usize); + const alignment = 0; + const pointer_type = di_builder.createPointerType(element_type, pointer_width, alignment, name.ptr, name.len) orelse unreachable; + break :b pointer_type.toType(); + }, + .bool => { + const flags = LLVM.DebugInfo.Node.Flags{ + .visibility = .none, + .forward_declaration = false, + .apple_block = false, + .block_by_ref_struct = false, + .virtual = false, + .artificial = false, + .explicit = false, + .prototyped = false, + .objective_c_class_complete = false, + .object_pointer = false, + .vector = false, + .static_member = false, + .lvalue_reference = false, + .rvalue_reference = false, + .reserved = false, + .inheritance = .none, + .introduced_virtual = false, + .bit_field = false, + .no_return = false, + .type_pass_by_value = false, + .type_pass_by_reference = false, + .enum_class = false, + .thunk = false, + .non_trivial = false, + .big_endian = false, + .little_endian = false, + .all_calls_described = false, + }; + const boolean_type = di_builder.createBasicType("bool", "bool".len, 1, .boolean, flags) orelse unreachable; + return boolean_type; + }, + // // .@"struct" => |struct_index| b: { + // // const sema_struct_type = unit.structs.get(struct_index); + // // + // // var field_types = try ArrayList(*LLVM.DebugInfo.Type).initCapacity(context.allocator, sema_struct_type.fields.items.len); + // // for (sema_struct_type.fields.items) |struct_field_index| { + // // const struct_field = llvm.sema.types.container_fields.get(struct_field_index); + // // const field_type = try llvm.getDebugType(struct_field.type); + // // field_types.appendAssumeCapacity(field_type); + // // } + // // const sema_declaration_index = llvm.sema.map.types.get(sema_type_index) orelse unreachable; + // // const sema_declaration = llvm.sema.values.declarations.get(sema_declaration_index); + // // const file = try llvm.getDebugInfoFile(llvm.sema.values.scopes.get(sema_declaration.scope).file); + // // const line = sema_declaration.line + 1; + // // const struct_type = llvm.createDebugStructType(.{ .scope = null, .name = name, .file = file, .line = line, .bitsize = sema_type.getBitSize(llvm.sema), .alignment = 0, .field_types = field_types.items }); + // // break :b struct_type.toType(); + // // }, + // .@"enum" => |enum_index| b: { + // const enum_type = llvm.sema.types.enums.get(enum_index); + // var enumerators = try ArrayList(*LLVM.DebugInfo.Type.Enumerator).initCapacity(context.allocator, enum_type.fields.items.len); + // for (enum_type.fields.items) |enum_field_index| { + // const enum_field = llvm.sema.types.enum_fields.get(enum_field_index); + // const enum_field_name = llvm.sema.getName(enum_field.name).?; + // + // const is_unsigned = true; + // const enumerator = llvm.debug_info_builder.createEnumerator(enum_field_name.ptr, enum_field_name.len, enum_field.value, is_unsigned) orelse unreachable; + // enumerators.appendAssumeCapacity(enumerator); + // } + // + // const sema_declaration_index = llvm.sema.map.types.get(sema_type_index) orelse unreachable; + // const sema_declaration = llvm.sema.values.declarations.get(sema_declaration_index); + // const file = try llvm.getDebugInfoFile(llvm.sema.values.scopes.get(sema_declaration.scope).file); + // const bit_size = llvm.sema.types.array.get(enum_type.backing_type).getBitSize(llvm.sema); + // const backing_type = try llvm.getDebugType(enum_type.backing_type); + // const alignment = 0; + // const line = sema_declaration.line + 1; + // const enumeration_type = llvm.debug_info_builder.createEnumerationType(llvm.scope, name.ptr, name.len, file, line, bit_size, alignment, enumerators.items.ptr, enumerators.items.len, backing_type) orelse unreachable; + // break :b enumeration_type.toType(); + // }, + // .optional => |optional| { + // const element_type = try llvm.getDebugType(optional.element_type); + // const bool_type = try llvm.getDebugType(Compilation.Type.boolean); + // const field_types = [2]*LLVM.DebugInfo.Type{ element_type, bool_type }; + // const struct_type = llvm.createDebugStructType(.{ + // .scope = null, + // .name = name, + // .file = null, + // .line = 1, + // .bitsize = sema_type.getBitSize(llvm.sema), + // .alignment = 0, + // .field_types = &field_types, + // }); + // return struct_type.toType(); + // }, + // .slice => |slice| b: { + // const pointer_type = try llvm.getDebugType(llvm.sema.map.pointers.get(.{ + // .element_type = slice.element_type, + // .many = true, + // .@"const" = slice.@"const", + // .termination = slice.termination, + // }).?); + // const len_type = try llvm.getDebugType(Compilation.Type.usize); + // const scope = null; + // const file = null; + // const line = 1; + // // const forward_declared_type = llvm.debug_info_builder.createReplaceableCompositeType(tag_count, name.ptr, name.len, scope, file, line) orelse unreachable; + // // tag_count += 1; + // + // const field_types = [2]*LLVM.DebugInfo.Type{ pointer_type, len_type }; + // const struct_type = llvm.createDebugStructType(.{ + // .scope = scope, + // .name = name, + // .file = file, + // .line = line, + // .bitsize = 2 * @bitSizeOf(usize), + // .alignment = @alignOf(usize), + // .field_types = &field_types, + // }); + // break :b struct_type.toType(); + // }, + // .array => |array| b: { + // const byte_size = array.element_count * llvm.sema.types.array.get(array.element_type).getSize(); + // const bit_size = byte_size * 8; + // const element_type = try llvm.getDebugType(array.element_type); + // // extern fn bindings.NativityLLVMDebugInfoBuilderCreateArrayType(builder: *LLVM.DebugInfo.Builder, bit_size: u64, alignment: u32, type: *LLVM.DebugInfo.Type, element_count: usize) ?*LLVM.DebugInfo.Type.Composite; + // const array_type = llvm.debug_info_builder.createArrayType(bit_size, 1, element_type, array.element_count) orelse unreachable; + // break :b array_type.toType(); + // }, + else => |t| @panic(@tagName(t)), + }; + + try llvm.debug_type_map.putNoClobber(context.allocator, sema_type_index, result); + + return result; + } + } else { + unreachable; + } + } + + fn emitRightValue(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, value_index: Compilation.Value.Index) !*LLVM.Value { + const value = unit.values.get(value_index); + + switch (value.*) { + .reference => |declaration_index| { + const declaration = unit.declarations.get(declaration_index); + switch (declaration.scope.kind) { + .function => { + const function_definition = @fieldParentPtr(Compilation.Function.Definition, "scope", declaration.scope); + const arg_instruction_index = function_definition.argument_map.get(declaration_index).?; + const arg_instruction = unit.instructions.get(arg_instruction_index); + const arg_type = try llvm.getType(unit, context, arg_instruction.argument_declaration.type); + const argument_alloca = llvm.argument_allocas.get(arg_instruction_index).?; + const is_volatile = false; + const load = llvm.builder.createLoad(arg_type, argument_alloca, is_volatile, "declaration_reference", "declaration_reference".len) orelse return LLVM.Value.Instruction.Error.load; + return load.toValue(); + }, + .block => { + var scope_it: ?*Compilation.Scope = declaration.scope; + const function_scope = while (scope_it) |scope| : (scope_it = scope.parent){ + if (scope.kind == .function) { + break scope; + } + } else { + unreachable; + }; + const function_definition = @fieldParentPtr(Compilation.Function.Definition, "scope", function_scope); + const local_variable_instruction = function_definition.declarations.get(declaration_index).?; + const variable_type = try llvm.getType(unit, context, unit.instructions.get(local_variable_instruction).stack_slot.type); + const alloca = llvm.alloca_map.get(local_variable_instruction).?; + const is_volatile = false; + const load = llvm.builder.createLoad(variable_type, alloca, is_volatile, "declaration_reference", "declaration_reference".len) orelse return LLVM.Value.Instruction.Error.load; + return load.toValue(); + }, + else => |t| @panic(@tagName(t)), + } + }, + .intrinsic => |intrinsic_index| { + const intrinsic = unit.intrinsics.get(intrinsic_index); + switch (intrinsic.kind) { + .int_to_pointer => |int_value_index| { + const integer_value = try llvm.emitRightValue(unit, context, int_value_index); + const cast_type = LLVM.Value.Instruction.Cast.Type.int_to_pointer; + const cast_name = @tagName(cast_type); + const cast = llvm.builder.createCast(cast_type, integer_value, try llvm.getType(unit, context, intrinsic.type), cast_name.ptr, cast_name.len) orelse return LLVM.Value.Instruction.Error.cast; + return cast; + }, + else => |t| @panic(@tagName(t)), + } + }, + .unary_operation => |unary_operation_index| { + const unary_operation = unit.unary_operations.get(unary_operation_index); + switch (unary_operation.id) { + else => |t| @panic(@tagName(t)), + } + }, + .enum_field => |enum_field_index| { + const enum_field = unit.enum_fields.get(enum_field_index); + // // + switch (unit.types.get(enum_field.parent).*) { + .@"enum" => |enum_index| { + const enum_type = unit.enums.get(enum_index); + const backing_type = unit.types.get(unit.unwrapTypeCopy(enum_type.backing_type)); + switch (backing_type.*) { + .integer => |integer| { + const is_signed = switch (integer.signedness) { + .signed => true, + .unsigned => false, + }; + assert(!is_signed); + const enum_value = llvm.context.getConstantInt(integer.bit_count, enum_field.value, is_signed) orelse unreachable; + return enum_value.toValue(); + }, + else => unreachable, + } + }, + else => |t| @panic(@tagName(t)), + } + @panic("TODO: compute backing type in sema"); + }, + else => |t| @panic(@tagName(t)), + } + + unreachable; + } + + fn emitBasicBlock(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, basic_block_index: Compilation.BasicBlock.Index) !void { + const block_name = ""; + const basic_block = llvm.context.createBasicBlock(block_name, block_name.len, llvm.function, null) orelse return Error.basic_block; + llvm.builder.setInsertPoint(basic_block); + const sema_basic_block = unit.basic_blocks.get(basic_block_index); + + for (sema_basic_block.instructions.items) |instruction_index| { + const sema_instruction = unit.instructions.get(instruction_index); + + switch (sema_instruction.*) { + .push_scope => |push_scope| { + _ = push_scope; // autofix + // const old_scope = try llvm.getScope(unit, context, push_scope.old); + // const lexical_block = llvm.debug_info_builder.createLexicalBlock(old_scope, llvm.file, push_scope.new.line + 1, push_scope.new.column + 1) orelse unreachable; + // try llvm.scope_map.putNoClobber(context.allocator, push_scope.new, lexical_block.toScope()); + // llvm.scope = lexical_block.toScope(); + }, + .debug_checkpoint => |debug_checkpoint| { + _ = debug_checkpoint; // autofix + // const scope = try llvm.getScope(unit, context, debug_checkpoint.scope); + // assert(scope == llvm.scope); + // llvm.builder.setCurrentDebugLocation(llvm.context, debug_checkpoint.line + 1, debug_checkpoint.column + 1, scope, llvm.function); + }, + .inline_assembly => |inline_assembly_index| { + const assembly_block = unit.inline_assembly.get(inline_assembly_index); + + var assembly_statements = ArrayList(u8){}; + var constraints = ArrayList(u8){}; + var operand_values = ArrayList(*LLVM.Value){}; + var operand_types = ArrayList(*LLVM.Type){}; + + switch (unit.descriptor.target.cpu.arch) { + .x86_64 => { + for (assembly_block.instructions) |assembly_instruction_index| { + const instruction = unit.assembly_instructions.get(assembly_instruction_index); + const instruction_id: Compilation.InlineAssembly.x86_64.Instruction = @enumFromInt(instruction.id); + + try assembly_statements.appendSlice(context.allocator, switch (instruction_id) { + .xor => "xorl", + .mov => "movq", + .@"and" => "andq", + .call => "callq", + }); + try assembly_statements.append(context.allocator, ' '); + + if (instruction.operands.len > 0) { + var reverse_operand_iterator = std.mem.reverseIterator(instruction.operands); + + while (reverse_operand_iterator.next()) |operand| { + switch (operand) { + .register => |register_value| { + const register: Compilation.InlineAssembly.x86_64.Register = @enumFromInt(register_value); + try assembly_statements.append(context.allocator, '%'); + try assembly_statements.appendSlice(context.allocator, @tagName(register)); + }, + .number_literal => |literal| { + try assembly_statements.writer(context.allocator).print("$$0x{x}", .{literal}); + }, + .value_index => |original_value_index| { + const value_index = unit.unwrapValueCopy(original_value_index); + switch (unit.values.get(value_index).*) { + .function_definition => |function_definition_index| { + const function_definition = llvm.function_definition_map.get(function_definition_index).?; + const value = function_definition.toValue(); + try assembly_statements.writer(context.allocator).print("${{{}:P}}", .{operand_values.items.len}); + try operand_values.append(context.allocator, value); + try constraints.append(context.allocator, 'X'); + const value_type = value.getType(); + try operand_types.append(context.allocator, value_type); + }, + else => |t| @panic(@tagName(t)), + } + }, + } + + try assembly_statements.appendSlice(context.allocator, ", "); + } + + _ = assembly_statements.pop(); + _ = assembly_statements.pop(); + } + + try assembly_statements.appendSlice(context.allocator, "\n\t"); + } + + try constraints.appendSlice(context.allocator, ",~{dirflag},~{fpsr},~{flags}"); + }, + else => |t| @panic(@tagName(t)), + } + + const is_var_args = false; + const function_type = LLVM.Context.getFunctionType(try llvm.getType(unit, context, Compilation.Type.Index.void), operand_types.items.ptr, operand_types.items.len, is_var_args) orelse unreachable; + const has_side_effects = true; + const is_align_stack = true; + const dialect = LLVM.Value.InlineAssembly.Dialect.@"at&t"; + const can_throw = false; + + const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly_statements.items.ptr, assembly_statements.items.len, constraints.items.ptr, constraints.items.len, has_side_effects, is_align_stack, dialect, can_throw) orelse return LLVM.Value.Error.inline_assembly; + const call = llvm.builder.createCall(function_type, inline_assembly.toValue(), operand_values.items.ptr, operand_values.items.len, "", "".len, null) orelse return LLVM.Value.Instruction.Error.call; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call.toValue()); + }, + .pop_scope => {}, + .argument_declaration => |argument_declaration| { + var argument_buffer: [16]*LLVM.Value.Argument = undefined; + var argument_count: usize = argument_buffer.len; + llvm.function.getArguments(&argument_buffer, &argument_count); + const arguments = argument_buffer[0..argument_count]; + const argument = arguments[llvm.arg_index]; + llvm.arg_index += 1; + const name = unit.getIdentifier(argument_declaration.name); + argument.toValue().setName(name.ptr, name.len); + const argument_type_index = unit.unwrapTypeCopy(argument_declaration.type); + switch (unit.types.get(argument_type_index).*) { + .void, .noreturn, .type => unreachable, + .comptime_int => unreachable, + .bool => unreachable, + .unresolved => unreachable, + .copy => unreachable, + .@"struct" => {}, + .@"enum" => {}, + .function => unreachable, + .integer => {}, + .pointer => {}, + } + const argument_type = argument.toValue().getType(); + const alloca_array_size: ?*LLVM.Value = null; + const argument_value = argument.toValue(); + const declaration_alloca = llvm.builder.createAlloca(argument_type, address_space, alloca_array_size, "", "".len) orelse return LLVM.Value.Instruction.Error.alloca; + const is_volatile = false; + const store = llvm.builder.createStore(argument_value, declaration_alloca.toValue(), is_volatile) orelse return LLVM.Value.Instruction.Error.store; + _ = store; // autofix + try llvm.argument_allocas.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); + }, + .stack_slot => |stack_slot| { + switch (unit.types.get(stack_slot.type).*) { + .void, .noreturn, .type => unreachable, + .comptime_int => unreachable, + .bool => unreachable, + .unresolved => unreachable, + .copy => unreachable, + .@"struct" => {}, + .@"enum" => {}, + .function => unreachable, + .integer => {}, + .pointer => {}, + } + const declaration_type = try llvm.getType(unit, context, stack_slot.type); + const alloca_array_size = null; + const declaration_alloca = llvm.builder.createAlloca(declaration_type, address_space, alloca_array_size, "", "".len) orelse return LLVM.Value.Instruction.Error.alloca; + try llvm.alloca_map.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); + }, + .store => |store| { + const right = llvm.llvm_instruction_map.get(store.source).?; + + const is_volatile = false; + const destination = if (llvm.alloca_map.get(store.destination)) |alloca| alloca else switch(unit.instructions.get(store.destination).*) { + .global_variable => |global_variable_index| b: { + const global = try llvm.getGlobal(unit, context, global_variable_index); + break :b global.toValue(); + }, + else => |t| @panic(@tagName(t)), + }; + + const store_instruction = llvm.builder.createStore(right, destination, is_volatile) orelse return LLVM.Value.Instruction.Error.store; + _ = store_instruction; + }, + .cast => |cast|{ + const value = try llvm.emitRightValue(unit, context, cast.value); + const dest_type = try llvm.getType(unit, context, cast.type); + switch (cast.id) { + .int_to_pointer => { + const cast_type = LLVM.Value.Instruction.Cast.Type.int_to_pointer; + const cast_name = @tagName(cast_type); + const cast_instruction = llvm.builder.createCast(cast_type, value, value.getType(), cast_name.ptr, cast_name.len) orelse return LLVM.Value.Instruction.Error.cast; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, cast_instruction); + }, + .enum_to_int => { + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, value); + }, + .sign_extend => { + const sign_extend = llvm.builder.createCast(.sign_extend, value, dest_type, "sign_extend", "sign_extend".len) orelse return LLVM.Value.Instruction.Error.cast; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, sign_extend); + }, + else => |t| @panic(@tagName(t)), + } + }, + .load => |load| { + const value_to_load = unit.values.get(load.value); + switch (value_to_load.*) { + .reference => { + const load_instruction = try llvm.emitRightValue(unit, context, load.value); + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, load_instruction); + }, + else => |t| @panic(@tagName(t)), + } + }, + .load_instruction => |load_instruction| { + const value = if (llvm.llvm_instruction_map.get(load_instruction.value)) |v| v else blk: { + const instruction = unit.instructions.get(load_instruction.value); + break :blk switch (instruction.*) { + .argument_declaration => llvm.argument_allocas.get(load_instruction.value).?, + else => |t| @panic(@tagName(t)), + }; + }; + + const value_type = switch (unit.instructions.get( load_instruction.value).*) { + .argument_declaration => |argument| try llvm.getType(unit, context, argument.type), + .stack_slot => |stack_slot| try llvm.getType(unit, context, stack_slot.type), + else => |t| @panic(@tagName(t)), + }; + + const is_volatile = false; + const load = llvm.builder.createLoad(value_type, value, is_volatile, "", "".len) orelse return LLVM.Value.Instruction.Error.load; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, load.toValue()); + }, + .load_global => |load_global| { + const global = try llvm.getGlobal(unit, context, load_global.value); + const global_type = try llvm.getType(unit, context, unit.global_variables.get(load_global.value).symbol.type); + const is_volatile = false; + const load = llvm.builder.createLoad(global_type, global.toValue(), is_volatile, "", "".len) orelse return LLVM.Value.Instruction.Error.load; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, load.toValue()); + }, + .constant_int => |constant_int| { + const integer = try llvm.emitInteger(unit, constant_int); + try llvm.llvm_instruction_map.put(context.allocator, instruction_index, integer.toValue()); + }, + .integer_binary_operation => |binary_operation| { + const left = llvm.llvm_instruction_map.get(binary_operation.left).?; + const right = llvm.llvm_instruction_map.get(binary_operation.right).?; + const no_signed_wrapping = binary_operation.signedness == .signed; + const no_unsigned_wrapping = binary_operation.signedness == .unsigned; + const name = @tagName(binary_operation.id); + + const instruction = switch (binary_operation.id) { + .add => llvm.builder.createAdd(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add, + .mul => llvm.builder.createMultiply(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.multiply, + // .sub => llvm.builder.createSub(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add, + //else => |t| @panic(@tagName(t)), + }; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, instruction); + }, + .call => |call| { + var argument_buffer: [32]*LLVM.Value = undefined; + const argument_count = call.arguments.len; + const arguments = argument_buffer[0..argument_count]; + + switch (call.callable) { + .function_definition => |function_definition_index| { + const callee = llvm.function_definition_map.get(function_definition_index).?; + const function_definition = unit.function_definitions.get(function_definition_index); + assert(function_definition.type == call.function_type); + + const function_prototype_type = unit.types.get(function_definition.type); + const function_prototype = unit.function_prototypes.get(function_prototype_type.function); + // const declaration_index = llvm.sema.map.function_definitions.get(function_definition_index).?; + // const declaration = llvm.sema.values.declarations.get(declaration_index); + // const declaration_name = llvm.sema.getName(declaration.name).?; + // std.debug.print("Call to {s}\n", .{declaration_name}); + + for (function_prototype.argument_types, call.arguments, arguments) |argument_declaration_index, argument_instruction_index, *argument| { + _ = argument_declaration_index; // autofix + // const argument_declaration = llvm.sema.values.declarations.get(argument_declaration_index); + // const argument_type = argument_declaration.getType(); + const argument_instruction = unit.instructions.get(argument_instruction_index); + switch (argument_instruction.*) { + .load_instruction => |load_instruction| { + const value = if (llvm.llvm_instruction_map.get(load_instruction.value)) |v| v else blk: { + const instruction = unit.instructions.get(load_instruction.value); + break :blk switch (instruction.*) { + .argument_declaration => llvm.argument_allocas.get(load_instruction.value).?, + else => |t| @panic(@tagName(t)), + }; + }; + + const value_type = switch (unit.instructions.get( load_instruction.value).*) { + .argument_declaration => |arg| try llvm.getType(unit, context, arg.type), + .stack_slot => |stack_slot| try llvm.getType(unit, context, stack_slot.type), + else => |t| @panic(@tagName(t)), + }; + + const is_volatile = false; + const load = llvm.builder.createLoad(value_type, value, is_volatile, "", "".len) orelse return LLVM.Value.Instruction.Error.load; + argument.* = load.toValue(); + // try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, load.toValue()); + }, + else => |t| @panic(@tagName(t)), + } + } + + const llvm_calling_convention = callee.getCallingConvention(); + const name = ""; + const call_type = try llvm.getType(unit, context, call.function_type); + const function_type = call_type.toFunction() orelse unreachable; + const call_instruction = llvm.builder.createCall(function_type, callee.toValue(), arguments.ptr, arguments.len, name.ptr, name.len, null) orelse return LLVM.Value.Instruction.Error.call; + call_instruction.setCallingConvention(llvm_calling_convention); + + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call_instruction.toValue()); + }, + // else => |t| @panic(@tagName(t)), + } + }, + .ret => |return_instruction| { + const value = llvm.llvm_instruction_map.get(return_instruction).?; + const ret = llvm.builder.createRet(value) orelse return LLVM.Value.Instruction.Error.ret; + _ = ret; // autofix + }, + .syscall => |syscall| { + var syscall_argument_buffer: [7]*LLVM.Value = undefined; + var syscall_argument_type_buffer: [7]*LLVM.Type = undefined; + const sema_syscall_arguments = syscall.arguments; + const syscall_argument_count: usize = sema_syscall_arguments.len; + const syscall_arguments = syscall_argument_buffer[0..syscall_argument_count]; + const syscall_argument_types = syscall_argument_type_buffer[0..syscall_argument_count]; + + for (sema_syscall_arguments, syscall_arguments, syscall_argument_types) |sema_syscall_argument_value_index, *syscall_argument, *syscall_argument_type| { + const llvm_value = llvm.llvm_instruction_map.get(sema_syscall_argument_value_index).?; + syscall_argument.* = llvm_value; + syscall_argument_type.* = llvm_value.getType(); + } + + const return_type = try llvm.getType(unit, context, Compilation.Type.Index.usize); + const is_var_args = false; + const function_type = LLVM.Context.getFunctionType(return_type, syscall_argument_types.ptr, syscall_argument_types.len, is_var_args) orelse unreachable; + var constraints = ArrayList(u8){}; + + const inline_asm = switch (unit.descriptor.target.cpu.arch) { + .x86_64 => blk: { + try constraints.appendSlice(context.allocator, "={rax}"); + + const syscall_registers = [7][]const u8{ "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9" }; + for (syscall_registers[0..syscall_argument_count]) |syscall_register| { + try constraints.appendSlice(context.allocator, ",{"); + try constraints.appendSlice(context.allocator, syscall_register); + try constraints.append(context.allocator, '}'); + } + + try constraints.appendSlice(context.allocator, ",~{rcx},~{r11},~{memory}"); + + const assembly = "syscall"; + const has_side_effects = true; + const is_align_stack = true; + const can_throw = false; + const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly, assembly.len, constraints.items.ptr, constraints.items.len, has_side_effects, is_align_stack, LLVM.Value.InlineAssembly.Dialect.@"at&t", can_throw) orelse return Value.Error.inline_assembly; + break :blk inline_assembly; + }, + else => |t| @panic(@tagName(t)), + }; + + const call_to_asm = llvm.builder.createCall(function_type, inline_asm.toValue(), syscall_arguments.ptr, syscall_arguments.len, "syscall", "syscall".len, null) orelse return LLVM.Value.Instruction.Error.call; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call_to_asm.toValue()); + }, + .@"unreachable" => { + _ = llvm.builder.createUnreachable() orelse return LLVM.Value.Instruction.Error.@"unreachable"; + }, + else => |t| @panic(@tagName(t)), + } + } + } + +fn getGlobal(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, global_variable_index: Compilation.GlobalVariable.Index) !*LLVM.Value.Constant.GlobalVariable { + if (llvm.global_variable_map.get(global_variable_index)) |result| { + return result; + } else { + const global_variable_descriptor = unit.global_variables.get(global_variable_index); + const is_constant = switch (global_variable_descriptor.mutability) { + .@"const" => true, + .@"var" => false, + }; + const global_type = try llvm.getType(unit, context, global_variable_descriptor.symbol.type); + const name = unit.getIdentifier(global_variable_descriptor.symbol.name); + // TODO: + const linkage = LLVM.Linkage.@"extern"; + // Manual lower here to make sure the expression is constant? + const initializer = switch (unit.values.get(global_variable_descriptor.value).*) { + .integer => |integer| b: { + const constant_int = try llvm.emitInteger(unit, integer); + const constant = constant_int.toConstant(); + break :b constant; + }, + .undefined => b: { + const undefined_value = global_type.getUndefined() orelse unreachable; + break :b undefined_value.toConstant(); + }, + else => |t| @panic(@tagName(t)), + }; + + const thread_local_mode = LLVM.ThreadLocalMode.not_thread_local; + const externally_initialized = false; + const global_variable = llvm.module.addGlobalVariable(global_type, is_constant, linkage, initializer, name.ptr, name.len, null, thread_local_mode, address_space, externally_initialized) orelse return LLVM.Value.Error.constant_int; + + try llvm.global_variable_map.putNoClobber(context.allocator, global_variable_index, global_variable); + + return global_variable; + } +} + + fn getDeclarationAlloca(llvm: *LLVM, unit: *Compilation.Unit, declaration_index: Compilation.Declaration.Index) !*LLVM.Value { + _ = llvm; // autofix + _ = unit; // autofix + _ = declaration_index; // autofix + unreachable; + } + + fn getScope(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, sema_scope: *Compilation.Scope) !*LLVM.DebugInfo.Scope { + _ = unit; // autofix + _ = context; // autofix + switch (sema_scope.kind) { + .function => { + if (llvm.scope.toSubprogram()) |_| { + return llvm.scope; + } else { + unreachable; + } + }, + .block => { + const scope = llvm.scope_map.get(sema_scope).?; + return scope; + }, + .file => { + unreachable; + }, + .file_container => { + if (llvm.scope_map.get(sema_scope)) |scope| { + return scope; + } else { + // unit.struct_type_map.get(); + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + } + // const lexical_block = llvm.debug_info_builder.createLexicalBlock(previous_scope, llvm.file, block.line + 1, block.column + 1) orelse unreachable; + unreachable; + } +}; + +var tag_count: c_uint = 0; + +const Error = error{ + context, + module, + builder, + function, + basic_block, + debug_info_builder, +}; + +const address_space = 0; + +pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !void { + const llvm_context = LLVM.Context.create() orelse return Error.context; + const module = LLVM.Module.create(@ptrCast(unit.descriptor.name.ptr), unit.descriptor.name.len, llvm_context) orelse return Error.module; + // TODO: + const builder = LLVM.Builder.create(llvm_context) orelse return Error.builder; + const generate_debug_info = false; + + var llvm = LLVM{ + .context = llvm_context, + .module = module, + .builder = builder, + .debug_info_builder = if (generate_debug_info) module.createDebugInfoBuilder() orelse return Error.debug_info_builder else null, + }; + + if (llvm.debug_info_builder) |debug_info_builder| { + const filename = "main"; + const directory = "."; + const debug_info_file = debug_info_builder.createFile(filename, filename.len, directory, directory.len) orelse unreachable; + const producer = "nativity"; + const is_optimized = false; + const flags = ""; + const runtime_version = 0; + const splitname = ""; + const DWOId = 0; + const debug_info_kind = LLVM.DebugInfo.CompileUnit.EmissionKind.full_debug; + const split_debug_inlining = true; + const debug_info_for_profiling = false; + const name_table_kind = LLVM.DebugInfo.CompileUnit.NameTableKind.default; + const ranges_base_address = false; + const sysroot = ""; + const sdk = ""; + const compile_unit = debug_info_builder.createCompileUnit(LLVM.DebugInfo.Language.c, debug_info_file, producer, producer.len, is_optimized, flags, flags.len, runtime_version, splitname, splitname.len, debug_info_kind, DWOId, split_debug_inlining, debug_info_for_profiling, name_table_kind, ranges_base_address, sysroot, sysroot.len, sdk, sdk.len) orelse unreachable; + llvm.scope = compile_unit.toScope(); + } + + for (unit.function_definitions.blocks.items, 0..) |function_definition_block, function_definition_block_index| { + for (function_definition_block.buffer[0..function_definition_block.len], 0..) |function_definition, block_function_definition_index| { + const function_definition_index = Compilation.Function.Definition.List.wrapSplit(function_definition_block_index, block_function_definition_index); + const llvm_prototype = (try llvm.getType(unit, context, function_definition.type)).toFunction() orelse unreachable; + const function_prototype = unit.function_prototypes.get(unit.types.get(function_definition.type).function); + + const mangle_name = !function_prototype.attributes.@"export"; + const declaration_index = unit.function_declaration_map.get(function_definition_index).?; + const name = try llvm.renderDeclarationName(unit, context, declaration_index, mangle_name); + if (llvm.module.getFunction(name.ptr, name.len)) |_| { + @panic("Function with same name"); + } + + const linkage = LLVM.Linkage.@"extern"; + const function = llvm.module.createFunction(llvm_prototype, linkage, address_space, name.ptr, name.len) orelse return Error.function; + switch (unit.types.get(function_prototype.return_type).*) { + .noreturn => { + function.addAttributeKey(.NoReturn); + }, + else => {}, + } + + // TODO: fix this + // switch (function_prototype.attributes.calling_convention) { + // .auto => { + // function.setCallingConvention(.Fast); + // }, + // .system_v => { + // function.setCallingConvention(.X86_64_SysV); + // }, + // .c => { + // function.setCallingConvention(.C); + // }, + // .naked => { + // function.addAttributeKey(.Naked); + // }, + // } + + try llvm.function_definition_map.putNoClobber(context.allocator, function_definition_index, function); + } + } + + for (unit.function_definitions.blocks.items, 0..) |function_definition_block, function_definition_block_index| { + for (function_definition_block.buffer[0..function_definition_block.len], 0..) |function_definition, block_function_definition_index| { + const function_definition_index = Compilation.Function.Definition.List.wrapSplit(function_definition_block_index, block_function_definition_index); + llvm.function = llvm.function_definition_map.get(function_definition_index).?; + llvm.sema_function = function_definition_index; + llvm.inside_branch = false; + const declaration_index = unit.function_declaration_map.get(function_definition_index).?; + const sema_declaration = unit.declarations.get(declaration_index); + const function_prototype = unit.function_prototypes.get(unit.types.get(function_definition.type).function); + const mangle_name = !function_prototype.attributes.@"export"; + const name = try llvm.renderDeclarationName(unit, context, declaration_index, mangle_name); + + if (llvm.debug_info_builder) |di_builder| { + const debug_file = try llvm.getDebugInfoFile(unit, context, sema_declaration.scope.file); + var parameter_types = try ArrayList(*LLVM.DebugInfo.Type).initCapacity(context.allocator, function_prototype.argument_types.len); + for (function_prototype.argument_types) |argument_type_index| { + const argument_type = try llvm.getDebugType(unit, context, argument_type_index); + parameter_types.appendAssumeCapacity(argument_type); + } + const subroutine_type_flags = LLVM.DebugInfo.Node.Flags{ + .visibility = .none, + .forward_declaration = false, + .apple_block = false, + .block_by_ref_struct = false, + .virtual = false, + .artificial = false, + .explicit = false, + .prototyped = false, + .objective_c_class_complete = false, + .object_pointer = false, + .vector = false, + .static_member = false, + .lvalue_reference = false, + .rvalue_reference = false, + .reserved = false, + .inheritance = .none, + .introduced_virtual = false, + .bit_field = false, + .no_return = false, + .type_pass_by_value = false, + .type_pass_by_reference = false, + .enum_class = false, + .thunk = false, + .non_trivial = false, + .big_endian = false, + .little_endian = false, + .all_calls_described = false, + }; + const subroutine_type_calling_convention = LLVM.DebugInfo.CallingConvention.none; + const subroutine_type = di_builder.createSubroutineType(parameter_types.items.ptr, parameter_types.items.len, subroutine_type_flags, subroutine_type_calling_convention) orelse unreachable; + const scope_line = 0; + const subprogram_flags = LLVM.DebugInfo.Subprogram.Flags{ + .virtuality = .none, + .local_to_unit = true, + .definition = true, + .optimized = false, + .pure = false, + .elemental = false, + .recursive = false, + .main_subprogram = false, + .deleted = false, + .object_c_direct = false, + }; + const subprogram_declaration = null; + const subprogram = di_builder.createFunction(debug_file.toScope(), name.ptr, name.len, name.ptr, name.len, debug_file, sema_declaration.line + 1, subroutine_type, scope_line, subroutine_type_flags, subprogram_flags, subprogram_declaration) orelse unreachable; + llvm.function.setSubprogram(subprogram); + llvm.file = subprogram.getFile() orelse unreachable; + llvm.scope = subprogram.toLocalScope().toScope(); + } + + const print_function_name = true; + if (print_function_name) { + std.debug.print("[LLVM] Compiling {s}...\n", .{name}); + } + + llvm.arg_index = 0; + llvm.alloca_map.clearRetainingCapacity(); + + try llvm.emitBasicBlock(unit, context, function_definition.basic_blocks.items[0]); + + if (!builder.isCurrentBlockTerminated()) { + const return_type = unit.unwrapTypeCopy(function_prototype.return_type); + if (return_type == Compilation.Type.Index.noreturn) { + _ = builder.createUnreachable() orelse return LLVM.Value.Instruction.Error.@"unreachable"; + } else if (return_type == Compilation.Type.Index.void) { + _ = builder.createRet(null) orelse unreachable; + } else { + unreachable; + } + } + + if (llvm.debug_info_builder) |di_builder| { + di_builder.finalizeSubprogram(llvm.function.getSubprogram() orelse unreachable, llvm.function); + } + + const verify_function = true; + if (verify_function) { + var message_ptr: [*]const u8 = undefined; + var message_len: usize = 0; + const result = llvm.function.verify(&message_ptr, &message_len); + + if (!result) { + var function_len: usize = 0; + const function_ptr = llvm.function.toString(&function_len); + const function_ir = function_ptr[0..function_len]; + const error_message = message_ptr[0..message_len]; + std.debug.panic("\n{s}. LLVM verification for the function above failed:\n{s}\n", .{ function_ir, error_message }); + } + } + } + } + + if (llvm.debug_info_builder) |di_builder| { + di_builder.finalize(); + } + + const print_module = true; + if (print_module) { + var module_len: usize = 0; + const module_ptr = llvm.module.toString(&module_len); + const module_string = module_ptr[0..module_len]; + std.debug.print("{s}\n", .{module_string}); + } + + const verify_module = true; + if (verify_module) { + var message_ptr: [*]const u8 = undefined; + var message_len: usize = 0; + const result = llvm.module.verify(&message_ptr, &message_len); + if (!result) { + std.debug.panic("LLVM module verification failed:\n{s}\n", .{message_ptr[0..message_len]}); + } + } + + const file_path = unit.descriptor.executable_path; + const object_file_path = try std.mem.joinZ(context.allocator, "", &.{ file_path, ".o" }); + const destination_file_path = try std.mem.joinZ(context.allocator, "", &.{file_path}); + const r = bindings.NativityLLVMGenerateMachineCode(llvm.module, object_file_path.ptr, object_file_path.len, destination_file_path.ptr, destination_file_path.len); + if (!r) { + @panic("Compilation failed!"); + } +} diff --git a/bootstrap/backend/llvm_bindings.zig b/bootstrap/backend/llvm_bindings.zig new file mode 100644 index 0000000..b2641a5 --- /dev/null +++ b/bootstrap/backend/llvm_bindings.zig @@ -0,0 +1,118 @@ +const llvm = @import("llvm.zig"); +const LLVM = llvm.LLVM; +pub extern fn NativityLLVMCreateContext() ?*LLVM.Context; +pub extern fn NativityLLVMCreateModule(module_name_ptr: [*:0]const u8, module_name_len: usize, context: *LLVM.Context) ?*LLVM.Module; +pub extern fn NativityLLVMCreateBuilder(context: *LLVM.Context) ?*LLVM.Builder; +pub extern fn NativityLLVMGetFunctionType(return_type: *LLVM.Type, argument_type_ptr: [*]const *LLVM.Type, argument_type_len: usize, is_var_args: bool) ?*LLVM.Type.Function; +pub extern fn NativityLLVMGetIntegerType(context: *LLVM.Context, bit_count: u32) ?*LLVM.Type.Integer; +pub extern fn NativityLLVMGetPointerType(context: *LLVM.Context, address_space: u32) ?*LLVM.Type.Pointer; +pub extern fn NativityLLVMGetArrayType(element_type: *LLVM.Type, element_count: u64) ?*LLVM.Type.Array; +pub extern fn NativityLLVMCreateStructType(context: *LLVM.Context, type_ptr: [*]const *LLVM.Type, type_count: usize, name_ptr: [*]const u8, name_len: usize, is_packed: bool) ?*LLVM.Type.Struct; +pub extern fn NativityLLVMConstantStruct(struct_type: *LLVM.Type.Struct, constant_ptr: [*]const *LLVM.Value.Constant, constant_count: usize) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMModuleGetFunction(module: *LLVM.Module, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value.Function; +pub extern fn NativityLLVModuleCreateFunction(module: *LLVM.Module, function_type: *LLVM.Type.Function, linkage: LLVM.Linkage, address_space: c_uint, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value.Function; +pub extern fn NativityLLVMModuleCreateDebugInfoBuilder(module: *LLVM.Module) ?*LLVM.DebugInfo.Builder; +pub extern fn NativityLLVMDebugInfoBuilderCreateFile(builder: *LLVM.DebugInfo.Builder, filename_ptr: [*]const u8, filename_len: usize, directory_ptr: [*]const u8, directory_len: usize) ?*LLVM.DebugInfo.File; +pub extern fn NativityLLVMDebugInfoBuilderCreateCompileUnit(builder: *LLVM.DebugInfo.Builder, language: LLVM.DebugInfo.Language, file: *LLVM.DebugInfo.File, producer_ptr: [*]const u8, producer_len: usize, is_optimized: bool, flags_ptr: [*]const u8, flags_len: usize, runtime_version: c_uint, split_name_ptr: [*]const u8, split_name_len: usize, debug_info_emission_kind: LLVM.DebugInfo.CompileUnit.EmissionKind, DWOId: u64, split_debug_inlining: bool, debug_info_for_profiling: bool, debug_info_name_table_kind: LLVM.DebugInfo.CompileUnit.NameTableKind, ranges_base_address: bool, sysroot_ptr: [*]const u8, sysroot_len: usize, sdk_ptr: [*]const u8, sdk_len: usize) ?*LLVM.DebugInfo.CompileUnit; +pub extern fn NativityLLVMDebugInfoBuilderCreateFunction(builder: *LLVM.DebugInfo.Builder, scope: *LLVM.DebugInfo.Scope, name_ptr: [*]const u8, name_len: usize, linkage_name_ptr: [*]const u8, linkage_name_len: usize, file: *LLVM.DebugInfo.File, line_number: c_uint, type: *LLVM.DebugInfo.SubroutineType, scope_line: c_uint, flags: LLVM.DebugInfo.Node.Flags, subprogram_flags: LLVM.DebugInfo.Subprogram.Flags, declaration: ?*LLVM.DebugInfo.Subprogram) ?*LLVM.DebugInfo.Subprogram; +pub extern fn NativityLLVMDebugInfoBuilderCreateSubroutineType(builder: *LLVM.DebugInfo.Builder, parameter_types_ptr: [*]const *LLVM.DebugInfo.Type, parameter_type_count: usize, flags: LLVM.DebugInfo.Node.Flags, calling_convention: LLVM.DebugInfo.CallingConvention) ?*LLVM.DebugInfo.SubroutineType; +pub extern fn NativityLLVMDebugInfoBuilderCreateLexicalBlock(builder: *LLVM.DebugInfo.Builder, parent_scope: *LLVM.DebugInfo.Scope, parent_file: *LLVM.DebugInfo.File, line: c_uint, column: c_uint) ?*LLVM.DebugInfo.LexicalBlock; +pub extern fn NativityLLVMDebugInfoBuilderCreateParameterVariable(builder: *LLVM.DebugInfo.Builder, scope: *LLVM.DebugInfo.Scope, name_ptr: [*]const u8, name_len: usize, argument_index: c_uint, file: *LLVM.DebugInfo.File, line_number: c_uint, type: *LLVM.DebugInfo.Type, always_preserve: bool, flags: LLVM.DebugInfo.Node.Flags) ?*LLVM.DebugInfo.LocalVariable; +pub extern fn NativityLLVMDebugInfoBuilderCreateAutoVariable(builder: *LLVM.DebugInfo.Builder, scope: *LLVM.DebugInfo.Scope, name_ptr: [*]const u8, name_len: usize, file: *LLVM.DebugInfo.File, line_number: c_uint, type: *LLVM.DebugInfo.Type, always_preserve: bool, flags: LLVM.DebugInfo.Node.Flags, alignment: u32) ?*LLVM.DebugInfo.LocalVariable; // 0 means 1 << 0 (alignment of 1) +pub extern fn NativityLLVMDebugInfoBuilderInsertDeclare(builder: *LLVM.DebugInfo.Builder, pointer: *LLVM.Value, local_variable: *LLVM.DebugInfo.LocalVariable, context: *LLVM.Context, line: c_uint, column: c_uint, scope: *LLVM.DebugInfo.Scope, basic_block: *LLVM.Value.BasicBlock) ?*LLVM.Value.Instruction; +pub extern fn NativityLLVMDebugInfoBuilderCreateBasicType(builder: *LLVM.DebugInfo.Builder, name_ptr: [*]const u8, name_len: usize, bit_count: u64, dwarf_encoding: LLVM.DebugInfo.AttributeType, flags: LLVM.DebugInfo.Node.Flags) ?*LLVM.DebugInfo.Type; +pub extern fn NativityLLVMDebugInfoBuilderCreatePointerType(builder: *LLVM.DebugInfo.Builder, element_type: *LLVM.DebugInfo.Type, pointer_bit_count: u64, alignment: u32, name_ptr: [*]const u8, name_len: usize) ?*LLVM.DebugInfo.Type.Derived; +pub extern fn NativityLLVMDebugInfoBuilderCreateStructType(builder: *LLVM.DebugInfo.Builder, scope: ?*LLVM.DebugInfo.Scope, name_ptr: [*]const u8, name_len: usize, file: ?*LLVM.DebugInfo.File, line_number: c_uint, bit_count: u64, alignment: u32, flags: LLVM.DebugInfo.Node.Flags, derived_from: ?*LLVM.DebugInfo.Type, element_type_ptr: [*]const *LLVM.DebugInfo.Type, element_type_count: usize) ?*LLVM.DebugInfo.Type.Composite; +pub extern fn NativityLLVMDebugInfoBuilderCreateArrayType(builder: *LLVM.DebugInfo.Builder, bit_size: u64, alignment: u32, type: *LLVM.DebugInfo.Type, element_count: usize) ?*LLVM.DebugInfo.Type.Composite; +pub extern fn NativityLLVMDebugInfoBuilderCreateEnumerationType(builder: *LLVM.DebugInfo.Builder, scope: ?*LLVM.DebugInfo.Scope, name_ptr: [*]const u8, name_len: usize, file: *LLVM.DebugInfo.File, line: c_uint, bit_size: u64, alignment: u32, enumerator_ptr: [*]const *LLVM.DebugInfo.Type.Enumerator, enumerator_count: usize, underlying_type: *LLVM.DebugInfo.Type) ?*LLVM.DebugInfo.Type.Composite; +pub extern fn NativityLLVMDebugInfoBuilderCreateEnumerator(builder: *LLVM.DebugInfo.Builder, name_ptr: [*]const u8, name_len: usize, value: u64, is_unsigned: bool) ?*LLVM.DebugInfo.Type.Enumerator; +pub extern fn NativityLLVMDebugInfoBuilderCreateReplaceableCompositeType(builder: *LLVM.DebugInfo.Builder, tag: c_uint, name_ptr: [*]const u8, name_len: usize, scope: ?*LLVM.DebugInfo.Scope, file: ?*LLVM.DebugInfo.File, line: c_uint) ?*LLVM.DebugInfo.Type.Composite; +pub extern fn NativityLLVMDebugInfoBuilderFinalizeSubprogram(builder: *LLVM.DebugInfo.Builder, subprogram: *LLVM.DebugInfo.Subprogram, function: *LLVM.Value.Function) void; +pub extern fn NativityLLVMDebugInfoBuilderFinalize(builder: *LLVM.DebugInfo.Builder) void; +pub extern fn NativityLLVMDebugInfoSubprogramGetFile(subprogram: *LLVM.DebugInfo.Subprogram) ?*LLVM.DebugInfo.File; +pub extern fn NativityLLVMDebugInfoSubprogramGetArgumentType(subprogram: *LLVM.DebugInfo.Subprogram, argument_index: usize) ?*LLVM.DebugInfo.Type; +pub extern fn NativityLLVMDebugInfoScopeToSubprogram(scope: *LLVM.DebugInfo.Scope) ?*LLVM.DebugInfo.Subprogram; +pub extern fn NativityLLVMCreateBasicBlock(context: *LLVM.Context, name_ptr: [*]const u8, name_len: usize, parent_function: ?*LLVM.Value.Function, insert_before: ?*LLVM.Value.BasicBlock) ?*LLVM.Value.BasicBlock; +pub extern fn NativityLLVMBasicBlockRemoveFromParent(basic_block: *LLVM.Value.BasicBlock) void; +pub extern fn NativityLLVMBuilderSetInsertPoint(builder: *LLVM.Builder, basic_block: *LLVM.Value.BasicBlock) void; +pub extern fn NativityLLVMBuilderGetInsertBlock(builder: *LLVM.Builder) ?*LLVM.Value.BasicBlock; +pub extern fn NativityLLVMBuilderSetCurrentDebugLocation(builder: *LLVM.Builder, context: *LLVM.Context, line: c_uint, column: c_uint, scope: *LLVM.DebugInfo.Scope, function: *LLVM.Value.Function) void; +pub extern fn NativityLLVMValueSetName(value: *LLVM.Value, name_ptr: [*]const u8, name_len: usize) void; +pub extern fn NativityLLVMValueGetType(value: *LLVM.Value) *LLVM.Type; +pub extern fn NativityLLVMArgumentGetIndex(argument: *LLVM.Value.Argument) c_uint; +pub extern fn NativityLLVMFunctionGetArguments(function: *LLVM.Value.Function, argument_ptr: [*]*LLVM.Value.Argument, argument_len: *usize) void; +pub extern fn NativityLLVMFunctionGetReturnType(function: *LLVM.Value.Function) ?*LLVM.Type; +pub extern fn NativityLLVMBuilderCreateAlloca(builder: *LLVM.Builder, type: *LLVM.Type, address_space: c_uint, array_size: ?*LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value.Instruction.Alloca; +pub extern fn NativityLLVMBuilderCreateStore(builder: *LLVM.Builder, value: *LLVM.Value, pointer: *LLVM.Value, is_volatile: bool) ?*LLVM.Value.Instruction.Store; +pub extern fn NativityLLVMContextGetConstantInt(context: *LLVM.Context, bit_count: c_uint, value: u64, is_signed: bool) ?*LLVM.Value.Constant.Int; +pub extern fn NativityLLVMContextGetConstString(context: *LLVM.Context, name_ptr: [*]const u8, name_len: usize, null_terminate: bool) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMContextGetConstArray(array_type: *LLVM.Type.Array, value_ptr: [*]const *LLVM.Value.Constant, value_count: usize) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMBuilderCreateICmp(builder: *LLVM.Builder, integer_comparison: LLVM.Value.Instruction.ICmp.Kind, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateLoad(builder: *LLVM.Builder, type: *LLVM.Type, value: *LLVM.Value, is_volatile: bool, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value.Instruction.Load; +pub extern fn NativityLLVMBuilderCreateRet(builder: *LLVM.Builder, value: ?*LLVM.Value) ?*LLVM.Value.Instruction.Ret; +pub extern fn NativityLLVMBuilderCreateCast(builder: *LLVM.Builder, cast_type: LLVM.Value.Instruction.Cast.Type, value: *LLVM.Value, type: *LLVM.Type, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMFunctionAddAttributeKey(builder: *LLVM.Value.Function, attribute_key: LLVM.Attribute) void; +pub extern fn NativityLLVMGetVoidType(context: *LLVM.Context) ?*LLVM.Type; +pub extern fn NativityLLVMGetInlineAssembly(function_type: *LLVM.Type.Function, assembly_ptr: [*]const u8, assembly_len: usize, constraints_ptr: [*]const u8, constrains_len: usize, has_side_effects: bool, is_align_stack: bool, dialect: LLVM.Value.InlineAssembly.Dialect, can_throw: bool) ?*LLVM.Value.InlineAssembly; +pub extern fn NativityLLVMBuilderCreateCall(builder: *LLVM.Builder, function_type: *LLVM.Type.Function, callee: *LLVM.Value, argument_ptr: [*]const *LLVM.Value, argument_count: usize, name_ptr: [*]const u8, name_len: usize, fp_math_tag: ?*LLVM.Metadata.Node) ?*LLVM.Value.Instruction.Call; +pub extern fn NativityLLVMBuilderCreateUnreachable(builder: *LLVM.Builder) ?*LLVM.Value.Instruction.Unreachable; +pub extern fn NativityLLVMModuleAddGlobalVariable(module: *LLVM.Module, type: *LLVM.Type, is_constant: bool, linkage: LLVM.Linkage, initializer: ?*LLVM.Value.Constant, name_ptr: [*]const u8, name_len: usize, insert_before: ?*LLVM.Value.Constant.GlobalVariable, thread_local_mode: LLVM.ThreadLocalMode, address_space: c_uint, externally_initialized: bool) ?*LLVM.Value.Constant.GlobalVariable; + +pub extern fn NativityLLVMBuilderCreateAdd(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize, no_unsigned_wrapping: bool, no_signed_wrapping: bool) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateSub(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize, no_unsigned_wrapping: bool, no_signed_wrapping: bool) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateMultiply(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize, no_unsigned_wrapping: bool, no_signed_wrapping: bool) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateShiftLeft(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize, no_unsigned_wrapping: bool, no_signed_wrapping: bool) ?*LLVM.Value; + +pub extern fn NativityLLVMBuilderCreateUDiv(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize, is_exact: bool) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateSDiv(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize, is_exact: bool) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateURem(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateSRem(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateLogicalShiftRight(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize, is_exact: bool) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateArithmeticShiftRight(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize, is_exact: bool) ?*LLVM.Value; + +pub extern fn NativityLLVMBuilderCreateXor(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateAnd(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateOr(builder: *LLVM.Builder, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateGEP(builder: *LLVM.Builder, type: *LLVM.Type, pointer: *LLVM.Value, index_ptr: [*]const *LLVM.Value, index_count: usize, name_ptr: [*]const u8, name_len: usize, in_bounds: bool) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateBranch(builder: *LLVM.Builder, basic_block: *LLVM.Value.BasicBlock) ?*LLVM.Value.Instruction.Branch; +pub extern fn NativityLLVMBuilderCreateConditionalBranch(builder: *LLVM.Builder, condition: *LLVM.Value, true_block: *LLVM.Value.BasicBlock, false_block: *LLVM.Value.BasicBlock, branch_weights: ?*LLVM.Metadata.Node, unpredictable: ?*LLVM.Metadata.Node) ?*LLVM.Value.Instruction.Branch; + +pub extern fn NativityLLVMVerifyFunction(function: *LLVM.Value.Function, message_ptr: *[*]const u8, message_len: *usize) bool; +pub extern fn NativityLLVMVerifyModule(module: *LLVM.Module, message_ptr: *[*]const u8, message_len: *usize) bool; + +pub extern fn NativityLLVMModuleToString(module: *LLVM.Module, message_len: *usize) [*]const u8; +pub extern fn NativityLLVMFunctionToString(function: *LLVM.Value.Function, message_len: *usize) [*]const u8; + +pub extern fn NativityLLVMGenerateMachineCode(module: *LLVM.Module, object_file_path_ptr: [*]const u8, object_file_path_len: usize, file_path_ptr: [*]const u8, file_path_len: usize) bool; +pub extern fn NativityLLVMBuilderIsCurrentBlockTerminated(builder: *LLVM.Builder) bool; +pub extern fn NativityLLVMGetUndefined(type: *LLVM.Type) ?*LLVM.Value.Constant.Undefined; +pub extern fn NativityLLVMFunctionSetCallingConvention(function: *LLVM.Value.Function, calling_convention: LLVM.Value.Function.CallingConvention) void; +pub extern fn NativityLLVMFunctionGetCallingConvention(function: *LLVM.Value.Function) LLVM.Value.Function.CallingConvention; +pub extern fn NativityLLVMFunctionSetSubprogram(function: *LLVM.Value.Function, subprogram: *LLVM.DebugInfo.Subprogram) void; +pub extern fn NativityLLVMFunctionGetSubprogram(function: *LLVM.Value.Function) ?*LLVM.DebugInfo.Subprogram; + +pub extern fn NativityLLVMCallSetCallingConvention(instruction: *LLVM.Value.Instruction.Call, calling_convention: LLVM.Value.Function.CallingConvention) void; +pub extern fn NativityLLVMGetStruct(struct_type: *LLVM.Type.Struct, constant_ptr: [*]const *LLVM.Value.Constant, constant_len: usize) ?*LLVM.Value.Constant; + +pub extern fn NativityLLVMValueToConstant(value: *LLVM.Value) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMValueToFunction(value: *LLVM.Value) ?*LLVM.Value.Function; + +pub extern fn NativityLLVMTypeIsPointer(type: *LLVM.Type) bool; +pub extern fn NativityLLVMTypeIsInteger(type: *LLVM.Type) bool; + +pub extern fn NativityLLVMTypeToStruct(type: *LLVM.Type) ?*LLVM.Type.Struct; +pub extern fn NativityLLVMTypeToFunction(type: *LLVM.Type) ?*LLVM.Type.Function; +pub extern fn NativityLLVMTypeToArray(type: *LLVM.Type) ?*LLVM.Type.Array; + +pub extern fn NativityLLVMArrayTypeGetElementType(array_type: *LLVM.Type.Array) ?*LLVM.Type; +pub extern fn NativityLLVMLookupIntrinsic(name_ptr: [*]const u8, name_len: usize) LLVM.Value.IntrinsicID; +pub extern fn NativityLLVMModuleGetIntrinsicDeclaration(module: *LLVM.Module, intrinsic_id: LLVM.Value.IntrinsicID, parameter_types_ptr: [*]const *LLVM.Type, parameter_type_count: usize) ?*LLVM.Value.Function; +pub extern fn NativityLLVMContextGetIntrinsicType(context: *LLVM.Context, intrinsic_id: LLVM.Value.IntrinsicID, parameter_type_ptr: [*]const *LLVM.Type, parameter_type_count: usize) ?*LLVM.Type.Function; +pub extern fn NativityLLVMBuilderCreateExtractValue(builder: *LLVM.Builder, aggregate: *LLVM.Value, indices_ptr: [*]const c_uint, indices_len: usize, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMBuilderCreateInsertValue(builder: *LLVM.Builder, aggregate: *LLVM.Value, value: *LLVM.Value, indices_ptr: [*]const c_uint, indices_len: usize, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; +pub extern fn NativityLLVMContextCreateGlobalStringPointer(builder: *LLVM.Builder, string_ptr: [*]const u8, string_len: usize, name_ptr: [*]const u8, name_len: usize, address_space: c_uint, module: *LLVM.Module) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMCompareTypes(a: *LLVM.Type, b: *LLVM.Type) bool; +pub extern fn NativityLLVMCreatePhiNode(type: *LLVM.Type, reserved_value_count: c_uint, name_ptr: [*]const u8, name_len: usize, basic_block: ?*LLVM.Value.BasicBlock) ?*LLVM.Value.Instruction.PhiNode; + +pub extern fn NativityLLVMAllocatGetAllocatedType(alloca: *LLVM.Value.Instruction.Alloca) *LLVM.Type; +pub extern fn NativityLLVMValueToAlloca(value: *LLVM.Value) ?*LLVM.Value.Instruction.Alloca; diff --git a/bootstrap/data_structures.zig b/bootstrap/data_structures.zig index 048f1c6..b2f6e1e 100644 --- a/bootstrap/data_structures.zig +++ b/bootstrap/data_structures.zig @@ -6,204 +6,163 @@ pub const AutoArrayHashMap = std.AutoArrayHashMapUnmanaged; pub const ArrayList = std.ArrayListUnmanaged; pub const ArrayListAligned = std.ArrayListAlignedUnmanaged; pub const AutoHashMap = std.AutoHashMapUnmanaged; +pub const BoundedArray = std.BoundedArray; pub const HashMap = std.HashMapUnmanaged; -pub const SegmentedList = std.SegmentedList; pub const StringHashMap = std.StringHashMapUnmanaged; pub const StringArrayHashMap = std.StringArrayHashMapUnmanaged; -pub fn BlockList(comptime T: type) type { +pub fn BlockList(comptime T: type, comptime E: type) type { const item_count = 64; - const Block = struct { - items: [item_count]T = undefined, - bitset: Bitset = Bitset.initEmpty(), - - const Bitset = std.StaticBitSet(item_count); - - fn allocateIndex(block: *@This()) !u6 { - if (block.bitset.mask != std.math.maxInt(@TypeOf(block.bitset.mask))) { - const index = @ctz(~block.bitset.mask); - block.bitset.set(index); - return @intCast(index); - } else { - return error.OutOfMemory; - } - } - }; return struct { - // TODO: make this not reallocate the whole block. Instead, use a pointer to the block as the ArrayList item blocks: ArrayList(*Block) = .{}, len: usize = 0, - first_block: u32 = 0, + const Block = BoundedArray(T, item_count); const List = @This(); - pub const Index = packed struct(u32) { - element: u6, - block: u24, - _reserved: bool = false, - invalid: bool = false, + pub const Index = getIndexForType(T, E); + pub const ElementIndex = Index.Index; - pub const invalid = Index{ - .invalid = true, - .element = 0, - .block = 0, - }; + // pub const append = switch (list_type) { + // .index => appendIndexed, + // .pointer => appendPointer, + // }; + // pub const addOne = switch (list_type) { + // .index => addOneIndexed, + // .pointer => addOnePointer, + // }; - pub fn eq(index: Index, other: Index) bool { - return @as(u32, @bitCast(index)) == @as(u32, @bitCast(other)); - } - - pub fn uniqueInteger(index: Index) u32 { - assert(!index.invalid); - return @as(u30, @truncate(@as(u32, @bitCast(index)))); - } - - pub fn fromInteger(usize_index: usize) Index { - const index: u32 = @intCast(usize_index); - const block: u24 = @intCast(index / item_count); - const i: u6 = @intCast(index % item_count); - return .{ - .element = i, - .block = block, - }; - } - }; - - pub const Iterator = struct { - index: Index, - list: *List, - - pub const Pair = struct { - index: Index, - }; - - pub fn nextIndex(i: *Iterator) ?Index { - // TODO: optimize with ctz and masking out already iterated indices in the bitmask - for (i.index.block..i.list.blocks.items.len) |block_index| { - for (@as(u8, i.index.element)..item_count) |element_index| { - if (i.list.blocks.items[block_index].bitset.isSet(element_index)) { - const index = Index{ - .element = @intCast(element_index), - .block = @intCast(block_index), - }; - - i.index = index; - i.index.element +%= 1; - i.index.block = @as(u24, @intCast(block_index)) + @intFromBool(i.index.element < element_index); - - return index; - } - } - } - - return null; - } - - pub fn nextPointer(i: *Iterator) ?*T { - if (i.nextIndex()) |index| { - const result = i.list.get(index); - return result; - } else { - return null; - } - } - }; - - pub fn iterator(list: *List) Iterator { - return .{ - .index = Index{ - .element = 0, - .block = 0, - }, - .list = list, - }; + pub fn wrapSplit(block: usize, element: usize) ElementIndex { + return @enumFromInt(block * item_count + element); } - pub fn get(list: *List, index: Index) *T { - assert(!index.invalid); - return &list.blocks.items[index.block].items[index.element]; + pub fn get(list: *List, index: ElementIndex) *T { + assert(index != .null); + const i: u32 = @intFromEnum(index); + const block_index = i / item_count; + const element_index = i % item_count; + const block = list.blocks.items[block_index]; + const block_slice = block.buffer[0..block.len]; + const element = &block_slice[element_index]; + return element; } - pub fn append(list: *List, allocator: Allocator, element: T) !Index { + pub fn append(list: *List, allocator: Allocator, element: T) !ElementIndex { const result = try list.addOne(allocator); list.get(result).* = element; return result; } - pub fn addOne(list: *List, allocator: Allocator) !Index { - try list.ensureCapacity(allocator, list.len + 1); - const max_allocation = list.blocks.items.len * item_count; - const result = switch (list.len < max_allocation) { - true => blk: { - const block = list.blocks.items[list.first_block]; - if (block.allocateIndex()) |element_index| { - break :blk Index{ - .element = element_index, - .block = @intCast(list.first_block), - }; - } else |_| { - @panic("TODO"); - } - }, - false => blk: { - const block_index = list.blocks.items.len; - const new_block = try allocator.create(Block); - new_block.* = .{}; - list.blocks.appendAssumeCapacity(new_block); - const element_index = new_block.allocateIndex() catch unreachable; - list.first_block += @intFromBool(block_index != 0); - break :blk Index{ - .element = element_index, - .block = @intCast(block_index), - }; - }, - }; - - list.len += 1; - - return result; + pub fn addOne(list: *List, allocator: Allocator) !ElementIndex { + const block_index = try list.getFreeBlock(allocator); + const block = list.blocks.items[block_index]; + const index = block.len; + _ = try block.addOne(); + return @enumFromInt(block_index * item_count + index); } - pub fn ensureCapacity(list: *List, allocator: Allocator, new_capacity: usize) !void { - const max_allocation = list.blocks.items.len * item_count; - if (max_allocation < new_capacity) { - const block_count = new_capacity / item_count + @intFromBool(new_capacity % item_count != 0); - try list.blocks.ensureTotalCapacity(allocator, block_count); + fn getFreeBlock(list: *List, allocator: Allocator) !usize { + for (list.blocks.items, 0..) |block, i| { + block.ensureUnusedCapacity(1) catch continue; + return i; + } else { + const new_block = try allocator.create(Block); + new_block.* = .{}; + const block_index = list.blocks.items.len; + try list.blocks.append(allocator, new_block); + return block_index; } } - pub fn indexOf(list: *List, elem: *const T) Index { + pub fn indexOf(list: *List, elem: *const T) ElementIndex { const address = @intFromPtr(elem); - for (list.blocks.items, 0..) |*block, block_index| { - const base = @intFromPtr(&block.items[0]); + for (list.blocks.items, 0..) |block, block_index| { + const base = @intFromPtr(&block.buffer[0]); const top = base + @sizeOf(T) * item_count; if (address >= base and address < top) { - return .{ - .block = @intCast(block_index), - .element = @intCast(@divExact(address - base, @sizeOf(T))), - }; + const result: u32 = @intCast(block_index * item_count + @divExact(address - base, @sizeOf(T))); + return Index.wrap(result); } } @panic("not found"); } + }; +} - test "Bitset index allocation" { - const expect = std.testing.expect; - var block = Block{}; - for (0..item_count) |expected_index| { - const new_index = try block.allocateIndex(); - try expect(new_index == expected_index); - } +pub fn getIndexForType(comptime T: type, comptime E: type) type { + assert(@typeInfo(E) == .Enum); + _ = T; + const IndexType = u32; + const MAX = std.math.maxInt(IndexType); - _ = block.allocateIndex() catch return; + const EnumField = std.builtin.Type.EnumField; + comptime var fields: []const EnumField = &.{}; + // comptime var enum_value: comptime_int = 0; + fields = fields ++ @typeInfo(E).Enum.fields; - return error.TestUnexpectedResult; + // for (names) |name| { + // fields = fields ++ [1]EnumField{.{ + // .name = name, + // .value = enum_value, + // }}; + // enum_value += 1; + // } + + fields = fields ++ [1]EnumField{.{ + .name = "null", + .value = MAX, + }}; + + const Result = @Type(.{ + .Enum = .{ + .tag_type = IndexType, + .fields = fields, + .decls = &.{}, + .is_exhaustive = false, + }, + }); + + return struct { + pub const Index = Result; + + pub fn unwrap(this: Index) IndexType { + assert(this != .null); + return @intFromEnum(this); + } + + pub fn wrap(value: IndexType) Index { + assert(value < MAX); + return @enumFromInt(value); + } + + pub fn addInt(this: Index, value: IndexType) Index{ + const this_int = @intFromEnum(this); + return @enumFromInt(this_int + value); + } + + pub fn subInt(this: Index, value: IndexType) IndexType{ + const this_int = @intFromEnum(this); + return this_int - value; + } + + pub fn add(a: Index, b: Index) Index{ + return @enumFromInt(@intFromEnum(a) + @intFromEnum(b)); + } + + pub fn sub(a: Index, b: Index) IndexType{ + return @intFromEnum(a) - @intFromEnum(b); } }; } +pub const ListType = enum{ + index, + pointer, +}; + + pub fn enumFromString(comptime E: type, string: []const u8) ?E { return inline for (@typeInfo(E).Enum.fields) |enum_field| { if (std.mem.eql(u8, string, enum_field.name)) { diff --git a/bootstrap/frontend/lexer.zig b/bootstrap/frontend/lexer.zig new file mode 100644 index 0000000..7cb0663 --- /dev/null +++ b/bootstrap/frontend/lexer.zig @@ -0,0 +1,435 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const log = std.log; + +const equal = std.mem.eql; + +const data_structures = @import("../data_structures.zig"); +const ArrayList = data_structures.ArrayList; +const enumFromString = data_structures.enumFromString; + +const Compilation = @import("../Compilation.zig"); +const File = Compilation.File; +const logln = Compilation.logln; +const Token = Compilation.Token; +const fs = @import("../fs.zig"); + + +// Needed information +// Token: u8 +// line: u32 +// column: u16 +// offset: u32 +// len: u24 + +pub const Result = struct { + offset: Token.Index, + count: u32, + line_offset: u32, + line_count: u32, + // ids: ArrayList(Token.Id) = .{}, + // token_lines: ArrayList(u32) = .{}, + // file_line_offsets: ArrayList(u32) = .{}, + // token_offsets: ArrayList(u32) = .{}, + // token_lengths: ArrayList(u32) = .{}, + time: u64 = 0, +}; + +pub const Logger = enum { + start, + end, + new_token, + number_literals, + + pub var bitset = std.EnumSet(Logger).initMany(&.{ + .new_token, + .start, + .end, + .number_literals, + }); +}; + +pub fn analyze(allocator: Allocator, text: []const u8, token_buffer: *Token.Buffer) !Result { + assert(text.len <= std.math.maxInt(u32)); + const len: u32 = @intCast(text.len); + + var lexer = Result{ + .offset = token_buffer.getOffset(), + .line_offset = token_buffer.getLineOffset(), + .count = 0, + .line_count = 0, + }; + + const time_start = std.time.Instant.now() catch unreachable; + + try token_buffer.line_offsets.append(allocator, 0); + + for (text, 0..) |byte, index| { + if (byte == '\n') { + try token_buffer.line_offsets.append(allocator, @intCast(index + 1)); + } + } + + var index: u32 = 0; + var line_index: u32 = lexer.line_offset; + + try token_buffer.tokens.ensureUnusedCapacity(allocator, text.len / 4); + + logln(.lexer, .end, "START LEXER - TOKEN OFFSET: {} - LINE OFFSET: {}", .{Token.unwrap(lexer.offset), lexer.line_offset}); + + while (index < len) { + const start_index = index; + const start_character = text[index]; + + const token_id: Token.Id = switch (start_character) { + 'a'...'z', 'A'...'Z', '_' => blk: { + while (true) { + const ch = text[index]; + if ((ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z') or ch == '_' or (ch >= '0' and ch <= '9')) { + index += 1; + continue; + } + + break; + } + + // const identifier = text[start_index..][0 .. index - start_index]; + // logln("Identifier: {s}", .{identifier}); + + if (start_character == 'u' or start_character == 's' and text[start_index + 1] >= '0' and text[start_index + 1] <= '9') { + var index_integer = start_index + 1; + while (text[index_integer] >= '0' and text[index_integer] <= '9') { + index_integer += 1; + } + + if (index_integer == index) { + const id: Token.Id = switch (start_character) { + 'u' => .keyword_unsigned_integer, + 's' => .keyword_signed_integer, + else => unreachable, + }; + + break :blk id; + } + } + + const string = text[start_index..][0 .. index - start_index]; + break :blk if (enumFromString(Compilation.FixedKeyword, string)) |fixed_keyword| switch (fixed_keyword) { + inline else => |comptime_fixed_keyword| @field(Token.Id, "fixed_keyword_" ++ @tagName(comptime_fixed_keyword)), + } else if (equal(u8, string, "_")) .discard else .identifier; + }, + '0'...'9' => blk: { + // Detect other non-decimal literals + if (text[index] == '0' and index + 1 < text.len) { + if (text[index + 1] == 'x') { + index += 2; + } else if (text[index + 1] == 'b') { + index += 2; + } else if (text[index + 1] == 'o') { + index += 2; + } + } + + while (text[index] >= '0' and text[index] <= '9' or text[index] >= 'a' and text[index] <= 'f' or text[index] >= 'A' and text[index] <= 'F') { + index += 1; + } + + break :blk .number_literal; + }, + '\'' => blk: { + index += 1; + index += @intFromBool(text[index] == '\''); + index += 1; + const is_end_char_literal = text[index] == '\''; + index += @intFromBool(is_end_char_literal); + if (!is_end_char_literal) unreachable; + + break :blk .character_literal; + }, + '"' => blk: { + index += 1; + + while (true) { + if (text[index] == '"' and text[index - 1] != '"') { + break; + } + + index += 1; + } + + index += 1; + + break :blk .string_literal; + }, + '#' => blk: { + index += 1; + // const start_intrinsic = index; + + while (true) { + const ch = text[index]; + if ((ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z') or ch == '_') { + index += 1; + } else break; + } + + // const end_intrinsic = index; + // const intrinsic_identifier = text[start_intrinsic..][0 .. end_intrinsic - start_intrinsic]; + // _ = intrinsic_identifier; + + break :blk .intrinsic; + }, + '\n' => { + index += 1; + line_index += 1; + continue; + }, + ' ', '\r', '\t' => { + index += 1; + continue; + }, + '(' => blk: { + index += 1; + break :blk .operator_left_parenthesis; + }, + ')' => blk: { + index += 1; + break :blk .operator_right_parenthesis; + }, + '{' => blk: { + index += 1; + break :blk .operator_left_brace; + }, + '}' => blk: { + index += 1; + break :blk .operator_right_brace; + }, + '[' => blk: { + index += 1; + break :blk .operator_left_bracket; + }, + ']' => blk: { + index += 1; + break :blk .operator_right_bracket; + }, + '<' => blk: { + index += 1; + switch (text[index]) { + '<' => { + index += 1; + break :blk switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_shift_left_assign; + }, + else => .operator_shift_left, + }; + }, + '=' => { + index += 1; + break :blk .operator_compare_less_equal; + }, + else =>break :blk .operator_compare_less, + } + }, + '>' => blk: { + index += 1; + switch (text[index]) { + '>' => { + index += 1; + break :blk switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_shift_right_assign; + }, + else => .operator_shift_right, + }; + }, + '=' => { + index += 1; + break :blk .operator_compare_greater_equal; + }, + else =>break :blk .operator_compare_greater, + } + }, + ';' => blk: { + index += 1; + break :blk .operator_semicolon; + }, + '@' => blk: { + index += 1; + break :blk .operator_at; + }, + ',' => blk: { + index += 1; + break :blk .operator_comma; + }, + '.' => blk: { + index += 1; + break :blk .operator_dot; + }, + ':' => blk: { + index += 1; + break :blk .operator_colon; + }, + '!' => blk: { + index += 1; + switch (text[index]) { + '=' => { + index += 1; + break :blk .operator_compare_not_equal; + }, + else => break :blk .operator_bang, + } + + }, + '=' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_compare_equal; + }, + '>' => b: { + index += 1; + break :b .operator_switch_case; + }, + else => .operator_assign, + }; + + break :blk token_id; + }, + '+' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_add_assign; + }, + else => .operator_add, + }; + + break :blk token_id; + }, + '-' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_sub_assign; + }, + else => .operator_minus, + }; + + break :blk token_id; + }, + '*' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_mul_assign; + }, + else => .operator_asterisk, + }; + + break :blk token_id; + }, + '/' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_div_assign; + }, + else => .operator_div, + }; + + break :blk token_id; + }, + '%' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_mod_assign; + }, + else => .operator_mod, + }; + + break :blk token_id; + }, + '|' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_or_assign; + }, + else => .operator_bar, + }; + + break :blk token_id; + }, + '&' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_and_assign; + }, + else => .operator_ampersand, + }; + + break :blk token_id; + }, + '^' => blk: { + index += 1; + const token_id: Token.Id = switch (text[index]) { + '=' => b: { + index += 1; + break :b .operator_xor_assign; + }, + else => .operator_xor, + }; + + break :blk token_id; + }, + '?' => blk: { + index += 1; + + break :blk .operator_optional; + }, + '$' => blk: { + index += 1; + + break :blk .operator_dollar; + }, + else => |ch| { + std.debug.panic("NI: '{c}'", .{ch}); + }, + }; + + const end_index = index; + const token_length = end_index - start_index; + + token_buffer.tokens.appendAssumeCapacity(.{ + .id = token_id, + .offset = start_index, + .length = token_length, + .line = line_index, + }); + const line_offset = token_buffer.line_offsets.items[line_index]; + const column = start_index - line_offset; + logln(.lexer, .new_token, "T at line {}, column {}, byte offset {}, with length {} -line offset: {}- ({s})", .{line_index, column, start_index, token_length, line_offset, @tagName(token_id)}); + } + + logln(.lexer, .end, "END LEXER - TOKEN OFFSET: {} - LINE OFFSET: {}", .{Token.unwrap(lexer.offset), lexer.line_offset}); + + lexer.count = Token.sub(token_buffer.getOffset(), lexer.offset); + lexer.line_count = token_buffer.getLineOffset() - lexer.line_offset; + + const time_end = std.time.Instant.now() catch unreachable; + lexer.time = time_end.since(time_start); + return lexer; +} diff --git a/bootstrap/frontend/lexical_analyzer.zig b/bootstrap/frontend/lexical_analyzer.zig deleted file mode 100644 index 567c553..0000000 --- a/bootstrap/frontend/lexical_analyzer.zig +++ /dev/null @@ -1,265 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; -const log = std.log; - -const equal = std.mem.eql; - -const data_structures = @import("../data_structures.zig"); -const ArrayList = data_structures.ArrayList; -const enumFromString = data_structures.enumFromString; - -const Compilation = @import("../Compilation.zig"); -const File = Compilation.File; -const logln = Compilation.logln; -const fs = @import("../fs.zig"); - -// TODO: switch to packed struct when speed is important -pub const Token = struct { - start: u32, - len: u24, - id: Id, - - pub const Id = enum(u8) { - eof = 0x00, - keyword_unsigned_integer = 0x01, - keyword_signed_integer = 0x02, - identifier = 0x03, - number_literal = 0x04, - string_literal = 0x05, - discard = 0x06, - bang = '!', // 0x21 - hash = '#', // 0x23 - dollar_sign = '$', // 0x24 - modulus = '%', // 0x25 - ampersand = '&', // 0x26 - left_parenthesis = '(', // 0x28 - right_parenthesis = ')', // 0x29 - asterisk = '*', // 0x2a - plus = '+', // 0x2b - comma = ',', // 0x2c - minus = '-', // 0x2d - period = '.', // 0x2e - slash = '/', // 0x2f - colon = ':', // 0x3a - semicolon = ';', // 0x3b - less = '<', // 0x3c - equal = '=', // 0x3d - greater = '>', // 0x3e - question_mark = '?', // 0x3f - at = '@', // 0x40 - left_bracket = '[', // 0x5b - backlash = '\\', // 0x5c - right_bracket = ']', // 0x5d - caret = '^', // 0x5e - underscore = '_', // 0x5f - grave = '`', // 0x60 - left_brace = '{', // 0x7b - vertical_bar = '|', // 0x7c - right_brace = '}', // 0x7d - tilde = '~', // 0x7e - fixed_keyword_function = 0x7f, - fixed_keyword_const = 0x80, - fixed_keyword_var = 0x81, - fixed_keyword_void = 0x82, - fixed_keyword_noreturn = 0x83, - fixed_keyword_comptime = 0x84, - fixed_keyword_while = 0x85, - fixed_keyword_bool = 0x86, - fixed_keyword_true = 0x87, - fixed_keyword_false = 0x88, - fixed_keyword_fn = 0x89, - fixed_keyword_unreachable = 0x8a, - fixed_keyword_return = 0x8b, - fixed_keyword_ssize = 0x8c, - fixed_keyword_usize = 0x8d, - fixed_keyword_switch = 0x8e, - fixed_keyword_if = 0x8f, - fixed_keyword_else = 0x90, - fixed_keyword_struct = 0x91, - fixed_keyword_enum = 0x92, - fixed_keyword_union = 0x93, - fixed_keyword_extern = 0x94, - fixed_keyword_null = 0x95, - fixed_keyword_align = 0x96, - fixed_keyword_export = 0x97, - fixed_keyword_cc = 0x98, - fixed_keyword_for = 0x99, - fixed_keyword_undefined = 0x9a, - fixed_keyword_break = 0x9b, - }; - - pub const Index = u32; -}; - -pub const FixedKeyword = enum { - @"comptime", - @"const", - @"var", - void, - noreturn, - function, - @"while", - bool, - true, - false, - @"fn", - @"unreachable", - @"return", - ssize, - usize, - @"switch", - @"if", - @"else", - @"struct", - @"enum", - @"union", - @"extern", - null, - @"align", - @"export", - cc, - @"for", - undefined, - @"break", -}; - -pub const Result = struct { - tokens: ArrayList(Token), - time: u64, -}; - -pub const Logger = enum { - main, - new_token, - number_literals, - - pub var bitset = std.EnumSet(Logger).initMany(&.{ - // .new_token, - .number_literals, - }); -}; - -pub fn analyze(allocator: Allocator, text: []const u8, file_index: File.Index) !Result { - _ = file_index; - const time_start = std.time.Instant.now() catch unreachable; - var tokens = try ArrayList(Token).initCapacity(allocator, text.len / 8); - var index: usize = 0; - - while (index < text.len) { - const start_index = index; - const start_character = text[index]; - const token_id: Token.Id = switch (start_character) { - 'a'...'z', 'A'...'Z', '_' => blk: { - while (true) { - const ch = text[index]; - if ((ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z') or ch == '_' or (ch >= '0' and ch <= '9')) { - index += 1; - continue; - } - - break; - } - - // const identifier = text[start_index..][0 .. index - start_index]; - // logln("Identifier: {s}", .{identifier}); - - if (start_character == 'u' or start_character == 's') { - var index_integer = start_index + 1; - while (text[index_integer] >= '0' and text[index_integer] <= '9') { - index_integer += 1; - } - - if (index_integer == index) { - const id: Token.Id = switch (start_character) { - 'u' => .keyword_unsigned_integer, - 's' => .keyword_signed_integer, - else => unreachable, - }; - - break :blk id; - } - } - - const string = text[start_index..][0 .. index - start_index]; - break :blk if (enumFromString(FixedKeyword, string)) |fixed_keyword| switch (fixed_keyword) { - inline else => |comptime_fixed_keyword| @field(Token.Id, "fixed_keyword_" ++ @tagName(comptime_fixed_keyword)), - } else if (equal(u8, string, "_")) .discard else .identifier; - }, - '0'...'9' => blk: { - // Detect other non-decimal literals - if (text[index] == '0' and index + 1 < text.len) { - logln(.lexer, .number_literals, "Number starts with 0. Checking for non-decimal literals...", .{}); - if (text[index + 1] == 'x') { - logln(.lexer, .number_literals, "Hex", .{}); - index += 2; - } else if (text[index + 1] == 'b') { - logln(.lexer, .number_literals, "Bin", .{}); - index += 2; - } else if (text[index + 1] == 'o') { - logln(.lexer, .number_literals, "Decimal", .{}); - index += 2; - } - } - - while (text[index] >= '0' and text[index] <= '9' or text[index] >= 'a' and text[index] <= 'f' or text[index] >= 'A' and text[index] <= 'F') { - index += 1; - } - - break :blk .number_literal; - }, - '\'' => { - unreachable; - }, - '"' => blk: { - index += 1; - - while (true) { - if (text[index] == '"' and text[index - 1] != '"') { - break; - } - - index += 1; - } - - index += 1; - - break :blk .string_literal; - }, - ' ', '\n', '\r', '\t' => { - index += 1; - continue; - }, - '(', ')', '{', '}', '[', ']', '=', ';', '#', '@', ',', '.', ':', '>', '<', '!', '+', '-', '*', '\\', '/', '&', '|', '^', '?', '$' => |operator| blk: { - index += 1; - break :blk @enumFromInt(operator); - }, - else => |ch| { - std.debug.panic("NI: '{c}'", .{ch}); - }, - }; - - const end_index = index; - const token = Token{ - .start = @intCast(start_index), - .len = @intCast(end_index - start_index), - .id = token_id, - }; - - logln(.lexer, .new_token, "New token {s} added: {s}", .{ @tagName(token.id), text[token.start..][0..token.len] }); - - try tokens.append(allocator, token); - } - - for (tokens.items, 0..) |token, i| { - logln(.lexer, .main, "#{} {s}\n", .{ i, @tagName(token.id) }); - } - - const time_end = std.time.Instant.now() catch unreachable; - const time = time_end.since(time_start); - - return .{ - .tokens = tokens, - .time = time, - }; -} diff --git a/bootstrap/frontend/syntactic_analyzer.zig b/bootstrap/frontend/parser.zig similarity index 55% rename from bootstrap/frontend/syntactic_analyzer.zig rename to bootstrap/frontend/parser.zig index 9df6f72..e27768b 100644 --- a/bootstrap/frontend/syntactic_analyzer.zig +++ b/bootstrap/frontend/parser.zig @@ -6,28 +6,25 @@ const panic = std.debug.panic; const data_structures = @import("../data_structures.zig"); const ArrayList = data_structures.ArrayList; +const BlockList = data_structures.BlockList; const enumFromString = data_structures.enumFromString; const HashMap = data_structures.HashMap; -const lexical_analyzer = @import("lexical_analyzer.zig"); -const Token = lexical_analyzer.Token; +const lexer = @import("lexer.zig"); const Compilation = @import("../Compilation.zig"); const File = Compilation.File; const log = Compilation.log; const logln = Compilation.logln; +const Token = Compilation.Token; pub const Result = struct { - nodes: ArrayList(Node), - node_lists: ArrayList(Node.List), + main_node_index: Node.Index, time: u64, }; -pub const Options = packed struct { - is_comptime: bool, -}; - pub const Logger = enum { + file, token_errors, symbol_declaration, node_creation, @@ -44,6 +41,7 @@ pub const Logger = enum { consume_token, pub var bitset = std.EnumSet(Logger).initMany(&.{ + .file, .token_errors, .symbol_declaration, .node_creation, @@ -68,31 +66,8 @@ pub const Node = struct { token: Token.Index, id: Id, - pub const List = ArrayList(Node.Index); - - pub const Index = packed struct(u32) { - value: u31, - invalid: bool = false, - - pub const invalid = Index{ - .value = 0, - .invalid = true, - }; - - pub fn get(index: Index) ?u32 { - return if (index.invvalid) null else index.value; - } - - pub fn unwrap(index: Index) u32 { - assert(!index.invalid); - return index.value; - } - - pub fn uniqueInteger(index: Index) u32 { - assert(!index.invalid); - return index.value; - } - }; + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; pub const Range = struct { start: u32, @@ -107,12 +82,13 @@ pub const Node = struct { function_declaration_no_arguments, container_declaration, string_literal, - simple_symbol_declaration, + constant_symbol_declaration, + variable_symbol_declaration, assign, @"comptime", node_list, - simple_while, - simple_function_prototype, + @"while", + function_prototype, function_definition, keyword_noreturn, keyword_true, @@ -126,7 +102,7 @@ pub const Node = struct { slice_type, array_type, argument_declaration, - compiler_intrinsic, + intrinsic, ssize_type, usize_type, void_type, @@ -138,10 +114,10 @@ pub const Node = struct { keyword_false, compare_equal, compare_not_equal, - compare_less_than, - compare_greater_than, - compare_less_or_equal, - compare_greater_or_equal, + compare_less, + compare_greater, + compare_less_equal, + compare_greater_equal, @"if", if_else, @"switch", @@ -150,17 +126,23 @@ pub const Node = struct { enum_field, extern_qualifier, export_qualifier, - function_prototype, add, sub, + mul, + div, + mod, bit_and, bit_xor, expression_group, bit_or, - multiply, - divide, shift_left, shift_right, + add_assign, + sub_assign, + mul_assign, + div_assign, + mod_assign, + discarded_assign, bool_type, named_argument, optional_type, @@ -184,10 +166,9 @@ pub const Node = struct { calling_convention, assembly_register, assembly_statement, - assembly_block, + // assembly_block, for_condition, for_loop, - add_assign, undefined, zero_terminated, null_terminated, @@ -196,8 +177,12 @@ pub const Node = struct { optional_unwrap, anonymous_empty_literal, empty_container_literal_guess, - discarded_assign, break_expression, + character_literal, + attribute_naked, + attribute_export, + attribute_extern, + attribute_cc, }; }; @@ -208,36 +193,57 @@ const Error = error{ }; const Analyzer = struct { - tokens: []const Token, - token_i: u32 = 0, - nodes: ArrayList(Node) = .{}, + lexer: lexer.Result, + token_i: Token.Index, + token_buffer: *Token.Buffer, + nodes: *Node.List, + node_lists: *ArrayList(ArrayList(Node.Index)), source_file: []const u8, file_index: File.Index, allocator: Allocator, - node_lists: ArrayList(Node.List) = .{}, suffix_depth: usize = 0, - fn expectToken(analyzer: *Analyzer, token_id: Token.Id) !u32 { + fn expectToken(analyzer: *Analyzer, expected_token_id: Token.Id) !Token.Index { const token_i = analyzer.token_i; - const token = analyzer.tokens[token_i]; - const is_expected_token = token.id == token_id; + const token_id = analyzer.peekToken(); + const is_expected_token = token_id == expected_token_id; if (is_expected_token) { analyzer.consumeToken(); const result = token_i; return result; } else { - logln(.parser, .token_errors, "Unexpected token {s} when expected {s}\n| |\n v \n```\n{s}\n```", .{ @tagName(token.id), @tagName(token_id), analyzer.source_file[token.start..] }); + const file_offset = analyzer.getTokenOffset(token_i); + const file_chunk = analyzer.source_file[file_offset..]; + std.debug.print("Unexpected token {s} when expected {s}\n| |\n v \n```\n{s}\n```", .{ @tagName(token_id), @tagName(expected_token_id), file_chunk }); @breakpoint(); return error.unexpected_token; } } - fn peekTokenAhead(analyzer: *Analyzer, ahead_offset: usize) Token { - return analyzer.tokens[analyzer.token_i + ahead_offset]; + fn getTokenOffset(analyzer: *Analyzer, token_index: Token.Index) u32 { + const index = Token.unwrap(token_index); + const offset = analyzer.token_buffer.tokens.items(.offset)[index]; + return offset; } - fn peekToken(analyzer: *Analyzer) Token { - return analyzer.peekTokenAhead(0); + fn peekTokenAhead(analyzer: *Analyzer, ahead_offset: u32) Token.Id { + const token_index = Token.addInt(analyzer.token_i, ahead_offset); + const token = analyzer.token_buffer.tokens.items(.id)[Token.unwrap(token_index)]; + return token; + } + + fn peekToken(analyzer: *Analyzer) Token.Id { + const token = analyzer.peekTokenAhead(0); + return token; + } + + fn hasTokens(analyzer: *Analyzer) bool { + const token_end = analyzer.getTokenEnd(); + return Token.unwrap(analyzer.token_i) < token_end; + } + + fn getTokenEnd(analyzer: *const Analyzer) u32 { + return @intFromEnum(Token.addInt(analyzer.lexer.offset, analyzer.lexer.count)); } fn consumeToken(analyzer: *Analyzer) void { @@ -245,201 +251,136 @@ const Analyzer = struct { } fn consumeTokens(analyzer: *Analyzer, token_count: u32) void { - assert(analyzer.token_i + token_count <= analyzer.tokens.len); + assert(Token.unwrap(Token.addInt(analyzer.token_i, token_count)) <= analyzer.getTokenEnd()); log(.parser, .consume_token, "Consuming {} {s}: ", .{ token_count, if (token_count == 1) "token" else "tokens" }); - for (0..token_count) |i| { - const id = analyzer.peekTokenAhead(i).id; - log(.parser, .consume_token, "{s}, ", .{@tagName(id)}); + for (0..token_count) |i_usize| { + const i: u32 = @intCast(i_usize); + const token_id = analyzer.peekTokenAhead(i); + const token_index = Token.addInt(analyzer.token_i, i); + const token_bytes = analyzer.bytes(token_index); + log(.parser, .consume_token, "{s}, '{s}'", .{ @tagName(token_id), token_bytes }); } + log(.parser, .consume_token, "\n", .{}); - analyzer.token_i += token_count; + analyzer.token_i = Token.addInt(analyzer.token_i, token_count); } fn bytes(analyzer: *const Analyzer, token_index: Token.Index) []const u8 { - const token = analyzer.tokens[token_index]; - return analyzer.source_file[token.start..][0..token.len]; + const index = Token.unwrap(token_index); + const offset = analyzer.token_buffer.tokens.items(.offset)[index]; + const len = analyzer.token_buffer.tokens.items(.length)[index]; + const slice = analyzer.source_file[offset..][0..len]; + return slice; } fn symbolDeclaration(analyzer: *Analyzer) anyerror!Node.Index { const first = analyzer.token_i; - assert(analyzer.tokens[first].id == .fixed_keyword_var or analyzer.tokens[first].id == .fixed_keyword_const); + const mutability_node_id: Node.Id = switch (analyzer.peekToken()) { + .fixed_keyword_var => .variable_symbol_declaration, + .fixed_keyword_const => .constant_symbol_declaration, + else => |t| @panic(@tagName(t)), + }; analyzer.consumeToken(); const declaration_name_token = try analyzer.expectToken(.identifier); const declaration_name = analyzer.bytes(declaration_name_token); logln(.parser, .symbol_declaration, "Starting parsing declaration \"{s}\"", .{declaration_name}); - logln(.parser, .symbol_declaration, "Current token: {}", .{analyzer.tokens[analyzer.token_i].id}); + logln(.parser, .symbol_declaration, "Current token: {}", .{analyzer.peekToken()}); - const type_node_index = switch (analyzer.tokens[analyzer.token_i].id) { - .colon => blk: { + const type_node_index = switch (analyzer.peekToken()) { + .operator_colon => blk: { analyzer.consumeToken(); break :blk try analyzer.typeExpression(); }, - else => Node.Index.invalid, + else => Node.Index.null, }; - _ = try analyzer.expectToken(.equal); + _ = try analyzer.expectToken(.operator_assign); const init_node_index = try analyzer.expression(); - const init_node = analyzer.nodes.items[init_node_index.unwrap()]; + const init_node = analyzer.nodes.get(init_node_index); switch (init_node.id) { .function_definition => {}, - else => _ = try analyzer.expectToken(.semicolon), + else => _ = try analyzer.expectToken(.operator_semicolon), } // TODO: const declaration = Node{ - .id = .simple_symbol_declaration, + .id = mutability_node_id, .token = first, .left = type_node_index, .right = init_node_index, }; logln(.parser, .symbol_declaration, "Adding declaration \"{s}\" with init node of type: {s}", .{ declaration_name, @tagName(init_node.id) }); - // if (analyzer.token_i < analyzer.tokens.len) { - // const first_token = analyzer.tokens[first]; - // const last_token = analyzer.tokens[analyzer.token_i]; - // const declaration_source_start = first_token.start; - // const declaration_source_end = last_token.start; - // - // logln("[ALL]\n", .{}); - // logln("Source file ({} bytes) :\n```\n{s}\n```\n", .{ analyzer.source_file.len, analyzer.source_file }); - // - // logln("[BEFORE]\n", .{}); - // - // logln("Tokens before the declaration: ", .{}); - // for (analyzer.tokens[0..first]) |t| { - // logln("{s} ", .{@tagName(t.id)}); - // } - // logln("\n", .{}); - // logln("Source before the declaration:\n```\n{s}\n```\n", .{analyzer.source_file[0..analyzer.tokens[first].start]}); - // logln("[DECLARATION]\n", .{}); - // - // logln("First token: {}\n", .{first_token}); - // logln("Last token: {}\n", .{last_token}); - // - // logln("Tokens including declaration ([{}-{}])", .{ first, analyzer.token_i }); - // for (analyzer.tokens[first..][0 .. analyzer.token_i - first]) |t| { - // logln("{s} ", .{@tagName(t.id)}); - // } - // logln("\n", .{}); - // - // logln("Source for the declaration:\n```\n{s}\n```\n", .{analyzer.source_file[declaration_source_start..declaration_source_end]}); - // logln("[AFTER]\n", .{}); - // - // // TODO - // // print("Tokens for file #{}\n", .{analyzer. - // // for (analyzer.tokens[ - // } return try analyzer.addNode(declaration); } fn function(analyzer: *Analyzer) !Node.Index { const token = analyzer.token_i; - assert(analyzer.tokens[token].id == .fixed_keyword_fn); + assert(analyzer.peekToken() == .fixed_keyword_fn); analyzer.consumeToken(); const function_prototype = try analyzer.functionPrototype(); const is_comptime = false; _ = is_comptime; - return switch (analyzer.tokens[analyzer.token_i].id) { - .left_brace => try analyzer.addNode(.{ + return switch (analyzer.peekToken()) { + .operator_left_brace => try analyzer.addNode(.{ .id = .function_definition, .token = token, .left = function_prototype, - .right = try analyzer.block(.{ .is_comptime = false }), + .right = try analyzer.block(), }), - .semicolon => function_prototype, + .operator_semicolon => function_prototype, else => |t| @panic(@tagName(t)), }; } fn functionPrototype(analyzer: *Analyzer) !Node.Index { const token = analyzer.token_i; - assert(analyzer.tokens[token].id == .left_parenthesis); - const arguments = try analyzer.argumentList(.left_parenthesis, .right_parenthesis); - const return_type = try analyzer.typeExpression(); + var attribute_and_return_type_node_list = ArrayList(Node.Index){}; - const simple_function_prototype = try analyzer.addNode(.{ - .id = .simple_function_prototype, - .token = token, - .left = arguments, - .right = return_type, - }); + while (analyzer.peekToken() != .operator_left_parenthesis) { + const identifier = try analyzer.expectToken(.identifier); + const identifier_name = analyzer.bytes(identifier); - return switch (analyzer.tokens[analyzer.token_i].id) { - .semicolon, - .left_brace, - .comma, - => simple_function_prototype, - else => blk: { - var list = Node.List{}; - - while (true) { - const attribute_token = analyzer.token_i; - const attribute = switch (analyzer.tokens[attribute_token].id) { - .semicolon, - .left_brace, - => break, - .fixed_keyword_extern => b: { - analyzer.consumeToken(); - _ = try analyzer.expectToken(.left_parenthesis); - const string_literal = try analyzer.addNode(.{ - .id = .string_literal, - .token = try analyzer.expectToken(.string_literal), - .left = Node.Index.invalid, - .right = Node.Index.invalid, - }); - _ = try analyzer.expectToken(.right_parenthesis); - - const result = try analyzer.addNode(.{ - .id = .extern_qualifier, - .token = attribute_token, - .left = string_literal, - .right = Node.Index.invalid, - }); - break :b result; - }, - .fixed_keyword_export => b: { - const result = try analyzer.addNode(.{ - .id = .export_qualifier, - .token = attribute_token, - .left = Node.Index.invalid, - .right = Node.Index.invalid, - }); - analyzer.consumeToken(); - break :b result; - }, - .fixed_keyword_cc => b: { - analyzer.consumeToken(); - _ = try analyzer.expectToken(.left_parenthesis); - const calling_conv_expression = try analyzer.expression(); - _ = try analyzer.expectToken(.right_parenthesis); - - const result = try analyzer.addNode(.{ - .id = .calling_convention, - .token = attribute_token, - .left = calling_conv_expression, - .right = Node.Index.invalid, - }); - break :b result; - }, + const attribute_node = inline for (@typeInfo(Compilation.Function.Attribute).Enum.fields) |enum_field| { + if (equal(u8, identifier_name, enum_field.name)) { + const attribute = @field(Compilation.Function.Attribute, enum_field.name); + const attribute_node = switch (attribute) { + .naked, .@"export", => try analyzer.addNode(.{ + .id = @field(Node.Id, "attribute_" ++ @tagName(attribute)), + .token = identifier, + .left = .null, + .right = .null, + }), else => |t| @panic(@tagName(t)), }; - - try list.append(analyzer.allocator, attribute); + break attribute_node; } + } else @panic("Not known attribute"); - break :blk try analyzer.addNode(.{ - .id = .function_prototype, - .token = token, - .left = simple_function_prototype, - .right = try analyzer.nodeList(list), - }); - }, - }; + try attribute_and_return_type_node_list.append(analyzer.allocator, attribute_node); + + if (analyzer.peekToken() == .operator_comma) analyzer.consumeToken(); + } + + assert(analyzer.peekToken() == .operator_left_parenthesis); + + const arguments = try analyzer.argumentList(.operator_left_parenthesis, .operator_right_parenthesis); + const return_type = try analyzer.typeExpression(); + try attribute_and_return_type_node_list.append(analyzer.allocator, return_type); + + const function_prototype = try analyzer.addNode(.{ + .id = .function_prototype, + .token = token, + .left = arguments, + .right = try analyzer.nodeList( attribute_and_return_type_node_list), + }); + + return function_prototype; } fn argumentList(analyzer: *Analyzer, maybe_start_token: ?Token.Id, end_token: Token.Id) !Node.Index { @@ -449,12 +390,12 @@ const Analyzer = struct { var list = ArrayList(Node.Index){}; - while (analyzer.tokens[analyzer.token_i].id != end_token) { + while (analyzer.peekToken() != end_token) { const identifier = try analyzer.expectToken(.identifier); - _ = try analyzer.expectToken(.colon); + _ = try analyzer.expectToken(.operator_colon); const type_expression = try analyzer.typeExpression(); - if (analyzer.tokens[analyzer.token_i].id == .comma) { + if (analyzer.peekToken() == .operator_comma) { analyzer.consumeToken(); } @@ -462,7 +403,7 @@ const Analyzer = struct { .id = .argument_declaration, .token = identifier, .left = type_expression, - .right = Node.Index.invalid, + .right = Node.Index.null, })); } @@ -471,26 +412,26 @@ const Analyzer = struct { if (list.items.len != 0) { return try analyzer.nodeList(list); } else { - return Node.Index.invalid; + return Node.Index.null; } } fn assignExpressionStatement(analyzer: *Analyzer) !Node.Index { const result = try analyzer.assignExpression(); - _ = try analyzer.expectToken(.semicolon); + _ = try analyzer.expectToken(.operator_semicolon); return result; } - fn block(analyzer: *Analyzer, options: Options) anyerror!Node.Index { - const left_brace = try analyzer.expectToken(.left_brace); + fn block(analyzer: *Analyzer) anyerror!Node.Index { + const left_brace = try analyzer.expectToken(.operator_left_brace); var list = ArrayList(Node.Index){}; - while (analyzer.tokens[analyzer.token_i].id != .right_brace) { - const first_statement_token = analyzer.tokens[analyzer.token_i]; - logln(.parser, .block, "First statement token: {s}", .{@tagName(first_statement_token.id)}); - const statement_index = switch (first_statement_token.id) { - .identifier => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .colon => { + while (analyzer.peekToken() != .operator_right_brace) { + const first_statement_token = analyzer.peekToken(); + logln(.parser, .block, "First statement token: {s}", .{@tagName(first_statement_token)}); + const statement_index = switch (first_statement_token) { + .identifier => switch (analyzer.peekTokenAhead(1)) { + .operator_colon => { unreachable; }, else => try analyzer.assignExpressionStatement(), @@ -500,57 +441,54 @@ const Analyzer = struct { .discard, => try analyzer.assignExpressionStatement(), - .fixed_keyword_while => try analyzer.whileExpression(options), + .fixed_keyword_while => try analyzer.whileExpression(), .fixed_keyword_switch => try analyzer.switchExpression(), .fixed_keyword_if => try analyzer.ifExpression(), .fixed_keyword_for => try analyzer.forExpression(), .fixed_keyword_const, .fixed_keyword_var, => try analyzer.symbolDeclaration(), - .hash => blk: { - const intrinsic = try analyzer.compilerIntrinsic(); - _ = try analyzer.expectToken(.semicolon); - break :blk intrinsic; - }, .fixed_keyword_break => b: { const node_index = try analyzer.breakExpression(); - _ = try analyzer.expectToken(.semicolon); + _ = try analyzer.expectToken(.operator_semicolon); break :b node_index; }, + .intrinsic => blk: { + const intrinsic = try analyzer.compilerIntrinsic(); + _ = try analyzer.expectToken(.operator_semicolon); + break :blk intrinsic; + }, else => |t| @panic(@tagName(t)), }; - const node = analyzer.nodes.items[statement_index.unwrap()]; + const node = analyzer.nodes.get(statement_index); logln(.parser, .block, "Adding statement: {s}", .{@tagName(node.id)}); try list.append(analyzer.allocator, statement_index); } - _ = try analyzer.expectToken(.right_brace); + _ = try analyzer.expectToken(.operator_right_brace); return try analyzer.addNode(.{ - .id = switch (options.is_comptime) { - true => .comptime_block, - false => .block, - }, + .id = .block, .token = left_brace, .left = try analyzer.nodeList(list), - .right = Node.Index.invalid, + .right = Node.Index.null, }); } - fn whileExpression(analyzer: *Analyzer, options: Options) anyerror!Node.Index { + fn whileExpression(analyzer: *Analyzer) anyerror!Node.Index { const while_identifier_index = try analyzer.expectToken(.fixed_keyword_while); - _ = try analyzer.expectToken(.left_parenthesis); + _ = try analyzer.expectToken(.operator_left_parenthesis); // TODO: const while_condition = try analyzer.expression(); - _ = try analyzer.expectToken(.right_parenthesis); + _ = try analyzer.expectToken(.operator_right_parenthesis); - const while_block = try analyzer.block(options); + const while_block = try analyzer.block(); return analyzer.addNode(.{ - .id = .simple_while, + .id = .@"while", .token = while_identifier_index, .left = while_condition, .right = while_block, @@ -561,32 +499,30 @@ const Analyzer = struct { logln(.parser, .@"switch", "Parsing switch...", .{}); const switch_token = analyzer.token_i; analyzer.consumeToken(); - _ = try analyzer.expectToken(.left_parenthesis); + _ = try analyzer.expectToken(.operator_left_parenthesis); const switch_expression = try analyzer.expression(); - _ = try analyzer.expectToken(.right_parenthesis); + _ = try analyzer.expectToken(.operator_right_parenthesis); logln(.parser, .@"switch", "Parsed switch expression...", .{}); - _ = try analyzer.expectToken(.left_brace); + _ = try analyzer.expectToken(.operator_left_brace); - var list = Node.List{}; + var list = ArrayList(Node.Index){}; - while (analyzer.tokens[analyzer.token_i].id != .right_brace) { + while (analyzer.peekToken() != .operator_right_brace) { const case_token = analyzer.token_i; logln(.parser, .@"switch", "Parsing switch case...", .{}); - const case_node = switch (analyzer.tokens[case_token].id) { + const case_node = switch (analyzer.peekToken()) { .fixed_keyword_else => blk: { analyzer.consumeToken(); - break :blk Node.Index.invalid; + break :blk Node.Index.null; }, else => blk: { - var array_list = Node.List{}; + var array_list = ArrayList(Node.Index){}; while (true) { - try array_list.append(analyzer.allocator, try analyzer.expression()); - switch (analyzer.tokens[analyzer.token_i].id) { - .comma => analyzer.consumeToken(), - .equal => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .greater => break, - else => {}, - }, + const switch_case_node = try analyzer.expression(); + try array_list.append(analyzer.allocator, switch_case_node); + switch (analyzer.peekToken()) { + .operator_comma => analyzer.consumeToken(), + .operator_switch_case => break, else => {}, } } @@ -598,18 +534,15 @@ const Analyzer = struct { }; }, }; - _ = try analyzer.expectToken(.equal); - _ = try analyzer.expectToken(.greater); - const is_left_brace = analyzer.tokens[analyzer.token_i].id == .left_brace; + _ = try analyzer.expectToken(.operator_switch_case); + const is_left_brace = analyzer.peekToken() == .operator_left_brace; const expr = switch (is_left_brace) { - true => try analyzer.block(.{ - .is_comptime = false, - }), + true => try analyzer.block(), false => try analyzer.assignExpression(), }; - logln(.parser, .switch_case, "Comma token: \n```\n{s}\n```\n", .{analyzer.source_file[analyzer.tokens[analyzer.token_i].start..]}); - _ = try analyzer.expectToken(.comma); + // logln(.parser, .switch_case, "Comma token: \n```\n{s}\n```\n", .{analyzer.source_file[analyzer.tokens[analyzer.token_i].start..]}); + _ = try analyzer.expectToken(.operator_comma); const node = try analyzer.addNode(.{ .id = .switch_case, @@ -621,7 +554,7 @@ const Analyzer = struct { try list.append(analyzer.allocator, node); } - _ = try analyzer.expectToken(.right_brace); + _ = try analyzer.expectToken(.operator_right_brace); return try analyzer.addNode(.{ .id = .@"switch", @@ -635,20 +568,20 @@ const Analyzer = struct { const if_token = analyzer.token_i; analyzer.consumeToken(); - _ = try analyzer.expectToken(.left_parenthesis); + _ = try analyzer.expectToken(.operator_left_parenthesis); const if_condition = try analyzer.expression(); - _ = try analyzer.expectToken(.right_parenthesis); + _ = try analyzer.expectToken(.operator_right_parenthesis); - const payload = if (analyzer.tokens[analyzer.token_i].id == .vertical_bar) blk: { + const payload = if (analyzer.peekToken() == .operator_bar) blk: { analyzer.consumeToken(); - const payload_node = switch (analyzer.tokens[analyzer.token_i].id) { + const payload_node = switch (analyzer.peekToken()) { .identifier => try analyzer.identifierNode(), .discard => try analyzer.discardNode(), else => unreachable, }; - _ = try analyzer.expectToken(.vertical_bar); + _ = try analyzer.expectToken(.operator_bar); break :blk payload_node; - } else Node.Index.invalid; + } else Node.Index.null; const if_taken_expression = try analyzer.expression(); @@ -659,7 +592,7 @@ const Analyzer = struct { .right = if_taken_expression, }); - const result = switch (analyzer.tokens[analyzer.token_i].id) { + const result = switch (analyzer.peekToken()) { .fixed_keyword_else => blk: { analyzer.consumeToken(); @@ -673,11 +606,11 @@ const Analyzer = struct { else => if_node, }; - if (payload.invalid) { + if (payload == .null) { return result; } else { return try analyzer.addNode(.{ - .id = switch (result.value == if_node.value) { + .id = switch (result == if_node) { true => .if_payload, false => .if_else_payload, }, @@ -690,42 +623,55 @@ const Analyzer = struct { fn forExpression(analyzer: *Analyzer) !Node.Index { const token = try analyzer.expectToken(.fixed_keyword_for); - _ = try analyzer.expectToken(.left_parenthesis); - const expression_token = analyzer.token_i; - const first = try analyzer.expression(); - const ForExpression = struct { - node_index: Node.Index, - expected_payload_count: usize, - }; - const for_expression = switch (analyzer.tokens[analyzer.token_i].id) { - .period => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .period => blk: { - analyzer.consumeTokens(2); - const second = try analyzer.expression(); + _ = try analyzer.expectToken(.operator_left_parenthesis); - break :blk ForExpression{ - .node_index = try analyzer.addNode(.{ + var for_expression_list = ArrayList(Node.Index){}; + + while (analyzer.peekToken() != .operator_right_parenthesis) { + const expression_token = analyzer.token_i; + const first = try analyzer.expression(); + + const node_index = switch (analyzer.peekToken()) { + .operator_dot => switch (analyzer.peekTokenAhead(1)) { + .operator_dot => blk: { + analyzer.consumeTokens(2); + + break :blk try analyzer.addNode(.{ .id = .range, .token = expression_token, .left = first, - .right = second, - }), - .expected_payload_count = 1, - }; + .right = switch (analyzer.peekToken()) { + .operator_right_parenthesis, .operator_comma => Node.Index.null, + else => try analyzer.expression(), + }, + }); + }, + else => |t| @panic(@tagName(t)), }, + .operator_right_parenthesis, + .operator_comma, + => first, else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }; + }; - _ = try analyzer.expectToken(.right_parenthesis); + try for_expression_list.append(analyzer.allocator, node_index); - _ = try analyzer.expectToken(.vertical_bar); + switch (analyzer.peekToken()) { + .operator_comma => analyzer.consumeToken(), + .operator_right_parenthesis => {}, + else => |t| @panic(@tagName(t)), + } + } + + _ = try analyzer.expectToken(.operator_right_parenthesis); + + _ = try analyzer.expectToken(.operator_bar); var payload_nodes = ArrayList(Node.Index){}; - while (analyzer.tokens[analyzer.token_i].id != .vertical_bar) { + + while (analyzer.peekToken() != .operator_bar) { const payload_token = analyzer.token_i; - const id: Node.Id = switch (analyzer.tokens[payload_token].id) { + const id: Node.Id = switch (analyzer.peekToken()) { .identifier => .identifier, .discard => .discard, else => |t| @panic(@tagName(t)), @@ -733,40 +679,38 @@ const Analyzer = struct { analyzer.consumeToken(); - switch (analyzer.tokens[analyzer.token_i].id) { - .vertical_bar => {}, - .comma => analyzer.consumeToken(), + switch (analyzer.peekToken()) { + .operator_bar => {}, + .operator_comma => analyzer.consumeToken(), else => |t| @panic(@tagName(t)), } try payload_nodes.append(analyzer.allocator, try analyzer.addNode(.{ .id = id, .token = payload_token, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, })); } - _ = try analyzer.expectToken(.vertical_bar); + _ = try analyzer.expectToken(.operator_bar); - if (payload_nodes.items.len != for_expression.expected_payload_count) { + if (payload_nodes.items.len != for_expression_list.items.len) { unreachable; } const for_condition_node = try analyzer.addNode(.{ .id = .for_condition, .token = token, - .left = for_expression.node_index, + .left = try analyzer.nodeList(for_expression_list), .right = try analyzer.nodeList(payload_nodes), }); - const for_content_node = switch (analyzer.tokens[analyzer.token_i].id) { - .left_brace => try analyzer.block(.{ - .is_comptime = false, - }), + const for_content_node = switch (analyzer.peekToken()) { + .operator_left_brace => try analyzer.block(), else => blk: { const for_content_expression = try analyzer.expression(); - _ = try analyzer.expectToken(.semicolon); + _ = try analyzer.expectToken(.operator_semicolon); break :blk for_content_expression; }, }; @@ -786,8 +730,8 @@ const Analyzer = struct { const node_index = try analyzer.addNode(.{ .id = .break_expression, .token = t, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }); return node_index; } @@ -795,21 +739,17 @@ const Analyzer = struct { fn assignExpression(analyzer: *Analyzer) !Node.Index { const left = try analyzer.expression(); const expression_token = analyzer.token_i; - const expression_id: Node.Id = switch (analyzer.tokens[expression_token].id) { - .semicolon, .comma => return left, - .equal => .assign, - .plus => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .equal => blk: { - analyzer.consumeToken(); - break :blk .add_assign; - }, - else => |t| @panic(@tagName(t)), - }, + const expression_id: Node.Id = switch (analyzer.peekToken()) { + .operator_semicolon, .operator_comma => return left, + .operator_assign => .assign, + .operator_add_assign => .add_assign, + .operator_sub_assign => .sub_assign, + .operator_mul_assign => .mul_assign, + .operator_div_assign => .div_assign, + .operator_mod_assign => .mod_assign, else => |t| @panic(@tagName(t)), }; - analyzer.consumeToken(); - const right = try analyzer.expression(); const node = Node{ @@ -821,33 +761,59 @@ const Analyzer = struct { logln(.parser, .assign, "assign:\nleft: {}.\nright: {}", .{ node.left, node.right }); return try analyzer.addNode(node); + // .operator_equal => .operator_assign, + // .operator_add => switch (analyzer.peekTokenAhead(1)) { + // .operator_equal => blk: { + // analyzer.consumeToken(); + // break :blk .operator_add_assign; + // }, + // else => |t| @panic(@tagName(t)), + // }, + // .operator_sub => switch (analyzer.peekTokenAhead(1)) { + // .equal => blk: { + // analyzer.consumeToken(); + // break :blk .operator_sub_assign; + // }, + // else => |t| @panic(@tagName(t)), + // }, + // .operator_div => switch (analyzer.peekTokenAhead(1)) { + // .operator_equal => blk: { + // analyzer.consumeToken(); + // break :blk .operator_div_assign; + // }, + // else => |t| @panic(@tagName(t)), + // }, + // else => |t| @panic(@tagName(t)), + // }; + // + // } fn parseAsmOperand(analyzer: *Analyzer) !Node.Index { const token = analyzer.token_i; - const result = switch (analyzer.tokens[token].id) { + const result = switch (analyzer.peekToken()) { .identifier => try analyzer.addNode(.{ .id = .assembly_register, .token = blk: { analyzer.consumeToken(); break :blk token; }, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), .number_literal => blk: { analyzer.consumeToken(); break :blk analyzer.addNode(.{ .id = .number_literal, .token = token, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }); }, - .left_brace => blk: { + .operator_left_brace => blk: { analyzer.consumeToken(); const result = try analyzer.expression(); - _ = try analyzer.expectToken(.right_brace); + _ = try analyzer.expectToken(.operator_right_brace); break :blk result; }, else => |t| @panic(@tagName(t)), @@ -856,69 +822,69 @@ const Analyzer = struct { } fn compilerIntrinsic(analyzer: *Analyzer) !Node.Index { - const hash = try analyzer.expectToken(.hash); - const intrinsic_token = try analyzer.expectToken(.identifier); - _ = try analyzer.expectToken(.left_parenthesis); + const intrinsic_token = try analyzer.expectToken(.intrinsic); + _ = try analyzer.expectToken(.operator_left_parenthesis); + const intrinsic_name = analyzer.bytes(intrinsic_token)[1..]; - if (equal(u8, analyzer.bytes(intrinsic_token), "asm")) { - _ = try analyzer.expectToken(.left_brace); - var statements = ArrayList(Node.Index){}; + const intrinsic_id = inline for (@typeInfo(Compilation.Intrinsic.Id).Enum.fields) |enum_field| { + if (equal(u8, enum_field.name, intrinsic_name)) { + break @field(Compilation.Intrinsic.Id, enum_field.name); + } + } else @panic(intrinsic_name); - while (analyzer.tokens[analyzer.token_i].id != .right_brace) { + var list = ArrayList(Node.Index){}; + + if (intrinsic_id == .@"asm") { + _ = try analyzer.expectToken(.operator_left_brace); + + while (analyzer.peekToken() != .operator_right_brace) { const instruction_token = try analyzer.expectToken(.identifier); var operand_list = ArrayList(Node.Index){}; - while (analyzer.tokens[analyzer.token_i].id != .semicolon) { + while (analyzer.peekToken() != .operator_semicolon) { const asm_operand = try analyzer.parseAsmOperand(); - switch (analyzer.tokens[analyzer.token_i].id) { - .semicolon => {}, - .comma => analyzer.consumeToken(), + switch (analyzer.peekToken()) { + .operator_semicolon => {}, + .operator_comma => analyzer.consumeToken(), else => |t| @panic(@tagName(t)), } try operand_list.append(analyzer.allocator, asm_operand); } - _ = try analyzer.expectToken(.semicolon); + _ = try analyzer.expectToken(.operator_semicolon); - try statements.append(analyzer.allocator, try analyzer.addNode(.{ + try list.append(analyzer.allocator, try analyzer.addNode(.{ .id = .assembly_statement, .token = instruction_token, .left = try analyzer.nodeList(operand_list), - .right = Node.Index.invalid, + .right = Node.Index.null, })); } - _ = try analyzer.expectToken(.right_brace); - _ = try analyzer.expectToken(.right_parenthesis); - return try analyzer.addNode(.{ - .id = .assembly_block, - .token = hash, - .left = try analyzer.nodeList(statements), - .right = Node.Index.invalid, - }); + _ = try analyzer.expectToken(.operator_right_brace); + _ = try analyzer.expectToken(.operator_right_parenthesis); } else { - var list = ArrayList(Node.Index){}; - while (analyzer.tokens[analyzer.token_i].id != .right_parenthesis) { + while (analyzer.peekToken() != .operator_right_parenthesis) { const parameter = try analyzer.expression(); try list.append(analyzer.allocator, parameter); - switch (analyzer.tokens[analyzer.token_i].id) { - .comma => analyzer.consumeToken(), - .right_parenthesis => continue, + switch (analyzer.peekToken()) { + .operator_comma => analyzer.consumeToken(), + .operator_right_parenthesis => continue, else => |t| @panic(@tagName(t)), } } // Consume the right parenthesis analyzer.consumeToken(); - - return try analyzer.addNode(.{ - .id = .compiler_intrinsic, - .token = hash, - .left = try analyzer.nodeList(list), - .right = Node.Index.invalid, - }); } + + return try analyzer.addNode(.{ + .id = .intrinsic, + .token = intrinsic_token, + .left = try analyzer.nodeList(list), + .right = @enumFromInt(@intFromEnum(intrinsic_id)), + }); } fn expression(analyzer: *Analyzer) anyerror!Node.Index { @@ -928,17 +894,18 @@ const Analyzer = struct { const PrecedenceOperator = enum { compare_equal, compare_not_equal, - compare_less_than, - compare_greater_than, - compare_less_or_equal, - compare_greater_or_equal, + compare_less, + compare_greater, + compare_less_equal, + compare_greater_equal, add, sub, + mul, + div, + mod, bit_and, bit_xor, bit_or, - multiply, - divide, shift_left, shift_right, }; @@ -946,17 +913,18 @@ const Analyzer = struct { const operator_precedence = std.EnumArray(PrecedenceOperator, i32).init(.{ .compare_equal = 30, .compare_not_equal = 30, - .compare_less_than = 30, - .compare_greater_than = 30, - .compare_less_or_equal = 30, - .compare_greater_or_equal = 30, + .compare_less = 30, + .compare_greater = 30, + .compare_less_equal = 30, + .compare_greater_equal = 30, .add = 60, .sub = 60, + .mul = 70, + .div = 70, + .mod = 70, .bit_and = 40, .bit_xor = 40, .bit_or = 40, - .multiply = 70, - .divide = 70, .shift_left = 50, .shift_right = 50, }); @@ -964,17 +932,18 @@ const Analyzer = struct { const operator_associativity = std.EnumArray(PrecedenceOperator, Associativity).init(.{ .compare_equal = .none, .compare_not_equal = .none, - .compare_less_than = .none, - .compare_greater_than = .none, - .compare_less_or_equal = .none, - .compare_greater_or_equal = .none, + .compare_less = .none, + .compare_greater = .none, + .compare_less_equal = .none, + .compare_greater_equal = .none, .add = .left, .sub = .left, .bit_and = .left, .bit_xor = .left, .bit_or = .left, - .multiply = .left, - .divide = .left, + .mul = .left, + .div = .left, + .mod = .left, .shift_left = .left, .shift_right = .left, }); @@ -982,17 +951,18 @@ const Analyzer = struct { const operator_node_id = std.EnumArray(PrecedenceOperator, Node.Id).init(.{ .compare_equal = .compare_equal, .compare_not_equal = .compare_not_equal, - .compare_greater_than = .compare_greater_than, - .compare_less_than = .compare_less_than, - .compare_greater_or_equal = .compare_greater_or_equal, - .compare_less_or_equal = .compare_less_or_equal, + .compare_greater = .compare_greater, + .compare_less = .compare_less, + .compare_greater_equal = .compare_greater_equal, + .compare_less_equal = .compare_less_equal, .add = .add, .sub = .sub, .bit_and = .bit_and, .bit_xor = .bit_xor, .bit_or = .bit_or, - .multiply = .multiply, - .divide = .divide, + .mul = .mul, + .div = .div, + .mod = .mod, .shift_left = .shift_left, .shift_right = .shift_right, }); @@ -1000,99 +970,58 @@ const Analyzer = struct { fn expressionPrecedence(analyzer: *Analyzer, minimum_precedence: i32) !Node.Index { assert(minimum_precedence >= 0); var result = try analyzer.prefixExpression(); - if (!result.invalid) { - const prefix_node = analyzer.nodes.items[result.unwrap()]; + if (result != .null) { + const prefix_node = analyzer.nodes.get(result); logln(.parser, .precedence, "Prefix: {s}", .{@tagName(prefix_node.id)}); } var banned_precedence: i32 = -1; - while (analyzer.token_i < analyzer.tokens.len) { - const token = analyzer.tokens[analyzer.token_i]; + while (analyzer.hasTokens()) { + const token = analyzer.peekToken(); // logln("Looping in expression precedence with token {}", .{token}); - const operator: PrecedenceOperator = switch (token.id) { - .semicolon, - .right_parenthesis, - .right_brace, - .right_bracket, - .comma, + const operator: PrecedenceOperator = switch (token) { + .operator_semicolon, + .operator_right_parenthesis, + .operator_right_brace, + .operator_right_bracket, + .operator_comma, + .operator_colon, + .operator_assign, + .operator_add_assign, + .operator_sub_assign, + .operator_mul_assign, + .operator_div_assign, + .operator_mod_assign, + .operator_dot, + .operator_switch_case, .fixed_keyword_const, .fixed_keyword_var, .fixed_keyword_return, - .identifier, - .colon, .fixed_keyword_if, .fixed_keyword_else, + .identifier, .discard, => break, - else => blk: { - const next_token_index = analyzer.token_i + 1; - if (next_token_index < analyzer.tokens.len) { - const next_token_id = analyzer.tokens[next_token_index].id; - const next_to_next_token_id = analyzer.tokens[next_token_index + 1].id; - break :blk switch (token.id) { - .equal => switch (next_token_id) { - .equal => .compare_equal, - else => break, - }, - .bang => switch (next_token_id) { - .equal => .compare_not_equal, - else => unreachable, - }, - .plus => switch (next_token_id) { - .plus => unreachable, - .equal => break, - else => .add, - }, - .minus => switch (next_token_id) { - .minus => unreachable, - .equal => unreachable, - else => .sub, - }, - .ampersand => switch (next_token_id) { - .equal => unreachable, - else => .bit_and, - }, - .caret => switch (next_token_id) { - .equal => unreachable, - else => .bit_xor, - }, - .vertical_bar => switch (next_token_id) { - .equal => unreachable, - else => .bit_or, - }, - .asterisk => switch (next_token_id) { - .equal => unreachable, - else => .multiply, - }, - .slash => switch (next_token_id) { - .equal => unreachable, - else => .divide, - }, - .less => switch (next_token_id) { - .less => switch (next_to_next_token_id) { - .equal => unreachable, - else => .shift_left, - }, - .equal => .compare_less_or_equal, - else => .compare_less_than, - }, - .greater => switch (next_token_id) { - .greater => .shift_right, - .equal => .compare_greater_or_equal, - else => .compare_greater_than, - }, - .period => switch (next_token_id) { - .period => break, - else => break, - }, - else => |t| @panic(@tagName(t)), - }; - } else { - unreachable; - } - }, + .operator_compare_equal => .compare_equal, + .operator_compare_not_equal => .compare_not_equal, + .operator_compare_less => .compare_less, + .operator_compare_greater => .compare_greater, + .operator_compare_less_equal => .compare_less_equal, + .operator_compare_greater_equal => .compare_greater_equal, + .operator_add => .add, + .operator_minus => .sub, + .operator_asterisk => .mul, + .operator_div => .div, + .operator_mod => .mod, + .operator_ampersand => .bit_and, + .operator_bar => .bit_or, + .operator_xor => .bit_xor, + .operator_shift_left => .shift_left, + .operator_shift_right => .shift_right, + else => |t| @panic(@tagName(t)), }; + logln(.parser, .precedence, "Precedence operator: {s}", .{@tagName(operator)}); const precedence = operator_precedence.get(operator); @@ -1107,27 +1036,7 @@ const Analyzer = struct { } const operator_token = analyzer.token_i; - const extra_tokens: u32 = switch (operator) { - .add, - .sub, - .bit_and, - .bit_xor, - .bit_or, - .multiply, - .divide, - .compare_less_than, - .compare_greater_than, - => 0, - .compare_equal, - .compare_not_equal, - .compare_less_or_equal, - .compare_greater_or_equal, - .shift_right, - .shift_left, - => 1, - // else => |t| @panic(@tagName(t)), - }; - analyzer.consumeTokens(@as(u32, 1) + extra_tokens); + analyzer.consumeToken(); // TODO: fix this logln(.parser, .precedence, "Going for right in expressionPrecedence with operator {s}", .{@tagName(operator)}); @@ -1155,17 +1064,14 @@ const Analyzer = struct { fn prefixExpression(analyzer: *Analyzer) !Node.Index { const token = analyzer.token_i; // logln("Prefix...", .{}); - const node_id: Node.Id = switch (analyzer.tokens[token].id) { + const node_id: Node.Id = switch (analyzer.peekToken()) { else => |pref| { _ = pref; return try analyzer.primaryExpression(); }, - .bang => switch (analyzer.tokens[token + 1].id) { - .equal => return try analyzer.primaryExpression(), - else => .boolean_not, - }, - .minus => .negation, - .tilde => |t| @panic(@tagName(t)), + .operator_bang => .boolean_not, + .operator_minus => .negation, + // .tilde => |t| @panic(@tagName(t)), }; return try analyzer.addNode(.{ @@ -1175,26 +1081,27 @@ const Analyzer = struct { break :blk token; }, .left = try analyzer.prefixExpression(), - .right = Node.Index.invalid, + .right = Node.Index.null, }); } fn primaryExpression(analyzer: *Analyzer) !Node.Index { - const result = switch (analyzer.tokens[analyzer.token_i].id) { - .identifier => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .colon => unreachable, + const result = switch (analyzer.peekToken()) { + .identifier => switch (analyzer.peekTokenAhead(1)) { + .operator_colon => unreachable, else => try analyzer.curlySuffixExpression(), }, .string_literal, + .character_literal, .number_literal, + .intrinsic, .fixed_keyword_true, .fixed_keyword_false, .fixed_keyword_unreachable, .fixed_keyword_null, .fixed_keyword_switch, - .hash, - .period, - .left_parenthesis, + .operator_dot, + .operator_left_parenthesis, .keyword_signed_integer, .keyword_unsigned_integer, .fixed_keyword_ssize, @@ -1203,7 +1110,7 @@ const Analyzer = struct { .fixed_keyword_struct, .discard, .fixed_keyword_undefined, - .left_bracket, + .operator_left_bracket, => try analyzer.curlySuffixExpression(), .fixed_keyword_fn => try analyzer.function(), .fixed_keyword_return => try analyzer.addNode(.{ @@ -1214,10 +1121,10 @@ const Analyzer = struct { break :blk token; }, .left = try analyzer.expression(), - .right = Node.Index.invalid, + .right = Node.Index.null, }), // todo:? - .left_brace => try analyzer.block(.{ .is_comptime = false }), + .operator_left_brace => try analyzer.block(), .fixed_keyword_if => try analyzer.ifExpression(), else => |id| std.debug.panic("WARN: By default, calling curlySuffixExpression with {s}", .{@tagName(id)}), }; @@ -1228,36 +1135,38 @@ const Analyzer = struct { fn curlySuffixExpression(analyzer: *Analyzer) !Node.Index { const left = try analyzer.typeExpression(); - return switch (analyzer.tokens[analyzer.token_i].id) { - .left_brace => try analyzer.containerLiteral(left), + return switch (analyzer.peekToken()) { + .operator_left_brace => try analyzer.containerLiteral(left), else => left, }; } fn noReturn(analyzer: *Analyzer) !Node.Index { const token_i = analyzer.token_i; - assert(analyzer.tokens[token_i].id == .fixed_keyword_noreturn); + assert(analyzer.peekToken() == .fixed_keyword_noreturn); analyzer.consumeToken(); return try analyzer.addNode(.{ .id = .keyword_noreturn, .token = token_i, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }); } fn boolLiteral(analyzer: *Analyzer) !Node.Index { - const token_i = analyzer.token_i; - analyzer.consumeToken(); return try analyzer.addNode(.{ - .id = switch (analyzer.tokens[token_i].id) { + .id = switch (analyzer.peekToken()) { .fixed_keyword_true => .keyword_true, .fixed_keyword_false => .keyword_false, else => unreachable, }, - .token = token_i, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .token = blk: { + const token_i = analyzer.token_i; + analyzer.consumeToken(); + break :blk token_i; + }, + .left = Node.Index.null, + .right = Node.Index.null, }); } @@ -1268,11 +1177,12 @@ const Analyzer = struct { }; fn parseTermination(analyzer: *Analyzer) !Node.Index { - _ = try analyzer.expectToken(.colon); - const token = analyzer.tokens[analyzer.token_i]; - const termination_id: Node.Id = switch (token.id) { + _ = try analyzer.expectToken(.operator_colon); + const token_i = analyzer.token_i; + const token = analyzer.peekToken(); + const termination_id: Node.Id = switch (token) { .fixed_keyword_null => .null_terminated, - .number_literal => switch (std.zig.parseNumberLiteral(analyzer.source_file[token.start..][0..token.len])) { + .number_literal => switch (std.zig.parseNumberLiteral(analyzer.bytes(token_i))) { .int => |integer| switch (integer) { 0 => .zero_terminated, else => @panic("Invalid number literal terminator"), @@ -1284,9 +1194,9 @@ const Analyzer = struct { const termination_node_index = try analyzer.addNode(.{ .id = termination_id, - .token = analyzer.token_i, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .token = token_i, + .left = Node.Index.null, + .right = Node.Index.null, }); analyzer.consumeToken(); @@ -1294,10 +1204,9 @@ const Analyzer = struct { } fn pointerOrArrayTypeExpression(analyzer: *Analyzer, expected: PointerOrArrayTypeExpectedExpression) !Node.Index { - logln(.parser, .pointer_like_type_expression, "Pointer start", .{}); const first = analyzer.token_i; - var list = Node.List{}; + var list = ArrayList(Node.Index){}; const expression_type: Node.Id = switch (expected) { .single_pointer_type => blk: { @@ -1309,43 +1218,43 @@ const Analyzer = struct { try list.append(analyzer.allocator, try analyzer.addNode(.{ .id = .many_pointer_expression, .token = analyzer.token_i, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, })); - _ = try analyzer.expectToken(.left_bracket); - _ = try analyzer.expectToken(.ampersand); - switch (analyzer.tokens[analyzer.token_i].id) { - .right_bracket => {}, - .colon => try list.append(analyzer.allocator, try analyzer.parseTermination()), + _ = try analyzer.expectToken(.operator_left_bracket); + _ = try analyzer.expectToken(.operator_ampersand); + switch (analyzer.peekToken()) { + .operator_right_bracket => {}, + .operator_colon => try list.append(analyzer.allocator, try analyzer.parseTermination()), else => |t| @panic(@tagName(t)), } - _ = try analyzer.expectToken(.right_bracket); + _ = try analyzer.expectToken(.operator_right_bracket); break :blk .pointer_type; }, .array_or_slice_type => blk: { - _ = try analyzer.expectToken(.left_bracket); - switch (analyzer.tokens[analyzer.token_i].id) { - .right_bracket => { + _ = try analyzer.expectToken(.operator_left_bracket); + switch (analyzer.peekToken()) { + .operator_right_bracket => { analyzer.consumeToken(); break :blk .slice_type; }, - .colon => { + .operator_colon => { try list.append(analyzer.allocator, try analyzer.parseTermination()); - _ = try analyzer.expectToken(.right_bracket); + _ = try analyzer.expectToken(.operator_right_bracket); break :blk .slice_type; }, else => { const length_expression = try analyzer.expression(); try list.append(analyzer.allocator, length_expression); - switch (analyzer.tokens[analyzer.token_i].id) { - .right_bracket => {}, - .colon => try list.append(analyzer.allocator, try analyzer.parseTermination()), + switch (analyzer.peekToken()) { + .operator_right_bracket => {}, + .operator_colon => try list.append(analyzer.allocator, try analyzer.parseTermination()), else => |t| @panic(@tagName(t)), } - _ = try analyzer.expectToken(.right_bracket); + _ = try analyzer.expectToken(.operator_right_bracket); break :blk .array_type; }, @@ -1354,18 +1263,18 @@ const Analyzer = struct { }; if (expression_type != .array_type) { - const const_node = switch (analyzer.tokens[analyzer.token_i].id) { + const const_node = switch (analyzer.peekToken()) { .fixed_keyword_const => try analyzer.addNode(.{ .id = .const_expression, .token = analyzer.token_i, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), - else => Node.Index.invalid, + else => Node.Index.null, }; - analyzer.consumeTokens(@intFromBool(analyzer.tokens[analyzer.token_i].id == .fixed_keyword_const)); + analyzer.consumeTokens(@intFromBool(analyzer.peekToken() == .fixed_keyword_const)); - if (!const_node.invalid) { + if (const_node != .null) { try list.append(analyzer.allocator, const_node); } } else { @@ -1373,7 +1282,7 @@ const Analyzer = struct { } const type_expression = try analyzer.typeExpression(); - assert(!type_expression.invalid); + assert(type_expression != .null); try list.append(analyzer.allocator, type_expression); const node_list = try analyzer.nodeList(list); @@ -1382,12 +1291,12 @@ const Analyzer = struct { .id = expression_type, .token = first, .left = node_list, - .right = Node.Index.invalid, + .right = Node.Index.null, }; logln(.parser, .pointer_like_type_expression, "ARRAY START\n===========", .{}); for (list.items) |ni| { - const n = analyzer.nodes.items[ni.unwrap()]; + const n = analyzer.nodes.get(ni); logln(.parser, .pointer_like_type_expression, "{s} node element: {s}", .{ @tagName(expression_type), @tagName(n.id) }); } logln(.parser, .pointer_like_type_expression, "ARRAY END\n=========", .{}); @@ -1395,12 +1304,11 @@ const Analyzer = struct { const node_index = try analyzer.addNode(node); logln(.parser, .pointer_like_type_expression, "Pointer end", .{}); - switch (analyzer.tokens[analyzer.token_i].id) { - .comma, - .right_parenthesis, - .left_brace, - .equal, - .fixed_keyword_extern, + switch (analyzer.peekToken()) { + .operator_comma, + .operator_right_parenthesis, + .operator_left_brace, + .operator_assign, => return node_index, else => |t| @panic(@tagName(t)), } @@ -1410,22 +1318,22 @@ const Analyzer = struct { fn typeExpression(analyzer: *Analyzer) anyerror!Node.Index { const first = analyzer.token_i; - return switch (analyzer.tokens[first].id) { + return switch (analyzer.peekToken()) { else => try analyzer.errorUnionExpression(), - .question_mark => blk: { + .operator_optional => blk: { analyzer.consumeToken(); break :blk try analyzer.addNode(.{ .id = .optional_type, .token = first, .left = try analyzer.typeExpression(), - .right = Node.Index.invalid, + .right = Node.Index.null, }); }, - .ampersand => try analyzer.pointerOrArrayTypeExpression(.single_pointer_type), - .bang => unreachable, // error - .left_bracket => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .ampersand => try analyzer.pointerOrArrayTypeExpression(.many_pointer_type), - .asterisk => @panic("Meant to use ampersand?"), + .operator_ampersand => try analyzer.pointerOrArrayTypeExpression(.single_pointer_type), + .operator_bang => unreachable, // error + .operator_left_bracket => switch (analyzer.peekTokenAhead(1)) { + .operator_ampersand => try analyzer.pointerOrArrayTypeExpression(.many_pointer_type), + .operator_asterisk => @panic("Meant to use ampersand?"), else => try analyzer.pointerOrArrayTypeExpression(.array_or_slice_type), }, }; @@ -1434,11 +1342,8 @@ const Analyzer = struct { fn errorUnionExpression(analyzer: *Analyzer) !Node.Index { const suffix_expression = try analyzer.suffixExpression(); - return switch (analyzer.tokens[analyzer.token_i].id) { - .bang => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .equal => suffix_expression, - else => unreachable, - }, + return switch (analyzer.peekToken()) { + .operator_bang => unreachable, else => suffix_expression, }; } @@ -1450,22 +1355,22 @@ const Analyzer = struct { while (true) { const suffix_operator = try analyzer.suffixOperator(result); - if (!suffix_operator.invalid) { + if (suffix_operator != .null) { result = suffix_operator; } else { - if (analyzer.tokens[analyzer.token_i].id == .left_parenthesis) { + if (analyzer.peekToken() == .operator_left_parenthesis) { const left_parenthesis = analyzer.token_i; analyzer.consumeToken(); var expression_list = ArrayList(Node.Index){}; logln(.parser, .suffix, "[DEPTH={}] Initializating suffix call-like expression", .{analyzer.suffix_depth}); - while (analyzer.tokens[analyzer.token_i].id != .right_parenthesis) { + while (analyzer.peekToken() != .operator_right_parenthesis) { const current_token = analyzer.token_i; - logln(.parser, .suffix, "[DEPTH={}] First token: {s}", .{ analyzer.suffix_depth, @tagName(analyzer.tokens[current_token].id) }); + // logln(.parser, .suffix, "[DEPTH={}] First token: {s}", .{ analyzer.suffix_depth, @tagName(analyzer.tokens[current_token].id) }); var parameter = try analyzer.expression(); - const parameter_node = analyzer.nodes.items[parameter.unwrap()]; - logln(.parser, .suffix, "[DEPTH={}] Parameter node: {s}", .{ analyzer.suffix_depth, @tagName(parameter_node.id) }); - if (analyzer.tokens[analyzer.token_i].id == .equal) { + // const parameter_node = analyzer.nodes.items[parameter.unwrap()]; + // logln(.parser, .suffix, "[DEPTH={}] Parameter node: {s}", .{ analyzer.suffix_depth, @tagName(parameter_node.id) }); + if (analyzer.peekToken() == .operator_assign) { analyzer.consumeToken(); parameter = try analyzer.addNode(.{ @@ -1475,19 +1380,21 @@ const Analyzer = struct { .right = try analyzer.expression(), }); } + try expression_list.append(analyzer.allocator, parameter); - switch (analyzer.tokens[analyzer.token_i].id) { - .comma => analyzer.consumeToken(), - .right_parenthesis => {}, - .colon, .right_brace, .right_bracket => unreachable, - .period => panic("[DEPTH={}] Unexpected period", .{analyzer.suffix_depth}), + + switch (analyzer.peekToken()) { + .operator_right_parenthesis => {}, + .operator_comma => analyzer.consumeToken(), + .operator_colon, .operator_right_brace, .operator_right_bracket => unreachable, + .operator_dot => panic("[DEPTH={}] Unexpected period", .{analyzer.suffix_depth}), else => |t| @panic(@tagName(t)), } } logln(.parser, .suffix, "[DEPTH={}] Ending suffix call-like expression", .{analyzer.suffix_depth}); - _ = try analyzer.expectToken(.right_parenthesis); + _ = try analyzer.expectToken(.operator_right_parenthesis); // const is_comma = analyzer.tokens[analyzer.token_i].id == .comma; result = try analyzer.addNode(.{ .id = .call, @@ -1505,7 +1412,7 @@ const Analyzer = struct { } fn containerLiteral(analyzer: *Analyzer, type_node: Node.Index) anyerror!Node.Index { - const token = try analyzer.expectToken(.left_brace); + const token = try analyzer.expectToken(.operator_left_brace); var list = ArrayList(Node.Index){}; @@ -1520,12 +1427,12 @@ const Analyzer = struct { var current_initialization: ?InitializationType = null; - while (analyzer.tokens[analyzer.token_i].id != .right_brace) { + while (analyzer.peekToken() != .operator_right_brace) { const start_token = analyzer.token_i; - const iteration_initialization_type: InitializationType = switch (analyzer.tokens[start_token].id) { - .period => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .identifier => switch (analyzer.tokens[analyzer.token_i + 2].id) { - .equal => blk: { + const iteration_initialization_type: InitializationType = switch (analyzer.peekToken()) { + .operator_dot => switch (analyzer.peekTokenAhead(1)) { + .identifier => switch (analyzer.peekTokenAhead(2)) { + .operator_assign => blk: { analyzer.consumeTokens(3); const field_expression_initializer = try analyzer.expression(); @@ -1533,11 +1440,11 @@ const Analyzer = struct { .id = .container_field_initialization, .token = start_token, .left = field_expression_initializer, - .right = Node.Index.invalid, + .right = Node.Index.null, }); try list.append(analyzer.allocator, field_initialization); - _ = try analyzer.expectToken(.comma); + _ = try analyzer.expectToken(.operator_comma); break :blk .container_field_names; }, @@ -1545,19 +1452,19 @@ const Analyzer = struct { }, else => blk: { try list.append(analyzer.allocator, try analyzer.anonymousExpression()); - _ = try analyzer.expectToken(.comma); + _ = try analyzer.expectToken(.operator_comma); break :blk .anonymous; }, }, .string_literal, .identifier, .number_literal, - .hash, + .intrinsic, .fixed_keyword_if, => blk: { const field_expression_initializer = try analyzer.expression(); - switch (analyzer.tokens[analyzer.token_i].id) { - .comma => analyzer.consumeToken(), + switch (analyzer.peekToken()) { + .operator_comma => analyzer.consumeToken(), else => {}, } @@ -1576,11 +1483,11 @@ const Analyzer = struct { current_initialization = iteration_initialization_type; } - _ = try analyzer.expectToken(.right_brace); + _ = try analyzer.expectToken(.operator_right_brace); - const initialization: InitializationType = current_initialization orelse switch (type_node.invalid) { - true => .empty_literal, - false => switch (analyzer.nodes.items[type_node.unwrap()].id) { + const initialization: InitializationType = current_initialization orelse switch (type_node) { + .null => .empty_literal, + else => switch (analyzer.nodes.get(type_node).id) { .identifier => .empty_container_literal_guess, .array_type => .empty_array_literal, else => |t| @panic(@tagName(t)), @@ -1588,14 +1495,14 @@ const Analyzer = struct { }; return try analyzer.addNode(.{ - .id = switch (type_node.invalid) { - true => switch (initialization) { + .id = switch (type_node) { + .null => switch (initialization) { .container_field_names => .anonymous_container_literal, .empty_literal => .anonymous_empty_literal, .anonymous => .anonymous_array_literal, else => |t| @panic(@tagName(t)), }, - false => switch (initialization) { + else => switch (initialization) { .container_field_names => .container_literal, .empty_container_literal_guess => .empty_container_literal_guess, .anonymous => .array_literal, @@ -1611,21 +1518,146 @@ const Analyzer = struct { fn discardNode(analyzer: *Analyzer) !Node.Index { const token = analyzer.token_i; - assert(analyzer.tokens[token].id == .discard); + assert(analyzer.peekToken() == .discard); analyzer.consumeToken(); return try analyzer.addNode(.{ .id = .discard, .token = token, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, + }); + } + + fn processContainerType(analyzer: *Analyzer, maybe_token_id: ?Token.Id) !Node.Index{ + const token_i = if (maybe_token_id) |tid| try analyzer.expectToken(tid) else analyzer.token_i; + const token_id = maybe_token_id orelse .fixed_keyword_struct; + const container_type: Compilation.ContainerType = switch (token_id) { + .fixed_keyword_struct => .@"struct", + .fixed_keyword_enum => .@"enum", + else => unreachable, + }; + + const node_id: Node.Id = switch (token_id) { + .fixed_keyword_struct => .@"struct_type", + .fixed_keyword_enum => .@"enum_type", + else => unreachable, + }; + + const type_node = if (analyzer.peekToken() == .operator_left_parenthesis) b: { + analyzer.consumeToken(); + const result = try analyzer.typeExpression(); + _ = try analyzer.expectToken(.operator_right_parenthesis); + break :b result; + } else Node.Index.null; + + if (maybe_token_id) |_| _ = try analyzer.expectToken(.operator_left_brace); + var node_list = ArrayList(Node.Index){}; + + while (analyzer.hasTokens() and analyzer.peekToken() != .operator_right_brace) { + const first = analyzer.token_i; + logln(.parser, .container_members, "First token for container member: {s}", .{@tagName(analyzer.peekToken())}); + + const member_node_index: Node.Index = switch (analyzer.peekToken()) { + .fixed_keyword_comptime => switch (analyzer.peekTokenAhead(1)) { + .operator_left_brace => b: { + analyzer.consumeToken(); + const comptime_block = try analyzer.block(); + + break :b try analyzer.addNode(.{ + .id = .@"comptime", + .token = first, + .left = comptime_block, + .right = Node.Index.null, + }); + }, + else => |t| @panic(@tagName(t)), + }, + .identifier => b: { + analyzer.consumeToken(); + + switch (container_type) { + .@"struct" => { + _ = try analyzer.expectToken(.operator_colon); + + const field_type = try analyzer.typeExpression(); + + const field_default_node = if (analyzer.peekToken() == .operator_assign) f: { + analyzer.consumeToken(); + const default_index = try analyzer.expression(); + const default_node = analyzer.nodes.get(default_index); + assert(default_node.id != .node_list); + break :f default_index; + } else Node.Index.null; + + _ = try analyzer.expectToken(.operator_comma); + + const field_node = try analyzer.addNode(.{ + .id = .container_field, + .token = first, + .left = field_type, + .right = field_default_node, + }); + + break :b field_node; + }, + .@"enum" => { + const value_associated = switch (analyzer.peekToken()) { + .operator_comma => Node.Index.null, + else => value: { + analyzer.consumeToken(); + break :value try analyzer.expression(); + }, + }; + + _ = try analyzer.expectToken(.operator_comma); + + const enum_field_node = try analyzer.addNode(.{ + .id = .enum_field, + .token = first, + .left = value_associated, + .right = Node.Index.null, + }); + + break :b enum_field_node; + }, + } + }, + .fixed_keyword_const, .fixed_keyword_var => try analyzer.symbolDeclaration(), + else => |t| @panic(@tagName(t)), + }; + + const member_node = analyzer.nodes.get(member_node_index); + logln(.parser, .container_members, "Container member {s}", .{@tagName(member_node.id)}); + assert(member_node.id != .identifier); + + try node_list.append(analyzer.allocator, member_node_index); + } + + if (maybe_token_id) |_| _ = try analyzer.expectToken(.operator_right_brace); + + for (node_list.items, 0..) |member_node_index, index| { + const member_node = analyzer.nodes.get(member_node_index); + if (member_node.id == .identifier) { + const token_offset = analyzer.getTokenOffset(member_node.token); + std.debug.print("Node index #{} (list index {}):\n```\n{s}\n```\n", .{Node.unwrap(member_node_index), index, analyzer.source_file[token_offset..]}); + // std.debug.print("ID: {s}\n", .{analyzer.bytes(member_node.token)}); + unreachable; + } + } + + return try analyzer.addNode(.{ + .id = node_id, + .token = token_i, + .left = try analyzer.nodeList(node_list), + .right = type_node, }); } fn primaryTypeExpression(analyzer: *Analyzer) anyerror!Node.Index { const token_i = analyzer.token_i; - const token = analyzer.tokens[token_i]; + const token = analyzer.peekToken(); - return try switch (token.id) { + return try switch (token) { .fixed_keyword_fn => blk: { analyzer.consumeToken(); break :blk analyzer.functionPrototype(); @@ -1635,8 +1667,17 @@ const Analyzer = struct { break :blk analyzer.addNode(.{ .id = .string_literal, .token = token_i, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, + }); + }, + .character_literal => blk: { + analyzer.consumeToken(); + break :blk analyzer.addNode(.{ + .id = .character_literal, + .token = token_i, + .left = Node.Index.null, + .right = Node.Index.null, }); }, .number_literal => blk: { @@ -1644,8 +1685,8 @@ const Analyzer = struct { break :blk analyzer.addNode(.{ .id = .number_literal, .token = token_i, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }); }, .identifier => analyzer.identifierNode(), @@ -1658,8 +1699,8 @@ const Analyzer = struct { analyzer.consumeToken(); break :blk token_i; }, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), .fixed_keyword_null => analyzer.addNode(.{ .id = .null_literal, @@ -1667,8 +1708,8 @@ const Analyzer = struct { analyzer.consumeToken(); break :blk token_i; }, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), .fixed_keyword_unreachable => analyzer.addNode(.{ .id = .@"unreachable", @@ -1676,18 +1717,18 @@ const Analyzer = struct { analyzer.consumeToken(); break :blk token_i; }, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), - .hash => analyzer.compilerIntrinsic(), + .intrinsic => analyzer.compilerIntrinsic(), .fixed_keyword_bool => analyzer.addNode(.{ .id = .bool_type, .token = blk: { analyzer.consumeToken(); break :blk token_i; }, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), .keyword_unsigned_integer, .keyword_signed_integer => |signedness| analyzer.addNode(.{ .id = switch (signedness) { @@ -1699,12 +1740,12 @@ const Analyzer = struct { analyzer.consumeToken(); break :blk token_i; }, - .left = @bitCast(@as(u32, try std.fmt.parseInt(u16, b: { + .left = @enumFromInt(@as(u32, try std.fmt.parseInt(u16, b: { const slice = analyzer.bytes(token_i)[1..]; if (slice.len == 0) unreachable; break :b slice; }, 10))), - .right = Node.Index.invalid, + .right = Node.Index.null, }), .fixed_keyword_usize, .fixed_keyword_ssize => |size_type| analyzer.addNode(.{ .id = switch (size_type) { @@ -1716,8 +1757,8 @@ const Analyzer = struct { analyzer.consumeToken(); break :blk token_i; }, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), .fixed_keyword_void => analyzer.addNode(.{ .id = .void_type, @@ -1725,63 +1766,17 @@ const Analyzer = struct { analyzer.consumeToken(); break :blk token_i; }, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), .fixed_keyword_switch => try analyzer.switchExpression(), - .period => try analyzer.anonymousExpression(), - .fixed_keyword_enum => blk: { - analyzer.consumeToken(); - - // TODO: is this the best way? - if (analyzer.tokens[analyzer.token_i].id == .left_parenthesis) { - analyzer.consumeToken(); - assert(analyzer.tokens[analyzer.token_i + 1].id == .right_parenthesis); - analyzer.consumeTokens(2); - } - - _ = try analyzer.expectToken(.left_brace); - const node_list = try analyzer.containerMembers(.@"enum"); - _ = try analyzer.expectToken(.right_brace); - - break :blk try analyzer.addNode(.{ - .id = .enum_type, - .token = token_i, - .left = try analyzer.nodeList(node_list), - .right = Node.Index.invalid, - }); - }, - .fixed_keyword_struct => blk: { - analyzer.consumeToken(); - - // TODO: is this the best way? - if (analyzer.tokens[analyzer.token_i].id == .left_parenthesis) { - analyzer.consumeToken(); - assert(analyzer.tokens[analyzer.token_i + 1].id == .right_parenthesis); - analyzer.consumeTokens(2); - } - - _ = try analyzer.expectToken(.left_brace); - const node_list = try analyzer.containerMembers(.@"struct"); - _ = try analyzer.expectToken(.right_brace); - - break :blk try analyzer.addNode(.{ - .id = .struct_type, - .token = token_i, - .left = try analyzer.nodeList(node_list), - .right = Node.Index.invalid, - }); - }, - .left_parenthesis => blk: { + .operator_dot => try analyzer.anonymousExpression(), + .fixed_keyword_enum, .fixed_keyword_struct => try analyzer.processContainerType(token), + .operator_left_parenthesis => blk: { analyzer.consumeToken(); const expr = try analyzer.expression(); - _ = try analyzer.expectToken(.right_parenthesis); - break :blk try analyzer.addNode(.{ - .id = .expression_group, - .token = token_i, - .left = expr, - .right = Node.Index.invalid, - }); + _ = try analyzer.expectToken(.operator_right_parenthesis); + break :blk expr; }, else => |t| switch (t) { .identifier => std.debug.panic("{s}: {s}", .{ @tagName(t), analyzer.bytes(token_i) }), @@ -1792,18 +1787,18 @@ const Analyzer = struct { fn anonymousExpression(analyzer: *Analyzer) !Node.Index { const token_i = analyzer.token_i; - _ = try analyzer.expectToken(.period); - return switch (analyzer.tokens[analyzer.token_i].id) { + _ = try analyzer.expectToken(.operator_dot); + return switch (analyzer.peekToken()) { .identifier => try analyzer.addNode(.{ .id = .enum_literal, .token = blk: { analyzer.consumeToken(); break :blk token_i; }, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }), - .left_brace => try analyzer.containerLiteral(Node.Index.invalid), + .operator_left_brace => try analyzer.containerLiteral(Node.Index.null), else => |t| @panic(@tagName(t)), }; } @@ -1811,19 +1806,19 @@ const Analyzer = struct { // TODO: fn suffixOperator(analyzer: *Analyzer, left: Node.Index) !Node.Index { const token = analyzer.token_i; - const result: Node.Index = switch (analyzer.tokens[token].id) { - .left_bracket => blk: { + const result: Node.Index = switch (analyzer.peekToken()) { + .operator_left_bracket => blk: { analyzer.consumeToken(); const index_expression = try analyzer.expression(); - if (analyzer.tokens[analyzer.token_i].id == .period and analyzer.token_i + 1 < analyzer.tokens.len and analyzer.tokens[analyzer.token_i + 1].id == .period) { + if (analyzer.peekToken() == .operator_dot and analyzer.peekTokenAhead(1) == .operator_dot) { analyzer.consumeTokens(2); - const range_end_expression = switch (analyzer.tokens[analyzer.token_i].id) { - .right_bracket => Node.Index.invalid, + const range_end_expression = switch (analyzer.peekToken()) { + .operator_right_bracket => Node.Index.null, else => try analyzer.expression(), }; - _ = try analyzer.expectToken(.right_bracket); + _ = try analyzer.expectToken(.operator_right_bracket); break :blk try analyzer.addNode(.{ .id = .slice, @@ -1837,7 +1832,7 @@ const Analyzer = struct { }), }); } else { - _ = try analyzer.expectToken(.right_bracket); + _ = try analyzer.expectToken(.operator_right_bracket); break :blk try analyzer.addNode(.{ .id = .indexed_access, .token = token, @@ -1846,7 +1841,7 @@ const Analyzer = struct { }); } }, - .period => switch (analyzer.tokens[analyzer.token_i + 1].id) { + .operator_dot => switch (analyzer.peekTokenAhead(1)) { .identifier => try analyzer.addNode(.{ .id = .field_access, .token = blk: { @@ -1854,77 +1849,79 @@ const Analyzer = struct { break :blk token; }, .left = left, - .right = blk: { - //TODO ??? - const result: Node.Index = @bitCast(analyzer.token_i); - analyzer.consumeToken(); - logln(.parser, .suffix, "WARNING: rhs has node index {} but it's token #{}", .{ result, token }); - break :blk result; - }, + .right = try analyzer.addNode(.{ + .id = .identifier, + .token = blk: { + const t = analyzer.token_i; + analyzer.consumeToken(); + break :blk t; + }, + .left = Node.Index.null, + .right = Node.Index.null, + }), }), - .period => Node.Index.invalid, - .ampersand => try analyzer.addNode(.{ + .operator_dot => Node.Index.null, + .operator_ampersand => try analyzer.addNode(.{ .id = .address_of, .token = blk: { analyzer.consumeTokens(2); break :blk token; }, .left = left, - .right = Node.Index.invalid, + .right = Node.Index.null, }), - .at => try analyzer.addNode(.{ + .operator_at => try analyzer.addNode(.{ .id = .pointer_dereference, .token = blk: { analyzer.consumeTokens(2); break :blk token; }, .left = left, - .right = Node.Index.invalid, + .right = Node.Index.null, }), - .question_mark => try analyzer.addNode(.{ + .operator_optional => try analyzer.addNode(.{ .id = .optional_unwrap, .token = blk: { analyzer.consumeToken(); break :blk token; }, .left = left, - .right = .{ - .value = @intCast(blk: { - const t = analyzer.token_i; - analyzer.consumeToken(); - break :blk t; - }), + .right = blk: { + const t = analyzer.token_i; + analyzer.consumeToken(); + break :blk Node.wrap(Token.unwrap(t)); }, }), else => |t| @panic(@tagName(t)), }, - else => Node.Index.invalid, + else => Node.Index.null, }; return result; } fn addNode(analyzer: *Analyzer, node: Node) !Node.Index { - const index = analyzer.nodes.items.len; - try analyzer.nodes.append(analyzer.allocator, node); - // if (index == 38 or index == 37) { - // @breakpoint(); + const node_index = try analyzer.nodes.append(analyzer.allocator, node); + logln(.parser, .node_creation, "Adding node #{} {s} to file #{} (left: {}, right: {})", .{ Node.unwrap(node_index), @tagName(node.id), File.unwrap(analyzer.file_index), switch (node.left) { + .null => 0xffff_ffff, + else => Node.unwrap(node.left), + }, switch (node.right) { + .null => 0xffff_ffff, + else => Node.unwrap(node.right), + }}); + // if (Logger.bitset.contains(.node_creation_detailed)) { + // const chunk_start = analyzer.lexer.offsets.items[node.token]; + // const chunk_end = analyzer.lexer.offsets.items[node.token + 1]; + // const chunk_from_start = analyzer.source_file[chunk_start..]; + // const end = @min(200, chunk_end - chunk_start); + // const chunk = chunk_from_start[0..end]; + // logln(.parser, .node_creation, "[SOURCE]: ```\n{s}\n```\n", .{chunk}); // } - logln(.parser, .node_creation, "Adding node #{} (0x{x}) {s} to file #{}", .{ index, @intFromPtr(&analyzer.nodes.items[index]), @tagName(node.id), analyzer.file_index.uniqueInteger() }); - if (Logger.bitset.contains(.node_creation_detailed)) { - const chunk_start = analyzer.tokens[node.token].start; - const chunk_from_start = analyzer.source_file[chunk_start..]; - const chunk_end = @min(200, chunk_from_start.len); - const chunk = chunk_from_start[0..chunk_end]; - logln(.parser, .node_creation, "[SOURCE]: ```\n{s}\n```\n", .{chunk}); - } // if (node.id == .identifier) { // logln("Node identifier: {s}", .{analyzer.bytes(node.token)}); // } - return Node.Index{ - .value = @intCast(index), - }; + return node_index; } fn nodeList(analyzer: *Analyzer, node_list: ArrayList(Node.Index)) !Node.Index { @@ -1932,145 +1929,45 @@ const Analyzer = struct { try analyzer.node_lists.append(analyzer.allocator, node_list); return try analyzer.addNode(.{ .id = .node_list, - .token = 0, - .left = .{ .value = @intCast(index) }, - .right = Node.Index.invalid, + .token = Token.wrap(0), + .left = @enumFromInt(index), + .right = Node.Index.null, }); } fn identifierNode(analyzer: *Analyzer) !Node.Index { const identifier_token = analyzer.token_i; - assert(analyzer.tokens[identifier_token].id == .identifier); + assert(analyzer.peekToken() == .identifier); analyzer.consumeToken(); return try analyzer.addNode(.{ .id = .identifier, .token = identifier_token, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + .left = Node.Index.null, + .right = Node.Index.null, }); } - - fn containerMembers(analyzer: *Analyzer, comptime container_type: Compilation.ContainerType) !ArrayList(Node.Index) { - var list = ArrayList(Node.Index){}; - while (analyzer.token_i < analyzer.tokens.len and analyzer.tokens[analyzer.token_i].id != .right_brace) { - const first = analyzer.token_i; - logln(.parser, .container_members, "First token for container member: {s}", .{@tagName(analyzer.tokens[first].id)}); - - const member_node_index: Node.Index = switch (analyzer.tokens[first].id) { - .fixed_keyword_comptime => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .left_brace => blk: { - analyzer.consumeToken(); - const comptime_block = try analyzer.block(.{ .is_comptime = true }); - - break :blk try analyzer.addNode(.{ - .id = .@"comptime", - .token = first, - .left = comptime_block, - .right = Node.Index.invalid, - }); - }, - else => |t| @panic(@tagName(t)), - }, - .identifier => blk: { - analyzer.consumeToken(); - switch (container_type) { - .@"struct" => { - _ = try analyzer.expectToken(.colon); - - const field_type = try analyzer.typeExpression(); - - const field_default_node = if (analyzer.tokens[analyzer.token_i].id == .equal) b: { - analyzer.consumeToken(); - const default_index = try analyzer.expression(); - const default_node = analyzer.nodes.items[default_index.unwrap()]; - assert(default_node.id != .node_list); - break :b default_index; - } else Node.Index.invalid; - - _ = try analyzer.expectToken(.comma); - - const field_node = try analyzer.addNode(.{ - .id = .container_field, - .token = first, - .left = field_type, - .right = field_default_node, - }); - - break :blk field_node; - }, - .@"enum" => { - const value_associated = switch (analyzer.tokens[analyzer.token_i].id) { - .comma => Node.Index.invalid, - else => value: { - analyzer.consumeToken(); - break :value try analyzer.expression(); - }, - }; - - _ = try analyzer.expectToken(.comma); - - const enum_field_node = try analyzer.addNode(.{ - .id = .enum_field, - .token = first, - .left = value_associated, - .right = Node.Index.invalid, - }); - - break :blk enum_field_node; - }, - } - }, - .fixed_keyword_const, .fixed_keyword_var => try analyzer.symbolDeclaration(), - else => |t| @panic(@tagName(t)), - }; - - logln(.parser, .container_members, "Container member {s}", .{@tagName(analyzer.nodes.items[member_node_index.unwrap()].id)}); - - try list.append(analyzer.allocator, member_node_index); - } - - return list; - } }; -const Members = struct { - len: usize, - left: Node.Index, - right: Node.Index, -}; // Here it is assumed that left brace is consumed - -pub fn analyze(allocator: Allocator, tokens: []const Token, source_file: []const u8, file_index: File.Index) !Result { +pub fn analyze(allocator: Allocator, lexer_result: lexer.Result, source_file: []const u8, file_index: File.Index, token_buffer: *Token.Buffer, node_list: *Node.List, node_lists: *ArrayList(ArrayList(Node.Index))) !Result { const start = std.time.Instant.now() catch unreachable; var analyzer = Analyzer{ - .tokens = tokens, + .lexer = lexer_result, + .token_buffer = token_buffer, .source_file = source_file, .file_index = file_index, + .token_i = lexer_result.offset, .allocator = allocator, + .nodes = node_list, + .node_lists = node_lists, }; - const node_index = try analyzer.addNode(.{ - .id = .struct_type, - .token = 0, - .left = Node.Index.invalid, - .right = Node.Index.invalid, - }); - - assert(node_index.value == 0); - assert(!node_index.invalid); - - const members = try analyzer.containerMembers(.@"struct"); - assert(analyzer.token_i == analyzer.tokens.len); - - const node_list = try analyzer.nodeList(members); - - analyzer.nodes.items[0].left = node_list; + const main_node_index = try analyzer.processContainerType(null); const end = std.time.Instant.now() catch unreachable; return .{ - .nodes = analyzer.nodes, - .node_lists = analyzer.node_lists, + .main_node_index = main_node_index, .time = end.since(start), }; } @@ -2085,3 +1982,5 @@ const Associativity = enum { none, left, }; + + diff --git a/bootstrap/frontend/semantic_analyzer.zig b/bootstrap/frontend/semantic_analyzer.zig deleted file mode 100644 index 617781d..0000000 --- a/bootstrap/frontend/semantic_analyzer.zig +++ /dev/null @@ -1,4376 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; -const equal = std.mem.eql; -const panic = std.debug.panic; -const Compilation = @import("../Compilation.zig"); -const File = Compilation.File; -const Module = Compilation.Module; -const Package = Compilation.Package; - -const ArrayType = Compilation.ArrayType; -const ArgumentList = Compilation.ArgumentList; -const Assignment = Compilation.Assignment; -const Block = Compilation.Block; -const Call = Compilation.Call; -const Declaration = Compilation.Declaration; -const Enum = Compilation.Type.Enum; -const Field = Compilation.Field; -const Function = Compilation.Function; -const Intrinsic = Compilation.Intrinsic; -const Loop = Compilation.Loop; -const Range = Compilation.Range; -const Scope = Compilation.Scope; -const ScopeType = Compilation.ScopeType; -const Slice = Compilation.Slice; -const Struct = Compilation.Struct; -const StringLiteral = Compilation.StringLiteral; -const Switch = Compilation.Switch; -const Termination = Compilation.Type.Termination; -const Type = Compilation.Type; -const ValidIntrinsic = Compilation.ValidIntrinsic; -const Value = Compilation.Value; - -const log = Compilation.log; -const logln = Compilation.logln; - -const ExpectedArrayType = struct { - element_type: Type.Index, - len: ?usize = null, - termination: Termination, -}; - -pub const Logger = enum { - type, - identifier, - symbol_declaration, - scope_node, - node, - typecheck, - @"switch", - block, - call, - scope_lookup, - debug, - fn_return_type, - address_of, - reaches_end, - - pub var bitset = std.EnumSet(Logger).initMany(&.{ - .type, - // .identifier, - // .symbol_declaration, - // .scope_node, - // .node, - // .typecheck, - // .@"switch", - // .block, - // .call, - // // .scope_lookup, - // .debug, - // .fn_return_type, - // .address_of, - // .reaches_end, - }); -}; - -const lexical_analyzer = @import("lexical_analyzer.zig"); -const Token = lexical_analyzer.Token; - -const syntactic_analyzer = @import("syntactic_analyzer.zig"); -const ContainerDeclaration = syntactic_analyzer.ContainerDeclaration; -const Node = syntactic_analyzer.Node; -const SymbolDeclaration = syntactic_analyzer.SymbolDeclaration; - -const data_structures = @import("../data_structures.zig"); -const ArrayList = data_structures.ArrayList; -const HashMap = data_structures.AutoHashMap; - -const ExpectType = union(enum) { - none, - type_index: Type.Index, - flexible_integer: FlexibleInteger, - addressable: Addressable, - dereferenceable: Dereferenceable, - - pub const none = ExpectType{ - .none = {}, - }; - pub const boolean = ExpectType{ - .type_index = Type.boolean, - }; - - pub const @"type" = ExpectType{ - .type_index = Type.type, - }; - - const FlexibleInteger = struct { - byte_count: u8, - sign: ?bool = null, - }; - - const Addressable = Type.Pointer; - const Dereferenceable = struct { - element_type: Type.Index, - }; -}; - -pub var unreachable_index = Value.Index.invalid; -pub var optional_null_index = Value.Index.invalid; -pub var pointer_null_index = Value.Index.invalid; -pub var undefined_index = Value.Index.invalid; -pub var boolean_true = Value.Index.invalid; -pub var boolean_false = Value.Index.invalid; - -pub var pointer_to_any_type = Type.Index.invalid; -pub var optional_pointer_to_any_type = Type.Index.invalid; -pub var optional_any = Type.Index.invalid; - -const Analyzer = struct { - allocator: Allocator, - module: *Module, - current_file: File.Index, - current_declaration: Declaration.Index = Declaration.Index.invalid, - payloads: ArrayList(Payload) = .{}, - current_block: Block.Index = Block.Index.invalid, - maybe_count: usize = 0, - for_count: usize = 0, - - fn getScopeSourceFile(analyzer: *Analyzer, scope_index: Scope.Index) []const u8 { - const scope = analyzer.module.values.scopes.get(scope_index); - const file = analyzer.module.values.files.get(scope.file); - return file.source_code; - } - - fn getScopeNode(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) Node { - const scope = analyzer.module.values.scopes.get(scope_index); - const file = analyzer.module.values.files.get(scope.file); - const result = &file.syntactic_analyzer_result.nodes.items[node_index.unwrap()]; - // logln(.sema, .scope_node, "Fetching node #{} (0x{x}) from scope #{} from file #{} with id: {s}", .{ node_index.uniqueInteger(), @intFromPtr(result), scope_index.uniqueInteger(), scope.file.uniqueInteger(), @tagName(result.id) }); - return result.*; - } - - fn getScopeToken(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index) Token { - const scope = analyzer.module.values.scopes.get(scope_index); - const file = analyzer.module.values.files.get(scope.file); - const result = file.lexical_analyzer_result.tokens.items[token_index]; - - return result; - } - - fn getScopeNodeList(analyzer: *Analyzer, scope_index: Scope.Index, node: Node) ArrayList(Node.Index) { - const scope = analyzer.module.values.scopes.get(scope_index); - return getFileNodeList(analyzer, scope.file, node); - } - - fn getFileNodeList(analyzer: *Analyzer, file_index: File.Index, node: Node) ArrayList(Node.Index) { - assert(node.id == .node_list); - const file = analyzer.module.values.files.get(file_index); - const list_index = node.left; - return file.syntactic_analyzer_result.node_lists.items[list_index.uniqueInteger()]; - } - - fn getFileToken(analyzer: *Analyzer, file_index: File.Index, token: Token.Index) Token { - const file = analyzer.module.values.files.get(file_index); - const result = file.lexical_analyzer_result.tokens.items[token]; - return result; - } - - fn getFileNode(analyzer: *Analyzer, file_index: File.Index, node_index: Node.Index) Node { - const file = analyzer.module.values.files.get(file_index); - const result = file.syntactic_analyzer_result.nodes.items[node_index.unwrap()]; - return result; - } - - fn comptimeBlock(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Value.Index { - const comptime_node = analyzer.getScopeNode(scope_index, node_index); - - const comptime_block = try analyzer.block(scope_index, .{ .none = {} }, comptime_node.left); - const value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .block = comptime_block, - }); - return value_index; - } - - fn unresolvedAllocate(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value.Index { - const value_index = try analyzer.module.values.array.addOne(analyzer.allocator); - analyzer.module.values.array.get(value_index).* = .{ - .unresolved = .{ - .node_index = node_index, - }, - }; - try analyzer.resolveNode(value_index, scope_index, expect_type, node_index); - return value_index; - } - - fn block(analyzer: *Analyzer, parent_scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) anyerror!Block.Index { - logln(.sema, .block, "Resolving block from scope #{} in file #{}", .{ parent_scope_index.uniqueInteger(), analyzer.module.values.scopes.get(parent_scope_index).file.uniqueInteger() }); - var reaches_end = true; - const block_node = analyzer.getScopeNode(parent_scope_index, node_index); - const statement_nodes = analyzer.getScopeNodeList(parent_scope_index, analyzer.getScopeNode(parent_scope_index, block_node.left)); - - const scope_index = try analyzer.module.values.scopes.append(analyzer.allocator, .{ - .parent = parent_scope_index, - .file = analyzer.module.values.scopes.get(parent_scope_index).file, - .token = block_node.token, - }); - - logln(.sema, .type, "Creating block scope #{}. Parent: #{}", .{ scope_index.uniqueInteger(), parent_scope_index.uniqueInteger() }); - - const block_index = try analyzer.module.values.blocks.append(analyzer.allocator, .{ - .statements = ArrayList(Value.Index){}, - .reaches_end = true, - }); - const previous_block = analyzer.current_block; - analyzer.current_block = block_index; - - for (analyzer.payloads.items) |payload| { - const declaration_type = Declaration.Type{ - .resolved = payload.type, - }; - const declaration_index = try analyzer.declarationCommon(scope_index, .local, payload.mutability, payload.name, declaration_type, payload.value, null); - const statement_value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .declaration = declaration_index, - }); - try analyzer.module.values.blocks.get(block_index).statements.append(analyzer.allocator, statement_value_index); - } - - analyzer.payloads.clearRetainingCapacity(); - - const is_comptime = switch (block_node.id) { - .comptime_block => true, - .block => false, - else => |t| @panic(@tagName(t)), - }; - - logln(.sema, .block, "Is comptime: {}", .{is_comptime}); - - for (statement_nodes.items) |statement_node_index| { - if (!reaches_end) { - switch (expect_type) { - .type_index => |type_index| if (!type_index.eq(Type.noreturn)) { - unreachable; - }, - else => |t| @panic(@tagName(t)), - } - unreachable; - } - - const statement_node = analyzer.getScopeNode(scope_index, statement_node_index); - logln(.sema, .node, "Trying to resolve statement of id {s}", .{@tagName(statement_node.id)}); - - const statement_value_index = switch (statement_node.id) { - .assign, .add_assign => try analyzer.module.values.array.append(analyzer.allocator, try analyzer.processAssignment(scope_index, statement_node_index)), - .@"unreachable" => blk: { - reaches_end = false; - logln(.sema, .reaches_end, "Not reaching end because of unreachable", .{}); - break :blk unreachable_index; - }, - .simple_symbol_declaration => blk: { - const declaration_index = try analyzer.symbolDeclaration(scope_index, statement_node_index, .local); - const declaration = analyzer.module.values.declarations.get(declaration_index); - const init_value = analyzer.module.values.array.get(declaration.init_value); - switch (init_value.isComptime(analyzer.module) and declaration.mutability == .@"const") { - // Dont add comptime declaration statements - true => continue, - false => { - const statement_value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .declaration = declaration_index, - }); - break :blk statement_value_index; - }, - } - }, - .@"return" => blk: { - reaches_end = false; - logln(.sema, .reaches_end, "Not reaching end because of unreachable", .{}); - - const return_expresssion = try analyzer.processReturn(scope_index, expect_type, statement_node_index); - const return_value_index = try analyzer.module.values.array.append(analyzer.allocator, return_expresssion); - - break :blk return_value_index; - }, - .call => blk: { - const call_index = try analyzer.processCall(scope_index, statement_node_index); - const call_statement_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .call = call_index, - }); - if (analyzer.getValueType(call_statement_index).eq(Type.noreturn)) { - logln(.sema, .reaches_end, "Not reaching end because of function call", .{}); - reaches_end = false; - } - break :blk call_statement_index; - }, - // TODO: reaches end switch statement - .@"switch" => blk: { - const switch_value = try analyzer.processSwitch(scope_index, expect_type, statement_node_index); - switch (switch_value) { - .@"return" => { - logln(.sema, .reaches_end, "Not reaching end because of return inside a switch statement", .{}); - reaches_end = false; - }, - else => {}, - } - const switch_value_index = try analyzer.module.values.array.append(analyzer.allocator, switch_value); - - break :blk switch_value_index; - }, - .if_else => blk: { - const if_else_node_index = statement_node_index; - const payload_node_index = Node.Index.invalid; - switch (try analyzer.processIfElse(scope_index, expect_type, if_else_node_index, payload_node_index)) { - .if_else => |if_else_value| { - const branch = analyzer.module.values.branches.get(if_else_value.branch); - reaches_end = branch.reaches_end; - if (!reaches_end) { - logln(.sema, .reaches_end, "Not reaching end because of branch statement", .{}); - } - assert(if_else_value.maybe_payload_declaration_index == null); - const branch_value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .branch = if_else_value.branch, - }); - break :blk branch_value_index; - }, - .expression => |expression_value_index| break :blk expression_value_index, - } - }, - .if_else_payload => blk: { - const if_else_node_index = statement_node.left; - const payload_node_index = statement_node.right; - switch (try analyzer.processIfElse(scope_index, expect_type, if_else_node_index, payload_node_index)) { - .if_else => |if_else_value| { - if (if_else_value.maybe_payload_declaration_index) |maybe_payload_declaration| { - try analyzer.module.values.blocks.get(block_index).statements.append(analyzer.allocator, maybe_payload_declaration); - } - - const branch = analyzer.module.values.branches.get(if_else_value.branch); - reaches_end = branch.reaches_end; - if (!reaches_end) { - logln(.sema, .reaches_end, "Not reaching end because of branch statement", .{}); - } - const branch_statement_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .branch = if_else_value.branch, - }); - break :blk branch_statement_index; - }, - .expression => unreachable, - } - }, - .@"if", - .if_payload, - => blk: { - const if_statement_node_index = switch (statement_node.id) { - .@"if" => statement_node_index, - .if_payload => statement_node.left, - else => unreachable, - }; - - const payload_node_index = switch (statement_node.id) { - .@"if" => Node.Index.invalid, - .if_payload => statement_node.right, - else => unreachable, - }; - - const if_expression = try analyzer.processIf(scope_index, expect_type, if_statement_node_index, payload_node_index); - - switch (if_expression.expression.invalid) { - // The condition is not evaluated at comptime, so emit both branches - false => { - if (if_expression.maybe_payload_declaration_value) |maybe_payload_declaration| { - try analyzer.module.values.blocks.get(block_index).statements.append(analyzer.allocator, maybe_payload_declaration); - } - - const branch_index = try analyzer.module.values.branches.append(analyzer.allocator, .{ - .expression = if_expression.expression, - .taken_expression = if_expression.taken_expression, - .not_taken_expression = Value.Index.invalid, - .reaches_end = true, // The else branch, as it doesnt exist, always reaches the end - }); - const branch_statement_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .branch = branch_index, - }); - break :blk branch_statement_index; - }, - true => switch (if_expression.taken_expression.invalid) { - true => continue, - false => break :blk if_expression.taken_expression, - }, - } - }, - .compiler_intrinsic => blk: { - const intrinsic_value = try analyzer.compilerIntrinsic(scope_index, ExpectType.none, statement_node_index); - const value_index = try analyzer.module.values.array.append(analyzer.allocator, intrinsic_value); - break :blk value_index; - }, - .assembly_block => blk: { - const assembly_value = try analyzer.assembly(scope_index, ExpectType.none, statement_node_index); - const value_index = try analyzer.module.values.array.append(analyzer.allocator, assembly_value); - break :blk value_index; - }, - .for_loop => blk: { - const loop_index = try analyzer.forLoop(scope_index, expect_type, statement_node_index); - const value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .loop = loop_index, - }); - break :blk value_index; - }, - .simple_while => blk: { - const loop_index = try analyzer.whileLoop(scope_index, expect_type, statement_node_index); - const value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .loop = loop_index, - }); - break :blk value_index; - }, - // TODO: analyze further - .break_expression => try analyzer.module.values.array.append(analyzer.allocator, .@"break"), - else => |t| @panic(@tagName(t)), - }; - - try analyzer.module.values.blocks.get(block_index).statements.append(analyzer.allocator, statement_value_index); - } - - analyzer.module.values.blocks.get(block_index).reaches_end = reaches_end; - - analyzer.current_block = previous_block; - - return block_index; - } - - fn processAssemblyStatements(analyzer: *Analyzer, comptime architecture: type, scope_index: Scope.Index, assembly_statement_nodes: []const Node.Index) ![]Compilation.Assembly.Instruction.Index { - var instructions = try ArrayList(Compilation.Assembly.Instruction.Index).initCapacity(analyzer.allocator, assembly_statement_nodes.len); - - for (assembly_statement_nodes) |assembly_statement_node_index| { - const assembly_statement_node = analyzer.getScopeNode(scope_index, assembly_statement_node_index); - const instruction_name = analyzer.tokenIdentifier(scope_index, assembly_statement_node.token); - const instruction = inline for (@typeInfo(architecture.Instruction).Enum.fields) |instruction_enum_field| { - if (equal(u8, instruction_name, instruction_enum_field.name)) { - break @field(architecture.Instruction, instruction_enum_field.name); - } - } else unreachable; - const operand_node_list_node = analyzer.getScopeNode(scope_index, assembly_statement_node.left); - const operand_nodes = analyzer.getScopeNodeList(scope_index, operand_node_list_node); - - var operand_list = try ArrayList(Compilation.Assembly.Operand).initCapacity(analyzer.allocator, operand_nodes.items.len); - - for (operand_nodes.items) |operand_node_index| { - const operand_node = analyzer.getScopeNode(scope_index, operand_node_index); - const operand: Compilation.Assembly.Operand = switch (operand_node.id) { - .assembly_register => blk: { - const register_name = analyzer.tokenIdentifier(scope_index, operand_node.token); - - const register = inline for (@typeInfo(architecture.Register).Enum.fields) |register_enum_field| { - if (equal(u8, register_name, register_enum_field.name)) { - break @field(architecture.Register, register_enum_field.name); - } - } else unreachable; - break :blk .{ - .register = @intFromEnum(register), - }; - }, - .number_literal => switch (std.zig.parseNumberLiteral(analyzer.numberBytes(scope_index, operand_node.token))) { - .int => |integer| .{ - .number_literal = integer, - }, - else => |t| @panic(@tagName(t)), - }, - .identifier => .{ - .value_index = try analyzer.doIdentifier(scope_index, ExpectType.none, operand_node.token, scope_index), - }, - .address_of => .{ - .value_index = try analyzer.module.values.array.append(analyzer.allocator, try analyzer.addressOf(scope_index, ExpectType.none, operand_node_index)), - }, - else => |t| @panic(@tagName(t)), - }; - operand_list.appendAssumeCapacity(operand); - } - - const assembly_instruction_index = try analyzer.module.values.assembly_instructions.append(analyzer.allocator, .{ - .id = @intFromEnum(instruction), - .operands = operand_list.items, - }); - instructions.appendAssumeCapacity(assembly_instruction_index); - } - - return instructions.items; - } - - fn assembly(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { - _ = expect_type; - const assembly_node = analyzer.getScopeNode(scope_index, node_index); - assert(assembly_node.id == .assembly_block); - assert(assembly_node.right.invalid); - const assembly_statement_node_list = analyzer.getScopeNode(scope_index, assembly_node.left); - const assembly_statement_nodes = analyzer.getScopeNodeList(scope_index, assembly_statement_node_list); - const assembly_statements = switch (analyzer.module.descriptor.target.cpu.arch) { - .x86_64 => try analyzer.processAssemblyStatements(Compilation.Assembly.x86_64, scope_index, assembly_statement_nodes.items), - else => unreachable, - }; - const assembly_block_index = try analyzer.module.values.assembly_blocks.append(analyzer.allocator, .{ - .instructions = assembly_statements, - }); - return .{ - .assembly_block = assembly_block_index, - }; - } - - fn processCallToFunctionPrototype(analyzer: *Analyzer, scope_index: Scope.Index, function_prototype_index: Function.Prototype.Index, call_argument_node_list: []const Node.Index, method_object: Value.Index) !ArrayList(Value.Index) { - const function_prototype = analyzer.module.types.function_prototypes.get(function_prototype_index); - - const method_object_count = @intFromBool(!method_object.invalid); - const call_argument_count = call_argument_node_list.len + method_object_count; - - var argument_array = try ArrayList(Value.Index).initCapacity(analyzer.allocator, call_argument_count); - logln(.sema, .call, "Method object valid: {}", .{!method_object.invalid}); - - if (!method_object.invalid) { - const first_argument_index = function_prototype.arguments.items[0]; - const first_argument = analyzer.module.values.declarations.get(first_argument_index); - const first_argument_type = first_argument.getType(); - const method_object_value = analyzer.module.values.array.get(method_object); - const method_object_type = method_object_value.getType(analyzer.module); - // TODO: further typecheck - const method_object_argument = switch (analyzer.module.types.array.get(first_argument_type).*) { - .pointer => switch (analyzer.module.types.array.get(method_object_type).*) { - .pointer => method_object, - else => blk: { - const unary_index = try analyzer.module.values.unary_operations.append(analyzer.allocator, .{ - .id = .address_of, - .value = method_object, - .type = first_argument_type, - }); - const address_of_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .unary_operation = unary_index, - }); - break :blk address_of_index; - }, - }, - else => switch (analyzer.module.types.array.get(method_object_type).*) { - .pointer => blk: { - const unary_index = try analyzer.module.values.unary_operations.append(analyzer.allocator, .{ - .id = .pointer_dereference, - .value = method_object, - .type = first_argument_type, - }); - const pointer_dereference_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .unary_operation = unary_index, - }); - break :blk pointer_dereference_index; - }, - else => method_object, - }, - }; - - argument_array.appendAssumeCapacity(method_object_argument); - } - - logln(.sema, .call, "Argument declaration count: {}. Argument node list count: {}", .{ function_prototype.arguments.items.len, call_argument_node_list.len }); - if (function_prototype.arguments.items.len == call_argument_count) { - for (function_prototype.arguments.items[method_object_count..], call_argument_node_list, 0..) |argument_declaration_index, argument_node_index, _index| { - const index = _index + method_object_count; - const argument_declaration = analyzer.module.values.declarations.get(argument_declaration_index); - const argument_node = analyzer.getScopeNode(scope_index, argument_node_index); - const value_node_index = switch (argument_node.id) { - .identifier => blk: { - const identifier = analyzer.tokenIdentifier(scope_index, argument_node.token); - const identifier_hash = try analyzer.processIdentifier(identifier); - - if (identifier_hash == argument_declaration.name) { - break :blk argument_node_index; - } else { - const call_site_name = analyzer.module.getName(identifier_hash).?; - const definition_site_name = analyzer.module.getName(argument_declaration.name).?; - // const function_name = analyzer.module.getName(analyzer.module.types.function_name_map.get(function_index).?).?; - std.debug.panic("At function call, argument #{} must be named the same way. Call site was name '{s}' while function definition has it named as '{s}'", .{ index, call_site_name, definition_site_name }); - } - }, - .named_argument => blk: { - const identifier_node = analyzer.getScopeNode(scope_index, argument_node.left); - if (identifier_node.id != .identifier) { - @panic("expected identifier"); - } - const identifier = analyzer.tokenIdentifier(scope_index, identifier_node.token); - const identifier_hash = try analyzer.processIdentifier(identifier); - - if (identifier_hash == argument_declaration.name) { - break :blk argument_node.right; - } else { - const call_site_name = analyzer.module.getName(identifier_hash).?; - const definition_site_name = analyzer.module.getName(argument_declaration.name).?; - // const function_name = analyzer.module.getName(analyzer.module.types.function_name_map.get(function_index).?).?; - std.debug.panic("At function call, argument #{} must be named the same way. Call site was name '{s}' while function definition has it named as '{s}'", .{ index, call_site_name, definition_site_name }); - } - }, - else => |node_id| { - const definition_site_name = analyzer.module.getName(argument_declaration.name).?; - // const function_name = analyzer.module.getName(analyzer.module.types.function_name_map.get(function_index).?).?; - - std.debug.panic("Argument #{} of call to function of type {s} must be named as '{s}'", .{ index, @tagName(node_id), definition_site_name }); - }, - }; - const argument_declaration_type = argument_declaration.getType(); - const argument_expect_type = ExpectType{ - .type_index = argument_declaration_type, - }; - const call_argument_value_index = try analyzer.unresolvedAllocate(scope_index, argument_expect_type, value_node_index); - const call_site_type = analyzer.getValueType(call_argument_value_index); - const result = try analyzer.typeCheck(argument_expect_type, call_site_type); - - argument_array.appendAssumeCapacity(switch (result) { - .array_coerce_to_slice => blk: { - const array_coerce_to_slice = try analyzer.module.values.intrinsics.append(analyzer.allocator, .{ - .kind = .{ - .array_coerce_to_slice = call_argument_value_index, - }, - .type = argument_declaration_type, - }); - - const coertion_value = try analyzer.module.values.array.append(analyzer.allocator, .{ - .intrinsic = array_coerce_to_slice, - }); - - break :blk coertion_value; - }, - else => |t| @panic(@tagName(t)), - .success => call_argument_value_index, - }); - } - } else { - panic("{s} call has argument count mismatch: call has {}, function declaration has {}", .{ switch (method_object.invalid) { - true => "Function", - false => "Method function", - }, call_argument_count, function_prototype.arguments.items.len }); - } - - return argument_array; - } - - fn processCall(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Call.Index { - const node = analyzer.getScopeNode(scope_index, node_index); - assert(!node.left.invalid); - var is_field_access = false; - - const left_value_index = switch (!node.left.invalid) { - true => blk: { - const member_or_namespace_node_index = node.left; - assert(!member_or_namespace_node_index.invalid); - const n = analyzer.getScopeNode(scope_index, member_or_namespace_node_index); - is_field_access = switch (n.id) { - .field_access => true, - else => false, - }; - const this_value_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, member_or_namespace_node_index); - break :blk this_value_index; - }, - false => unreachable, //Value.Index.invalid, - }; - - const left_type = switch (left_value_index.invalid) { - false => switch (analyzer.module.values.array.get(left_value_index).*) { - .function_definition => |function_index| analyzer.module.types.function_prototypes.get(analyzer.module.types.array.get(analyzer.module.types.function_definitions.get(function_index).prototype).function).return_type, - .function_declaration => |function_index| analyzer.module.types.function_prototypes.get(analyzer.module.types.array.get(analyzer.module.types.function_declarations.get(function_index).prototype).function).return_type, - .field_access => |field_access_index| blk: { - const field_access_type_index = analyzer.module.types.container_fields.get(analyzer.module.values.field_accesses.get(field_access_index).field).type; - const field_access_type = analyzer.module.types.array.get(field_access_type_index); - break :blk switch (field_access_type.*) { - .pointer => |pointer| b: { - assert(!pointer.many); - assert(pointer.@"const"); - const appointee_type = analyzer.module.types.array.get(pointer.element_type); - break :b switch (appointee_type.*) { - .function => |function_prototype_index| analyzer.module.types.function_prototypes.get(function_prototype_index).return_type, - else => |t| @panic(@tagName(t)), - }; - }, - else => |t| @panic(@tagName(t)), - }; - }, - else => |t| @panic(@tagName(t)), - }, - true => Type.Index.invalid, - }; - - const arguments_index = switch (node.id) { - .call => try analyzer.module.values.argument_lists.append(analyzer.allocator, .{ - .array = b: { - const argument_list_node_index = node.right; - const call_argument_node_list = analyzer.getScopeNodeList(scope_index, analyzer.getScopeNode(scope_index, argument_list_node_index)); - - switch (analyzer.module.values.array.get(left_value_index).*) { - .function_definition => |function_index| { - const function_definition = analyzer.module.types.function_definitions.get(function_index); - const function_prototype_index = analyzer.module.types.array.get(function_definition.prototype).function; - - logln(.sema, .call, "Is field access: {}", .{is_field_access}); - const method_object: Value.Index = switch (is_field_access) { - true => mob: { - const field_access_node = analyzer.getScopeNode(scope_index, node.left); - assert(field_access_node.id == .field_access); - const maybe_left_value_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, field_access_node.left); - const left_value_type_index = analyzer.getValueType(maybe_left_value_index); - const left_value_type = analyzer.module.types.array.get(left_value_type_index); - logln(.sema, .call, "Left value type: {}", .{left_value_type}); - break :mob switch (left_value_type.*) { - .type => Value.Index.invalid, - .@"struct" => switch (analyzer.module.values.array.get(left_value_index).*) { - .function_definition => maybe_left_value_index, - else => |t| @panic(@tagName(t)), - }, - .pointer => maybe_left_value_index, - // .field_access => maybe_left_value.index, - else => |t| @panic(@tagName(t)), - }; - }, - false => Value.Index.invalid, - }; - break :b try analyzer.processCallToFunctionPrototype(scope_index, function_prototype_index, call_argument_node_list.items, method_object); - }, - .function_declaration => |function_index| { - const function_declaration = analyzer.module.types.function_declarations.get(function_index); - const function_prototype_index = analyzer.module.types.array.get(function_declaration.prototype).function; - // TODO: - assert(!is_field_access); - const method_object = Value.Index.invalid; - - break :b try analyzer.processCallToFunctionPrototype(scope_index, function_prototype_index, call_argument_node_list.items, method_object); - }, - .field_access => |field_access_index| { - const field_access = analyzer.module.values.field_accesses.get(field_access_index); - const container_field = analyzer.module.types.container_fields.get(field_access.field); - const container_field_type = analyzer.module.types.array.get(container_field.type); - switch (container_field_type.*) { - .pointer => |function_pointer| { - if (!function_pointer.@"const") { - unreachable; - } - - if (function_pointer.many) { - unreachable; - } - - const appointee_type = analyzer.module.types.array.get(function_pointer.element_type); - switch (appointee_type.*) { - .function => |function_prototype_index| { - break :b try analyzer.processCallToFunctionPrototype(scope_index, function_prototype_index, call_argument_node_list.items, Value.Index.invalid); - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - }, - }), - else => |t| @panic(@tagName(t)), - }; - - const call_index = try analyzer.module.values.calls.append(analyzer.allocator, .{ - .value = left_value_index, - .arguments = arguments_index, - - .type = left_type, - }); - - return call_index; - } - - fn typeCheckEnumLiteral(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index, enum_type: Enum) !?Enum.Field.Index { - const enum_name = tokenBytes(analyzer.getScopeToken(scope_index, token_index), analyzer.getScopeSourceFile(scope_index)); - const enum_name_hash = try analyzer.processIdentifier(enum_name); - - for (enum_type.fields.items) |enum_field_index| { - const enum_field = analyzer.module.types.enum_fields.get(enum_field_index); - const existing = analyzer.module.getName(enum_field.name).?; - if (enum_field.name == enum_name_hash) { - return enum_field_index; - } - - logln(.sema, .typecheck, "Existing enum field \"{s}\" != enum literal \"{s}\"", .{ existing, enum_name }); - } else { - return null; - } - } - - const TypeCheckSwitchEnums = struct { - switch_case_groups: ArrayList(ArrayList(Enum.Field.Index)), - else_switch_case_group_index: ?usize = null, - }; - - fn typecheckSwitchEnums(analyzer: *Analyzer, scope_index: Scope.Index, enum_type: Type.Enum, switch_case_node_list: []const Node.Index) !TypeCheckSwitchEnums { - var result = TypeCheckSwitchEnums{ - .switch_case_groups = try ArrayList(ArrayList(Enum.Field.Index)).initCapacity(analyzer.allocator, switch_case_node_list.len), - }; - - var existing_enums = ArrayList(Enum.Field.Index){}; - - for (switch_case_node_list, 0..) |switch_case_node_index, index| { - const switch_case_node = analyzer.getScopeNode(scope_index, switch_case_node_index); - - switch (switch_case_node.left.invalid) { - false => { - const switch_case_condition_node = analyzer.getScopeNode(scope_index, switch_case_node.left); - var switch_case_group = ArrayList(Enum.Field.Index){}; - - switch (switch_case_condition_node.id) { - .enum_literal => { - if (try typeCheckEnumLiteral(analyzer, scope_index, switch_case_condition_node.token + 1, enum_type)) |enum_field_index| { - for (existing_enums.items) |existing| { - if (enum_field_index.eq(existing)) { - // Duplicate case - unreachable; - } - } - - try switch_case_group.append(analyzer.allocator, enum_field_index); - try existing_enums.append(analyzer.allocator, enum_field_index); - } else { - unreachable; - } - }, - .node_list => { - const node_list = analyzer.getScopeNodeList(scope_index, switch_case_condition_node); - try switch_case_group.ensureTotalCapacity(analyzer.allocator, node_list.items.len); - - for (node_list.items) |case_condition_node_index| { - const case_condition_node = analyzer.getScopeNode(scope_index, case_condition_node_index); - switch (case_condition_node.id) { - .enum_literal => { - if (try typeCheckEnumLiteral(analyzer, scope_index, case_condition_node.token + 1, enum_type)) |enum_field_index| { - for (existing_enums.items) |existing| { - if (enum_field_index.eq(existing)) { - // Duplicate case - unreachable; - } - } - - try existing_enums.append(analyzer.allocator, enum_field_index); - switch_case_group.appendAssumeCapacity(enum_field_index); - } else { - unreachable; - } - }, - else => |t| @panic(@tagName(t)), - } - } - }, - else => |t| @panic(@tagName(t)), - } - - result.switch_case_groups.appendAssumeCapacity(switch_case_group); - }, - true => { - result.else_switch_case_group_index = index; - }, - } - } - - return result; - } - - fn processSwitch(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { - const node = analyzer.getScopeNode(scope_index, node_index); - assert(node.id == .@"switch"); - - // analyzer.debugNode(scope_index, node_index); - - const switch_expression_value_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); - const switch_case_list_node = analyzer.getScopeNode(scope_index, node.right); - const switch_case_node_list = switch (switch_case_list_node.id) { - .node_list => analyzer.getScopeNodeList(scope_index, switch_case_list_node).items, - else => |t| @panic(@tagName(t)), - }; - - switch (analyzer.module.values.array.get(switch_expression_value_index).*) { - .enum_field => |e_field_index| { - const e_field = analyzer.module.types.enum_fields.get(e_field_index); - const enum_type_general = analyzer.module.types.array.get(e_field.parent); - const enum_type = analyzer.module.types.enums.get(enum_type_general.@"enum"); - const enum_field_name = analyzer.module.getName(e_field.name); - _ = enum_field_name; - - const typecheck_enum_result = try analyzer.typecheckSwitchEnums(scope_index, enum_type.*, switch_case_node_list); - - const group_index = for (typecheck_enum_result.switch_case_groups.items, 0..) |switch_case_group, switch_case_group_index| { - break for (switch_case_group.items) |enum_field_index| { - if (e_field_index.eq(enum_field_index)) { - break switch_case_group_index; - } - } else { - continue; - }; - } else typecheck_enum_result.else_switch_case_group_index orelse unreachable; - - logln(.sema, .@"switch", "Index: {}", .{group_index}); - - const true_switch_case_node = analyzer.getScopeNode(scope_index, switch_case_node_list[group_index]); - const result_index = try analyzer.unresolvedAllocate(scope_index, expect_type, true_switch_case_node.right); - - return analyzer.module.values.array.get(result_index).*; - }, - .declaration_reference => |declaration_reference| { - switch (analyzer.module.types.array.get(declaration_reference.getType(analyzer.module)).*) { - .@"enum" => |enum_index| { - const enum_type = analyzer.module.types.enums.get(enum_index); - const typecheck_enum_result = try analyzer.typecheckSwitchEnums(scope_index, enum_type.*, switch_case_node_list); - - var group_list = try ArrayList(Switch.Group).initCapacity(analyzer.allocator, switch_case_node_list.len); - - for (switch_case_node_list, typecheck_enum_result.switch_case_groups.items) |case_node_index, case_enum_group| { - const case_node = analyzer.getScopeNode(scope_index, case_node_index); - const expression_node_index = case_node.right; - const expression_value_index = try analyzer.unresolvedAllocate(scope_index, expect_type, expression_node_index); - var value_list = try ArrayList(Value.Index).initCapacity(analyzer.allocator, case_enum_group.items.len); - - for (case_enum_group.items) |case_enum_item| { - const value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .enum_field = case_enum_item, - }); - - value_list.appendAssumeCapacity(value_index); - } - - group_list.appendAssumeCapacity(.{ - .conditions = value_list, - .expression = expression_value_index, - }); - } - - const switch_expression = try analyzer.module.values.switches.append(analyzer.allocator, .{ - .value = switch_expression_value_index, - .groups = group_list, - }); - - const switch_value = Value{ - .switch_expression = switch_expression, - }; - - return switch_value; - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - } - - fn range(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Range { - const range_node = analyzer.getScopeNode(scope_index, node_index); - assert(range_node.id == .range); - - const expect_type = ExpectType{ - .type_index = Type.usize, - }; - - const range_start_index = try analyzer.unresolvedAllocate(scope_index, expect_type, range_node.left); - const range_end_index = switch (range_node.right.invalid) { - true => Value.Index.invalid, - false => try analyzer.unresolvedAllocate(scope_index, expect_type, range_node.right), - }; - - return Range{ - .start = range_start_index, - .end = range_end_index, - }; - } - - fn getPayloadName(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) ?[]const u8 { - const payload_node = analyzer.getScopeNode(scope_index, node_index); - const maybe_payload_name: ?[]const u8 = switch (payload_node.id) { - .identifier => analyzer.tokenIdentifier(scope_index, payload_node.token), - .discard => null, - else => |t| @panic(@tagName(t)), - }; - return maybe_payload_name; - } - - fn whileLoop(analyzer: *Analyzer, parent_scope_index: Scope.Index, expect_type: ExpectType, while_node_index: Node.Index) !Loop.Index { - const while_loop_node = analyzer.getScopeNode(parent_scope_index, while_node_index); - assert(while_loop_node.id == .simple_while); - // TODO: complete - const scope_index = parent_scope_index; - const condition_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.boolean, while_loop_node.left); - const body_index = try analyzer.unresolvedAllocate(scope_index, expect_type, while_loop_node.right); - const reaches_end = switch (analyzer.module.values.array.get(body_index).*) { - .block => |block_index| analyzer.module.values.blocks.get(block_index).reaches_end, - else => |t| @panic(@tagName(t)), - }; - - const loop_index = try analyzer.module.values.loops.append(analyzer.allocator, .{ - .pre = Value.Index.invalid, - .condition = condition_index, - .body = body_index, - .post = Value.Index.invalid, - .reaches_end = reaches_end, - }); - - return loop_index; - } - - fn forLoop(analyzer: *Analyzer, parent_scope_index: Scope.Index, expect_type: ExpectType, for_node_index: Node.Index) !Loop.Index { - const for_loop_node = analyzer.getScopeNode(parent_scope_index, for_node_index); - assert(for_loop_node.id == .for_loop); - - const scope_index = try analyzer.module.values.scopes.append(analyzer.allocator, .{ - .token = for_loop_node.token, - .file = analyzer.module.values.scopes.get(parent_scope_index).file, - .parent = parent_scope_index, - }); - - logln(.sema, .type, "Creating for loop scope #{}. Parent: #{}", .{ scope_index.uniqueInteger(), parent_scope_index.uniqueInteger() }); - - const for_condition_node = analyzer.getScopeNode(scope_index, for_loop_node.left); - assert(for_condition_node.id == .for_condition); - - const for_loop_element_node = analyzer.getScopeNode(scope_index, for_condition_node.left); - var pre = Value.Index.invalid; - const for_condition = switch (for_loop_element_node.id) { - .range => blk: { - const for_range = try analyzer.range(scope_index, for_condition_node.left); - - const for_loop_payload_node = analyzer.getScopeNode(scope_index, for_condition_node.right); - const maybe_payload_name = switch (for_loop_payload_node.id) { - .node_list => b: { - const nodes = analyzer.getScopeNodeList(scope_index, for_loop_payload_node); - assert(nodes.items.len == 1); - const payload_node_index = nodes.items[0]; - break :b analyzer.getPayloadName(scope_index, payload_node_index); - }, - else => |t| @panic(@tagName(t)), - }; - const payload_name = if (maybe_payload_name) |name| name else "_"; - const declaration_type = Declaration.Type{ - .resolved = Type.usize, - }; - const declaration_index = try analyzer.declarationCommon(scope_index, .local, .@"var", payload_name, declaration_type, for_range.start, null); - const declaration_value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .declaration = declaration_index, - }); - pre = declaration_value_index; - - const binary_condition_index = try analyzer.module.values.binary_operations.append(analyzer.allocator, .{ - .id = .compare_less_than, - .type = Type.boolean, - .left = try analyzer.doIdentifierString(scope_index, ExpectType{ - .type_index = Type.usize, - }, payload_name, scope_index), - .right = for_range.end, - }); - - const condition_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .binary_operation = binary_condition_index, - }); - break :blk condition_index; - }, - else => |t| @panic(@tagName(t)), - }; - - const for_loop_body_index = try analyzer.unresolvedAllocate(scope_index, expect_type, for_loop_node.right); - var post = Value.Index.invalid; - switch (for_loop_element_node.id) { - .range => { - const for_condition_value = analyzer.module.values.array.get(for_condition); - switch (for_condition_value.*) { - .binary_operation => |binary_operation_index| { - const binary_operation = analyzer.module.values.binary_operations.get(binary_operation_index); - const left_index = binary_operation.left; - const right_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .integer = .{ - .value = 1, - .type = Type.usize, - .signedness = .unsigned, - }, - }); - - const assignment_index = try analyzer.module.values.assignments.append(analyzer.allocator, .{ - .operation = .add, - .destination = left_index, - .source = right_index, - }); - - const assignment_value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .assign = assignment_index, - }); - post = assignment_value_index; - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - - const reaches_end = switch (analyzer.module.values.array.get(for_loop_body_index).*) { - .block => |block_index| analyzer.module.values.blocks.get(block_index).reaches_end, - else => |t| @panic(@tagName(t)), - }; - - const loop_index = try analyzer.module.values.loops.append(analyzer.allocator, .{ - .pre = pre, - .condition = for_condition, - .body = for_loop_body_index, - .reaches_end = reaches_end, - .post = post, - }); - - return loop_index; - } - - fn evaluateComptimeValue(analyzer: *Analyzer, value_index: Value.Index) ?Value.Index { - const value = analyzer.module.values.array.get(value_index); - return switch (value.*) { - .declaration_reference => |declaration_reference| blk: { - const declaration_index = declaration_reference.value; - const declaration = analyzer.module.values.declarations.get(declaration_index); - - if (declaration.mutability == .@"const") { - if (!declaration.init_value.invalid) { - break :blk analyzer.evaluateComptimeValue(declaration.init_value); - } - } - - break :blk null; - }, - .intrinsic => |intrinsic_index| blk: { - const intrinsic = analyzer.module.values.intrinsics.get(intrinsic_index); - break :blk switch (intrinsic.kind) { - .cast => null, - else => |t| @panic(@tagName(t)), - }; - }, - .unary_operation => |unary_operation_index| blk: { - const unary_operation = analyzer.module.values.unary_operations.get(unary_operation_index); - if (analyzer.evaluateComptimeValue(unary_operation.value)) |_| { - unreachable; - } else { - break :blk null; - } - }, - .binary_operation => |binary_operation_index| blk: { - const binary_operation = analyzer.module.values.binary_operations.get(binary_operation_index); - if (analyzer.evaluateComptimeValue(binary_operation.left)) |left_index| { - if (analyzer.evaluateComptimeValue(binary_operation.right)) |right_index| { - switch (binary_operation.id) { - .compare_equal => { - if (left_index.eq(right_index)) break :blk boolean_true; - const left = analyzer.module.values.array.get(left_index); - const right = analyzer.module.values.array.get(right_index); - - switch (left.*) { - .enum_field => if (left.enum_field.eq(right.enum_field)) { - break :blk boolean_true; - } else { - break :blk boolean_false; - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - - unreachable; - } - } - - break :blk null; - }, - // TODO: delete optional unwrap as value - .optional_unwrap => |optional_unwrap_index| blk: { - const optional_unwrap = analyzer.module.values.optional_unwraps.get(optional_unwrap_index); - if (analyzer.evaluateComptimeValue(optional_unwrap.value)) |_| { - unreachable; - } else { - break :blk null; - } - }, - // TODO: support comptime code? - .field_access, - .call, - .slice_access, - => null, - .bool => |boolean_value| switch (boolean_value) { - true => return boolean_true, - false => return boolean_false, - }, - .enum_field => value_index, - .indexed_access => |indexed_access_index| blk: { - const indexed_access = analyzer.module.values.indexed_accesses.get(indexed_access_index); - if (analyzer.evaluateComptimeValue(indexed_access.indexed_expression)) |comptime_indexed| { - _ = comptime_indexed; - - if (analyzer.evaluateComptimeValue(indexed_access.index_expression)) |comptime_index| { - _ = comptime_index; - - unreachable; - } - } - - break :blk null; - }, - else => |t| @panic(@tagName(t)), - }; - } - - const If = struct { - maybe_payload_declaration_value: ?Value.Index, - expression: Value.Index, - taken_expression: Value.Index, - reaches_end: bool, - }; - - const Payload = struct { - name: []const u8, - mutability: Compilation.Mutability, - type: Type.Index, - value: Value.Index, - }; - - fn processIf(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, if_node_index: Node.Index, payload_node_index: Node.Index) !If { - const if_branch_node = analyzer.getScopeNode(scope_index, if_node_index); - // analyzer.debugNode(scope_index, if_node_index); - assert(if_branch_node.id == .@"if"); - - var if_expression_index = try analyzer.unresolvedAllocate(scope_index, ExpectType{ - .type_index = switch (payload_node_index.invalid) { - true => Type.boolean, - false => optional_any, - }, - }, if_branch_node.left); - - if (payload_node_index.invalid) { - if (analyzer.evaluateComptimeValue(if_expression_index)) |comptime_evaluated_condition| { - if (comptime_evaluated_condition.eq(boolean_true)) { - return If{ - .maybe_payload_declaration_value = null, - .expression = Value.Index.invalid, - .taken_expression = try analyzer.unresolvedAllocate(scope_index, expect_type, if_branch_node.right), - .reaches_end = true, - }; - } else if (comptime_evaluated_condition.eq(boolean_false)) { - return If{ - .maybe_payload_declaration_value = null, - .expression = Value.Index.invalid, - .taken_expression = Value.Index.invalid, - .reaches_end = true, - }; - } else { - @panic("internal error"); - } - } - } - - const maybe_payload_declaration_value_index: ?Value.Index = if (!payload_node_index.invalid) blk: { - const if_type_index = analyzer.getValueType(if_expression_index); - logln(.sema, .fn_return_type, "If condition expression has type #{}", .{if_type_index.uniqueInteger()}); - const if_type = analyzer.module.types.array.get(if_type_index); - assert(if_type.* == .optional); - const payload_type_index = if_type.optional.element_type; - const maybe_payload_name = analyzer.getPayloadName(scope_index, payload_node_index); - - switch (analyzer.module.types.array.get(if_type_index).*) { - .optional => {}, - else => |t| @panic(@tagName(t)), - } - - const result: ?Value.Index = if (maybe_payload_name) |payload_name| b: { - const maybe_payload_declaration_type = Declaration.Type{ - .resolved = if_type_index, - }; - - const maybe_payload_declaration_index = try analyzer.declarationCommon(scope_index, .local, .@"const", try std.fmt.allocPrint(analyzer.allocator, "maybe_{}_{s}", .{ maybe: { - const r = analyzer.maybe_count; - analyzer.maybe_count += 1; - break :maybe r; - }, payload_name }), maybe_payload_declaration_type, if_expression_index, null); - - const maybe_payload_declaration_value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .declaration = maybe_payload_declaration_index, - }); - - if_expression_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .declaration_reference = .{ - .value = maybe_payload_declaration_index, - }, - }); - - break :b maybe_payload_declaration_value_index; - } else null; - - const if_expression_before_optional_check = if_expression_index; - - const optional_check_index = try analyzer.module.values.optional_checks.append(analyzer.allocator, .{ - .value = if_expression_index, - }); - - if_expression_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .optional_check = optional_check_index, - }); - - if (maybe_payload_name) |payload_name| { - const optional_unwrap_index = try analyzer.module.values.optional_unwraps.append(analyzer.allocator, .{ - .value = if_expression_before_optional_check, - }); - const payload_value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .optional_unwrap = optional_unwrap_index, - }); - - try analyzer.payloads.append(analyzer.allocator, Payload{ - .name = payload_name, - .mutability = .@"const", - .type = payload_type_index, - .value = payload_value_index, - }); - } - - break :blk result; - } else null; - - const taken_expression_index = try analyzer.unresolvedAllocate(scope_index, expect_type, if_branch_node.right); - - const true_reaches_end = switch (analyzer.module.values.array.get(taken_expression_index).*) { - .block => |block_index| analyzer.module.values.blocks.get(block_index).reaches_end, - .string_literal => true, - else => |t| @panic(@tagName(t)), - }; - - const if_result = If{ - .maybe_payload_declaration_value = maybe_payload_declaration_value_index, - .expression = if_expression_index, - .taken_expression = taken_expression_index, - .reaches_end = true_reaches_end, - }; - return if_result; - } - - const IfElseResult = union(enum) { - if_else: struct { - maybe_payload_declaration_index: ?Value.Index, - branch: Compilation.Branch.Index, - }, - expression: Value.Index, - }; - - fn processIfElse(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index, payload_node_index: Node.Index) !IfElseResult { - const node = analyzer.getScopeNode(scope_index, node_index); - assert(node.id == .if_else); - assert(!node.left.invalid); - assert(!node.right.invalid); - - const if_result = try analyzer.processIf(scope_index, expect_type, node.left, payload_node_index); - switch (if_result.expression.invalid) { - // The condition is not evaluated at comptime, so emit both branches - false => { - const not_taken_expression_index = try analyzer.unresolvedAllocate(scope_index, expect_type, node.right); - const false_reaches_end = switch (analyzer.module.values.array.get(not_taken_expression_index).*) { - .block => |block_index| analyzer.module.values.blocks.get(block_index).reaches_end, - .branch => |branch_index| analyzer.module.values.branches.get(branch_index).reaches_end, - .string_literal => true, - else => |t| @panic(@tagName(t)), - }; - const reaches_end = if_result.reaches_end or false_reaches_end; - - const branch_index = try analyzer.module.values.branches.append(analyzer.allocator, .{ - .expression = if_result.expression, - .taken_expression = if_result.taken_expression, - .not_taken_expression = not_taken_expression_index, - .reaches_end = reaches_end, - }); - - return IfElseResult{ - .if_else = .{ - .maybe_payload_declaration_index = if_result.maybe_payload_declaration_value, - .branch = branch_index, - }, - }; - }, - true => return .{ - .expression = switch (if_result.taken_expression.invalid) { - true => try analyzer.unresolvedAllocate(scope_index, expect_type, node.right), - false => if_result.taken_expression, - }, - }, - } - } - - fn processAssignment(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Value { - const node = analyzer.getScopeNode(scope_index, node_index); - assert(!node.left.invalid); - const left_node = analyzer.getScopeNode(scope_index, node.left); - switch (left_node.id) { - .discard => { - assert(node.id == .assign); - - const result = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.right); - - return analyzer.module.values.array.get(result).*; - }, - else => { - // const id = analyzer.tokenIdentifier(.token); - // logln("id: {s}", .{id}); - const left = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); - const left_type_index = analyzer.getValueType(left); - assert(!left_type_index.invalid); - const right = try analyzer.unresolvedAllocate(scope_index, ExpectType{ - .type_index = left_type_index, - }, node.right); - - if (analyzer.module.values.array.get(left).isComptime(analyzer.module) and analyzer.module.values.array.get(right).isComptime(analyzer.module)) { - unreachable; - } else { - const assignment_index = try analyzer.module.values.assignments.append(analyzer.allocator, .{ - .destination = left, - .source = right, - .operation = switch (node.id) { - .assign => .none, - .add_assign => .add, - else => unreachable, - }, - }); - - return Value{ - .assign = assignment_index, - }; - } - }, - } - } - - fn processReturn(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { - const node = analyzer.getScopeNode(scope_index, node_index); - const return_value: Value.Index = switch (node_index.invalid) { - // TODO: expect type - false => ret: { - const return_value_index = try analyzer.unresolvedAllocate(scope_index, expect_type, node.left); - break :ret return_value_index; - }, - true => @panic("TODO: ret void"), - }; - - const return_expression_index = try analyzer.module.values.returns.append(analyzer.allocator, .{ - .value = return_value, - }); - - return .{ - .@"return" = return_expression_index, - }; - } - - fn processBinaryOperation(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { - const node = analyzer.getScopeNode(scope_index, node_index); - const binary_operation_id: Compilation.BinaryOperation.Id = switch (node.id) { - .add => .add, - .sub => .sub, - .bit_and => .bit_and, - .bit_xor => .bit_xor, - .bit_or => .bit_or, - .multiply => .multiply, - .divide => .divide, - .shift_left => .shift_left, - .shift_right => .shift_right, - .compare_equal => .compare_equal, - .compare_not_equal => .compare_not_equal, - .compare_greater_than => .compare_greater_than, - .compare_greater_or_equal => .compare_greater_or_equal, - .compare_less_than => .compare_less_than, - .compare_less_or_equal => .compare_less_or_equal, - else => |t| @panic(@tagName(t)), - }; - const left_expect_type: ExpectType = switch (binary_operation_id) { - .compare_equal, - .compare_not_equal, - .compare_less_or_equal, - .compare_less_than, - .compare_greater_than, - .compare_greater_or_equal, - => ExpectType.none, - else => expect_type, - }; - - const left_index = try analyzer.unresolvedAllocate(scope_index, left_expect_type, node.left); - const right_expect_type: ExpectType = switch (binary_operation_id) { - .add, - .sub, - .bit_and, - .bit_xor, - .bit_or, - .multiply, - .divide, - => expect_type, - .shift_left, - .shift_right, - => ExpectType{ - .type_index = Type.u8, - }, - .compare_equal, - .compare_less_than, - .compare_greater_or_equal, - .compare_greater_than, - .compare_less_or_equal, - .compare_not_equal, - => ExpectType{ - .type_index = analyzer.getValueType(left_index), - }, - }; - const left_type = analyzer.getValueType(left_index); - const right_index = try analyzer.unresolvedAllocate(scope_index, right_expect_type, node.right); - - const binary_operation_index = try analyzer.module.values.binary_operations.append(analyzer.allocator, .{ - .left = left_index, - .right = right_index, - .type = switch (expect_type) { - .none => switch (binary_operation_id) { - .bit_and, - .shift_right, - .sub, - .multiply, - => left_type, - else => |t| @panic(@tagName(t)), - }, - .type_index => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }, - .id = binary_operation_id, - }); - - return .{ - .binary_operation = binary_operation_index, - }; - } - - const DeclarationLookup = struct { - declaration: Declaration.Index, - scope: Scope.Index, - }; - - fn getScopeSlice(analyzer: *Analyzer, scope_index: Scope.Index) []const u8 { - const scope = analyzer.module.values.scopes.get(scope_index); - - return analyzer.getScopeSliceCustomToken(scope_index, scope.token); - } - - fn getScopeSliceCustomToken(analyzer: *Analyzer, scope_index: Scope.Index, custom_token_index: Token.Index) []const u8 { - const scope = analyzer.module.values.scopes.get(scope_index); - const token = analyzer.getFileToken(scope.file, custom_token_index); - const scope_slice = analyzer.module.values.files.get(scope.file).source_code[token.start..]; - return scope_slice; - } - - fn lookupDeclarationInCurrentAndParentScopes(analyzer: *Analyzer, scope_index: Scope.Index, identifier_hash: u32) ?DeclarationLookup { - var scope_iterator = scope_index; - while (!scope_iterator.invalid) { - const scope = analyzer.module.values.scopes.get(scope_iterator); - if (Logger.bitset.contains(.scope_lookup)) { - const scope_slice = analyzer.getScopeSlice(scope_iterator); - logln(.sema, .scope_lookup, "Searching for identifier 0x{x} in scope #{}:\n```{s}\n```", .{ identifier_hash, scope_iterator.uniqueInteger(), scope_slice }); - } - - if (scope.declarations.get(identifier_hash)) |declaration_index| { - logln(.sema, .scope_lookup, "Identifier 0x{x} found in scope #{}", .{ identifier_hash, scope_iterator.uniqueInteger() }); - - return .{ - .declaration = declaration_index, - .scope = scope_iterator, - }; - } - - scope_iterator = scope.parent; - } - - return null; - } - - fn addressOf(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { - const node = analyzer.getScopeNode(scope_index, node_index); - - const pointer_expect_type: ExpectType = switch (expect_type) { - .none => expect_type, - .type_index => |type_index| .{ - .addressable = switch (analyzer.module.types.array.get(type_index).*) { - .pointer => |pointer| pointer, - .slice => |slice| .{ - .@"const" = slice.@"const", - .many = true, - .element_type = slice.element_type, - .termination = slice.termination, - }, - else => |t| @panic(@tagName(t)), - }, - }, - .flexible_integer => unreachable, - else => unreachable, - }; - - const appointee_value_index = try analyzer.unresolvedAllocate(scope_index, pointer_expect_type, node.left); - const addressable = switch (pointer_expect_type) { - .none => switch (analyzer.module.values.array.get(appointee_value_index).*) { - .declaration_reference => |*declaration_reference| switch (analyzer.module.types.array.get(declaration_reference.getType(analyzer.module)).*) { - .integer => ExpectType.Addressable{ - .element_type = declaration_reference.getType(analyzer.module), - .many = false, - .@"const" = false, - .termination = .none, - }, - .pointer => |pointer| pointer, - .any => { - // TODO - // const declaration = analyzer.module.values.declarations.get(declaration_reference.value); - // switch (analyzer.module.types.array.get(declaration.type).*) { - // else => |t| @panic(@tagName(t)), - // } - unreachable; - }, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }, - .addressable => |addressable| addressable, - else => |t| @panic(@tagName(t)), - }; - // - const unary_type_index: Type.Index = try analyzer.getPointerType(.{ - .element_type = analyzer.module.values.array.get(appointee_value_index).getType(analyzer.module), - .many = addressable.many, - .@"const" = addressable.@"const", - .termination = addressable.termination, - }); - - const unary_index = try analyzer.module.values.unary_operations.append(analyzer.allocator, .{ - .id = .address_of, - .value = appointee_value_index, - .type = unary_type_index, - }); - - const value: Value = switch (expect_type) { - .none => .{ - .unary_operation = unary_index, - }, - .type_index => |type_index| switch (analyzer.module.types.array.get(type_index).*) { - .slice => b: { - const array_coerce_to_slice = try analyzer.module.values.intrinsics.append(analyzer.allocator, .{ - .kind = .{ - .array_coerce_to_slice = appointee_value_index, - }, - .type = type_index, - }); - break :b .{ - .intrinsic = array_coerce_to_slice, - }; - }, - else => .{ - .unary_operation = unary_index, - }, - }, - else => |t| @panic(@tagName(t)), - }; - - return value; - } - - fn doIdentifierString(analyzer: *Analyzer, from_scope_index: Scope.Index, expect_type: ExpectType, identifier: []const u8, in_scope_index: Scope.Index) !Value.Index { - logln(.sema, .identifier, "Referencing identifier: \"{s}\" from scope #{} in scope #{}", .{ identifier, from_scope_index.uniqueInteger(), in_scope_index.uniqueInteger() }); - const identifier_hash = try analyzer.processIdentifier(identifier); - - if (analyzer.lookupDeclarationInCurrentAndParentScopes(from_scope_index, identifier_hash)) |lookup| { - const declaration_index = lookup.declaration; - const declaration = analyzer.module.values.declarations.get(declaration_index); - - switch (declaration.init_value.invalid) { - false => { - // logln(.sema, .identifier, "Declaration found: {}", .{init_value}); - switch (analyzer.module.values.array.get(declaration.init_value).*) { - .unresolved => |unresolved| { - const previous_declaration = analyzer.current_declaration; - analyzer.current_declaration = declaration_index; - - switch (declaration.type) { - .inferred => |inferred_type_index| assert(inferred_type_index.invalid), - .unresolved => |node_index| { - declaration.type = .{ - .resolved = try analyzer.resolveType(.{ - .scope_index = declaration.scope, - .node_index = node_index, - }), - }; - }, - else => |t| @panic(@tagName(t)), - } - - try analyzer.resolveNode(declaration.init_value, lookup.scope, expect_type, unresolved.node_index); - analyzer.current_declaration = previous_declaration; - - switch (analyzer.module.values.array.get(declaration.init_value).*) { - .function_definition => |function_index| { - const function_definition = analyzer.module.types.function_definitions.get(function_index); - const function_prototype = analyzer.module.types.array.get(function_definition.prototype); - const return_type_index = analyzer.functionPrototypeReturnType(function_prototype.function); - logln(.sema, .fn_return_type, "Function {s} has return type #{}", .{ analyzer.module.getName(declaration.name).?, return_type_index.uniqueInteger() }); - try analyzer.module.map.function_definitions.put(analyzer.allocator, function_index, declaration_index); - }, - .function_declaration => |function_index| { - try analyzer.module.map.function_declarations.put(analyzer.allocator, function_index, declaration_index); - }, - .type => |type_index| { - try analyzer.module.map.types.put(analyzer.allocator, type_index, declaration_index); - }, - else => {}, - } - }, - else => {}, - } - }, - true => { - switch (declaration.type) { - .resolved => {}, - else => |t| @panic(@tagName(t)), - } - }, - } - - switch (declaration.type) { - .inferred => |*inferred_type_index| { - const declaration_value_type = analyzer.module.values.array.get(declaration.init_value).getType(analyzer.module); - switch (inferred_type_index.invalid) { - true => inferred_type_index.* = declaration_value_type, - false => {}, - } - }, - .resolved => {}, - else => |t| @panic(@tagName(t)), - } - - const declaration_type_index = declaration.getType(); - const typecheck_result = try analyzer.typeCheck(expect_type, declaration_type_index); - - assert(!declaration_type_index.invalid); - assert(!declaration_type_index.eq(pointer_to_any_type)); - assert(!declaration_type_index.eq(optional_pointer_to_any_type)); - assert(!declaration_type_index.eq(optional_any)); - - if (!declaration.init_value.invalid and analyzer.module.values.array.get(declaration.init_value).isComptime(analyzer.module) and declaration.mutability == .@"const") { - assert(!declaration.init_value.invalid); - assert(typecheck_result == .success); - return declaration.init_value; - } else { - const declaration_reference = try analyzer.module.values.array.append(analyzer.allocator, .{ - .declaration_reference = .{ - .value = declaration_index, - }, - }); - - const result: Value.Index = switch (typecheck_result) { - .success, - .take_source, - => declaration_reference, - .optional_wrap => blk: { - const cast_type = switch (expect_type) { - .type_index => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }; - const intrinsic_index = try analyzer.module.values.intrinsics.append(analyzer.allocator, .{ - .kind = .{ - .optional_wrap = declaration_reference, - }, - .type = cast_type, - }); - - const value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .intrinsic = intrinsic_index, - }); - break :blk value_index; - }, - inline .zero_extend, .sign_extend => |extension_type| blk: { - const intrinsic = try analyzer.module.values.intrinsics.append(analyzer.allocator, .{ - .kind = @unionInit(Intrinsic.Kind, @tagName(extension_type), declaration_reference), - .type = switch (expect_type) { - .flexible_integer => |flexible_integer| t: { - const cast_type = Type.Integer.getIndex(.{ - .signedness = switch (extension_type) { - .zero_extend => .unsigned, - .sign_extend => .signed, - else => unreachable, - }, - .bit_count = flexible_integer.byte_count << 3, - }); - break :t cast_type; - }, - else => |t| @panic(@tagName(t)), - }, - }); - - const value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .intrinsic = intrinsic, - }); - break :blk value_index; - }, - else => |t| @panic(@tagName(t)), - }; - return result; - } - - // return switch (typecheck_result) { - // .success, - // .take_source, - // => reference_index, - // .array_coerce_to_slice => { - // const cast_type = switch (expect_type) { - // .type_index => |type_index| type_index, - // else => |t| @panic(@tagName(t)), - // }; - // _ = cast_type; - // unreachable; - // // const cast_index = try analyzer.module.values.casts.append(analyzer.allocator, .{ - // // .value = reference_index, - // // .type = cast_type, - // // }); - // // - // // const value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - // // .array_coerce_to_slice = cast_index, - // // }); - // // - // // break :blk value_index; - // }, - // }; - } else { - panic("Identifier \"{s}\" not found as a declaration from scope #{} referenced in scope #{}", .{ identifier, from_scope_index.uniqueInteger(), in_scope_index.uniqueInteger() }); - } - } - - fn doIdentifier(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_token: Token.Index, node_scope_index: Scope.Index) !Value.Index { - const identifier = analyzer.tokenIdentifier(node_scope_index, node_token); - return try analyzer.doIdentifierString(scope_index, expect_type, identifier, node_scope_index); - } - - fn resolveInteger(analyzer: *Analyzer, scope_index: Scope.Index, value_index: Value.Index) usize { - const value = analyzer.module.values.array.get(value_index); - return switch (value.*) { - .declaration_reference => |declaration_reference| blk: { - const declaration = analyzer.module.values.declarations.get(declaration_reference.value); - break :blk analyzer.resolveInteger(declaration.scope, declaration.init_value); - }, - .integer => |integer| integer.value, - .binary_operation => |binary_operation_index| { - const binary_operation = analyzer.module.values.binary_operations.get(binary_operation_index); - const left = analyzer.resolveInteger(scope_index, binary_operation.left); - const right = analyzer.resolveInteger(scope_index, binary_operation.right); - return switch (binary_operation.id) { - .add => left + right, - else => |t| @panic(@tagName(t)), - }; - }, - else => |t| @panic(@tagName(t)), - }; - } - - fn resolveNode(analyzer: *Analyzer, value_index: Value.Index, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) anyerror!void { - const node = analyzer.getScopeNode(scope_index, node_index); - // logln(.sema, .node, "Resolving node #{} in scope #{} from file #{}: {}", .{ node_index.uniqueInteger(), scope_index.uniqueInteger(), analyzer.module.values.scopes.get(scope_index).file.uniqueInteger(), node }); - - assert(analyzer.module.values.array.get(value_index).* == .unresolved); - - const new_value: Value = switch (node.id) { - .identifier => blk: { - const identifier_value_index = try analyzer.doIdentifier(scope_index, expect_type, node.token, scope_index); - const value_ref = analyzer.module.values.array.get(identifier_value_index); - break :blk value_ref.*; - }, - .keyword_true, .keyword_false => blk: { - switch (expect_type) { - .none => {}, - .type_index => |expected_type| { - if (@as(u32, @bitCast(Type.boolean)) != @as(u32, @bitCast(expected_type))) { - @panic("TODO: compile error"); - } - }, - else => unreachable, - } - - break :blk .{ - .bool = switch (node.id) { - .keyword_true => true, - .keyword_false => false, - else => unreachable, - }, - }; - }, - .compiler_intrinsic => try analyzer.compilerIntrinsic(scope_index, expect_type, node_index), - .function_definition => blk: { - const function_prototype_index = try analyzer.functionPrototype(scope_index, node.left); - const function_prototype = analyzer.module.types.function_prototypes.get(function_prototype_index); - assert(!function_prototype.attributes.@"extern"); - const function_scope_index = function_prototype.scope; - - const expected_type = ExpectType{ - .type_index = analyzer.functionPrototypeReturnType(function_prototype_index), - }; - const function_body = try analyzer.block(function_scope_index, expected_type, node.right); - - const prototype_type_index = try analyzer.module.types.array.append(analyzer.allocator, .{ - .function = function_prototype_index, - }); - - const function_index = try analyzer.module.types.function_definitions.append(analyzer.allocator, .{ - .prototype = prototype_type_index, - .body = function_body, - }); - - const result = Value{ - .function_definition = function_index, - }; - break :blk result; - }, - .function_prototype => blk: { - const function_prototype_index = try analyzer.functionPrototype(scope_index, node_index); - const function_prototype = analyzer.module.types.function_prototypes.get(function_prototype_index); - - break :blk switch (function_prototype.attributes.@"extern") { - true => b: { - const prototype_type_index = try analyzer.module.types.array.append(analyzer.allocator, .{ - .function = function_prototype_index, - }); - const function_declaration_index = try analyzer.module.types.function_declarations.append(analyzer.allocator, .{ - .prototype = prototype_type_index, - .body = Block.Index.invalid, - }); - break :b Value{ - .function_declaration = function_declaration_index, - }; - }, - false => unreachable, - }; - }, - .block => blk: { - const block_index = try analyzer.block(scope_index, expect_type, node_index); - break :blk Value{ - .block = block_index, - }; - }, - .number_literal => switch (std.zig.parseNumberLiteral(analyzer.numberBytes(scope_index, node.token))) { - .int => |integer| Value{ - .integer = .{ - .value = integer, - .type = switch (expect_type) { - .none => Type.comptime_int, - .flexible_integer, .type_index => switch (expect_type) { - .flexible_integer => |flexible_integer_type| Type.Integer.getIndex(Compilation.Type.Integer{ - .bit_count = flexible_integer_type.byte_count << 3, - .signedness = .unsigned, - }), - .type_index => |type_index| a: { - const type_info = analyzer.module.types.array.get(type_index); - break :a switch (type_info.*) { - .integer => type_index, - .optional => |optional_type| optional_type.element_type, - else => |t| @panic(@tagName(t)), - }; - }, - else => unreachable, - }, - else => unreachable, - }, - .signedness = .unsigned, - }, - }, - else => |t| @panic(@tagName(t)), - }, - .call => .{ - .call = try analyzer.processCall(scope_index, node_index), - }, - .field_access => blk: { - logln(.sema, .node, "Field access", .{}); - const identifier = analyzer.tokenIdentifier(scope_index, node.right.value); - const identifier_hash = try analyzer.processIdentifier(identifier); - logln(.sema, .node, "Field access identifier for RHS: \"{s}\"", .{identifier}); - // analyzer.debugNode(scope_index, node_index); - const left_value_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); - - switch (analyzer.module.values.array.get(left_value_index).*) { - .type => |type_index| { - if (!type_index.invalid) { - const left_type = analyzer.module.types.array.get(type_index); - switch (left_type.*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - const right_index = try analyzer.doIdentifier(struct_type.scope, ExpectType.none, node.right.value, scope_index); - const right_value = analyzer.module.values.array.get(right_index); - - break :blk right_value.*; - }, - .@"enum" => |enum_index| { - const enum_type = analyzer.module.types.enums.get(enum_index); - - const result = for (enum_type.fields.items) |enum_field_index| { - const enum_field = analyzer.module.types.enum_fields.get(enum_field_index); - if (enum_field.name == identifier_hash) { - break enum_field_index; - } - } else { - const right_index = try analyzer.doIdentifier(enum_type.scope, ExpectType.none, node.right.value, scope_index); - const right_value = analyzer.module.values.array.get(right_index); - - switch (right_value.*) { - .function_definition, - .type, - .enum_field, - .declaration_reference, - .integer, - => break :blk right_value.*, - else => |t| @panic(@tagName(t)), - } - - logln(.sema, .node, "Right: {}", .{right_value}); - // struct_scope.declarations.get(identifier); - - unreachable; - }; - - const enum_field = analyzer.module.types.enum_fields.get(result); - const enum_field_name = analyzer.module.getName(enum_field.name).?; - logln(.sema, .node, "Enum field name resolution: {s}", .{enum_field_name}); - break :blk Value{ - .enum_field = result, - }; - }, - else => |t| @panic(@tagName(t)), - } - unreachable; - } else { - panic("Identifier \"{s}\" not found. Type empty", .{identifier}); - } - }, - .enum_field => |enum_field_index| { - const enum_field = analyzer.module.types.enum_fields.get(enum_field_index); - const enum_field_name = analyzer.module.getName(enum_field.name).?; - std.debug.panic("LEFT: enum {s}. RIGHT: {s}", .{ enum_field_name, identifier }); - }, - .declaration_reference => |declaration_reference| { - const declaration_type = analyzer.module.types.array.get(declaration_reference.getType(analyzer.module)); - - switch (declaration_type.*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - for (struct_type.fields.items) |struct_field_index| { - const struct_field = analyzer.module.types.container_fields.get(struct_field_index); - if (struct_field.name == identifier_hash) { - const field_access_index = try analyzer.module.values.field_accesses.append(analyzer.allocator, .{ - .declaration_reference = left_value_index, - .field = struct_field_index, - }); - break :blk Value{ - .field_access = field_access_index, - }; - } - } else { - const declaration_value = try analyzer.doIdentifier(struct_type.scope, ExpectType.none, node.right.value, scope_index); - const value_ref = analyzer.module.values.array.get(declaration_value); - break :blk value_ref.*; - } - }, - .pointer => |pointer| { - const pointer_element_type = analyzer.module.types.array.get(pointer.element_type); - switch (pointer_element_type.*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - for (struct_type.fields.items) |struct_field_index| { - const struct_field = analyzer.module.types.container_fields.get(struct_field_index); - if (struct_field.name == identifier_hash) { - const field_access_index = try analyzer.module.values.field_accesses.append(analyzer.allocator, .{ - .declaration_reference = left_value_index, - .field = struct_field_index, - }); - - break :blk Value{ - .field_access = field_access_index, - }; - } - } else { - const declaration_value = try analyzer.doIdentifier(struct_type.scope, ExpectType.none, node.right.value, scope_index); - const value_ref = analyzer.module.values.array.get(declaration_value); - break :blk value_ref.*; - } - }, - else => |t| @panic(@tagName(t)), - } - - unreachable; - }, - .slice => { - const slice_field = inline for (@typeInfo(Slice.Field).Enum.fields) |slice_field| { - if (equal(u8, slice_field.name, identifier)) { - break @field(Slice.Field, slice_field.name); - } - } else unreachable; - const slice_access_type = switch (slice_field) { - .ptr => t: { - const slice_type_index = analyzer.getValueType(left_value_index); - const slice_type = analyzer.module.types.array.get(slice_type_index); - const slice_type_slice = slice_type.slice; - const pointer_type = try analyzer.getPointerType(.{ - .element_type = slice_type_slice.element_type, - .@"const" = slice_type_slice.@"const", - .many = true, - .termination = slice_type_slice.termination, - }); - break :t pointer_type; - }, - .len => Type.usize, - }; - - const field_access_index = try analyzer.module.values.slice_accesses.append(analyzer.allocator, .{ - .value = left_value_index, - .field = slice_field, - .type = slice_access_type, - }); - - break :blk Value{ - .slice_access = field_access_index, - }; - }, - .array => |array| break :blk Value{ - .integer = .{ - .value = array.element_count, - .type = expect_type.type_index, - .signedness = .unsigned, - }, - }, - else => |t| @panic(@tagName(t)), - } - - unreachable; - }, - .field_access => |field_access| { - const left_field_access = analyzer.module.values.field_accesses.get(field_access); - const left_field = analyzer.module.types.container_fields.get(left_field_access.field); - const left_field_type = analyzer.module.types.array.get(left_field.type); - - switch (left_field_type.*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - - for (struct_type.fields.items) |struct_field_index| { - const struct_field = analyzer.module.types.container_fields.get(struct_field_index); - if (struct_field.name == identifier_hash) { - const field_access_index = try analyzer.module.values.field_accesses.append(analyzer.allocator, .{ - .declaration_reference = left_value_index, - .field = struct_field_index, - }); - break :blk Value{ - .field_access = field_access_index, - }; - } - } else { - const scope1 = struct_type.scope; - const scope2 = scope_index; - const declaration_value = try analyzer.doIdentifier(scope1, ExpectType.none, node.right.value, scope2); - - const value_ref = analyzer.module.values.array.get(declaration_value); - break :blk value_ref.*; - } - - unreachable; - }, - .slice => |slice| { - _ = slice; - const slice_field = inline for (@typeInfo(Slice.Field).Enum.fields) |slice_field| { - if (equal(u8, slice_field.name, identifier)) { - break @field(Slice.Field, slice_field.name); - } - } else unreachable; - - const slice_access_type = switch (slice_field) { - .ptr => t: { - const slice_type_index = analyzer.getValueType(left_value_index); - const slice_type = analyzer.module.types.array.get(slice_type_index); - const slice_type_slice = slice_type.slice; - const pointer_type = try analyzer.getPointerType(.{ - .element_type = slice_type_slice.element_type, - .@"const" = slice_type_slice.@"const", - .many = true, - .termination = slice_type_slice.termination, - }); - break :t pointer_type; - }, - .len => Type.usize, - }; - - const field_access_index = try analyzer.module.values.slice_accesses.append(analyzer.allocator, .{ - .value = left_value_index, - .field = slice_field, - .type = slice_access_type, - }); - - break :blk Value{ - .slice_access = field_access_index, - }; - }, - else => |t| @panic(@tagName(t)), - } - unreachable; - }, - .call => |call_index| { - const call = analyzer.module.values.calls.get(call_index); - switch (analyzer.module.types.array.get(call.type).*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - - for (struct_type.fields.items) |struct_field_index| { - const struct_field = analyzer.module.types.container_fields.get(struct_field_index); - if (struct_field.name == identifier_hash) { - const field_access_index = try analyzer.module.values.field_accesses.append(analyzer.allocator, .{ - .declaration_reference = left_value_index, - .field = struct_field_index, - }); - break :blk Value{ - .field_access = field_access_index, - }; - } - } else { - const scope1 = struct_type.scope; - const scope2 = scope_index; - const declaration_value = try analyzer.doIdentifier(scope1, ExpectType.none, node.right.value, scope2); - - const value_ref = analyzer.module.values.array.get(declaration_value); - break :blk value_ref.*; - } - - unreachable; - }, - else => |t| @panic(@tagName(t)), - } - unreachable; - }, - .indexed_access => |indexed_access_index| { - const indexed_access = analyzer.module.values.indexed_accesses.get(indexed_access_index); - const indexed_expression_value = analyzer.module.values.array.get(indexed_access.indexed_expression); - const indexed_expression_type = analyzer.module.types.array.get(indexed_expression_value.getType(analyzer.module)); - - const element_type_index = switch (indexed_expression_type.*) { - .array => |array_type| array_type.element_type, - else => |t| @panic(@tagName(t)), - }; - - switch (analyzer.module.types.array.get(element_type_index).*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - - for (struct_type.fields.items) |struct_field_index| { - const struct_field = analyzer.module.types.container_fields.get(struct_field_index); - if (struct_field.name == identifier_hash) { - const field_access_index = try analyzer.module.values.field_accesses.append(analyzer.allocator, .{ - .declaration_reference = left_value_index, - .field = struct_field_index, - }); - break :blk Value{ - .field_access = field_access_index, - }; - } - } else { - const scope1 = struct_type.scope; - const scope2 = scope_index; - const declaration_value = try analyzer.doIdentifier(scope1, ExpectType.none, node.right.value, scope2); - - const value_ref = analyzer.module.values.array.get(declaration_value); - break :blk value_ref.*; - } - - unreachable; - }, - else => |t| @panic(@tagName(t)), - } - - unreachable; - }, - else => |t| @panic(@tagName(t)), - } - }, - .string_literal => .{ - .string_literal = try analyzer.processStringLiteral(scope_index, node_index), - }, - .@"switch" => try analyzer.processSwitch(scope_index, expect_type, node_index), - .enum_type => blk: { - const list_node = analyzer.getScopeNode(scope_index, node.left); - const field_node_list = switch (list_node.id) { - .node_list => analyzer.getScopeNodeList(scope_index, list_node), - else => |t| @panic(@tagName(t)), - }; - const file = analyzer.module.values.scopes.get(scope_index).file; - const enum_type = try analyzer.processContainerType(value_index, scope_index, field_node_list.items, file, node_index, .@"enum"); - break :blk .{ - .type = enum_type, - }; - }, - .assign => try analyzer.processAssignment(scope_index, node_index), - .signed_integer_type, .unsigned_integer_type => .{ - .type = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = node_index, - }), - }, - .@"return" => try analyzer.processReturn(scope_index, expect_type, node_index), - .add, - .sub, - .bit_and, - .bit_xor, - .bit_or, - .multiply, - .divide, - .shift_left, - .shift_right, - .compare_equal, - .compare_greater_than, - .compare_greater_or_equal, - .compare_less_than, - .compare_less_or_equal, - .compare_not_equal, - => try analyzer.processBinaryOperation(scope_index, expect_type, node_index), - .expression_group => return try analyzer.resolveNode(value_index, scope_index, expect_type, node.left), //unreachable, - .struct_type => blk: { - const left_node = analyzer.getScopeNode(scope_index, node.left); - const nodes = analyzer.getScopeNodeList(scope_index, left_node); - const scope = analyzer.module.values.scopes.get(scope_index); - const struct_type = try analyzer.processContainerType(value_index, scope_index, nodes.items, scope.file, node_index, .@"struct"); - break :blk .{ - .type = struct_type, - }; - }, - .boolean_not => blk: { - const typecheck_result = try analyzer.typeCheck(expect_type, Type.boolean); - assert(typecheck_result == .success); - const not_value_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.boolean, node.left); - const unary_index = try analyzer.module.values.unary_operations.append(analyzer.allocator, .{ - .id = .boolean_not, - .value = not_value_index, - .type = Type.boolean, - }); - - break :blk .{ - .unary_operation = unary_index, - }; - }, - .null_literal => switch (expect_type) { - .type_index => |type_index| switch (analyzer.module.types.array.get(type_index).*) { - .optional => |optional| switch (analyzer.module.types.array.get(optional.element_type).*) { - .pointer => Value.pointer_null_literal, - else => Value.optional_null_literal, - }, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }, - .negation => blk: { - const negation_value_index = try analyzer.unresolvedAllocate(scope_index, expect_type, node.left); - const unary_index = try analyzer.module.values.unary_operations.append(analyzer.allocator, .{ - .id = .negation, - .value = negation_value_index, - .type = analyzer.getValueType(negation_value_index), - }); - - break :blk .{ - .unary_operation = unary_index, - }; - }, - .address_of => try analyzer.addressOf(scope_index, expect_type, node_index), - .pointer_dereference => blk: { - const new_expect_type = switch (expect_type) { - .none => expect_type, - .type_index => |type_index| ExpectType{ - .type_index = try analyzer.getPointerType(.{ - .element_type = type_index, - .@"const" = true, // TODO - .many = false, // TODO - .termination = .none, // TODO - }), - }, - .flexible_integer => unreachable, - else => unreachable, - }; - const pointer_value_index = try analyzer.unresolvedAllocate(scope_index, new_expect_type, node.left); - const pointer_type = analyzer.module.types.array.get(analyzer.getValueType(pointer_value_index)); - assert(pointer_type.* == .pointer); - const element_type = pointer_type.pointer.element_type; - const unary_index = try analyzer.module.values.unary_operations.append(analyzer.allocator, .{ - .id = .pointer_dereference, - .value = pointer_value_index, - .type = element_type, - }); - - break :blk .{ - .unary_operation = unary_index, - }; - }, - .slice => blk: { - const expression_to_slice_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); - const expression_to_slice_type = analyzer.getValueType(expression_to_slice_index); - - const slice_index = try analyzer.module.values.slices.append(analyzer.allocator, .{ - .sliceable = expression_to_slice_index, - .range = try analyzer.range(scope_index, node.right), - .type = try analyzer.getSliceType(switch (analyzer.module.types.array.get(expression_to_slice_type).*) { - .pointer => |pointer| .{ - .@"const" = constblk: { - assert(pointer.many); - break :constblk pointer.@"const"; - }, - .element_type = pointer.element_type, - .termination = pointer.termination, - }, - .slice => |slice| slice, - .array => |array| b: { - const is_const = switch (expect_type) { - .type_index => |type_index| switch (analyzer.module.types.array.get(type_index).*) { - .slice => |slice| slice.@"const", - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }; - - break :b .{ - .@"const" = is_const, - .element_type = array.element_type, - .termination = array.termination, - }; - }, - else => |t| @panic(@tagName(t)), - }), - }); - - break :blk .{ - .slice = slice_index, - }; - }, - .indexed_access => blk: { - const indexable_expression_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); - // const indexable_expression_type = switch (analyzer.module.values.array.get(indexable_expression_index).*) { - // .declaration_reference => |declaration_reference| declaration_reference.type, - // else => |t| @panic(@tagName(t)), - // }; - const indexable_expression_type = analyzer.getValueType(indexable_expression_index); - switch (analyzer.module.types.array.get(indexable_expression_type).*) { - .slice => {}, - .array => {}, - .pointer => |pointer| { - switch (pointer.many) { - true => {}, - false => unreachable, // TODO: pointer to array? - } - }, - else => |t| @panic(@tagName(t)), - } - const index_expression_index = try analyzer.unresolvedAllocate(scope_index, ExpectType{ - .type_index = Type.usize, - }, node.right); - - const indexed_access_index = try analyzer.module.values.indexed_accesses.append(analyzer.allocator, .{ - .indexed_expression = indexable_expression_index, - .index_expression = index_expression_index, - }); - - break :blk .{ - .indexed_access = indexed_access_index, - }; - }, - .enum_literal => blk: { - const enum_literal_identifier_token = node.token + 1; - switch (expect_type) { - .type_index => |type_index| { - switch (analyzer.module.types.array.get(type_index).*) { - .@"enum" => |enum_index| { - const enum_type = analyzer.module.types.enums.get(enum_index).*; - const enum_field_index = try analyzer.typeCheckEnumLiteral(scope_index, enum_literal_identifier_token, enum_type) orelse unreachable; - - break :blk .{ - .enum_field = enum_field_index, - }; - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - }, - .undefined => .undefined, - .empty_container_literal_guess, .container_literal => blk: { - const list_nodes = analyzer.getScopeNodeList(scope_index, analyzer.getScopeNode(scope_index, node.right)); - const literal_type = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = node.left, - .length_hint = list_nodes.items.len, - }); - const container_initialization = try analyzer.analyzeContainerLiteral(scope_index, literal_type, node.right); - break :blk .{ - .container_initialization = container_initialization, - }; - }, - .anonymous_container_literal => blk: { - const t = switch (expect_type) { - .type_index => |type_index| type_index, - .addressable => |addressable| addressable.element_type, - .none => Type.Index.invalid, - else => |t| @panic(@tagName(t)), - }; - analyzer.debugNode(scope_index, node_index); - const container_initialization = try analyzer.analyzeContainerLiteral(scope_index, t, node.right); - break :blk .{ - .container_initialization = container_initialization, - }; - }, - .array_literal => blk: { - const list_nodes = analyzer.getScopeNodeList(scope_index, analyzer.getScopeNode(scope_index, node.right)); - const literal_type = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = node.left, - .length_hint = list_nodes.items.len, - }); - - const array_initialization = try analyzer.analyzeArrayLiteral(scope_index, node.right, switch (analyzer.module.types.array.get(literal_type).*) { - .array => |array| .{ - .element_type = array.element_type, - .len = array.element_count, - .termination = array.termination, - }, - else => |t| @panic(@tagName(t)), - }); - - break :blk .{ - .array_initialization = array_initialization, - }; - }, - .anonymous_array_literal => blk: { - const expected_array_type = switch (expect_type) { - .addressable => |addressable| addr: { - assert(addressable.many); - break :addr .{ - .element_type = addressable.element_type, - .termination = addressable.termination, - }; - }, - .type_index => |type_index| switch (analyzer.module.types.array.get(type_index).*) { - .array => |array_type| .{ - .element_type = array_type.element_type, - .termination = array_type.termination, - }, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }; - - const array_initialization = try analyzer.analyzeArrayLiteral(scope_index, node.right, expected_array_type); - break :blk .{ - .array_initialization = array_initialization, - }; - }, - .optional_unwrap => blk: { - const optional_expect_type = ExpectType{ - .type_index = try analyzer.getOptionalType(switch (expect_type) { - .type_index => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }), - }; - - const optional_value = try analyzer.unresolvedAllocate(scope_index, optional_expect_type, node.left); - - const optional_unwrap = try analyzer.module.values.optional_unwraps.append(analyzer.allocator, .{ - .value = optional_value, - }); - - const optional_unwrap_value = Value{ - .optional_unwrap = optional_unwrap, - }; - break :blk optional_unwrap_value; - }, - .anonymous_empty_literal => blk: { - const expected_type_index = switch (expect_type) { - .type_index => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }; - switch (analyzer.module.types.array.get(expected_type_index).*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - var list = try ArrayList(Value.Index).initCapacity(analyzer.allocator, struct_type.fields.items.len); - for (struct_type.fields.items) |struct_field_index| { - const struct_field = analyzer.module.types.container_fields.get(struct_field_index); - - if (struct_field.default_value.invalid) { - unreachable; - } - - logln(.sema, .type, "Field name: {s}", .{analyzer.module.getName(struct_field.name).?}); - - switch (analyzer.module.values.array.get(struct_field.default_value).*) { - .unresolved => |unresolved| { - logln(.sema, .type, "IN node index: #{}", .{unresolved.node_index.uniqueInteger()}); - try analyzer.resolveNode(struct_field.default_value, struct_type.scope, ExpectType{ - .type_index = struct_field.type, - }, unresolved.node_index); - }, - else => {}, - } - - list.appendAssumeCapacity(struct_field.default_value); - } - - const container_initialization_index = try analyzer.module.values.container_initializations.append(analyzer.allocator, .{ - .field_initializations = list, - .type = expected_type_index, - }); - break :blk Value{ - .container_initialization = container_initialization_index, - }; - }, - else => |t| @panic(@tagName(t)), - } - }, - .usize_type => Value{ - .type = Type.usize, - }, - .if_else => blk: { - const if_else_node_index = node_index; - const payload_node_index = Node.Index.invalid; - switch (try analyzer.processIfElse(scope_index, expect_type, if_else_node_index, payload_node_index)) { - .if_else => |if_else_value| { - assert(if_else_value.maybe_payload_declaration_index == null); - break :blk .{ - .branch = if_else_value.branch, - }; - }, - .expression => |expression_value_index| break :blk analyzer.module.values.array.get(expression_value_index).*, - } - }, - else => |t| @panic(@tagName(t)), - }; - - analyzer.module.values.array.get(value_index).* = new_value; - } - - fn analyzeArrayLiteral(analyzer: *Analyzer, scope_index: Scope.Index, node_list_node_index: Node.Index, expected_array_type: ExpectedArrayType) !Compilation.ContainerInitialization.Index { - const field_initialization_node_list = analyzer.getScopeNode(scope_index, node_list_node_index); - const field_nodes = analyzer.getScopeNodeList(scope_index, field_initialization_node_list); - assert(!expected_array_type.element_type.invalid); - - const found_element_count = field_nodes.items.len; - const element_count = if (expected_array_type.len) |ec| if (ec == found_element_count) ec else @panic("Element count mismatch in array literal") else found_element_count; - - var list = try ArrayList(Value.Index).initCapacity(analyzer.allocator, element_count); - const element_expect_type = ExpectType{ - .type_index = expected_array_type.element_type, - }; - - for (field_nodes.items) |element_node_index| { - const array_element_value_index = try analyzer.unresolvedAllocate(scope_index, element_expect_type, element_node_index); - list.appendAssumeCapacity(array_element_value_index); - // const element_node = analyzer.getScopeNode(scope_index, element_node_index); - } - - const container_initialization_index = try analyzer.module.values.container_initializations.append(analyzer.allocator, .{ - .field_initializations = list, - .type = try analyzer.getArrayType(.{ - .element_count = @intCast(element_count), - .element_type = expected_array_type.element_type, - .termination = expected_array_type.termination, - }), - }); - - return container_initialization_index; - } - - fn analyzeContainerLiteral(analyzer: *Analyzer, scope_index: Scope.Index, expected_type_index: Type.Index, node_list_node_index: Node.Index) !Compilation.ContainerInitialization.Index { - const field_initialization_node_list = analyzer.getScopeNode(scope_index, node_list_node_index); - const field_nodes = analyzer.getScopeNodeList(scope_index, field_initialization_node_list); - assert(!expected_type_index.invalid); - const expected_type = analyzer.module.types.array.get(expected_type_index); - - switch (expected_type.*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - - var list = try ArrayList(Value.Index).initCapacity(analyzer.allocator, struct_type.fields.items.len); - var bitset = try std.DynamicBitSetUnmanaged.initEmpty(analyzer.allocator, field_nodes.items.len); - - for (struct_type.fields.items) |struct_field_index| { - const struct_field = analyzer.module.types.container_fields.get(struct_field_index); - const struct_field_name = analyzer.module.getName(struct_field.name).?; - // logln(.sema, .type, "struct field name in container literal: {s}", .{struct_field_name}); - - var value_index = Value.Index.invalid; - - for (field_nodes.items, 0..) |field_node_index, index| { - const field_node = analyzer.getScopeNode(scope_index, field_node_index); - assert(field_node.id == .container_field_initialization); - const identifier = analyzer.tokenIdentifier(scope_index, field_node.token + 1); - const identifier_index = try analyzer.processIdentifier(identifier); - - if (struct_field.name == identifier_index) { - if (!value_index.invalid) { - @panic("Field initialized twice"); - } - - bitset.set(index); - - value_index = try analyzer.unresolvedAllocate(scope_index, ExpectType{ - .type_index = struct_field.type, - }, field_node.left); - } - } - - if (value_index.invalid) { - if (!struct_field.default_value.invalid) { - switch (analyzer.module.values.array.get(struct_field.default_value).*) { - .unresolved => |unresolved| { - logln(.sema, .type, "Node index: #{}", .{unresolved.node_index.uniqueInteger()}); - try analyzer.resolveNode(struct_field.default_value, struct_type.scope, ExpectType{ - .type_index = struct_field.type, - }, unresolved.node_index); - }, - else => {}, - } - - assert(analyzer.module.values.array.get(struct_field.default_value).* != .unresolved); - - value_index = struct_field.default_value; - } else { - std.debug.panic("Field \"{s}\" forgotten in struct initialization", .{struct_field_name}); - } - } - - logln(.sema, .type, "struct field {s}, container field #{}", .{ struct_field_name, struct_field_index.uniqueInteger() }); - - list.appendAssumeCapacity(value_index); - } - - if (bitset.count() != bitset.bit_length) { - @panic("Some field name in struct initialization is wrong"); - } - - const container_initialization_index = try analyzer.module.values.container_initializations.append(analyzer.allocator, .{ - .field_initializations = list, - .type = expected_type_index, - }); - return container_initialization_index; - }, - .array => |array_type| { - if (field_nodes.items.len != array_type.element_count) { - unreachable; - } - - var list = try ArrayList(Value.Index).initCapacity(analyzer.allocator, array_type.element_count); - - const expect_type = ExpectType{ - .type_index = array_type.element_type, - }; - - for (field_nodes.items) |array_element_node_index| { - const element_value_index = try analyzer.unresolvedAllocate(scope_index, expect_type, array_element_node_index); - list.appendAssumeCapacity(element_value_index); - } - - const container_initialization_index = try analyzer.module.values.container_initializations.append(analyzer.allocator, .{ - .field_initializations = list, - .type = expected_type_index, - }); - return container_initialization_index; - }, - .optional => |optional_type| { - return try analyzer.analyzeContainerLiteral(scope_index, optional_type.element_type, node_list_node_index); - }, - else => |t| @panic(@tagName(t)), - } - } - - fn debugNode(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) void { - const node = analyzer.getScopeNode(scope_index, node_index); - analyzer.debugToken(scope_index, node.token); - } - - fn debugToken(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index) void { - const source_file = analyzer.getScopeSourceFile(scope_index); - const token = analyzer.getScopeToken(scope_index, token_index); - logln(.sema, .debug, "Debugging:\n\n```\n{s}\n```", .{source_file[token.start..]}); - } - - fn processStringLiteral(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !StringLiteral { - const string_literal_node = analyzer.getScopeNode(scope_index, node_index); - assert(string_literal_node.id == .string_literal); - const original_string_literal = analyzer.tokenStringLiteral(scope_index, string_literal_node.token); - const fixed_string_literal = try fixupStringLiteral(analyzer.allocator, original_string_literal); - const string_literal = switch (analyzer.module.descriptor.transpile_to_c) { - true => original_string_literal, - false => fixed_string_literal, - }; - - const len: u32 = @intCast(fixed_string_literal.len); - const array_type_descriptor = Type.Array{ - .element_type = Type.u8, - .element_count = len, - .termination = .null, - }; - const array_type = try analyzer.getArrayType(array_type_descriptor); - - const pointer_type = try analyzer.getPointerType(.{ - .many = true, - .@"const" = true, - .element_type = array_type, - .termination = array_type_descriptor.termination, - }); - - const hash = try Module.addString(&analyzer.module.map.strings, analyzer.allocator, string_literal); - - return StringLiteral{ - .hash = hash, - .type = pointer_type, - }; - } - - fn fixupStringLiteral(allocator: Allocator, string_literal: []const u8) ![]const u8 { - var result = try ArrayList(u8).initCapacity(allocator, string_literal.len); - var i: usize = 0; - - while (i < string_literal.len) : (i += 1) { - const ch = string_literal[i]; - if (ch != '\\') { - result.appendAssumeCapacity(ch); - } else { - const next_ch: u8 = switch (string_literal[i + 1]) { - 'n' => '\n', - else => |next_ch| panic("Unexpected character: {c}, 0x{x}", .{ next_ch, next_ch }), - }; - result.appendAssumeCapacity(next_ch); - i += 1; - } - } - - return result.items; - } - - fn functionPrototypeReturnType(analyzer: *Analyzer, function_prototype_index: Function.Prototype.Index) Type.Index { - const function_prototype = analyzer.module.types.function_prototypes.get(function_prototype_index); - return function_prototype.return_type; - } - - fn resolveType(analyzer: *Analyzer, args: struct { - scope_index: Scope.Index, - node_index: Node.Index, - length_hint: ?usize = null, - allow_non_primitive_size: bool = false, - }) anyerror!Type.Index { - const scope_index = args.scope_index; - const node_index = args.node_index; - - const type_node = analyzer.getScopeNode(scope_index, node_index); - const type_index: Type.Index = switch (type_node.id) { - .identifier => blk: { - const resolved_value_index = try analyzer.doIdentifier(scope_index, ExpectType.type, type_node.token, scope_index); - const resolved_value = analyzer.module.values.array.get(resolved_value_index); - break :blk switch (resolved_value.*) { - .type => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }; - }, - .keyword_noreturn => Type.noreturn, - inline .signed_integer_type, .unsigned_integer_type => |int_type_signedness| blk: { - const bit_count: u16 = @intCast(type_node.left.value); - break :blk switch (bit_count) { - inline 8, 16, 32, 64 => |hardware_bit_count| Type.Integer.getIndex(.{ - .bit_count = hardware_bit_count, - .signedness = switch (int_type_signedness) { - .signed_integer_type => .signed, - .unsigned_integer_type => .unsigned, - else => @compileError("OOO"), - }, - }), - else => switch (args.allow_non_primitive_size) { - true => b: { - const integer = .{ - .bit_count = bit_count, - .signedness = switch (int_type_signedness) { - .signed_integer_type => .signed, - .unsigned_integer_type => .unsigned, - else => @compileError("OOO"), - }, - }; - const gop = try analyzer.module.map.non_primitive_integer.getOrPut(analyzer.allocator, integer); - - if (!gop.found_existing) { - const type_index = try analyzer.module.types.array.append(analyzer.allocator, .{ - .integer = integer, - }); - - gop.value_ptr.* = type_index; - } - - const result = gop.value_ptr.*; - break :b result; - }, - false => @panic("non primitive size not allowed"), - }, - }; - }, - .void_type => Type.void, - .ssize_type => Type.ssize, - .usize_type => Type.usize, - .bool_type => Type.boolean, - .simple_function_prototype => blk: { - const function_prototype_index = try analyzer.module.types.function_prototypes.append(analyzer.allocator, try analyzer.processSimpleFunctionPrototype(scope_index, node_index)); - - const function_type_index = try analyzer.module.types.array.append(analyzer.allocator, .{ - .function = function_prototype_index, - }); - break :blk function_type_index; - }, - .field_access => blk: { - const type_value_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node_index); - const type_value_ptr = analyzer.module.values.array.get(type_value_index); - assert(type_value_ptr.* == .type); - break :blk type_value_ptr.type; - }, - .optional_type => blk: { - const element_type = try resolveType(analyzer, .{ - .scope_index = scope_index, - .node_index = type_node.left, - }); - - break :blk try analyzer.getOptionalType(element_type); - }, - .pointer_type => blk: { - const list_node = analyzer.getScopeNode(scope_index, type_node.left); - const node_list = analyzer.getScopeNodeList(scope_index, list_node); - - var is_const = false; - var type_index = Type.Index.invalid; - var termination = Termination.none; - var many = false; - - for (node_list.items) |element_node_index| { - const element_node = analyzer.getScopeNode(scope_index, element_node_index); - switch (element_node.id) { - .simple_function_prototype, - .identifier, - .unsigned_integer_type, - .signed_integer_type, - .optional_type, - .array_type, - .usize_type, - .pointer_type, - => { - if (!type_index.invalid) { - unreachable; - } - type_index = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = element_node_index, - }); - }, - .const_expression => is_const = true, - .many_pointer_expression => many = true, - .zero_terminated => { - assert(many); - assert(termination == .none); - termination = .zero; - }, - .null_terminated => { - assert(many); - assert(termination == .none); - termination = .null; - }, - else => |t| @panic(@tagName(t)), - } - } - - assert(!type_index.invalid); - - break :blk try analyzer.getPointerType(.{ - .@"const" = is_const, - .many = many, - .element_type = type_index, - .termination = termination, - }); - }, - .slice_type => blk: { - const list_node = analyzer.getScopeNode(scope_index, type_node.left); - const node_list = analyzer.getScopeNodeList(scope_index, list_node); - - var is_const = false; - var type_index = Type.Index.invalid; - var termination = Termination.none; - - for (node_list.items) |element_node_index| { - const element_node = analyzer.getScopeNode(scope_index, element_node_index); - switch (element_node.id) { - .simple_function_prototype, - .identifier, - .unsigned_integer_type, - => { - if (!type_index.invalid) { - unreachable; - } - type_index = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = element_node_index, - }); - }, - .const_expression => is_const = true, - .zero_terminated => { - if (termination != .none) { - unreachable; - } - termination = .zero; - }, - else => |t| @panic(@tagName(t)), - } - } - - assert(!type_index.invalid); - - break :blk try analyzer.getSliceType(.{ - .@"const" = is_const, - .element_type = type_index, - .termination = termination, - }); - }, - .array_type => blk: { - const list_node = analyzer.getScopeNode(scope_index, type_node.left); - const node_list = analyzer.getScopeNodeList(scope_index, list_node); - - var termination: ?Termination = null; - var length_expression: ?usize = null; - - for (node_list.items[0 .. node_list.items.len - 1]) |element_node_index| { - const element_node = analyzer.getScopeNode(scope_index, element_node_index); - switch (element_node.id) { - .identifier, - .number_literal, - .add, - => { - if (length_expression != null) { - unreachable; - } - - length_expression = analyzer.resolveInteger(scope_index, try analyzer.unresolvedAllocate(scope_index, ExpectType{ - .type_index = Type.usize, - }, element_node_index)); - }, - .discard => { - const length = args.length_hint orelse unreachable; - length_expression = length; - }, - .null_terminated => { - if (termination != null) { - unreachable; - } - termination = .null; - }, - else => |t| @panic(@tagName(t)), - } - } - - assert(length_expression != null); - - const type_index = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = node_list.items[node_list.items.len - 1], - }); - - assert(!type_index.invalid); - - break :blk try analyzer.getArrayType(.{ - .element_type = type_index, - .element_count = length_expression orelse unreachable, - .termination = termination orelse .none, - }); - }, - else => |t| @panic(@tagName(t)), - }; - return type_index; - } - - fn processSimpleFunctionPrototype(analyzer: *Analyzer, old_scope_index: Scope.Index, simple_function_prototype_node_index: Node.Index) !Function.Prototype { - const simple_function_prototype_node = analyzer.getScopeNode(old_scope_index, simple_function_prototype_node_index); - assert(simple_function_prototype_node.id == .simple_function_prototype); - const arguments_node_index = simple_function_prototype_node.left; - const return_type_node_index = simple_function_prototype_node.right; - - const scope_index = try analyzer.module.values.scopes.append(analyzer.allocator, .{ - .parent = old_scope_index, - .file = analyzer.module.values.scopes.get(old_scope_index).file, - .token = simple_function_prototype_node.token, - }); - - var argument_declarations = ArrayList(Declaration.Index){}; - switch (arguments_node_index.invalid) { - true => {}, - false => { - const argument_list_node = analyzer.getScopeNode(scope_index, arguments_node_index); - // logln("Function prototype argument list node: {}\n", .{function_prototype_node.left.uniqueInteger()}); - const argument_node_list = switch (argument_list_node.id) { - .node_list => analyzer.getScopeNodeList(scope_index, argument_list_node), - else => |t| @panic(@tagName(t)), - }; - - assert(argument_node_list.items.len > 0); - if (argument_node_list.items.len > 0) { - argument_declarations = try ArrayList(Declaration.Index).initCapacity(analyzer.allocator, argument_node_list.items.len); - - for (argument_node_list.items, 0..) |argument_node_index, index| { - const argument_node = analyzer.getScopeNode(scope_index, argument_node_index); - switch (argument_node.id) { - .argument_declaration => { - const argument_name = analyzer.tokenIdentifier(scope_index, argument_node.token); - const argument_type = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = argument_node.left, - }); - const argument_declaration_type = Declaration.Type{ - .resolved = argument_type, - }; - const argument_declaration = try analyzer.declarationCommon(scope_index, .local, .@"const", argument_name, argument_declaration_type, Value.Index.invalid, @intCast(index)); - argument_declarations.appendAssumeCapacity(argument_declaration); - }, - else => |t| @panic(@tagName(t)), - } - } - } - }, - } - - const return_type = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = return_type_node_index, - }); - - return .{ - .arguments = argument_declarations, - .return_type = return_type, - .scope = scope_index, - }; - } - - fn functionPrototype(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Function.Prototype.Index { - const function_prototype_node = analyzer.getScopeNode(scope_index, node_index); - switch (function_prototype_node.id) { - .simple_function_prototype => { - const function_prototype_index = try analyzer.module.types.function_prototypes.append(analyzer.allocator, try analyzer.processSimpleFunctionPrototype(scope_index, node_index)); - - return function_prototype_index; - }, - .function_prototype => { - var function_prototype = try analyzer.processSimpleFunctionPrototype(scope_index, function_prototype_node.left); - const function_prototype_attribute_list_node = analyzer.getScopeNode(scope_index, function_prototype_node.right); - const attribute_node_list = analyzer.getScopeNodeList(scope_index, function_prototype_attribute_list_node); - var calling_convention: ?Compilation.CallingConvention = null; - - for (attribute_node_list.items) |attribute_node_index| { - const attribute_node = analyzer.getScopeNode(scope_index, attribute_node_index); - - switch (attribute_node.id) { - .export_qualifier => function_prototype.attributes.@"export" = true, - .extern_qualifier => { - const string_literal_node = analyzer.getScopeNode(scope_index, attribute_node.left); - const original_string_literal = analyzer.tokenStringLiteral(scope_index, string_literal_node.token); - const fixed_string_literal = try fixupStringLiteral(analyzer.allocator, original_string_literal); - _ = try analyzer.module.map.libraries.getOrPut(analyzer.allocator, fixed_string_literal); - function_prototype.attributes.@"extern" = true; - }, - .calling_convention => { - const calling_convention_type_declaration = try analyzer.forceDeclarationAnalysis(scope_index, "std.builtin.CallingConvention"); - const calling_convention_type = switch (analyzer.module.values.array.get(calling_convention_type_declaration).*) { - .type => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }; - const cc_value = try analyzer.unresolvedAllocate(scope_index, ExpectType{ - .type_index = calling_convention_type, - }, attribute_node.left); - - switch (analyzer.module.values.array.get(cc_value).*) { - .enum_field => |enum_field_index| { - const enum_field = analyzer.module.types.enum_fields.get(enum_field_index); - const enum_field_name = analyzer.module.getName(enum_field.name).?; - - calling_convention = inline for (@typeInfo(Compilation.CallingConvention).Enum.fields) |cc_enum_field| { - if (equal(u8, cc_enum_field.name, enum_field_name)) { - break @field(Compilation.CallingConvention, cc_enum_field.name); - } - } else unreachable; - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - } - - function_prototype.attributes.calling_convention = calling_convention orelse Compilation.CallingConvention.system_v; - - const function_prototype_index = try analyzer.module.types.function_prototypes.append(analyzer.allocator, function_prototype); - return function_prototype_index; - }, - else => |t| @panic(@tagName(t)), - } - } - - fn forceDeclarationAnalysis(analyzer: *Analyzer, scope_index: Scope.Index, whole_expression: []const u8) !Value.Index { - var expression_iterator = std.mem.tokenizeScalar(u8, whole_expression, '.'); - var before_expression = Value.Index.invalid; - var last_scope = scope_index; - - while (expression_iterator.next()) |expression_name| { - const result = switch (before_expression.invalid) { - true => try analyzer.doIdentifierString(scope_index, ExpectType.type, expression_name, scope_index), - false => blk: { - const expression_name_hash = try analyzer.processIdentifier(expression_name); - switch (analyzer.module.values.array.get(before_expression).*) { - .type => |type_index| { - const expression_type = analyzer.module.types.array.get(type_index); - switch (expression_type.*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - const struct_type_scope = analyzer.module.values.scopes.get(struct_type.scope); - const declaration_index = struct_type_scope.declarations.get(expression_name_hash).?; - const declaration = analyzer.module.values.declarations.get(declaration_index); - assert(declaration.name == expression_name_hash); - last_scope = declaration.scope; - - break :blk declaration.init_value; - }, - else => |t| @panic(@tagName(t)), - } - }, - .unresolved => |unresolved| { - try analyzer.resolveNode(before_expression, last_scope, ExpectType.none, unresolved.node_index); - switch (analyzer.module.values.array.get(before_expression).*) { - .type => |type_index| { - const expression_type = analyzer.module.types.array.get(type_index); - switch (expression_type.*) { - .@"struct" => |struct_index| { - const struct_type = analyzer.module.types.structs.get(struct_index); - const struct_type_scope = analyzer.module.values.scopes.get(struct_type.scope); - const declaration_index = struct_type_scope.declarations.get(expression_name_hash).?; - const declaration = analyzer.module.values.declarations.get(declaration_index); - assert(declaration.name == expression_name_hash); - last_scope = declaration.scope; - - break :blk declaration.init_value; - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - }, - }; - - before_expression = result; - } - - switch (analyzer.module.values.array.get(before_expression).*) { - .unresolved => |unresolved| { - try analyzer.resolveNode(before_expression, last_scope, ExpectType.none, unresolved.node_index); - }, - else => {}, - } - - return before_expression; - } - - fn processContainerType(analyzer: *Analyzer, value_index: Value.Index, parent_scope_index: Scope.Index, container_nodes: []const Node.Index, file_index: File.Index, container_node_index: Node.Index, comptime container_type: Compilation.ContainerType) !Type.Index { - const container_node = analyzer.getFileNode(file_index, container_node_index); - switch (container_type) { - .@"struct" => assert(container_node.id == .struct_type), - .@"enum" => assert(container_node.id == .enum_type), - } - const scope_index = try analyzer.module.values.scopes.append(analyzer.allocator, .{ - .parent = parent_scope_index, - .file = file_index, - .token = container_node.token, - }); - logln(.sema, .type, "Creating container scope #{}. Parent: #{}", .{ - scope_index.uniqueInteger(), switch (parent_scope_index.invalid) { - true => 0xffff_ffff, - false => parent_scope_index.uniqueInteger(), - }, - }); - const is_file = parent_scope_index.invalid; - const backing_type = blk: { - if (!is_file) { - if (analyzer.getScopeToken(parent_scope_index, container_node.token + 1).id == .left_parenthesis) { - const backing_type_token = analyzer.getScopeToken(parent_scope_index, container_node.token + 2); - const source_file = analyzer.getScopeSourceFile(parent_scope_index); - const token_bytes = tokenBytes(backing_type_token, source_file); - - break :blk switch (backing_type_token.id) { - .keyword_unsigned_integer => if (equal(u8, token_bytes, "u8")) Type.u8 else if (equal(u8, token_bytes, "u16")) Type.u16 else if (equal(u8, token_bytes, "u32")) Type.u32 else if (equal(u8, token_bytes, "u64")) Type.u64 else if (equal(u8, token_bytes, "usize")) Type.usize else unreachable, - .fixed_keyword_usize => Type.usize, - else => |t| @panic(@tagName(t)), - }; - } - } - - break :blk Type.Index.invalid; - }; - - const container_descriptor = .{ - .scope = scope_index, - .backing_type = backing_type, - }; - const container_type_descriptor = switch (container_type) { - .@"struct" => blk: { - const struct_index = try analyzer.module.types.structs.append(analyzer.allocator, container_descriptor); - break :blk Type{ - .@"struct" = struct_index, - }; - }, - .@"enum" => blk: { - const enum_index = try analyzer.module.types.enums.append(analyzer.allocator, container_descriptor); - break :blk Type{ - .@"enum" = enum_index, - }; - }, - }; - - const container_type_index = try analyzer.module.types.array.append(analyzer.allocator, container_type_descriptor); - if (is_file) { - const file = analyzer.module.values.files.get(file_index); - file.type = container_type_index; - } - - analyzer.module.values.scopes.get(scope_index).type = container_type_index; - analyzer.module.values.array.get(value_index).* = .{ - .type = container_type_index, - }; - - if (!analyzer.current_declaration.invalid) { - const current_declaration = analyzer.module.values.declarations.get(analyzer.current_declaration); - switch (current_declaration.type) { - .inferred => |*inferred_type_index| { - assert(inferred_type_index.invalid); - inferred_type_index.* = Type.type; - }, - else => |t| @panic(@tagName(t)), - } - } - - const count = blk: { - var result: struct { - fields: u32 = 0, - declarations: u32 = 0, - } = .{}; - - for (container_nodes) |member_index| { - const member = analyzer.getFileNode(file_index, member_index); - switch (container_type) { - .@"struct" => assert(member.id != .enum_field), - .@"enum" => assert(member.id != .container_field), - } - const member_type = getContainerMemberType(member.id); - - switch (member_type) { - .declaration => result.declarations += 1, - .field => result.fields += 1, - } - } - - break :blk result; - }; - - var declaration_nodes = try ArrayList(Node.Index).initCapacity(analyzer.allocator, count.declarations); - var field_nodes = try ArrayList(Node.Index).initCapacity(analyzer.allocator, count.fields); - - for (container_nodes) |member_index| { - const member = analyzer.getFileNode(file_index, member_index); - const member_type = getContainerMemberType(member.id); - const array_list = switch (member_type) { - .declaration => &declaration_nodes, - .field => &field_nodes, - }; - array_list.appendAssumeCapacity(member_index); - } - - for (declaration_nodes.items) |declaration_node_index| { - const declaration_node = analyzer.getFileNode(file_index, declaration_node_index); - switch (declaration_node.id) { - .@"comptime" => {}, - .simple_symbol_declaration => _ = try analyzer.symbolDeclaration(scope_index, declaration_node_index, .global), - else => unreachable, - } - } - - if (field_nodes.items.len > 0) { - // This is done in order for the names inside fields not to collision with the declaration ones - const field_scope_index = try analyzer.module.values.scopes.append(analyzer.allocator, .{ - .token = analyzer.getScopeNode(scope_index, field_nodes.items[0]).token, - .file = file_index, - .parent = scope_index, - }); - - logln(.sema, .type, "Creating container field scope #{}. Parent: #{}", .{ field_scope_index.uniqueInteger(), scope_index.uniqueInteger() }); - - switch (container_type) { - .@"struct" => { - { - const struct_type_general = analyzer.module.types.array.get(container_type_index); - const struct_type = analyzer.module.types.structs.get(struct_type_general.@"struct"); - struct_type.fields = try ArrayList(Compilation.ContainerField.Index).initCapacity(analyzer.allocator, field_nodes.items.len); - } - - for (field_nodes.items) |field_index| { - const field_node = analyzer.getFileNode(file_index, field_index); - const identifier = analyzer.tokenIdentifier(field_scope_index, field_node.token); - const file_path = analyzer.module.values.files.get(file_index).relative_path; - logln(.sema, .type, "Field node index for '{s}' in file {s}", .{ identifier, file_path }); - const identifier_index = try analyzer.processIdentifier(identifier); - const type_index = try analyzer.resolveType(.{ - .scope_index = field_scope_index, - .node_index = field_node.left, - .allow_non_primitive_size = !backing_type.invalid, - }); - - const default_value = if (field_node.right.invalid) Value.Index.invalid else try analyzer.module.values.array.append(analyzer.allocator, .{ - .unresolved = .{ - .node_index = blk: { - const def_node = analyzer.getScopeNode(scope_index, field_node.right); - assert(def_node.id != .node_list); - break :blk field_node.right; - }, - }, - }); - - const container_field_index = try analyzer.module.types.container_fields.append(analyzer.allocator, .{ - .name = identifier_index, - .type = type_index, - .default_value = default_value, - .parent = container_type_index, - }); - - { - const struct_type_general = analyzer.module.types.array.get(container_type_index); - const struct_type = analyzer.module.types.structs.get(struct_type_general.@"struct"); - struct_type.fields.appendAssumeCapacity(container_field_index); - } - } - }, - .@"enum" => { - { - const enum_type_general = analyzer.module.types.array.get(container_type_index); - const enum_type = analyzer.module.types.enums.get(enum_type_general.@"enum"); - enum_type.fields = try ArrayList(Compilation.Type.Enum.Field.Index).initCapacity(analyzer.allocator, field_nodes.items.len); - } - - for (field_nodes.items) |field_node_index| { - const field_node = analyzer.getScopeNode(scope_index, field_node_index); - assert(field_node.id == .enum_field); - - const identifier = analyzer.tokenIdentifier(scope_index, field_node.token); - logln(.sema, .node, "Enum field: {s}", .{identifier}); - const enum_value = switch (field_node.left.invalid) { - false => try analyzer.unresolvedAllocate(scope_index, ExpectType{ - .type_index = Type.usize, - }, field_node.left), - true => Value.Index.invalid, - }; - - const enum_hash_name = try analyzer.processIdentifier(identifier); - - const enum_field_index = try analyzer.module.types.enum_fields.append(analyzer.allocator, .{ - .name = enum_hash_name, - .value = enum_value, - .parent = container_type_index, - }); - - const enum_type_general = analyzer.module.types.array.get(container_type_index); - const enum_type = analyzer.module.types.enums.get(enum_type_general.@"enum"); - enum_type.fields.appendAssumeCapacity(enum_field_index); - } - }, - } - } - - // TODO: consider iterating over scope declarations instead? - for (declaration_nodes.items) |declaration_node_index| { - const declaration_node = analyzer.getFileNode(file_index, declaration_node_index); - switch (declaration_node.id) { - .@"comptime" => _ = try analyzer.comptimeBlock(scope_index, declaration_node_index), - .simple_symbol_declaration => {}, - else => |t| @panic(@tagName(t)), - } - } - - return container_type_index; - } - - fn declarationCommon(analyzer: *Analyzer, scope_index: Scope.Index, scope_type: ScopeType, mutability: Compilation.Mutability, name: []const u8, declaration_type: Declaration.Type, init_value: Value.Index, argument_index: ?u32) !Declaration.Index { - const identifier_index = try analyzer.processIdentifier(name); - - if (analyzer.lookupDeclarationInCurrentAndParentScopes(scope_index, identifier_index)) |lookup| { - const declaration = analyzer.module.values.declarations.get(lookup.declaration); - const declaration_name = analyzer.module.getName(declaration.name).?; - panic("Existing name in lookup: {s}.\nSource scope: #{}. Lookup scope: #{}", .{ declaration_name, scope_index.uniqueInteger(), lookup.scope.uniqueInteger() }); - } - - // Check if the symbol name is already occupied in the same scope - const scope = analyzer.module.values.scopes.get(scope_index); - const declaration_index = try analyzer.module.values.declarations.append(analyzer.allocator, .{ - .name = identifier_index, - .scope_type = scope_type, - .mutability = mutability, - .init_value = init_value, - .type = declaration_type, - .argument_index = argument_index, - .scope = scope_index, - }); - - try scope.declarations.putNoClobber(analyzer.allocator, identifier_index, declaration_index); - - return declaration_index; - } - - fn symbolDeclaration(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index, scope_type: ScopeType) !Declaration.Index { - const declaration_node = analyzer.getScopeNode(scope_index, node_index); - assert(declaration_node.id == .simple_symbol_declaration); - const expected_identifier_token_index = declaration_node.token + 1; - const identifier = analyzer.tokenIdentifier(scope_index, expected_identifier_token_index); - logln(.sema, .type, "Analyzing '{s}' declaration in {s} scope #{}", .{ identifier, @tagName(scope_type), scope_index.uniqueInteger() }); - - const expect_type = switch (declaration_node.left.invalid) { - false => switch (scope_type) { - .local => ExpectType{ - .type_index = try analyzer.resolveType(.{ - .scope_index = scope_index, - .node_index = declaration_node.left, - }), - }, - .global => ExpectType.none, - }, - true => ExpectType.none, - }; - - const mutability: Compilation.Mutability = switch (analyzer.getScopeToken(scope_index, declaration_node.token).id) { - .fixed_keyword_const => .@"const", - .fixed_keyword_var => .@"var", - else => |t| @panic(@tagName(t)), - }; - const expected_identifier_token = analyzer.getScopeToken(scope_index, expected_identifier_token_index); - if (expected_identifier_token.id != .identifier) { - logln(.sema, .symbol_declaration, "Error: found: {}", .{expected_identifier_token.id}); - @panic("Expected identifier"); - } - // TODO: Check if it is a keyword - - assert(!declaration_node.right.invalid); - - const argument = null; - assert(argument == null); - - const init_value_index = switch (scope_type) { - .local => try analyzer.unresolvedAllocate(scope_index, expect_type, declaration_node.right), - .global => try analyzer.module.values.array.append(analyzer.allocator, .{ - .unresolved = .{ - .node_index = declaration_node.right, - }, - }), - }; - - assert(argument == null); - const declaration_type: Declaration.Type = switch (declaration_node.left.invalid) { - false => switch (scope_type) { - .local => .{ - .resolved = expect_type.type_index, - }, - .global => .{ - .unresolved = declaration_node.left, - }, - }, - true => switch (scope_type) { - .local => .{ - .inferred = switch (expect_type) { - .none => analyzer.module.values.array.get(init_value_index).getType(analyzer.module), - .type_index => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }, - }, - .global => .{ - .inferred = Type.Index.invalid, - }, - }, - }; - - const result = try analyzer.declarationCommon(scope_index, scope_type, mutability, identifier, declaration_type, init_value_index, argument); - - return result; - } - - const MemberType = enum { - declaration, - field, - }; - - fn getContainerMemberType(member_id: Node.Id) MemberType { - return switch (member_id) { - .@"comptime", - .simple_symbol_declaration, - => .declaration, - .enum_field, - .container_field, - => .field, - else => |t| @panic(@tagName(t)), - }; - } - - fn processIdentifier(analyzer: *Analyzer, string: []const u8) !u32 { - return analyzer.module.addName(analyzer.allocator, string); - } - - fn tokenIdentifier(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index) []const u8 { - const token = analyzer.getScopeToken(scope_index, token_index); - // logln(.sema, .identifier, "trying to get identifier from token of id {s}", .{@tagName(token.id)}); - assert(token.id == .identifier); - const source_file = analyzer.getScopeSourceFile(scope_index); - const identifier = tokenBytes(token, source_file); - - return identifier; - } - - fn tokenBytes(token: Token, source_code: []const u8) []const u8 { - return source_code[token.start..][0..token.len]; - } - - fn numberBytes(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index) []const u8 { - const token = analyzer.getScopeToken(scope_index, token_index); - assert(token.id == .number_literal); - const source_file = analyzer.getScopeSourceFile(scope_index); - const bytes = tokenBytes(token, source_file); - - return bytes; - } - - fn tokenStringLiteral(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index) []const u8 { - const token = analyzer.getScopeToken(scope_index, token_index); - assert(token.id == .string_literal); - const source_file = analyzer.getScopeSourceFile(scope_index); - // Eat double quotes - const string_literal = tokenBytes(token, source_file)[1..][0 .. token.len - 2]; - - return string_literal; - } - - const TypeCheckResult = enum { - success, - zero_extend, - sign_extend, - take_source, - optional_wrap, - array_coerce_to_slice, - }; - - fn canCast(analyzer: *Analyzer, expect_type: ExpectType, source: Type.Index) !TypeCheckResult { - return switch (expect_type) { - .none => unreachable, - .flexible_integer => |flexible_integer| blk: { - const source_type = analyzer.module.types.array.get(source); - break :blk switch (source_type.*) { - .pointer => .success, - .optional => b: { - const optional_element_type = analyzer.module.types.array.get(source_type.optional.element_type); - break :b switch (optional_element_type.*) { - .pointer => .success, - else => |t| @panic(@tagName(t)), - }; - }, - .@"struct" => |struct_type_index| { - const struct_type = analyzer.module.types.structs.get(struct_type_index); - if (!struct_type.backing_type.invalid) { - const backing_integer_type = analyzer.module.types.array.get(struct_type.backing_type); - if (backing_integer_type.integer.bit_count >> 3 <= flexible_integer.byte_count) { - return .success; - } else { - unreachable; - } - } - - unreachable; - }, - .@"enum" => |enum_index| { - const enum_type = analyzer.module.types.enums.get(enum_index); - if (!enum_type.backing_type.invalid) { - const backing_integer_type = analyzer.module.types.array.get(enum_type.backing_type); - if (backing_integer_type.integer.bit_count >> 3 <= flexible_integer.byte_count) { - return .success; - } else { - unreachable; - } - } else { - unreachable; - } - }, - // TODO: integer cast - .integer => return .success, - else => |t| @panic(@tagName(t)), - }; - }, - .type_index => |type_index| blk: { - if (source.eq(type_index)) { - // TODO: turn into a compiler error - return .success; - } else { - const destination_type = analyzer.module.types.array.get(type_index); - const source_type = analyzer.module.types.array.get(source); - - break :blk switch (source_type.*) { - .integer => |integer| switch (destination_type.*) { - .optional => |optional| switch (analyzer.module.types.array.get(optional.element_type).*) { - .pointer => if (integer.bit_count == 64) .success else unreachable, - .integer => .optional_wrap, - else => |t| @panic(@tagName(t)), - }, - .integer => .success, - .pointer => .success, - else => |t| @panic(@tagName(t)), - }, - .pointer => switch (destination_type.*) { - .optional => |destination_optional| switch (analyzer.module.types.array.get(destination_optional.element_type).*) { - .pointer => .success, - else => |t| @panic(@tagName(t)), - }, - else => .success, - }, - .@"enum" => |enum_type_descriptor| switch (destination_type.*) { - .integer => |integer| { - _ = integer; - const enum_type = analyzer.module.types.enums.get(enum_type_descriptor); - if (!enum_type.backing_type.invalid) { - if (enum_type.backing_type.eq(type_index)) { - return .success; - } else { - unreachable; - } - } else { - return .success; - } - }, - else => |t| @panic(@tagName(t)), - }, - .slice => switch (destination_type.*) { - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }; - } - }, - else => unreachable, - }; - } - - fn typeCheck(analyzer: *Analyzer, expect_type: ExpectType, source: Type.Index) !TypeCheckResult { - return switch (expect_type) { - .none => TypeCheckResult.success, - .type_index => |expected_type_index| { - if (expected_type_index.eq(source)) { - return TypeCheckResult.success; - } - - const destination_type = analyzer.module.types.array.get(expected_type_index); - const source_type = analyzer.module.types.array.get(source); - - switch (destination_type.*) { - .type => switch (source_type.* == .type) { - true => return TypeCheckResult.success, - false => unreachable, - }, - .integer => |destination_int| switch (source_type.*) { - .integer => |source_int| { - const dst_size = destination_int.getSize(); - const src_size = source_int.getSize(); - logln(.sema, .typecheck, "Dst size: {}. Src size: {}", .{ dst_size, src_size }); - if (dst_size < src_size) { - @panic("Destination integer type is smaller than source"); - } else if (dst_size > src_size) { - unreachable; - } else { - return TypeCheckResult.success; - } - }, - .comptime_int => return TypeCheckResult.success, - .@"struct" => @panic("Expected int, have struct"), - .array => @panic("Expected int, have array"), - else => |t| @panic(@tagName(t)), - }, - // TODO: type safety - .pointer => |destination_pointer| switch (source_type.*) { - .pointer => |source_pointer| { - if (source_pointer.many == destination_pointer.many) { - if (source_pointer.element_type.eq(destination_pointer.element_type)) { - return .success; - } else { - switch (analyzer.module.types.array.get(source_pointer.element_type).*) { - .array => |array| { - if (array.element_type.eq(destination_pointer.element_type)) { - return .success; - } else { - unreachable; - } - }, - .optional => |source_optional| switch (analyzer.module.types.array.get(destination_pointer.element_type).*) { - .optional => |destination_optional| { - if (destination_optional.element_type.eq(source_optional.element_type)) { - return .success; - } else { - unreachable; - } - }, - else => |t| @panic(@tagName(t)), - }, - .@"struct" => { - logln(.sema, .type, "Expected pointer to #{}, got pointer to #{} (pointer to struct)", .{ destination_pointer.element_type.uniqueInteger(), source_pointer.element_type.uniqueInteger() }); - unreachable; - }, - else => |t| @panic(@tagName(t)), - } - } - } else { - unreachable; - } - }, - .@"struct" => |struct_index| { - _ = struct_index; - - @panic("expected pointer, found struct"); - }, - else => |t| @panic(@tagName(t)), - }, - .bool => switch (source_type.*) { - else => |t| @panic(@tagName(t)), - }, - .optional => |destination_optional| switch (source_type.*) { - .optional => |source_optional| { - if (expected_type_index.eq(optional_any)) { - return .take_source; - } else { - if (destination_optional.element_type.eq(source_optional.element_type)) { - return .success; - } else { - const destination_optional_element_type = analyzer.module.types.array.get(destination_optional.element_type); - const source_optional_element_type = analyzer.module.types.array.get(source_optional.element_type); - - switch (destination_optional_element_type.*) { - .pointer => |destination_pointer| switch (source_optional_element_type.*) { - .pointer => |source_pointer| { - if (source.eq(optional_pointer_to_any_type)) { - return .success; - } - - if (expected_type_index.eq(optional_pointer_to_any_type)) { - return .take_source; - } - - if (destination_pointer.many == source_pointer.many) { - if (destination_pointer.@"const" == source_pointer.@"const") { - if (destination_pointer.element_type.eq(source_pointer.element_type)) { - return .success; - } - } - } - - unreachable; - }, - .slice => |source_slice| { - _ = source_slice; - - unreachable; - // _ = source_slice; - // if (source.eq(optional_any) or expected_type_index.eq(optional_pointer_to_any_type)) { - // return .success; - // } - }, - else => |t| @panic(@tagName(t)), - }, - .slice => |destination_slice| switch (source_optional_element_type.*) { - .slice => |source_slice| { - if (destination_slice.element_type.eq(source_slice.element_type)) { - return .success; - } else { - unreachable; - } - }, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - } - } - } - }, - .pointer => |source_pointer| { - if (destination_optional.element_type.eq(source)) { - return .optional_wrap; - } else { - const destination_optional_element_type = analyzer.module.types.array.get(destination_optional.element_type); - - switch (destination_optional_element_type.*) { - .pointer => |destination_pointer| { - if (source.eq(optional_pointer_to_any_type)) { - return .optional_wrap; - } - - if (expected_type_index.eq(optional_pointer_to_any_type)) { - return .optional_wrap; - } - - if (destination_pointer.many == source_pointer.many) { - if (destination_pointer.@"const" or destination_pointer.@"const" == source_pointer.@"const") { - if (destination_pointer.element_type.eq(source_pointer.element_type)) { - return .optional_wrap; - } - } - } - - std.debug.panic("Destination: {}. Source: {}", .{ destination_pointer, source_pointer }); - }, - .slice => |destination_slice| { - _ = destination_slice; - - // if (destination_slice.element_type.eq(source_slice.element_type)) { - // return .success; - // } else { - unreachable; - // } - }, - else => |t| @panic(@tagName(t)), - } - unreachable; - } - }, - // TODO - .integer => if (destination_optional.element_type.eq(source)) { - return .optional_wrap; - } else { - logln(.sema, .type, "Destination optional element type: #{}. Source: #{}", .{ destination_optional.element_type.uniqueInteger(), source.uniqueInteger() }); - unreachable; - }, - .slice => |source_slice| if (destination_optional.element_type.eq(source)) { - return .optional_wrap; - } else { - switch (analyzer.module.types.array.get(destination_optional.element_type).*) { - .slice => |destination_slice| { - if (destination_slice.element_type.eq(source_slice.element_type)) { - return .optional_wrap; - } else { - unreachable; - } - }, - else => |t| @panic(@tagName(t)), - } - }, - .array => |source_array| if (destination_optional.element_type.eq(source)) { - _ = source_array; - return .optional_wrap; - } else { - unreachable; - }, - .@"struct" => |source_struct| if (destination_optional.element_type.eq(source)) { - return .optional_wrap; - } else { - _ = source_struct; - unreachable; - }, - else => |t| @panic(@tagName(t)), - }, - .function => |destination_function| switch (source_type.*) { - .function => |source_function| { - _ = destination_function; - _ = source_function; - - // TODO: typecheck properly - return .success; - }, - else => |t| @panic(@tagName(t)), - }, - .slice => |destination_slice| switch (source_type.*) { - .slice => |source_slice| { - if (destination_slice.@"const" or destination_slice.@"const" == source_slice.@"const") { - if (destination_slice.element_type.eq(source_slice.element_type)) { - return .success; - } else { - unreachable; - } - } else { - @panic("Const mismatch"); - } - }, - .pointer => |source_pointer| { - const source_pointer_element_type = analyzer.module.types.array.get(source_pointer.element_type); - switch (source_pointer_element_type.*) { - .array => |array| { - logln(.sema, .type, "Destination slice: {}", .{destination_slice}); - if (array.element_type.eq(Type.u8)) { - if (array.element_type.eq(destination_slice.element_type)) { - if (destination_slice.@"const") { - if (destination_slice.@"const" == source_pointer.@"const") { - if (source_pointer.many) { - return .array_coerce_to_slice; - } - } - } - } - } - }, - else => |t| @panic(@tagName(t)), - } - // - unreachable; - }, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - } - }, - .flexible_integer => |expected_flexible_integer| { - const source_type = analyzer.module.types.array.get(source); - switch (source_type.*) { - .integer => |source_integer| { - const source_size = source_integer.getSize(); - if (expected_flexible_integer.byte_count < source_size) { - unreachable; - } else if (expected_flexible_integer.byte_count > source_size) { - return switch (source_integer.signedness) { - .signed => .sign_extend, - .unsigned => .zero_extend, - }; - } else { - return TypeCheckResult.success; - } - }, - // TODO: add type safety - .pointer => |pointer| { - _ = pointer; - switch (expected_flexible_integer.byte_count == 8) { - true => return TypeCheckResult.success, - false => unreachable, - } - }, - .comptime_int => return .success, - else => |t| @panic(@tagName(t)), - } - }, - .addressable => |addressable| { - if (addressable.element_type.eq(source)) return .success else { - const destination_type = analyzer.module.types.array.get(addressable.element_type); - const source_type = analyzer.module.types.array.get(source); - - switch (source_type.*) { - .array => |source_array| { - assert(addressable.termination == source_array.termination); - - if (source_array.element_type.eq(addressable.element_type)) { - return .success; - } else { - switch (destination_type.*) { - .array => |destination_array| { - logln(.sema, .type, "SRC: {}. DST: {}", .{ source_array, destination_array }); - unreachable; - }, - else => |t| @panic(@tagName(t)), - } - } - }, - .function => |source_function| { - assert(!addressable.many); - assert(addressable.termination == .none); - assert(addressable.@"const"); - - if (addressable.element_type.eq(source)) { - return .success; - } else { - switch (destination_type.*) { - .function => |destination_function| { - if (source_function.eq(destination_function)) { - return .success; - } else { - // TODO: FIXME - return .success; - } - }, - else => |t| @panic(@tagName(t)), - } - } - }, - .integer => |source_integer| { - assert(!addressable.many); - assert(addressable.termination == .none); - _ = source_integer; - if (addressable.element_type.eq(source)) { - return .success; - } else { - // TODO: hack - unreachable; - } - - unreachable; - }, - else => |t| @panic(@tagName(t)), - } - } - }, - .dereferenceable => { - unreachable; - }, - // else => |t| @panic(@tagName(t)), - }; - } - - fn getPointerType(analyzer: *Analyzer, pointer: Type.Pointer) !Type.Index { - const gop = try analyzer.module.map.pointers.getOrPut(analyzer.allocator, pointer); - if (!gop.found_existing) { - const type_index = try analyzer.module.types.array.append(analyzer.allocator, .{ - .pointer = .{ - .element_type = pointer.element_type, - .many = pointer.many, - .@"const" = pointer.@"const", - .termination = pointer.termination, - }, - }); - gop.value_ptr.* = type_index; - } - - const result = gop.value_ptr.*; - return result; - } - - fn getSliceType(analyzer: *Analyzer, slice: Type.Slice) !Type.Index { - const gop = try analyzer.module.map.slices.getOrPut(analyzer.allocator, slice); - - if (!gop.found_existing) { - const type_index = try analyzer.module.types.array.append(analyzer.allocator, .{ - .slice = slice, - }); - gop.value_ptr.* = type_index; - } - - const result = gop.value_ptr.*; - return result; - } - - fn getArrayType(analyzer: *Analyzer, array: Type.Array) !Type.Index { - const gop = try analyzer.module.map.arrays.getOrPut(analyzer.allocator, array); - - if (!gop.found_existing) { - const type_index = try analyzer.module.types.array.append(analyzer.allocator, .{ - .array = array, - }); - gop.value_ptr.* = type_index; - } - - const result = gop.value_ptr.*; - return result; - } - - fn getOptionalType(analyzer: *Analyzer, element_type_index: Type.Index) !Type.Index { - const gop = try analyzer.module.map.optionals.getOrPut(analyzer.allocator, element_type_index); - if (!gop.found_existing) { - const type_index = try analyzer.module.types.array.append(analyzer.allocator, .{ - .optional = .{ - .element_type = element_type_index, - }, - }); - gop.value_ptr.* = type_index; - } - - const result = gop.value_ptr.*; - return result; - } - - fn compilerIntrinsic(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { - const intrinsic_node = analyzer.getScopeNode(scope_index, node_index); - - const intrinsic_name = analyzer.tokenIdentifier(scope_index, intrinsic_node.token + 1); - logln(.sema, .node, "Intrinsic: {s}", .{intrinsic_name}); - const intrinsic = data_structures.enumFromString(ValidIntrinsic, intrinsic_name) orelse panic("Unknown intrinsic: {s}", .{intrinsic_name}); - const intrinsic_argument_node_list = analyzer.getScopeNodeList(scope_index, analyzer.getScopeNode(scope_index, intrinsic_node.left)); - - const result = switch (intrinsic) { - .cast => b: { - assert(intrinsic_argument_node_list.items.len == 1); - const value_to_cast_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, intrinsic_argument_node_list.items[0]); - const value_type = analyzer.getValueType(value_to_cast_index); - assert(expect_type != .none); - const cast_result = try analyzer.canCast(expect_type, value_type); - - const value: Value = switch (cast_result) { - .success => .{ - .intrinsic = try analyzer.module.values.intrinsics.append(analyzer.allocator, .{ - .kind = .{ - .cast = value_to_cast_index, - }, - .type = switch (expect_type) { - .none => unreachable, - .flexible_integer => |flexible_integer| if (flexible_integer.sign) |sign| switch (sign) { - else => unreachable, - } else switch (flexible_integer.byte_count) { - 1 => Type.u8, - 2 => Type.u16, - 4 => Type.u32, - 8 => Type.u64, - else => unreachable, - }, - .type_index => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }, - }), - }, - .optional_wrap => .{ - .intrinsic = try analyzer.module.values.intrinsics.append(analyzer.allocator, .{ - .kind = .{ - .optional_wrap = value_to_cast_index, - }, - .type = expect_type.type_index, - }), - }, - else => |t| @panic(@tagName(t)), - }; - break :b value; - }, - .@"error" => { - assert(intrinsic_argument_node_list.items.len == 1); - const message_node = analyzer.getScopeNode(scope_index, intrinsic_argument_node_list.items[0]); - switch (message_node.id) { - .string_literal => panic("error: {s}", .{analyzer.tokenStringLiteral(scope_index, message_node.token)}), - else => |t| @panic(@tagName(t)), - } - unreachable; - }, - .import => blk: { - assert(intrinsic_argument_node_list.items.len == 1); - const import_argument = analyzer.getScopeNode(scope_index, intrinsic_argument_node_list.items[0]); - - switch (import_argument.id) { - .string_literal => { - const import_name = analyzer.tokenStringLiteral(scope_index, import_argument.token); - const import_file = try analyzer.module.importFile(analyzer.allocator, analyzer.current_file, import_name); - logln(.sema, .node, "Importing \"{s}\"...", .{import_name}); - - if (import_file.file.is_new) { - const new_file_index = import_file.file.index; - try analyzer.module.generateAbstractSyntaxTreeForFile(analyzer.allocator, new_file_index); - const value_index = try analyzer.module.values.array.append(analyzer.allocator, .{ - .unresolved = undefined, - }); - const analyze_result = try analyzeFile(value_index, analyzer.allocator, analyzer.module, new_file_index); - logln(.sema, .node, "Done analyzing {s}!", .{import_name}); - const result = Value{ - .type = analyze_result, - }; - break :blk result; - } else { - const result = Value{ - .type = analyzer.module.values.files.get(import_file.file.index).type, - }; - assert(!result.type.invalid); - - break :blk result; - } - }, - else => unreachable, - } - }, - .min => blk: { - assert(intrinsic_argument_node_list.items.len == 2); - const value1 = try analyzer.unresolvedAllocate(scope_index, expect_type, intrinsic_argument_node_list.items[0]); - const value2 = try analyzer.unresolvedAllocate(scope_index, expect_type, intrinsic_argument_node_list.items[1]); - const intrinsic_index = try analyzer.module.values.intrinsics.append(analyzer.allocator, .{ - .kind = .{ - .min = .{ - .left = value1, - .right = value2, - }, - }, - .type = switch (expect_type) { - .type_index => |type_index| type_index, - .none => analyzer.module.values.array.get(value1).getType(analyzer.module), - else => |t| @panic(@tagName(t)), - }, - }); - break :blk Value{ - .intrinsic = intrinsic_index, - }; - }, - .size => blk: { - assert(intrinsic_argument_node_list.items.len == 1); - const intrinsic_argument_value_index = try analyzer.unresolvedAllocate(scope_index, ExpectType.type, intrinsic_argument_node_list.items[0]); - const type_size = switch (analyzer.module.values.array.get(intrinsic_argument_value_index).*) { - .type => |type_index| analyzer.module.types.array.get(type_index).getSize(), - else => |t| @panic(@tagName(t)), - }; - const intrinsic_argument_value = analyzer.module.values.array.get(intrinsic_argument_value_index); - intrinsic_argument_value.* = .{ - .integer = .{ - .value = type_size, - .type = switch (expect_type) { - .type_index => |type_index| type_index, - else => |t| @panic(@tagName(t)), - }, - .signedness = .unsigned, - }, - }; - - break :blk intrinsic_argument_value.*; - }, - .syscall => if (intrinsic_argument_node_list.items.len > 0 and intrinsic_argument_node_list.items.len <= 6 + 1) blk: { - const argument_expect_type = .{ - .flexible_integer = .{ - .byte_count = 8, - }, - }; - - var list = try ArrayList(Value.Index).initCapacity(analyzer.allocator, intrinsic_argument_node_list.items.len); - for (intrinsic_argument_node_list.items) |argument_node_index| { - const argument_value_index = try analyzer.unresolvedAllocate(scope_index, argument_expect_type, argument_node_index); - list.appendAssumeCapacity(argument_value_index); - } - - const intrinsic_index = try analyzer.module.values.intrinsics.append(analyzer.allocator, .{ - .kind = .{ - .syscall = list, - }, - .type = Type.usize, - }); - - break :blk Value{ - .intrinsic = intrinsic_index, - }; - } else unreachable, - }; - - return result; - } - - fn getValueType(analyzer: *Analyzer, value_index: Value.Index) Type.Index { - const value_type_index = analyzer.module.values.array.get(value_index).getType(analyzer.module); - return value_type_index; - } -}; - -pub fn initialize(compilation: *Compilation, module: *Module, package: *Package, main_value_index: Value.Index) !void { - _ = try analyzeExistingPackage(main_value_index, compilation, module, package); -} - -pub fn analyzeExistingPackage(value_index: Value.Index, compilation: *Compilation, module: *Module, package: *Package) !Type.Index { - const package_import = try module.importPackage(compilation.base_allocator, package); - assert(!package_import.file.is_new); - const file_index = package_import.file.index; - - return try analyzeFile(value_index, compilation.base_allocator, module, file_index); -} - -pub fn analyzeFile(value_index: Value.Index, allocator: Allocator, module: *Module, file_index: File.Index) !Type.Index { - const file = module.values.files.get(file_index); - assert(module.values.array.get(value_index).* == .unresolved); - assert(file.status == .parsed); - - var analyzer = Analyzer{ - .current_file = file_index, - .allocator = allocator, - .module = module, - }; - - const node = file.syntactic_analyzer_result.nodes.items[0]; - const node_list_node = analyzer.getFileNode(file_index, node.left); - const nodes = analyzer.getFileNodeList(file_index, node_list_node); - const result = try analyzer.processContainerType(value_index, Scope.Index.invalid, nodes.items, file_index, .{ .value = 0 }, .@"struct"); - return result; -} diff --git a/bootstrap/main.zig b/bootstrap/main.zig index 5f18c83..bbbd932 100644 --- a/bootstrap/main.zig +++ b/bootstrap/main.zig @@ -5,10 +5,8 @@ const Compilation = @import("Compilation.zig"); pub const panic = Compilation.panic; pub fn main() !void { - const GPA = std.heap.GeneralPurposeAllocator(.{}); - var gpa = GPA{}; - - try Compilation.init(gpa.allocator()); + var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); + try Compilation.init(arena_allocator.allocator()); } test { diff --git a/build.zig b/build.zig index 0991ac6..868d8de 100644 --- a/build.zig +++ b/build.zig @@ -3,19 +3,238 @@ var all: bool = false; pub fn build(b: *std.Build) !void { all = b.option(bool, "all", "All") orelse false; - const target = b.standardTargetOptions(.{}); const optimization = b.standardOptimizeOption(.{}); - const use_llvm = b.option(bool, "use_llvm", "Use LLVM as the backend for generate the compiler binary") orelse true; + const llvm_debug = b.option(bool, "llvm_debug", "Use LLVM in the debug version") orelse false; + const llvm_debug_path = b.option([]const u8, "llvm_debug_path", "LLVM debug path") orelse "../dev/llvm-17-static-debug"; + const llvm_release_path = b.option([]const u8, "llvm_release_path", "LLVM release path") orelse "../dev/llvm-17-static-release"; + const target_query = try std.zig.CrossTarget.parse(.{ + .arch_os_abi = "native-linux-musl", + }); + const target = b.resolveTargetQuery(target_query); const exe = b.addExecutable(.{ .name = "nat", .root_source_file = .{ .path = "bootstrap/main.zig" }, .target = target, .optimize = optimization, - .use_llvm = use_llvm, - .use_lld = false, + .use_llvm = true, + .use_lld = true, }); - exe.unwind_tables = false; - exe.omit_frame_pointer = false; + exe.formatted_panics = false; + exe.root_module.unwind_tables = false; + exe.root_module.omit_frame_pointer = false; + + const llvm_dir = if (llvm_debug) llvm_debug_path else llvm_release_path; + const llvm_include_dir = try std.mem.concat(b.allocator, u8, &.{ llvm_dir, "/include" }); + const llvm_lib_dir = try std.mem.concat(b.allocator, u8, &.{ llvm_dir, "/lib" }); + + exe.linkLibC(); + exe.linkLibCpp(); + + exe.addIncludePath(std.Build.LazyPath.relative(llvm_include_dir)); + exe.addCSourceFile(.{ + .file = std.Build.LazyPath.relative("bootstrap/backend/llvm.cpp"), + .flags = &.{"-g"}, + }); + + const llvm_libraries = [_][]const u8{ + "libLLVMAArch64AsmParser.a", + "libLLVMAArch64CodeGen.a", + "libLLVMAArch64Desc.a", + "libLLVMAArch64Disassembler.a", + "libLLVMAArch64Info.a", + "libLLVMAArch64Utils.a", + "libLLVMAggressiveInstCombine.a", + "libLLVMAMDGPUAsmParser.a", + "libLLVMAMDGPUCodeGen.a", + "libLLVMAMDGPUDesc.a", + "libLLVMAMDGPUDisassembler.a", + "libLLVMAMDGPUInfo.a", + "libLLVMAMDGPUTargetMCA.a", + "libLLVMAMDGPUUtils.a", + "libLLVMAnalysis.a", + "libLLVMARMAsmParser.a", + "libLLVMARMCodeGen.a", + "libLLVMARMDesc.a", + "libLLVMARMDisassembler.a", + "libLLVMARMInfo.a", + "libLLVMARMUtils.a", + "libLLVMAsmParser.a", + "libLLVMAsmPrinter.a", + "libLLVMAVRAsmParser.a", + "libLLVMAVRCodeGen.a", + "libLLVMAVRDesc.a", + "libLLVMAVRDisassembler.a", + "libLLVMAVRInfo.a", + "libLLVMBinaryFormat.a", + "libLLVMBitReader.a", + "libLLVMBitstreamReader.a", + "libLLVMBitWriter.a", + "libLLVMBPFAsmParser.a", + "libLLVMBPFCodeGen.a", + "libLLVMBPFDesc.a", + "libLLVMBPFDisassembler.a", + "libLLVMBPFInfo.a", + "libLLVMCFGuard.a", + "libLLVMCFIVerify.a", + "libLLVMCodeGen.a", + "libLLVMCodeGenTypes.a", + "libLLVMCore.a", + "libLLVMCoroutines.a", + "libLLVMCoverage.a", + "libLLVMDebugInfoBTF.a", + "libLLVMDebugInfoCodeView.a", + "libLLVMDebuginfod.a", + "libLLVMDebugInfoDWARF.a", + "libLLVMDebugInfoGSYM.a", + "libLLVMDebugInfoLogicalView.a", + "libLLVMDebugInfoMSF.a", + "libLLVMDebugInfoPDB.a", + "libLLVMDemangle.a", + "libLLVMDiff.a", + "libLLVMDlltoolDriver.a", + "libLLVMDWARFLinker.a", + "libLLVMDWARFLinkerParallel.a", + "libLLVMDWP.a", + "libLLVMExecutionEngine.a", + "libLLVMExtensions.a", + "libLLVMFileCheck.a", + "libLLVMFrontendHLSL.a", + "libLLVMFrontendOpenACC.a", + "libLLVMFrontendOpenMP.a", + "libLLVMFuzzerCLI.a", + "libLLVMFuzzMutate.a", + "libLLVMGlobalISel.a", + "libLLVMHexagonAsmParser.a", + "libLLVMHexagonCodeGen.a", + "libLLVMHexagonDesc.a", + "libLLVMHexagonDisassembler.a", + "libLLVMHexagonInfo.a", + "libLLVMInstCombine.a", + "libLLVMInstrumentation.a", + "libLLVMInterfaceStub.a", + "libLLVMInterpreter.a", + "libLLVMipo.a", + "libLLVMIRPrinter.a", + "libLLVMIRReader.a", + "libLLVMJITLink.a", + "libLLVMLanaiAsmParser.a", + "libLLVMLanaiCodeGen.a", + "libLLVMLanaiDesc.a", + "libLLVMLanaiDisassembler.a", + "libLLVMLanaiInfo.a", + "libLLVMLibDriver.a", + "libLLVMLineEditor.a", + "libLLVMLinker.a", + "libLLVMLoongArchAsmParser.a", + "libLLVMLoongArchCodeGen.a", + "libLLVMLoongArchDesc.a", + "libLLVMLoongArchDisassembler.a", + "libLLVMLoongArchInfo.a", + "libLLVMLTO.a", + "libLLVMMC.a", + "libLLVMMCA.a", + "libLLVMMCDisassembler.a", + "libLLVMMCJIT.a", + "libLLVMMCParser.a", + "libLLVMMipsAsmParser.a", + "libLLVMMipsCodeGen.a", + "libLLVMMipsDesc.a", + "libLLVMMipsDisassembler.a", + "libLLVMMipsInfo.a", + "libLLVMMIRParser.a", + "libLLVMMSP430AsmParser.a", + "libLLVMMSP430CodeGen.a", + "libLLVMMSP430Desc.a", + "libLLVMMSP430Disassembler.a", + "libLLVMMSP430Info.a", + "libLLVMNVPTXCodeGen.a", + "libLLVMNVPTXDesc.a", + "libLLVMNVPTXInfo.a", + "libLLVMObjCARCOpts.a", + "libLLVMObjCopy.a", + "libLLVMObject.a", + "libLLVMObjectYAML.a", + "libLLVMOption.a", + "libLLVMOrcJIT.a", + "libLLVMOrcShared.a", + "libLLVMOrcTargetProcess.a", + "libLLVMPasses.a", + "libLLVMPowerPCAsmParser.a", + "libLLVMPowerPCCodeGen.a", + "libLLVMPowerPCDesc.a", + "libLLVMPowerPCDisassembler.a", + "libLLVMPowerPCInfo.a", + "libLLVMProfileData.a", + "libLLVMRemarks.a", + "libLLVMRISCVAsmParser.a", + "libLLVMRISCVCodeGen.a", + "libLLVMRISCVDesc.a", + "libLLVMRISCVDisassembler.a", + "libLLVMRISCVInfo.a", + "libLLVMRISCVTargetMCA.a", + "libLLVMRuntimeDyld.a", + "libLLVMScalarOpts.a", + "libLLVMSelectionDAG.a", + "libLLVMSparcAsmParser.a", + "libLLVMSparcCodeGen.a", + "libLLVMSparcDesc.a", + "libLLVMSparcDisassembler.a", + "libLLVMSparcInfo.a", + "libLLVMSupport.a", + "libLLVMSymbolize.a", + "libLLVMSystemZAsmParser.a", + "libLLVMSystemZCodeGen.a", + "libLLVMSystemZDesc.a", + "libLLVMSystemZDisassembler.a", + "libLLVMSystemZInfo.a", + "libLLVMTableGen.a", + "libLLVMTableGenCommon.a", + "libLLVMTableGenGlobalISel.a", + "libLLVMTarget.a", + "libLLVMTargetParser.a", + "libLLVMTextAPI.a", + "libLLVMTransformUtils.a", + "libLLVMVEAsmParser.a", + "libLLVMVECodeGen.a", + "libLLVMVectorize.a", + "libLLVMVEDesc.a", + "libLLVMVEDisassembler.a", + "libLLVMVEInfo.a", + "libLLVMWebAssemblyAsmParser.a", + "libLLVMWebAssemblyCodeGen.a", + "libLLVMWebAssemblyDesc.a", + "libLLVMWebAssemblyDisassembler.a", + "libLLVMWebAssemblyInfo.a", + "libLLVMWebAssemblyUtils.a", + "libLLVMWindowsDriver.a", + "libLLVMWindowsManifest.a", + "libLLVMX86AsmParser.a", + "libLLVMX86CodeGen.a", + "libLLVMX86Desc.a", + "libLLVMX86Disassembler.a", + "libLLVMX86Info.a", + "libLLVMX86TargetMCA.a", + "libLLVMXCoreCodeGen.a", + "libLLVMXCoreDesc.a", + "libLLVMXCoreDisassembler.a", + "libLLVMXCoreInfo.a", + "libLLVMXRay.a", + // Zlib + "libz.a", + "libzstd.a", + //LLD + "liblldCOFF.a", + "liblldCommon.a", + "liblldELF.a", + "liblldMachO.a", + "liblldMinGW.a", + "liblldWasm.a", + }; + + inline for (llvm_libraries) |llvm_library| { + exe.addObjectFile(std.Build.LazyPath.relative(try std.mem.concat(b.allocator, u8, &.{ llvm_lib_dir, "/", llvm_library }))); + } + const install_exe = b.addInstallArtifact(exe, .{}); b.getInstallStep().dependOn(&install_exe.step); diff --git a/ci.sh b/ci.sh index b1c5c1a..9bede8b 100755 --- a/ci.sh +++ b/ci.sh @@ -1,8 +1,19 @@ #!/usr/bin/env bash +argument_count=$#; +extra_args="" +use_debug="true" +if [ $argument_count -ne 0 ]; then + llvm_debug_path=$1 + llvm_release_path=$2 + use_debug="false" + extra_args="-Dllvm_debug_path=$llvm_debug_path -Dllvm_release_path=$llvm_release_path" +fi echo -e "\e[90mCompiling Nativity with Zig...\e[0m" -nativity_use_llvm=true -zig build -Duse_llvm=$nativity_use_llvm +zig build -Dllvm_debug=$use_debug $extra_args +if [[ "$?" != 0 ]]; then + exit 1 +fi failed_test_count=0 passed_test_count=0 test_directory_name=test @@ -57,41 +68,41 @@ do test_i=$(($test_i + 1)) done -for integral_test_case in $integral_test_directory_files -do - MY_TESTNAME=${integral_test_case##*/} - cd test/integral/$MY_TESTNAME - $nat_compiler - - if [[ "$?" == "0" ]]; then - passed_compilation_count=$(($passed_compilation_count + 1)) - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - nat/$MY_TESTNAME - - if [[ "$?" == "0" ]]; then - passed_test_count=$(($passed_test_count + 1)) - result="\e[32mPASSED\e[0m" - else - failed_test_count=$(($failed_test_count + 1)) - result="\e[31mFAILED\e[0m" - failed_tests+=("$test_i. $MY_TESTNAME") - fi - - ran_test_count=$(($ran_test_count + 1)) - else - result="\e[31mOS NOT SUPPORTED\e[0m" - fi - else - failed_compilation_count=$(($failed_compilation_count + 1)) - result="\e[31mCOMPILATION FAILURE\e[0m" - failed_compilations+=("$test_i. $MY_TESTNAME") - fi - - echo -e "[$test_i/$total_test_count] [$result] [INTEGRAL] $MY_TESTNAME" - - test_i=$(($test_i + 1)) - cd $my_current_directory -done +# for integral_test_case in $integral_test_directory_files +# do +# MY_TESTNAME=${integral_test_case##*/} +# cd test/integral/$MY_TESTNAME +# $nat_compiler +# +# if [[ "$?" == "0" ]]; then +# passed_compilation_count=$(($passed_compilation_count + 1)) +# if [[ "$OSTYPE" == "linux-gnu"* ]]; then +# nat/$MY_TESTNAME +# +# if [[ "$?" == "0" ]]; then +# passed_test_count=$(($passed_test_count + 1)) +# result="\e[32mPASSED\e[0m" +# else +# failed_test_count=$(($failed_test_count + 1)) +# result="\e[31mFAILED\e[0m" +# failed_tests+=("$test_i. $MY_TESTNAME") +# fi +# +# ran_test_count=$(($ran_test_count + 1)) +# else +# result="\e[31mOS NOT SUPPORTED\e[0m" +# fi +# else +# failed_compilation_count=$(($failed_compilation_count + 1)) +# result="\e[31mCOMPILATION FAILURE\e[0m" +# failed_compilations+=("$test_i. $MY_TESTNAME") +# fi +# +# echo -e "[$test_i/$total_test_count] [$result] [INTEGRAL] $MY_TESTNAME" +# +# test_i=$(($test_i + 1)) +# cd $my_current_directory +# done printf "\n" echo -e "\e[35m[SUMMARY]\e[0m" diff --git a/lib/std/build.nat b/lib/std/build.nat index 7f5d6d5..5f5089a 100644 --- a/lib/std/build.nat +++ b/lib/std/build.nat @@ -12,7 +12,7 @@ const Executable = struct{ const compile = fn(executable: Executable) bool { const argument_count = std.start.argument_count; const argument_values = std.start.argument_values; - assert(ok = argument_count == 3); + assert(ok = argument_count >= 3); const compiler_path = argument_values[2]; const result = executable.compile_with_compiler_path(compiler_path); diff --git a/lib/std/builtin.nat b/lib/std/builtin.nat index 924d084..1c815e3 100644 --- a/lib/std/builtin.nat +++ b/lib/std/builtin.nat @@ -17,5 +17,4 @@ const Abi = enum{ const CallingConvention = enum{ system_v, - naked, }; diff --git a/lib/std/os.nat b/lib/std/os.nat index 75c2416..44ff06f 100644 --- a/lib/std/os.nat +++ b/lib/std/os.nat @@ -9,7 +9,7 @@ const system = switch (current) { const exit = fn(exit_code: s32) noreturn { switch (current) { - .linux => _ = #syscall(#cast(linux.Syscall.exit_group), exit_code), + .linux => _ = #syscall(#cast(linux.Syscall.exit_group), #cast(exit_code)), .macos => macos.exit(exit_code), .windows => windows.ExitProcess(exit_code), } @@ -30,7 +30,7 @@ const FileDescriptor = struct{ switch (current) { .linux => { const len: usize = #min(max_file_operation_byte_count, bytes.len); - if (linux.unwrapSyscall(syscall_result = #syscall(#cast(linux.Syscall.read), file_descriptor.handle, #cast(bytes.ptr), len))) |byte_count| { + if (linux.unwrapSyscall(syscall_result = #syscall(#cast(linux.Syscall.read), #cast(file_descriptor.handle), #cast(bytes.ptr), len))) |byte_count| { return byte_count; } else { return null; @@ -47,7 +47,7 @@ const FileDescriptor = struct{ switch (current) { .linux => { const len: usize = #min(max_file_operation_byte_count, bytes.len); - const raw_result = #syscall(#cast(linux.Syscall.write), file_descriptor.handle, #cast(bytes.ptr), len); + const raw_result = #syscall(#cast(linux.Syscall.write), #cast(file_descriptor.handle), #cast(bytes.ptr), len); if (linux.unwrapSyscall(syscall_result = raw_result)) |byte_count| { return byte_count; } else { diff --git a/lib/std/os/linux.nat b/lib/std/os/linux.nat index f19c472..1d3d1d4 100644 --- a/lib/std/os/linux.nat +++ b/lib/std/os/linux.nat @@ -411,7 +411,7 @@ const get_map_flags = fn(flags: std.os.MapFlags) MapFlags{ } const mmap = fn(address: ?[&]u8, length: usize, protection_flags: ProtectionFlags, map_flags: MapFlags, fd: s32, offset: u64) usize { - const result = #syscall(#cast(Syscall.mmap), #cast(address), length, #cast(protection_flags), #cast(map_flags), fd, offset); + const result = #syscall(#cast(Syscall.mmap), #cast(address), length, #cast(protection_flags), #cast(map_flags), #cast(fd), offset); return result; } diff --git a/lib/std/start.nat b/lib/std/start.nat index 6021d2d..5e08fd6 100644 --- a/lib/std/start.nat +++ b/lib/std/start.nat @@ -8,7 +8,7 @@ comptime { } } -const _start = fn () noreturn export cc(.naked) { +const _start = fn naked, export () noreturn { #asm({ xor ebp, ebp; mov rdi, rsp; @@ -21,7 +21,7 @@ var argument_count: usize = 0; var argument_values: [&]const [&:0]const u8 = undefined; var environment_values: [&:null]const ?[&:null]const u8 = undefined; -const start = fn(argc_argv_address: usize) noreturn export { +const start = fn export (argc_argv_address: usize) noreturn { var argument_address_iterator = argc_argv_address; const argument_count_ptr: &usize = #cast(argument_address_iterator); argument_count = argument_count_ptr.@; @@ -33,6 +33,6 @@ const start = fn(argc_argv_address: usize) noreturn export { std.os.exit(exit_code = result); } -const main = fn(argc: s32, argv: [&:null]?[&:null]u8, env: [&:null]?[&:null]u8) s32 export { +const main = fn export (argc: s32, argv: [&:null]?[&:null]u8, env: [&:null]?[&:null]u8) s32 { return #import("main").main(); } diff --git a/lib/std/std.nat b/lib/std/std.nat index 7ddb972..84edca3 100644 --- a/lib/std/std.nat +++ b/lib/std/std.nat @@ -21,6 +21,38 @@ const print = fn(bytes: []const u8) void { _ = file_writer.writeAll(bytes); } +const format_usize = fn(n: usize, buffer: &[65]u8) []u8 { + var index: usize = buffer.len; + var absolute = n; + + while (true) { + const digit: u8 = #cast(absolute % 10); + index -= 1; + buffer[index] = '0' + digit; + absolute /= 10; + + if (absolute == 0) { + break; + } + } + + return buffer[index..]; +} + +const print_usize = fn(n: usize) void { + var buffer: [65]u8 = undefined; + const bytes = format_usize(n, buffer = buffer.&); + const file_descriptor = os.StdFileDescriptor.get(descriptor = .stdout); + const file_writer = FileWriter{ + .descriptor = file_descriptor, + }; + _ = file_writer.writeAll(bytes); +} + +const print_u8 = fn(n: u8) void { + print_usize(n); +} + const Allocator = struct { handler: &const fn(allocator: &Allocator, old_ptr: ?[&]const u8, old_size: usize, new_size: usize, alignment: u16) ?[&]u8, @@ -135,6 +167,25 @@ const copy_bytes = fn(destination: []u8, source: []const u8) void { } } +const concatenate_bytes = fn(allocator: &Allocator, slices: []const []const u8) ?[]u8 { + var total_byte_count: usize = 0; + for (slices) |slice| { + total_byte_count += slice.len; + } + + if (allocator.allocate(total_byte_count, 1)) |bytes| { + var offset: usize = 0; + for (slice) |slice| { + copy_bytes(bytes[offset..][0..slice.len], slice); + offset += slice.len; + } + + return bytes; + } else { + return null; + } +} + const Target = struct { cpu: builtin.Cpu, os: builtin.Os, diff --git a/test/integral/first/.gitignore b/todo_test/integral/first/.gitignore similarity index 100% rename from test/integral/first/.gitignore rename to todo_test/integral/first/.gitignore diff --git a/test/integral/first/build.nat b/todo_test/integral/first/build.nat similarity index 100% rename from test/integral/first/build.nat rename to todo_test/integral/first/build.nat diff --git a/test/integral/first/src/main.nat b/todo_test/integral/first/src/main.nat similarity index 100% rename from test/integral/first/src/main.nat rename to todo_test/integral/first/src/main.nat diff --git a/test/integral/link_libc/.gitignore b/todo_test/integral/link_libc/.gitignore similarity index 100% rename from test/integral/link_libc/.gitignore rename to todo_test/integral/link_libc/.gitignore diff --git a/test/integral/link_libc/build.nat b/todo_test/integral/link_libc/build.nat similarity index 100% rename from test/integral/link_libc/build.nat rename to todo_test/integral/link_libc/build.nat diff --git a/test/integral/link_libc/src/main.nat b/todo_test/integral/link_libc/src/main.nat similarity index 100% rename from test/integral/link_libc/src/main.nat rename to todo_test/integral/link_libc/src/main.nat diff --git a/test/standalone/add_sub/main.nat b/todo_test/standalone/add_sub/main.nat similarity index 100% rename from test/standalone/add_sub/main.nat rename to todo_test/standalone/add_sub/main.nat diff --git a/test/standalone/and/main.nat b/todo_test/standalone/and/main.nat similarity index 100% rename from test/standalone/and/main.nat rename to todo_test/standalone/and/main.nat diff --git a/todo_test/standalone/break/main.nat b/todo_test/standalone/break/main.nat new file mode 100644 index 0000000..16281d1 --- /dev/null +++ b/todo_test/standalone/break/main.nat @@ -0,0 +1,12 @@ +const main = fn() s32 { + var i: s32 = 0; + const j: s32 = 5; + for (0..10) |_| { + if (i == j) { + break; + } + i += 1; + } + + return i - j; +} diff --git a/test/standalone/div/main.nat b/todo_test/standalone/div/main.nat similarity index 100% rename from test/standalone/div/main.nat rename to todo_test/standalone/div/main.nat diff --git a/todo_test/standalone/first/main.nat b/todo_test/standalone/first/main.nat new file mode 100644 index 0000000..34ec86b --- /dev/null +++ b/todo_test/standalone/first/main.nat @@ -0,0 +1,3 @@ +const main = fn() s32 { + return 0; +} diff --git a/test/standalone/foreach/main.nat b/todo_test/standalone/foreach/main.nat similarity index 100% rename from test/standalone/foreach/main.nat rename to todo_test/standalone/foreach/main.nat diff --git a/todo_test/standalone/foreach_slice/main.nat b/todo_test/standalone/foreach_slice/main.nat new file mode 100644 index 0000000..375dfc7 --- /dev/null +++ b/todo_test/standalone/foreach_slice/main.nat @@ -0,0 +1,30 @@ +const std = #import("std"); +const print = std.print; + +const count_slice_byte_count = fn(slices: []const []const u8) usize { + var byte_count: usize = 0; + + for (slices) |slice| { + byte_count += slice.len; + } + + return byte_count; +} + +const print_values = fn(slice: []const u8) void { + for (slice, 0..) |value, index| { + std.print_usize(n = index); + std.print(bytes = ": "); + std.print_u8(n = value); + std.print(bytes = "\n"); + } +} + +const main = fn () s32 { + const a = [_]u8{1, 1, 4, 5, 6}; + const b = [_]u8{1, 4, 6}; + const expected_result: usize = a.len + b.len; + const result = count_slice_byte_count(slices = .{a.&, b.&}.&); + print_values(slice = a.&); + return #cast(expected_result - result); +} diff --git a/test/standalone/fork/main.nat b/todo_test/standalone/fork/main.nat similarity index 100% rename from test/standalone/fork/main.nat rename to todo_test/standalone/fork/main.nat diff --git a/test/standalone/fork_exec/main.nat b/todo_test/standalone/fork_exec/main.nat similarity index 100% rename from test/standalone/fork_exec/main.nat rename to todo_test/standalone/fork_exec/main.nat diff --git a/test/standalone/hello_world/main.nat b/todo_test/standalone/hello_world/main.nat similarity index 100% rename from test/standalone/hello_world/main.nat rename to todo_test/standalone/hello_world/main.nat diff --git a/test/standalone/imul/main.nat b/todo_test/standalone/imul/main.nat similarity index 100% rename from test/standalone/imul/main.nat rename to todo_test/standalone/imul/main.nat diff --git a/test/standalone/loop_break/main.nat b/todo_test/standalone/loop_break/main.nat similarity index 100% rename from test/standalone/loop_break/main.nat rename to todo_test/standalone/loop_break/main.nat diff --git a/todo_test/standalone/optional_wrap/main.nat b/todo_test/standalone/optional_wrap/main.nat new file mode 100644 index 0000000..d6dbe76 --- /dev/null +++ b/todo_test/standalone/optional_wrap/main.nat @@ -0,0 +1,8 @@ +const foo = fn(slice: []u8) ?[]u8 { + return slice[0..1]; +} +const main = fn() s32 { + _ = foo(slice = .{ 0, 1, 2, 3 }.&); + + return 0; +} diff --git a/test/standalone/or/main.nat b/todo_test/standalone/or/main.nat similarity index 66% rename from test/standalone/or/main.nat rename to todo_test/standalone/or/main.nat index ea5f2e6..5b9b4ea 100644 --- a/test/standalone/or/main.nat +++ b/todo_test/standalone/or/main.nat @@ -2,5 +2,6 @@ const main = fn() s32 { const a: u32 = 0xffff; const b: u32 = 0xffff0000; const c: u32 = 0xffffffff; - return c - (a | b); + const result = c - (a | b); + return #cast(result); } diff --git a/test/standalone/self_exe_path/main.nat b/todo_test/standalone/self_exe_path/main.nat similarity index 100% rename from test/standalone/self_exe_path/main.nat rename to todo_test/standalone/self_exe_path/main.nat diff --git a/test/standalone/shifts/main.nat b/todo_test/standalone/shifts/main.nat similarity index 100% rename from test/standalone/shifts/main.nat rename to todo_test/standalone/shifts/main.nat diff --git a/test/standalone/simple_bool/main.nat b/todo_test/standalone/simple_bool/main.nat similarity index 100% rename from test/standalone/simple_bool/main.nat rename to todo_test/standalone/simple_bool/main.nat diff --git a/todo_test/standalone/slice/main.nat b/todo_test/standalone/slice/main.nat new file mode 100644 index 0000000..43e53d4 --- /dev/null +++ b/todo_test/standalone/slice/main.nat @@ -0,0 +1,14 @@ +const main = fn () s32{ + const a: [4]u8 = .{1, 2, 3, 4}; + const b: []const []const u8 = .{a.&}.&; + + var sum: u8 = 0; + + for (b) |b_slice| { + for (b_slice) |a_element| { + sum += a_element; + } + } + + return #cast(sum - 10); +} diff --git a/todo_test/standalone/slice2/main.nat b/todo_test/standalone/slice2/main.nat new file mode 100644 index 0000000..b183571 --- /dev/null +++ b/todo_test/standalone/slice2/main.nat @@ -0,0 +1,6 @@ +const main = fn () s32{ + const a: [4]u8 = .{1, 2, 3, 4}; + const b: []const []const u8 = .{a.&}.&; + + return #cast(b[0][0] - 1); +} diff --git a/todo_test/standalone/slice_expression/main.nat b/todo_test/standalone/slice_expression/main.nat new file mode 100644 index 0000000..5fd368d --- /dev/null +++ b/todo_test/standalone/slice_expression/main.nat @@ -0,0 +1,10 @@ +const main = fn () s32 { + _ = foo(); + return 0; +} + +const foo = fn () []u8 { + var buffer: [5]u8 = undefined; + const buffer_ptr: &[5]u8 = buffer.&; + return buffer_ptr[2..]; +} diff --git a/test/standalone/stack/main.nat b/todo_test/standalone/stack/main.nat similarity index 100% rename from test/standalone/stack/main.nat rename to todo_test/standalone/stack/main.nat diff --git a/test/standalone/virtual_memory/main.nat b/todo_test/standalone/virtual_memory/main.nat similarity index 100% rename from test/standalone/virtual_memory/main.nat rename to todo_test/standalone/virtual_memory/main.nat diff --git a/test/standalone/xor/main.nat b/todo_test/standalone/xor/main.nat similarity index 100% rename from test/standalone/xor/main.nat rename to todo_test/standalone/xor/main.nat