Commit a5a89fde13

Koakuma <koachan@protonmail.com>
2022-03-28 15:31:40
stage2: sparcv9: Add skeleton codegen impl and necessary fields
1 parent a30688e
Changed files (3)
src/arch/sparcv9/CodeGen.zig
@@ -2,24 +2,198 @@
 //! This lowers AIR into MIR.
 const std = @import("std");
 const assert = std.debug.assert;
+const mem = std.mem;
+const Allocator = mem.Allocator;
 const builtin = @import("builtin");
 const link = @import("../../link.zig");
 const Module = @import("../../Module.zig");
+const ErrorMsg = Module.ErrorMsg;
 const Air = @import("../../Air.zig");
 const Mir = @import("Mir.zig");
 const Emit = @import("Emit.zig");
 const Liveness = @import("../../Liveness.zig");
-const build_options = @import("build_options");
-
+const Type = @import("../../type.zig").Type;
 const GenerateSymbolError = @import("../../codegen.zig").GenerateSymbolError;
 const FnResult = @import("../../codegen.zig").FnResult;
 const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
 
+const build_options = @import("build_options");
+
 const bits = @import("bits.zig");
 const abi = @import("abi.zig");
+const Register = bits.Register;
 
 const Self = @This();
 
+const InnerError = error{
+    OutOfMemory,
+    CodegenFail,
+    OutOfRegisters,
+};
+
+gpa: Allocator,
+air: Air,
+liveness: Liveness,
+bin_file: *link.File,
+target: *const std.Target,
+mod_fn: *const Module.Fn,
+code: *std.ArrayList(u8),
+debug_output: DebugInfoOutput,
+err_msg: ?*ErrorMsg,
+args: []MCValue,
+ret_mcv: MCValue,
+fn_type: Type,
+arg_index: usize,
+src_loc: Module.SrcLoc,
+stack_align: u32,
+
+/// MIR Instructions
+mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
+/// MIR extra data
+mir_extra: std.ArrayListUnmanaged(u32) = .{},
+
+/// Byte offset within the source file of the ending curly.
+end_di_line: u32,
+end_di_column: u32,
+
+/// The value is an offset into the `Function` `code` from the beginning.
+/// To perform the reloc, write 32-bit signed little-endian integer
+/// which is a relative jump, based on the address following the reloc.
+exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{},
+
+/// Whenever there is a runtime branch, we push a Branch onto this stack,
+/// and pop it off when the runtime branch joins. This provides an "overlay"
+/// of the table of mappings from instructions to `MCValue` from within the branch.
+/// This way we can modify the `MCValue` for an instruction in different ways
+/// within different branches. Special consideration is needed when a branch
+/// joins with its parent, to make sure all instructions have the same MCValue
+/// across each runtime branch upon joining.
+branch_stack: *std.ArrayList(Branch),
+
+// Key is the block instruction
+blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{},
+
+/// Maps offset to what is stored there.
+stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{},
+
+/// Offset from the stack base, representing the end of the stack frame.
+max_end_stack: u32 = 0,
+/// Represents the current end stack offset. If there is no existing slot
+/// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
+next_stack_offset: u32 = 0,
+
+/// Debug field, used to find bugs in the compiler.
+air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,
+
+const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {};
+
+const MCValue = union(enum) {
+    /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
+    /// TODO Look into deleting this tag and using `dead` instead, since every use
+    /// of MCValue.none should be instead looking at the type and noticing it is 0 bits.
+    none,
+    /// Control flow will not allow this value to be observed.
+    unreach,
+    /// No more references to this value remain.
+    dead,
+    /// The value is undefined.
+    undef,
+    /// A pointer-sized integer that fits in a register.
+    /// If the type is a pointer, this is the pointer address in virtual address space.
+    immediate: u64,
+    /// The value is in a target-specific register.
+    register: Register,
+    /// The value is in memory at a hard-coded address.
+    /// If the type is a pointer, it means the pointer address is at this memory location.
+    memory: u64,
+    /// The value is one of the stack variables.
+    /// If the type is a pointer, it means the pointer address is in the stack at this offset.
+    stack_offset: u32,
+    /// The value is a pointer to one of the stack variables (payload is stack offset).
+    ptr_stack_offset: u32,
+
+    fn isMemory(mcv: MCValue) bool {
+        return switch (mcv) {
+            .memory, .stack_offset => true,
+            else => false,
+        };
+    }
+
+    fn isImmediate(mcv: MCValue) bool {
+        return switch (mcv) {
+            .immediate => true,
+            else => false,
+        };
+    }
+
+    fn isMutable(mcv: MCValue) bool {
+        return switch (mcv) {
+            .none => unreachable,
+            .unreach => unreachable,
+            .dead => unreachable,
+
+            .immediate,
+            .memory,
+            .ptr_stack_offset,
+            .undef,
+            => false,
+
+            .register,
+            .stack_offset,
+            => true,
+        };
+    }
+};
+
+const Branch = struct {
+    inst_table: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, MCValue) = .{},
+
+    fn deinit(self: *Branch, gpa: Allocator) void {
+        self.inst_table.deinit(gpa);
+        self.* = undefined;
+    }
+};
+
+const StackAllocation = struct {
+    inst: Air.Inst.Index,
+    /// TODO do we need size? should be determined by inst.ty.abiSize()
+    size: u32,
+};
+
+const BlockData = struct {
+    relocs: std.ArrayListUnmanaged(Reloc),
+    /// The first break instruction encounters `null` here and chooses a
+    /// machine code value for the block result, populating this field.
+    /// Following break instructions encounter that value and use it for
+    /// the location to store their block results.
+    mcv: MCValue,
+};
+
+const Reloc = union(enum) {
+    /// The value is an offset into the `Function` `code` from the beginning.
+    /// To perform the reloc, write 32-bit signed little-endian integer
+    /// which is a relative jump, based on the address following the reloc.
+    rel32: usize,
+    /// A branch in the ARM instruction set
+    arm_branch: struct {
+        pos: usize,
+        cond: @import("../arm/bits.zig").Condition,
+    },
+};
+
+const CallMCValues = struct {
+    args: []MCValue,
+    return_value: MCValue,
+    stack_byte_count: u32,
+    stack_align: u32,
+
+    fn deinit(self: *CallMCValues, func: *Self) void {
+        func.gpa.free(self.args);
+        self.* = undefined;
+    }
+};
+
+
 pub fn generate(
     bin_file: *link.File,
     src_loc: Module.SrcLoc,
@@ -29,19 +203,110 @@ pub fn generate(
     code: *std.ArrayList(u8),
     debug_output: DebugInfoOutput,
 ) GenerateSymbolError!FnResult {
-    _ = bin_file;
-    _ = src_loc;
-    _ = module_fn;
-    _ = air;
-    _ = liveness;
-    _ = code;
-    _ = debug_output;
-
     if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) {
         @panic("Attempted to compile for architecture that was disabled by build configuration");
     }
 
     assert(module_fn.owner_decl.has_tv);
+    const fn_type = module_fn.owner_decl.ty;
+
+    var branch_stack = std.ArrayList(Branch).init(bin_file.allocator);
+    defer {
+        assert(branch_stack.items.len == 1);
+        branch_stack.items[0].deinit(bin_file.allocator);
+        branch_stack.deinit();
+    }
+    try branch_stack.append(.{});
+
+    var function = Self{
+        .gpa = bin_file.allocator,
+        .air = air,
+        .liveness = liveness,
+        .target = &bin_file.options.target,
+        .bin_file = bin_file,
+        .mod_fn = module_fn,
+        .code = code,
+        .debug_output = debug_output,
+        .err_msg = null,
+        .args = undefined, // populated after `resolveCallingConventionValues`
+        .ret_mcv = undefined, // populated after `resolveCallingConventionValues`
+        .fn_type = fn_type,
+        .arg_index = 0,
+        .branch_stack = &branch_stack,
+        .src_loc = src_loc,
+        .stack_align = undefined,
+        .end_di_line = module_fn.rbrace_line,
+        .end_di_column = module_fn.rbrace_column,
+    };
+    defer function.stack.deinit(bin_file.allocator);
+    defer function.blocks.deinit(bin_file.allocator);
+    defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
+
+    var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
+        error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
+        else => |e| return e,
+    };
+    defer call_info.deinit(&function);
+
+    function.args = call_info.args;
+    function.ret_mcv = call_info.return_value;
+    function.stack_align = call_info.stack_align;
+    function.max_end_stack = call_info.stack_byte_count;
+
+    function.gen() catch |err| switch (err) {
+        error.CodegenFail => return FnResult{ .fail = function.err_msg.? },
+        error.OutOfRegisters => return FnResult{
+            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+        },
+        else => |e| return e,
+    };
+
+    var mir = Mir{
+        .instructions = function.mir_instructions.toOwnedSlice(),
+        .extra = function.mir_extra.toOwnedSlice(bin_file.allocator),
+    };
+    defer mir.deinit(bin_file.allocator);
+
+    var emit = Emit{
+        .mir = mir,
+        .bin_file = bin_file,
+        .debug_output = debug_output,
+        .target = &bin_file.options.target,
+        .src_loc = src_loc,
+        .code = code,
+        .prev_di_pc = 0,
+        .prev_di_line = module_fn.lbrace_line,
+        .prev_di_column = module_fn.lbrace_column,
+    };
+    defer emit.deinit();
+
+    emit.emitMir() catch |err| switch (err) {
+        error.EmitFail => return FnResult{ .fail = emit.err_msg.? },
+        else => |e| return e,
+    };
+
+    if (function.err_msg) |em| {
+        return FnResult{ .fail = em };
+    } else {
+        return FnResult{ .appended = {} };
+    }
+}
+
+/// Caller must call `CallMCValues.deinit`.
+fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues {
+    _ = self;
+    _ = fn_ty;
+
+    @panic("TODO implement resolveCallingConventionValues");
+}
+
+
+/// Caller must call `CallMCValues.deinit`.
+fn gen(self: *Self) !void {
+    _ = self;
 
-    @panic("TODO implement SPARCv9 codegen");
+    @panic("TODO implement gen");
 }
