diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 6354cb261a..e0e0edf906 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -759,6 +759,13 @@ else pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr: ?usize) noreturn { @setCold(true); + // stage2_riscv64 backend doesn't support loops yet. + if (builtin.zig_backend == .stage2_riscv64 or + builtin.cpu.arch == .riscv64) + { + unreachable; + } + // For backends that cannot handle the language features depended on by the // default panic handler, we have a simpler panic handler: if (builtin.zig_backend == .stage2_wasm or @@ -766,7 +773,6 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr builtin.zig_backend == .stage2_aarch64 or builtin.zig_backend == .stage2_x86 or (builtin.zig_backend == .stage2_x86_64 and (builtin.target.ofmt != .elf and builtin.target.ofmt != .macho)) or - builtin.zig_backend == .stage2_riscv64 or builtin.zig_backend == .stage2_sparc64 or builtin.zig_backend == .stage2_spirv64) { diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 5abe3afcfd..de78dd75dc 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -33,7 +33,6 @@ const abi = @import("abi.zig"); const Register = bits.Register; const RegisterManager = abi.RegisterManager; const RegisterLock = RegisterManager.RegisterLock; -const Instruction = abi.Instruction; const callee_preserved_regs = abi.callee_preserved_regs; const gp = abi.RegisterClass.gp; @@ -96,6 +95,8 @@ air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init, const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {}; +const SymbolOffset = struct { sym: u32, off: i32 = 0 }; + 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 @@ -110,6 +111,9 @@ const MCValue = union(enum) { /// 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 memory at an address not-yet-allocated by the linker. + /// This traditionally corresponds to a relocation emitted in a relocatable object file. + load_symbol: SymbolOffset, /// The value is in a target-specific register. register: Register, /// The value is in memory at a hard-coded address. @@ -145,6 +149,7 @@ const MCValue = union(enum) { .memory, .ptr_stack_offset, .undef, + .load_symbol, => false, .register, @@ -165,12 +170,12 @@ const Branch = struct { const StackAllocation = struct { inst: Air.Inst.Index, - /// TODO do we need size? should be determined by inst.ty.abiSize() + /// TODO: make the size inferred from the bits of the inst size: u32, }; const BlockData = struct { - relocs: std.ArrayListUnmanaged(Reloc), + relocs: std.ArrayListUnmanaged(Mir.Inst.Index), /// 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 @@ -178,18 +183,6 @@ const BlockData = struct { 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 BigTomb = struct { function: *Self, inst: Air.Inst.Index, @@ -272,6 +265,7 @@ pub fn generate( }, else => |e| return e, }; + defer call_info.deinit(&function); function.args = call_info.args; @@ -328,6 +322,13 @@ fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { return result_index; } +fn addNop(self: *Self) error{OutOfMemory}!Mir.Inst.Index { + return try self.addInst(.{ + .tag = .nop, + .data = .{ .nop = {} }, + }); +} + pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 { const fields = std.meta.fields(@TypeOf(extra)); try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len); @@ -350,115 +351,45 @@ pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { fn gen(self: *Self) !void { const mod = self.bin_file.comp.module.?; const cc = self.fn_type.fnCallingConvention(mod); - if (cc != .Naked) { - // TODO Finish function prologue and epilogue for riscv64. - // TODO Backpatch stack offset - // addi sp, sp, -16 - _ = try self.addInst(.{ - .tag = .addi, - .data = .{ .i_type = .{ - .rd = .sp, - .rs1 = .sp, - .imm12 = -16, - } }, - }); + if (cc == .Naked) return self.fail("TODO: gen support callconv(.{s})", .{@tagName(cc)}); - // sd ra, 8(sp) - _ = try self.addInst(.{ - .tag = .sd, - .data = .{ .i_type = .{ - .rd = .ra, - .rs1 = .sp, - .imm12 = 8, - } }, - }); + _ = try self.addInst(.{ + .tag = .psuedo_prologue, + .data = .{ .imm12 = 0 }, // Backpatched later. + }); - // sd s0, 0(sp) - _ = try self.addInst(.{ - .tag = .sd, - .data = .{ .i_type = .{ - .rd = .s0, - .rs1 = .sp, - .imm12 = 0, - } }, - }); + _ = try self.addInst(.{ + .tag = .dbg_prologue_end, + .data = .{ .nop = {} }, + }); - _ = try self.addInst(.{ - .tag = .dbg_prologue_end, - .data = .{ .nop = {} }, - }); + try self.genBody(self.air.getMainBody()); - try self.genBody(self.air.getMainBody()); + // Backpatch prologue stack size + if (math.cast(i12, self.max_end_stack)) |casted_stack_size| { + self.mir_instructions.items(.data)[0].imm12 = casted_stack_size; + } else return self.fail("TODO support larger stack sizes, got {}", .{self.max_end_stack}); - _ = try self.addInst(.{ - .tag = .dbg_epilogue_begin, - .data = .{ .nop = {} }, - }); + _ = try self.addInst(.{ + .tag = .dbg_epilogue_begin, + .data = .{ .nop = {} }, + }); - // exitlude jumps - if (self.exitlude_jump_relocs.items.len > 0 and - self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 2) - { - // If the last Mir instruction (apart from the - // dbg_epilogue_begin) is the last exitlude jump - // relocation (which would just jump one instruction - // further), it can be safely removed - self.mir_instructions.orderedRemove(self.exitlude_jump_relocs.pop()); - } + // exitlude jumps + if (self.exitlude_jump_relocs.items.len > 0 and + self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 2) + { + // If the last Mir instruction (apart from the + // dbg_epilogue_begin) is the last exitlude jump + // relocation (which would just jump one instruction + // further), it can be safely removed + self.mir_instructions.orderedRemove(self.exitlude_jump_relocs.pop()); + } - for (self.exitlude_jump_relocs.items) |jmp_reloc| { - _ = jmp_reloc; - return self.fail("TODO add branches in RISCV64", .{}); - } - - // ld ra, 8(sp) - _ = try self.addInst(.{ - .tag = .ld, - .data = .{ .i_type = .{ - .rd = .ra, - .rs1 = .sp, - .imm12 = 8, - } }, - }); - - // ld s0, 0(sp) - _ = try self.addInst(.{ - .tag = .ld, - .data = .{ .i_type = .{ - .rd = .s0, - .rs1 = .sp, - .imm12 = 0, - } }, - }); - - // addi sp, sp, 16 - _ = try self.addInst(.{ - .tag = .addi, - .data = .{ .i_type = .{ - .rd = .sp, - .rs1 = .sp, - .imm12 = 16, - } }, - }); - - // ret - _ = try self.addInst(.{ - .tag = .ret, - .data = .{ .nop = {} }, - }); - } else { - _ = try self.addInst(.{ - .tag = .dbg_prologue_end, - .data = .{ .nop = {} }, - }); - - try self.genBody(self.air.getMainBody()); - - _ = try self.addInst(.{ - .tag = .dbg_epilogue_begin, - .data = .{ .nop = {} }, - }); + for (self.exitlude_jump_relocs.items) |jmp_reloc| { + _ = jmp_reloc; + return self.fail("TODO add branches in RISCV64", .{}); } // Drop them off at the rbrace. @@ -535,12 +466,12 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), - .cmp_lt => try self.airCmp(inst, .lt), - .cmp_lte => try self.airCmp(inst, .lte), - .cmp_eq => try self.airCmp(inst, .eq), - .cmp_gte => try self.airCmp(inst, .gte), - .cmp_gt => try self.airCmp(inst, .gt), - .cmp_neq => try self.airCmp(inst, .neq), + .cmp_lt => try self.airCmp(inst), + .cmp_lte => try self.airCmp(inst), + .cmp_eq => try self.airCmp(inst), + .cmp_gte => try self.airCmp(inst), + .cmp_gt => try self.airCmp(inst), + .cmp_neq => try self.airCmp(inst), .cmp_vector => try self.airCmpVector(inst), .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), @@ -565,6 +496,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .frame_addr => try self.airFrameAddress(inst), .fence => try self.airFence(), .cond_br => try self.airCondBr(inst), + .dbg_stmt => try self.airDbgStmt(inst), .fptrunc => try self.airFptrunc(inst), .fpext => try self.airFpext(inst), .intcast => try self.airIntCast(inst), @@ -617,17 +549,17 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), .mul_add => try self.airMulAdd(inst), - .addrspace_cast => @panic("TODO"), + .addrspace_cast => return self.fail("TODO: addrspace_cast", .{}), - .@"try" => @panic("TODO"), - .try_ptr => @panic("TODO"), + .@"try" => return self.fail("TODO: try", .{}), + .try_ptr => return self.fail("TODO: try_ptr", .{}), - .dbg_stmt => try self.airDbgStmt(inst), - .dbg_inline_block => try self.airDbgInlineBlock(inst), .dbg_var_ptr, .dbg_var_val, => try self.airDbgVar(inst), + .dbg_inline_block => try self.airDbgInlineBlock(inst), + .call => try self.airCall(inst, .auto), .call_always_tail => try self.airCall(inst, .always_tail), .call_never_tail => try self.airCall(inst, .never_tail), @@ -1019,17 +951,20 @@ fn binOpRegister( const mir_tag: Mir.Inst.Tag = switch (tag) { .add => .add, .sub => .sub, - else => unreachable, + .cmp_eq => .cmp_eq, + .cmp_gt => .cmp_gt, + else => return self.fail("TODO: binOpRegister {s}", .{@tagName(tag)}), }; const mir_data: Mir.Inst.Data = switch (tag) { .add, .sub, + .cmp_eq, => .{ .r_type = .{ .rd = dest_reg, .rs1 = lhs_reg, .rs2 = rhs_reg, } }, - else => unreachable, + else => return self.fail("TODO: binOpRegister {s}", .{@tagName(tag)}), }; _ = try self.addInst(.{ @@ -1052,6 +987,8 @@ fn binOpRegister( /// looks at the lhs and rhs and determines which kind of lowering /// would be best suitable and then delegates the lowering to other /// functions. +/// +/// `maybe_inst` **needs** to be a bin_op, make sure of that. fn binOp( self: *Self, tag: Air.Inst.Tag, @@ -1066,6 +1003,12 @@ fn binOp( // Arithmetic operations on integers and floats .add, .sub, + .cmp_eq, + .cmp_neq, + .cmp_gt, + .cmp_gte, + .cmp_lt, + .cmp_lte, => { switch (lhs_ty.zigTypeTag(mod)) { .Float => return self.fail("TODO binary operations on floats", .{}), @@ -1180,8 +1123,19 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { } fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void { - _ = inst; - return self.fail("TODO implement airAddWithOverflow for {}", .{self.target.cpu.arch}); + const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; + const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; + + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { + const lhs = try self.resolveInst(extra.lhs); + const rhs = try self.resolveInst(extra.rhs); + const lhs_ty = self.typeOf(extra.lhs); + const rhs_ty = self.typeOf(extra.rhs); + + break :result try self.binOp(.add, null, lhs, rhs, lhs_ty, rhs_ty); + }; + + return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } fn airSubWithOverflow(self: *Self, inst: Air.Inst.Index) !void { @@ -1352,13 +1306,30 @@ fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void { fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement slice_ptr for {}", .{self.target.cpu.arch}); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { + const mcv = try self.resolveInst(ty_op.operand); + break :result try self.slicePtr(mcv); + }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } +fn slicePtr(self: *Self, mcv: MCValue) !MCValue { + switch (mcv) { + .dead, .unreach, .none => unreachable, + .register => unreachable, // a slice doesn't fit in one register + .stack_offset => |off| { + return MCValue{ .stack_offset = off }; + }, + .memory => |addr| { + return MCValue{ .memory = addr }; + }, + else => return self.fail("TODO slicePtr {s}", .{@tagName(mcv)}), + } +} + fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement slice_len for {}", .{self.target.cpu.arch}); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSliceLen for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } @@ -1500,6 +1471,7 @@ fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_ind fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void { const mod = self.bin_file.comp.module.?; const elem_ty = ptr_ty.childType(mod); + switch (ptr) { .none => unreachable, .undef => unreachable, @@ -1507,9 +1479,7 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo .dead => unreachable, .immediate => |imm| try self.setRegOrMem(elem_ty, dst_mcv, .{ .memory = imm }), .ptr_stack_offset => |off| try self.setRegOrMem(elem_ty, dst_mcv, .{ .stack_offset = off }), - .register => { - return self.fail("TODO implement loading from MCValue.register", .{}); - }, + .register => |src_reg| try self.setRegOrMem(elem_ty, dst_mcv, .{ .register = src_reg }), .memory, .stack_offset, => { @@ -1520,6 +1490,10 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo try self.genSetReg(ptr_ty, reg, ptr); try self.load(dst_mcv, .{ .register = reg }, ptr_ty); }, + .load_symbol => { + const reg = try self.copyToTmpRegister(ptr_ty, ptr); + try self.load(dst_mcv, .{ .register = reg }, ptr_ty); + }, } } @@ -1553,6 +1527,8 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) !void { fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type) !void { _ = ptr_ty; + log.debug("storing {s}", .{@tagName(ptr)}); + switch (ptr) { .none => unreachable, .undef => unreachable, @@ -1573,6 +1549,9 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type .stack_offset => { return self.fail("TODO implement storing to MCValue.stack_offset", .{}); }, + .load_symbol => { + return self.fail("TODO implement storing to MCValue.load_symbol", .{}); + }, } } @@ -1596,27 +1575,32 @@ fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) !void { fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; - return self.structFieldPtr(extra.struct_operand, ty_pl.ty, extra.field_index); + const result = try self.structFieldPtr(inst, extra.struct_operand, ty_pl.ty, extra.field_index); + return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none }); } fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) !void { const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; - return self.structFieldPtr(ty_op.operand, ty_op.ty, index); + const result = try self.structFieldPtr(inst, ty_op.operand, ty_op.ty, index); + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } -fn structFieldPtr(self: *Self, operand: Air.Inst.Ref, ty: Air.Inst.Ref, index: u32) !void { + +fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, ty: Air.Inst.Ref, index: u32) !MCValue { + _ = inst; _ = operand; _ = ty; _ = index; - return self.fail("TODO implement codegen struct_field_ptr", .{}); - //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none }); + + return self.fail("TODO: structFieldPtr", .{}); } fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; - _ = extra; - return self.fail("TODO implement codegen struct_field_val", .{}); - //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none }); + _ = ty_pl; + + return self.fail("TODO: airStructFieldVal", .{}); + + // return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none }); } fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void { @@ -1732,12 +1716,13 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier try self.register_manager.getReg(reg, null); try self.genSetReg(arg_ty, reg, arg_mcv); }, - .stack_offset => { - return self.fail("TODO implement calling with parameters in memory", .{}); - }, + .stack_offset => |off| try self.genSetStack(arg_ty, off, arg_mcv), .ptr_stack_offset => { return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{}); }, + .load_symbol => { + return self.fail("TODO implement calling with MCValue.load_symbol", .{}); + }, } } @@ -1747,7 +1732,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const sym_index = try elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, func.owner_decl); const sym = elf_file.symbol(sym_index); _ = try sym.getOrCreateZigGotEntry(sym_index, elf_file); - const got_addr: u32 = @intCast(sym.zigGotAddress(elf_file)); + const got_addr = sym.zigGotAddress(elf_file); try self.genSetReg(Type.usize, .ra, .{ .memory = got_addr }); _ = try self.addInst(.{ .tag = .jalr, @@ -1830,7 +1815,8 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { //return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } -fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { +fn airCmp(self: *Self, inst: Air.Inst.Index) !void { + const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)]; const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none }); @@ -1842,12 +1828,12 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - _ = op; - _ = lhs; - _ = rhs; + const lhs_ty = self.typeOf(bin_op.lhs); + const rhs_ty = self.typeOf(bin_op.rhs); - return self.fail("TODO implement cmp for {}", .{self.target.cpu.arch}); - // return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + const result = try self.binOp(tag, null, lhs, rhs, lhs_ty, rhs_ty); + + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void { @@ -1878,13 +1864,11 @@ fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { } fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void { - const mod = self.bin_file.comp.module.?; const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.DbgInlineBlock, ty_pl.payload); - const func = mod.funcInfo(extra.data.func); - // TODO emit debug info for function change - _ = func; - try self.lowerBlock(inst, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len])); + _ = extra; + // TODO: emit debug info for this block + return self.finishAir(inst, .dead, .{ .none, .none, .none }); } fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { @@ -1897,10 +1881,165 @@ fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void { } fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { - _ = inst; + const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; + const cond = try self.resolveInst(pl_op.operand); + const cond_ty = self.typeOf(pl_op.operand); + const extra = self.air.extraData(Air.CondBr, pl_op.payload); + const then_body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end..][0..extra.data.then_body_len]); + const else_body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const liveness_condbr = self.liveness.getCondBr(inst); - return self.fail("TODO implement condbr {}", .{self.target.cpu.arch}); - // return self.finishAir(inst, .unreach, .{ pl_op.operand, .none, .none }); + // A branch to the false section. Uses beq + const reloc = try self.condBr(cond_ty, cond); + + // If the condition dies here in this condbr instruction, process + // that death now instead of later as this has an effect on + // whether it needs to be spilled in the branches + if (self.liveness.operandDies(inst, 0)) { + if (pl_op.operand.toIndex()) |op_index| { + self.processDeath(op_index); + } + } + + // Save state + const parent_next_stack_offset = self.next_stack_offset; + const parent_free_registers = self.register_manager.free_registers; + var parent_stack = try self.stack.clone(self.gpa); + defer parent_stack.deinit(self.gpa); + const parent_registers = self.register_manager.registers; + + try self.branch_stack.append(.{}); + errdefer { + _ = self.branch_stack.pop(); + } + + try self.ensureProcessDeathCapacity(liveness_condbr.then_deaths.len); + for (liveness_condbr.then_deaths) |operand| { + self.processDeath(operand); + } + try self.genBody(then_body); + + // Revert to the previous register and stack allocation state. + + var saved_then_branch = self.branch_stack.pop(); + defer saved_then_branch.deinit(self.gpa); + + self.register_manager.registers = parent_registers; + + self.stack.deinit(self.gpa); + self.stack = parent_stack; + parent_stack = .{}; + + self.next_stack_offset = parent_next_stack_offset; + self.register_manager.free_registers = parent_free_registers; + + try self.performReloc(reloc); + const else_branch = self.branch_stack.addOneAssumeCapacity(); + else_branch.* = .{}; + + try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len); + for (liveness_condbr.else_deaths) |operand| { + self.processDeath(operand); + } + try self.genBody(else_body); + + // At this point, each branch will possibly have conflicting values for where + // each instruction is stored. They agree, however, on which instructions are alive/dead. + // We use the first ("then") branch as canonical, and here emit + // instructions into the second ("else") branch to make it conform. + // We continue respect the data structure semantic guarantees of the else_branch so + // that we can use all the code emitting abstractions. This is why at the bottom we + // assert that parent_branch.free_registers equals the saved_then_branch.free_registers + // rather than assigning it. + const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2]; + try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count()); + const else_slice = else_branch.inst_table.entries.slice(); + const else_keys = else_slice.items(.key); + const else_values = else_slice.items(.value); + for (else_keys, 0..) |else_key, else_idx| { + const else_value = else_values[else_idx]; + const canon_mcv = if (saved_then_branch.inst_table.fetchSwapRemove(else_key)) |then_entry| blk: { + // The instruction's MCValue is overridden in both branches. + log.debug("condBr put branch table (key = %{d}, value = {})", .{ else_key, then_entry.value }); + parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value); + if (else_value == .dead) { + assert(then_entry.value == .dead); + continue; + } + break :blk then_entry.value; + } else blk: { + if (else_value == .dead) + continue; + // The instruction is only overridden in the else branch. + var i: usize = self.branch_stack.items.len - 2; + while (true) { + i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead? + if (self.branch_stack.items[i].inst_table.get(else_key)) |mcv| { + assert(mcv != .dead); + break :blk mcv; + } + } + }; + log.debug("consolidating else_entry {d} {}=>{}", .{ else_key, else_value, canon_mcv }); + // TODO make sure the destination stack offset / register does not already have something + // going on there. + try self.setRegOrMem(self.typeOfIndex(else_key), canon_mcv, else_value); + // TODO track the new register / stack allocation + } + try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, saved_then_branch.inst_table.count()); + const then_slice = saved_then_branch.inst_table.entries.slice(); + const then_keys = then_slice.items(.key); + const then_values = then_slice.items(.value); + for (then_keys, 0..) |then_key, then_idx| { + const then_value = then_values[then_idx]; + // We already deleted the items from this table that matched the else_branch. + // So these are all instructions that are only overridden in the then branch. + parent_branch.inst_table.putAssumeCapacity(then_key, then_value); + if (then_value == .dead) + continue; + const parent_mcv = blk: { + var i: usize = self.branch_stack.items.len - 2; + while (true) { + i -= 1; + if (self.branch_stack.items[i].inst_table.get(then_key)) |mcv| { + assert(mcv != .dead); + break :blk mcv; + } + } + }; + log.debug("consolidating then_entry {d} {}=>{}", .{ then_key, parent_mcv, then_value }); + // TODO make sure the destination stack offset / register does not already have something + // going on there. + try self.setRegOrMem(self.typeOfIndex(then_key), parent_mcv, then_value); + // TODO track the new register / stack allocation + } + + { + var item = self.branch_stack.pop(); + item.deinit(self.gpa); + } + + return self.finishAir(inst, .unreach, .{ .none, .none, .none }); +} + +fn condBr(self: *Self, cond_ty: Type, condition: MCValue) !Mir.Inst.Index { + _ = cond_ty; + + const reg = switch (condition) { + .register => |r| r, + else => try self.copyToTmpRegister(Type.bool, condition), + }; + + return try self.addInst(.{ + .tag = .beq, + .data = .{ + .b_type = .{ + .rs1 = reg, + .rs2 = .zero, + .imm12 = 0, // patched later. + }, + }, + }); } fn isNull(self: *Self, operand: MCValue) !MCValue { @@ -2044,25 +2183,26 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const loop = self.air.extraData(Air.Block, ty_pl.payload); const body: []const Air.Inst.Index = @ptrCast(self.air.extra[loop.end..][0..loop.data.body_len]); - const start_index = self.code.items.len; + + const start_index: Mir.Inst.Index = @intCast(self.code.items.len); + try self.genBody(body); try self.jump(start_index); + return self.finishAirBookkeeping(); } /// Send control flow to the `index` of `self.code`. -fn jump(self: *Self, index: usize) !void { - _ = index; - return self.fail("TODO implement jump for {}", .{self.target.cpu.arch}); +fn jump(self: *Self, index: Mir.Inst.Index) !void { + _ = try self.addInst(.{ + .tag = .psuedo_jump, + .data = .{ + .inst = index, + }, + }); } fn airBlock(self: *Self, inst: Air.Inst.Index) !void { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Block, ty_pl.payload); - try self.lowerBlock(inst, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len])); -} - -fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) !void { try self.blocks.putNoClobber(self.gpa, inst, .{ // A block is a setup to be able to jump to the end. .relocs = .{}, @@ -2074,10 +2214,16 @@ fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) ! .mcv = MCValue{ .none = {} }, }); defer self.blocks.getPtr(inst).?.relocs.deinit(self.gpa); + + const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; + const extra = self.air.extraData(Air.Block, ty_pl.payload); + const body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]); // TODO emit debug info lexical block try self.genBody(body); - for (self.blocks.getPtr(inst).?.relocs.items) |reloc| try self.performReloc(reloc); + for (self.blocks.getPtr(inst).?.relocs.items) |reloc| { + try self.performReloc(reloc); + } const result = self.blocks.getPtr(inst).?.mcv; return self.finishAir(inst, result, .{ .none, .none, .none }); @@ -2091,11 +2237,12 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { // return self.finishAir(inst, .dead, .{ condition, .none, .none }); } -fn performReloc(self: *Self, reloc: Reloc) !void { - _ = self; - switch (reloc) { - .rel32 => unreachable, - .arm_branch => unreachable, +fn performReloc(self: *Self, inst: Mir.Inst.Index) !void { + const tag = self.mir_instructions.items(.tag)[inst]; + + switch (tag) { + .beq => self.mir_instructions.items(.data)[inst].b_type.imm12 = @intCast(inst), + else => return self.fail("TODO: performReloc {s}", .{@tagName(tag)}), } } @@ -2135,7 +2282,15 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { // Emit a jump with a relocation. It will be patched up after the block ends. try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); - return self.fail("TODO implement brvoid for {}", .{self.target.cpu.arch}); + block_data.relocs.appendAssumeCapacity(try self.addInst(.{ + .tag = .jal, + .data = .{ + .j_type = .{ + .rd = .ra, + .imm21 = undefined, // populated later through performReloc + }, + }, + })); } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { @@ -2261,28 +2416,138 @@ fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigT /// Sets the value without any modifications to register allocation metadata or stack allocation metadata. fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void { + if (!loc.isMutable()) { + return std.debug.panic("tried to setRegOrMem immutable: {s}", .{@tagName(loc)}); + } + switch (loc) { .none => return, .register => |reg| return self.genSetReg(ty, reg, val), .stack_offset => |off| return self.genSetStack(ty, off, val), - .memory => { - return self.fail("TODO implement setRegOrMem for memory", .{}); - }, - else => unreachable, + else => return self.fail("TODO: setRegOrMem {s}", .{@tagName(loc)}), } } fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { - _ = ty; - _ = stack_offset; - _ = mcv; - return self.fail("TODO implement getSetStack for {}", .{self.target.cpu.arch}); + const mod = self.bin_file.comp.module.?; + const abi_size: u32 = @intCast(ty.abiSize(mod)); + + switch (mcv) { + .none => return, + .dead => unreachable, + .immediate => { + const reg = try self.copyToTmpRegister(ty, mcv); + return self.genSetStack(ty, stack_offset, .{ .register = reg }); + }, + .register => |reg| { + switch (abi_size) { + 1, 2, 4, 8 => { + assert(std.mem.isAlignedGeneric(u32, stack_offset, abi_size)); + + const tag: Mir.Inst.Tag = switch (abi_size) { + 1 => .sb, + 2 => .sh, + 4 => .sw, + 8 => .sd, + else => unreachable, + }; + + _ = try self.addInst(.{ + .tag = tag, + .data = .{ .i_type = .{ + .rd = reg, + .rs1 = .sp, + .imm12 = @intCast(stack_offset), + } }, + }); + }, + else => return self.fail("TODO: genSetStack for size={d}", .{abi_size}), + } + }, + .stack_offset, .load_symbol => { + if (abi_size <= 8) { + const reg = try self.copyToTmpRegister(ty, mcv); + return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); + } + + const ptr_ty = try mod.singleMutPtrType(ty); + + // TODO call extern memcpy + const regs = try self.register_manager.allocRegs(5, .{ null, null, null, null, null }, gp); + const regs_locks = self.register_manager.lockRegsAssumeUnused(5, regs); + defer for (regs_locks) |reg| { + self.register_manager.unlockReg(reg); + }; + + const src_reg = regs[0]; + const dst_reg = regs[1]; + const len_reg = regs[2]; + const count_reg = regs[3]; + const tmp_reg = regs[4]; + + switch (mcv) { + .stack_offset => |offset| { + if (offset == stack_offset) return; + try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = offset }); + }, + .load_symbol => |sym_off| { + const atom_index = atom: { + const decl_index = mod.funcOwnerDeclIndex(self.func_index); + + if (self.bin_file.cast(link.File.Elf)) |elf_file| { + const atom_index = try elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index); + break :atom atom_index; + } else return self.fail("TODO genSetStack for {s}", .{@tagName(self.bin_file.tag)}); + }; + + _ = try self.addInst(.{ + .tag = .load_symbol, + .data = .{ + .payload = try self.addExtra(Mir.LoadSymbolPayload{ + .register = @intFromEnum(src_reg), + .atom_index = atom_index, + .sym_index = sym_off.sym, + }), + }, + }); + }, + else => return self.fail("TODO: genSetStack unreachable {s}", .{@tagName(mcv)}), + } + + try self.genSetReg(ptr_ty, dst_reg, .{ .ptr_stack_offset = stack_offset }); + try self.genSetReg(Type.usize, len_reg, .{ .immediate = abi_size }); + + // memcpy(src, dst, len) + try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg); + }, + else => return self.fail("TODO: genSetStack {s}", .{@tagName(mcv)}), + } +} + +fn genInlineMemcpy( + self: *Self, + src: Register, + dst: Register, + len: Register, + count: Register, + tmp: Register, +) !void { + _ = src; + _ = dst; + _ = len; + _ = count; + _ = tmp; + + return self.fail("TODO: genInlineMemcpy", .{}); } fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { + const mod = self.bin_file.comp.module.?; + const abi_size: u32 = @intCast(ty.abiSize(mod)); + switch (mcv) { .dead => unreachable, - .ptr_stack_offset => unreachable, + .ptr_stack_offset => return self.fail("TODO genSetReg ptr_stack_offset", .{}), .unreach, .none => return, // Nothing to do. .undef => { if (!self.wantSafety()) @@ -2343,8 +2608,6 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void }); }, .memory => |addr| { - // 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. try self.genSetReg(ty, reg, .{ .immediate = addr }); _ = try self.addInst(.{ @@ -2355,11 +2618,51 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .imm12 = 0, } }, }); - // LOAD imm=[i12 offset = 0], rs1 = - // return self.fail("TODO implement genSetReg memory for riscv64"); + // LOAD imm=[i12 offset = 0], rs1 + }, + .stack_offset => |off| { + const tag: Mir.Inst.Tag = switch (abi_size) { + 1 => .lb, + 2 => .lh, + 4 => .lw, + 8 => .ld, + else => return self.fail("TODO: genSetReg for size {d}", .{abi_size}), + }; + + _ = try self.addInst(.{ + .tag = tag, + .data = .{ .i_type = .{ + .rd = reg, + .rs1 = .sp, + .imm12 = @intCast(off), + } }, + }); + }, + .load_symbol => |sym_off| { + assert(sym_off.off == 0); + + const decl_index = mod.funcOwnerDeclIndex(self.func_index); + + const atom_index = switch (self.bin_file.tag) { + .elf => blk: { + const elf_file = self.bin_file.cast(link.File.Elf).?; + const atom_index = try elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index); + break :blk atom_index; + }, + else => return self.fail("TODO genSetReg load_symbol for {s}", .{@tagName(self.bin_file.tag)}), + }; + _ = try self.addInst(.{ + .tag = .load_symbol, + .data = .{ + .payload = try self.addExtra(Mir.LoadSymbolPayload{ + .register = @intFromEnum(reg), + .atom_index = atom_index, + .sym_index = sym_off.sym, + }), + }, + }); }, - else => return self.fail("TODO implement getSetReg for riscv64 {}", .{mcv}), } } @@ -2579,9 +2882,12 @@ fn genTypedValue(self: *Self, val: Value) InnerError!MCValue { .mcv => |mcv| switch (mcv) { .none => .none, .undef => .undef, - .load_got, .load_symbol, .load_direct, .load_tlv => unreachable, // TODO + .load_symbol => |sym_index| .{ .load_symbol = .{ .sym = sym_index } }, .immediate => |imm| .{ .immediate = imm }, .memory => |addr| .{ .memory = addr }, + .load_got, .load_direct, .load_tlv => { + return self.fail("TODO: genTypedValue {s}", .{@tagName(mcv)}); + }, }, .fail => |msg| { self.err_msg = msg; @@ -2634,41 +2940,17 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { // TODO make this generic with other ABIs, in particular // with different hardware floating-point calling // conventions - var next_register: usize = 0; - var next_stack_offset: u32 = 0; - // TODO: this is never assigned, which is a bug, but I don't know how this code works - // well enough to try and fix it. I *think* `next_register += next_stack_offset` is - // supposed to be `next_stack_offset += param_size` in every case where it appears. - _ = &next_stack_offset; - - const argument_registers = [_]Register{ .a0, .a1, .a2, .a3, .a4, .a5, .a6, .a7 }; + var stack_offset: u32 = 0; for (fn_info.param_types.get(ip), result.args) |ty, *result_arg| { - const param_size: u32 = @intCast(Type.fromInterned(ty).abiSize(mod)); - if (param_size <= 8) { - if (next_register < argument_registers.len) { - result_arg.* = .{ .register = argument_registers[next_register] }; - next_register += 1; - } else { - result_arg.* = .{ .stack_offset = next_stack_offset }; - next_register += next_stack_offset; - } - } else if (param_size <= 16) { - if (next_register < argument_registers.len - 1) { - return self.fail("TODO MCValues with 2 registers", .{}); - } else if (next_register < argument_registers.len) { - return self.fail("TODO MCValues split register + stack", .{}); - } else { - result_arg.* = .{ .stack_offset = next_stack_offset }; - next_register += next_stack_offset; - } - } else { - result_arg.* = .{ .stack_offset = next_stack_offset }; - next_register += next_stack_offset; - } + const param_type = Type.fromInterned(ty); + const param_size: u32 = @intCast(param_type.abiSize(mod)); + + result_arg.* = .{ .stack_offset = stack_offset }; + stack_offset += param_size; } - result.stack_byte_count = next_stack_offset; + result.stack_byte_count = stack_offset; result.stack_align = .@"16"; }, else => return self.fail("TODO implement function parameters for {} on riscv64", .{cc}), diff --git a/src/arch/riscv64/Emit.zig b/src/arch/riscv64/Emit.zig index f382f6f9eb..6e2b638b76 100644 --- a/src/arch/riscv64/Emit.zig +++ b/src/arch/riscv64/Emit.zig @@ -27,6 +27,8 @@ prev_di_column: u32, /// Relative to the beginning of `code`. prev_di_pc: usize, +const log = std.log.scoped(.emit); + const InnerError = error{ OutOfMemory, EmitFail, @@ -37,33 +39,57 @@ pub fn emitMir( ) InnerError!void { const mir_tags = emit.mir.instructions.items(.tag); + // TODO: compute branch offsets + // try emit.lowerMir(); + // Emit machine code for (mir_tags, 0..) |tag, index| { const inst = @as(u32, @intCast(index)); + log.debug("emitMir: {s}", .{@tagName(tag)}); switch (tag) { .add => try emit.mirRType(inst), .sub => try emit.mirRType(inst), + .cmp_eq => try emit.mirRType(inst), + .cmp_gt => try emit.mirRType(inst), + + .beq => try emit.mirBType(inst), + .bne => try emit.mirBType(inst), + .addi => try emit.mirIType(inst), .jalr => try emit.mirIType(inst), - .ld => try emit.mirIType(inst), - .sd => try emit.mirIType(inst), + + .jal => try emit.mirJType(inst), .ebreak => try emit.mirSystem(inst), .ecall => try emit.mirSystem(inst), .unimp => try emit.mirSystem(inst), .dbg_line => try emit.mirDbgLine(inst), - .dbg_prologue_end => try emit.mirDebugPrologueEnd(), .dbg_epilogue_begin => try emit.mirDebugEpilogueBegin(), + .psuedo_prologue => try emit.mirPsuedo(inst), + .psuedo_jump => try emit.mirPsuedo(inst), + .mv => try emit.mirRR(inst), .nop => try emit.mirNop(inst), .ret => try emit.mirNop(inst), .lui => try emit.mirUType(inst), + + .ld => try emit.mirIType(inst), + .sd => try emit.mirIType(inst), + .lw => try emit.mirIType(inst), + .sw => try emit.mirIType(inst), + .lh => try emit.mirIType(inst), + .sh => try emit.mirIType(inst), + .lb => try emit.mirIType(inst), + .sb => try emit.mirIType(inst), + .ldr_ptr_stack => try emit.mirIType(inst), + + .load_symbol => try emit.mirLoadSymbol(inst), } } } @@ -86,15 +112,19 @@ fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { return error.EmitFail; } -fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void { - const delta_line = @as(i32, @intCast(line)) - @as(i32, @intCast(self.prev_di_line)); - const delta_pc: usize = self.code.items.len - self.prev_di_pc; - switch (self.debug_output) { +fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) !void { + log.debug("Line: {} {}\n", .{ line, emit.prev_di_line }); + const delta_line = @as(i32, @intCast(line)) - @as(i32, @intCast(emit.prev_di_line)); + const delta_pc: usize = emit.code.items.len - emit.prev_di_pc; + log.debug("(advance pc={d} and line={d})", .{ delta_pc, delta_line }); + switch (emit.debug_output) { .dwarf => |dw| { + if (column != emit.prev_di_column) try dw.setColumn(column); + if (delta_line == 0) return; // TODO: remove this try dw.advancePCAndLine(delta_line, delta_pc); - self.prev_di_line = line; - self.prev_di_column = column; - self.prev_di_pc = self.code.items.len; + emit.prev_di_line = line; + emit.prev_di_column = column; + emit.prev_di_pc = emit.code.items.len; }, .plan9 => |dbg_out| { if (delta_pc <= 0) return; // only do this when the pc changes @@ -113,12 +143,12 @@ fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void { // we don't need to do anything, because adding the pc quanta does it for us } else unreachable; if (dbg_out.start_line == null) - dbg_out.start_line = self.prev_di_line; + dbg_out.start_line = emit.prev_di_line; dbg_out.end_line = line; // only do this if the pc changed - self.prev_di_line = line; - self.prev_di_column = column; - self.prev_di_pc = self.code.items.len; + emit.prev_di_line = line; + emit.prev_di_column = column; + emit.prev_di_pc = emit.code.items.len; }, .none => {}, } @@ -131,6 +161,19 @@ fn mirRType(emit: *Emit, inst: Mir.Inst.Index) !void { switch (tag) { .add => try emit.writeInstruction(Instruction.add(r_type.rd, r_type.rs1, r_type.rs2)), .sub => try emit.writeInstruction(Instruction.sub(r_type.rd, r_type.rs1, r_type.rs2)), + .cmp_eq => try emit.writeInstruction(Instruction.slt(r_type.rd, r_type.rs1, r_type.rs2)), + else => unreachable, + } +} + +fn mirBType(emit: *Emit, inst: Mir.Inst.Index) !void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const b_type = emit.mir.instructions.items(.data)[inst].b_type; + + // const inst = b_type.imm12; + + switch (tag) { + .beq => try emit.writeInstruction(Instruction.beq(b_type.rs1, b_type.rs2, b_type.imm12)), else => unreachable, } } @@ -142,8 +185,30 @@ fn mirIType(emit: *Emit, inst: Mir.Inst.Index) !void { switch (tag) { .addi => try emit.writeInstruction(Instruction.addi(i_type.rd, i_type.rs1, i_type.imm12)), .jalr => try emit.writeInstruction(Instruction.jalr(i_type.rd, i_type.imm12, i_type.rs1)), + .ld => try emit.writeInstruction(Instruction.ld(i_type.rd, i_type.imm12, i_type.rs1)), .sd => try emit.writeInstruction(Instruction.sd(i_type.rd, i_type.imm12, i_type.rs1)), + .lw => try emit.writeInstruction(Instruction.lw(i_type.rd, i_type.imm12, i_type.rs1)), + .sw => try emit.writeInstruction(Instruction.sw(i_type.rd, i_type.imm12, i_type.rs1)), + .lh => try emit.writeInstruction(Instruction.lh(i_type.rd, i_type.imm12, i_type.rs1)), + .sh => try emit.writeInstruction(Instruction.sh(i_type.rd, i_type.imm12, i_type.rs1)), + .lb => try emit.writeInstruction(Instruction.lb(i_type.rd, i_type.imm12, i_type.rs1)), + .sb => try emit.writeInstruction(Instruction.sb(i_type.rd, i_type.imm12, i_type.rs1)), + + .ldr_ptr_stack => try emit.writeInstruction(Instruction.add(i_type.rd, i_type.rs1, .sp)), + + else => unreachable, + } +} + +fn mirJType(emit: *Emit, inst: Mir.Inst.Index) !void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const j_type = emit.mir.instructions.items(.data)[inst].j_type; + + switch (tag) { + .jal => { + try emit.writeInstruction(Instruction.jal(j_type.rd, j_type.imm21)); + }, else => unreachable, } } @@ -169,28 +234,55 @@ fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void { } } -fn mirDebugPrologueEnd(self: *Emit) !void { - switch (self.debug_output) { +fn mirDebugPrologueEnd(emit: *Emit) !void { + switch (emit.debug_output) { .dwarf => |dw| { try dw.setPrologueEnd(); - try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column); + try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column); }, .plan9 => {}, .none => {}, } } -fn mirDebugEpilogueBegin(self: *Emit) !void { - switch (self.debug_output) { +fn mirDebugEpilogueBegin(emit: *Emit) !void { + switch (emit.debug_output) { .dwarf => |dw| { try dw.setEpilogueBegin(); - try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column); + try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column); }, .plan9 => {}, .none => {}, } } +fn mirPsuedo(emit: *Emit, inst: Mir.Inst.Index) !void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const data = emit.mir.instructions.items(.data)[inst]; + + switch (tag) { + .psuedo_prologue => { + const imm12 = data.imm12; + const stack_size: i12 = @max(32, imm12); + + try emit.writeInstruction(Instruction.addi(.sp, .sp, -stack_size)); + try emit.writeInstruction(Instruction.sd(.ra, stack_size - 8, .sp)); + try emit.writeInstruction(Instruction.sd(.s0, stack_size - 16, .sp)); + try emit.writeInstruction(Instruction.addi(.s0, .sp, stack_size)); + }, + + .psuedo_jump => { + const target = data.inst; + const offset: i12 = @intCast(emit.code.items.len); + _ = target; + + try emit.writeInstruction(Instruction.jal(.s0, offset)); + }, + + else => unreachable, + } +} + fn mirRR(emit: *Emit, inst: Mir.Inst.Index) !void { const tag = emit.mir.instructions.items(.tag)[inst]; const rr = emit.mir.instructions.items(.data)[inst].rr; @@ -200,6 +292,7 @@ fn mirRR(emit: *Emit, inst: Mir.Inst.Index) !void { else => unreachable, } } + fn mirUType(emit: *Emit, inst: Mir.Inst.Index) !void { const tag = emit.mir.instructions.items(.tag)[inst]; const u_type = emit.mir.instructions.items(.data)[inst].u_type; @@ -219,3 +312,63 @@ fn mirNop(emit: *Emit, inst: Mir.Inst.Index) !void { else => unreachable, } } + +fn mirLoadSymbol(emit: *Emit, inst: Mir.Inst.Index) !void { + // const tag = emit.mir.instructions.items(.tag)[inst]; + const payload = emit.mir.instructions.items(.data)[inst].payload; + const data = emit.mir.extraData(Mir.LoadSymbolPayload, payload).data; + const reg = @as(Register, @enumFromInt(data.register)); + + const end_offset = @as(u32, @intCast(emit.code.items.len)); + try emit.writeInstruction(Instruction.lui(reg, 0)); + try emit.writeInstruction(Instruction.lw(reg, 0, reg)); + + switch (emit.bin_file.tag) { + .elf => { + const elf_file = emit.bin_file.cast(link.File.Elf).?; + const atom_ptr = elf_file.symbol(data.atom_index).atom(elf_file).?; + + const hi_r_type = @intFromEnum(std.elf.R_RISCV.HI20); + + try atom_ptr.addReloc(elf_file, .{ + .r_offset = end_offset, + .r_info = (@as(u64, @intCast(data.sym_index)) << 32) | hi_r_type, + .r_addend = 0, + }); + + const lo_r_type = @intFromEnum(std.elf.R_RISCV.LO12_I); + + try atom_ptr.addReloc(elf_file, .{ + .r_offset = end_offset + 4, + .r_info = (@as(u64, @intCast(data.sym_index)) << 32) | lo_r_type, + .r_addend = 0, + }); + }, + else => unreachable, + } +} + +fn isBranch(tag: Mir.Inst.Tag) bool { + switch (tag) { + .psuedo_jump => true, + else => false, + } +} + +fn lowerMir(emit: *Emit) !void { + const comp = emit.bin_file.comp; + const gpa = comp.gpa; + const mir_tags = emit.mir.instructions.items(.tag); + + _ = gpa; + + for (mir_tags, 0..) |tag, index| { + const inst: u32 = @intCast(index); + + if (isBranch(tag)) { + const target_inst = emit.mir.instructions.items(.data)[inst].inst; + + _ = target_inst; + } + } +} diff --git a/src/arch/riscv64/Mir.zig b/src/arch/riscv64/Mir.zig index 9fe29a7ecd..738012022c 100644 --- a/src/arch/riscv64/Mir.zig +++ b/src/arch/riscv64/Mir.zig @@ -24,25 +24,72 @@ pub const Inst = struct { data: Data, pub const Tag = enum(u16) { - add, addi, + jalr, + lui, + mv, + + unimp, + ebreak, + ecall, + + /// Addition + add, + /// Subtraction + sub, + + jal, + + // TODO: Maybe create a special data for compares that includes the ops + /// Compare equal, uses r_type + cmp_eq, + /// Compare greater than, uses r_type + cmp_gt, + + /// Branch if equal Uses b_type + beq, + /// Branch if not eql Uses b_type + bne, + + nop, + ret, + + /// Load double (64 bits) + ld, + /// Store double (64 bits) + sd, + /// Load word (32 bits) + lw, + /// Store word (32 bits) + sw, + /// Load half (16 bits) + lh, + /// Store half (16 bits) + sh, + /// Load byte (8 bits) + lb, + /// Store byte (8 bits) + sb, + /// Pseudo-instruction: End of prologue dbg_prologue_end, /// Pseudo-instruction: Beginning of epilogue dbg_epilogue_begin, /// Pseudo-instruction: Update debug line dbg_line, - unimp, - ebreak, - ecall, - jalr, - ld, - lui, - mv, - nop, - ret, - sd, - sub, + + /// Psuedo-instruction that will generate a backpatched + /// function prologue. + psuedo_prologue, + /// Jumps. Uses `inst` payload. + psuedo_jump, + + // TODO: add description + load_symbol, + + // TODO: add description + // this is bad, remove this + ldr_ptr_stack, }; /// The position of an MIR instruction within the `Mir` instructions array. @@ -63,7 +110,11 @@ pub const Inst = struct { /// A 16-bit immediate value. /// /// Used by e.g. svc - imm16: u16, + imm16: i16, + /// A 12-bit immediate value. + /// + /// Used by e.g. psuedo_prologue + imm12: i12, /// Index into `extra`. Meaning of what can be found there is context-dependent. /// /// Used by e.g. load_memory @@ -95,6 +146,21 @@ pub const Inst = struct { rs1: Register, rs2: Register, }, + /// B-Type + /// + /// Used by e.g. beq + b_type: struct { + rs1: Register, + rs2: Register, + imm12: i13, + }, + /// J-Type + /// + /// Used by e.g. jal + j_type: struct { + rd: Register, + imm21: i21, + }, /// U-Type /// /// Used by e.g. lui @@ -111,10 +177,19 @@ pub const Inst = struct { }, }; + const CompareOp = enum { + eq, + neq, + gt, + gte, + lt, + lte, + }; + // Make sure we don't accidentally make instructions bigger than expected. - // Note that in safety builds, Zig is allowed to insert a secret field for safety checks. + // Note that in Debug builds, Zig is allowed to insert a secret field for safety checks. // comptime { - // if (!std.debug.runtime_safety) { + // if (builtin.mode != .Debug) { // assert(@sizeOf(Inst) == 8); // } // } @@ -145,3 +220,9 @@ pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end .end = i, }; } + +pub const LoadSymbolPayload = struct { + register: u32, + atom_index: u32, + sym_index: u32, +};