From 2be3033acda53389cac9f3e9a8ca0a3d41348eef Mon Sep 17 00:00:00 2001 From: David Rubin Date: Thu, 14 Mar 2024 02:44:24 -0700 Subject: [PATCH] riscv: implement basic branching we use a code offset map in Emit.zig to pre-compute what byte offset each MIR instruction is at. this is important because they can be of different size --- lib/std/builtin.zig | 8 +-- src/arch/riscv64/CodeGen.zig | 51 +++++++++++-------- src/arch/riscv64/Emit.zig | 98 +++++++++++++++++++++++++++++++----- src/arch/riscv64/Mir.zig | 4 +- 4 files changed, 119 insertions(+), 42 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index b28e17e38b..3fb9494305 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -759,11 +759,6 @@ 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) { - 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 @@ -772,7 +767,8 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr 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_sparc64 or - builtin.zig_backend == .stage2_spirv64) + builtin.zig_backend == .stage2_spirv64 or + builtin.zig_backend == .stage2_riscv64) { while (true) { @breakpoint(); diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index e7b2506a01..7e6ad49c67 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -301,6 +301,7 @@ pub fn generate( .prev_di_line = func.lbrace_line, .prev_di_column = func.lbrace_column, .stack_size = @max(32, function.max_end_stack), + .code_offset_mapping = .{}, }; defer emit.deinit(); @@ -929,21 +930,16 @@ fn binOpRegister( .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 => return self.fail("TODO: binOpRegister {s}", .{@tagName(tag)}), - }; _ = try self.addInst(.{ .tag = mir_tag, - .data = mir_data, + .data = .{ + .r_type = .{ + .rd = dest_reg, + .rs1 = lhs_reg, + .rs2 = rhs_reg, + }, + }, }); return MCValue{ .register = dest_reg }; @@ -1636,7 +1632,7 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { const dst_mcv = switch (src_mcv) { .register => |src_reg| dst: { - self.register_manager.getRegAssumeFree(src_reg, inst); + try self.register_manager.getReg(src_reg, inst); break :dst src_mcv; }, else => return self.fail("TODO: airArg {s}", .{@tagName(src_mcv)}), @@ -1914,6 +1910,8 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { self.processDeath(operand); } try self.genBody(then_body); + // point at the to-be-generated else case + try self.performReloc(reloc, @intCast(self.mir_instructions.len)); // Revert to the previous register and stack allocation state. @@ -1929,7 +1927,6 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { 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.* = .{}; @@ -2014,8 +2011,6 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { 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 { @@ -2027,12 +2022,12 @@ fn condBr(self: *Self, cond_ty: Type, condition: MCValue) !Mir.Inst.Index { }; return try self.addInst(.{ - .tag = .beq, + .tag = .bne, .data = .{ .b_type = .{ .rs1 = reg, .rs2 = .zero, - .imm12 = 0, // patched later. + .inst = undefined, }, }, }); @@ -2218,7 +2213,13 @@ fn airBlock(self: *Self, inst: Air.Inst.Index) !void { try self.genBody(body); for (self.blocks.getPtr(inst).?.relocs.items) |reloc| { - try self.performReloc(reloc); + // here we are relocing to point at the instruction after the block. + // [then case] + // [jump to end] // this is reloced + // [else case] + // [jump to end] // this is reloced + // [this isn't generated yet] // point to here + try self.performReloc(reloc, @intCast(self.mir_instructions.len)); } const result = self.blocks.getPtr(inst).?.mcv; @@ -2233,11 +2234,14 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { // return self.finishAir(inst, .dead, .{ condition, .none, .none }); } -fn performReloc(self: *Self, inst: Mir.Inst.Index) !void { +fn performReloc(self: *Self, inst: Mir.Inst.Index, target: 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), + .bne, + .beq, + => self.mir_instructions.items(.data)[inst].b_type.inst = target, + .jal => self.mir_instructions.items(.data)[inst].j_type.inst = target, else => return self.fail("TODO: performReloc {s}", .{@tagName(tag)}), } } @@ -2283,7 +2287,7 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { .data = .{ .j_type = .{ .rd = .ra, - .imm21 = undefined, // populated later through performReloc + .inst = undefined, }, }, })); @@ -2467,6 +2471,9 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, src_val: MCValue) Inner } }, .stack_offset, .load_symbol => { + if (true) + return self.fail("TODO: genSetStack {s}", .{@tagName(src_val)}); + if (abi_size <= 8) { const reg = try self.copyToTmpRegister(ty, src_val); return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); diff --git a/src/arch/riscv64/Emit.zig b/src/arch/riscv64/Emit.zig index cfd61f8189..a40d7ba03d 100644 --- a/src/arch/riscv64/Emit.zig +++ b/src/arch/riscv64/Emit.zig @@ -26,8 +26,14 @@ prev_di_line: u32, prev_di_column: u32, /// Relative to the beginning of `code`. prev_di_pc: usize, - +/// Function's stack size. Used for backpatching. stack_size: u32, +/// For backward branches: stores the code offset of the target +/// instruction +/// +/// For forward branches: stores the code offset of the branch +/// instruction +code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{}, const log = std.log.scoped(.emit); @@ -100,6 +106,10 @@ pub fn emitMir( } pub fn deinit(emit: *Emit) void { + const comp = emit.bin_file.comp; + const gpa = comp.gpa; + + emit.code_offset_mapping.deinit(gpa); emit.* = undefined; } @@ -118,10 +128,8 @@ fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { } 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); @@ -166,7 +174,7 @@ 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)), + .cmp_gt => try emit.writeInstruction(Instruction.slt(r_type.rd, r_type.rs1, r_type.rs2)), else => unreachable, } } @@ -175,8 +183,17 @@ 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 offset = @as(i64, @intCast(emit.code_offset_mapping.get(b_type.inst).?)) - @as(i64, @intCast(emit.code.items.len)); + switch (tag) { - .beq => try emit.writeInstruction(Instruction.beq(b_type.rs1, b_type.rs2, b_type.imm12)), + .beq => { + log.debug("beq: {} offset={}", .{ inst, offset }); + try emit.writeInstruction(Instruction.beq(b_type.rs1, b_type.rs2, @intCast(offset))); + }, + .bne => { + log.debug("bne: {} offset={}", .{ inst, offset }); + try emit.writeInstruction(Instruction.bne(b_type.rs1, b_type.rs2, @intCast(offset))); + }, else => unreachable, } } @@ -215,9 +232,12 @@ 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; + const offset = @as(i64, @intCast(emit.code_offset_mapping.get(j_type.inst).?)) - @as(i64, @intCast(emit.code.items.len)); + switch (tag) { .jal => { - try emit.writeInstruction(Instruction.jal(j_type.rd, j_type.imm21)); + log.debug("jal: {} offset={}", .{ inst, offset }); + try emit.writeInstruction(Instruction.jal(j_type.rd, @intCast(offset))); }, else => unreachable, } @@ -304,12 +324,8 @@ fn mirPsuedo(emit: *Emit, inst: Mir.Inst.Index) !void { }, .j => { - const target = data.inst; - const offset: i12 = @intCast(emit.code.items.len); - _ = target; - - try emit.writeInstruction(Instruction.jal(.s0, offset)); - unreachable; // TODO: mirPsuedo j + const offset = @as(i64, @intCast(emit.code_offset_mapping.get(data.inst).?)) - @as(i64, @intCast(emit.code.items.len)); + try emit.writeInstruction(Instruction.jal(.s0, @intCast(offset))); }, else => unreachable, @@ -401,7 +417,51 @@ fn isLoad(tag: Mir.Inst.Tag) bool { }; } +pub fn isBranch(tag: Mir.Inst.Tag) bool { + return switch (tag) { + .beq => true, + .bne => true, + .jal => true, + .j => true, + else => false, + }; +} + +pub fn branchTarget(emit: *Emit, inst: Mir.Inst.Index) Mir.Inst.Index { + const tag = emit.mir.instructions.items(.tag)[inst]; + const data = emit.mir.instructions.items(.data)[inst]; + + switch (tag) { + .bne, + .beq, + => return data.b_type.inst, + .jal => return data.j_type.inst, + .j => return data.inst, + else => std.debug.panic("branchTarget {s}", .{@tagName(tag)}), + } +} + +fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize { + const tag = emit.mir.instructions.items(.tag)[inst]; + + return switch (tag) { + .dbg_line, + .dbg_epilogue_begin, + .dbg_prologue_end, + => 0, + + .psuedo_epilogue => 12, // 3 * 4 + .psuedo_prologue => 16, // 4 * 4 + + .abs => 12, // 3 * 4 + + else => 4, + }; +} + fn lowerMir(emit: *Emit) !void { + const comp = emit.bin_file.comp; + const gpa = comp.gpa; const mir_tags = emit.mir.instructions.items(.tag); const mir_datas = emit.mir.instructions.items(.data); @@ -419,5 +479,19 @@ fn lowerMir(emit: *Emit) !void { mir_datas[inst].i_type.imm12 = -(casted_size - 12 - offset); } } + + if (isBranch(tag)) { + const target_inst = emit.branchTarget(inst); + try emit.code_offset_mapping.put(gpa, target_inst, 0); + } + } + var current_code_offset: usize = 0; + + for (0..mir_tags.len) |index| { + const inst = @as(u32, @intCast(index)); + if (emit.code_offset_mapping.getPtr(inst)) |offset| { + offset.* = current_code_offset; + } + current_code_offset += emit.instructionSize(inst); } } diff --git a/src/arch/riscv64/Mir.zig b/src/arch/riscv64/Mir.zig index 38d7fe59f0..e64ba0c755 100644 --- a/src/arch/riscv64/Mir.zig +++ b/src/arch/riscv64/Mir.zig @@ -158,14 +158,14 @@ pub const Inst = struct { b_type: struct { rs1: Register, rs2: Register, - imm12: i13, + inst: Inst.Index, }, /// J-Type /// /// Used by e.g. jal j_type: struct { rd: Register, - imm21: i21, + inst: Inst.Index, }, /// U-Type ///