src/arch/sparcv9/Emit.zig
@@ -1,6 +1,43 @@
 //! This file contains the functionality for lowering SPARCv9 MIR into
 //! machine code
 
+const std = @import("std");
+const link = @import("../../link.zig");
+const Module = @import("../../Module.zig");
+const ErrorMsg = Module.ErrorMsg;
+const Liveness = @import("../../Liveness.zig");
+const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
+
 const Emit = @This();
 const Mir = @import("Mir.zig");
 const bits = @import("bits.zig");
+
+mir: Mir,
+bin_file: *link.File,
+debug_output: DebugInfoOutput,
+target: *const std.Target,
+err_msg: ?*ErrorMsg = null,
+src_loc: Module.SrcLoc,
+code: *std.ArrayList(u8),
+
+prev_di_line: u32,
+prev_di_column: u32,
+/// Relative to the beginning of `code`.
+prev_di_pc: usize,
+
+const InnerError = error{
+    OutOfMemory,
+    EmitFail,
+};
+
+pub fn emitMir(
+    emit: *Emit,
+) InnerError!void {
+    _ = emit;
+
+    @panic("TODO implement emitMir");
+}
+
+pub fn deinit(emit: *Emit) void {
+    emit.* = undefined;
+}
src/arch/sparcv9/Mir.zig
@@ -6,6 +6,55 @@
 //! The main purpose of MIR is to postpone the assignment of offsets until Isel,
 //! so that, for example, the smaller encodings of jump instructions can be used.
 
