From df53762d923a997814d1baa66dcb0fc1adef263b Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Sun, 19 Nov 2023 09:22:39 -0600 Subject: [PATCH] commit before transpiling --- build.zig | 8 +- ci.sh | 5 +- src/Compilation.zig | 84 +- src/backend/emit.zig | 3 +- src/backend/intermediate_representation.zig | 2282 +++++++++---------- src/backend/x86_64.zig | 752 +++--- src/data_structures.zig | 81 +- src/frontend/semantic_analyzer.zig | 153 +- src/frontend/syntactic_analyzer.zig | 15 +- src/main.zig | 1 + test/simple_bool/main.nat | 8 + 11 files changed, 1774 insertions(+), 1618 deletions(-) create mode 100644 test/simple_bool/main.nat diff --git a/build.zig b/build.zig index 0ac8555..6447c07 100644 --- a/build.zig +++ b/build.zig @@ -5,14 +5,18 @@ 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 exe = b.addExecutable(.{ .name = "nativity", .root_source_file = .{ .path = "src/main.zig" }, .target = target, .optimize = optimization, - .use_llvm = true, + .use_llvm = use_llvm, .use_lld = false, }); + exe.unwind_tables = false; + exe.omit_frame_pointer = false; + b.installArtifact(exe); b.installDirectory(.{ .source_dir = std.Build.LazyPath.relative("lib"), @@ -31,6 +35,8 @@ pub fn build(b: *std.Build) !void { const debug_command = switch (@import("builtin").os.tag) { .linux => blk: { const result = b.addSystemCommand(&.{"gf2"}); + result.addArg("-ex=r"); + result.addArgs(&.{ "-ex", "up" }); result.addArg("--args"); result.addArtifactArg(exe); break :blk result; diff --git a/ci.sh b/ci.sh index 3d15aef..56f8e23 100755 --- a/ci.sh +++ b/ci.sh @@ -2,7 +2,8 @@ echo "Testing Nativity with Zig" echo "Compiling Nativity with Zig" -zig build +nativity_use_llvm=false +zig build -Duse_llvm=$nativity_use_llvm failed_test_count=0 passed_test_count=0 test_directory_name=test @@ -18,7 +19,7 @@ failed_tests=() for dir in $test_directory do MY_TESTNAME=${dir##*/} - zig build run -- $dir/main.nat + zig build run -Duse_llvm=$nativity_use_llvm -- $dir/main.nat -log ir if [[ "$?" == "0" ]]; then passed_compilation_count=$(($passed_compilation_count + 1)) if [[ "$OSTYPE" == "linux-gnu"* ]]; then diff --git a/src/Compilation.zig b/src/Compilation.zig index 69be14b..3226105 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -53,6 +53,7 @@ fn parseArguments(allocator: Allocator) !Compilation.Module.Descriptor { var maybe_executable_path: ?[]const u8 = null; var maybe_main_package_path: ?[]const u8 = null; var target_triplet: []const u8 = "x86_64-linux-gnu"; + var transpile_to_c: ?bool = null; var i: usize = 0; while (i < arguments.len) : (i += 1) { @@ -112,6 +113,21 @@ fn parseArguments(allocator: Allocator) !Compilation.Module.Descriptor { } else { reportUnterminatedArgumentError(current_argument); } + } else if (equal(u8, current_argument, "-transpile_to_c")) { + if (i + 1 != arguments.len) { + i += 1; + + const arg = arguments[i]; + if (std.mem.eql(u8, arg, "true")) { + transpile_to_c = true; + } else if (std.mem.equal(u8, arg, "false")) { + transpile_to_c = false; + } else { + unreachable; + } + } else { + reportUnterminatedArgumentError(current_argument); + } } else { maybe_main_package_path = current_argument; } @@ -133,6 +149,7 @@ fn parseArguments(allocator: Allocator) !Compilation.Module.Descriptor { .main_package_path = main_package_path, .executable_path = executable_path, .target = target, + .transpile_to_c = transpile_to_c orelse true, }; } @@ -236,7 +253,7 @@ pub const Type = union(enum) { pub fn getIndex(integer: Integer) Compilation.Type.Index { return .{ .block = 0, - .index = @ctz(integer.bit_count) - @ctz(@as(u8, 8)) + @as(u6, switch (integer.signedness) { + .element = @ctz(integer.bit_count) - @ctz(@as(u8, 8)) + @as(u6, switch (integer.signedness) { .signed => Compilation.HardwareSignedIntegerType.offset, .unsigned => Compilation.HardwareUnsignedIntegerType.offset, }), @@ -287,26 +304,6 @@ pub const Type = union(enum) { }; // Each time an enum is added here, a corresponding insertion in the initialization must be made -pub const Values = enum { - bool_false, - bool_true, - @"unreachable", - - pub fn getIndex(value: Values) Value.Index { - const absolute: u32 = @intFromEnum(value); - const foo = @as(Value.Index, undefined); - const ElementT = @TypeOf(@field(foo, "index")); - const BlockT = @TypeOf(@field(foo, "block")); - const divider = std.math.maxInt(ElementT); - const element_index: ElementT = @intCast(absolute % divider); - const block_index: BlockT = @intCast(absolute / divider); - return .{ - .index = element_index, - .block = block_index, - }; - } -}; - pub const Intrinsic = enum { @"error", import, @@ -556,6 +553,7 @@ pub const BinaryOperation = struct { divide, shift_left, shift_right, + compare_equal, }; }; @@ -563,6 +561,17 @@ pub const CallingConvention = enum { system_v, }; +pub const Branch = struct { + condition: Value.Index, + true_expression: Value.Index, + false_expression: Value.Index, + reaches_end: bool, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + pub const Value = union(enum) { unresolved: Unresolved, declaration: Declaration.Index, @@ -572,7 +581,8 @@ pub const Value = union(enum) { undefined, @"unreachable", loop: Loop.Index, - function: Function.Index, + function_definition: Function.Index, + function_declaration: Function.Index, block: Block.Index, runtime: Runtime, assign: Assignment.Index, @@ -589,6 +599,7 @@ pub const Value = union(enum) { sign_extend: Cast.Index, zero_extend: Cast.Index, binary_operation: BinaryOperation.Index, + branch: Branch.Index, pub const List = BlockList(@This()); pub const Index = List.Index; @@ -606,7 +617,7 @@ pub const Value = union(enum) { pub fn isComptime(value: *Value, module: *Module) bool { return switch (value.*) { - .bool, .void, .undefined, .function, .type, .enum_field => true, + .bool, .void, .undefined, .function_definition, .type, .enum_field => true, .integer => |integer| integer.type.eq(Type.comptime_int), .call => false, .binary_operation => false, @@ -623,8 +634,11 @@ pub const Value = union(enum) { .string_literal => |string_literal_hash| module.string_literal_types.get(@intCast(module.getStringLiteral(string_literal_hash).?.len)).?, .type => Type.type, .enum_field => |enum_field_index| module.enums.get(module.enum_fields.get(enum_field_index).parent).type, - .function => |function_index| module.functions.get(function_index).prototype, + .function_definition => |function_index| module.function_definitions.get(function_index).prototype, + .function_declaration => |function_index| module.function_declarations.get(function_index).prototype, .binary_operation => |binary_operation| module.binary_operations.get(binary_operation).type, + .bool => Type.boolean, + .declaration => Type.void, else => |t| @panic(@tagName(t)), }; @@ -703,9 +717,10 @@ pub const Module = struct { scopes: BlockList(Scope) = .{}, files: BlockList(File) = .{}, values: BlockList(Value) = .{}, - functions: BlockList(Function) = .{}, - fields: BlockList(Field) = .{}, + function_definitions: BlockList(Function) = .{}, + function_declarations: BlockList(Function) = .{}, function_prototypes: BlockList(Function.Prototype) = .{}, + fields: BlockList(Field) = .{}, types: BlockList(Type) = .{}, blocks: BlockList(Block) = .{}, loops: BlockList(Loop) = .{}, @@ -721,6 +736,7 @@ pub const Module = struct { arrays: BlockList(Array) = .{}, casts: BlockList(Cast) = .{}, binary_operations: BlockList(BinaryOperation) = .{}, + branches: BlockList(Branch) = .{}, string_literal_types: data_structures.AutoArrayHashMap(u32, Type.Index) = .{}, array_types: data_structures.AutoArrayHashMap(Array, Type.Index) = .{}, entry_point: Function.Index = Function.Index.invalid, @@ -729,6 +745,7 @@ pub const Module = struct { main_package_path: []const u8, executable_path: []const u8, target: std.Target, + transpile_to_c: bool, }; const ImportFileResult = struct { @@ -1047,17 +1064,7 @@ pub fn compileModule(compilation: *Compilation, descriptor: Module.Descriptor) ! _ = try module.types.append(compilation.base_allocator, type_data); } - _ = try module.values.append(compilation.base_allocator, .{ - .bool = false, - }); - - _ = try module.values.append(compilation.base_allocator, .{ - .bool = true, - }); - - _ = try module.values.append(compilation.base_allocator, .{ - .@"unreachable" = {}, - }); + semantic_analyzer.unreachable_index = (try module.values.append(compilation.base_allocator, .@"unreachable")).index; const value_allocation = try module.values.append(compilation.base_allocator, .{ .unresolved = .{ @@ -1196,13 +1203,14 @@ pub fn log(comptime logger_scope: LoggerScope, logger: getLoggerScopeType(logger } pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, return_address: ?usize) noreturn { - const print_stack_trace = true; + const print_stack_trace = false; switch (print_stack_trace) { true => @call(.always_inline, std.builtin.default_panic, .{ message, stack_trace, return_address }), false => { writer.writeAll("\nPANIC: ") catch {}; writer.writeAll(message) catch {}; writer.writeByte('\n') catch {}; + @breakpoint(); std.os.abort(); }, } diff --git a/src/backend/emit.zig b/src/backend/emit.zig index aa2850f..799dab6 100644 --- a/src/backend/emit.zig +++ b/src/backend/emit.zig @@ -9,6 +9,7 @@ const expectEqual = std.testing.expectEqual; const Compilation = @import("../Compilation.zig"); const ir = @import("intermediate_representation.zig"); +const IR = ir.IR; const data_structures = @import("../data_structures.zig"); const ArrayList = data_structures.ArrayList; @@ -234,7 +235,7 @@ pub fn get(comptime arch: std.Target.Cpu.Arch) type { }; return struct { - pub fn initialize(allocator: Allocator, intermediate: *ir.Result, descriptor: Compilation.Module.Descriptor) !void { + pub fn initialize(allocator: Allocator, intermediate: *IR, descriptor: Compilation.Module.Descriptor) !void { switch (arch) { .x86_64 => { var mir = try backend.MIR.selectInstructions(allocator, intermediate, descriptor.target); diff --git a/src/backend/intermediate_representation.zig b/src/backend/intermediate_representation.zig index d2a5a34..0dd7532 100644 --- a/src/backend/intermediate_representation.zig +++ b/src/backend/intermediate_representation.zig @@ -20,207 +20,47 @@ const SectionManager = emit.SectionManager; pub const Logger = enum { function, + function_name, phi_removal, + weird_bug, pub var bitset = std.EnumSet(Logger).initMany(&.{ .function, + .weird_bug, + .function_name, }); }; -pub const Result = struct { - blocks: BlockList(BasicBlock) = .{}, +pub const IR = struct { + arguments: BlockList(Argument) = .{}, + basic_blocks: BlockList(BasicBlock) = .{}, + binary_operations: BlockList(BinaryOperation) = .{}, + branches: BlockList(Branch) = .{}, calls: BlockList(Call) = .{}, - function_declarations: BlockList(Function.Declaration) = .{}, - function_definitions: BlockList(Function) = .{}, + casts: BlockList(Cast) = .{}, + function_definitions: BlockList(FunctionDefinition) = .{}, instructions: BlockList(Instruction) = .{}, jumps: BlockList(Jump) = .{}, loads: BlockList(Load) = .{}, phis: BlockList(Phi) = .{}, + returns: BlockList(Return) = .{}, + stack_slots: BlockList(StackSlot) = .{}, + string_literals: BlockList(StringLiteral) = .{}, stores: BlockList(Store) = .{}, syscalls: BlockList(Syscall) = .{}, - arguments: BlockList(Argument) = .{}, - returns: BlockList(Return) = .{}, - stack_references: BlockList(StackReference) = .{}, - string_literals: BlockList(StringLiteral) = .{}, - casts: BlockList(Cast) = .{}, - binary_operations: BlockList(BinaryOperation) = .{}, - section_manager: emit.SectionManager, - module: *Module, - entry_point: Function.Index = Function.Index.invalid, - pub fn getFunctionName(ir: *Result, function_index: Function.Declaration.Index) []const u8 { + section_manager: SectionManager, + module: *Module, + entry_point: FunctionDefinition.Index, + + pub fn getFunctionName(ir: *IR, function_index: FunctionDefinition.Index) []const u8 { return ir.module.getName(ir.module.function_name_map.get(@bitCast(function_index)).?).?; } }; -pub fn initialize(compilation: *Compilation, module: *Module) !*Result { - var function_iterator = module.functions.iterator(); - const builder = try compilation.base_allocator.create(Builder); - builder.* = .{ - .allocator = compilation.base_allocator, - .ir = .{ - .module = module, - .section_manager = SectionManager{ - .allocator = compilation.base_allocator, - }, - }, - }; +pub const StringLiteral = struct { + offset: u32, - builder.ir.module = module; - _ = try builder.ir.section_manager.addSection(.{ - .name = ".text", - .size_guess = 0, - .alignment = 0x1000, - .flags = .{ - .execute = true, - .read = true, - .write = false, - }, - .type = .loadable_program, - }); - - var sema_function_index = function_iterator.getCurrentIndex(); - while (function_iterator.next()) |sema_function| { - const function_index = try builder.buildFunction(sema_function); - if (sema_function_index.eq(module.entry_point)) { - assert(!function_index.invalid); - builder.ir.entry_point = function_index; - } - - sema_function_index = function_iterator.getCurrentIndex(); - } - - assert(!builder.ir.entry_point.invalid); - - return &builder.ir; -} - -pub const BasicBlock = struct { - instructions: ArrayList(Instruction.Index) = .{}, - incomplete_phis: ArrayList(Instruction.Index) = .{}, - filled: bool = false, - sealed: bool = false, - - pub const List = BlockList(@This()); - pub const Index = List.Index; - - fn seal(basic_block: *BasicBlock) void { - for (basic_block.incomplete_phis.items) |incomplete_phi| { - _ = incomplete_phi; - unreachable; - } - - basic_block.sealed = true; - } - - fn hasJump(basic_block: *BasicBlock, ir: *Result) bool { - if (basic_block.instructions.items.len > 0) { - const last_instruction = ir.instructions.get(basic_block.instructions.getLast()); - return switch (last_instruction.u) { - .jump => true, - else => false, - }; - } else return false; - } -}; - -const Phi = struct { - instruction: Instruction.Index, - jump: Jump.Index, - block: BasicBlock.Index, - next: Phi.Index, - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Jump = struct { - source: BasicBlock.Index, - destination: BasicBlock.Index, - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -const Syscall = struct { - arguments: ArrayList(Instruction.Index), - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const AtomicOrder = enum { - unordered, - monotonic, - acquire, - release, - acquire_release, - sequentially_consistent, -}; - -pub const Load = struct { - instruction: Instruction.Index, - ordering: ?AtomicOrder = null, - @"volatile": bool = false, - - pub fn isUnordered(load: *const Load) bool { - return (load.ordering == null or load.ordering == .unordered) and !load.@"volatile"; - } - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Store = struct { - source: Instruction.Index, - destination: Instruction.Index, - ordering: ?AtomicOrder = null, - @"volatile": bool = false, - - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const StackReference = struct { - type: Type, - count: usize = 1, - alignment: u64, - offset: u64, - pub const List = BlockList(@This()); - pub const Index = List.Index; -}; - -pub const Call = struct { - function: Function.Declaration.Index, - arguments: []const Instruction.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; - pub const Allocation = List.Allocation; -}; - -pub const Argument = struct { - type: Type, - // index: usize, - pub const List = BlockList(@This()); - pub const Index = List.Index; - pub const Allocation = List.Allocation; -}; - -pub const Return = struct { - instruction: Instruction.Index, - pub const List = BlockList(@This()); - pub const Index = List.Index; - pub const Allocation = List.Allocation; -}; - -pub const Copy = struct { - foo: u64 = 0, - pub const List = BlockList(@This()); - pub const Index = List.Index; - pub const Allocation = List.Allocation; -}; - -pub const Cast = struct { - value: Instruction.Index, - type: Type, pub const List = BlockList(@This()); pub const Index = List.Index; pub const Allocation = List.Allocation; @@ -242,6 +82,7 @@ pub const BinaryOperation = struct { signed_divide, shift_left, shift_right, + integer_compare_equal, }; pub const List = BlockList(@This()); @@ -249,488 +90,361 @@ pub const BinaryOperation = struct { pub const Allocation = List.Allocation; }; -pub const CastType = enum { - sign_extend, -}; - -pub const Type = enum { - void, - noreturn, - i8, - i16, - i32, - i64, - - fn isInteger(t: Type) bool { - return switch (t) { - .i8, - .i16, - .i32, - .i64, - => true, - .void, - .noreturn, - => false, - }; - } - - pub fn getSize(t: Type) u64 { - return switch (t) { - .i8 => @sizeOf(i8), - .i16 => @sizeOf(i16), - .i32 => @sizeOf(i32), - .i64 => @sizeOf(i64), - .void, - .noreturn, - => unreachable, - }; - } - - pub fn getAlignment(t: Type) u64 { - return switch (t) { - .i8 => @alignOf(i8), - .i16 => @alignOf(i16), - .i32 => @alignOf(i32), - .i64 => @alignOf(i64), - .void, - .noreturn, - => unreachable, - }; - } -}; - -pub const Instruction = struct { - u: U, - use_list: ArrayList(Instruction.Index) = .{}, - - const U = union(enum) { - call: Call.Index, - jump: Jump.Index, - load: Load.Index, - phi: Phi.Index, - ret: Return.Index, - store: Store.Index, - syscall: Syscall.Index, - copy: Instruction.Index, - @"unreachable", - argument: Argument.Index, - load_integer: Integer, - load_string_literal: StringLiteral.Index, - stack: StackReference.Index, - sign_extend: Cast.Index, - binary_operation: BinaryOperation.Index, - }; +const Cast = struct { + value: Instruction.Index, + type: Type, pub const List = BlockList(@This()); pub const Index = List.Index; + pub const Allocation = List.Allocation; }; - -pub const StringLiteral = struct { - offset: u32, +const Syscall = struct { + arguments: ArrayList(Instruction.Index), pub const List = BlockList(@This()); pub const Index = List.Index; + pub const Allocation = List.Allocation; }; -pub const Function = struct { - declaration: Declaration.Index = Declaration.Index.invalid, - blocks: ArrayList(BasicBlock.Index) = .{}, - stack_map: AutoHashMap(Compilation.Declaration.Index, Instruction.Index) = .{}, - current_basic_block: BasicBlock.Index = BasicBlock.Index.invalid, - return_phi_node: Instruction.Index = Instruction.Index.invalid, - return_phi_block: BasicBlock.Index = BasicBlock.Index.invalid, - ir: *Result, - current_stack_offset: usize = 0, +pub const Jump = struct { + target: BasicBlock.Index, pub const List = BlockList(@This()); pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; - pub const Declaration = struct { - definition: Function.Index = Function.Index.invalid, - arguments: AutoArrayHashMap(Compilation.Declaration.Index, Instruction.Index) = .{}, - calling_convention: Compilation.CallingConvention, - return_type: Type, +pub const Branch = struct { + condition: Instruction.Index, + true_jump: Jump.Index, + false_jump: Jump.Index, - pub const List = BlockList(@This()); - pub const Index = Declaration.List.Index; - }; + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; - pub fn format(function: *const Function, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - const function_index = function.declaration; - const sema_function_index: Compilation.Function.Index = @bitCast(function_index); - const function_name_hash = function.ir.module.function_name_map.get(sema_function_index).?; - const function_name = function.ir.module.getName(function_name_hash).?; - try writer.print("Function #{} \"{s}\"\n", .{ function_index.uniqueInteger(), function_name }); - for (function.blocks.items, 0..) |block_index, function_block_index| { - try writer.print("#{}: ({})\n", .{ function_block_index, block_index.uniqueInteger() }); - const block = function.ir.blocks.get(block_index); - for (block.instructions.items, 0..) |instruction_index, block_instruction_index| { - try writer.print("%{} (${}): ", .{ block_instruction_index, instruction_index.uniqueInteger() }); - const instruction = function.ir.instructions.get(instruction_index); - if (instruction.u != .binary_operation) try writer.writeAll(@tagName(instruction.u)); - switch (instruction.u) { - .syscall => |syscall_index| { - const syscall = function.ir.syscalls.get(syscall_index); - try writer.writeAll(" ("); - for (syscall.arguments.items, 0..) |arg_index, i| { - const arg_value = function.ir.instructions.get(arg_index); +pub const Load = struct { + value: Instruction.Index, + ordering: ?AtomicOrder = null, + @"volatile": bool = false, - try writer.print("${}: {s}", .{ i, @tagName(arg_value.u) }); - - if (i < syscall.arguments.items.len - 1) { - try writer.writeAll(", "); - } - } - try writer.writeAll(")"); - }, - .jump => |jump_index| { - const jump = function.ir.jumps.get(jump_index); - try writer.print(" ${}", .{jump.destination.uniqueInteger()}); - }, - .phi => {}, - .ret => |ret_index| { - const ret = function.ir.returns.get(ret_index); - switch (ret.instruction.invalid) { - false => { - const ret_value = function.ir.instructions.get(ret.instruction); - try writer.print(" {s}", .{@tagName(ret_value.u)}); - }, - true => try writer.writeAll(" void"), - } - }, - // .load => |load_index| { - // const load = function.ir.loads.get(load_index); - // try writer.print(" {s}", .{@tagName(function.ir.values.get(load.value).*)}); - // }, - .store => |store_index| { - const store = function.ir.stores.get(store_index); - const source = function.ir.instructions.get(store.source); - const destination = function.ir.instructions.get(store.destination); - try writer.print(" {s}, {s}", .{ @tagName(destination.u), @tagName(source.u) }); - }, - .call => |call_index| { - const call = function.ir.calls.get(call_index); - try writer.print(" ${} {s}(", .{ call.function.uniqueInteger(), function.ir.getFunctionName(call.function) }); - for (call.arguments, 0..) |arg_index, i| { - const arg_value = function.ir.instructions.get(arg_index); - - try writer.print("${}: {s}", .{ i, @tagName(arg_value.u) }); - - if (i < call.arguments.len - 1) { - try writer.writeAll(", "); - } - } - try writer.writeAll(")"); - }, - .load_integer => |integer| { - try writer.print(" {s} (unsigned: 0x{x}, signed {})", .{ @tagName(integer.type), integer.value.unsigned, integer.value.unsigned }); - }, - .@"unreachable" => {}, - .load_string_literal => |string_literal_index| { - const string_literal = function.ir.string_literals.get(string_literal_index); - try writer.print(" at 0x{x}", .{string_literal.offset}); - }, - .stack => |stack_index| { - const stack = function.ir.stack_references.get(stack_index); - try writer.print(" offset: {}. size: {}. alignment: {}", .{ stack.offset, stack.type.getSize(), stack.alignment }); - }, - .argument => |argument_index| { - const argument = function.ir.arguments.get(argument_index); - try writer.print("${}, size: {}. alignment: {}", .{ argument_index, argument.type.getSize(), argument.type.getAlignment() }); - }, - .sign_extend => |cast_index| { - const cast = function.ir.casts.get(cast_index); - try writer.print(" {s} ${}", .{ @tagName(cast.type), cast.value.uniqueInteger() }); - }, - .load => |load_index| { - const load = function.ir.loads.get(load_index); - try writer.print(" ${}", .{load.instruction.uniqueInteger()}); - }, - .binary_operation => |binary_operation_index| { - const binary_operation = function.ir.binary_operations.get(binary_operation_index); - try writer.writeAll(@tagName(binary_operation.id)); - try writer.print(" {s} ${}, ${}", .{ @tagName(binary_operation.type), binary_operation.left.uniqueInteger(), binary_operation.right.uniqueInteger() }); - }, - else => |t| @panic(@tagName(t)), - } - - try writer.writeByte('\n'); - } - - try writer.writeByte('\n'); - } - _ = options; - _ = fmt; + pub fn isUnordered(load: *const Load) bool { + return (load.ordering == null or load.ordering == .unordered) and !load.@"volatile"; } + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; }; -pub const Integer = struct { +const ConstantInteger = struct { value: extern union { signed: i64, unsigned: u64, }, - type: Type, + type: Type.Scalar, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; }; -pub const Builder = struct { +pub const AtomicOrder = enum { + unordered, + monotonic, + acquire, + release, + acquire_release, + sequentially_consistent, +}; + +pub const Store = struct { + source: Instruction.Index, + destination: Instruction.Index, + ordering: ?AtomicOrder = null, + @"volatile": bool = false, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; +pub const StackSlot = struct { + type: Type, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const Return = struct { + value: Instruction.Index, + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const Argument = struct { + type: Type, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; + + pub const Map = AutoArrayHashMap(Compilation.Declaration.Index, Instruction.Index); +}; + +pub const BasicBlock = struct { + instructions: ArrayList(Instruction.Index) = .{}, + parent: FunctionDefinition.Index = FunctionDefinition.Index.invalid, + /// This variable `filled` is set to true when local value numbering is finished for a basic block, + /// that is, whenever the block is not going to receive more instructions + filled: bool = false, + sealed: bool = false, + predecessors: ArrayList(BasicBlock.Index) = .{}, + incomplete_phis: ArrayList(Instruction.Index) = .{}, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +const Builder = struct { allocator: Allocator, - ir: Result, - current_function_index: Function.Index = Function.Index.invalid, + ir: IR, + current_function_index: FunctionDefinition.Index = FunctionDefinition.Index.invalid, + current_basic_block_index: BasicBlock.Index = BasicBlock.Index.invalid, + return_phi_index: Instruction.Index = Instruction.Index.invalid, + return_basic_block_index: BasicBlock.Index = BasicBlock.Index.invalid, - fn currentFunction(builder: *Builder) *Function { - return builder.ir.function_definitions.get(builder.current_function_index); - } - - fn useInstruction(builder: *Builder, args: struct { - instruction: Instruction.Index, - user: Instruction.Index, - }) !void { - try builder.ir.instructions.get(args.instruction).use_list.append(builder.allocator, args.user); - } - - fn buildFunction(builder: *Builder, sema_function: Compilation.Function) !Function.Index { - const sema_prototype = builder.ir.module.function_prototypes.get(builder.ir.module.types.get(sema_function.prototype).function); - const function_declaration_allocation = try builder.ir.function_declarations.addOne(builder.allocator); - const function_declaration = function_declaration_allocation.ptr; - function_declaration.* = .{ - .calling_convention = sema_prototype.attributes.calling_convention, - .return_type = try builder.translateType(sema_prototype.return_type), - }; - - const function_decl_name = builder.ir.getFunctionName(function_declaration_allocation.index); - _ = function_decl_name; - - if (sema_prototype.arguments) |sema_arguments| { - try function_declaration.arguments.ensureTotalCapacity(builder.allocator, @intCast(sema_arguments.len)); - for (sema_arguments) |sema_argument_declaration_index| { - const sema_argument_declaration = builder.ir.module.declarations.get(sema_argument_declaration_index); - const argument_allocation = try builder.ir.arguments.append(builder.allocator, .{ - .type = try builder.translateType(sema_argument_declaration.type), - }); - const value_allocation = try builder.ir.instructions.append(builder.allocator, .{ - .u = .{ - .argument = argument_allocation.index, - }, - }); - function_declaration.arguments.putAssumeCapacity(sema_argument_declaration_index, value_allocation.index); - } - } - - switch (sema_prototype.attributes.@"extern") { - true => return Function.Index.invalid, - false => { - const function_allocation = try builder.ir.function_definitions.append(builder.allocator, .{ - .ir = &builder.ir, - }); - const function = function_allocation.ptr; - - builder.current_function_index = function_allocation.index; - function.declaration = function_declaration_allocation.index; - - // TODO: arguments - function.current_basic_block = try builder.newBlock(); - - const return_type = builder.ir.module.types.get(sema_prototype.return_type); - const is_noreturn = return_type.* == .noreturn; - - if (!is_noreturn) { - const exit_block = try builder.newBlock(); - const phi_instruction = try builder.appendToBlock(exit_block, .{ - .u = .{ - .phi = Phi.Index.invalid, - }, - }); - // phi.ptr.* = .{ - // .value = Value.Index.invalid, - // .jump = Jump.Index.invalid, - // .block = exit_block, - // .next = Phi.Index.invalid, - // }; - const ret = try builder.appendToBlock(exit_block, .{ - .u = .{ - .ret = (try builder.ir.returns.append(builder.allocator, .{ - .instruction = phi_instruction, - })).index, - }, - }); - try builder.useInstruction(.{ - .instruction = phi_instruction, - .user = ret, - }); - function.return_phi_node = phi_instruction; - function.return_phi_block = exit_block; - } - - try function.stack_map.ensureUnusedCapacity(builder.allocator, @intCast(function_declaration.arguments.keys().len)); - - for (function_declaration.arguments.keys(), function_declaration.arguments.values()) |sema_argument_index, ir_argument_instruction_index| { - const ir_argument_instruction = builder.ir.instructions.get(ir_argument_instruction_index); - const ir_argument = builder.ir.arguments.get(ir_argument_instruction.u.argument); - - _ = try builder.stackReference(.{ - .type = ir_argument.type, - .sema = sema_argument_index, - }); - } - - for (function_declaration.arguments.keys(), function_declaration.arguments.values()) |sema_argument_index, ir_argument_instruction_index| { - const stack_reference = builder.currentFunction().stack_map.get(sema_argument_index).?; - - const store_instruction = try builder.store(.{ - .source = ir_argument_instruction_index, - .destination = stack_reference, - }); - _ = store_instruction; - } - - const sema_block = sema_function.getBodyBlock(builder.ir.module); - try builder.block(sema_block, .{ .emit_exit_block = !is_noreturn }); - - if (!is_noreturn and sema_block.reaches_end) { - if (!builder.ir.blocks.get(builder.currentFunction().current_basic_block).hasJump(&builder.ir)) { - _ = try builder.append(.{ - .u = .{ - .jump = try builder.jump(.{ - .source = builder.currentFunction().current_basic_block, - .destination = builder.currentFunction().return_phi_block, - }), - }, - }); - } - } - - builder.currentFunction().current_stack_offset = std.mem.alignForward(usize, builder.currentFunction().current_stack_offset, 0x10); - try builder.optimizeFunction(builder.currentFunction()); - - return function_allocation.index; + fn translateType(builder: *Builder, type_index: Compilation.Type.Index) !?Type { + const sema_type = builder.ir.module.types.get(type_index); + return switch (sema_type.*) { + .integer => |integer| switch (integer.bit_count) { + 8 => Type.i8, + 16 => Type.i16, + 32 => Type.i32, + 64 => Type.i64, + else => unreachable, }, - } - } - - fn blockInsideBasicBlock(builder: *Builder, sema_block: *Compilation.Block, block_index: BasicBlock.Index) !BasicBlock.Index { - const current_function = builder.currentFunction(); - current_function.current_basic_block = block_index; - try builder.block(sema_block, .{}); - return current_function.current_basic_block; - } - - const BlockOptions = packed struct { - emit_exit_block: bool = true, - }; - - fn emitSyscallArgument(builder: *Builder, sema_syscall_argument_value_index: Compilation.Value.Index) !Instruction.Index { - const sema_syscall_argument_value = builder.ir.module.values.get(sema_syscall_argument_value_index); - return switch (sema_syscall_argument_value.*) { - .integer => |integer| try builder.processInteger(integer), - .sign_extend => |cast_index| try builder.processCast(cast_index, .sign_extend), - .declaration_reference => |declaration_reference| try builder.loadDeclarationReference(declaration_reference.value), + // TODO + .pointer => Type.i64, + .bool => Type.i1, + .void, + .noreturn, + => null, else => |t| @panic(@tagName(t)), }; } - fn processCast(builder: *Builder, sema_cast_index: Compilation.Cast.Index, cast_type: CastType) !Instruction.Index { - const sema_cast = builder.ir.module.casts.get(sema_cast_index); - const sema_source_value = builder.ir.module.values.get(sema_cast.value); - const source_value = switch (sema_source_value.*) { - .declaration_reference => |declaration_reference| try builder.loadDeclarationReference(declaration_reference.value), - else => |t| @panic(@tagName(t)), - }; + fn allocateBlock(builder: *Builder) !BasicBlock.Allocation { + const current_function_index = builder.current_function_index; + assert(!current_function_index.invalid); + const basic_block = try builder.ir.basic_blocks.append(builder.allocator, .{}); + basic_block.ptr.parent = current_function_index; - const cast_allocation = try builder.ir.casts.append(builder.allocator, .{ - .value = source_value, - .type = try builder.translateType(sema_cast.type), - }); - - const result = try builder.append(.{ - .u = @unionInit(Instruction.U, switch (cast_type) { - inline else => |ct| @tagName(ct), - }, cast_allocation.index), - }); - - return result; + return basic_block; } - fn processDeclarationReferenceRaw(builder: *Builder, declaration_index: Compilation.Declaration.Index) !Instruction.Index { - const sema_declaration = builder.ir.module.declarations.get(declaration_index); - const result = switch (sema_declaration.scope_type) { - .local => builder.currentFunction().stack_map.get(declaration_index).?, - .global => unreachable, - }; - return result; + fn appendAndSetCurrentBlock(builder: *Builder) !BasicBlock.Allocation { + const basic_block = try builder.allocateBlock(); + builder.current_basic_block_index = basic_block.index; + return basic_block; } - fn loadDeclarationReference(builder: *Builder, declaration_index: Compilation.Declaration.Index) !Instruction.Index { - const stack_instruction = try builder.processDeclarationReferenceRaw(declaration_index); - const load = try builder.ir.loads.append(builder.allocator, .{ - .instruction = stack_instruction, - }); - return try builder.append(.{ - .u = .{ - .load = load.index, - }, - }); - } - - fn processInteger(builder: *Builder, integer_value: Compilation.Value.Integer) !Instruction.Index { - const integer = Integer{ - .value = .{ - .unsigned = integer_value.value, - }, - .type = try builder.translateType(integer_value.type), - }; - assert(integer.type.isInteger()); - const instruction_allocation = try builder.ir.instructions.append(builder.allocator, .{ - .u = .{ - .load_integer = integer, - }, - }); - // const load_integer = try builder.append(.{ - // .load_integer = integer, - // }); - return instruction_allocation.index; - } - - fn processSyscall(builder: *Builder, sema_syscall_index: Compilation.Syscall.Index) anyerror!Instruction.Index { - const sema_syscall = builder.ir.module.syscalls.get(sema_syscall_index); - var arguments = try ArrayList(Instruction.Index).initCapacity(builder.allocator, sema_syscall.argument_count + 1); - - const sema_syscall_number = sema_syscall.number; - assert(!sema_syscall_number.invalid); - const number_value_index = try builder.emitSyscallArgument(sema_syscall_number); - - arguments.appendAssumeCapacity(number_value_index); - - for (sema_syscall.getArguments()) |sema_syscall_argument| { - assert(!sema_syscall_argument.invalid); - const argument_value_index = try builder.emitSyscallArgument(sema_syscall_argument); - arguments.appendAssumeCapacity(argument_value_index); - } - - const syscall_allocation = try builder.ir.syscalls.append(builder.allocator, .{ - .arguments = arguments, + fn createStackSlot(builder: *Builder, arguments: struct { + type: Type, + sema: Compilation.Declaration.Index, + }) !Instruction.Index { + const current_function = builder.ir.function_definitions.get(builder.current_function_index); + const stack_reference_allocation = try builder.ir.stack_slots.append(builder.allocator, .{ + .type = arguments.type, }); - const instruction_index = try builder.append(.{ - .u = .{ - .syscall = syscall_allocation.index, - }, + const instruction_index = try builder.createInstructionAndAppendToCurrentBlock(.{ + .stack_slot = stack_reference_allocation.index, }); - for (arguments.items) |argument| { - try builder.useInstruction(.{ - .instruction = argument, - .user = instruction_index, - }); - } + try current_function.stack_map.put(builder.allocator, arguments.sema, instruction_index); return instruction_index; } - fn processBinaryOperation(builder: *Builder, sema_binary_operation_index: Compilation.BinaryOperation.Index) !Instruction.Index { + fn createInstructionAndAppendToCurrentBlock(builder: *Builder, instruction_union: Instruction.U) !Instruction.Index { + const current_function_index = builder.current_function_index; + assert(!current_function_index.invalid); + const current_basic_block_index = builder.current_basic_block_index; + assert(!current_basic_block_index.invalid); + const current_basic_block = builder.ir.basic_blocks.get(current_basic_block_index); + assert(current_basic_block.parent.eq(current_function_index)); + const instruction = try builder.ir.instructions.append(builder.allocator, .{ + .u = instruction_union, + }); + + try builder.appendToBlock(current_basic_block_index, instruction.index); + + return instruction.index; + } + + fn appendToBlock(builder: *Builder, basic_block_index: BasicBlock.Index, instruction_index: Instruction.Index) !void { + const basic_block = builder.ir.basic_blocks.get(basic_block_index); + const instruction = builder.ir.instructions.get(instruction_index); + assert(instruction.parent.invalid); + instruction.parent = basic_block_index; + try basic_block.instructions.append(builder.allocator, instruction_index); + } + + fn emitConstantInteger(builder: *Builder, constant_integer: ConstantInteger) !Instruction.Index { + // TODO: should we emit integer constants to the block? + assert(constant_integer.type.getKind() == .integer); + const load_integer = try builder.createInstructionAndAppendToCurrentBlock(.{ + .constant_integer = constant_integer, + }); + return load_integer; + } + + fn emitValue(builder: *Builder, sema_value_index: Compilation.Value.Index) !Instruction.Index { + const sema_value = builder.ir.module.values.get(sema_value_index); + const result = switch (sema_value.*) { + .bool => |boolean| try builder.emitConstantInteger(ConstantInteger{ + .value = .{ + .unsigned = @intFromBool(boolean), + }, + .type = .i8, + }), + .integer => |integer| try builder.emitConstantInteger(ConstantInteger{ + .value = .{ + .unsigned = integer.value, + }, + .type = (builder.translateType(integer.type) catch unreachable orelse unreachable).scalar, + }), + .declaration_reference => |sema_declaration_reference| blk: { + const sema_declaration_index = sema_declaration_reference.value; + const sema_declaration = builder.ir.module.declarations.get(sema_declaration_index); + // TODO: substitute stack slot with a precise name + const stack_slot = switch (sema_declaration.scope_type) { + .local => local: { + const current_function = builder.ir.function_definitions.get(builder.current_function_index); + const stack = current_function.stack_map.get(sema_declaration_index).?; + break :local stack; + }, + .global => unreachable, + }; + const load = try builder.ir.loads.append(builder.allocator, .{ + .value = stack_slot, + }); + const instruction = try builder.createInstructionAndAppendToCurrentBlock(.{ + .load = load.index, + }); + + break :blk instruction; + }, + .sign_extend => |sema_cast_index| blk: { + const cast_type: CastType = switch (sema_value.*) { + .sign_extend => .sign_extend, + else => unreachable, + }; + const sema_cast = builder.ir.module.casts.get(sema_cast_index); + const source_value = try builder.emitValue(sema_cast.value); + + const cast_allocation = try builder.ir.casts.append(builder.allocator, .{ + .value = source_value, + .type = try builder.translateType(sema_cast.type) orelse unreachable, + }); + + break :blk try builder.createInstructionAndAppendToCurrentBlock(switch (cast_type) { + inline else => |ct| @unionInit(Instruction.U, @tagName(ct), cast_allocation.index), + }); + }, + .call => |sema_call_index| try builder.emitCall(sema_call_index), + .binary_operation => |sema_binary_operation_index| try builder.emitBinaryOperation(sema_binary_operation_index), + .syscall => |sema_syscall_index| try builder.emitSyscall(sema_syscall_index), + .string_literal => |sema_string_literal_index| blk: { + const string_literal = builder.ir.module.string_literals.getValue(sema_string_literal_index).?; + + if (builder.ir.section_manager.rodata == null) { + const rodata_index = try builder.ir.section_manager.addSection(.{ + .name = ".rodata", + .size_guess = 0, + .alignment = 0x1000, + .flags = .{ + .read = true, + .write = false, + .execute = false, + }, + .type = .loadable_program, + }); + + builder.ir.section_manager.rodata = @intCast(rodata_index); + } + + const rodata_index = builder.ir.section_manager.rodata orelse unreachable; + const rodata_section_offset = builder.ir.section_manager.getSectionOffset(rodata_index); + + try builder.ir.section_manager.appendToSection(rodata_index, string_literal); + try builder.ir.section_manager.appendByteToSection(rodata_index, 0); + + const string_literal_allocation = try builder.ir.string_literals.append(builder.allocator, .{ + .offset = @intCast(rodata_section_offset), + }); + + break :blk try builder.createInstructionAndAppendToCurrentBlock(.{ + .constant_string_literal = string_literal_allocation.index, + }); + }, + else => |t| @panic(@tagName(t)), + }; + + return result; + } + + fn emitCall(builder: *Builder, sema_call_index: Compilation.Call.Index) anyerror!Instruction.Index { + const sema_call = builder.ir.module.calls.get(sema_call_index); + const sema_argument_list_index = sema_call.arguments; + + const argument_list: []const Instruction.Index = switch (sema_argument_list_index.invalid) { + false => blk: { + var argument_list = ArrayList(Instruction.Index){}; + const sema_argument_list = builder.ir.module.argument_lists.get(sema_argument_list_index); + try argument_list.ensureTotalCapacity(builder.allocator, sema_argument_list.array.items.len); + for (sema_argument_list.array.items) |sema_argument_value_index| { + const argument_value_index = try builder.emitValue(sema_argument_value_index); + argument_list.appendAssumeCapacity(argument_value_index); + } + break :blk argument_list.items; + }, + true => &.{}, + }; + + const call = try builder.ir.calls.append(builder.allocator, .{ + .callable = switch (builder.ir.module.values.get(sema_call.value).*) { + .function_definition => |sema_function_definition_index| .{ + .function_definition = .{ + .element = sema_function_definition_index.element, + .block = sema_function_definition_index.block, + }, + }, + // .function => |function_index| .{ + // .index = function_index.index, + // .block = function_index.block, + // }, + else => |t| @panic(@tagName(t)), + }, + .arguments = argument_list, + }); + + const instruction_index = try builder.createInstructionAndAppendToCurrentBlock(.{ + .call = call.index, + }); + + return instruction_index; + } + + fn emitBinaryOperation(builder: *Builder, sema_binary_operation_index: Compilation.BinaryOperation.Index) anyerror!Instruction.Index { const sema_binary_operation = builder.ir.module.binary_operations.get(sema_binary_operation_index); - const left = try builder.emitBinaryOperationOperand(sema_binary_operation.left); - const right = try builder.emitBinaryOperationOperand(sema_binary_operation.right); + const left = try builder.emitValue(sema_binary_operation.left); + const right = try builder.emitValue(sema_binary_operation.right); const sema_type = builder.ir.module.types.get(sema_binary_operation.type).*; const binary_operation_type = try builder.translateType(sema_binary_operation.type); @@ -760,131 +474,27 @@ pub const Builder = struct { }, .shift_left => .shift_left, .shift_right => .shift_right, + .compare_equal => switch (sema_type) { + .integer => .integer_compare_equal, + else => unreachable, + }, }, - .type = binary_operation_type, + .type = binary_operation_type orelse unreachable, }); - const instruction = try builder.append(.{ - .u = .{ - .binary_operation = binary_operation.index, - }, - }); - - try builder.useInstruction(.{ - .instruction = left, - .user = instruction, - }); - - try builder.useInstruction(.{ - .instruction = right, - .user = instruction, + const instruction = try builder.createInstructionAndAppendToCurrentBlock(.{ + .binary_operation = binary_operation.index, }); return instruction; } - fn block(builder: *Builder, sema_block: *Compilation.Block, options: BlockOptions) anyerror!void { + fn emitBlock(builder: *Builder, sema_block_index: Compilation.Block.Index) anyerror!void { + const sema_block = builder.ir.module.blocks.get(sema_block_index); + for (sema_block.statements.items) |sema_statement_index| { const sema_statement = builder.ir.module.values.get(sema_statement_index); switch (sema_statement.*) { - .loop => |loop_index| { - const sema_loop = builder.ir.module.loops.get(loop_index); - const sema_loop_condition = builder.ir.module.values.get(sema_loop.condition); - const sema_loop_body = builder.ir.module.values.get(sema_loop.body); - const condition: Compilation.Value.Index = switch (sema_loop_condition.*) { - .bool => |bool_value| switch (bool_value) { - true => Compilation.Value.Index.invalid, - false => unreachable, - }, - else => |t| @panic(@tagName(t)), - }; - - const original_block = builder.currentFunction().current_basic_block; - const jump_to_loop = try builder.append(.{ - .u = .{ - .jump = undefined, - }, - }); - const loop_body_block = try builder.newBlock(); - const loop_prologue_block = if (options.emit_exit_block) try builder.newBlock() else BasicBlock.Index.invalid; - - const loop_head_block = switch (!condition.invalid) { - false => loop_body_block, - true => unreachable, - }; - - builder.ir.instructions.get(jump_to_loop).u.jump = try builder.jump(.{ - .source = original_block, - .destination = loop_head_block, - }); - - const sema_body_block = builder.ir.module.blocks.get(sema_loop_body.block); - builder.currentFunction().current_basic_block = try builder.blockInsideBasicBlock(sema_body_block, loop_body_block); - if (!loop_prologue_block.invalid) { - builder.ir.blocks.get(loop_prologue_block).seal(); - } - - if (sema_body_block.reaches_end) { - _ = try builder.append(.{ - .u = .{ - .jump = try builder.jump(.{ - .source = builder.currentFunction().current_basic_block, - .destination = loop_head_block, - }), - }, - }); - } - - builder.ir.blocks.get(builder.currentFunction().current_basic_block).filled = true; - builder.ir.blocks.get(loop_body_block).seal(); - if (!loop_head_block.eq(loop_body_block)) { - unreachable; - } - - if (!loop_prologue_block.invalid) { - builder.currentFunction().current_basic_block = loop_prologue_block; - } - }, - .syscall => |sema_syscall_index| _ = try builder.processSyscall(sema_syscall_index), - .@"unreachable" => _ = try builder.append(.{ - .u = .{ - .@"unreachable" = {}, - }, - }), - .@"return" => |sema_ret_index| { - const sema_ret = builder.ir.module.returns.get(sema_ret_index); - const return_value = try builder.emitReturnValue(sema_ret.value); - const phi_instruction = builder.ir.instructions.get(builder.currentFunction().return_phi_node); - const phi = switch (phi_instruction.u.phi.invalid) { - false => unreachable, - true => (try builder.ir.phis.append(builder.allocator, std.mem.zeroes(Phi))).ptr, - }; //builder.ir.phis.get(phi_instruction.phi); - const exit_jump = try builder.jump(.{ - .source = builder.currentFunction().current_basic_block, - .destination = switch (!phi_instruction.u.phi.invalid) { - true => phi.block, - false => builder.currentFunction().return_phi_block, - }, - }); - - phi_instruction.u.phi = (try builder.ir.phis.append(builder.allocator, .{ - .instruction = return_value, - .jump = exit_jump, - .next = phi_instruction.u.phi, - .block = phi.block, - })).index; - - try builder.useInstruction(.{ - .instruction = return_value, - .user = builder.currentFunction().return_phi_node, - }); - - _ = try builder.append(.{ - .u = .{ - .jump = exit_jump, - }, - }); - }, .declaration => |sema_declaration_index| { const sema_declaration = builder.ir.module.declarations.get(sema_declaration_index); //logln("Name: {s}\n", .{builder.module.getName(sema_declaration.name).?}); @@ -893,561 +503,767 @@ pub const Builder = struct { switch (declaration_type.*) { .comptime_int => unreachable, else => { - var value_index = try builder.emitDeclarationInitValue(sema_declaration.init_value); - const value = builder.ir.instructions.get(value_index); - value_index = switch (value.u) { - .load_integer, - .call, - .binary_operation, - => value_index, - // .call => try builder.load(value_index), - else => |t| @panic(@tagName(t)), - }; - const ir_type = try builder.translateType(sema_declaration.type); - - const stack_i = try builder.stackReference(.{ - .type = ir_type, + const stack_slot = try builder.createStackSlot(.{ + .type = ir_type orelse unreachable, .sema = sema_declaration_index, }); - const store_instruction = try builder.store(.{ - .source = value_index, - .destination = stack_i, + + _ = try builder.emitAssignment(.{ + .destination = stack_slot, + .sema_source = sema_declaration.init_value, }); - _ = store_instruction; }, } }, - .call => |sema_call_index| _ = try builder.processCall(sema_call_index), + .branch => |sema_branch_index| { + const sema_branch = builder.ir.module.branches.get(sema_branch_index); + const condition = try builder.emitValue(sema_branch.condition); + const true_expression = builder.ir.module.values.get(sema_branch.true_expression); + const false_expression = builder.ir.module.values.get(sema_branch.false_expression); + + const true_block = try builder.allocateBlock(); + const false_block = try builder.allocateBlock(); + + const current_basic_block_index = builder.current_basic_block_index; + assert(!current_basic_block_index.invalid); + + const branch = try builder.ir.branches.append(builder.allocator, .{ + .condition = condition, + .true_jump = try builder.createJump(.{ + .source = current_basic_block_index, + .target = true_block.index, + }), + .false_jump = try builder.createJump(.{ + .source = current_basic_block_index, + .target = false_block.index, + }), + }); + + _ = try builder.createInstructionAndAppendToCurrentBlock(.{ + .branch = branch.index, + }); + builder.ir.basic_blocks.get(current_basic_block_index).filled = true; + try builder.sealBlock(true_block.index); + try builder.sealBlock(false_block.index); + + const exit_block = try builder.allocateBlock(); + + const sema_true_block = builder.ir.module.blocks.get(true_expression.block); + try builder.pushBlockAndEmit(true_block.index, true_expression.block); + if (sema_true_block.reaches_end) { + const jump_index = try builder.createJump(.{ + .source = builder.current_basic_block_index, + .target = exit_block.index, + }); + _ = try builder.createInstructionAndAppendToCurrentBlock(.{ + .jump = jump_index, + }); + } + builder.ir.basic_blocks.get(builder.current_basic_block_index).filled = true; + + const sema_false_block = builder.ir.module.blocks.get(false_expression.block); + try builder.pushBlockAndEmit(false_block.index, false_expression.block); + if (sema_false_block.reaches_end) { + const jump_index = try builder.createJump(.{ + .source = builder.current_basic_block_index, + .target = exit_block.index, + }); + _ = try builder.createInstructionAndAppendToCurrentBlock(.{ + .jump = jump_index, + }); + } + builder.ir.basic_blocks.get(builder.current_basic_block_index).filled = true; + + try builder.sealBlock(exit_block.index); + + builder.current_basic_block_index = exit_block.index; + }, + .@"return" => |sema_return_index| { + const sema_return = builder.ir.module.returns.get(sema_return_index); + assert(!builder.return_basic_block_index.invalid); + const jump_index = try builder.createJump(.{ + .source = builder.current_basic_block_index, + .target = builder.return_basic_block_index, + }); + + if (!sema_return.value.invalid) { + const return_value = try builder.emitValue(sema_return.value); + const return_phi_instruction = builder.ir.instructions.get(builder.return_phi_index); + assert(return_phi_instruction.parent.eq(builder.return_basic_block_index)); + const return_phi = builder.ir.phis.get(return_phi_instruction.u.phi); + try return_phi.operands.append(builder.allocator, .{ + .jump = jump_index, + .value = return_value, + }); + } + + _ = try builder.createInstructionAndAppendToCurrentBlock(.{ + .jump = jump_index, + }); + }, + .syscall => |sema_syscall_index| _ = try builder.emitSyscall(sema_syscall_index), + .@"unreachable" => _ = try builder.createInstructionAndAppendToCurrentBlock(.@"unreachable"), + .call => |sema_call_index| _ = try builder.emitCall(sema_call_index), .assign => |sema_assignment_index| { const sema_assignment = builder.ir.module.assignments.get(sema_assignment_index); - const sema_left = builder.ir.module.values.get(sema_assignment.store); - assert(sema_left.* == .declaration_reference); - const sema_declaration_index = sema_left.declaration_reference.value; - const stack = builder.currentFunction().stack_map.get(sema_declaration_index).?; - const value_index = try builder.emitDeclarationInitValue(sema_assignment.load); - const store_instruction = try builder.store(.{ - .source = value_index, - .destination = stack, + const current_function = builder.ir.function_definitions.get(builder.current_function_index); + const sema_declaration = builder.ir.module.values.get(sema_assignment.store).declaration_reference.value; + const destination = current_function.stack_map.get(sema_declaration).?; + _ = try builder.emitAssignment(.{ + .destination = destination, + .sema_source = sema_assignment.load, }); - _ = store_instruction; }, else => |t| @panic(@tagName(t)), } } } - fn emitDeclarationInitValue(builder: *Builder, declaration_init_value_index: Compilation.Value.Index) !Instruction.Index { - const declaration_init_value = builder.ir.module.values.get(declaration_init_value_index); - return switch (declaration_init_value.*) { - .call => |call_index| try builder.processCall(call_index), - .integer => |integer| try builder.processInteger(integer), - .binary_operation => |binary_operation_index| try builder.processBinaryOperation(binary_operation_index), - else => |t| @panic(@tagName(t)), - }; - } - - fn emitReturnValue(builder: *Builder, return_value_index: Compilation.Value.Index) !Instruction.Index { - const return_value = builder.ir.module.values.get(return_value_index); - return switch (return_value.*) { - .syscall => |syscall_index| try builder.processSyscall(syscall_index), - .integer => |integer| try builder.processInteger(integer), - .call => |call_index| try builder.processCall(call_index), - .declaration_reference => |declaration_reference| try builder.loadDeclarationReference(declaration_reference.value), - .binary_operation => |binary_operation_index| try builder.processBinaryOperation(binary_operation_index), - else => |t| @panic(@tagName(t)), - }; - } - - fn emitBinaryOperationOperand(builder: *Builder, binary_operation_index: Compilation.Value.Index) anyerror!Instruction.Index { - const value = builder.ir.module.values.get(binary_operation_index); - return switch (value.*) { - .integer => |integer| try builder.processInteger(integer), - .call => |call_index| try builder.processCall(call_index), - .declaration_reference => |declaration_reference| try builder.loadDeclarationReference(declaration_reference.value), - .binary_operation => |boi| try builder.processBinaryOperation(boi), - else => |t| @panic(@tagName(t)), - }; - } - - fn stackReference(builder: *Builder, arguments: struct { - type: Type, - sema: Compilation.Declaration.Index, - alignment: ?u64 = null, + fn emitAssignment(builder: *Builder, arguments: struct { + destination: Instruction.Index, + sema_source: Compilation.Value.Index, }) !Instruction.Index { - const size = arguments.type.getSize(); - assert(size > 0); - const alignment = if (arguments.alignment) |a| a else arguments.type.getAlignment(); - builder.currentFunction().current_stack_offset = std.mem.alignForward(u64, builder.currentFunction().current_stack_offset, alignment); - builder.currentFunction().current_stack_offset += size; - const stack_offset = builder.currentFunction().current_stack_offset; - const stack_reference_allocation = try builder.ir.stack_references.append(builder.allocator, .{ - .offset = stack_offset, - .type = arguments.type, - .alignment = alignment, + const value_index = try builder.emitValue(arguments.sema_source); + + const store = try builder.ir.stores.append(builder.allocator, .{ + .destination = arguments.destination, + .source = value_index, }); - const instruction_index = try builder.append(.{ - .u = .{ - .stack = stack_reference_allocation.index, - }, + return try builder.createInstructionAndAppendToCurrentBlock(.{ + .store = store.index, }); - - try builder.currentFunction().stack_map.put(builder.allocator, arguments.sema, instruction_index); - - return instruction_index; } - fn store(builder: *Builder, descriptor: Store) !Instruction.Index { - const store_allocation = try builder.ir.stores.append(builder.allocator, descriptor); + fn emitSyscall(builder: *Builder, sema_syscall_index: Compilation.Syscall.Index) anyerror!Instruction.Index { + const sema_syscall = builder.ir.module.syscalls.get(sema_syscall_index); + var arguments = try ArrayList(Instruction.Index).initCapacity(builder.allocator, sema_syscall.argument_count + 1); - const result = try builder.append(.{ - .u = .{ - .store = store_allocation.index, - }, - }); + const sema_syscall_number = sema_syscall.number; + assert(!sema_syscall_number.invalid); + const number_value_index = try builder.emitValue(sema_syscall_number); - try builder.useInstruction(.{ - .instruction = descriptor.source, - .user = result, - }); + arguments.appendAssumeCapacity(number_value_index); - try builder.useInstruction(.{ - .instruction = descriptor.destination, - .user = result, - }); - - return result; - } - - fn emitCallArgument(builder: *Builder, call_argument_value_index: Compilation.Value.Index) !Instruction.Index { - const call_argument_value = builder.ir.module.values.get(call_argument_value_index); - return switch (call_argument_value.*) { - .integer => |integer| try builder.processInteger(integer), - .declaration_reference => |declaration_reference| try builder.loadDeclarationReference(declaration_reference.value), - .string_literal => |string_literal_index| try builder.processStringLiteral(string_literal_index), - else => |t| @panic(@tagName(t)), - }; - } - - fn processCall(builder: *Builder, sema_call_index: Compilation.Call.Index) anyerror!Instruction.Index { - const sema_call = builder.ir.module.calls.get(sema_call_index); - const sema_argument_list_index = sema_call.arguments; - const argument_list: []const Instruction.Index = switch (sema_argument_list_index.invalid) { - false => blk: { - var argument_list = ArrayList(Instruction.Index){}; - const sema_argument_list = builder.ir.module.argument_lists.get(sema_argument_list_index); - try argument_list.ensureTotalCapacity(builder.allocator, sema_argument_list.array.items.len); - for (sema_argument_list.array.items) |sema_argument_value_index| { - const argument_value_index = try builder.emitCallArgument(sema_argument_value_index); - argument_list.appendAssumeCapacity(argument_value_index); - } - break :blk argument_list.items; - }, - true => &.{}, - }; - - const call_index = try builder.call(.{ - .function = switch (builder.ir.module.values.get(sema_call.value).*) { - .function => |function_index| .{ - .index = function_index.index, - .block = function_index.block, - }, - else => |t| @panic(@tagName(t)), - }, - .arguments = argument_list, - }); - - const instruction_index = try builder.append(.{ - .u = .{ - .call = call_index, - }, - }); - - for (argument_list) |argument| { - try builder.useInstruction(.{ - .instruction = argument, - .user = instruction_index, - }); + for (sema_syscall.getArguments()) |sema_syscall_argument| { + assert(!sema_syscall_argument.invalid); + const argument_value_index = try builder.emitValue(sema_syscall_argument); + arguments.appendAssumeCapacity(argument_value_index); } - return instruction_index; + const syscall = try builder.ir.syscalls.append(builder.allocator, .{ + .arguments = arguments, + }); + + return try builder.createInstructionAndAppendToCurrentBlock(.{ + .syscall = syscall.index, + }); } - fn processStringLiteral(builder: *Builder, string_literal_hash: u32) !Instruction.Index { - const string_literal = builder.ir.module.string_literals.getValue(string_literal_hash).?; + fn createJump(builder: *Builder, arguments: struct { + source: BasicBlock.Index, + target: BasicBlock.Index, + }) !Jump.Index { + assert(!arguments.source.invalid); + assert(!arguments.target.invalid); - if (builder.ir.section_manager.rodata == null) { - const rodata_index = try builder.ir.section_manager.addSection(.{ - .name = ".rodata", - .size_guess = 0, - .alignment = 0x1000, - .flags = .{ - .read = true, - .write = false, - .execute = false, - }, - .type = .loadable_program, - }); + const target_block = builder.ir.basic_blocks.get(arguments.target); + assert(!target_block.sealed); + const jump = try builder.ir.jumps.append(builder.allocator, .{ + .target = arguments.target, + }); + try target_block.predecessors.append(builder.allocator, arguments.source); + // TODO: predecessors + return jump.index; + } - builder.ir.section_manager.rodata = @intCast(rodata_index); + fn sealBlock(builder: *Builder, basic_block_index: BasicBlock.Index) !void { + const block = builder.ir.basic_blocks.get(basic_block_index); + for (block.incomplete_phis.items) |_| { + unreachable; } - - const rodata_index = builder.ir.section_manager.rodata orelse unreachable; - const rodata_section_offset = builder.ir.section_manager.getSectionOffset(rodata_index); - - try builder.ir.section_manager.appendToSection(rodata_index, string_literal); - try builder.ir.section_manager.appendByteToSection(rodata_index, 0); - - const string_literal_allocation = try builder.ir.string_literals.append(builder.allocator, .{ - .offset = @intCast(rodata_section_offset), - }); - - const result = try builder.append(.{ - .u = .{ - .load_string_literal = string_literal_allocation.index, - }, - }); - - return result; + block.sealed = true; } - fn translateType(builder: *Builder, type_index: Compilation.Type.Index) !Type { - const sema_type = builder.ir.module.types.get(type_index); - return switch (sema_type.*) { - .integer => |integer| switch (integer.bit_count) { - 8 => .i8, - 16 => .i16, - 32 => .i32, - 64 => .i64, - else => unreachable, - }, - // TODO - .pointer => .i64, - .void => .void, - .noreturn => .noreturn, - else => |t| @panic(@tagName(t)), - }; + fn pushBlockAndEmit(builder: *Builder, basic_block_index: BasicBlock.Index, sema_block_index: Compilation.Block.Index) !void { + builder.current_basic_block_index = basic_block_index; + try builder.emitBlock(sema_block_index); } +}; +pub const CastType = enum { + sign_extend, +}; - fn call(builder: *Builder, descriptor: Call) !Call.Index { - const call_allocation = try builder.ir.calls.append(builder.allocator, descriptor); - return call_allocation.index; - } - - fn jump(builder: *Builder, descriptor: Jump) !Jump.Index { - const destination_block = builder.ir.blocks.get(descriptor.destination); - assert(!destination_block.sealed); - assert(!descriptor.source.invalid); - const jump_allocation = try builder.ir.jumps.append(builder.allocator, descriptor); - return jump_allocation.index; - } - - fn append(builder: *Builder, instruction: Instruction) !Instruction.Index { - assert(!builder.current_function_index.invalid); - const current_function = builder.currentFunction(); - assert(!current_function.current_basic_block.invalid); - return builder.appendToBlock(current_function.current_basic_block, instruction); - } - - fn appendToBlock(builder: *Builder, block_index: BasicBlock.Index, instruction: Instruction) !Instruction.Index { - const instruction_allocation = try builder.ir.instructions.append(builder.allocator, instruction); - try builder.ir.blocks.get(block_index).instructions.append(builder.allocator, instruction_allocation.index); - - return instruction_allocation.index; - } - - fn newBlock(builder: *Builder) !BasicBlock.Index { - const new_block_allocation = try builder.ir.blocks.append(builder.allocator, .{}); - const current_function = builder.currentFunction(); - try current_function.blocks.append(builder.allocator, new_block_allocation.index); - - return new_block_allocation.index; - } +pub fn findReachableBlocks(arguments: struct { + allocator: Allocator, + ir: *IR, + first: BasicBlock.Index, + traverse_functions: bool, +}) !ArrayList(BasicBlock.Index) { + const allocator = arguments.allocator; + const ir = arguments.ir; + const first = arguments.first; + const traverse_functions = arguments.traverse_functions; const BlockSearcher = struct { to_visit: ArrayList(BasicBlock.Index) = .{}, visited: AutoArrayHashMap(BasicBlock.Index, void) = .{}, + + fn visit(searcher: *@This(), a: Allocator, basic_block: BasicBlock.Index) !void { + if (searcher.visited.get(basic_block) == null) { + try searcher.to_visit.append(a, basic_block); + try searcher.visited.put(a, basic_block, {}); + } + } }; - fn findReachableBlocks(builder: *Builder, first: BasicBlock.Index) !ArrayList(BasicBlock.Index) { - var searcher = BlockSearcher{}; - try searcher.to_visit.append(builder.allocator, first); - try searcher.visited.put(builder.allocator, first, {}); + var searcher = BlockSearcher{}; + try searcher.to_visit.append(allocator, first); + try searcher.visited.put(allocator, first, {}); - while (searcher.to_visit.items.len > 0) { - const block_index = searcher.to_visit.swapRemove(0); - const block_to_visit = builder.ir.blocks.get(block_index); - const last_instruction_index = block_to_visit.instructions.items[block_to_visit.instructions.items.len - 1]; - const last_instruction = builder.ir.instructions.get(last_instruction_index); - const block_to_search = switch (last_instruction.u) { - .jump => |jump_index| blk: { - const ir_jump = builder.ir.jumps.get(jump_index); - assert(ir_jump.source.eq(block_index)); - const new_block = ir_jump.destination; - break :blk new_block; - }, - .call => |call_index| blk: { - const ir_call = builder.ir.calls.get(call_index); - const function_declaration_index = ir_call.function; - const function_declaration = builder.ir.function_declarations.get(function_declaration_index); - const function_definition_index = function_declaration.definition; - switch (function_definition_index.invalid) { - false => { - const function = builder.ir.function_definitions.get(function_definition_index); - const first_block = function.blocks.items[0]; - break :blk first_block; + while (searcher.to_visit.items.len > 0) { + const block_index = searcher.to_visit.swapRemove(0); + const block_to_visit = ir.basic_blocks.get(block_index); + const last_instruction_index = block_to_visit.instructions.items[block_to_visit.instructions.items.len - 1]; + const last_instruction = ir.instructions.get(last_instruction_index); + switch (last_instruction.u) { + .jump => |jump_index| { + const ir_jump = ir.jumps.get(jump_index); + const new_block = ir_jump.target; + try searcher.visit(allocator, new_block); + }, + .call => |call_index| { + if (traverse_functions) { + const ir_call = ir.calls.get(call_index); + switch (ir_call.callable) { + .function_definition => |definition_index| { + switch (definition_index.invalid) { + false => { + const function = ir.function_definitions.get(definition_index); + try searcher.visit(allocator, function.entry_block); + }, + true => {}, + } }, - true => continue, + // else => unreachable, } - }, - .@"unreachable", .ret, .store => continue, - else => |t| @panic(@tagName(t)), + } + }, + .branch => |branch_index| { + const branch = ir.branches.get(branch_index); + const true_jump = ir.jumps.get(branch.true_jump); + const false_jump = ir.jumps.get(branch.false_jump); + try searcher.visit(allocator, true_jump.target); + try searcher.visit(allocator, false_jump.target); + }, + .@"unreachable", + .ret, + .store, + => {}, + else => |t| @panic(@tagName(t)), + } + } + + var list = try ArrayList(BasicBlock.Index).initCapacity(allocator, searcher.visited.keys().len); + list.appendSliceAssumeCapacity(searcher.visited.keys()); + + return list; +} + +const Callable = struct { + argument_map: AutoArrayHashMap(Compilation.Declaration.Index, Instruction.Index), + calling_convention: Compilation.CallingConvention, + return_type: ?Type, + attributes: Attributes, + + const Attributes = struct { + returns: bool, + }; + + pub const Index = union(enum) { + function_definition: FunctionDefinition.Index, + }; +}; + +pub const Type = union(enum) { + scalar: Scalar, + vector: Vector, + aggregate: Aggregate.Index, + + pub const Vector = struct { + count: u16, + scalar: Scalar, + alignment: u16 = Vector.default_alignment, + + const default_alignment = std.math.log2_int(u16, 16); + }; + + pub const Scalar = enum { + i1, + i8, + i16, + i32, + i64, + + pub const Kind = enum { + integer, + float, + }; + + pub fn getKind(scalar: Type.Scalar) Kind { + return switch (scalar) { + .i1, + .i8, + .i16, + .i32, + .i64, + => .integer, }; - - if (searcher.visited.get(block_to_search) == null) { - try searcher.to_visit.append(builder.allocator, block_to_search); - try searcher.visited.put(builder.allocator, block_to_search, {}); - } } + }; - var list = try ArrayList(BasicBlock.Index).initCapacity(builder.allocator, searcher.visited.keys().len); - list.appendSliceAssumeCapacity(searcher.visited.keys()); - - return list; - } - - fn optimizeFunction(builder: *Builder, function: *Function) !void { - // HACK - logln(.ir, .function, "\n[BEFORE OPTIMIZE]:\n{}", .{function}); - var reachable_blocks = try builder.findReachableBlocks(function.blocks.items[0]); - var did_something = true; - - while (did_something) { - did_something = false; - for (reachable_blocks.items) |basic_block_index| { - const basic_block = builder.ir.blocks.get(basic_block_index); - for (basic_block.instructions.items) |instruction_index| { - did_something = did_something or try builder.removeUnreachablePhis(reachable_blocks.items, instruction_index); - did_something = did_something or try builder.removeTrivialPhis(instruction_index); - const copy = try builder.removeCopyReferences(instruction_index); - did_something = did_something or copy; - } - - if (basic_block.instructions.items.len > 0) { - const instruction = builder.ir.instructions.get(basic_block.instructions.getLast()); - switch (instruction.u) { - .jump => |jump_index| { - const jump_instruction = builder.ir.jumps.get(jump_index); - const source = basic_block_index; - assert(source.eq(jump_instruction.source)); - const destination = jump_instruction.destination; - - const source_index = for (function.blocks.items, 0..) |bi, index| { - if (source.eq(bi)) break index; - } else unreachable; - const destination_index = for (function.blocks.items, 0..) |bi, index| { - if (destination.eq(bi)) break index; - } else unreachable; - - if (destination_index == source_index + 1) { - const destination_block = builder.ir.blocks.get(destination); - _ = basic_block.instructions.pop(); - try basic_block.instructions.appendSlice(builder.allocator, destination_block.instructions.items); - _ = function.blocks.orderedRemove(destination_index); - const reachable_index = for (reachable_blocks.items, 0..) |bi, index| { - if (destination.eq(bi)) break index; - } else unreachable; - _ = reachable_blocks.swapRemove(reachable_index); - did_something = true; - break; - } - }, - .ret, .@"unreachable", .call => {}, - else => |t| @panic(@tagName(t)), - } - } else { - unreachable; - } - } - } - - var instructions_to_delete = ArrayList(u32){}; - for (reachable_blocks.items) |basic_block_index| { - instructions_to_delete.clearRetainingCapacity(); - const basic_block = builder.ir.blocks.get(basic_block_index); - for (basic_block.instructions.items, 0..) |instruction_index, index| { - const instruction = builder.ir.instructions.get(instruction_index); - switch (instruction.u) { - .copy => try instructions_to_delete.append(builder.allocator, @intCast(index)), - else => {}, - } - } - - var deleted_instruction_count: usize = 0; - for (instructions_to_delete.items) |instruction_to_delete| { - _ = basic_block.instructions.orderedRemove(instruction_to_delete - deleted_instruction_count); - } - } - - logln(.ir, .function, "[AFTER OPTIMIZE]:\n{}", .{function}); - } - - fn removeUnreachablePhis(builder: *Builder, reachable_blocks: []const BasicBlock.Index, instruction_index: Instruction.Index) !bool { - const instruction = builder.ir.instructions.get(instruction_index); - return switch (instruction.u) { - .phi => blk: { - var did_something = false; - var head = &instruction.u.phi; - next: while (!head.invalid) { - const phi = builder.ir.phis.get(head.*); - const phi_jump = builder.ir.jumps.get(phi.jump); - assert(!phi_jump.source.invalid); - - for (reachable_blocks) |block_index| { - if (phi_jump.source.eq(block_index)) { - head = &phi.next; - continue :next; - } - } - - head.* = phi.next; - did_something = true; - } - - break :blk did_something; - }, - else => false, + const Aggregate = struct { + kind: Kind, + const Kind = enum { + @"struct", + @"union", }; + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; + }; + + pub const @"i1" = Type{ + .scalar = .i1, + }; + pub const @"i8" = Type{ + .scalar = .i8, + }; + pub const @"i16" = Type{ + .scalar = .i16, + }; + pub const @"i32" = Type{ + .scalar = .i32, + }; + pub const @"i64" = Type{ + .scalar = .i64, + }; + + pub fn getSize(t: Type) usize { + const result: usize = switch (t) { + .scalar => switch (t.scalar) { + .i1 => @sizeOf(i1), + .i8 => @sizeOf(i8), + .i16 => @sizeOf(i16), + .i32 => @sizeOf(i32), + .i64 => @sizeOf(i64), + }, + else => |tg| @panic(@tagName(tg)), + }; + + return result; } - fn removeTrivialPhis(builder: *Builder, instruction_index: Instruction.Index) !bool { - const instruction = builder.ir.instructions.get(instruction_index); - return switch (instruction.u) { - .phi => |phi_index| blk: { - const trivial_phi: ?Instruction.Index = trivial_blk: { - var only_value = Instruction.Index.invalid; - var it = phi_index; - - while (!it.invalid) { - const phi = builder.ir.phis.get(it); - const phi_value = builder.ir.instructions.get(phi.instruction); - if (phi_value.u == .phi) unreachable; - // TODO: undefined - if (!only_value.invalid) { - if (!only_value.eq(phi.instruction)) { - break :trivial_blk null; - } - } else { - only_value = phi.instruction; - } - - it = phi.next; - } - - break :trivial_blk only_value; - }; - - if (trivial_phi) |trivial_value| { - if (!trivial_value.invalid) { - // Option to delete - const delete = false; - if (delete) { - unreachable; - } else { - instruction.* = .{ - .u = .{ - .copy = trivial_value, - }, - }; - } - } else { - logln(.ir, .phi_removal, "TODO: maybe this phi removal is wrong?", .{}); - instruction.* = .{ - .u = .{ - .copy = trivial_value, - }, - }; - } - } - - break :blk instruction.u != .phi; - }, - else => false, - }; - } - - fn removeCopyReferences(builder: *Builder, instruction_index: Instruction.Index) !bool { - const instruction = builder.ir.instructions.get(instruction_index); - return switch (instruction.u) { - .copy => false, - else => { - var did_something = false; - - const operands: []const *Instruction.Index = switch (instruction.u) { - .jump, .@"unreachable", .load_integer, .load_string_literal, .stack, .argument => &.{}, - .ret => &.{&builder.ir.returns.get(instruction.u.ret).instruction}, - // TODO: arguments - .call => blk: { - var list = ArrayList(*Instruction.Index){}; - break :blk list.items; - }, - .store => |store_index| blk: { - const store_instr = builder.ir.stores.get(store_index); - break :blk &.{ &store_instr.source, &store_instr.destination }; - }, - .syscall => |syscall_index| blk: { - const syscall = builder.ir.syscalls.get(syscall_index); - var list = ArrayList(*Instruction.Index){}; - try list.ensureTotalCapacity(builder.allocator, syscall.arguments.items.len); - for (syscall.arguments.items) |*arg| { - list.appendAssumeCapacity(arg); - } - - break :blk list.items; - }, - .sign_extend => |cast_index| blk: { - const cast = builder.ir.casts.get(cast_index); - break :blk &.{&cast.value}; - }, - .load => |load_index| blk: { - const load = builder.ir.loads.get(load_index); - break :blk &.{&load.instruction}; - }, - .binary_operation => |binary_operation_index| blk: { - const binary_operation = builder.ir.binary_operations.get(binary_operation_index); - break :blk &.{ &binary_operation.left, &binary_operation.right }; - }, - else => |t| @panic(@tagName(t)), - }; - - for (operands) |operand_instruction_index_pointer| { - switch (operand_instruction_index_pointer.invalid) { - false => { - const operand_value = builder.ir.instructions.get(operand_instruction_index_pointer.*); - switch (operand_value.u) { - .copy => |copy_value| { - operand_instruction_index_pointer.* = copy_value; - did_something = true; - }, - .load_integer, - .stack, - .call, - .argument, - .syscall, - .sign_extend, - .load, - .binary_operation, - => {}, - else => |t| @panic(@tagName(t)), - } - }, - true => {}, - } - } - - return did_something; + pub fn getAlignment(t: Type) u16 { + const result: u16 = switch (t) { + .scalar => switch (t.scalar) { + .i1 => @alignOf(i1), + .i8 => @alignOf(i8), + .i16 => @alignOf(i16), + .i32 => @alignOf(i32), + .i64 => @alignOf(i64), }, + else => |tag| @panic(@tagName(tag)), }; + return result; } }; + +pub const FunctionDefinition = struct { + callable: Callable, + entry_block: BasicBlock.Index = BasicBlock.Index.invalid, + stack_map: AutoHashMap(Compilation.Declaration.Index, Instruction.Index) = .{}, + + fn formatter(allocator: Allocator, function_definition: FunctionDefinition.Index, ir: *IR) FunctionDefinition.Formatter { + return .{ + .function = function_definition, + .ir = ir, + .allocator = allocator, + }; + } + + pub const Formatter = struct { + function: FunctionDefinition.Index, + ir: *IR, + allocator: Allocator, + + pub fn format(function_formatter: *const Formatter, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + const function_index = function_formatter.function; + const ir = function_formatter.ir; + const function = ir.function_definitions.get(function_index); + const sema_function_index: Compilation.Function.Index = @bitCast(function_index); + const function_name_hash = ir.module.function_name_map.get(sema_function_index).?; + const function_name = ir.module.getName(function_name_hash).?; + try writer.print("Function #{} \"{s}\"\n", .{ function_index.uniqueInteger(), function_name }); + + const reachable_blocks = findReachableBlocks(.{ + .allocator = function_formatter.allocator, + .ir = ir, + .first = function.entry_block, + .traverse_functions = false, + }) catch unreachable; + + for (reachable_blocks.items, 0..) |basic_block_index, function_block_index| { + const basic_block = ir.basic_blocks.get(basic_block_index); + try writer.print("\nBLOCK #{} (${}):\n", .{ function_block_index, basic_block_index.uniqueInteger() }); + + for (basic_block.instructions.items, 0..) |instruction_index, block_instruction_index| { + try writer.print("%{} (${}): ", .{ block_instruction_index, instruction_index.uniqueInteger() }); + + const instruction = ir.instructions.get(instruction_index); + switch (instruction.u) { + // .binary_operation => {}, @tagName(binary_operation.type) + else => try writer.writeAll(@tagName(instruction.u)), + } + + try writer.writeByte(' '); + + switch (instruction.u) { + .syscall => |syscall_index| { + const syscall = ir.syscalls.get(syscall_index); + try writer.writeAll(" ("); + for (syscall.arguments.items, 0..) |arg_index, i| { + const arg_value = ir.instructions.get(arg_index); + + try writer.print("${}: {s}", .{ i, @tagName(arg_value.u) }); + + if (i < syscall.arguments.items.len - 1) { + try writer.writeAll(", "); + } + } + try writer.writeAll(")"); + }, + .jump => |jump_index| { + const jump = ir.jumps.get(jump_index); + try writer.print("${}", .{jump.target.uniqueInteger()}); + }, + .phi => |phi_index| { + const phi = ir.phis.get(phi_index); + for (phi.operands.items, 0..) |phi_operand, i| { + const arg_value = ir.instructions.get(phi_operand.value); + + try writer.print("%{} (#{}): {s}", .{ i, phi_operand.value.uniqueInteger(), @tagName(arg_value.u) }); + + if (i < phi.operands.items.len - 1) { + try writer.writeAll(", "); + } + } + try writer.writeAll(")"); + }, + .ret => |ret_index| { + const ret = ir.returns.get(ret_index); + switch (ret.value.invalid) { + false => { + const ret_value = ir.instructions.get(ret.value); + try writer.print("{s}", .{@tagName(ret_value.u)}); + }, + true => try writer.writeAll("void"), + } + }, + // .load => |load_index| { + // const load = ir.loads.get(load_index); + // try writer.print("{s}", .{@tagName(ir.values.get(load.value).*)}); + // }, + .store => |store_index| { + const store = ir.stores.get(store_index); + const source = ir.instructions.get(store.source); + const destination = ir.instructions.get(store.destination); + try writer.print("{s}, {s}", .{ @tagName(destination.u), @tagName(source.u) }); + }, + .call => |call_index| { + const call = ir.calls.get(call_index); + + switch (call.callable) { + .function_definition => |definition_index| try writer.print("${} {s}(", .{ definition_index.uniqueInteger(), ir.getFunctionName(definition_index) }), + } + + for (call.arguments, 0..) |arg_index, i| { + const arg_value = ir.instructions.get(arg_index); + + try writer.print("${}: {s}", .{ i, @tagName(arg_value.u) }); + + if (i < call.arguments.len - 1) { + try writer.writeAll(", "); + } + } + try writer.writeAll(")"); + }, + .constant_integer => |integer| { + try writer.print("{s} (unsigned: 0x{x}, signed {})", .{ @tagName(integer.type), integer.value.unsigned, integer.value.signed }); + }, + .@"unreachable" => {}, + .constant_string_literal => |string_literal_index| { + const string_literal = ir.string_literals.get(string_literal_index); + try writer.print("at 0x{x}", .{string_literal.offset}); + }, + .stack_slot => |stack_index| { + const stack = ir.stack_slots.get(stack_index); + try writer.print("size: {}. alignment: {}", .{ stack.type.getSize(), stack.type.getAlignment() }); + }, + .argument => |argument_index| { + const argument = ir.arguments.get(argument_index); + try writer.print("${}, size: {}. alignment: {}", .{ argument_index, argument.type.getSize(), argument.type.getAlignment() }); + }, + .sign_extend => |cast_index| { + const cast = ir.casts.get(cast_index); + try writer.print("{s} ${}", .{ @tagName(cast.type), cast.value.uniqueInteger() }); + }, + .load => |load_index| { + const load = ir.loads.get(load_index); + try writer.print("${}", .{load.value.uniqueInteger()}); + }, + .binary_operation => |binary_operation_index| { + const binary_operation = ir.binary_operations.get(binary_operation_index); + try writer.writeAll(@tagName(binary_operation.id)); + try writer.print("${}, ${}", .{ binary_operation.left.uniqueInteger(), binary_operation.right.uniqueInteger() }); + }, + .branch => |branch_index| { + const branch = ir.branches.get(branch_index); + try writer.print("${}, #{}, #{}", .{ branch.condition.uniqueInteger(), branch.true_jump.uniqueInteger(), branch.false_jump.uniqueInteger() }); + }, + // else => |t| @panic(@tagName(t)), + } + + try writer.writeByte('\n'); + } + } + _ = options; + _ = fmt; + } + }; + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +const Phi = struct { + operands: ArrayList(Phi.Operand) = .{}, + + const Operand = struct { + jump: Jump.Index, + value: Instruction.Index, + }; + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const Call = struct { + callable: Callable.Index, + arguments: []const Instruction.Index, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const Instruction = struct { + u: U, + parent: BasicBlock.Index = BasicBlock.Index.invalid, + + pub const U = union(enum) { + argument: Argument.Index, + binary_operation: BinaryOperation.Index, + branch: Branch.Index, + call: Call.Index, + constant_integer: ConstantInteger, + constant_string_literal: StringLiteral.Index, + jump: Jump.Index, + load: Load.Index, + phi: Phi.Index, + ret: Return.Index, + sign_extend: Cast.Index, + stack_slot: StackSlot.Index, + store: Store.Index, + syscall: Syscall.Index, + @"unreachable": void, + }; + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +const ArgumentMap = AutoArrayHashMap(Compilation.Declaration.Index, Instruction.Index); + +pub fn initialize(compilation: *Compilation, module: *Module) !*IR { + const builder = try compilation.base_allocator.create(Builder); + const allocator = compilation.base_allocator; + builder.* = .{ + .allocator = allocator, + .ir = .{ + .module = module, + .section_manager = SectionManager{ + .allocator = allocator, + }, + .entry_point = @bitCast(module.entry_point), + }, + }; + + _ = try builder.ir.section_manager.addSection(.{ + .name = ".text", + .size_guess = 0, + .alignment = 0x1000, + .flags = .{ + .execute = true, + .read = true, + .write = false, + }, + .type = .loadable_program, + }); + + var sema_function_definition_iterator = module.function_definitions.iterator(); + + while (sema_function_definition_iterator.nextIndex()) |sema_function_definition_index| { + builder.return_basic_block_index = BasicBlock.Index.invalid; + builder.current_function_index = FunctionDefinition.Index.invalid; + builder.current_basic_block_index = BasicBlock.Index.invalid; + builder.return_phi_index = Instruction.Index.invalid; + // const function_decl_name = builder.ir.getFunctionName(function_declaration_allocation.index); + + const function_name = module.getName(module.function_name_map.get(sema_function_definition_index).?).?; + const sema_function_definition = module.function_definitions.get(sema_function_definition_index); + const sema_prototype = builder.ir.module.function_prototypes.get(builder.ir.module.types.get(sema_function_definition.prototype).function); + const function_calling_convention = sema_prototype.attributes.calling_convention; + const returns = !sema_prototype.return_type.eq(Compilation.Type.noreturn); + const function_return_type = try builder.translateType(sema_prototype.return_type); + // arguments: + const function_argument_map = if (sema_prototype.arguments) |sema_arguments| blk: { + var arg_map = ArgumentMap{}; + try arg_map.ensureTotalCapacity(builder.allocator, @intCast(sema_arguments.len)); + + for (sema_arguments) |sema_argument_declaration_index| { + const sema_argument_declaration = builder.ir.module.declarations.get(sema_argument_declaration_index); + const argument_allocation = try builder.ir.arguments.append(builder.allocator, .{ + .type = try builder.translateType(sema_argument_declaration.type) orelse unreachable, + }); + const value_allocation = try builder.ir.instructions.append(builder.allocator, .{ + .u = .{ + .argument = argument_allocation.index, + }, + }); + arg_map.putAssumeCapacity(sema_argument_declaration_index, value_allocation.index); + } + + break :blk arg_map; + } else ArgumentMap{}; + + const function_definition_allocation = try builder.ir.function_definitions.addOne(builder.allocator); + function_definition_allocation.ptr.* = .{ + .callable = .{ + .argument_map = function_argument_map, + .calling_convention = function_calling_convention, + .return_type = function_return_type, + .attributes = .{ + .returns = returns, + }, + }, + }; + const function_definition = function_definition_allocation.ptr; + builder.current_function_index = function_definition_allocation.index; + + builder.return_basic_block_index = if (returns) blk: { + const exit_block = try builder.ir.basic_blocks.append(builder.allocator, .{}); + const is_void = false; + builder.return_phi_index = if (is_void) ret_value: { + break :ret_value Instruction.Index.invalid; + } else ret_value: { + const phi = try builder.ir.phis.append(builder.allocator, .{}); + const phi_instruction = try builder.ir.instructions.append(builder.allocator, .{ + .u = .{ + .phi = phi.index, + }, + }); + + try builder.appendToBlock(exit_block.index, phi_instruction.index); + + break :ret_value phi_instruction.index; + }; + const ret = try builder.ir.returns.append(builder.allocator, .{ + .value = builder.return_phi_index, + }); + const ret_instruction = try builder.ir.instructions.append(builder.allocator, .{ + .u = .{ + .ret = ret.index, + }, + }); + + try builder.appendToBlock(exit_block.index, ret_instruction.index); + + break :blk exit_block.index; + } else BasicBlock.Index.invalid; + + const function_body = module.blocks.get(sema_function_definition.body); + + if (function_body.statements.items.len > 0) { + // Create the entry block and assign it to the function + const entry_block = try builder.appendAndSetCurrentBlock(); + function_definition.entry_block = entry_block.index; + + // Process arguments. TODO: Currently we spill them to the stack just like LLVM, but surely there must be a better way + try function_definition.stack_map.ensureUnusedCapacity(builder.allocator, @intCast(function_definition.callable.argument_map.keys().len)); + + for (function_definition.callable.argument_map.keys(), function_definition.callable.argument_map.values()) |sema_argument_index, ir_argument_instruction_index| { + const ir_argument_instruction = builder.ir.instructions.get(ir_argument_instruction_index); + const ir_argument = builder.ir.arguments.get(ir_argument_instruction.u.argument); + + _ = try builder.createStackSlot(.{ + .type = ir_argument.type, + .sema = sema_argument_index, + }); + } + + for (function_definition.callable.argument_map.keys(), function_definition.callable.argument_map.values()) |sema_argument_index, ir_argument_instruction_index| { + const stack_slot = function_definition.stack_map.get(sema_argument_index).?; + + const store = try builder.ir.stores.append(builder.allocator, .{ + .destination = stack_slot, + .source = ir_argument_instruction_index, + }); + + _ = try builder.createInstructionAndAppendToCurrentBlock(.{ + .store = store.index, + }); + } + // End processing arguments + + try builder.emitBlock(sema_function_definition.body); + } + + if (function_body.reaches_end) { + assert(returns); + if (!function_definition.entry_block.invalid) { + const jump_index = try builder.createJump(.{ + .source = builder.current_basic_block_index, + .target = builder.return_basic_block_index, + }); + _ = try builder.createInstructionAndAppendToCurrentBlock(.{ + .jump = jump_index, + }); + } else { + function_definition.entry_block = builder.return_basic_block_index; + } + } + + assert(!function_definition.entry_block.invalid); + + if (std.mem.eql(u8, function_name, "main")) { + logln(.ir, .function, "{}", .{FunctionDefinition.formatter(builder.allocator, function_definition_allocation.index, &builder.ir)}); + } + } + + //unreachable; + return &builder.ir; +} diff --git a/src/backend/x86_64.zig b/src/backend/x86_64.zig index a9eb53e..fee1c5b 100644 --- a/src/backend/x86_64.zig +++ b/src/backend/x86_64.zig @@ -4,6 +4,7 @@ const assert = std.debug.assert; const panic = std.debug.panic; const emit = @import("emit.zig"); const ir = @import("intermediate_representation.zig"); +const IR = ir.IR; const Compilation = @import("../Compilation.zig"); @@ -1243,13 +1244,13 @@ const InstructionSelection = struct { const instruction = mir.ir.instructions.get(ir_instruction_index); const defer_materialization = switch (instruction.u) { - .stack => !instruction_selection.stack_map.contains(ir_instruction_index), - .load_integer => false, + .stack_slot => !instruction_selection.stack_map.contains(ir_instruction_index), + .constant_integer => false, else => true, }; if (defer_materialization) { - const ir_type = getIrType(mir.ir, ir_instruction_index); + const ir_type = getIrType(mir.ir, ir_instruction_index) orelse unreachable; const value_type = resolveType(ir_type); const register_class = register_classes.get(value_type); const new_register = try mir.createVirtualRegister(register_class); @@ -1257,7 +1258,7 @@ const InstructionSelection = struct { return new_register; } else { const new_register = switch (instruction.u) { - .load_integer => try instruction_selection.materializeInteger(mir, ir_instruction_index), + .constant_integer => try instruction_selection.materializeInteger(mir, ir_instruction_index), else => unreachable, }; try instruction_selection.local_value_map.put(mir.allocator, ir_instruction_index, new_register); @@ -1269,8 +1270,8 @@ const InstructionSelection = struct { // Moving an immediate to a register fn materializeInteger(instruction_selection: *InstructionSelection, mir: *MIR, ir_instruction_index: ir.Instruction.Index) !Register { // const destination_register = try instruction_selection.getRegisterForValue(mir, ir_instruction_index); - const integer = mir.ir.instructions.get(ir_instruction_index).u.load_integer; - const value_type = resolveType(integer.type); + const integer = mir.ir.instructions.get(ir_instruction_index).u.constant_integer; + const value_type = resolveScalarType(integer.type); switch (integer.value.unsigned == 0) { true => { const instruction_id: Instruction.Id = switch (value_type) { @@ -1361,7 +1362,7 @@ const InstructionSelection = struct { fn getAddressingModeFromIr(instruction_selection: *InstructionSelection, mir: *MIR, ir_instruction_index: ir.Instruction.Index) AddressingMode { const instruction = mir.ir.instructions.get(ir_instruction_index); switch (instruction.u) { - .stack => { + .stack_slot => { const frame_index: u32 = @intCast(instruction_selection.stack_map.getIndex(ir_instruction_index).?); return AddressingMode{ .base = .{ @@ -1387,10 +1388,9 @@ const InstructionSelection = struct { } } - fn lowerArguments(instruction_selection: *InstructionSelection, mir: *MIR, ir_function: *ir.Function) !void { - const ir_function_declaration = mir.ir.function_declarations.get(ir_function.declaration); - const ir_arguments = ir_function_declaration.arguments.values(); - const calling_convention = calling_conventions.get(ir_function_declaration.calling_convention); + fn lowerArguments(instruction_selection: *InstructionSelection, mir: *MIR, ir_function: *ir.FunctionDefinition) !void { + const ir_arguments = ir_function.callable.argument_map.values(); + const calling_convention = calling_conventions.get(ir_function.callable.calling_convention); try instruction_selection.local_value_map.ensureUnusedCapacity(mir.allocator, ir_arguments.len); @@ -1400,10 +1400,11 @@ const InstructionSelection = struct { const ir_argument_instruction = mir.ir.instructions.get(ir_argument_instruction_index); const ir_argument = mir.ir.arguments.get(ir_argument_instruction.u.argument); switch (ir_argument.type) { - .i8, .i16, .i32, .i64 => gp_count += 1, - .void, - .noreturn, - => unreachable, + .scalar => |scalar_type| switch (scalar_type) { + .i8, .i16, .i32, .i64 => gp_count += 1, + else => unreachable, + }, + else => unreachable, } } @@ -1574,16 +1575,20 @@ const Instruction = struct { add32rr, add32rm, add32mr, + and8ri, and32rm, and32mr, and32rr, call64pcrel32, + cmp32mi, + cmp32mi8, copy, idiv32r, idiv32m, imul32mr, imul32rm, imul32rr, + jump_pcrel32, lea64r, mov32r0, mov32rm, @@ -1600,6 +1605,7 @@ const Instruction = struct { or32mr, or32rr, ret, + seter, shl32mi, shl32ri, shr32mi, @@ -1802,6 +1808,7 @@ pub const Operand = struct { unknown, i32mem, i64mem, + gp8, gp32, gp64, gp64_nosp, @@ -1813,6 +1820,7 @@ pub const Operand = struct { lea64mem, ccr, }; + pub const Type = enum(u1) { use = 0, def = 1, @@ -1879,6 +1887,11 @@ pub const Operand = struct { const PCRelative = struct { index: u32, section: u16, + kind: enum { + function, + block, + rodata, + }, }; const Lea64Mem = struct { @@ -1896,6 +1909,7 @@ const register_class_operand_matcher = std.EnumArray(Operand.Id, Register.Class) .i64i32imm_brtarget = .not_a_register, .i32mem = .not_a_register, .i64mem = .not_a_register, + .gp8 = .gp8, .gp32 = .gp32, .gp64 = .gp64, .gp64_nosp = .gp64_nosp, @@ -1965,6 +1979,25 @@ const instruction_descriptors = std.EnumArray(Instruction.Id, Instruction.Descri }, .implicit_definitions = &.{.eflags}, }, + .and8ri = .{ + // .format = .mrm_dest_reg, // right? + .opcode = 0x21, + .operands = &.{ + .{ + .id = .gp8, + .kind = .dst, + }, + .{ + .id = .gp8, + .kind = .src, + }, + .{ + .id = .imm8, + .kind = .src, + }, + }, + .implicit_definitions = &.{.eflags}, + }, .and32mr = .{ // .format = .mrm_dest_reg, // right? .opcode = 0x21, @@ -2034,6 +2067,34 @@ const instruction_descriptors = std.EnumArray(Instruction.Id, Instruction.Descri .implicit_definitions = &.{}, .implicit_uses = &.{}, }, + .cmp32mi8 = .{ + .opcode = 0x83, + .operands = &.{ + .{ + .id = .gp32, + .kind = .dst, + }, + .{ + .id = .imm8, + .kind = .src, + }, + }, + .implicit_definitions = &.{.eflags}, + }, + .cmp32mi = .{ + .opcode = 0x81, + .operands = &.{ + .{ + .id = .gp32, + .kind = .dst, + }, + .{ + .id = .imm32, + .kind = .src, + }, + }, + .implicit_definitions = &.{.eflags}, + }, .copy = .{ // .format = .pseudo, .opcode = 0, @@ -2129,6 +2190,16 @@ const instruction_descriptors = std.EnumArray(Instruction.Id, Instruction.Descri }, .implicit_definitions = &.{.eflags}, }, + .jump_pcrel32 = .{ + // .format = .mrm_source_mem, + .opcode = 0xe9, + .operands = &.{ + .{ + .id = .i64i32imm_brtarget, + .kind = .src, + }, + }, + }, .lea64r = .{ // .format = .mrm_source_mem, .opcode = 0x8d, @@ -2359,6 +2430,16 @@ const instruction_descriptors = std.EnumArray(Instruction.Id, Instruction.Descri }, }, }, + .seter = .{ + .opcode = 0xc3, + .operands = &.{ + .{ + .id = .gp8, + .kind = .dst, + }, + }, + .implicit_uses = &.{.eflags}, + }, .shl32ri = .{ .opcode = 0xc1, .operands = &.{ @@ -2603,7 +2684,7 @@ const BasicBlock = struct { pub const MIR = struct { allocator: Allocator, - ir: *ir.Result, + ir: *IR, target: std.Target, instructions: BlockList(Instruction) = .{}, functions: BlockList(Function) = .{}, @@ -2611,10 +2692,10 @@ pub const MIR = struct { operands: BlockList(Operand) = .{}, instruction_selections: ArrayList(InstructionSelection) = .{}, virtual_registers: BlockList(Register.Virtual) = .{}, - function_declaration_map: std.AutoHashMapUnmanaged(ir.Function.Declaration.Index, Function.Index) = .{}, + function_definition_map: std.AutoHashMapUnmanaged(ir.FunctionDefinition.Index, Function.Index) = .{}, entry_point: u32 = 0, - pub fn selectInstructions(allocator: Allocator, intermediate: *ir.Result, target: std.Target) !*MIR { + pub fn selectInstructions(allocator: Allocator, intermediate: *IR, target: std.Target) !*MIR { logln(.codegen, .instruction_selection_block, "\n[INSTRUCTION SELECTION]\n", .{}); const mir = try allocator.create(MIR); mir.* = .{ @@ -2623,20 +2704,21 @@ pub const MIR = struct { .target = target, }; - try mir.blocks.ensureCapacity(allocator, intermediate.blocks.len); + try mir.blocks.ensureCapacity(allocator, intermediate.basic_blocks.len); try mir.functions.ensureCapacity(allocator, intermediate.function_definitions.len); try mir.instruction_selections.ensureUnusedCapacity(allocator, intermediate.function_definitions.len); var ir_function_definition_iterator = intermediate.function_definitions.iterator(); - try mir.function_declaration_map.ensureTotalCapacity(mir.allocator, @intCast(intermediate.function_definitions.len)); + try mir.function_definition_map.ensureTotalCapacity(mir.allocator, @intCast(intermediate.function_definitions.len)); - while (ir_function_definition_iterator.nextPointer()) |ir_function| { - const fn_name = mir.ir.getFunctionName(ir_function.declaration); + var entry_point: ?u32 = null; + while (ir_function_definition_iterator.nextIndex()) |ir_function_index| { + const fn_name = mir.ir.getFunctionName(ir_function_index); const instruction_selection = mir.instruction_selections.addOneAssumeCapacity(); const function_allocation = try mir.functions.addOne(mir.allocator); const function = function_allocation.ptr; - mir.function_declaration_map.putAssumeCapacityNoClobber(ir_function.declaration, function_allocation.index); + mir.function_definition_map.putAssumeCapacityNoClobber(ir_function_index, function_allocation.index); function.* = .{ .mir = mir, .instruction_selection = instruction_selection, @@ -2645,51 +2727,56 @@ pub const MIR = struct { instruction_selection.* = .{ .function = function, }; + + if (ir_function_index.eq(intermediate.entry_point)) { + entry_point = function_allocation.index.uniqueInteger(); + } } var function_iterator = mir.functions.iterator(); ir_function_definition_iterator = intermediate.function_definitions.iterator(); - var entry_point: ?u32 = null; - var ir_function_index = ir_function_definition_iterator.getCurrentIndex(); - while (ir_function_definition_iterator.nextPointer()) |ir_function| { - const function_index = function_iterator.getCurrentIndex(); - const function = function_iterator.nextPointer() orelse unreachable; + while (ir_function_definition_iterator.nextIndex()) |ir_function_index| { + const ir_function = intermediate.function_definitions.get(ir_function_index); + const function_index = function_iterator.nextIndex() orelse unreachable; + const function = mir.functions.get(function_index); logln(.codegen, .instruction_selection_ir_function, "Selecting instructions for {}", .{ir_function}); const instruction_selection = function.instruction_selection; - if (ir_function_index.eq(intermediate.entry_point)) { - entry_point = function_index.uniqueInteger(); - } + const calling_convention = calling_conventions.get(ir_function.callable.calling_convention); - const ir_function_declaration = mir.ir.function_declarations.get(ir_function.declaration); - const calling_convention = calling_conventions.get(ir_function_declaration.calling_convention); + const ir_basic_blocks = try ir.findReachableBlocks(.{ + .allocator = allocator, + .ir = mir.ir, + .first = ir_function.entry_block, + .traverse_functions = false, + }); - try instruction_selection.block_map.ensureUnusedCapacity(allocator, @intCast(ir_function.blocks.items.len)); - try function.blocks.ensureTotalCapacity(allocator, ir_function.blocks.items.len); + try instruction_selection.block_map.ensureUnusedCapacity(allocator, @intCast(ir_basic_blocks.items.len)); + try function.blocks.ensureTotalCapacity(allocator, ir_basic_blocks.items.len); - for (ir_function.blocks.items) |block| { + for (ir_basic_blocks.items) |ir_block_index| { const block_allocation = try mir.blocks.append(allocator, .{}); - instruction_selection.block_map.putAssumeCapacity(block, block_allocation.index); + instruction_selection.block_map.putAssumeCapacity(ir_block_index, block_allocation.index); function.blocks.appendAssumeCapacity(block_allocation.index); - } - for (mir.ir.blocks.get(ir_function.blocks.items[0]).instructions.items) |ir_instruction_index| { - const ir_instruction = mir.ir.instructions.get(ir_instruction_index); + const ir_block = mir.ir.basic_blocks.get(ir_block_index); + for (ir_block.instructions.items) |ir_instruction_index| { + const ir_instruction = mir.ir.instructions.get(ir_instruction_index); - // TODO: take into account exceptions, dynamic allocas? - if (ir_instruction.u == .stack) { - const stack = mir.ir.stack_references.get(ir_instruction.u.stack); - const ir_type = getIrType(mir.ir, ir_instruction_index); - const value_type = resolveType(ir_type); - const type_info = value_types.get(value_type); - const total_size = type_info.size * stack.count; - assert(total_size <= 8); - const frame_index = try mir.createStackObject(instruction_selection, total_size, @intCast(stack.alignment), ir_instruction_index, false); - try instruction_selection.stack_map.putNoClobber(allocator, ir_instruction_index, frame_index); + // TODO: take into account exceptions, dynamic allocas? + if (ir_instruction.u == .stack_slot) { + const ir_type = getIrType(mir.ir, ir_instruction_index) orelse unreachable; + const size = ir_type.getSize(); + const alignment = ir_type.getAlignment(); + + assert(size <= 8 and alignment <= 8); + const frame_index = try mir.createStackObject(instruction_selection, size, alignment, ir_instruction_index, false); + try instruction_selection.stack_map.putNoClobber(allocator, ir_instruction_index, frame_index); + } + + // TODO: handle stack references outside blocks } - - // TODO: handle stack references outside blocks } instruction_selection.current_block = function.blocks.items[0]; @@ -2701,10 +2788,9 @@ pub const MIR = struct { while (block_i > 0) { block_i -= 1; - const block_index = function.blocks.items[block_i]; - _ = block_index; - const ir_block_index = ir_function.blocks.items[block_i]; - const ir_block = mir.ir.blocks.get(ir_block_index); + const ir_block_index = ir_basic_blocks.items[block_i]; + const ir_block = mir.ir.basic_blocks.get(ir_block_index); + instruction_selection.current_block = instruction_selection.block_map.get(ir_block_index).?; var instruction_i: usize = ir_block.instructions.items.len; @@ -2721,14 +2807,14 @@ pub const MIR = struct { switch (ir_instruction.u) { .ret => |ir_ret_index| { const ir_ret = mir.ir.returns.get(ir_ret_index); - switch (ir_ret.instruction.invalid) { + switch (ir_ret.value.invalid) { true => { const ret = try mir.buildInstruction(instruction_selection, .ret, &.{}); try instruction_selection.instruction_cache.append(mir.allocator, ret); }, false => { - const value_type = resolveType(getIrType(mir.ir, ir_ret.instruction)); - const source_register = try instruction_selection.getRegisterForValue(mir, ir_ret.instruction); + const value_type = resolveType(getIrType(mir.ir, ir_ret.value) orelse unreachable); + const source_register = try instruction_selection.getRegisterForValue(mir, ir_ret.value); const register_class = register_classes.get(value_type); @@ -2783,7 +2869,7 @@ pub const MIR = struct { }, } }, - .load_integer => unreachable, + .constant_integer => {}, .@"unreachable" => try instruction_selection.instruction_cache.append(mir.allocator, try mir.buildInstruction(instruction_selection, .ud2, &.{})), .syscall => |ir_syscall_index| { const ir_syscall = mir.ir.syscalls.get(ir_syscall_index); @@ -2894,8 +2980,8 @@ pub const MIR = struct { const ir_load_instruction_index = ir_sign_extend.value; const ir_load_instruction = mir.ir.instructions.get(ir_sign_extend.value); const ir_load = mir.ir.loads.get(ir_load_instruction.u.load); - const ir_source = ir_load.instruction; - const source_type = resolveType(getIrType(mir.ir, ir_source)); + const ir_source = ir_load.value; + const source_type = resolveType(getIrType(mir.ir, ir_source) orelse unreachable); if (destination_type != source_type) { try instruction_selection.folded_loads.putNoClobber(mir.allocator, ir_load_instruction_index, {}); @@ -2953,9 +3039,9 @@ pub const MIR = struct { .load => |ir_load_index| { if (!instruction_selection.folded_loads.swapRemove(ir_instruction_index)) { const ir_load = mir.ir.loads.get(ir_load_index); - const ir_source = ir_load.instruction; + const ir_source = ir_load.value; const addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_source); - const value_type = resolveType(getIrType(mir.ir, ir_source)); + const value_type = resolveType(getIrType(mir.ir, ir_source) orelse unreachable); switch (value_type) { inline .i32, @@ -3015,15 +3101,15 @@ pub const MIR = struct { const addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_destination); const ir_source = mir.ir.instructions.get(ir_source_index); - const value_type = resolveType(getIrType(mir.ir, ir_source_index)); + const value_type = resolveType(getIrType(mir.ir, ir_source_index) orelse unreachable); - if (ir_source.u == .load_integer and value_types.get(value_type).getSize() <= @sizeOf(u32)) { + if (ir_source.u == .constant_integer and value_types.get(value_type).getSize() <= @sizeOf(u32)) { const instruction_id: Instruction.Id = switch (value_type) { .i32 => .mov32mi, else => unreachable, }; - const source_immediate = ir_source.u.load_integer; + const source_immediate = ir_source.u.constant_integer; const instruction_descriptor = instruction_descriptors.getPtrConst(instruction_id); const source_operand_index = 1; @@ -3096,7 +3182,7 @@ pub const MIR = struct { } } }, - .stack => { + .stack_slot => { assert(instruction_selection.stack_map.get(ir_instruction_index) != null); }, .call => |ir_call_index| { @@ -3153,8 +3239,9 @@ pub const MIR = struct { .id = .i64i32imm_brtarget, .u = .{ .pc_relative = .{ - .index = @bitCast(mir.function_declaration_map.get(ir_call.function).?), + .index = @bitCast(mir.function_definition_map.get(ir_call.callable.function_definition).?), .section = @intCast(mir.ir.section_manager.getTextSectionIndex()), + .kind = .function, }, }, .flags = .{}, @@ -3163,60 +3250,54 @@ pub const MIR = struct { try instruction_selection.instruction_cache.append(mir.allocator, call); - const ir_return_type = mir.ir.function_declarations.get(ir_call.function).return_type; - switch (ir_return_type) { - .void, - .noreturn, - => {}, - else => { - const return_type = resolveType(ir_return_type); - switch (return_type) { - inline .i64, .i32 => |rt| { - const register_operand_id = switch (rt) { - .i32 => .gp32, - .i64 => .gp64, - else => unreachable, - }; - const physical_return_register = Register{ - .index = .{ - .physical = switch (rt) { - .i32 => .eax, - .i64 => .rax, - else => unreachable, - }, + if (mir.ir.function_definitions.get(ir_call.callable.function_definition).callable.return_type) |ir_return_type| { + const return_type = resolveType(ir_return_type); + switch (return_type) { + inline .i64, .i32 => |rt| { + const register_operand_id = switch (rt) { + .i32 => .gp32, + .i64 => .gp64, + else => unreachable, + }; + const physical_return_register = Register{ + .index = .{ + .physical = switch (rt) { + .i32 => .eax, + .i64 => .rax, + else => unreachable, }, - }; + }, + }; - const physical_return_operand = Operand{ - .id = register_operand_id, - .u = .{ - .register = physical_return_register, - }, - .flags = .{}, - }; + const physical_return_operand = Operand{ + .id = register_operand_id, + .u = .{ + .register = physical_return_register, + }, + .flags = .{}, + }; - const virtual_return_register = try instruction_selection.getRegisterForValue(mir, ir_instruction_index); - const virtual_return_operand = Operand{ - .id = register_operand_id, - .u = .{ - .register = virtual_return_register, - }, - .flags = .{ .type = .def }, - }; + const virtual_return_register = try instruction_selection.getRegisterForValue(mir, ir_instruction_index); + const virtual_return_operand = Operand{ + .id = register_operand_id, + .u = .{ + .register = virtual_return_register, + }, + .flags = .{ .type = .def }, + }; - const call_result_copy = try mir.buildInstruction(instruction_selection, .copy, &.{ - virtual_return_operand, - physical_return_operand, - }); + const call_result_copy = try mir.buildInstruction(instruction_selection, .copy, &.{ + virtual_return_operand, + physical_return_operand, + }); - try instruction_selection.instruction_cache.append(mir.allocator, call_result_copy); - }, - else => |t| @panic(@tagName(t)), - } - }, + try instruction_selection.instruction_cache.append(mir.allocator, call_result_copy); + }, + else => |t| @panic(@tagName(t)), + } } }, - .load_string_literal => |ir_load_string_literal_index| { + .constant_string_literal => |ir_load_string_literal_index| { const ir_load_string_literal = mir.ir.string_literals.get(ir_load_string_literal_index); const virtual_register = try instruction_selection.getRegisterForValue(mir, ir_instruction_index); const virtual_operand = Operand{ @@ -3236,6 +3317,7 @@ pub const MIR = struct { .displacement = Operand.PCRelative{ .index = ir_load_string_literal.offset, .section = mir.ir.section_manager.rodata orelse unreachable, + .kind = .rodata, }, }, }, @@ -3263,12 +3345,12 @@ pub const MIR = struct { const left_kind: BinaryOperandKind = switch (mir.ir.instructions.get(ir_binary_operation.left).u) { .load => .load, - .load_integer => .immediate, + .constant_integer => .immediate, else => .rest, }; const right_kind: BinaryOperandKind = switch (mir.ir.instructions.get(ir_binary_operation.right).u) { .load => .load, - .load_integer => .immediate, + .constant_integer => .immediate, else => .rest, }; @@ -3324,7 +3406,7 @@ pub const MIR = struct { const instruction_descriptor = instruction_descriptors.get(instruction_id); const right_operand_id = instruction_descriptor.operands[0].id; const ir_load = mir.ir.loads.get(mir.ir.instructions.get(ir_binary_operation.right).u.load); - const right_operand_addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_load.instruction); + const right_operand_addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_load.value); const right_operand = Operand{ .id = right_operand_id, .u = .{ @@ -3438,6 +3520,7 @@ pub const MIR = struct { .signed_divide => unreachable, .shift_left => unreachable, .shift_right => unreachable, + .integer_compare_equal => unreachable, }; try instruction_selection.folded_loads.putNoClobber(mir.allocator, ir_binary_operation.right, {}); @@ -3448,7 +3531,7 @@ pub const MIR = struct { const left_operand_id = instruction_descriptor.operands[1].id; const right_operand_id = instruction_descriptor.operands[2].id; const ir_load = mir.ir.loads.get(mir.ir.instructions.get(ir_binary_operation.right).u.load); - const right_operand_addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_load.instruction); + const right_operand_addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_load.value); const destination_operand = Operand{ .id = destination_operand_id, @@ -3487,59 +3570,160 @@ pub const MIR = struct { try instruction_selection.updateValueMap(mir.allocator, ir_instruction_index, destination_register, false); }, .immediate => { - const destination_register = try instruction_selection.getRegisterForValue(mir, ir_instruction_index); - const instruction_id: Instruction.Id = switch (ir_binary_operation.id) { - .shift_left => .shl32ri, - .shift_right => .shr32ri, - else => |t| @panic(@tagName(t)), - }; - const instruction_descriptor = instruction_descriptors.get(instruction_id); - // const left_register = try instruction_selection.getRegisterForValue(mir, ir_binary_operation.left); - const left_register = try instruction_selection.getRegisterForValue(mir, ir_binary_operation.left); - const destination_operand_id = instruction_descriptor.operands[0].id; - const left_operand_id = instruction_descriptor.operands[1].id; + switch (ir_binary_operation.id) { + .shift_left, .shift_right => { + const destination_register = try instruction_selection.getRegisterForValue(mir, ir_instruction_index); + const instruction_id: Instruction.Id = switch (ir_binary_operation.id) { + .shift_left => .shl32ri, + .shift_right => .shr32ri, + else => |t| @panic(@tagName(t)), + }; + const instruction_descriptor = instruction_descriptors.get(instruction_id); + // const left_register = try instruction_selection.getRegisterForValue(mir, ir_binary_operation.left); + const left_register = try instruction_selection.getRegisterForValue(mir, ir_binary_operation.left); + const destination_operand_id = instruction_descriptor.operands[0].id; + const left_operand_id = instruction_descriptor.operands[1].id; - const destination_operand = Operand{ - .id = destination_operand_id, - .u = .{ - .register = destination_register, - }, - .flags = .{ - .type = .def, - }, - }; + const destination_operand = Operand{ + .id = destination_operand_id, + .u = .{ + .register = destination_register, + }, + .flags = .{ + .type = .def, + }, + }; - const left_operand = Operand{ - .id = left_operand_id, - .u = .{ - .register = left_register, - }, - .flags = .{}, - }; + const left_operand = Operand{ + .id = left_operand_id, + .u = .{ + .register = left_register, + }, + .flags = .{}, + }; - const right_immediate = mir.ir.instructions.get(ir_binary_operation.right).u.load_integer; - const right_value_type: ValueType.Id = switch (right_immediate.type) { - .i8 => .i8, + const right_immediate = mir.ir.instructions.get(ir_binary_operation.right).u.constant_integer; + const right_value_type: ValueType.Id = switch (right_immediate.type) { + .i8 => .i8, + else => unreachable, + }; + _ = right_value_type; + const right_operand = Operand{ + .id = .imm8, + .u = .{ + .immediate = right_immediate.value.unsigned, + }, + .flags = .{}, + }; + + const binary_op_instruction = try mir.buildInstruction(instruction_selection, instruction_id, &.{ + destination_operand, + left_operand, + right_operand, + }); + + try instruction_selection.instruction_cache.append(mir.allocator, binary_op_instruction); + + try instruction_selection.updateValueMap(mir.allocator, ir_instruction_index, destination_register, false); + }, + .integer_compare_equal => { + const result = try instruction_selection.getRegisterForValue(mir, ir_instruction_index); + const right_immediate = mir.ir.instructions.get(ir_binary_operation.right).u.constant_integer; + const instruction_id: Instruction.Id = switch (ir_binary_operation.id) { + .integer_compare_equal => blk: { + const instruction_id: Instruction.Id = switch (right_immediate.type) { + .i32 => if (std.math.cast(i8, right_immediate.value.signed)) |_| .cmp32mi8 else .cmp32mi, + else => |t| @panic(@tagName(t)), + }; + break :blk instruction_id; + }, + else => unreachable, + }; + try instruction_selection.folded_loads.putNoClobber(mir.allocator, ir_binary_operation.left, {}); + // + const instruction_descriptor = instruction_descriptors.get(instruction_id); + // const left_register = try instruction_selection.getRegisterForValue(mir, ir_binary_operation.left); + // const destination_operand_id = instruction_descriptor.operands[0].id; + const right_operand_id = instruction_descriptor.operands[1].id; + const left_operand_id = instruction_descriptor.operands[0].id; + const ir_load = mir.ir.loads.get(mir.ir.instructions.get(ir_binary_operation.left).u.load); + const left_operand_addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_load.value); + const left_operand = Operand{ + .id = left_operand_id, + .u = .{ + .memory = .{ .addressing_mode = left_operand_addressing_mode }, + }, + .flags = .{}, + }; + const right_operand = Operand{ + .id = right_operand_id, + .u = .{ + .immediate = right_immediate.value.unsigned, + }, + .flags = .{}, + }; + const binary_op_instruction = try mir.buildInstruction(instruction_selection, instruction_id, &.{ + left_operand, + right_operand, + }); + try instruction_selection.instruction_cache.append(mir.allocator, binary_op_instruction); + + const set_instruction: Instruction.Id = switch (ir_binary_operation.id) { + .integer_compare_equal => .seter, + else => unreachable, + }; + const flag1_register = try mir.createVirtualRegister(.gp8); + const flag1_operand = Operand{ + .id = .gp8, + .u = .{ + .register = flag1_register, + }, + .flags = .{ + .type = .def, + }, + }; + const setcc = try mir.buildInstruction(instruction_selection, set_instruction, &.{ + flag1_operand, + }); + try instruction_selection.instruction_cache.append(mir.allocator, setcc); + // TODO: parity? + + const select_instruction: Instruction.Id = switch (ir_binary_operation.id) { + .integer_compare_equal => .and8ri, + else => unreachable, + }; + + const result_operand = Operand{ + .id = .gp8, + .u = .{ + .register = result, + }, + .flags = .{ + .type = .def, + }, + }; + const flag1_src_operand = Operand{ + .id = flag1_operand.id, + .u = flag1_operand.u, + .flags = .{}, + }; + const select = try mir.buildInstruction(instruction_selection, select_instruction, &.{ + result_operand, + flag1_src_operand, + Operand{ + .id = .imm8, + .u = .{ + .immediate = 0x01, + }, + .flags = .{}, + }, + }); + + try instruction_selection.instruction_cache.append(mir.allocator, select); + try instruction_selection.updateValueMap(mir.allocator, ir_instruction_index, result, false); + }, else => unreachable, - }; - _ = right_value_type; - const right_operand = Operand{ - .id = .imm8, - .u = .{ - .immediate = right_immediate.value.unsigned, - }, - .flags = .{}, - }; - - const binary_op_instruction = try mir.buildInstruction(instruction_selection, instruction_id, &.{ - destination_operand, - left_operand, - right_operand, - }); - - try instruction_selection.instruction_cache.append(mir.allocator, binary_op_instruction); - - try instruction_selection.updateValueMap(mir.allocator, ir_instruction_index, destination_register, false); + } }, .rest => { const destination_register = try instruction_selection.getRegisterForValue(mir, ir_instruction_index); @@ -3571,6 +3755,7 @@ pub const MIR = struct { .signed_divide => unreachable, .shift_left => unreachable, .shift_right => unreachable, + .integer_compare_equal => unreachable, }; const instruction_descriptor = instruction_descriptors.get(instruction_id); @@ -3651,6 +3836,7 @@ pub const MIR = struct { .signed_divide => unreachable, .shift_left => unreachable, .shift_right => unreachable, + .integer_compare_equal => unreachable, }; const instruction_descriptor = instruction_descriptors.get(instruction_id); @@ -3706,88 +3892,38 @@ pub const MIR = struct { .rest => unreachable, }, } - // if (!is_left_load and is_right_load) { - // unreachable; - // } else if (is_left_load and !is_right_load) { - // try instruction_selection.folded_loads.putNoClobber(mir.allocator, ir_binary_operation.left, {}); - // const instruction_id: Instruction.Id = switch (ir_binary_operation.id) { - // .add => switch (value_type) { - // .i32 => .add32mr, - // else => unreachable, - // }, - // .sub => switch (value_type) { - // .i32 => .sub32mr, - // else => unreachable, - // }, - // .logical_and => switch (value_type) { - // .i32 => .and32mr, - // else => unreachable, - // }, - // .logical_xor => switch (value_type) { - // .i32 => .xor32mr, - // else => unreachable, - // }, - // .logical_or => switch (value_type) { - // .i32 => .or32mr, - // else => unreachable, - // }, - // .signed_multiply => switch (value_type) { - // .i32 => .imul32mr, - // else => unreachable, - // }, - // .signed_divide => unreachable, - // .shift_left => unreachable, - // .shift_right => unreachable, - // }; - // - // const instruction_descriptor = instruction_descriptors.get(instruction_id); - // // const left_register = try instruction_selection.getRegisterForValue(mir, ir_binary_operation.left); - // const destination_operand_id = instruction_descriptor.operands[0].id; - // const left_operand_id = instruction_descriptor.operands[1].id; - // const right_operand_id = instruction_descriptor.operands[2].id; - // // const ir_load = mir.ir.loads.get(mir.ir.instructions.get(ir_binary_operation.right).u.load); - // // const right_operand_addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_load.instruction); - // const ir_load = mir.ir.loads.get(mir.ir.instructions.get(ir_binary_operation.left).u.load); - // - // const right_register = try instruction_selection.getRegisterForValue(mir, ir_binary_operation.right); - // const right_operand = Operand{ - // .id = right_operand_id, - // .u = .{ - // .register = right_register, - // }, - // .flags = .{}, - // }; - // - // const left_operand_addressing_mode = instruction_selection.getAddressingModeFromIr(mir, ir_load.instruction); - // const destination_operand = Operand{ - // .id = destination_operand_id, - // .u = .{ - // .memory = .{ .addressing_mode = left_operand_addressing_mode }, - // }, - // .flags = .{}, - // }; - // - // const left_operand = Operand{ - // .id = left_operand_id, - // .u = .{ - // .memory = .{ .addressing_mode = left_operand_addressing_mode }, - // }, - // .flags = .{}, - // }; - // - // const binary_op_instruction = try mir.buildInstruction(instruction_selection, instruction_id, &.{ - // destination_operand, - // left_operand, - // right_operand, - // }); - // - // try instruction_selection.instruction_cache.append(mir.allocator, binary_op_instruction); - // } else if (!is_left_load and !is_right_load) { - // } else { - // } }, } }, + .phi => |ir_phi_index| { + const ir_phi = mir.ir.phis.get(ir_phi_index); + + // TODO: check if we should something else here + const virtual_register = instruction_selection.value_map.get(ir_instruction_index).?; + for (ir_phi.operands.items) |phi_operand| { + try instruction_selection.updateValueMap(mir.allocator, phi_operand.value, virtual_register, false); + } + }, + .jump => |ir_jump_index| { + const ir_jump = mir.ir.jumps.get(ir_jump_index); + const ir_target_block = ir_jump.target; + const target_block = instruction_selection.block_map.get(ir_target_block).?; + + const jump = try mir.buildInstruction(instruction_selection, .jump_pcrel32, &.{ + Operand{ + .id = .i64i32imm_brtarget, + .u = .{ + .pc_relative = .{ + .index = @bitCast(target_block), + .section = @intCast(mir.ir.section_manager.getTextSectionIndex()), + .kind = .block, + }, + }, + .flags = .{}, + }, + }); + try instruction_selection.instruction_cache.append(mir.allocator, jump); + }, else => |t| @panic(@tagName(t)), } @@ -3810,8 +3946,6 @@ pub const MIR = struct { try instruction_selection.emitLiveInCopies(mir, function.blocks.items[0]); logln(.codegen, .instruction_selection_ir_function, "Selected instructions for {}", .{function}); - - ir_function_index = ir_function_definition_iterator.getCurrentIndex(); } mir.entry_point = entry_point orelse unreachable; @@ -4202,7 +4336,7 @@ pub const MIR = struct { .virtual => |virtual_register_index| blk: { const stack_slot = register_allocator.stack_slots.get(virtual_register_index) != null; const live_out = register_allocator.live_virtual_registers.get(virtual_register_index).?.live_out; - log(.codegen, .register_allocation_problematic_hint, "Register {s} has stack slot: {}. Live out: {}", .{ @tagName(physical_register), stack_slot, live_out }); + logln(.codegen, .register_allocation_problematic_hint, "Register {s} has stack slot: {}. Live out: {}", .{ @tagName(physical_register), stack_slot, live_out }); const sure_spill = stack_slot or live_out; break :blk if (sure_spill) SpillCost.clean else SpillCost.dirty; }, @@ -4862,18 +4996,17 @@ pub const MIR = struct { fn clearVirtualRegisters(mir: *MIR) void { var vr_it = mir.virtual_registers.iterator(); - var vr_index = vr_it.getCurrentIndex(); var verified_virtual_register_count: usize = 0; var skipped: usize = 0; - while (vr_it.nextPointer()) |vr| { + while (vr_it.nextIndex()) |virtual_register_index| { + const virtual_register = mir.virtual_registers.get(virtual_register_index); verified_virtual_register_count += 1; - if (vr.use_def_list_head.invalid) { + if (virtual_register.use_def_list_head.invalid) { skipped += 1; continue; } - // mir.verifyUseList(vr.use_def_list_head, vr_index); - vr_index = vr_it.getCurrentIndex(); + mir.verifyUseList(virtual_register.use_def_list_head, virtual_register_index); } logln(.codegen, .register_allocation_operand_list_verification, "Verified {} virtual registers ({} skipped)", .{ verified_virtual_register_count, skipped }); @@ -4954,8 +5087,8 @@ pub const MIR = struct { try function_offsets.ensureTotalCapacity(mir.allocator, mir.functions.len); try image.section_manager.getTextSection().symbol_table.ensureTotalCapacity(mir.allocator, mir.functions.len); - while (function_iterator.nextPointer()) |function| { - const function_index = mir.functions.indexOf(function); + while (function_iterator.nextIndex()) |function_index| { + const function = mir.functions.get(function_index); logln(.codegen, .encoding, "\n{s}:", .{function.name}); const function_offset: u32 = @intCast(image.section_manager.getCodeOffset()); @@ -5550,6 +5683,65 @@ pub const MIR = struct { try image.section_manager.appendCodeByte(source_immediate); }, + .cmp32mi8 => { + const left_operand_index = instruction.operands.items[0]; + const left_operand = mir.operands.get(left_operand_index); + + switch (left_operand.u) { + .memory => |memory| switch (memory.addressing_mode.base) { + .register_base => unreachable, + .frame_index => |frame_index| { + const modrm = ModRm{ + .rm = @intFromEnum(Encoding.GP64.bp), + .reg = switch (instruction.id) { + .cmp32mi8 => 7, + else => unreachable, + }, + .mod = @as(u2, @intFromBool(false)) << 1 | @intFromBool(true), + }; + try image.section_manager.appendCodeByte(@bitCast(modrm)); + + const stack_offset = computeStackOffset(function.instruction_selection.stack_objects.items[0 .. frame_index + 1]); + const displacement_bytes: u3 = if (std.math.cast(i8, stack_offset)) |_| @sizeOf(i8) else if (std.math.cast(i32, stack_offset)) |_| @sizeOf(i32) else unreachable; + + const stack_bytes = std.mem.asBytes(&stack_offset)[0..displacement_bytes]; + try image.section_manager.appendCode(stack_bytes); + }, + }, + else => unreachable, + } + + const source_operand_index = instruction.operands.items[1]; + const source_operand = mir.operands.get(source_operand_index); + assert(source_operand.id == .imm8); + const source_immediate: u8 = @intCast(source_operand.u.immediate); + + try image.section_manager.appendCodeByte(source_immediate); + }, + .seter => { + const operand_index = instruction.operands.items[0]; + const operand = mir.operands.get(operand_index); + assert(operand.u == .register); + const instruction_descriptor = instruction_descriptors.get(instruction.id); + const opcode = &[_]u8{ 0x0f, @intCast(instruction_descriptor.opcode) }; + try image.section_manager.appendCode(opcode); + const modrm = ModRm{ + .rm = @intFromEnum(Encoding.GP64.bp), + .reg = 0, + .mod = @as(u2, @intFromBool(false)) << 1 | @intFromBool(true), + }; + try image.section_manager.appendCodeByte(@bitCast(modrm)); + }, + .and8ri => { + const destination_register_index = instruction.operands.items[0]; + const destination_register_operand = mir.operands.get(destination_register_index); + switch (destination_register_operand.u.register.index.physical) { + .al => { + unreachable; + }, + else => |t| @panic(@tagName(t)), + } + }, else => |t| @panic(@tagName(t)), } @@ -5741,6 +5933,7 @@ pub const MIR = struct { .id = switch (implicitly_used_register) { .eax => .gp32, .edx => .gp32, + .eflags => .ccr, else => |t| @panic(@tagName(t)), }, .u = .{ @@ -5877,33 +6070,44 @@ const ModRm = packed struct(u8) { mod: u2, }; -fn getIrType(intermediate: *ir.Result, ir_instruction_index: ir.Instruction.Index) ir.Type { +fn getIrType(intermediate: *IR, ir_instruction_index: ir.Instruction.Index) ?ir.Type { const ir_instruction = intermediate.instructions.get(ir_instruction_index); return switch (ir_instruction.u) { .argument => |argument_index| intermediate.arguments.get(argument_index).type, - .stack => |stack_index| intermediate.stack_references.get(stack_index).type, - .load => |load_index| getIrType(intermediate, intermediate.loads.get(load_index).instruction), - .syscall => |_| .i64, - .load_integer => |integer| integer.type, - .load_string_literal => .i64, - .call => |call_index| intermediate.function_declarations.get(intermediate.calls.get(call_index).function).return_type, + .stack_slot => |stack_index| intermediate.stack_slots.get(stack_index).type, + .load => |load_index| getIrType(intermediate, intermediate.loads.get(load_index).value), + .syscall => |_| .{ + .scalar = .i64, + }, + .constant_integer => |integer| .{ + .scalar = integer.type, + }, + .constant_string_literal => .{ + .scalar = .i64, + }, + .call => |call_index| intermediate.function_definitions.get(intermediate.calls.get(call_index).callable.function_definition).callable.return_type, .sign_extend => |cast_index| intermediate.casts.get(cast_index).type, .binary_operation => |binary_operation_index| intermediate.binary_operations.get(binary_operation_index).type, + .phi => |phi_index| getIrType(intermediate, intermediate.phis.get(phi_index).operands.items[0].value), else => |t| @panic(@tagName(t)), }; } +fn resolveScalarType(ir_scalar_type: ir.Type.Scalar) ValueType.Id { + return switch (ir_scalar_type) { + .i1 => unreachable, + inline .i8, + .i16, + .i32, + .i64, + => |ir_type_ct| @field(ValueType.Id, @typeInfo(ir.Type.Scalar).Enum.fields[@intFromEnum(ir_type_ct)].name), + }; +} + fn resolveType(ir_type: ir.Type) ValueType.Id { return switch (ir_type) { - inline //.i8, - //.i16, - .i32, - .i64, - => |ir_type_ct| @field(ValueType.Id, @typeInfo(ir.Type).Enum.fields[@intFromEnum(ir_type_ct)].name), - .i8, .i16 => unreachable, - .void, - .noreturn, - => unreachable, + .scalar => |ir_scalar_type| resolveScalarType(ir_scalar_type), + else => unreachable, }; } diff --git a/src/data_structures.zig b/src/data_structures.zig index df8043d..131f1a8 100644 --- a/src/data_structures.zig +++ b/src/data_structures.zig @@ -31,6 +31,7 @@ pub fn BlockList(comptime T: type) type { }; 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, @@ -38,14 +39,14 @@ pub fn BlockList(comptime T: type) type { const List = @This(); pub const Index = packed struct(u32) { - index: u6, + element: u6, block: u24, _reserved: bool = false, invalid: bool = false, pub const invalid = Index{ .invalid = true, - .index = 0, + .element = 0, .block = 0, }; @@ -63,42 +64,50 @@ pub fn BlockList(comptime T: type) type { const block: u24 = @intCast(index / item_count); const i: u6 = @intCast(index % item_count); return .{ - .index = i, + .element = i, .block = block, }; } }; pub const Iterator = struct { - block_index: u24, - element_index: u6, - list: *const List, + index: Index, + list: *List, - pub fn getCurrentIndex(i: *const Iterator) Index { - return .{ - .block = i.block_index, - .index = @intCast(i.element_index), - }; - } + pub const Pair = struct { + index: Index, + }; - pub fn next(i: *Iterator) ?T { - return if (i.nextPointer()) |ptr| ptr.* else null; - } + 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), + }; - pub fn nextPointer(i: *Iterator) ?*T { - for (i.block_index..i.list.blocks.items.len) |block_index| { - for (@as(u8, i.element_index)..item_count) |element_index| { - if (i.list.blocks.items[i.block_index].bitset.isSet(element_index)) { - i.element_index = @intCast(element_index); - i.element_index +%= 1; - i.block_index = @as(u24, @intCast(block_index)) + @intFromBool(i.element_index < element_index); - return &i.list.blocks.items[block_index].items[element_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 const Allocation = struct { @@ -106,17 +115,19 @@ pub fn BlockList(comptime T: type) type { index: Index, }; - pub fn iterator(list: *const List) Iterator { + pub fn iterator(list: *List) Iterator { return .{ - .block_index = 0, - .element_index = 0, + .index = Index{ + .element = 0, + .block = 0, + }, .list = list, }; } pub fn get(list: *List, index: Index) *T { assert(!index.invalid); - return &list.blocks.items[index.block].items[index.index]; + return &list.blocks.items[index.block].items[index.element]; } pub fn append(list: *List, allocator: Allocator, element: T) !Allocation { @@ -131,12 +142,12 @@ pub fn BlockList(comptime T: type) type { const result = switch (list.len < max_allocation) { true => blk: { const block = &list.blocks.items[list.first_block]; - if (block.allocateIndex()) |index| { - const ptr = &block.items[index]; + if (block.allocateIndex()) |element_index| { + const ptr = &block.items[element_index]; break :blk Allocation{ .ptr = ptr, .index = .{ - .index = index, + .element = element_index, .block = @intCast(list.first_block), }, }; @@ -148,13 +159,13 @@ pub fn BlockList(comptime T: type) type { const block_index = list.blocks.items.len; const new_block = list.blocks.addOneAssumeCapacity(); new_block.* = .{}; - const index = new_block.allocateIndex() catch unreachable; - const ptr = &new_block.items[index]; + const element_index = new_block.allocateIndex() catch unreachable; + const ptr = &new_block.items[element_index]; list.first_block += @intFromBool(block_index != 0); break :blk Allocation{ .ptr = ptr, .index = .{ - .index = index, + .element = element_index, .block = @intCast(block_index), }, }; @@ -174,7 +185,7 @@ pub fn BlockList(comptime T: type) type { } } - pub fn indexOf(list: *const List, elem: *const T) Index { + pub fn indexOf(list: *List, elem: *const T) Index { const address = @intFromPtr(elem); for (list.blocks.items, 0..) |*block, block_index| { const base = @intFromPtr(&block.items[0]); @@ -182,7 +193,7 @@ pub fn BlockList(comptime T: type) type { if (address >= base and address < top) { return .{ .block = @intCast(block_index), - .index = @intCast(@divExact(address - base, @sizeOf(T))), + .element = @intCast(@divExact(address - base, @sizeOf(T))), }; } } diff --git a/src/frontend/semantic_analyzer.zig b/src/frontend/semantic_analyzer.zig index 27e2076..e4ef3cd 100644 --- a/src/frontend/semantic_analyzer.zig +++ b/src/frontend/semantic_analyzer.zig @@ -146,6 +146,7 @@ const Analyzer = struct { var reaches_end = true; const block_node = analyzer.getScopeNode(scope_index, node_index); var statement_nodes = ArrayList(Node.Index){}; + switch (block_node.id) { .block_one, .comptime_block_one => { try statement_nodes.append(analyzer.allocator, block_node.left); @@ -175,7 +176,7 @@ const Analyzer = struct { } const statement_node = analyzer.getScopeNode(scope_index, statement_node_index); - const statement_value = switch (statement_node.id) { + const statement_value_index = switch (statement_node.id) { .assign => (try analyzer.module.values.append(analyzer.allocator, try analyzer.processAssignment(scope_index, statement_node_index))).index, .simple_while => blk: { const loop_allocation = try analyzer.module.loops.append(analyzer.allocator, .{ @@ -196,7 +197,7 @@ const Analyzer = struct { }, .@"unreachable" => blk: { reaches_end = false; - break :blk Compilation.Values.@"unreachable".getIndex(); + break :blk unreachable_index; }, .simple_symbol_declaration => blk: { const declaration_index = try analyzer.symbolDeclaration(scope_index, statement_node_index, .local); @@ -216,18 +217,43 @@ const Analyzer = struct { .@"return" => blk: { reaches_end = false; - const return_value_allocation = try analyzer.module.values.append(analyzer.allocator, try analyzer.processReturn(scope_index, expect_type, statement_node_index)); + const return_expresssion = try analyzer.processReturn(scope_index, expect_type, statement_node_index); + const return_value_allocation = try analyzer.module.values.append(analyzer.allocator, return_expresssion); break :blk return_value_allocation.index; }, - .call_two, .call => (try analyzer.module.values.append(analyzer.allocator, .{ - .call = try analyzer.processCall(scope_index, statement_node_index), - })).index, - .@"switch" => (try analyzer.module.values.append(analyzer.allocator, try analyzer.processSwitch(scope_index, statement_node_index))).index, + .call_two, .call => blk: { + const call_index = try analyzer.processCall(scope_index, statement_node_index); + const call_statement = try analyzer.module.values.append(analyzer.allocator, .{ + .call = call_index, + }); + if (call_statement.ptr.getType(analyzer.module).eq(Type.noreturn)) { + 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" => reaches_end = false, + else => {}, + } + const switch_value_allocation = try analyzer.module.values.append(analyzer.allocator, switch_value); + + break :blk switch_value_allocation.index; + }, + .if_else => blk: { + const if_else_value = try analyzer.processIfElse(scope_index, expect_type, statement_node_index); + const branch = analyzer.module.branches.get(if_else_value.branch); + reaches_end = branch.reaches_end; + const branch_statement = try analyzer.module.values.append(analyzer.allocator, if_else_value); + break :blk branch_statement.index; + }, else => |t| @panic(@tagName(t)), }; - try statements.append(analyzer.allocator, statement_value); + try statements.append(analyzer.allocator, statement_value_index); } const block_allocation = try analyzer.module.blocks.append(analyzer.allocator, .{ @@ -254,7 +280,7 @@ const Analyzer = struct { const left_type = switch (left_value_index.invalid) { false => switch (analyzer.module.values.get(left_value_index).*) { - .function => |function_index| analyzer.module.function_prototypes.get(analyzer.module.types.get(analyzer.module.functions.get(function_index).prototype).function).return_type, + .function_definition => |function_index| analyzer.module.function_prototypes.get(analyzer.module.types.get(analyzer.module.function_definitions.get(function_index).prototype).function).return_type, else => |t| @panic(@tagName(t)), }, true => Type.Index.invalid, @@ -270,9 +296,9 @@ const Analyzer = struct { }; switch (analyzer.module.values.get(left_value_index).*) { - .function => |function_index| { - const function = analyzer.module.functions.get(function_index); - const function_prototype = analyzer.module.function_prototypes.get(analyzer.module.types.get(function.prototype).function); + .function_definition => |function_index| { + const function_definition = analyzer.module.function_definitions.get(function_index); + const function_prototype = analyzer.module.function_prototypes.get(analyzer.module.types.get(function_definition.prototype).function); const argument_declarations = function_prototype.arguments.?; logln(.sema, .call, "Argument declaration count: {}. Argument node list count: {}\n", .{ argument_declarations.len, call_argument_node_list.len }); var argument_array = ArrayList(Value.Index){}; @@ -340,7 +366,8 @@ const Analyzer = struct { } } - fn processSwitch(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Value { + fn processSwitch(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { + _ = expect_type; const node = analyzer.getScopeNode(scope_index, node_index); assert(node.id == .@"switch"); @@ -458,6 +485,44 @@ const Analyzer = struct { unreachable; } + fn processIfElse(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 == .if_else); + assert(!node.left.invalid); + assert(!node.right.invalid); + + const if_branch_node = analyzer.getScopeNode(scope_index, node.left); + const if_condition = try analyzer.unresolvedAllocate(scope_index, ExpectType.boolean, if_branch_node.left); + switch (if_condition.ptr.*) { + .declaration_reference => { + const true_expression = try analyzer.unresolvedAllocate(scope_index, expect_type, if_branch_node.right); + const true_reaches_end = switch (true_expression.ptr.*) { + .block => |block_index| analyzer.module.blocks.get(block_index).reaches_end, + else => |t| @panic(@tagName(t)), + }; + const false_expression = try analyzer.unresolvedAllocate(scope_index, expect_type, node.right); + const false_reaches_end = switch (true_expression.ptr.*) { + .block => |block_index| analyzer.module.blocks.get(block_index).reaches_end, + else => |t| @panic(@tagName(t)), + }; + const reaches_end = true_reaches_end and false_reaches_end; + + const branch = try analyzer.module.branches.append(analyzer.allocator, .{ + .condition = if_condition.index, + .true_expression = true_expression.index, + .false_expression = false_expression.index, + .reaches_end = reaches_end, + }); + + return Value{ + .branch = branch.index, + }; + }, + .bool => unreachable, + else => |t| @panic(@tagName(t)), + } + } + fn processAssignment(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Value { const node = analyzer.getScopeNode(scope_index, node_index); assert(node.id == .assign); @@ -536,9 +601,15 @@ const Analyzer = struct { .divide => .divide, .shift_left => .shift_left, .shift_right => .shift_right, + .compare_equal => .compare_equal, else => |t| @panic(@tagName(t)), }; + const left_expect_type: ExpectType = switch (binary_operation_id) { + .compare_equal => ExpectType.none, + else => expect_type, + }; + const left_allocation = try analyzer.unresolvedAllocate(scope_index, left_expect_type, node.left); const right_expect_type: ExpectType = switch (binary_operation_id) { .add, .sub, @@ -553,18 +624,26 @@ const Analyzer = struct { => ExpectType{ .type_index = Type.u8, }, + .compare_equal => ExpectType{ + .type_index = left_allocation.ptr.getType(analyzer.module), + }, }; - - const left_allocation = try analyzer.unresolvedAllocate(scope_index, expect_type, node.left); const right_allocation = try analyzer.unresolvedAllocate(scope_index, right_expect_type, node.right); const left_type = left_allocation.ptr.getType(analyzer.module); - const right_type = right_allocation.ptr.getType(analyzer.module); - _ = right_type; + // const right_type = right_allocation.ptr.getType(analyzer.module); + // _ = right_type; const binary_operation = try analyzer.module.binary_operations.append(analyzer.allocator, .{ .left = left_allocation.index, .right = right_allocation.index, - .type = left_type, + .type = switch (expect_type) { + .none => switch (binary_operation_id) { + .logical_and => left_type, + else => |t| @panic(@tagName(t)), + }, + .type_index => |type_index| type_index, + else => |t| @panic(@tagName(t)), + }, .id = binary_operation_id, }); @@ -615,7 +694,7 @@ const Analyzer = struct { try analyzer.resolveNode(init_value, lookup.scope, expect_type, init_value.unresolved.node_index); declaration.type = init_value.getType(analyzer.module); switch (init_value.*) { - .function => |function_index| { + .function_definition => |function_index| { try analyzer.module.function_name_map.put(analyzer.allocator, function_index, declaration.name); }, else => {}, @@ -721,7 +800,7 @@ const Analyzer = struct { const value_ref = analyzer.module.values.get(value_index); break :blk value_ref.*; }, - .keyword_true => { + .keyword_true, .keyword_false => blk: { switch (expect_type) { .none => {}, .type_index => |expected_type| { @@ -732,10 +811,13 @@ const Analyzer = struct { else => unreachable, } - // TODO - unreachable; - - // break :blk Values.getIndex(.bool_true); + break :blk .{ + .bool = switch (node.id) { + .keyword_true => true, + .keyword_false => false, + else => unreachable, + }, + }; }, .compiler_intrinsic_one, .compiler_intrinsic_two, .compiler_intrinsic => blk: { const intrinsic_name = analyzer.tokenIdentifier(scope_index, node.token + 1); @@ -826,6 +908,8 @@ const Analyzer = struct { }); const function_prototype_index = try analyzer.functionPrototype(function_scope_allocation.index, node.left); + const function_prototype = analyzer.module.function_prototypes.get(function_prototype_index); + assert(!function_prototype.attributes.@"extern"); const function_body = try analyzer.block(function_scope_allocation.index, .{ .type_index = analyzer.functionPrototypeReturnType(function_prototype_index), @@ -835,14 +919,14 @@ const Analyzer = struct { .function = function_prototype_index, }); - const function_allocation = try analyzer.module.functions.append(analyzer.allocator, .{ + const function_allocation = try analyzer.module.function_definitions.append(analyzer.allocator, .{ .prototype = prototype_type.index, .body = function_body, .scope = function_scope_allocation.index, }); break :blk .{ - .function = function_allocation.index, + .function_definition = function_allocation.index, }; }, .function_prototype => blk: { @@ -854,13 +938,13 @@ const Analyzer = struct { const prototype_type = try analyzer.module.types.append(analyzer.allocator, .{ .function = function_prototype_index, }); - const function_allocation = try analyzer.module.functions.append(analyzer.allocator, .{ + const function_declaration = try analyzer.module.function_declarations.append(analyzer.allocator, .{ .prototype = prototype_type.index, .body = Block.Index.invalid, .scope = Scope.Index.invalid, }); break :b .{ - .function = function_allocation.index, + .function_declaration = function_declaration.index, }; }, false => unreachable, @@ -919,7 +1003,7 @@ const Analyzer = struct { const right_index = try analyzer.doIdentifier(struct_type.scope, ExpectType.none, node.right.value, scope_index); const right_value = analyzer.module.values.get(right_index); switch (right_value.*) { - .function, .type, .enum_field => break :blk right_value.*, + .function_definition, .type, .enum_field => break :blk right_value.*, .declaration_reference => |declaration_reference| { const declaration = analyzer.module.declarations.get(declaration_reference.value); const declaration_name = analyzer.module.getName(declaration.name).?; @@ -983,7 +1067,7 @@ const Analyzer = struct { .string_literal => .{ .string_literal = try analyzer.processStringLiteral(scope_index, node_index), }, - .@"switch" => try analyzer.processSwitch(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) { @@ -1038,6 +1122,7 @@ const Analyzer = struct { .divide, .shift_left, .shift_right, + .compare_equal, => try analyzer.processBinaryOperation(scope_index, expect_type, node_index), .expression_group => return try analyzer.resolveNode(value, scope_index, expect_type, node.left), //unreachable, else => |t| @panic(@tagName(t)), @@ -1146,6 +1231,7 @@ const Analyzer = struct { .void_type => Type.void, .ssize_type => Type.ssize, .usize_type => Type.usize, + .bool_type => Type.boolean, else => |t| @panic(@tagName(t)), }; return type_index; @@ -1525,6 +1611,9 @@ const Analyzer = struct { }, else => |t| @panic(@tagName(t)), }, + .bool => switch (source_type.*) { + else => |t| @panic(@tagName(t)), + }, else => |t| @panic(@tagName(t)), } }, @@ -1584,6 +1673,8 @@ const ExpectType = union(enum) { }; }; +pub var unreachable_index = Value.Index.invalid; + pub fn initialize(compilation: *Compilation, module: *Module, package: *Package, main_value: *Value) !void { _ = try analyzeExistingPackage(main_value, compilation, module, package); @@ -1593,7 +1684,7 @@ pub fn initialize(compilation: *Compilation, module: *Module, package: *Package, if (equal(u8, declaration_name, "_start")) { const value = module.values.get(decl.init_value); module.entry_point = switch (value.*) { - .function => |function_index| function_index, + .function_definition => |function_index| function_index, .unresolved => panic("Unresolved declaration: {s}\n", .{declaration_name}), else => |t| @panic(@tagName(t)), }; diff --git a/src/frontend/syntactic_analyzer.zig b/src/frontend/syntactic_analyzer.zig index 43cbcfd..0b398e6 100644 --- a/src/frontend/syntactic_analyzer.zig +++ b/src/frontend/syntactic_analyzer.zig @@ -162,6 +162,7 @@ pub const Node = packed struct(u128) { divide = 68, shift_left = 69, shift_right = 70, + bool_type = 71, }; }; @@ -611,7 +612,7 @@ const Analyzer = struct { analyzer.token_i += 1; _ = try analyzer.expectToken(.left_parenthesis); - const if_expression = try analyzer.expression(); + const if_condition = try analyzer.expression(); _ = try analyzer.expectToken(.right_parenthesis); const if_block = try analyzer.block(.{ .is_comptime = false }); @@ -619,7 +620,7 @@ const Analyzer = struct { const if_node = try analyzer.addNode(.{ .id = .@"if", .token = if_token, - .left = if_expression, + .left = if_condition, .right = if_block, }); @@ -782,7 +783,6 @@ const Analyzer = struct { const token = analyzer.tokens[analyzer.token_i]; // logln("Looping in expression precedence with token {}\n", .{token}); const operator: PrecedenceOperator = switch (token.id) { - .equal, .semicolon, .right_parenthesis, .right_brace, @@ -1180,6 +1180,15 @@ const Analyzer = struct { .right = Node.Index.invalid, }), .hash => analyzer.compilerIntrinsic(), + .fixed_keyword_bool => analyzer.addNode(.{ + .id = .bool_type, + .token = blk: { + analyzer.token_i += 1; + break :blk token_i; + }, + .left = Node.Index.invalid, + .right = Node.Index.invalid, + }), .keyword_unsigned_integer, .keyword_signed_integer => |signedness| analyzer.addNode(.{ .id = switch (signedness) { .keyword_unsigned_integer => .unsigned_integer_type, diff --git a/src/main.zig b/src/main.zig index 89c7c0e..8b4d474 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,7 @@ pub const panic = Compilation.panic; pub fn main() !void { const allocator = std.heap.page_allocator; + try Compilation.init(allocator); } diff --git a/test/simple_bool/main.nat b/test/simple_bool/main.nat new file mode 100644 index 0000000..088682b --- /dev/null +++ b/test/simple_bool/main.nat @@ -0,0 +1,8 @@ +const main = fn () s32 { + var false_boolean: bool = false; + if (false_boolean) { + return 1; + } else { + return 0; + } +}