Commit a5a89fde13
Changed files (3)
src
arch
sparcv9
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;
+}
+