+const std = @import("std");
+
 const Mir = @This();
 const bits = @import("bits.zig");
 const Register = bits.Register;
+
+instructions: std.MultiArrayList(Inst).Slice,
+
+/// The meaning of this data is determined by `Inst.Tag` value.
+extra: []const u32,
+
+pub const Inst = struct {
+    tag: Tag,
+    /// The meaning of this depends on `tag`.
+    data: Data,
+
+    pub const Tag = enum(u16) {
+        /// Pseudo-instruction: End of prologue
+        dbg_prologue_end,
+        /// Pseudo-instruction: Beginning of epilogue
+        dbg_epilogue_begin,
+        /// Pseudo-instruction: Update debug line
+        dbg_line,
+    };
+
+    /// The position of an MIR instruction within the `Mir` instructions array.
+    pub const Index = u32;
+
+    /// All instructions have a 4-byte payload, which is contained within
+    /// this union. `Tag` determines which union field is active, as well as
+    /// how to interpret the data within.
+    pub const Data = union {
+        /// No additional data
+        ///
+        /// Used by e.g. flushw
+        nop: void,
+        /// Debug info: line and column
+        ///
+        /// Used by e.g. dbg_line
+        dbg_line_column: struct {
+            line: u32,
+            column: u32,
+        },
+    };
+};
+
+pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
+    mir.instructions.deinit(gpa);
+    gpa.free(mir.extra);
+    mir.* = undefined;
+}
+