From 42f4bd34216ae1ae03df0a56502919109e030136 Mon Sep 17 00:00:00 2001 From: Koakuma Date: Mon, 4 Apr 2022 21:28:55 +0700 Subject: [PATCH] stage2: sparcv9: Add breakpoint, ret, and calling mechanism --- src/arch/sparcv9/CodeGen.zig | 441 +++++++++++++++++++++++++++-------- src/arch/sparcv9/Emit.zig | 13 ++ src/arch/sparcv9/Mir.zig | 85 ++++++- 3 files changed, 436 insertions(+), 103 deletions(-) diff --git a/src/arch/sparcv9/CodeGen.zig b/src/arch/sparcv9/CodeGen.zig index 40d6db176f..7d0a178f9b 100644 --- a/src/arch/sparcv9/CodeGen.zig +++ b/src/arch/sparcv9/CodeGen.zig @@ -453,7 +453,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .bitcast => @panic("TODO try self.airBitCast(inst)"), .block => try self.airBlock(inst), .br => @panic("TODO try self.airBr(inst)"), - .breakpoint => @panic("TODO try self.airBreakpoint()"), + .breakpoint => try self.airBreakpoint(), .ret_addr => @panic("TODO try self.airRetAddr(inst)"), .frame_addr => @panic("TODO try self.airFrameAddress(inst)"), .fence => @panic("TODO try self.airFence()"), @@ -476,7 +476,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .loop => @panic("TODO try self.airLoop(inst)"), .not => @panic("TODO try self.airNot(inst)"), .ptrtoint => @panic("TODO try self.airPtrToInt(inst)"), - .ret => @panic("TODO try self.airRet(inst)"), + .ret => try self.airRet(inst), .ret_load => try self.airRetLoad(inst), .store => try self.airStore(inst), .struct_field_ptr=> @panic("TODO try self.airStructFieldPtr(inst)"), @@ -667,6 +667,21 @@ fn airBlock(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ .none, .none, .none }); } +fn airBreakpoint(self: *Self) !void { + // ta 0x01 + _ = try self.addInst(.{ + .tag = .tcc, + .data = .{ + .trap = .{ + .is_imm = true, + .cond = 0b1000, // TODO need to look into changing this into an enum + .rs2_or_imm = .{ .imm = 0x01 }, + }, + }, + }); + return self.finishAirBookkeeping(); +} + fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.Modifier) !void { if (modifier == .always_tail) return self.fail("TODO implement tail calls for {}", .{self.target.cpu.arch}); @@ -695,10 +710,6 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. .unreach => unreachable, .dead => unreachable, .memory => unreachable, - .compare_flags_signed => unreachable, - .compare_flags_unsigned => unreachable, - .got_load => unreachable, - .direct_load => unreachable, .register => |reg| { try self.register_manager.getReg(reg, null); try self.genSetReg(arg_ty, reg, arg_mcv); @@ -712,6 +723,44 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } } + // Due to incremental compilation, how function calls are generated depends + // on linking. + if (self.air.value(callee)) |func_value| { + if (self.bin_file.tag == link.File.Elf.base_tag) { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; + const ptr_bits = self.target.cpu.arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; + break :blk @intCast(u32, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * ptr_bytes); + } else unreachable; + + try self.genSetReg(Type.initTag(.usize), .o7, .{ .memory = got_addr }); + + _ = try self.addInst(.{ + .tag = .jmpl, + .data = .{ .branch_link_indirect = .{ .reg = .o7 } }, + }); + } else if (func_value.castTag(.extern_fn)) |_| { + return self.fail("TODO implement calling extern functions", .{}); + } else { + return self.fail("TODO implement calling bitcasted functions", .{}); + } + } else @panic("TODO SPARCv9 currently does not support non-ELF binaries"); + } else { + assert(ty.zigTypeTag() == .Pointer); + const mcv = try self.resolveInst(callee); + try self.genSetReg(ty, .o7, mcv); + + _ = try self.addInst(.{ + .tag = .jmpl, + .data = .{ .branch_link_indirect = .{ .reg = .o7 } }, + }); + } + + // TODO handle return value + return self.fail("TODO implement call for {}", .{self.target.cpu.arch}); } @@ -759,8 +808,17 @@ fn airDiv(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } +fn airRet(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + try self.ret(operand); + return self.finishAir(inst, .dead, .{ un_op, .none, .none }); +} + fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { - _ = inst; + const un_op = self.air.instructions.items(.data)[inst].un_op; + const ptr = try self.resolveInst(un_op); + _ = ptr; return self.fail("TODO implement airRetLoad for {}", .{self.target.cpu.arch}); //return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -832,6 +890,37 @@ fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 { return self.allocMem(inst, abi_size, abi_align); } +fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue { + const elem_ty = self.air.typeOfIndex(inst); + const target = self.target.*; + const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { + return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(target)}); + }; + const abi_align = elem_ty.abiAlignment(self.target.*); + if (abi_align > self.stack_align) + self.stack_align = abi_align; + + if (reg_ok) { + // Make sure the type can fit in a register before we try to allocate one. + if (abi_size <= 8) { + if (self.register_manager.tryAllocReg(inst)) |reg| { + return MCValue{ .register = reg }; + } + } + } + const stack_offset = try self.allocMem(inst, abi_size, abi_align); + return MCValue{ .stack_offset = stack_offset }; +} + +/// Copies a value to a register without tracking the register. The register is not considered +/// allocated. A second call to `copyToTmpRegister` may return the same register. +/// This can have a side effect of spilling instructions to the stack to free up a register. +fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register { + const reg = try self.register_manager.allocReg(null); + try self.genSetReg(ty, reg, mcv); + return reg; +} + fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void { const table = &self.branch_stack.items[self.branch_stack.items.len - 1].inst_table; try table.ensureUnusedCapacity(self.gpa, additional_count); @@ -885,37 +974,216 @@ fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Live self.finishAirBookkeeping(); } +fn genLoad(self: *Self, value_reg: Register, addr_reg: Register, off: i13, abi_size: u64) !void { + _ = value_reg; + _ = addr_reg; + _ = off; + + switch (abi_size) { + 1, 2, 4, 8 => return self.fail("TODO: A.27 Load Integer", .{}), + 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}), + else => unreachable, + } +} + +fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { + switch (mcv) { + .dead => unreachable, + .unreach, .none => return, // Nothing to do. + .undef => { + if (!self.wantSafety()) + return; // The already existing value will do just fine. + // Write the debug undefined value. + return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }); + }, + .ptr_stack_offset => |off| { + const simm13 = math.cast(u12, off) catch + return self.fail("TODO larger stack offsets", .{}); + + _ = try self.addInst(.{ + .tag = .add, + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = reg, + .rs1 = .sp, + .rs2_or_imm = .{ .imm = simm13 }, + }, + }, + }); + }, + .immediate => |x| { + if (x <= math.maxInt(u12)) { + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = reg, + .rs1 = .g0, + .rs2_or_imm = .{ .imm = @truncate(u12, x) }, + }, + }, + }); + } else if (x <= math.maxInt(u32)) { + _ = try self.addInst(.{ + .tag = .sethi, + .data = .{ + .sethi = .{ + .rd = reg, + .imm = @truncate(u22, x >> 10), + }, + }, + }); + + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .imm = @truncate(u10, x) }, + }, + }, + }); + } else if (x <= math.maxInt(u44)) { + try self.genSetReg(ty, reg, .{ .immediate = @truncate(u32, x >> 12) }); + + _ = try self.addInst(.{ + .tag = .sllx, + .data = .{ + .shift = .{ + .is_imm = true, + .width = .shift64, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .imm = 12 }, + }, + }, + }); + + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = true, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .imm = @truncate(u12, x) }, + }, + }, + }); + } else { + // Need to allocate a temporary register to load 64-bit immediates. + const tmp_reg = try self.register_manager.allocReg(null); + + try self.genSetReg(ty, tmp_reg, .{ .immediate = @truncate(u32, x) }); + try self.genSetReg(ty, reg, .{ .immediate = @truncate(u32, x >> 32) }); + + _ = try self.addInst(.{ + .tag = .sllx, + .data = .{ + .shift = .{ + .is_imm = true, + .width = .shift64, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .imm = 32 }, + }, + }, + }); + + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = false, + .rd = reg, + .rs1 = reg, + .rs2_or_imm = .{ .rs2 = tmp_reg }, + }, + }, + }); + } + }, + .register => |src_reg| { + // If the registers are the same, nothing to do. + if (src_reg.id() == reg.id()) + return; + + // or %g0, src, dst (aka mov src, dst) + _ = try self.addInst(.{ + .tag = .@"or", + .data = .{ + .arithmetic_3op = .{ + .is_imm = false, + .rd = reg, + .rs1 = .g0, + .rs2_or_imm = .{ .rs2 = src_reg }, + }, + }, + }); + }, + .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.genLoad(reg, reg, 0, ty.abiSize(self.target.*)); + }, + .stack_offset => |off| { + const simm13 = math.cast(u12, off) catch + return self.fail("TODO larger stack offsets", .{}); + try self.genLoad(reg, .sp, simm13, ty.abiSize(self.target.*)); + }, + } +} + +fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { + const abi_size = ty.abiSize(self.target.*); + switch (mcv) { + .dead => unreachable, + .unreach, .none => return, // Nothing to do. + .undef => { + if (!self.wantSafety()) + return; // The already existing value will do just fine. + // TODO Upgrade this to a memset call when we have that available. + switch (ty.abiSize(self.target.*)) { + 1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }), + 2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }), + 4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }), + 8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), + else => return self.fail("TODO implement memset", .{}), + } + }, + .immediate, + .ptr_stack_offset, + => { + const reg = try self.copyToTmpRegister(ty, mcv); + return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); + }, + .register => return self.fail("TODO implement storing types abi_size={}", .{abi_size}), + .memory, .stack_offset => return self.fail("TODO implement memcpy", .{}), + } +} + fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { if (typed_value.val.isUndef()) return MCValue{ .undef = {} }; if (typed_value.val.castTag(.decl_ref)) |payload| { - return self.lowerDeclRef(typed_value, payload.data); + _ = payload; + return self.fail("TODO implement lowerDeclRef", .{}); + // return self.lowerDeclRef(typed_value, payload.data); } if (typed_value.val.castTag(.decl_ref_mut)) |payload| { - return self.lowerDeclRef(typed_value, payload.data.decl); + _ = payload; + return self.fail("TODO implement lowerDeclRef", .{}); + // return self.lowerDeclRef(typed_value, payload.data.decl); } const target = self.target.*; switch (typed_value.ty.zigTypeTag()) { - .Pointer => switch (typed_value.ty.ptrSize()) { - .Slice => { - return self.lowerUnnamedConst(typed_value); - }, - else => { - switch (typed_value.val.tag()) { - .int_u64 => { - return MCValue{ .immediate = typed_value.val.toUnsignedInt(target) }; - }, - .slice => { - return self.lowerUnnamedConst(typed_value); - }, - else => { - return self.fail("TODO codegen more kinds of const pointers: {}", .{typed_value.val.tag()}); - }, - } - }, - }, .Int => { const info = typed_value.ty.intInfo(self.target.*); if (info.bits <= 64) { @@ -929,83 +1197,11 @@ fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { return MCValue{ .immediate = unsigned }; } else { - return self.lowerUnnamedConst(typed_value); + return self.fail("TODO implement int genTypedValue of > 64 bits", .{}); } }, - .Bool => { - return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) }; - }, .ComptimeInt => unreachable, // semantic analysis prevents this .ComptimeFloat => unreachable, // semantic analysis prevents this - .Optional => { - if (typed_value.ty.isPtrLikeOptional()) { - if (typed_value.val.isNull()) - return MCValue{ .immediate = 0 }; - - var buf: Type.Payload.ElemType = undefined; - return self.genTypedValue(.{ - .ty = typed_value.ty.optionalChild(&buf), - .val = typed_value.val, - }); - } else if (typed_value.ty.abiSize(self.target.*) == 1) { - return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) }; - } - return self.fail("TODO non pointer optionals", .{}); - }, - .Enum => { - if (typed_value.val.castTag(.enum_field_index)) |field_index| { - switch (typed_value.ty.tag()) { - .enum_simple => { - return MCValue{ .immediate = field_index.data }; - }, - .enum_full, .enum_nonexhaustive => { - const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data; - if (enum_full.values.count() != 0) { - const tag_val = enum_full.values.keys()[field_index.data]; - return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val }); - } else { - return MCValue{ .immediate = field_index.data }; - } - }, - else => unreachable, - } - } else { - var int_tag_buffer: Type.Payload.Bits = undefined; - const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer); - return self.genTypedValue(.{ .ty = int_tag_ty, .val = typed_value.val }); - } - }, - .ErrorSet => { - const err_name = typed_value.val.castTag(.@"error").?.data.name; - const module = self.bin_file.options.module.?; - const global_error_set = module.global_error_set; - const error_index = global_error_set.get(err_name).?; - return MCValue{ .immediate = error_index }; - }, - .ErrorUnion => { - const error_type = typed_value.ty.errorUnionSet(); - const payload_type = typed_value.ty.errorUnionPayload(); - - if (typed_value.val.castTag(.eu_payload)) |pl| { - if (!payload_type.hasRuntimeBits()) { - // We use the error type directly as the type. - return MCValue{ .immediate = 0 }; - } - - _ = pl; - return self.fail("TODO implement error union const of type '{}' (non-error)", .{typed_value.ty.fmtDebug()}); - } else { - if (!payload_type.hasRuntimeBits()) { - // We use the error type directly as the type. - return self.genTypedValue(.{ .ty = error_type, .val = typed_value.val }); - } - - return self.fail("TODO implement error union const of type '{}' (error)", .{typed_value.ty.fmtDebug()}); - } - }, - .Struct => { - return self.lowerUnnamedConst(typed_value); - }, else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty.fmtDebug()}), } } @@ -1171,6 +1367,18 @@ fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { } } +fn ret(self: *Self, mcv: MCValue) !void { + const ret_ty = self.fn_type.fnReturnType(); + try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); + + // Just add space for an instruction, patch this later + const index = try self.addInst(.{ + .tag = .nop, + .data = .{ .nop = {} }, + }); + try self.exitlude_jump_relocs.append(self.gpa, index); +} + fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool { if (!self.liveness.operandDies(inst, op_index)) return false; @@ -1201,3 +1409,36 @@ fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_ind return true; } + +/// 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 { + 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, + } +} + +pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void { + const stack_mcv = try self.allocRegOrMem(inst, false); + log.debug("spilling {d} to stack mcv {any}", .{ inst, stack_mcv }); + const reg_mcv = self.getResolvedInstValue(inst); + assert(reg == reg_mcv.register); + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + try branch.inst_table.put(self.gpa, inst, stack_mcv); + try self.genSetStack(self.air.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv); +} + +/// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`. +fn wantSafety(self: *Self) bool { + return switch (self.bin_file.options.optimize_mode) { + .Debug => true, + .ReleaseSafe => true, + .ReleaseFast => false, + .ReleaseSmall => false, + }; +} diff --git a/src/arch/sparcv9/Emit.zig b/src/arch/sparcv9/Emit.zig index 2192b21c10..4cb789b942 100644 --- a/src/arch/sparcv9/Emit.zig +++ b/src/arch/sparcv9/Emit.zig @@ -47,11 +47,16 @@ pub fn emitMir( .dbg_prologue_end => try emit.mirDebugPrologueEnd(), .dbg_epilogue_begin => try emit.mirDebugEpilogueBegin(), + .add => @panic("TODO implement sparcv9 add"), + .bpcc => @panic("TODO implement sparcv9 bpcc"), .call => @panic("TODO implement sparcv9 call"), .jmpl => @panic("TODO implement sparcv9 jmpl"), + .jmpl_i => @panic("TODO implement sparcv9 jmpl to reg"), + + .@"or" => @panic("TODO implement sparcv9 or"), .nop => @panic("TODO implement sparcv9 nop"), @@ -59,6 +64,14 @@ pub fn emitMir( .save => @panic("TODO implement sparcv9 save"), .restore => @panic("TODO implement sparcv9 restore"), + + .sethi => @panic("TODO implement sparcv9 sethi"), + + .sllx => @panic("TODO implement sparcv9 sllx"), + + .sub => @panic("TODO implement sparcv9 sub"), + + .tcc => @panic("TODO implement sparcv9 tcc"), } } } diff --git a/src/arch/sparcv9/Mir.zig b/src/arch/sparcv9/Mir.zig index 21c6224930..3ff675fc36 100644 --- a/src/arch/sparcv9/Mir.zig +++ b/src/arch/sparcv9/Mir.zig @@ -40,6 +40,11 @@ pub const Inst = struct { // All the real instructions are ordered by their section number // in The SPARC Architecture Manual, Version 9. + /// A.2 Add + /// Those uses the arithmetic_3op field. + // TODO add other operations. + add, + /// A.7 Branch on Integer Condition Codes with Prediction (BPcc) /// It uses the branch_predict field. bpcc, @@ -49,8 +54,16 @@ pub const Inst = struct { call, /// A.24 Jump and Link - /// It uses the branch_link field. + /// jmpl (far direct jump) uses the branch_link field, + /// while jmpl_i (indirect jump) uses the branch_link_indirect field. + /// Those two MIR instructions will be lowered into SPARCv9 jmpl instruction. jmpl, + jmpl_i, + + /// A.31 Logical Operations + /// Those uses the arithmetic_3op field. + // TODO add other operations. + @"or", /// A.40 No Operation /// It uses the nop field. @@ -64,6 +77,24 @@ pub const Inst = struct { /// Those uses the arithmetic_3op field. save, restore, + + /// A.48 SETHI + /// It uses the sethi field. + sethi, + + /// A.49 Shift + /// Those uses the shift field. + // TODO add other operations. + sllx, + + /// A.56 Subtract + /// Those uses the arithmetic_3op field. + // TODO add other operations. + sub, + + /// A.61 Trap on Integer Condition Codes (Tcc) + /// It uses the trap field. + tcc, }; /// The position of an MIR instruction within the `Mir` instructions array. @@ -72,6 +103,7 @@ pub const Inst = struct { /// All instructions have a 8-byte payload, which is contained within /// this union. `Tag` determines which union field is active, as well as /// how to interpret the data within. + // TODO this is a quick-n-dirty solution that needs to be cleaned up. pub const Data = union { /// Debug info: argument /// @@ -122,14 +154,21 @@ pub const Inst = struct { /// Used by e.g. call branch_link: struct { inst: Index, - link: Register, + link: Register = .o7, + }, + + /// Indirect branch and link (always unconditional). + /// Used by e.g. jmpl_i + branch_link_indirect: struct { + reg: Register, + link: Register = .o7, }, /// Branch with prediction. /// Used by e.g. bpcc branch_predict: struct { annul: bool, - pt: bool, + pt: bool = true, ccr: Instruction.CCR, cond: Instruction.Condition, inst: Index, @@ -139,6 +178,46 @@ pub const Inst = struct { /// /// Used by e.g. flushw nop: void, + + /// SETHI operands. + /// + /// Used by sethi + sethi: struct { + rd: Register, + imm: u22, + }, + + /// Shift operands. + /// if is_imm true then it uses the imm field of rs2_or_imm, + /// otherwise it uses rs2 field. + /// + /// Used by e.g. add, sub + shift: struct { + is_imm: bool, + width: Instruction.ShiftWidth, + rd: Register, + rs1: Register, + rs2_or_imm: union { + rs2: Register, + imm: u6, + }, + }, + + /// Trap. + /// if is_imm true then it uses the imm field of rs2_or_imm, + /// otherwise it uses rs2 field. + /// + /// Used by e.g. tcc + trap: struct { + is_imm: bool = true, + cond: Instruction.Condition, + ccr: Instruction.CCR = .icc, + rs1: Register = .g0, + rs2_or_imm: union { + rs2: Register, + imm: u8, + }, + }, }; };