Commit 1a7d89a84d

David Rubin <daviru007@icloud.com>
2024-07-24 14:53:57
riscv: clean up and unify encoding logic
1 parent 574028e
lib/compiler/test_runner.zig
@@ -271,7 +271,6 @@ pub fn mainSimple() anyerror!void {
     };
     // is the backend capable of using std.fmt.format to print a summary at the end?
     const print_summary = switch (builtin.zig_backend) {
-        .stage2_riscv64 => true,
         else => false,
     };
 
src/arch/riscv64/bits.zig
@@ -5,7 +5,6 @@ const testing = std.testing;
 const Target = std.Target;
 
 const Zcu = @import("../../Zcu.zig");
-const Encoding = @import("Encoding.zig");
 const Mir = @import("Mir.zig");
 const abi = @import("abi.zig");
 
@@ -193,7 +192,7 @@ pub const Register = enum(u8) {
     /// The goal of this function is to return the same ID for `zero` and `x0` but two
     /// seperate IDs for `x0` and `f0`. We will assume that each register set has 32 registers
     /// and is repeated twice, once for the named version, once for the number version.
-    pub fn id(reg: Register) u8 {
+    pub fn id(reg: Register) std.math.IntFittingRange(0, @typeInfo(Register).Enum.fields.len) {
         const base = switch (@intFromEnum(reg)) {
             // zig fmt: off
             @intFromEnum(Register.zero) ... @intFromEnum(Register.x31) => @intFromEnum(Register.zero),
src/arch/riscv64/CodeGen.zig
@@ -20,6 +20,7 @@ const InternPool = @import("../../InternPool.zig");
 const Compilation = @import("../../Compilation.zig");
 const trace = @import("../../tracy.zig").trace;
 const codegen = @import("../../codegen.zig");
+const Mnemonic = @import("mnem.zig").Mnemonic;
 
 const ErrorMsg = Zcu.ErrorMsg;
 const Target = std.Target;
@@ -65,7 +66,6 @@ arg_index: usize,
 src_loc: Zcu.LazySrcLoc,
 
 mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
-mir_extra: std.ArrayListUnmanaged(u32) = .{},
 
 owner: Owner,
 
@@ -794,7 +794,6 @@ pub fn generate(
         function.const_tracking.deinit(gpa);
         function.exitlude_jump_relocs.deinit(gpa);
         function.mir_instructions.deinit(gpa);
-        function.mir_extra.deinit(gpa);
     }
 
     wip_mir_log.debug("{}:", .{function.fmtDecl(func.owner_decl)});
@@ -855,7 +854,6 @@ pub fn generate(
 
     var mir: Mir = .{
         .instructions = function.mir_instructions.toOwnedSlice(),
-        .extra = try function.mir_extra.toOwnedSlice(gpa),
         .frame_locs = function.frame_locs.toOwnedSlice(),
     };
     defer mir.deinit(gpa);
@@ -940,10 +938,7 @@ pub fn generateLazy(
         .avl = null,
         .vtype = null,
     };
-    defer {
-        function.mir_instructions.deinit(gpa);
-        function.mir_extra.deinit(gpa);
-    }
+    defer function.mir_instructions.deinit(gpa);
 
     function.genLazy(lazy_sym) catch |err| switch (err) {
         error.CodegenFail => return Result{ .fail = function.err_msg.? },
@@ -955,7 +950,6 @@ pub fn generateLazy(
 
     var mir: Mir = .{
         .instructions = function.mir_instructions.toOwnedSlice(),
-        .extra = try function.mir_extra.toOwnedSlice(gpa),
         .frame_locs = function.frame_locs.toOwnedSlice(),
     };
     defer mir.deinit(gpa);
@@ -1022,7 +1016,6 @@ fn formatWipMir(
         .allocator = data.func.gpa,
         .mir = .{
             .instructions = data.func.mir_instructions.slice(),
-            .extra = data.func.mir_extra.items,
             .frame_locs = data.func.frame_locs.slice(),
         },
         .cc = .Unspecified,
@@ -1120,7 +1113,7 @@ fn addInst(func: *Func, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
     try func.mir_instructions.ensureUnusedCapacity(gpa, 1);
     const result_index: Mir.Inst.Index = @intCast(func.mir_instructions.len);
     func.mir_instructions.appendAssumeCapacity(inst);
-    if (inst.tag != .pseudo or switch (inst.ops) {
+    if (switch (inst.tag) {
         else => true,
         .pseudo_dbg_prologue_end,
         .pseudo_dbg_line_column,
@@ -1131,49 +1124,13 @@ fn addInst(func: *Func, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
     return result_index;
 }
 
-fn addNop(func: *Func) error{OutOfMemory}!Mir.Inst.Index {
-    return func.addInst(.{
-        .tag = .nop,
-        .ops = .none,
-        .data = undefined,
-    });
-}
-
-fn addPseudoNone(func: *Func, ops: Mir.Inst.Ops) !void {
-    _ = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = ops,
-        .data = undefined,
-    });
-}
-
-fn addPseudo(func: *Func, ops: Mir.Inst.Ops) !Mir.Inst.Index {
+fn addPseudo(func: *Func, mnem: Mnemonic) error{OutOfMemory}!Mir.Inst.Index {
     return func.addInst(.{
-        .tag = .pseudo,
-        .ops = ops,
-        .data = undefined,
+        .tag = mnem,
+        .data = .none,
     });
 }
 
-pub fn addExtra(func: *Func, extra: anytype) Allocator.Error!u32 {
-    const fields = std.meta.fields(@TypeOf(extra));
-    try func.mir_extra.ensureUnusedCapacity(func.gpa, fields.len);
-    return func.addExtraAssumeCapacity(extra);
-}
-
-pub fn addExtraAssumeCapacity(func: *Func, extra: anytype) u32 {
-    const fields = std.meta.fields(@TypeOf(extra));
-    const result: u32 = @intCast(func.mir_extra.items.len);
-    inline for (fields) |field| {
-        func.mir_extra.appendAssumeCapacity(switch (field.type) {
-            u32 => @field(extra, field.name),
-            i32 => @bitCast(@field(extra, field.name)),
-            else => @compileError("bad field type"),
-        });
-    }
-    return result;
-}
-
 /// Returns a temporary register that contains the value of the `reg` csr.
 ///
 /// Caller's duty to lock the return register is needed.
@@ -1182,14 +1139,11 @@ fn getCsr(func: *Func, csr: CSR) !Register {
     const dst_reg = try func.register_manager.allocReg(null, func.regTempClassForType(Type.u64));
     _ = try func.addInst(.{
         .tag = .csrrs,
-        .ops = .csr,
-        .data = .{
-            .csr = .{
-                .csr = csr,
-                .rd = dst_reg,
-                .rs1 = .x0,
-            },
-        },
+        .data = .{ .csr = .{
+            .csr = csr,
+            .rd = dst_reg,
+            .rs1 = .x0,
+        } },
     });
     return dst_reg;
 }
@@ -1208,7 +1162,6 @@ fn setVl(func: *Func, dst_reg: Register, avl: u64, options: bits.VType) !void {
         const options_int: u12 = @as(u12, 0) | @as(u8, @bitCast(options));
         _ = try func.addInst(.{
             .tag = .vsetvli,
-            .ops = .rri,
             .data = .{ .i_type = .{
                 .rd = dst_reg,
                 .rs1 = .zero,
@@ -1221,7 +1174,6 @@ fn setVl(func: *Func, dst_reg: Register, avl: u64, options: bits.VType) !void {
             const options_int: u12 = (~@as(u12, 0) << 10) | @as(u8, @bitCast(options));
             _ = try func.addInst(.{
                 .tag = .vsetivli,
-                .ops = .rri,
                 .data = .{
                     .i_type = .{
                         .rd = dst_reg,
@@ -1235,7 +1187,6 @@ fn setVl(func: *Func, dst_reg: Register, avl: u64, options: bits.VType) !void {
             const temp_reg = try func.copyToTmpRegister(Type.u64, .{ .immediate = avl });
             _ = try func.addInst(.{
                 .tag = .vsetvli,
-                .ops = .rri,
                 .data = .{ .i_type = .{
                     .rd = dst_reg,
                     .rs1 = temp_reg,
@@ -1270,7 +1221,7 @@ fn gen(func: *Func) !void {
     }
 
     if (fn_info.cc != .Naked) {
-        try func.addPseudoNone(.pseudo_dbg_prologue_end);
+        _ = try func.addPseudo(.pseudo_dbg_prologue_end);
 
         const backpatch_stack_alloc = try func.addPseudo(.pseudo_dead);
         const backpatch_ra_spill = try func.addPseudo(.pseudo_dead);
@@ -1300,11 +1251,11 @@ fn gen(func: *Func) !void {
         try func.genBody(func.air.getMainBody());
 
         for (func.exitlude_jump_relocs.items) |jmp_reloc| {
-            func.mir_instructions.items(.data)[jmp_reloc].inst =
+            func.mir_instructions.items(.data)[jmp_reloc].j_type.inst =
                 @intCast(func.mir_instructions.len);
         }
 
-        try func.addPseudoNone(.pseudo_dbg_epilogue_begin);
+        _ = try func.addPseudo(.pseudo_dbg_epilogue_begin);
 
         const backpatch_restore_callee_preserved_regs = try func.addPseudo(.pseudo_dead);
         const backpatch_ra_restore = try func.addPseudo(.pseudo_dead);
@@ -1314,7 +1265,6 @@ fn gen(func: *Func) !void {
         // ret
         _ = try func.addInst(.{
             .tag = .jalr,
-            .ops = .rri,
             .data = .{
                 .i_type = .{
                     .rd = .zero,
@@ -1329,7 +1279,6 @@ fn gen(func: *Func) !void {
 
         func.mir_instructions.set(backpatch_stack_alloc, .{
             .tag = .addi,
-            .ops = .rri,
             .data = .{ .i_type = .{
                 .rd = .sp,
                 .rs1 = .sp,
@@ -1337,8 +1286,7 @@ fn gen(func: *Func) !void {
             } },
         });
         func.mir_instructions.set(backpatch_ra_spill, .{
-            .tag = .pseudo,
-            .ops = .pseudo_store_rm,
+            .tag = .pseudo_store_rm,
             .data = .{ .rm = .{
                 .r = .ra,
                 .m = .{
@@ -1348,8 +1296,7 @@ fn gen(func: *Func) !void {
             } },
         });
         func.mir_instructions.set(backpatch_ra_restore, .{
-            .tag = .pseudo,
-            .ops = .pseudo_load_rm,
+            .tag = .pseudo_load_rm,
             .data = .{ .rm = .{
                 .r = .ra,
                 .m = .{
@@ -1359,8 +1306,7 @@ fn gen(func: *Func) !void {
             } },
         });
         func.mir_instructions.set(backpatch_fp_spill, .{
-            .tag = .pseudo,
-            .ops = .pseudo_store_rm,
+            .tag = .pseudo_store_rm,
             .data = .{ .rm = .{
                 .r = .s0,
                 .m = .{
@@ -1370,8 +1316,7 @@ fn gen(func: *Func) !void {
             } },
         });
         func.mir_instructions.set(backpatch_fp_restore, .{
-            .tag = .pseudo,
-            .ops = .pseudo_load_rm,
+            .tag = .pseudo_load_rm,
             .data = .{ .rm = .{
                 .r = .s0,
                 .m = .{
@@ -1382,7 +1327,6 @@ fn gen(func: *Func) !void {
         });
         func.mir_instructions.set(backpatch_fp_add, .{
             .tag = .addi,
-            .ops = .rri,
             .data = .{ .i_type = .{
                 .rd = .s0,
                 .rs1 = .sp,
@@ -1391,7 +1335,6 @@ fn gen(func: *Func) !void {
         });
         func.mir_instructions.set(backpatch_stack_alloc_restore, .{
             .tag = .addi,
-            .ops = .rri,
             .data = .{ .i_type = .{
                 .rd = .sp,
                 .rs1 = .sp,
@@ -1401,27 +1344,24 @@ fn gen(func: *Func) !void {
 
         if (need_save_reg) {
             func.mir_instructions.set(backpatch_spill_callee_preserved_regs, .{
-                .tag = .pseudo,
-                .ops = .pseudo_spill_regs,
+                .tag = .pseudo_spill_regs,
                 .data = .{ .reg_list = frame_layout.save_reg_list },
             });
 
             func.mir_instructions.set(backpatch_restore_callee_preserved_regs, .{
-                .tag = .pseudo,
-                .ops = .pseudo_restore_regs,
+                .tag = .pseudo_restore_regs,
                 .data = .{ .reg_list = frame_layout.save_reg_list },
             });
         }
     } else {
-        try func.addPseudoNone(.pseudo_dbg_prologue_end);
+        _ = try func.addPseudo(.pseudo_dbg_prologue_end);
         try func.genBody(func.air.getMainBody());
-        try func.addPseudoNone(.pseudo_dbg_epilogue_begin);
+        _ = try func.addPseudo(.pseudo_dbg_epilogue_begin);
     }
 
     // Drop them off at the rbrace.
     _ = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_dbg_line_column,
+        .tag = .pseudo_dbg_line_column,
         .data = .{ .pseudo_dbg_line_column = .{
             .line = func.end_di_line,
             .column = func.end_di_column,
@@ -1493,9 +1433,11 @@ fn genLazy(func: *Func, lazy_sym: link.File.LazySymbol) InnerError!void {
                 );
 
                 exitlude_jump_reloc.* = try func.addInst(.{
-                    .tag = .pseudo,
-                    .ops = .pseudo_j,
-                    .data = .{ .inst = undefined },
+                    .tag = .pseudo_j,
+                    .data = .{ .j_type = .{
+                        .rd = .zero,
+                        .inst = undefined,
+                    } },
                 });
                 func.performReloc(skip_reloc);
 
@@ -1508,7 +1450,7 @@ fn genLazy(func: *Func, lazy_sym: link.File.LazySymbol) InnerError!void {
 
             _ = try func.addInst(.{
                 .tag = .jalr,
-                .ops = .rri,
+
                 .data = .{
                     .i_type = .{
                         .rd = .zero,
@@ -2041,7 +1983,7 @@ fn truncateRegister(func: *Func, ty: Type, reg: Register) !void {
         .signed => {
             _ = try func.addInst(.{
                 .tag = .slli,
-                .ops = .rri,
+
                 .data = .{
                     .i_type = .{
                         .rd = reg,
@@ -2052,7 +1994,7 @@ fn truncateRegister(func: *Func, ty: Type, reg: Register) !void {
             });
             _ = try func.addInst(.{
                 .tag = .srai,
-                .ops = .rri,
+
                 .data = .{
                     .i_type = .{
                         .rd = reg,
@@ -2067,7 +2009,7 @@ fn truncateRegister(func: *Func, ty: Type, reg: Register) !void {
             if (mask < 256) {
                 _ = try func.addInst(.{
                     .tag = .andi,
-                    .ops = .rri,
+
                     .data = .{
                         .i_type = .{
                             .rd = reg,
@@ -2079,7 +2021,7 @@ fn truncateRegister(func: *Func, ty: Type, reg: Register) !void {
             } else {
                 _ = try func.addInst(.{
                     .tag = .slli,
-                    .ops = .rri,
+
                     .data = .{
                         .i_type = .{
                             .rd = reg,
@@ -2090,7 +2032,7 @@ fn truncateRegister(func: *Func, ty: Type, reg: Register) !void {
                 });
                 _ = try func.addInst(.{
                     .tag = .srli,
-                    .ops = .rri,
+
                     .data = .{
                         .i_type = .{
                             .rd = reg,
@@ -2411,8 +2353,7 @@ fn airNot(func: *Func, inst: Air.Inst.Index) !void {
         switch (ty.zigTypeTag(zcu)) {
             .Bool => {
                 _ = try func.addInst(.{
-                    .tag = .pseudo,
-                    .ops = .pseudo_not,
+                    .tag = .pseudo_not,
                     .data = .{
                         .rr = .{
                             .rs = operand_reg,
@@ -2430,7 +2371,6 @@ fn airNot(func: *Func, inst: Air.Inst.Index) !void {
                     32, 64 => {
                         _ = try func.addInst(.{
                             .tag = .xori,
-                            .ops = .rri,
                             .data = .{
                                 .i_type = .{
                                     .rd = dst_reg,
@@ -2628,7 +2568,7 @@ fn genBinOp(
 
             switch (lhs_ty.zigTypeTag(zcu)) {
                 .Int => {
-                    const mir_tag: Mir.Inst.Tag = switch (tag) {
+                    const mnem: Mnemonic = switch (tag) {
                         .add, .add_wrap => switch (bit_size) {
                             8, 16, 64 => .add,
                             32 => .addw,
@@ -2656,8 +2596,7 @@ fn genBinOp(
                     };
 
                     _ = try func.addInst(.{
-                        .tag = mir_tag,
-                        .ops = .rrr,
+                        .tag = mnem,
                         .data = .{
                             .r_type = .{
                                 .rd = dst_reg,
@@ -2668,7 +2607,7 @@ fn genBinOp(
                     });
                 },
                 .Float => {
-                    const mir_tag: Mir.Inst.Tag = switch (tag) {
+                    const mir_tag: Mnemonic = switch (tag) {
                         .add => switch (bit_size) {
                             32 => .fadds,
                             64 => .faddd,
@@ -2689,7 +2628,6 @@ fn genBinOp(
 
                     _ = try func.addInst(.{
                         .tag = mir_tag,
-                        .ops = .rrr,
                         .data = .{
                             .r_type = .{
                                 .rd = dst_reg,
@@ -2705,7 +2643,7 @@ fn genBinOp(
 
                     const child_ty = lhs_ty.childType(zcu);
 
-                    const mir_tag: Mir.Inst.Tag = switch (tag) {
+                    const mir_tag: Mnemonic = switch (tag) {
                         .add => switch (child_ty.zigTypeTag(zcu)) {
                             .Int => .vaddvv,
                             .Float => .vfaddvv,
@@ -2739,7 +2677,6 @@ fn genBinOp(
 
                     _ = try func.addInst(.{
                         .tag = mir_tag,
-                        .ops = .rrr,
                         .data = .{
                             .r_type = .{
                                 .rd = dst_reg,
@@ -2764,7 +2701,6 @@ fn genBinOp(
 
             _ = try func.addInst(.{
                 .tag = .add,
-                .ops = .rrr,
                 .data = .{ .r_type = .{
                     .rd = tmp_reg,
                     .rs1 = rhs_reg,
@@ -2774,7 +2710,6 @@ fn genBinOp(
 
             _ = try func.addInst(.{
                 .tag = .sltu,
-                .ops = .rrr,
                 .data = .{ .r_type = .{
                     .rd = dst_reg,
                     .rs1 = tmp_reg,
@@ -2785,7 +2720,6 @@ fn genBinOp(
             // neg dst_reg, dst_reg
             _ = try func.addInst(.{
                 .tag = .sub,
-                .ops = .rrr,
                 .data = .{ .r_type = .{
                     .rd = dst_reg,
                     .rs1 = .zero,
@@ -2795,7 +2729,6 @@ fn genBinOp(
 
             _ = try func.addInst(.{
                 .tag = .@"or",
-                .ops = .rrr,
                 .data = .{ .r_type = .{
                     .rd = dst_reg,
                     .rs1 = dst_reg,
@@ -2850,7 +2783,6 @@ fn genBinOp(
                     .bit_or, .bool_or => .@"or",
                     else => unreachable,
                 },
-                .ops = .rrr,
                 .data = .{
                     .r_type = .{
                         .rd = dst_reg,
@@ -2876,7 +2808,7 @@ fn genBinOp(
             if (bit_size > 64) return func.fail("TODO: genBinOp shift > 64 bits, {}", .{bit_size});
             try func.truncateRegister(rhs_ty, rhs_reg);
 
-            const mir_tag: Mir.Inst.Tag = switch (tag) {
+            const mir_tag: Mnemonic = switch (tag) {
                 .shl, .shl_exact => switch (bit_size) {
                     1...31, 33...64 => .sll,
                     32 => .sllw,
@@ -2892,7 +2824,6 @@ fn genBinOp(
 
             _ = try func.addInst(.{
                 .tag = mir_tag,
-                .ops = .rrr,
                 .data = .{ .r_type = .{
                     .rd = dst_reg,
                     .rs1 = lhs_reg,
@@ -2910,8 +2841,7 @@ fn genBinOp(
         .cmp_gte,
         => {
             _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_compare,
+                .tag = .pseudo_compare,
                 .data = .{
                     .compare = .{
                         .op = switch (tag) {
@@ -2966,7 +2896,6 @@ fn genBinOp(
 
                     _ = try func.addInst(.{
                         .tag = if (int_info.signedness == .unsigned) .sltu else .slt,
-                        .ops = .rrr,
                         .data = .{ .r_type = .{
                             .rd = mask_reg,
                             .rs1 = lhs_reg,
@@ -2976,7 +2905,6 @@ fn genBinOp(
 
                     _ = try func.addInst(.{
                         .tag = .sub,
-                        .ops = .rrr,
                         .data = .{ .r_type = .{
                             .rd = mask_reg,
                             .rs1 = .zero,
@@ -2986,7 +2914,6 @@ fn genBinOp(
 
                     _ = try func.addInst(.{
                         .tag = .xor,
-                        .ops = .rrr,
                         .data = .{ .r_type = .{
                             .rd = dst_reg,
                             .rs1 = lhs_reg,
@@ -2996,7 +2923,6 @@ fn genBinOp(
 
                     _ = try func.addInst(.{
                         .tag = .@"and",
-                        .ops = .rrr,
                         .data = .{ .r_type = .{
                             .rd = mask_reg,
                             .rs1 = dst_reg,
@@ -3006,7 +2932,6 @@ fn genBinOp(
 
                     _ = try func.addInst(.{
                         .tag = .xor,
-                        .ops = .rrr,
                         .data = .{ .r_type = .{
                             .rd = dst_reg,
                             .rs1 = if (tag == .min) rhs_reg else lhs_reg,
@@ -3103,7 +3028,6 @@ fn airAddWithOverflow(func: *Func, inst: Air.Inst.Index) !void {
 
                     _ = try func.addInst(.{
                         .tag = .add,
-                        .ops = .rrr,
                         .data = .{ .r_type = .{
                             .rs1 = rhs_reg,
                             .rs2 = lhs_reg,
@@ -3209,7 +3133,6 @@ fn airSubWithOverflow(func: *Func, inst: Air.Inst.Index) !void {
             .unsigned => {
                 _ = try func.addInst(.{
                     .tag = .sltu,
-                    .ops = .rrr,
                     .data = .{ .r_type = .{
                         .rd = overflow_reg,
                         .rs1 = lhs_reg,
@@ -3231,7 +3154,6 @@ fn airSubWithOverflow(func: *Func, inst: Air.Inst.Index) !void {
                     64 => {
                         _ = try func.addInst(.{
                             .tag = .slt,
-                            .ops = .rrr,
                             .data = .{ .r_type = .{
                                 .rd = overflow_reg,
                                 .rs1 = overflow_reg,
@@ -3241,7 +3163,6 @@ fn airSubWithOverflow(func: *Func, inst: Air.Inst.Index) !void {
 
                         _ = try func.addInst(.{
                             .tag = .slt,
-                            .ops = .rrr,
                             .data = .{ .r_type = .{
                                 .rd = rhs_reg,
                                 .rs1 = rhs_reg,
@@ -3251,7 +3172,6 @@ fn airSubWithOverflow(func: *Func, inst: Air.Inst.Index) !void {
 
                         _ = try func.addInst(.{
                             .tag = .xor,
-                            .ops = .rrr,
                             .data = .{ .r_type = .{
                                 .rd = lhs_reg,
                                 .rs1 = overflow_reg,
@@ -3843,7 +3763,6 @@ fn genSliceElemPtr(func: *Func, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref) !MCValue {
 
     _ = try func.addInst(.{
         .tag = .add,
-        .ops = .rrr,
         .data = .{ .r_type = .{
             .rd = addr_reg,
             .rs1 = addr_reg,
@@ -3907,7 +3826,6 @@ fn airArrayElemVal(func: *Func, inst: Air.Inst.Index) !void {
 
                 _ = try func.addInst(.{
                     .tag = .vslidedownvx,
-                    .ops = .rrr,
                     .data = .{ .r_type = .{
                         .rd = src_reg,
                         .rs1 = index_reg,
@@ -3925,7 +3843,6 @@ fn airArrayElemVal(func: *Func, inst: Air.Inst.Index) !void {
         defer func.register_manager.unlockReg(offset_lock);
         _ = try func.addInst(.{
             .tag = .add,
-            .ops = .rrr,
             .data = .{ .r_type = .{
                 .rd = addr_reg,
                 .rs1 = addr_reg,
@@ -4080,7 +3997,6 @@ fn airClz(func: *Func, inst: Air.Inst.Index) !void {
                 32 => .clzw,
                 else => .clz,
             },
-            .ops = .rrr,
             .data = .{
                 .r_type = .{
                     .rs2 = .zero, // rs2 is 0 filled in the spec
@@ -4093,7 +4009,6 @@ fn airClz(func: *Func, inst: Air.Inst.Index) !void {
         if (!(bit_size == 32 or bit_size == 64)) {
             _ = try func.addInst(.{
                 .tag = .addi,
-                .ops = .rri,
                 .data = .{ .i_type = .{
                     .rd = dst_reg,
                     .rs1 = dst_reg,
@@ -4151,7 +4066,6 @@ fn airAbs(func: *Func, inst: Air.Inst.Index) !void {
                         64 => .srai,
                         else => unreachable,
                     },
-                    .ops = .rri,
                     .data = .{ .i_type = .{
                         .rd = temp_reg,
                         .rs1 = operand_reg,
@@ -4161,7 +4075,6 @@ fn airAbs(func: *Func, inst: Air.Inst.Index) !void {
 
                 _ = try func.addInst(.{
                     .tag = .xor,
-                    .ops = .rrr,
                     .data = .{ .r_type = .{
                         .rd = operand_reg,
                         .rs1 = operand_reg,
@@ -4175,7 +4088,6 @@ fn airAbs(func: *Func, inst: Air.Inst.Index) !void {
                         64 => .sub,
                         else => unreachable,
                     },
-                    .ops = .rrr,
                     .data = .{ .r_type = .{
                         .rd = operand_reg,
                         .rs1 = operand_reg,
@@ -4187,14 +4099,14 @@ fn airAbs(func: *Func, inst: Air.Inst.Index) !void {
             },
             .Float => {
                 const float_bits = scalar_ty.floatBits(zcu.getTarget());
-                switch (float_bits) {
+                const mnem: Mnemonic = switch (float_bits) {
                     16 => return func.fail("TODO: airAbs 16-bit float", .{}),
-                    32 => {},
-                    64 => {},
+                    32 => .fsgnjxs,
+                    64 => .fsgnjxd,
                     80 => return func.fail("TODO: airAbs 80-bit float", .{}),
                     128 => return func.fail("TODO: airAbs 128-bit float", .{}),
                     else => unreachable,
-                }
+                };
 
                 const return_mcv = try func.copyToNewRegister(inst, operand);
                 const operand_reg = return_mcv.register;
@@ -4202,13 +4114,12 @@ fn airAbs(func: *Func, inst: Air.Inst.Index) !void {
                 assert(operand_reg.class() == .float);
 
                 _ = try func.addInst(.{
-                    .tag = .pseudo,
-                    .ops = .pseudo_fabs,
+                    .tag = mnem,
                     .data = .{
-                        .fabs = .{
+                        .r_type = .{
                             .rd = operand_reg,
-                            .rs = operand_reg,
-                            .bits = float_bits,
+                            .rs1 = operand_reg,
+                            .rs2 = operand_reg,
                         },
                     },
                 });
@@ -4231,54 +4142,56 @@ fn airByteSwap(func: *Func, inst: Air.Inst.Index) !void {
         const ty = func.typeOf(ty_op.operand);
         const operand = try func.resolveInst(ty_op.operand);
 
-        const int_bits = ty.intInfo(zcu).bits;
+        switch (ty.zigTypeTag(zcu)) {
+            .Int => {
+                const int_bits = ty.intInfo(zcu).bits;
 
-        // bytes are no-op
-        if (int_bits == 8 and func.reuseOperand(inst, ty_op.operand, 0, operand)) {
-            return func.finishAir(inst, operand, .{ ty_op.operand, .none, .none });
-        }
+                // bytes are no-op
+                if (int_bits == 8 and func.reuseOperand(inst, ty_op.operand, 0, operand)) {
+                    return func.finishAir(inst, operand, .{ ty_op.operand, .none, .none });
+                }
 
-        const dest_mcv = try func.copyToNewRegister(inst, operand);
-        const dest_reg = dest_mcv.register;
+                const dest_mcv = try func.copyToNewRegister(inst, operand);
+                const dest_reg = dest_mcv.register;
 
-        switch (int_bits) {
-            16 => {
-                const temp_reg, const temp_lock = try func.allocReg(.int);
-                defer func.register_manager.unlockReg(temp_lock);
+                switch (int_bits) {
+                    16 => {
+                        const temp_reg, const temp_lock = try func.allocReg(.int);
+                        defer func.register_manager.unlockReg(temp_lock);
 
-                _ = try func.addInst(.{
-                    .tag = .srli,
-                    .ops = .rri,
-                    .data = .{ .i_type = .{
-                        .imm12 = Immediate.s(8),
-                        .rd = temp_reg,
-                        .rs1 = dest_reg,
-                    } },
-                });
+                        _ = try func.addInst(.{
+                            .tag = .srli,
+                            .data = .{ .i_type = .{
+                                .imm12 = Immediate.s(8),
+                                .rd = temp_reg,
+                                .rs1 = dest_reg,
+                            } },
+                        });
 
-                _ = try func.addInst(.{
-                    .tag = .slli,
-                    .ops = .rri,
-                    .data = .{ .i_type = .{
-                        .imm12 = Immediate.s(8),
-                        .rd = dest_reg,
-                        .rs1 = dest_reg,
-                    } },
-                });
-                _ = try func.addInst(.{
-                    .tag = .@"or",
-                    .ops = .rri,
-                    .data = .{ .r_type = .{
-                        .rd = dest_reg,
-                        .rs1 = dest_reg,
-                        .rs2 = temp_reg,
-                    } },
-                });
+                        _ = try func.addInst(.{
+                            .tag = .slli,
+                            .data = .{ .i_type = .{
+                                .imm12 = Immediate.s(8),
+                                .rd = dest_reg,
+                                .rs1 = dest_reg,
+                            } },
+                        });
+                        _ = try func.addInst(.{
+                            .tag = .@"or",
+                            .data = .{ .r_type = .{
+                                .rd = dest_reg,
+                                .rs1 = dest_reg,
+                                .rs2 = temp_reg,
+                            } },
+                        });
+                    },
+                    else => return func.fail("TODO: {d} bits for airByteSwap", .{int_bits}),
+                }
+
+                break :result dest_mcv;
             },
-            else => return func.fail("TODO: {d} bits for airByteSwap", .{int_bits}),
+            else => return func.fail("TODO: airByteSwap {}", .{ty.fmt(pt)}),
         }
-
-        break :result dest_mcv;
     };
     return func.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
@@ -4322,7 +4235,6 @@ fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
                     .sqrt => {
                         _ = try func.addInst(.{
                             .tag = if (operand_bit_size == 64) .fsqrtd else .fsqrts,
-                            .ops = .rrr,
                             .data = .{
                                 .r_type = .{
                                     .rd = dst_reg,
@@ -4332,6 +4244,7 @@ fn airUnaryMath(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
                             },
                         });
                     },
+
                     else => return func.fail("TODO: airUnaryMath Float {s}", .{@tagName(tag)}),
                 }
             },
@@ -4538,17 +4451,14 @@ fn structFieldPtr(func: *Func, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde
     const zcu = pt.zcu;
     const ptr_field_ty = func.typeOfIndex(inst);
     const ptr_container_ty = func.typeOf(operand);
-    const ptr_container_ty_info = ptr_container_ty.ptrInfo(zcu);
     const container_ty = ptr_container_ty.childType(zcu);
 
-    const field_offset: i32 = if (zcu.typeToPackedStruct(container_ty)) |struct_obj|
-        if (ptr_field_ty.ptrInfo(zcu).packed_offset.host_size == 0)
-            @divExact(pt.structPackedFieldBitOffset(struct_obj, index) +
-                ptr_container_ty_info.packed_offset.bit_offset, 8)
-        else
-            0
-    else
-        @intCast(container_ty.structFieldOffset(index, pt));
+    const field_offset: i32 = switch (container_ty.containerLayout(zcu)) {
+        .auto, .@"extern" => @intCast(container_ty.structFieldOffset(index, pt)),
+        .@"packed" => @divExact(@as(i32, ptr_container_ty.ptrInfo(zcu).packed_offset.bit_offset) +
+            (if (zcu.typeToStruct(container_ty)) |struct_obj| pt.structPackedFieldBitOffset(struct_obj, index) else 0) -
+            ptr_field_ty.ptrInfo(zcu).packed_offset.bit_offset, 8),
+    };
 
     const src_mcv = try func.resolveInst(operand);
     const dst_mcv = if (switch (src_mcv) {
@@ -4600,7 +4510,6 @@ fn airStructFieldVal(func: *Func, inst: Air.Inst.Index) !void {
                 if (field_off > 0) {
                     _ = try func.addInst(.{
                         .tag = .srli,
-                        .ops = .rri,
                         .data = .{ .i_type = .{
                             .imm12 = Immediate.u(@intCast(field_off)),
                             .rd = dst_reg,
@@ -4720,8 +4629,7 @@ fn airArg(func: *Func, inst: Air.Inst.Index) !void {
 fn airTrap(func: *Func) !void {
     _ = try func.addInst(.{
         .tag = .unimp,
-        .ops = .none,
-        .data = undefined,
+        .data = .none,
     });
     return func.finishAirBookkeeping();
 }
@@ -4729,8 +4637,7 @@ fn airTrap(func: *Func) !void {
 fn airBreakpoint(func: *Func) !void {
     _ = try func.addInst(.{
         .tag = .ebreak,
-        .ops = .none,
-        .data = undefined,
+        .data = .none,
     });
     return func.finishAirBookkeeping();
 }
@@ -4758,8 +4665,7 @@ fn airFence(func: *Func, inst: Air.Inst.Index) !void {
     };
 
     _ = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_fence,
+        .tag = .pseudo_fence,
         .data = .{
             .fence = .{
                 .pred = pred,
@@ -4951,7 +4857,6 @@ fn genCall(
                                 try func.genSetReg(Type.u64, .ra, .{ .load_symbol = .{ .sym = sym.esym_index } });
                                 _ = try func.addInst(.{
                                     .tag = .jalr,
-                                    .ops = .rri,
                                     .data = .{ .i_type = .{
                                         .rd = .ra,
                                         .rs1 = .ra,
@@ -4967,16 +4872,15 @@ fn genCall(
                         const decl_name = owner_decl.name.toSlice(&zcu.intern_pool);
                         const atom_index = try func.owner.getSymbolIndex(func);
 
-                        if (func.bin_file.cast(link.File.Elf)) |elf_file| {
-                            _ = try func.addInst(.{
-                                .tag = .pseudo,
-                                .ops = .pseudo_extern_fn_reloc,
-                                .data = .{ .reloc = .{
-                                    .atom_index = atom_index,
-                                    .sym_index = try elf_file.getGlobalSymbol(decl_name, lib_name),
-                                } },
-                            });
-                        } else unreachable; // not a valid riscv64 format
+                        const elf_file = func.bin_file.cast(link.File.Elf).?;
+                        _ = try func.addInst(.{
+                            .tag = .pseudo_extern_fn_reloc,
+                            .data = .{ .reloc = .{
+                                .register = .ra,
+                                .atom_index = atom_index,
+                                .sym_index = try elf_file.getGlobalSymbol(decl_name, lib_name),
+                            } },
+                        });
                     },
                     else => return func.fail("TODO implement calling bitcasted functions", .{}),
                 }
@@ -4988,7 +4892,6 @@ fn genCall(
 
                 _ = try func.addInst(.{
                     .tag = .jalr,
-                    .ops = .rri,
                     .data = .{ .i_type = .{
                         .rd = .ra,
                         .rs1 = addr_reg,
@@ -5065,9 +4968,11 @@ fn airRet(func: *Func, inst: Air.Inst.Index, safety: bool) !void {
 
     // Just add space for an instruction, reloced this later
     const index = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_j,
-        .data = .{ .inst = undefined },
+        .tag = .pseudo_j,
+        .data = .{ .j_type = .{
+            .rd = .zero,
+            .inst = undefined,
+        } },
     });
 
     try func.exitlude_jump_relocs.append(func.gpa, index);
@@ -5089,9 +4994,11 @@ fn airRetLoad(func: *Func, inst: Air.Inst.Index) !void {
 
     // Just add space for an instruction, reloced this later
     const index = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_j,
-        .data = .{ .inst = undefined },
+        .tag = .pseudo_j,
+        .data = .{ .j_type = .{
+            .rd = .zero,
+            .inst = undefined,
+        } },
     });
 
     try func.exitlude_jump_relocs.append(func.gpa, index);
@@ -5171,8 +5078,7 @@ fn airDbgStmt(func: *Func, inst: Air.Inst.Index) !void {
     const dbg_stmt = func.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
 
     _ = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_dbg_line_column,
+        .tag = .pseudo_dbg_line_column,
         .data = .{ .pseudo_dbg_line_column = .{
             .line = dbg_stmt.line,
             .column = dbg_stmt.column,
@@ -5290,7 +5196,6 @@ fn condBr(func: *Func, cond_ty: Type, condition: MCValue) !Mir.Inst.Index {
 
     return try func.addInst(.{
         .tag = .beq,
-        .ops = .rr_inst,
         .data = .{
             .b_type = .{
                 .rs1 = cond_reg,
@@ -5332,8 +5237,7 @@ fn isNull(func: *Func, inst: Air.Inst.Index, opt_ty: Type, opt_mcv: MCValue) !MC
         .register => |opt_reg| {
             if (some_info.off == 0) {
                 _ = try func.addInst(.{
-                    .tag = .pseudo,
-                    .ops = .pseudo_compare,
+                    .tag = .pseudo_compare,
                     .data = .{
                         .compare = .{
                             .op = .eq,
@@ -5382,8 +5286,7 @@ fn isNull(func: *Func, inst: Air.Inst.Index, opt_ty: Type, opt_mcv: MCValue) !MC
             defer func.register_manager.unlockReg(opt_reg_lock);
 
             _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_compare,
+                .tag = .pseudo_compare,
                 .data = .{
                     .compare = .{
                         .op = .eq,
@@ -5432,8 +5335,7 @@ fn airIsNonNull(func: *Func, inst: Air.Inst.Index) !void {
     assert(result == .register);
 
     _ = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_not,
+        .tag = .pseudo_not,
         .data = .{
             .rr = .{
                 .rd = result.register,
@@ -5565,8 +5467,7 @@ fn isNonErr(func: *Func, inst: Air.Inst.Index, eu_ty: Type, eu_mcv: MCValue) !MC
     switch (is_err_res) {
         .register => |reg| {
             _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_not,
+                .tag = .pseudo_not,
                 .data = .{
                     .rr = .{
                         .rd = reg,
@@ -5633,11 +5534,11 @@ fn airLoop(func: *Func, inst: Air.Inst.Index) !void {
 /// Send control flow to the `index` of `func.code`.
 fn jump(func: *Func, index: Mir.Inst.Index) !Mir.Inst.Index {
     return func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_j,
-        .data = .{
+        .tag = .pseudo_j,
+        .data = .{ .j_type = .{
+            .rd = .zero,
             .inst = index,
-        },
+        } },
     });
 }
 
@@ -5727,8 +5628,7 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {
 
             if (!(i < relocs.len - 1)) {
                 _ = try func.addInst(.{
-                    .tag = .pseudo,
-                    .ops = .pseudo_not,
+                    .tag = .pseudo_not,
                     .data = .{ .rr = .{
                         .rd = cmp_reg,
                         .rs = cmp_reg,
@@ -5775,18 +5675,13 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {
 
 fn performReloc(func: *Func, inst: Mir.Inst.Index) void {
     const tag = func.mir_instructions.items(.tag)[inst];
-    const ops = func.mir_instructions.items(.ops)[inst];
     const target: Mir.Inst.Index = @intCast(func.mir_instructions.len);
 
     switch (tag) {
-        .bne,
         .beq,
         => func.mir_instructions.items(.data)[inst].b_type.inst = target,
         .jal => func.mir_instructions.items(.data)[inst].j_type.inst = target,
-        .pseudo => switch (ops) {
-            .pseudo_j => func.mir_instructions.items(.data)[inst].inst = target,
-            else => std.debug.panic("TODO: performReloc {s}", .{@tagName(ops)}),
-        },
+        .pseudo_j => func.mir_instructions.items(.data)[inst].j_type.inst = target,
         else => std.debug.panic("TODO: performReloc {s}", .{@tagName(tag)}),
     }
 }
@@ -5873,7 +5768,6 @@ fn airBoolOp(func: *Func, inst: Air.Inst.Index) !void {
 
         _ = try func.addInst(.{
             .tag = if (tag == .bool_or) .@"or" else .@"and",
-            .ops = .rrr,
             .data = .{ .r_type = .{
                 .rd = result_reg,
                 .rs1 = lhs_reg,
@@ -5885,7 +5779,6 @@ fn airBoolOp(func: *Func, inst: Air.Inst.Index) !void {
         if (func.wantSafety()) {
             _ = try func.addInst(.{
                 .tag = .andi,
-                .ops = .rri,
                 .data = .{ .i_type = .{
                     .rd = result_reg,
                     .rs1 = result_reg,
@@ -5970,11 +5863,10 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void {
 
         const asm_source = std.mem.sliceAsBytes(func.air.extra[extra_i..])[0..extra.data.source_len];
 
-        if (std.meta.stringToEnum(Mir.Inst.Tag, asm_source)) |tag| {
+        if (std.meta.stringToEnum(Mnemonic, asm_source)) |tag| {
             _ = try func.addInst(.{
                 .tag = tag,
-                .ops = .none,
-                .data = undefined,
+                .data = .none,
             });
         } else {
             return func.fail("TODO: asm_source {s}", .{asm_source});
@@ -6116,7 +6008,6 @@ fn genInlineMemcpy(
     // if count is 0, there's nothing to copy
     _ = try func.addInst(.{
         .tag = .beq,
-        .ops = .rr_inst,
         .data = .{ .b_type = .{
             .rs1 = count,
             .rs2 = .zero,
@@ -6127,7 +6018,6 @@ fn genInlineMemcpy(
     // lb tmp, 0(src)
     const first_inst = try func.addInst(.{
         .tag = .lb,
-        .ops = .rri,
         .data = .{
             .i_type = .{
                 .rd = tmp,
@@ -6140,7 +6030,6 @@ fn genInlineMemcpy(
     // sb tmp, 0(dst)
     _ = try func.addInst(.{
         .tag = .sb,
-        .ops = .rri,
         .data = .{
             .i_type = .{
                 .rd = dst,
@@ -6153,7 +6042,6 @@ fn genInlineMemcpy(
     // dec count by 1
     _ = try func.addInst(.{
         .tag = .addi,
-        .ops = .rri,
         .data = .{
             .i_type = .{
                 .rd = count,
@@ -6166,7 +6054,6 @@ fn genInlineMemcpy(
     // branch if count is 0
     _ = try func.addInst(.{
         .tag = .beq,
-        .ops = .rr_inst,
         .data = .{
             .b_type = .{
                 .inst = @intCast(func.mir_instructions.len + 4), // points after the last inst
@@ -6179,7 +6066,6 @@ fn genInlineMemcpy(
     // increment the pointers
     _ = try func.addInst(.{
         .tag = .addi,
-        .ops = .rri,
         .data = .{
             .i_type = .{
                 .rd = src,
@@ -6191,7 +6077,6 @@ fn genInlineMemcpy(
 
     _ = try func.addInst(.{
         .tag = .addi,
-        .ops = .rri,
         .data = .{
             .i_type = .{
                 .rd = dst,
@@ -6203,9 +6088,11 @@ fn genInlineMemcpy(
 
     // jump back to start of loop
     _ = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_j,
-        .data = .{ .inst = first_inst },
+        .tag = .pseudo_j,
+        .data = .{ .j_type = .{
+            .rd = .zero,
+            .inst = first_inst,
+        } },
     });
 }
 
@@ -6230,7 +6117,6 @@ fn genInlineMemset(
     // sb src, 0(dst)
     const first_inst = try func.addInst(.{
         .tag = .sb,
-        .ops = .rri,
         .data = .{
             .i_type = .{
                 .rd = dst,
@@ -6243,7 +6129,6 @@ fn genInlineMemset(
     // dec count by 1
     _ = try func.addInst(.{
         .tag = .addi,
-        .ops = .rri,
         .data = .{
             .i_type = .{
                 .rd = count,
@@ -6256,7 +6141,6 @@ fn genInlineMemset(
     // branch if count is 0
     _ = try func.addInst(.{
         .tag = .beq,
-        .ops = .rr_inst,
         .data = .{
             .b_type = .{
                 .inst = @intCast(func.mir_instructions.len + 4), // points after the last inst
@@ -6269,7 +6153,6 @@ fn genInlineMemset(
     // increment the pointers
     _ = try func.addInst(.{
         .tag = .addi,
-        .ops = .rri,
         .data = .{
             .i_type = .{
                 .rd = dst,
@@ -6281,11 +6164,11 @@ fn genInlineMemset(
 
     // jump back to start of loop
     _ = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_j,
-        .data = .{
+        .tag = .pseudo_j,
+        .data = .{ .j_type = .{
+            .rd = .zero,
             .inst = first_inst,
-        },
+        } },
     });
 }
 
@@ -6331,7 +6214,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
             if (math.minInt(i12) <= x and x <= math.maxInt(i12)) {
                 _ = try func.addInst(.{
                     .tag = .addi,
-                    .ops = .rri,
                     .data = .{ .i_type = .{
                         .rd = reg,
                         .rs1 = .zero,
@@ -6345,7 +6227,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
 
                 _ = try func.addInst(.{
                     .tag = .lui,
-                    .ops = .ri,
                     .data = .{ .u_type = .{
                         .rd = reg,
                         .imm20 = Immediate.s(hi20),
@@ -6353,7 +6234,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
                 });
                 _ = try func.addInst(.{
                     .tag = .addi,
-                    .ops = .rri,
                     .data = .{ .i_type = .{
                         .rd = reg,
                         .rs1 = reg,
@@ -6376,7 +6256,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
 
                 _ = try func.addInst(.{
                     .tag = .slli,
-                    .ops = .rri,
                     .data = .{ .i_type = .{
                         .rd = reg,
                         .rs1 = reg,
@@ -6386,7 +6265,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
 
                 _ = try func.addInst(.{
                     .tag = .add,
-                    .ops = .rrr,
                     .data = .{ .r_type = .{
                         .rd = reg,
                         .rs1 = reg,
@@ -6423,8 +6301,7 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
 
             // mv reg, src_reg
             _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_mv,
+                .tag = .pseudo_mv,
                 .data = .{ .rr = .{
                     .rd = reg,
                     .rs = src_reg,
@@ -6445,8 +6322,7 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
                 try func.genCopy(ty, .{ .register = reg }, .{ .indirect = .{ .reg = addr_reg } });
             } else {
                 _ = try func.addInst(.{
-                    .tag = .pseudo,
-                    .ops = .pseudo_load_rm,
+                    .tag = .pseudo_load_rm,
                     .data = .{ .rm = .{
                         .r = reg,
                         .m = .{
@@ -6466,7 +6342,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
 
             _ = try func.addInst(.{
                 .tag = .ld,
-                .ops = .rri,
                 .data = .{ .i_type = .{
                     .rd = reg,
                     .rs1 = reg,
@@ -6476,8 +6351,7 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
         },
         .lea_frame, .register_offset => {
             _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_lea_rm,
+                .tag = .pseudo_lea_rm,
                 .data = .{
                     .rm = .{
                         .r = reg,
@@ -6505,7 +6379,7 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
             });
         },
         .indirect => |reg_off| {
-            const load_tag: Mir.Inst.Tag = switch (reg.class()) {
+            const load_tag: Mnemonic = switch (reg.class()) {
                 .float => switch (abi_size) {
                     1 => unreachable, // Zig does not support 8-bit floats
                     2 => return func.fail("TODO: genSetReg indirect 16-bit float", .{}),
@@ -6544,8 +6418,7 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
                     });
 
                     _ = try func.addInst(.{
-                        .tag = .pseudo,
-                        .ops = .pseudo_load_rm,
+                        .tag = .pseudo_load_rm,
                         .data = .{ .rm = .{
                             .r = reg,
                             .m = .{
@@ -6565,7 +6438,6 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
 
             _ = try func.addInst(.{
                 .tag = load_tag,
-                .ops = .rri,
                 .data = .{ .i_type = .{
                     .rd = reg,
                     .rs1 = reg_off.reg,
@@ -6578,13 +6450,12 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError!
             const atom_index = try func.owner.getSymbolIndex(func);
 
             _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_load_symbol,
-                .data = .{ .payload = try func.addExtra(Mir.LoadSymbolPayload{
-                    .register = reg.encodeId(),
+                .tag = .pseudo_load_symbol,
+                .data = .{ .reloc = .{
+                    .register = reg,
                     .atom_index = atom_index,
                     .sym_index = sym_off.sym,
-                }) },
+                } },
             });
         },
         .load_symbol => {
@@ -6676,8 +6547,7 @@ fn genSetMem(
                 });
 
                 _ = try func.addInst(.{
-                    .tag = .pseudo,
-                    .ops = .pseudo_store_rm,
+                    .tag = .pseudo_store_rm,
                     .data = .{ .rm = .{
                         .r = reg,
                         .m = .{
@@ -6716,8 +6586,7 @@ fn genSetMem(
                 }));
                 const frame_mcv: MCValue = .{ .load_frame = .{ .index = frame_index } };
                 _ = try func.addInst(.{
-                    .tag = .pseudo,
-                    .ops = .pseudo_store_rm,
+                    .tag = .pseudo_store_rm,
                     .data = .{ .rm = .{
                         .r = reg,
                         .m = .{
@@ -6732,8 +6601,7 @@ fn genSetMem(
                 try func.genSetMem(base, disp, ty, frame_mcv);
                 try func.freeValue(frame_mcv);
             } else _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_store_rm,
+                .tag = .pseudo_store_rm,
                 .data = .{ .rm = .{
                     .r = reg,
                     .m = .{
@@ -6852,9 +6720,59 @@ fn airFloatFromInt(func: *Func, inst: Air.Inst.Index) !void {
 
 fn airIntFromFloat(func: *Func, inst: Air.Inst.Index) !void {
     const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
-    const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else return func.fail("TODO implement airIntFromFloat for {}", .{
-        func.target.cpu.arch,
-    });
+    const result: MCValue = if (func.liveness.isUnused(inst)) .unreach else result: {
+        const pt = func.pt;
+        const zcu = pt.zcu;
+
+        const operand = try func.resolveInst(ty_op.operand);
+        const src_ty = func.typeOf(ty_op.operand);
+        const dst_ty = func.typeOfIndex(inst);
+
+        const is_unsigned = dst_ty.isUnsignedInt(zcu);
+        const src_bits = src_ty.bitSize(pt);
+        const dst_bits = dst_ty.bitSize(pt);
+
+        const float_mod: enum { s, d } = switch (src_bits) {
+            32 => .s,
+            64 => .d,
+            else => return func.fail("TODO: airIntFromFloat src size {d}", .{src_bits}),
+        };
+
+        const int_mod: Mir.FcvtOp = switch (dst_bits) {
+            32 => if (is_unsigned) .wu else .w,
+            64 => if (is_unsigned) .lu else .l,
+            else => return func.fail("TODO: airIntFromFloat dst size: {d}", .{dst_bits}),
+        };
+
+        const src_reg, const src_lock = try func.promoteReg(src_ty, operand);
+        defer if (src_lock) |lock| func.register_manager.unlockReg(lock);
+
+        const dst_reg, const dst_lock = try func.allocReg(.int);
+        defer func.register_manager.unlockReg(dst_lock);
+
+        _ = try func.addInst(.{
+            .tag = switch (float_mod) {
+                .s => switch (int_mod) {
+                    .l => .fcvtls,
+                    .lu => .fcvtlus,
+                    .w => .fcvtws,
+                    .wu => .fcvtwus,
+                },
+                .d => switch (int_mod) {
+                    .l => .fcvtld,
+                    .lu => .fcvtlud,
+                    .w => .fcvtwd,
+                    .wu => .fcvtwud,
+                },
+            },
+            .data = .{ .rr = .{
+                .rd = dst_reg,
+                .rs = src_reg,
+            } },
+        });
+
+        break :result .{ .register = dst_reg };
+    };
     return func.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
@@ -6917,8 +6835,7 @@ fn airAtomicRmw(func: *Func, inst: Air.Inst.Index) !void {
     };
 
     _ = try func.addInst(.{
-        .tag = .pseudo,
-        .ops = .pseudo_amo,
+        .tag = .pseudo_amo,
         .data = .{ .amo = .{
             .rd = result_mcv.register,
             .rs1 = ptr_register,
@@ -6961,15 +6878,12 @@ fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void {
 
     if (order == .seq_cst) {
         _ = try func.addInst(.{
-            .tag = .pseudo,
-            .ops = .pseudo_fence,
-            .data = .{
-                .fence = .{
-                    .pred = .rw,
-                    .succ = .rw,
-                    .fm = .none,
-                },
-            },
+            .tag = .pseudo_fence,
+            .data = .{ .fence = .{
+                .pred = .rw,
+                .succ = .rw,
+                .fm = .none,
+            } },
         });
     }
 
@@ -6982,8 +6896,7 @@ fn airAtomicLoad(func: *Func, inst: Air.Inst.Index) !void {
         // Make sure all previous reads happen before any reading or writing accurs.
         .seq_cst, .acquire => {
             _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_fence,
+                .tag = .pseudo_fence,
                 .data = .{
                     .fence = .{
                         .pred = .r,
@@ -7015,8 +6928,7 @@ fn airAtomicStore(func: *Func, inst: Air.Inst.Index, order: std.builtin.AtomicOr
         .unordered, .monotonic => {},
         .release, .seq_cst => {
             _ = try func.addInst(.{
-                .tag = .pseudo,
-                .ops = .pseudo_fence,
+                .tag = .pseudo_fence,
                 .data = .{
                     .fence = .{
                         .pred = .rw,
@@ -7183,7 +7095,6 @@ fn airTagName(func: *Func, inst: Air.Inst.Index) !void {
             try func.genSetReg(Type.u64, .ra, .{ .load_symbol = .{ .sym = sym.esym_index } });
             _ = try func.addInst(.{
                 .tag = .jalr,
-                .ops = .rri,
                 .data = .{ .i_type = .{
                     .rd = .ra,
                     .rs1 = .ra,
src/arch/riscv64/Emit.zig
@@ -40,7 +40,7 @@ pub fn emitMir(emit: *Emit) Error!void {
                     .source = start_offset,
                     .target = target,
                     .offset = 0,
-                    .enc = std.meta.activeTag(lowered_inst.encoding.data),
+                    .fmt = std.meta.activeTag(lowered_inst),
                 }),
                 .load_symbol_reloc => |symbol| {
                     const is_obj_or_static_lib = switch (emit.lower.output_mode) {
@@ -49,46 +49,45 @@ pub fn emitMir(emit: *Emit) Error!void {
                         .Lib => emit.lower.link_mode == .static,
                     };
 
-                    if (emit.bin_file.cast(link.File.Elf)) |elf_file| {
-                        const atom_ptr = elf_file.symbol(symbol.atom_index).atom(elf_file).?;
-                        const sym_index = elf_file.zigObjectPtr().?.symbol(symbol.sym_index);
-                        const sym = elf_file.symbol(sym_index);
-
-                        var hi_r_type: u32 = @intFromEnum(std.elf.R_RISCV.HI20);
-                        var lo_r_type: u32 = @intFromEnum(std.elf.R_RISCV.LO12_I);
-
-                        if (sym.flags.needs_zig_got and !is_obj_or_static_lib) {
-                            _ = try sym.getOrCreateZigGotEntry(sym_index, elf_file);
-
-                            hi_r_type = Elf.R_ZIG_GOT_HI20;
-                            lo_r_type = Elf.R_ZIG_GOT_LO12;
-                        }
-
-                        try atom_ptr.addReloc(elf_file, .{
-                            .r_offset = start_offset,
-                            .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | hi_r_type,
-                            .r_addend = 0,
-                        });
-
-                        try atom_ptr.addReloc(elf_file, .{
-                            .r_offset = start_offset + 4,
-                            .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | lo_r_type,
-                            .r_addend = 0,
-                        });
-                    } else unreachable;
+                    const elf_file = emit.bin_file.cast(link.File.Elf).?;
+
+                    const atom_ptr = elf_file.symbol(symbol.atom_index).atom(elf_file).?;
+                    const sym_index = elf_file.zigObjectPtr().?.symbol(symbol.sym_index);
+                    const sym = elf_file.symbol(sym_index);
+
+                    var hi_r_type: u32 = @intFromEnum(std.elf.R_RISCV.HI20);
+                    var lo_r_type: u32 = @intFromEnum(std.elf.R_RISCV.LO12_I);
+
+                    if (sym.flags.needs_zig_got and !is_obj_or_static_lib) {
+                        _ = try sym.getOrCreateZigGotEntry(sym_index, elf_file);
+
+                        hi_r_type = Elf.R_ZIG_GOT_HI20;
+                        lo_r_type = Elf.R_ZIG_GOT_LO12;
+                    }
+
+                    try atom_ptr.addReloc(elf_file, .{
+                        .r_offset = start_offset,
+                        .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | hi_r_type,
+                        .r_addend = 0,
+                    });
+
+                    try atom_ptr.addReloc(elf_file, .{
+                        .r_offset = start_offset + 4,
+                        .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | lo_r_type,
+                        .r_addend = 0,
+                    });
                 },
                 .call_extern_fn_reloc => |symbol| {
-                    if (emit.bin_file.cast(link.File.Elf)) |elf_file| {
-                        const atom_ptr = elf_file.symbol(symbol.atom_index).atom(elf_file).?;
+                    const elf_file = emit.bin_file.cast(link.File.Elf).?;
+                    const atom_ptr = elf_file.symbol(symbol.atom_index).atom(elf_file).?;
 
-                        const r_type: u32 = @intFromEnum(std.elf.R_RISCV.CALL_PLT);
+                    const r_type: u32 = @intFromEnum(std.elf.R_RISCV.CALL_PLT);
 
-                        try atom_ptr.addReloc(elf_file, .{
-                            .r_offset = start_offset,
-                            .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | r_type,
-                            .r_addend = 0,
-                        });
-                    } else return emit.fail("TODO: call_extern_fn_reloc non-ELF", .{});
+                    try atom_ptr.addReloc(elf_file, .{
+                        .r_offset = start_offset,
+                        .r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | r_type,
+                        .r_addend = 0,
+                    });
                 },
             };
         }
@@ -98,40 +97,37 @@ pub fn emitMir(emit: *Emit) Error!void {
             const mir_inst = emit.lower.mir.instructions.get(mir_index);
             switch (mir_inst.tag) {
                 else => unreachable,
-                .pseudo => switch (mir_inst.ops) {
-                    else => unreachable,
-                    .pseudo_dbg_prologue_end => {
-                        switch (emit.debug_output) {
-                            .dwarf => |dw| {
-                                try dw.setPrologueEnd();
-                                log.debug("mirDbgPrologueEnd (line={d}, col={d})", .{
-                                    emit.prev_di_line, emit.prev_di_column,
-                                });
-                                try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
-                            },
-                            .plan9 => {},
-                            .none => {},
-                        }
-                    },
-                    .pseudo_dbg_line_column => try emit.dbgAdvancePCAndLine(
-                        mir_inst.data.pseudo_dbg_line_column.line,
-                        mir_inst.data.pseudo_dbg_line_column.column,
-                    ),
-                    .pseudo_dbg_epilogue_begin => {
-                        switch (emit.debug_output) {
-                            .dwarf => |dw| {
-                                try dw.setEpilogueBegin();
-                                log.debug("mirDbgEpilogueBegin (line={d}, col={d})", .{
-                                    emit.prev_di_line, emit.prev_di_column,
-                                });
-                                try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
-                            },
-                            .plan9 => {},
-                            .none => {},
-                        }
-                    },
-                    .pseudo_dead => {},
+                .pseudo_dbg_prologue_end => {
+                    switch (emit.debug_output) {
+                        .dwarf => |dw| {
+                            try dw.setPrologueEnd();
+                            log.debug("mirDbgPrologueEnd (line={d}, col={d})", .{
+                                emit.prev_di_line, emit.prev_di_column,
+                            });
+                            try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
+                        },
+                        .plan9 => {},
+                        .none => {},
+                    }
                 },
+                .pseudo_dbg_line_column => try emit.dbgAdvancePCAndLine(
+                    mir_inst.data.pseudo_dbg_line_column.line,
+                    mir_inst.data.pseudo_dbg_line_column.column,
+                ),
+                .pseudo_dbg_epilogue_begin => {
+                    switch (emit.debug_output) {
+                        .dwarf => |dw| {
+                            try dw.setEpilogueBegin();
+                            log.debug("mirDbgEpilogueBegin (line={d}, col={d})", .{
+                                emit.prev_di_line, emit.prev_di_column,
+                            });
+                            try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
+                        },
+                        .plan9 => {},
+                        .none => {},
+                    }
+                },
+                .pseudo_dead => {},
             }
         }
     }
@@ -151,8 +147,8 @@ const Reloc = struct {
     target: Mir.Inst.Index,
     /// Offset of the relocation within the instruction.
     offset: u32,
-    /// Encoding of the instruction, used to determine how to modify it.
-    enc: Encoding.InstEnc,
+    /// Format of the instruction, used to determine how to modify it.
+    fmt: encoding.Lir.Format,
 };
 
 fn fixupRelocs(emit: *Emit) Error!void {
@@ -164,12 +160,10 @@ fn fixupRelocs(emit: *Emit) Error!void {
         const disp = @as(i32, @intCast(target)) - @as(i32, @intCast(reloc.source));
         const code: *[4]u8 = emit.code.items[reloc.source + reloc.offset ..][0..4];
 
-        log.debug("disp: {x}", .{disp});
-
-        switch (reloc.enc) {
+        switch (reloc.fmt) {
             .J => riscv_util.writeInstJ(code, @bitCast(disp)),
             .B => riscv_util.writeInstB(code, @bitCast(disp)),
-            else => return emit.fail("tried to reloc encoding type {s}", .{@tagName(reloc.enc)}),
+            else => return emit.fail("tried to reloc format type {s}", .{@tagName(reloc.fmt)}),
         }
     }
 }
@@ -209,5 +203,5 @@ const Emit = @This();
 const Lower = @import("Lower.zig");
 const Mir = @import("Mir.zig");
 const riscv_util = @import("../../link/riscv.zig");
-const Encoding = @import("Encoding.zig");
 const Elf = @import("../../link/Elf.zig");
+const encoding = @import("encoding.zig");
src/arch/riscv64/encoder.zig
@@ -1,80 +0,0 @@
-pub const Instruction = struct {
-    encoding: Encoding,
-    ops: [5]Operand = .{.none} ** 5,
-
-    pub const Operand = union(enum) {
-        none,
-        reg: Register,
-        csr: CSR,
-        mem: Memory,
-        imm: Immediate,
-        barrier: Mir.Barrier,
-    };
-
-    pub fn new(mnemonic: Encoding.Mnemonic, ops: []const Operand) !Instruction {
-        const encoding = (try Encoding.findByMnemonic(mnemonic, ops)) orelse {
-            std.log.err("no encoding found for:  {s} [{s} {s} {s} {s} {s}]", .{
-                @tagName(mnemonic),
-                @tagName(if (ops.len > 0) ops[0] else .none),
-                @tagName(if (ops.len > 1) ops[1] else .none),
-                @tagName(if (ops.len > 2) ops[2] else .none),
-                @tagName(if (ops.len > 3) ops[3] else .none),
-                @tagName(if (ops.len > 4) ops[4] else .none),
-            });
-            return error.InvalidInstruction;
-        };
-
-        var result_ops: [5]Operand = .{.none} ** 5;
-        @memcpy(result_ops[0..ops.len], ops);
-
-        return .{
-            .encoding = encoding,
-            .ops = result_ops,
-        };
-    }
-
-    pub fn encode(inst: Instruction, writer: anytype) !void {
-        try writer.writeInt(u32, inst.encoding.data.toU32(), .little);
-    }
-
-    pub fn format(
-        inst: Instruction,
-        comptime fmt: []const u8,
-        _: std.fmt.FormatOptions,
-        writer: anytype,
-    ) !void {
-        std.debug.assert(fmt.len == 0);
-
-        const encoding = inst.encoding;
-
-        try writer.print("{s} ", .{@tagName(encoding.mnemonic)});
-
-        var i: u32 = 0;
-        while (i < inst.ops.len and inst.ops[i] != .none) : (i += 1) {
-            if (i != inst.ops.len and i != 0) try writer.writeAll(", ");
-
-            switch (@as(Instruction.Operand, inst.ops[i])) {
-                .none => unreachable, // it's sliced out above
-                .reg => |reg| try writer.writeAll(@tagName(reg)),
-                .imm => |imm| try writer.print("{d}", .{imm.asSigned(64)}),
-                .mem => try writer.writeAll("mem"),
-                .barrier => |barrier| try writer.writeAll(@tagName(barrier)),
-                .csr => |csr| try writer.writeAll(@tagName(csr)),
-            }
-        }
-    }
-};
-
-const std = @import("std");
-
-const Lower = @import("Lower.zig");
-const Mir = @import("Mir.zig");
-const bits = @import("bits.zig");
-const Encoding = @import("Encoding.zig");
-
-const Register = bits.Register;
-const CSR = bits.CSR;
-const Memory = bits.Memory;
-const Immediate = bits.Immediate;
-
-const log = std.log.scoped(.encode);
src/arch/riscv64/Encoding.zig
@@ -1,1136 +0,0 @@
-mnemonic: Mnemonic,
-data: Data,
-
-const OpCode = enum(u7) {
-    LOAD = 0b0000011,
-    LOAD_FP = 0b0000111,
-    MISC_MEM = 0b0001111,
-    OP_IMM = 0b0010011,
-    AUIPC = 0b0010111,
-    OP_IMM_32 = 0b0011011,
-    STORE = 0b0100011,
-    STORE_FP = 0b0100111,
-    AMO = 0b0101111,
-    OP_V = 0b1010111,
-    OP = 0b0110011,
-    OP_32 = 0b0111011,
-    LUI = 0b0110111,
-    MADD = 0b1000011,
-    MSUB = 0b1000111,
-    NMSUB = 0b1001011,
-    NMADD = 0b1001111,
-    OP_FP = 0b1010011,
-    OP_IMM_64 = 0b1011011,
-    BRANCH = 0b1100011,
-    JALR = 0b1100111,
-    JAL = 0b1101111,
-    SYSTEM = 0b1110011,
-    OP_64 = 0b1111011,
-    NONE = 0b00000000,
-};
-
-const FpFmt = enum(u2) {
-    /// 32-bit single-precision
-    S = 0b00,
-    /// 64-bit double-precision
-    D = 0b01,
-
-    // H = 0b10, unused in the G extension
-
-    /// 128-bit quad-precision
-    Q = 0b11,
-};
-
-const AmoWidth = enum(u3) {
-    W = 0b010,
-    D = 0b011,
-};
-
-const FenceMode = enum(u4) {
-    none = 0b0000,
-    tso = 0b1000,
-};
-
-const Enc = struct {
-    opcode: OpCode,
-
-    data: union(enum) {
-        /// funct3 + funct7
-        ff: struct {
-            funct3: u3,
-            funct7: u7,
-        },
-        amo: struct {
-            funct5: u5,
-            width: AmoWidth,
-        },
-        fence: struct {
-            funct3: u3,
-            fm: FenceMode,
-        },
-        /// funct5 + rm + fmt
-        fmt: struct {
-            funct5: u5,
-            rm: u3,
-            fmt: FpFmt,
-        },
-        /// funct3
-        f: struct {
-            funct3: u3,
-        },
-        /// typ + funct3 + has_5
-        sh: struct {
-            typ: u6,
-            funct3: u3,
-            has_5: bool,
-        },
-        vecls: struct {
-            width: VecWidth,
-            umop: Umop,
-            vm: bool,
-            mop: Mop,
-            mew: bool,
-            nf: u3,
-        },
-        vecmath: struct {
-            vm: bool,
-            funct6: u6,
-            funct3: VecType,
-        },
-        /// U-type
-        none,
-    },
-
-    const Mop = enum(u2) {
-        unit = 0b00,
-        unord = 0b01,
-        stride = 0b10,
-        ord = 0b11,
-    };
-
-    const Umop = enum(u5) {
-        unit = 0b00000,
-        whole = 0b01000,
-        mask = 0b01011,
-        fault = 0b10000,
-    };
-
-    const VecWidth = enum(u3) {
-        // zig fmt: off
-        @"8"  = 0b000,
-        @"16" = 0b101,
-        @"32" = 0b110,
-        @"64" = 0b111,
-        // zig fmt: on
-    };
-
-    const VecType = enum(u3) {
-        OPIVV = 0b000,
-        OPFVV = 0b001,
-        OPMVV = 0b010,
-        OPIVI = 0b011,
-        OPIVX = 0b100,
-        OPFVF = 0b101,
-        OPMVX = 0b110,
-    };
-};
-
-// TODO: this is basically a copy of the MIR table, we should be able to de-dupe them somehow.
-pub const Mnemonic = enum {
-    // base mnemonics
-
-    // I Type
-    ld,
-    lw,
-    lwu,
-    lh,
-    lhu,
-    lb,
-    lbu,
-
-    sltiu,
-    xori,
-    andi,
-
-    slli,
-    srli,
-    srai,
-
-    slliw,
-    srliw,
-    sraiw,
-
-    addi,
-    jalr,
-
-    vsetivli,
-    vsetvli,
-
-    // U Type
-    lui,
-    auipc,
-
-    // S Type
-    sd,
-    sw,
-    sh,
-    sb,
-
-    // J Type
-    jal,
-
-    // B Type
-    beq,
-
-    // R Type
-    add,
-    addw,
-    sub,
-    subw,
-    @"and",
-    @"or",
-    slt,
-    sltu,
-    xor,
-
-    sll,
-    srl,
-    sra,
-
-    sllw,
-    srlw,
-    sraw,
-
-    // System
-    ecall,
-    ebreak,
-    unimp,
-
-    csrrs,
-
-    // M extension
-    mul,
-    mulw,
-
-    mulh,
-    mulhu,
-    mulhsu,
-
-    div,
-    divu,
-
-    divw,
-    divuw,
-
-    rem,
-    remu,
-
-    remw,
-    remuw,
-
-    // F extension (32-bit float)
-    fadds,
-    fsubs,
-    fmuls,
-    fdivs,
-
-    fmins,
-    fmaxs,
-
-    fsqrts,
-
-    flw,
-    fsw,
-
-    feqs,
-    flts,
-    fles,
-
-    fsgnjns,
-    fsgnjxs,
-
-    // D extension (64-bit float)
-    faddd,
-    fsubd,
-    fmuld,
-    fdivd,
-
-    fmind,
-    fmaxd,
-
-    fsqrtd,
-
-    fld,
-    fsd,
-
-    feqd,
-    fltd,
-    fled,
-
-    fsgnjnd,
-    fsgnjxd,
-
-    // V Extension
-    vle8v,
-    vle16v,
-    vle32v,
-    vle64v,
-
-    vse8v,
-    vse16v,
-    vse32v,
-    vse64v,
-
-    vsoxei8v,
-
-    vaddvv,
-    vsubvv,
-
-    vfaddvv,
-    vfsubvv,
-
-    vmulvv,
-    vfmulvv,
-
-    vadcvv,
-
-    vmvvx,
-
-    vslidedownvx,
-
-    // MISC
-    fence,
-    fencetso,
-
-    // AMO
-    amoswapw,
-    amoaddw,
-    amoandw,
-    amoorw,
-    amoxorw,
-    amomaxw,
-    amominw,
-    amomaxuw,
-    amominuw,
-
-    amoswapd,
-    amoaddd,
-    amoandd,
-    amoord,
-    amoxord,
-    amomaxd,
-    amomind,
-    amomaxud,
-    amominud,
-
-    // TODO: Q extension
-
-    // Zbb Extension
-    clz,
-    clzw,
-
-    pub fn encoding(mnem: Mnemonic) Enc {
-        return switch (mnem) {
-            // zig fmt: off
-
-            // OP
-
-            .add     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0000000 } } },
-            .sub     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0100000 } } }, 
-
-            .@"and"  => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b111, .funct7 = 0b0000000 } } },
-            .@"or"   => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b110, .funct7 = 0b0000000 } } },
-            .xor     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b100, .funct7 = 0b0000000 } } },
-
-            .sltu    => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b011, .funct7 = 0b0000000 } } },
-            .slt     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b010, .funct7 = 0b0000000 } } },
-
-            .mul     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0000001 } } },
-            .mulh    => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0000001 } } },
-            .mulhsu  => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b010, .funct7 = 0b0000001 } } },
-            .mulhu   => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b011, .funct7 = 0b0000001 } } },
- 
-            .div     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b100, .funct7 = 0b0000001 } } },
-            .divu    => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0000001 } } },
-
-            .rem     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b110, .funct7 = 0b0000001 } } },
-            .remu    => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b111, .funct7 = 0b0000001 } } },
-
-            .sll     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0000000 } } },
-            .srl     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0000000 } } },
-            .sra     => .{ .opcode = .OP, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0100000 } } },
-
-
-            // OP_IMM
-
-            .addi    => .{ .opcode = .OP_IMM, .data = .{ .f = .{ .funct3 = 0b000 } } },
-            .andi    => .{ .opcode = .OP_IMM, .data = .{ .f = .{ .funct3 = 0b111 } } },
-            .xori    => .{ .opcode = .OP_IMM, .data = .{ .f = .{ .funct3 = 0b100 } } },
-            
-            .sltiu   => .{ .opcode = .OP_IMM, .data = .{ .f = .{ .funct3 = 0b011 } } },
-
-            .slli    => .{ .opcode = .OP_IMM, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b001, .has_5 = true } } },
-            .srli    => .{ .opcode = .OP_IMM, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b101, .has_5 = true } } },
-            .srai    => .{ .opcode = .OP_IMM, .data = .{ .sh = .{ .typ = 0b010000, .funct3 = 0b101, .has_5 = true } } },
-
-            .clz     => .{ .opcode = .OP_IMM, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0110000 } } },
-
-            // OP_IMM_32
-
-            .slliw   => .{ .opcode = .OP_IMM_32, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b001, .has_5 = false } } },
-            .srliw   => .{ .opcode = .OP_IMM_32, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b101, .has_5 = false } } },
-            .sraiw   => .{ .opcode = .OP_IMM_32, .data = .{ .sh = .{ .typ = 0b010000, .funct3 = 0b101, .has_5 = false } } },
-
-            .clzw     => .{ .opcode = .OP_IMM_32, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0110000 } } },
-
-            // OP_32
-
-            .addw    => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0000000 } } },
-            .subw    => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0100000 } } },
-            .mulw    => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0000001 } } }, 
-
-            .divw    => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b100, .funct7 = 0b0000001 } } },
-            .divuw   => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0000001 } } },
-
-            .remw    => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b110, .funct7 = 0b0000001 } } },
-            .remuw   => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b111, .funct7 = 0b0000001 } } },
-
-            .sllw    => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0000000 } } },
-            .srlw    => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0000000 } } },
-            .sraw    => .{ .opcode = .OP_32, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0100000 } } },
-
-
-            // OP_FP
-
-            .fadds   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00000, .fmt = .S, .rm = 0b111 } } },
-            .faddd   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00000, .fmt = .D, .rm = 0b111 } } },
-
-            .fsubs   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00001, .fmt = .S, .rm = 0b111 } } },
-            .fsubd   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00001, .fmt = .D, .rm = 0b111 } } },
-
-            .fmuls   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00010, .fmt = .S, .rm = 0b111 } } },
-            .fmuld   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00010, .fmt = .D, .rm = 0b111 } } },
-
-            .fdivs   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00011, .fmt = .S, .rm = 0b111 } } },
-            .fdivd   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00011, .fmt = .D, .rm = 0b111 } } },
-
-            .fmins   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00101, .fmt = .S, .rm = 0b000 } } },
-            .fmind   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00101, .fmt = .D, .rm = 0b000 } } },
-
-            .fmaxs   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00101, .fmt = .S, .rm = 0b001 } } },
-            .fmaxd   => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00101, .fmt = .D, .rm = 0b001 } } },
-
-            .fsqrts  => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b01011, .fmt = .S, .rm = 0b111 } } },
-            .fsqrtd  => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b01011, .fmt = .D, .rm = 0b111 } } },
-
-            .fles    => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .S, .rm = 0b000 } } },
-            .fled    => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .D, .rm = 0b000 } } },
-
-            .flts    => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .S, .rm = 0b001 } } },
-            .fltd    => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .D, .rm = 0b001 } } },
-
-            .feqs    => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .S, .rm = 0b010 } } },
-            .feqd    => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .D, .rm = 0b010 } } },
-
-            .fsgnjns => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00100, .fmt = .S, .rm = 0b000 } } },
-            .fsgnjnd => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00100, .fmt = .D, .rm = 0b000 } } },
-
-            .fsgnjxs => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00100, .fmt = .S, .rm = 0b0010} } },
-            .fsgnjxd => .{ .opcode = .OP_FP, .data = .{ .fmt = .{ .funct5 = 0b00100, .fmt = .D, .rm = 0b0010} } },
-
-
-            // LOAD
-
-            .lb      => .{ .opcode = .LOAD, .data = .{ .f = .{ .funct3 = 0b000 } } },
-            .lh      => .{ .opcode = .LOAD, .data = .{ .f = .{ .funct3 = 0b001 } } },
-            .lw      => .{ .opcode = .LOAD, .data = .{ .f = .{ .funct3 = 0b010 } } },
-            .ld      => .{ .opcode = .LOAD, .data = .{ .f = .{ .funct3 = 0b011 } } },
-            .lbu     => .{ .opcode = .LOAD, .data = .{ .f = .{ .funct3 = 0b100 } } },
-            .lhu     => .{ .opcode = .LOAD, .data = .{ .f = .{ .funct3 = 0b101 } } },
-            .lwu     => .{ .opcode = .LOAD, .data = .{ .f = .{ .funct3 = 0b110 } } },
-
-
-            // STORE
-            
-            .sb      => .{ .opcode = .STORE, .data = .{ .f = .{ .funct3 = 0b000 } } },
-            .sh      => .{ .opcode = .STORE, .data = .{ .f = .{ .funct3 = 0b001 } } },
-            .sw      => .{ .opcode = .STORE, .data = .{ .f = .{ .funct3 = 0b010 } } },
-            .sd      => .{ .opcode = .STORE, .data = .{ .f = .{ .funct3 = 0b011 } } },
-
-
-            // LOAD_FP
-
-            .flw     => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b010 } } },
-            .fld     => .{ .opcode = .LOAD_FP, .data = .{ .f = .{ .funct3 = 0b011 } } },   
-    
-            .vle8v   => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"8",  .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
-            .vle16v  => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"16", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
-            .vle32v  => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
-            .vle64v  => .{ .opcode = .LOAD_FP, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
-            
-
-            // STORE_FP
-
-            .fsw        => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b010 } } },
-            .fsd        => .{ .opcode = .STORE_FP, .data = .{ .f = .{ .funct3 = 0b011 } } },
-
-            .vse8v      => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"8",  .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
-            .vse16v     => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"16", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
-            .vse32v     => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
-            .vse64v     => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
-
-            .vsoxei8v   => .{ .opcode = .STORE_FP, .data = .{ .vecls = .{ .width = .@"8", .umop = .unit, .vm = true, .mop = .ord,  .mew = false, .nf = 0b000 } } },
-
-            // JALR
-
-            .jalr    => .{ .opcode = .JALR, .data = .{ .f = .{ .funct3 = 0b000 } } },
-
-
-            // LUI
-
-            .lui     => .{ .opcode = .LUI, .data = .{ .none = {} } },
-
-
-            // AUIPC
-
-            .auipc   => .{ .opcode = .AUIPC, .data = .{ .none = {} } },
-
-
-            // JAL
-
-            .jal     => .{ .opcode = .JAL, .data = .{ .none = {} } },
-
-
-            // BRANCH
-
-            .beq     => .{ .opcode = .BRANCH, .data = .{ .f = .{ .funct3 = 0b000 } } },
-
-
-            // SYSTEM
-
-            .ecall   => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b000 } } },
-            .ebreak  => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b000 } } },
-
-            .csrrs   => .{ .opcode = .SYSTEM, .data = .{ .f = .{ .funct3 = 0b010 } } },
-           
-
-            // NONE
-            
-            .unimp   => .{ .opcode = .NONE, .data = .{ .f = .{ .funct3 = 0b000 } } },
-
-
-            // MISC_MEM
-
-            .fence    => .{ .opcode = .MISC_MEM, .data = .{ .fence = .{ .funct3 = 0b000, .fm = .none } } },
-            .fencetso => .{ .opcode = .MISC_MEM, .data = .{ .fence = .{ .funct3 = 0b000, .fm = .tso  } } },
-
-
-            // AMO
-
-            .amoaddw   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00000 } } },
-            .amoswapw  => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00001 } } },
-            // LR.W
-            // SC.W
-            .amoxorw   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00100 } } },
-            .amoandw   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b01100 } } },
-            .amoorw    => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b01000 } } },
-            .amominw   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b10000 } } },
-            .amomaxw   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b10100 } } },
-            .amominuw  => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b11000 } } },
-            .amomaxuw  => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .W, .funct5 = 0b11100 } } },
-
-            .amoaddd   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00000 } } },
-            .amoswapd  => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00001 } } },
-            // LR.D
-            // SC.D
-            .amoxord   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00100 } } },
-            .amoandd   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b01100 } } },
-            .amoord    => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b01000 } } },
-            .amomind   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b10000 } } },
-            .amomaxd   => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b10100 } } },
-            .amominud  => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b11000 } } },
-            .amomaxud  => .{ .opcode = .AMO, .data = .{ .amo = .{ .width = .D, .funct5 = 0b11100 } } },
-
-            // OP_V
-            .vsetivli       => .{ .opcode = .OP_V, .data = .{ .f = .{ .funct3 = 0b111 } } },
-            .vsetvli        => .{ .opcode = .OP_V, .data = .{ .f = .{ .funct3 = 0b111 } } },
-            .vaddvv         => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000000, .funct3 = .OPIVV } } },
-            .vsubvv         => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000010, .funct3 = .OPIVV } } },
-            .vmulvv         => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b100101, .funct3 = .OPIVV } } },
-            
-            .vfaddvv         => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000000, .funct3 = .OPFVV } } },
-            .vfsubvv         => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000010, .funct3 = .OPFVV } } },
-            .vfmulvv         => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b100100, .funct3 = .OPFVV } } },
-            
-            .vadcvv         => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010000, .funct3 = .OPMVV } } },
-            .vmvvx          => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010111, .funct3 = .OPIVX } } },
-
-            .vslidedownvx   => .{ .opcode = .OP_V, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b001111, .funct3 = .OPIVX } } },
-            
-            // zig fmt: on
-        };
-    }
-};
-
-pub const InstEnc = enum {
-    R,
-    R4,
-    I,
-    S,
-    B,
-    U,
-    J,
-    fence,
-    amo,
-    system,
-
-    pub fn fromMnemonic(mnem: Mnemonic) InstEnc {
-        return switch (mnem) {
-            .addi,
-            .jalr,
-            .sltiu,
-            .xori,
-            .andi,
-
-            .slli,
-            .srli,
-            .srai,
-
-            .slliw,
-            .srliw,
-            .sraiw,
-
-            .ld,
-            .lw,
-            .lwu,
-            .lh,
-            .lhu,
-            .lb,
-            .lbu,
-
-            .flw,
-            .fld,
-
-            .csrrs,
-            .vsetivli,
-            .vsetvli,
-            => .I,
-
-            .lui,
-            .auipc,
-            => .U,
-
-            .sd,
-            .sw,
-            .sh,
-            .sb,
-
-            .fsd,
-            .fsw,
-            => .S,
-
-            .jal,
-            => .J,
-
-            .beq,
-            => .B,
-
-            .slt,
-            .sltu,
-
-            .sll,
-            .srl,
-            .sra,
-
-            .sllw,
-            .srlw,
-            .sraw,
-
-            .div,
-            .divu,
-            .divw,
-            .divuw,
-
-            .rem,
-            .remu,
-            .remw,
-            .remuw,
-
-            .xor,
-            .@"and",
-            .@"or",
-
-            .add,
-            .addw,
-
-            .sub,
-            .subw,
-
-            .mul,
-            .mulw,
-            .mulh,
-            .mulhu,
-            .mulhsu,
-
-            .fadds,
-            .faddd,
-
-            .fsubs,
-            .fsubd,
-
-            .fmuls,
-            .fmuld,
-
-            .fdivs,
-            .fdivd,
-
-            .fmins,
-            .fmind,
-
-            .fmaxs,
-            .fmaxd,
-
-            .fsqrts,
-            .fsqrtd,
-
-            .fles,
-            .fled,
-
-            .flts,
-            .fltd,
-
-            .feqs,
-            .feqd,
-
-            .fsgnjns,
-            .fsgnjnd,
-
-            .fsgnjxs,
-            .fsgnjxd,
-
-            .vle8v,
-            .vle16v,
-            .vle32v,
-            .vle64v,
-
-            .vse8v,
-            .vse16v,
-            .vse32v,
-            .vse64v,
-
-            .vsoxei8v,
-
-            .vaddvv,
-            .vsubvv,
-            .vmulvv,
-            .vfaddvv,
-            .vfsubvv,
-            .vfmulvv,
-            .vadcvv,
-            .vmvvx,
-            .vslidedownvx,
-
-            .clz,
-            .clzw,
-            => .R,
-
-            .ecall,
-            .ebreak,
-            .unimp,
-            => .system,
-
-            .fence,
-            .fencetso,
-            => .fence,
-
-            .amoswapw,
-            .amoaddw,
-            .amoandw,
-            .amoorw,
-            .amoxorw,
-            .amomaxw,
-            .amominw,
-            .amomaxuw,
-            .amominuw,
-
-            .amoswapd,
-            .amoaddd,
-            .amoandd,
-            .amoord,
-            .amoxord,
-            .amomaxd,
-            .amomind,
-            .amomaxud,
-            .amominud,
-            => .amo,
-        };
-    }
-
-    pub fn opsList(enc: InstEnc) [5]std.meta.FieldEnum(Operand) {
-        return switch (enc) {
-            // zig fmt: off
-            .R      => .{ .reg,     .reg,     .reg,  .none,    .none,   },
-            .R4     => .{ .reg,     .reg,     .reg,  .reg,     .none,   },  
-            .I      => .{ .reg,     .reg,     .imm,  .none,    .none,   },
-            .S      => .{ .reg,     .reg,     .imm,  .none,    .none,   },
-            .B      => .{ .reg,     .reg,     .imm,  .none,    .none,   },
-            .U      => .{ .reg,     .imm,     .none, .none,    .none,   },
-            .J      => .{ .reg,     .imm,     .none, .none,    .none,   },
-            .system => .{ .none,    .none,    .none, .none,    .none,   },
-            .fence  => .{ .barrier, .barrier, .none, .none,    .none,   },
-            .amo    => .{ .reg,     .reg,     .reg,  .barrier, .barrier },
-            // zig fmt: on
-        };
-    }
-};
-
-pub const Data = union(InstEnc) {
-    R: packed struct {
-        opcode: u7,
-        rd: u5,
-        funct3: u3,
-        rs1: u5,
-        rs2: u5,
-        funct7: u7,
-    },
-    R4: packed struct {
-        opcode: u7,
-        rd: u5,
-        funct3: u3,
-        rs1: u5,
-        rs2: u5,
-        funct2: u2,
-        rs3: u5,
-    },
-    I: packed struct {
-        opcode: u7,
-        rd: u5,
-        funct3: u3,
-        rs1: u5,
-        imm0_11: u12,
-    },
-    S: packed struct {
-        opcode: u7,
-        imm0_4: u5,
-        funct3: u3,
-        rs1: u5,
-        rs2: u5,
-        imm5_11: u7,
-    },
-    B: packed struct {
-        opcode: u7,
-        imm11: u1,
-        imm1_4: u4,
-        funct3: u3,
-        rs1: u5,
-        rs2: u5,
-        imm5_10: u6,
-        imm12: u1,
-    },
-    U: packed struct {
-        opcode: u7,
-        rd: u5,
-        imm12_31: u20,
-    },
-    J: packed struct {
-        opcode: u7,
-        rd: u5,
-        imm12_19: u8,
-        imm11: u1,
-        imm1_10: u10,
-        imm20: u1,
-    },
-    fence: packed struct {
-        opcode: u7,
-        rd: u5 = 0,
-        funct3: u3,
-        rs1: u5 = 0,
-        succ: u4,
-        pred: u4,
-        fm: u4,
-    },
-    amo: packed struct {
-        opcode: u7,
-        rd: u5,
-        funct3: u3,
-        rs1: u5,
-        rs2: u5,
-        rl: bool,
-        aq: bool,
-        funct5: u5,
-    },
-    system: u32,
-
-    comptime {
-        for (std.meta.fields(Data)) |field| {
-            assert(@bitSizeOf(field.type) == 32);
-        }
-    }
-
-    pub fn toU32(self: Data) u32 {
-        return switch (self) {
-            .fence => |v| @as(u32, @intCast(v.opcode)) + (@as(u32, @intCast(v.rd)) << 7) + (@as(u32, @intCast(v.funct3)) << 12) + (@as(u32, @intCast(v.rs1)) << 15) + (@as(u32, @intCast(v.succ)) << 20) + (@as(u32, @intCast(v.pred)) << 24) + (@as(u32, @intCast(v.fm)) << 28),
-            inline else => |v| @bitCast(v),
-            .system => unreachable,
-        };
-    }
-
-    pub fn construct(mnem: Mnemonic, ops: []const Operand) !Data {
-        const inst_enc = InstEnc.fromMnemonic(mnem);
-        const enc = mnem.encoding();
-
-        // special mnemonics
-        switch (mnem) {
-            .ecall,
-            .ebreak,
-            .unimp,
-            => {
-                assert(ops.len == 0);
-                return .{
-                    .I = .{
-                        .rd = Register.zero.encodeId(),
-                        .rs1 = Register.zero.encodeId(),
-                        .imm0_11 = switch (mnem) {
-                            .ecall => 0x000,
-                            .ebreak => 0x001,
-                            .unimp => 0x000,
-                            else => unreachable,
-                        },
-
-                        .opcode = @intFromEnum(enc.opcode),
-                        .funct3 = enc.data.f.funct3,
-                    },
-                };
-            },
-            .csrrs => {
-                assert(ops.len == 3);
-
-                const csr = ops[0].csr;
-                const rs1 = ops[1].reg;
-                const rd = ops[2].reg;
-
-                return .{
-                    .I = .{
-                        .rd = rd.encodeId(),
-                        .rs1 = rs1.encodeId(),
-
-                        .imm0_11 = @intFromEnum(csr),
-
-                        .opcode = @intFromEnum(enc.opcode),
-                        .funct3 = enc.data.f.funct3,
-                    },
-                };
-            },
-            else => {},
-        }
-
-        switch (inst_enc) {
-            .R => {
-                assert(ops.len == 3);
-                return .{
-                    .R = switch (enc.data) {
-                        .ff => |ff| .{
-                            .rd = ops[0].reg.encodeId(),
-                            .rs1 = ops[1].reg.encodeId(),
-                            .rs2 = ops[2].reg.encodeId(),
-
-                            .opcode = @intFromEnum(enc.opcode),
-                            .funct3 = ff.funct3,
-                            .funct7 = ff.funct7,
-                        },
-                        .fmt => |fmt| .{
-                            .rd = ops[0].reg.encodeId(),
-                            .rs1 = ops[1].reg.encodeId(),
-                            .rs2 = ops[2].reg.encodeId(),
-
-                            .opcode = @intFromEnum(enc.opcode),
-                            .funct3 = fmt.rm,
-                            .funct7 = (@as(u7, fmt.funct5) << 2) | @intFromEnum(fmt.fmt),
-                        },
-                        .vecls => |vec| .{
-                            .rd = ops[0].reg.encodeId(),
-                            .rs1 = ops[1].reg.encodeId(),
-
-                            .rs2 = @intFromEnum(vec.umop),
-
-                            .opcode = @intFromEnum(enc.opcode),
-                            .funct3 = @intFromEnum(vec.width),
-                            .funct7 = (@as(u7, vec.nf) << 4) | (@as(u7, @intFromBool(vec.mew)) << 3) | (@as(u7, @intFromEnum(vec.mop)) << 1) | @intFromBool(vec.vm),
-                        },
-                        .vecmath => |vec| .{
-                            .rd = ops[0].reg.encodeId(),
-                            .rs1 = ops[1].reg.encodeId(),
-                            .rs2 = ops[2].reg.encodeId(),
-
-                            .opcode = @intFromEnum(enc.opcode),
-                            .funct3 = @intFromEnum(vec.funct3),
-                            .funct7 = (@as(u7, vec.funct6) << 1) | @intFromBool(vec.vm),
-                        },
-                        else => unreachable,
-                    },
-                };
-            },
-            .S => {
-                assert(ops.len == 3);
-                const umm = ops[2].imm.asBits(u12);
-
-                return .{
-                    .S = .{
-                        .imm0_4 = @truncate(umm),
-                        .rs1 = ops[0].reg.encodeId(),
-                        .rs2 = ops[1].reg.encodeId(),
-                        .imm5_11 = @truncate(umm >> 5),
-
-                        .opcode = @intFromEnum(enc.opcode),
-                        .funct3 = enc.data.f.funct3,
-                    },
-                };
-            },
-            .I => {
-                assert(ops.len == 3);
-                return .{
-                    .I = switch (enc.data) {
-                        .f => |f| .{
-                            .rd = ops[0].reg.encodeId(),
-                            .rs1 = ops[1].reg.encodeId(),
-                            .imm0_11 = ops[2].imm.asBits(u12),
-
-                            .opcode = @intFromEnum(enc.opcode),
-                            .funct3 = f.funct3,
-                        },
-                        .sh => |sh| .{
-                            .rd = ops[0].reg.encodeId(),
-                            .rs1 = ops[1].reg.encodeId(),
-                            .imm0_11 = (@as(u12, sh.typ) << 6) |
-                                if (sh.has_5) ops[2].imm.asBits(u6) else (@as(u6, 0) | ops[2].imm.asBits(u5)),
-
-                            .opcode = @intFromEnum(enc.opcode),
-                            .funct3 = sh.funct3,
-                        },
-                        else => unreachable,
-                    },
-                };
-            },
-            .U => {
-                assert(ops.len == 2);
-                return .{
-                    .U = .{
-                        .rd = ops[0].reg.encodeId(),
-                        .imm12_31 = ops[1].imm.asBits(u20),
-
-                        .opcode = @intFromEnum(enc.opcode),
-                    },
-                };
-            },
-            .J => {
-                assert(ops.len == 2);
-
-                const umm = ops[1].imm.asBits(u21);
-                assert(umm % 4 == 0); // misaligned jump target
-
-                return .{
-                    .J = .{
-                        .rd = ops[0].reg.encodeId(),
-                        .imm1_10 = @truncate(umm >> 1),
-                        .imm11 = @truncate(umm >> 11),
-                        .imm12_19 = @truncate(umm >> 12),
-                        .imm20 = @truncate(umm >> 20),
-
-                        .opcode = @intFromEnum(enc.opcode),
-                    },
-                };
-            },
-            .B => {
-                assert(ops.len == 3);
-
-                const umm = ops[2].imm.asBits(u13);
-                assert(umm % 4 == 0); // misaligned branch target
-
-                return .{
-                    .B = .{
-                        .rs1 = ops[0].reg.encodeId(),
-                        .rs2 = ops[1].reg.encodeId(),
-                        .imm1_4 = @truncate(umm >> 1),
-                        .imm5_10 = @truncate(umm >> 5),
-                        .imm11 = @truncate(umm >> 11),
-                        .imm12 = @truncate(umm >> 12),
-
-                        .opcode = @intFromEnum(enc.opcode),
-                        .funct3 = enc.data.f.funct3,
-                    },
-                };
-            },
-            .fence => {
-                assert(ops.len == 2);
-
-                const succ = ops[0].barrier;
-                const pred = ops[1].barrier;
-
-                return .{
-                    .fence = .{
-                        .succ = @intFromEnum(succ),
-                        .pred = @intFromEnum(pred),
-
-                        .opcode = @intFromEnum(enc.opcode),
-                        .funct3 = enc.data.fence.funct3,
-                        .fm = @intFromEnum(enc.data.fence.fm),
-                    },
-                };
-            },
-            .amo => {
-                assert(ops.len == 5);
-
-                const rd = ops[0].reg;
-                const rs1 = ops[1].reg;
-                const rs2 = ops[2].reg;
-                const rl = ops[3].barrier;
-                const aq = ops[4].barrier;
-
-                return .{
-                    .amo = .{
-                        .rd = rd.encodeId(),
-                        .rs1 = rs1.encodeId(),
-                        .rs2 = rs2.encodeId(),
-
-                        // TODO: https://github.com/ziglang/zig/issues/20113
-                        .rl = if (rl == .rl) true else false,
-                        .aq = if (aq == .aq) true else false,
-
-                        .opcode = @intFromEnum(enc.opcode),
-                        .funct3 = @intFromEnum(enc.data.amo.width),
-                        .funct5 = enc.data.amo.funct5,
-                    },
-                };
-            },
-            else => std.debug.panic("TODO: construct {s}", .{@tagName(inst_enc)}),
-        }
-    }
-};
-
-pub fn findByMnemonic(mnem: Mnemonic, ops: []const Operand) !?Encoding {
-    if (!verifyOps(mnem, ops)) return null;
-
-    return .{
-        .mnemonic = mnem,
-        .data = try Data.construct(mnem, ops),
-    };
-}
-
-fn verifyOps(mnem: Mnemonic, ops: []const Operand) bool {
-    const inst_enc = InstEnc.fromMnemonic(mnem);
-    const list = std.mem.sliceTo(&inst_enc.opsList(), .none);
-    for (list, ops) |l, o| if (l != std.meta.activeTag(o)) return false;
-    return true;
-}
-
-const std = @import("std");
-const assert = std.debug.assert;
-const log = std.log.scoped(.encoding);
-
-const Encoding = @This();
-const bits = @import("bits.zig");
-const Register = bits.Register;
-const encoder = @import("encoder.zig");
-const Instruction = encoder.Instruction;
-const Operand = Instruction.Operand;
-const OperandEnum = std.meta.FieldEnum(Operand);
src/arch/riscv64/encoding.zig
@@ -0,0 +1,716 @@
+//! This file is responsible for going from MIR, which is emitted by CodeGen
+//! and converting it into Instructions, which can be used as needed.
+//!
+//! Here we encode how mnemonics relate to opcodes and where their operands go.
+
+/// Lower Instruction Representation
+///
+/// This format encodes a specific instruction, however it's still abstracted
+/// away from the true encoding it'll be in. It's meant to make the process of
+/// indicating unique encoding data easier.
+pub const Lir = struct {
+    opcode: OpCode,
+    format: Format,
+    data: Data,
+
+    pub const Format = enum {
+        R,
+        I,
+        S,
+        B,
+        U,
+        J,
+        extra,
+    };
+
+    const Data = union(enum) {
+        none,
+        f: struct { funct3: u3 },
+        ff: struct {
+            funct3: u3,
+            funct7: u7,
+        },
+        sh: struct {
+            typ: u6,
+            funct3: u3,
+            has_5: bool,
+        },
+
+        fmt: struct {
+            funct5: u5,
+            rm: u3,
+            fmt: FpFmt,
+        },
+        fcvt: struct {
+            funct5: u5,
+            rm: u3,
+            fmt: FpFmt,
+            width: Mir.FcvtOp,
+        },
+
+        vecls: struct {
+            width: VecWidth,
+            umop: Umop,
+            vm: bool,
+            mop: Mop,
+            mew: bool,
+            nf: u3,
+        },
+        vecmath: struct {
+            vm: bool,
+            funct6: u6,
+            funct3: VecType,
+        },
+
+        amo: struct {
+            funct5: u5,
+            width: AmoWidth,
+        },
+        fence: struct {
+            funct3: u3,
+            fm: FenceMode,
+        },
+
+        /// the mnemonic has some special properities that can't be handled in a generic fashion
+        extra: Mnemonic,
+    };
+
+    const OpCode = enum(u7) {
+        LOAD = 0b0000011,
+        LOAD_FP = 0b0000111,
+        MISC_MEM = 0b0001111,
+        OP_IMM = 0b0010011,
+        AUIPC = 0b0010111,
+        OP_IMM_32 = 0b0011011,
+        STORE = 0b0100011,
+        STORE_FP = 0b0100111,
+        AMO = 0b0101111,
+        OP_V = 0b1010111,
+        OP = 0b0110011,
+        OP_32 = 0b0111011,
+        LUI = 0b0110111,
+        MADD = 0b1000011,
+        MSUB = 0b1000111,
+        NMSUB = 0b1001011,
+        NMADD = 0b1001111,
+        OP_FP = 0b1010011,
+        OP_IMM_64 = 0b1011011,
+        BRANCH = 0b1100011,
+        JALR = 0b1100111,
+        JAL = 0b1101111,
+        SYSTEM = 0b1110011,
+        OP_64 = 0b1111011,
+        NONE = 0b00000000,
+    };
+
+    const FpFmt = enum(u2) {
+        /// 32-bit single-precision
+        S = 0b00,
+        /// 64-bit double-precision
+        D = 0b01,
+
+        // H = 0b10, unused in the G extension
+
+        /// 128-bit quad-precision
+        Q = 0b11,
+    };
+
+    const AmoWidth = enum(u3) {
+        W = 0b010,
+        D = 0b011,
+    };
+
+    const FenceMode = enum(u4) {
+        none = 0b0000,
+        tso = 0b1000,
+    };
+
+    const Mop = enum(u2) {
+        // zig fmt: off
+        unit   = 0b00,
+        unord  = 0b01,
+        stride = 0b10,
+        ord    = 0b11,
+        // zig fmt: on
+    };
+
+    const Umop = enum(u5) {
+        // zig fmt: off
+        unit  = 0b00000,
+        whole = 0b01000,
+        mask  = 0b01011,
+        fault = 0b10000,
+        // zig fmt: on
+    };
+
+    const VecWidth = enum(u3) {
+        // zig fmt: off
+        @"8"  = 0b000,
+        @"16" = 0b101,
+        @"32" = 0b110,
+        @"64" = 0b111,
+        // zig fmt: on
+    };
+
+    const VecType = enum(u3) {
+        OPIVV = 0b000,
+        OPFVV = 0b001,
+        OPMVV = 0b010,
+        OPIVI = 0b011,
+        OPIVX = 0b100,
+        OPFVF = 0b101,
+        OPMVX = 0b110,
+    };
+
+    pub fn fromMnem(mnem: Mnemonic) Lir {
+        return switch (mnem) {
+            // zig fmt: off
+
+            // OP
+            .add     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0000000 } } },
+            .sub     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0100000 } } }, 
+
+            .@"and"  => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b111, .funct7 = 0b0000000 } } },
+            .@"or"   => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b110, .funct7 = 0b0000000 } } },
+            .xor     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b100, .funct7 = 0b0000000 } } },
+
+            .sltu    => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b011, .funct7 = 0b0000000 } } },
+            .slt     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b010, .funct7 = 0b0000000 } } },
+
+            .mul     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0000001 } } },
+            .mulh    => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0000001 } } },
+            .mulhsu  => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b010, .funct7 = 0b0000001 } } },
+            .mulhu   => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b011, .funct7 = 0b0000001 } } },
+ 
+            .div     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b100, .funct7 = 0b0000001 } } },
+            .divu    => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0000001 } } },
+
+            .rem     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b110, .funct7 = 0b0000001 } } },
+            .remu    => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b111, .funct7 = 0b0000001 } } },
+
+            .sll     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0000000 } } },
+            .srl     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0000000 } } },
+            .sra     => .{ .opcode = .OP, .format = .R, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0100000 } } },
+
+
+            // OP_IMM
+
+            .addi    => .{ .opcode = .OP_IMM, .format = .I, .data = .{ .f = .{ .funct3 = 0b000 } } },
+            .andi    => .{ .opcode = .OP_IMM, .format = .I, .data = .{ .f = .{ .funct3 = 0b111 } } },
+            .xori    => .{ .opcode = .OP_IMM, .format = .I, .data = .{ .f = .{ .funct3 = 0b100 } } },
+            
+            .sltiu   => .{ .opcode = .OP_IMM, .format = .I, .data = .{ .f = .{ .funct3 = 0b011 } } },
+
+            .slli    => .{ .opcode = .OP_IMM, .format = .I, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b001, .has_5 = true } } },
+            .srli    => .{ .opcode = .OP_IMM, .format = .I, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b101, .has_5 = true } } },
+            .srai    => .{ .opcode = .OP_IMM, .format = .I, .data = .{ .sh = .{ .typ = 0b010000, .funct3 = 0b101, .has_5 = true } } },
+
+            .clz     => .{ .opcode = .OP_IMM, .format = .R, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0110000 } } },
+
+            // OP_IMM_32
+
+            .slliw   => .{ .opcode = .OP_IMM_32, .format = .I, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b001, .has_5 = false } } },
+            .srliw   => .{ .opcode = .OP_IMM_32, .format = .I, .data = .{ .sh = .{ .typ = 0b000000, .funct3 = 0b101, .has_5 = false } } },
+            .sraiw   => .{ .opcode = .OP_IMM_32, .format = .I, .data = .{ .sh = .{ .typ = 0b010000, .funct3 = 0b101, .has_5 = false } } },
+
+            .clzw     => .{ .opcode = .OP_IMM_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0110000 } } },
+
+            // OP_32
+
+            .addw    => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0000000 } } },
+            .subw    => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0100000 } } },
+            .mulw    => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b000, .funct7 = 0b0000001 } } }, 
+
+            .divw    => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b100, .funct7 = 0b0000001 } } },
+            .divuw   => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0000001 } } },
+
+            .remw    => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b110, .funct7 = 0b0000001 } } },
+            .remuw   => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b111, .funct7 = 0b0000001 } } },
+
+            .sllw    => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b001, .funct7 = 0b0000000 } } },
+            .srlw    => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0000000 } } },
+            .sraw    => .{ .opcode = .OP_32, .format = .R, .data = .{ .ff = .{ .funct3 = 0b101, .funct7 = 0b0100000 } } },
+
+
+            // OP_FP
+
+            .fadds   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00000, .fmt = .S, .rm = 0b111 } } },
+            .faddd   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00000, .fmt = .D, .rm = 0b111 } } },
+
+            .fsubs   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00001, .fmt = .S, .rm = 0b111 } } },
+            .fsubd   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00001, .fmt = .D, .rm = 0b111 } } },
+
+            .fmuls   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00010, .fmt = .S, .rm = 0b111 } } },
+            .fmuld   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00010, .fmt = .D, .rm = 0b111 } } },
+
+            .fdivs   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00011, .fmt = .S, .rm = 0b111 } } },
+            .fdivd   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00011, .fmt = .D, .rm = 0b111 } } },
+
+            .fmins   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00101, .fmt = .S, .rm = 0b000 } } },
+            .fmind   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00101, .fmt = .D, .rm = 0b000 } } },
+
+            .fmaxs   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00101, .fmt = .S, .rm = 0b001 } } },
+            .fmaxd   => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00101, .fmt = .D, .rm = 0b001 } } },
+
+            .fsqrts  => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b01011, .fmt = .S, .rm = 0b111 } } },
+            .fsqrtd  => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b01011, .fmt = .D, .rm = 0b111 } } },
+
+            .fles    => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .S, .rm = 0b000 } } },
+            .fled    => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .D, .rm = 0b000 } } },
+
+            .flts    => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .S, .rm = 0b001 } } },
+            .fltd    => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .D, .rm = 0b001 } } },
+
+            .feqs    => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .S, .rm = 0b010 } } },
+            .feqd    => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b10100, .fmt = .D, .rm = 0b010 } } },
+
+            .fsgnjns => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00100, .fmt = .S, .rm = 0b000 } } },
+            .fsgnjnd => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00100, .fmt = .D, .rm = 0b000 } } },
+
+            .fsgnjxs => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00100, .fmt = .S, .rm = 0b010 } } },
+            .fsgnjxd => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fmt = .{ .funct5 = 0b00100, .fmt = .D, .rm = 0b010 } } },
+
+            .fcvtws  => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fcvt = .{ .funct5 = 0b11000, .fmt = .S, .rm = 0b111, .width = .w  } } },
+            .fcvtwus => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fcvt = .{ .funct5 = 0b11000, .fmt = .S, .rm = 0b111, .width = .wu } } },
+            .fcvtls  => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fcvt = .{ .funct5 = 0b11000, .fmt = .S, .rm = 0b111, .width = .l  } } },
+            .fcvtlus => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fcvt = .{ .funct5 = 0b11000, .fmt = .S, .rm = 0b111, .width = .lu } } },
+
+            .fcvtwd  => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fcvt = .{ .funct5 = 0b11000, .fmt = .D, .rm = 0b111, .width = .w  } } },
+            .fcvtwud => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fcvt = .{ .funct5 = 0b11000, .fmt = .D, .rm = 0b111, .width = .wu } } },
+            .fcvtld  => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fcvt = .{ .funct5 = 0b11000, .fmt = .D, .rm = 0b111, .width = .l  } } },
+            .fcvtlud => .{ .opcode = .OP_FP, .format = .R, .data = .{ .fcvt = .{ .funct5 = 0b11000, .fmt = .D, .rm = 0b111, .width = .lu } } },
+
+            // LOAD
+
+            .lb      => .{ .opcode = .LOAD, .format = .I, .data = .{ .f = .{ .funct3 = 0b000 } } },
+            .lh      => .{ .opcode = .LOAD, .format = .I, .data = .{ .f = .{ .funct3 = 0b001 } } },
+            .lw      => .{ .opcode = .LOAD, .format = .I, .data = .{ .f = .{ .funct3 = 0b010 } } },
+            .ld      => .{ .opcode = .LOAD, .format = .I, .data = .{ .f = .{ .funct3 = 0b011 } } },
+            .lbu     => .{ .opcode = .LOAD, .format = .I, .data = .{ .f = .{ .funct3 = 0b100 } } },
+            .lhu     => .{ .opcode = .LOAD, .format = .I, .data = .{ .f = .{ .funct3 = 0b101 } } },
+            .lwu     => .{ .opcode = .LOAD, .format = .I, .data = .{ .f = .{ .funct3 = 0b110 } } },
+
+
+            // STORE
+            
+            .sb      => .{ .opcode = .STORE, .format = .S, .data = .{ .f = .{ .funct3 = 0b000 } } },
+            .sh      => .{ .opcode = .STORE, .format = .S, .data = .{ .f = .{ .funct3 = 0b001 } } },
+            .sw      => .{ .opcode = .STORE, .format = .S, .data = .{ .f = .{ .funct3 = 0b010 } } },
+            .sd      => .{ .opcode = .STORE, .format = .S, .data = .{ .f = .{ .funct3 = 0b011 } } },
+
+
+            // LOAD_FP
+
+            .flw     => .{ .opcode = .LOAD_FP, .format = .I, .data = .{ .f = .{ .funct3 = 0b010 } } },
+            .fld     => .{ .opcode = .LOAD_FP, .format = .I, .data = .{ .f = .{ .funct3 = 0b011 } } },   
+    
+            .vle8v   => .{ .opcode = .LOAD_FP, .format = .R, .data = .{ .vecls = .{ .width = .@"8",  .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
+            .vle16v  => .{ .opcode = .LOAD_FP, .format = .R, .data = .{ .vecls = .{ .width = .@"16", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
+            .vle32v  => .{ .opcode = .LOAD_FP, .format = .R, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
+            .vle64v  => .{ .opcode = .LOAD_FP, .format = .R, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
+            
+
+            // STORE_FP
+
+            .fsw        => .{ .opcode = .STORE_FP, .format = .S, .data = .{ .f = .{ .funct3 = 0b010 } } },
+            .fsd        => .{ .opcode = .STORE_FP, .format = .S, .data = .{ .f = .{ .funct3 = 0b011 } } },
+
+            .vse8v      => .{ .opcode = .STORE_FP, .format = .R, .data = .{ .vecls = .{ .width = .@"8",  .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
+            .vse16v     => .{ .opcode = .STORE_FP, .format = .R, .data = .{ .vecls = .{ .width = .@"16", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
+            .vse32v     => .{ .opcode = .STORE_FP, .format = .R, .data = .{ .vecls = .{ .width = .@"32", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
+            .vse64v     => .{ .opcode = .STORE_FP, .format = .R, .data = .{ .vecls = .{ .width = .@"64", .umop = .unit, .vm = true, .mop = .unit, .mew = false, .nf = 0b000 } } },
+
+            // JALR
+
+            .jalr    => .{ .opcode = .JALR, .format = .I, .data = .{ .f = .{ .funct3 = 0b000 } } },
+
+
+            // LUI
+
+            .lui     => .{ .opcode = .LUI, .format = .U, .data = .{ .none = {} } },
+
+
+            // AUIPC
+
+            .auipc   => .{ .opcode = .AUIPC, .format = .U, .data = .{ .none = {} } },
+
+
+            // JAL
+
+            .jal     => .{ .opcode = .JAL, .format = .J, .data = .{ .none = {} } },
+
+
+            // BRANCH
+
+            .beq     => .{ .opcode = .BRANCH, .format = .B, .data = .{ .f = .{ .funct3 = 0b000 } } },
+
+
+            // SYSTEM
+
+            .ecall   => .{ .opcode = .SYSTEM, .format = .extra, .data = .{ .extra = .ecall  } },
+            .ebreak  => .{ .opcode = .SYSTEM, .format = .extra, .data = .{ .extra = .ebreak } },
+
+            .csrrs   => .{ .opcode = .SYSTEM, .format = .I, .data = .{ .f = .{ .funct3 = 0b010 } } },
+           
+
+            // NONE
+
+            .unimp  => .{ .opcode = .NONE, .format = .extra, .data = .{ .extra = .unimp } },
+
+
+            // MISC_MEM
+
+            .fence    => .{ .opcode = .MISC_MEM, .format = .I, .data = .{ .fence = .{ .funct3 = 0b000, .fm = .none } } },
+            .fencetso => .{ .opcode = .MISC_MEM, .format = .I, .data = .{ .fence = .{ .funct3 = 0b000, .fm = .tso  } } },
+
+
+            // AMO
+
+            .amoaddw   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00000 } } },
+            .amoswapw  => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00001 } } },
+            // LR.W
+            // SC.W
+            .amoxorw   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b00100 } } },
+            .amoandw   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b01100 } } },
+            .amoorw    => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b01000 } } },
+            .amominw   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b10000 } } },
+            .amomaxw   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b10100 } } },
+            .amominuw  => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b11000 } } },
+            .amomaxuw  => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .W, .funct5 = 0b11100 } } },
+
+            .amoaddd   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00000 } } },
+            .amoswapd  => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00001 } } },
+            // LR.D
+            // SC.D
+            .amoxord   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b00100 } } },
+            .amoandd   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b01100 } } },
+            .amoord    => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b01000 } } },
+            .amomind   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b10000 } } },
+            .amomaxd   => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b10100 } } },
+            .amominud  => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b11000 } } },
+            .amomaxud  => .{ .opcode = .AMO, .format = .R, .data = .{ .amo = .{ .width = .D, .funct5 = 0b11100 } } },
+
+            // OP_V
+            .vsetivli       => .{ .opcode = .OP_V, .format = .I, .data = .{ .f = .{ .funct3 = 0b111 } } },
+            .vsetvli        => .{ .opcode = .OP_V, .format = .I, .data = .{ .f = .{ .funct3 = 0b111 } } },
+            .vaddvv         => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000000, .funct3 = .OPIVV } } },
+            .vsubvv         => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000010, .funct3 = .OPIVV } } },
+            .vmulvv         => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b100101, .funct3 = .OPIVV } } },
+            
+            .vfaddvv        => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000000, .funct3 = .OPFVV } } },
+            .vfsubvv        => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b000010, .funct3 = .OPFVV } } },
+            .vfmulvv        => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b100100, .funct3 = .OPFVV } } },
+            
+            .vadcvv         => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010000, .funct3 = .OPMVV } } },
+            .vmvvx          => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b010111, .funct3 = .OPIVX } } },
+
+            .vslidedownvx   => .{ .opcode = .OP_V, .format = .R, .data = .{ .vecmath = .{ .vm = true, .funct6 = 0b001111, .funct3 = .OPIVX } } },
+            
+
+            .pseudo_prologue,
+            .pseudo_epilogue,
+            .pseudo_dbg_prologue_end,
+            .pseudo_dbg_epilogue_begin,
+            .pseudo_dbg_line_column,
+            .pseudo_load_rm,
+            .pseudo_store_rm,
+            .pseudo_lea_rm,
+            .pseudo_j,
+            .pseudo_dead,
+            .pseudo_load_symbol,
+            .pseudo_mv,
+            .pseudo_restore_regs,
+            .pseudo_spill_regs,
+            .pseudo_compare,
+            .pseudo_not,
+            .pseudo_extern_fn_reloc,
+            .pseudo_fence,
+            .pseudo_amo,
+            .nop,
+            => std.debug.panic("lir: didn't catch pseudo {s}", .{@tagName(mnem)}),
+            // zig fmt: on
+        };
+    }
+};
+
+/// This is the final form of the instruction. Lir is transformed into
+/// this, which is then bitcast into a u32.
+pub const Instruction = union(Lir.Format) {
+    R: packed struct(u32) {
+        opcode: u7,
+        rd: u5,
+        funct3: u3,
+        rs1: u5,
+        rs2: u5,
+        funct7: u7,
+    },
+    I: packed struct(u32) {
+        opcode: u7,
+        rd: u5,
+        funct3: u3,
+        rs1: u5,
+        imm0_11: u12,
+    },
+    S: packed struct(u32) {
+        opcode: u7,
+        imm0_4: u5,
+        funct3: u3,
+        rs1: u5,
+        rs2: u5,
+        imm5_11: u7,
+    },
+    B: packed struct(u32) {
+        opcode: u7,
+        imm11: u1,
+        imm1_4: u4,
+        funct3: u3,
+        rs1: u5,
+        rs2: u5,
+        imm5_10: u6,
+        imm12: u1,
+    },
+    U: packed struct(u32) {
+        opcode: u7,
+        rd: u5,
+        imm12_31: u20,
+    },
+    J: packed struct(u32) {
+        opcode: u7,
+        rd: u5,
+        imm12_19: u8,
+        imm11: u1,
+        imm1_10: u10,
+        imm20: u1,
+    },
+    extra: u32,
+
+    comptime {
+        for (std.meta.fields(Instruction)) |field| {
+            assert(@bitSizeOf(field.type) == 32);
+        }
+    }
+
+    pub const Operand = union(enum) {
+        none,
+        reg: Register,
+        csr: CSR,
+        mem: Memory,
+        imm: Immediate,
+        barrier: Mir.Barrier,
+    };
+
+    pub fn toU32(inst: Instruction) u32 {
+        return switch (inst) {
+            inline else => |v| @bitCast(v),
+        };
+    }
+
+    pub fn encode(inst: Instruction, writer: anytype) !void {
+        try writer.writeInt(u32, inst.toU32(), .little);
+    }
+
+    pub fn fromLir(lir: Lir, ops: []const Operand) Instruction {
+        const opcode: u7 = @intFromEnum(lir.opcode);
+
+        switch (lir.format) {
+            .R => {
+                return .{
+                    .R = switch (lir.data) {
+                        .ff => |ff| .{
+                            .rd = ops[0].reg.encodeId(),
+                            .rs1 = ops[1].reg.encodeId(),
+                            .rs2 = ops[2].reg.encodeId(),
+
+                            .opcode = opcode,
+                            .funct3 = ff.funct3,
+                            .funct7 = ff.funct7,
+                        },
+                        .fmt => |fmt| .{
+                            .rd = ops[0].reg.encodeId(),
+                            .rs1 = ops[1].reg.encodeId(),
+                            .rs2 = ops[2].reg.encodeId(),
+
+                            .opcode = opcode,
+                            .funct3 = fmt.rm,
+                            .funct7 = (@as(u7, fmt.funct5) << 2) | @intFromEnum(fmt.fmt),
+                        },
+                        .fcvt => |fcvt| .{
+                            .rd = ops[0].reg.encodeId(),
+                            .rs1 = ops[1].reg.encodeId(),
+                            .rs2 = @intFromEnum(fcvt.width),
+
+                            .opcode = opcode,
+                            .funct3 = fcvt.rm,
+                            .funct7 = (@as(u7, fcvt.funct5) << 2) | @intFromEnum(fcvt.fmt),
+                        },
+                        .vecls => |vec| .{
+                            .rd = ops[0].reg.encodeId(),
+                            .rs1 = ops[1].reg.encodeId(),
+
+                            .rs2 = @intFromEnum(vec.umop),
+
+                            .opcode = opcode,
+                            .funct3 = @intFromEnum(vec.width),
+                            .funct7 = (@as(u7, vec.nf) << 4) | (@as(u7, @intFromBool(vec.mew)) << 3) | (@as(u7, @intFromEnum(vec.mop)) << 1) | @intFromBool(vec.vm),
+                        },
+                        .vecmath => |vec| .{
+                            .rd = ops[0].reg.encodeId(),
+                            .rs1 = ops[1].reg.encodeId(),
+                            .rs2 = ops[2].reg.encodeId(),
+
+                            .opcode = opcode,
+                            .funct3 = @intFromEnum(vec.funct3),
+                            .funct7 = (@as(u7, vec.funct6) << 1) | @intFromBool(vec.vm),
+                        },
+                        .amo => |amo| .{
+                            .rd = ops[0].reg.encodeId(),
+                            .rs1 = ops[1].reg.encodeId(),
+                            .rs2 = ops[2].reg.encodeId(),
+
+                            .opcode = opcode,
+                            .funct3 = @intFromEnum(amo.width),
+                            .funct7 = @as(u7, amo.funct5) << 2 |
+                                @as(u7, @intFromBool(ops[3].barrier == .rl)) << 1 |
+                                @as(u7, @intFromBool(ops[4].barrier == .aq)),
+                        },
+                        else => unreachable,
+                    },
+                };
+            },
+            .S => {
+                assert(ops.len == 3);
+                const umm = ops[2].imm.asBits(u12);
+                return .{
+                    .S = .{
+                        .imm0_4 = @truncate(umm),
+                        .rs1 = ops[0].reg.encodeId(),
+                        .rs2 = ops[1].reg.encodeId(),
+                        .imm5_11 = @truncate(umm >> 5),
+
+                        .opcode = opcode,
+                        .funct3 = lir.data.f.funct3,
+                    },
+                };
+            },
+            .I => {
+                return .{
+                    .I = switch (lir.data) {
+                        .f => |f| .{
+                            .rd = ops[0].reg.encodeId(),
+                            .rs1 = ops[1].reg.encodeId(),
+                            .imm0_11 = ops[2].imm.asBits(u12),
+
+                            .opcode = opcode,
+                            .funct3 = f.funct3,
+                        },
+                        .sh => |sh| .{
+                            .rd = ops[0].reg.encodeId(),
+                            .rs1 = ops[1].reg.encodeId(),
+                            .imm0_11 = (@as(u12, sh.typ) << 6) |
+                                if (sh.has_5) ops[2].imm.asBits(u6) else (@as(u6, 0) | ops[2].imm.asBits(u5)),
+
+                            .opcode = opcode,
+                            .funct3 = sh.funct3,
+                        },
+                        .fence => |fence| .{
+                            .rd = 0,
+                            .rs1 = 0,
+                            .funct3 = 0,
+                            .imm0_11 = (@as(u12, @intFromEnum(fence.fm)) << 8) |
+                                (@as(u12, @intFromEnum(ops[1].barrier)) << 4) |
+                                @as(u12, @intFromEnum(ops[0].barrier)),
+                            .opcode = opcode,
+                        },
+                        else => unreachable,
+                    },
+                };
+            },
+            .U => {
+                assert(ops.len == 2);
+                return .{
+                    .U = .{
+                        .rd = ops[0].reg.encodeId(),
+                        .imm12_31 = ops[1].imm.asBits(u20),
+
+                        .opcode = opcode,
+                    },
+                };
+            },
+            .J => {
+                assert(ops.len == 2);
+
+                const umm = ops[1].imm.asBits(u21);
+                // the RISC-V spec says the target index of a jump
+                // must be a multiple of 2
+                assert(umm % 2 == 0);
+
+                return .{
+                    .J = .{
+                        .rd = ops[0].reg.encodeId(),
+                        .imm1_10 = @truncate(umm >> 1),
+                        .imm11 = @truncate(umm >> 11),
+                        .imm12_19 = @truncate(umm >> 12),
+                        .imm20 = @truncate(umm >> 20),
+
+                        .opcode = opcode,
+                    },
+                };
+            },
+            .B => {
+                assert(ops.len == 3);
+
+                const umm = ops[2].imm.asBits(u13);
+                // the RISC-V spec says the target index of a branch
+                // must be a multiple of 2
+                assert(umm % 2 == 0);
+
+                return .{
+                    .B = .{
+                        .rs1 = ops[0].reg.encodeId(),
+                        .rs2 = ops[1].reg.encodeId(),
+                        .imm1_4 = @truncate(umm >> 1),
+                        .imm5_10 = @truncate(umm >> 5),
+                        .imm11 = @truncate(umm >> 11),
+                        .imm12 = @truncate(umm >> 12),
+
+                        .opcode = opcode,
+                        .funct3 = lir.data.f.funct3,
+                    },
+                };
+            },
+            .extra => {
+                assert(ops.len == 0);
+
+                return .{
+                    .I = .{
+                        .rd = Register.zero.encodeId(),
+                        .rs1 = Register.zero.encodeId(),
+                        .imm0_11 = switch (lir.data.extra) {
+                            .ecall => 0x000,
+                            .ebreak => 0x001,
+                            .unimp => 0x000,
+                            else => unreachable,
+                        },
+
+                        .opcode = opcode,
+                        .funct3 = 0b000,
+                    },
+                };
+            },
+        }
+    }
+};
+
+const std = @import("std");
+const assert = std.debug.assert;
+const log = std.log.scoped(.format);
+
+const bits = @import("bits.zig");
+const Mir = @import("Mir.zig");
+const Mnemonic = @import("mnem.zig").Mnemonic;
+const Lower = @import("Lower.zig");
+
+const Register = bits.Register;
+const CSR = bits.CSR;
+const Memory = bits.Memory;
+const Immediate = bits.Immediate;
src/arch/riscv64/Lower.zig
@@ -61,451 +61,427 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct {
     log.debug("lowerMir {}", .{inst});
     switch (inst.tag) {
         else => try lower.generic(inst),
-        .pseudo => switch (inst.ops) {
-            .pseudo_dbg_line_column,
-            .pseudo_dbg_epilogue_begin,
-            .pseudo_dbg_prologue_end,
-            .pseudo_dead,
-            => {},
-
-            .pseudo_load_rm, .pseudo_store_rm => {
-                const rm = inst.data.rm;
-
-                const frame_loc: Mir.FrameLoc = if (options.allow_frame_locs)
-                    rm.m.toFrameLoc(lower.mir)
-                else
-                    .{ .base = .s0, .disp = 0 };
-
-                switch (inst.ops) {
-                    .pseudo_load_rm => {
-                        const dest_reg = rm.r;
-                        const dest_reg_class = dest_reg.class();
-
-                        const src_size = rm.m.mod.size;
-                        const unsigned = rm.m.mod.unsigned;
-
-                        const tag: Encoding.Mnemonic = switch (dest_reg_class) {
-                            .int => switch (src_size) {
-                                .byte => if (unsigned) .lbu else .lb,
-                                .hword => if (unsigned) .lhu else .lh,
-                                .word => if (unsigned) .lwu else .lw,
-                                .dword => .ld,
-                            },
-                            .float => switch (src_size) {
-                                .byte => unreachable, // Zig does not support 8-bit floats
-                                .hword => return lower.fail("TODO: lowerMir pseudo_load_rm support 16-bit floats", .{}),
-                                .word => .flw,
-                                .dword => .fld,
-                            },
-                            .vector => switch (src_size) {
-                                .byte => .vle8v,
-                                .hword => .vle32v,
-                                .word => .vle32v,
-                                .dword => .vle64v,
-                            },
-                        };
-
-                        switch (dest_reg_class) {
-                            .int, .float => {
-                                try lower.emit(tag, &.{
-                                    .{ .reg = rm.r },
-                                    .{ .reg = frame_loc.base },
-                                    .{ .imm = Immediate.s(frame_loc.disp) },
-                                });
-                            },
-                            .vector => {
-                                assert(frame_loc.disp == 0);
-                                try lower.emit(tag, &.{
-                                    .{ .reg = rm.r },
-                                    .{ .reg = frame_loc.base },
-                                    .{ .reg = .zero },
-                                });
-                            },
-                        }
-                    },
-                    .pseudo_store_rm => {
-                        const src_reg = rm.r;
-                        const src_reg_class = src_reg.class();
-
-                        const dest_size = rm.m.mod.size;
-
-                        const tag: Encoding.Mnemonic = switch (src_reg_class) {
-                            .int => switch (dest_size) {
-                                .byte => .sb,
-                                .hword => .sh,
-                                .word => .sw,
-                                .dword => .sd,
-                            },
-                            .float => switch (dest_size) {
-                                .byte => unreachable, // Zig does not support 8-bit floats
-                                .hword => return lower.fail("TODO: lowerMir pseudo_store_rm support 16-bit floats", .{}),
-                                .word => .fsw,
-                                .dword => .fsd,
-                            },
-                            .vector => switch (dest_size) {
-                                .byte => .vse8v,
-                                .hword => .vse16v,
-                                .word => .vse32v,
-                                .dword => .vse64v,
-                            },
-                        };
-
-                        switch (src_reg_class) {
-                            .int, .float => {
-                                try lower.emit(tag, &.{
-                                    .{ .reg = frame_loc.base },
-                                    .{ .reg = rm.r },
-                                    .{ .imm = Immediate.s(frame_loc.disp) },
-                                });
-                            },
-                            .vector => {
-                                assert(frame_loc.disp == 0);
-                                try lower.emit(tag, &.{
-                                    .{ .reg = rm.r },
-                                    .{ .reg = frame_loc.base },
-                                    .{ .reg = .zero },
-                                });
-                            },
-                        }
-                    },
-                    else => unreachable,
-                }
-            },
-
-            .pseudo_mv => {
-                const rr = inst.data.rr;
-
-                const dst_class = rr.rd.class();
-                const src_class = rr.rs.class();
-
-                switch (src_class) {
-                    .float => switch (dst_class) {
-                        .float => {
-                            try lower.emit(if (lower.hasFeature(.d)) .fsgnjnd else .fsgnjns, &.{
-                                .{ .reg = rr.rd },
-                                .{ .reg = rr.rs },
-                                .{ .reg = rr.rs },
-                            });
+        .pseudo_dbg_line_column,
+        .pseudo_dbg_epilogue_begin,
+        .pseudo_dbg_prologue_end,
+        .pseudo_dead,
+        => {},
+
+        .pseudo_load_rm, .pseudo_store_rm => {
+            const rm = inst.data.rm;
+
+            const frame_loc: Mir.FrameLoc = if (options.allow_frame_locs)
+                rm.m.toFrameLoc(lower.mir)
+            else
+                .{ .base = .s0, .disp = 0 };
+
+            switch (inst.tag) {
+                .pseudo_load_rm => {
+                    const dest_reg = rm.r;
+                    const dest_reg_class = dest_reg.class();
+
+                    const src_size = rm.m.mod.size;
+                    const unsigned = rm.m.mod.unsigned;
+
+                    const mnem: Mnemonic = switch (dest_reg_class) {
+                        .int => switch (src_size) {
+                            .byte => if (unsigned) .lbu else .lb,
+                            .hword => if (unsigned) .lhu else .lh,
+                            .word => if (unsigned) .lwu else .lw,
+                            .dword => .ld,
                         },
-                        .int, .vector => return lower.fail("TODO: lowerMir pseudo_mv float -> {s}", .{@tagName(dst_class)}),
-                    },
-                    .int => switch (dst_class) {
-                        .int => {
-                            try lower.emit(.addi, &.{
-                                .{ .reg = rr.rd },
-                                .{ .reg = rr.rs },
-                                .{ .imm = Immediate.s(0) },
-                            });
+                        .float => switch (src_size) {
+                            .byte => unreachable, // Zig does not support 8-bit floats
+                            .hword => return lower.fail("TODO: lowerMir pseudo_load_rm support 16-bit floats", .{}),
+                            .word => .flw,
+                            .dword => .fld,
                         },
-                        .vector => {
-                            try lower.emit(.vmvvx, &.{
-                                .{ .reg = rr.rd },
-                                .{ .reg = rr.rs },
-                                .{ .reg = .x0 },
+                        .vector => switch (src_size) {
+                            .byte => .vle8v,
+                            .hword => .vle32v,
+                            .word => .vle32v,
+                            .dword => .vle64v,
+                        },
+                    };
+
+                    switch (dest_reg_class) {
+                        .int, .float => {
+                            try lower.emit(mnem, &.{
+                                .{ .reg = rm.r },
+                                .{ .reg = frame_loc.base },
+                                .{ .imm = Immediate.s(frame_loc.disp) },
                             });
                         },
-                        .float => return lower.fail("TODO: lowerMir pseudo_mv int -> {s}", .{@tagName(dst_class)}),
-                    },
-                    .vector => switch (dst_class) {
-                        .int => {
-                            try lower.emit(.vadcvv, &.{
-                                .{ .reg = rr.rd },
+                        .vector => {
+                            assert(frame_loc.disp == 0);
+                            try lower.emit(mnem, &.{
+                                .{ .reg = rm.r },
+                                .{ .reg = frame_loc.base },
                                 .{ .reg = .zero },
-                                .{ .reg = rr.rs },
                             });
                         },
-                        .float, .vector => return lower.fail("TODO: lowerMir pseudo_mv vector -> {s}", .{@tagName(dst_class)}),
-                    },
-                }
-            },
-
-            .pseudo_j => {
-                try lower.emit(.jal, &.{
-                    .{ .reg = .zero },
-                    .{ .imm = lower.reloc(.{ .inst = inst.data.inst }) },
-                });
-            },
-
-            .pseudo_spill_regs => try lower.pushPopRegList(true, inst.data.reg_list),
-            .pseudo_restore_regs => try lower.pushPopRegList(false, inst.data.reg_list),
-
-            .pseudo_load_symbol => {
-                const payload = inst.data.payload;
-                const data = lower.mir.extraData(Mir.LoadSymbolPayload, payload).data;
-                const dst_reg: bits.Register = @enumFromInt(data.register);
-                assert(dst_reg.class() == .int);
-
-                try lower.emit(.lui, &.{
-                    .{ .reg = dst_reg },
-                    .{ .imm = lower.reloc(.{
-                        .load_symbol_reloc = .{
-                            .atom_index = data.atom_index,
-                            .sym_index = data.sym_index,
-                        },
-                    }) },
-                });
-
-                // the above reloc implies this one
-                try lower.emit(.addi, &.{
-                    .{ .reg = dst_reg },
-                    .{ .reg = dst_reg },
-                    .{ .imm = Immediate.s(0) },
-                });
-            },
-
-            .pseudo_lea_rm => {
-                const rm = inst.data.rm;
-                assert(rm.r.class() == .int);
-
-                const frame: Mir.FrameLoc = if (options.allow_frame_locs)
-                    rm.m.toFrameLoc(lower.mir)
-                else
-                    .{ .base = .s0, .disp = 0 };
-
-                try lower.emit(.addi, &.{
-                    .{ .reg = rm.r },
-                    .{ .reg = frame.base },
-                    .{ .imm = Immediate.s(frame.disp) },
-                });
-            },
-
-            .pseudo_fabs => {
-                const fabs = inst.data.fabs;
-                assert(fabs.rs.class() == .float and fabs.rd.class() == .float);
-
-                const mnem: Encoding.Mnemonic = switch (fabs.bits) {
-                    16 => return lower.fail("TODO: airAbs Float 16", .{}),
-                    32 => .fsgnjxs,
-                    64 => .fsgnjxd,
-                    80 => return lower.fail("TODO: airAbs Float 80", .{}),
-                    128 => return lower.fail("TODO: airAbs Float 128", .{}),
-                    else => unreachable,
-                };
-
-                try lower.emit(mnem, &.{
-                    .{ .reg = fabs.rs },
-                    .{ .reg = fabs.rd },
-                    .{ .reg = fabs.rd },
-                });
-            },
-
-            .pseudo_compare => {
-                const compare = inst.data.compare;
-                const op = compare.op;
-
-                const rd = compare.rd;
-                const rs1 = compare.rs1;
-                const rs2 = compare.rs2;
-
-                const class = rs1.class();
-                const ty = compare.ty;
-                const size = std.math.ceilPowerOfTwo(u64, ty.bitSize(pt)) catch {
-                    return lower.fail("pseudo_compare size {}", .{ty.bitSize(pt)});
-                };
-
-                const is_unsigned = ty.isUnsignedInt(pt.zcu);
-                const less_than: Encoding.Mnemonic = if (is_unsigned) .sltu else .slt;
-
-                switch (class) {
-                    .int => switch (op) {
-                        .eq => {
-                            try lower.emit(.xor, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs1 },
-                                .{ .reg = rs2 },
-                            });
-
-                            try lower.emit(.sltiu, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rd },
-                                .{ .imm = Immediate.s(1) },
-                            });
+                    }
+                },
+                .pseudo_store_rm => {
+                    const src_reg = rm.r;
+                    const src_reg_class = src_reg.class();
+
+                    const dest_size = rm.m.mod.size;
+
+                    const mnem: Mnemonic = switch (src_reg_class) {
+                        .int => switch (dest_size) {
+                            .byte => .sb,
+                            .hword => .sh,
+                            .word => .sw,
+                            .dword => .sd,
                         },
-                        .neq => {
-                            try lower.emit(.xor, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs1 },
-                                .{ .reg = rs2 },
-                            });
-
-                            try lower.emit(.sltu, &.{
-                                .{ .reg = rd },
-                                .{ .reg = .zero },
-                                .{ .reg = rd },
-                            });
+                        .float => switch (dest_size) {
+                            .byte => unreachable, // Zig does not support 8-bit floats
+                            .hword => return lower.fail("TODO: lowerMir pseudo_store_rm support 16-bit floats", .{}),
+                            .word => .fsw,
+                            .dword => .fsd,
                         },
-                        .gt => {
-                            try lower.emit(less_than, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs2 },
-                                .{ .reg = rs1 },
-                            });
+                        .vector => switch (dest_size) {
+                            .byte => .vse8v,
+                            .hword => .vse16v,
+                            .word => .vse32v,
+                            .dword => .vse64v,
                         },
-                        .gte => {
-                            try lower.emit(less_than, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs1 },
-                                .{ .reg = rs2 },
-                            });
-                            try lower.emit(.xori, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rd },
-                                .{ .imm = Immediate.s(1) },
+                    };
+
+                    switch (src_reg_class) {
+                        .int, .float => {
+                            try lower.emit(mnem, &.{
+                                .{ .reg = frame_loc.base },
+                                .{ .reg = rm.r },
+                                .{ .imm = Immediate.s(frame_loc.disp) },
                             });
                         },
-                        .lt => {
-                            try lower.emit(less_than, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs1 },
-                                .{ .reg = rs2 },
+                        .vector => {
+                            assert(frame_loc.disp == 0);
+                            try lower.emit(mnem, &.{
+                                .{ .reg = rm.r },
+                                .{ .reg = frame_loc.base },
+                                .{ .reg = .zero },
                             });
                         },
-                        .lte => {
-                            try lower.emit(less_than, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs2 },
-                                .{ .reg = rs1 },
-                            });
+                    }
+                },
+                else => unreachable,
+            }
+        },
 
-                            try lower.emit(.xori, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rd },
-                                .{ .imm = Immediate.s(1) },
-                            });
-                        },
+        .pseudo_mv => {
+            const rr = inst.data.rr;
+
+            const dst_class = rr.rd.class();
+            const src_class = rr.rs.class();
+
+            switch (src_class) {
+                .float => switch (dst_class) {
+                    .float => {
+                        try lower.emit(if (lower.hasFeature(.d)) .fsgnjnd else .fsgnjns, &.{
+                            .{ .reg = rr.rd },
+                            .{ .reg = rr.rs },
+                            .{ .reg = rr.rs },
+                        });
                     },
-                    .float => switch (op) {
-                        // eq
-                        .eq => {
-                            try lower.emit(if (size == 64) .feqd else .feqs, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs1 },
-                                .{ .reg = rs2 },
-                            });
-                        },
-                        // !(eq)
-                        .neq => {
-                            try lower.emit(if (size == 64) .feqd else .feqs, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs1 },
-                                .{ .reg = rs2 },
-                            });
-                            try lower.emit(.xori, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rd },
-                                .{ .imm = Immediate.s(1) },
-                            });
-                        },
-                        .lt => {
-                            try lower.emit(if (size == 64) .fltd else .flts, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs1 },
-                                .{ .reg = rs2 },
-                            });
-                        },
-                        .lte => {
-                            try lower.emit(if (size == 64) .fled else .fles, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs1 },
-                                .{ .reg = rs2 },
-                            });
-                        },
-                        .gt => {
-                            try lower.emit(if (size == 64) .fltd else .flts, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs2 },
-                                .{ .reg = rs1 },
-                            });
-                        },
-                        .gte => {
-                            try lower.emit(if (size == 64) .fled else .fles, &.{
-                                .{ .reg = rd },
-                                .{ .reg = rs2 },
-                                .{ .reg = rs1 },
-                            });
-                        },
+                    .int, .vector => return lower.fail("TODO: lowerMir pseudo_mv float -> {s}", .{@tagName(dst_class)}),
+                },
+                .int => switch (dst_class) {
+                    .int => {
+                        try lower.emit(.addi, &.{
+                            .{ .reg = rr.rd },
+                            .{ .reg = rr.rs },
+                            .{ .imm = Immediate.s(0) },
+                        });
+                    },
+                    .vector => {
+                        try lower.emit(.vmvvx, &.{
+                            .{ .reg = rr.rd },
+                            .{ .reg = rr.rs },
+                            .{ .reg = .x0 },
+                        });
+                    },
+                    .float => return lower.fail("TODO: lowerMir pseudo_mv int -> {s}", .{@tagName(dst_class)}),
+                },
+                .vector => switch (dst_class) {
+                    .int => {
+                        try lower.emit(.vadcvv, &.{
+                            .{ .reg = rr.rd },
+                            .{ .reg = .zero },
+                            .{ .reg = rr.rs },
+                        });
+                    },
+                    .float, .vector => return lower.fail("TODO: lowerMir pseudo_mv vector -> {s}", .{@tagName(dst_class)}),
+                },
+            }
+        },
+
+        .pseudo_j => {
+            const j_type = inst.data.j_type;
+            try lower.emit(.jal, &.{
+                .{ .reg = j_type.rd },
+                .{ .imm = lower.reloc(.{ .inst = j_type.inst }) },
+            });
+        },
+
+        .pseudo_spill_regs => try lower.pushPopRegList(true, inst.data.reg_list),
+        .pseudo_restore_regs => try lower.pushPopRegList(false, inst.data.reg_list),
+
+        .pseudo_load_symbol => {
+            const payload = inst.data.reloc;
+            const dst_reg = payload.register;
+            assert(dst_reg.class() == .int);
+
+            try lower.emit(.lui, &.{
+                .{ .reg = dst_reg },
+                .{ .imm = lower.reloc(.{
+                    .load_symbol_reloc = .{
+                        .atom_index = payload.atom_index,
+                        .sym_index = payload.sym_index,
+                    },
+                }) },
+            });
+
+            // the reloc above implies this one
+            try lower.emit(.addi, &.{
+                .{ .reg = dst_reg },
+                .{ .reg = dst_reg },
+                .{ .imm = Immediate.s(0) },
+            });
+        },
+
+        .pseudo_lea_rm => {
+            const rm = inst.data.rm;
+            assert(rm.r.class() == .int);
+
+            const frame: Mir.FrameLoc = if (options.allow_frame_locs)
+                rm.m.toFrameLoc(lower.mir)
+            else
+                .{ .base = .s0, .disp = 0 };
+
+            try lower.emit(.addi, &.{
+                .{ .reg = rm.r },
+                .{ .reg = frame.base },
+                .{ .imm = Immediate.s(frame.disp) },
+            });
+        },
+
+        .pseudo_compare => {
+            const compare = inst.data.compare;
+            const op = compare.op;
+
+            const rd = compare.rd;
+            const rs1 = compare.rs1;
+            const rs2 = compare.rs2;
+
+            const class = rs1.class();
+            const ty = compare.ty;
+            const size = std.math.ceilPowerOfTwo(u64, ty.bitSize(pt)) catch {
+                return lower.fail("pseudo_compare size {}", .{ty.bitSize(pt)});
+            };
+
+            const is_unsigned = ty.isUnsignedInt(pt.zcu);
+            const less_than: Mnemonic = if (is_unsigned) .sltu else .slt;
+
+            switch (class) {
+                .int => switch (op) {
+                    .eq => {
+                        try lower.emit(.xor, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs1 },
+                            .{ .reg = rs2 },
+                        });
+
+                        try lower.emit(.sltiu, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rd },
+                            .{ .imm = Immediate.s(1) },
+                        });
+                    },
+                    .neq => {
+                        try lower.emit(.xor, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs1 },
+                            .{ .reg = rs2 },
+                        });
+
+                        try lower.emit(.sltu, &.{
+                            .{ .reg = rd },
+                            .{ .reg = .zero },
+                            .{ .reg = rd },
+                        });
+                    },
+                    .gt => {
+                        try lower.emit(less_than, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs2 },
+                            .{ .reg = rs1 },
+                        });
+                    },
+                    .gte => {
+                        try lower.emit(less_than, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs1 },
+                            .{ .reg = rs2 },
+                        });
+                        try lower.emit(.xori, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rd },
+                            .{ .imm = Immediate.s(1) },
+                        });
+                    },
+                    .lt => {
+                        try lower.emit(less_than, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs1 },
+                            .{ .reg = rs2 },
+                        });
+                    },
+                    .lte => {
+                        try lower.emit(less_than, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs2 },
+                            .{ .reg = rs1 },
+                        });
+
+                        try lower.emit(.xori, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rd },
+                            .{ .imm = Immediate.s(1) },
+                        });
+                    },
+                },
+                .float => switch (op) {
+                    // eq
+                    .eq => {
+                        try lower.emit(if (size == 64) .feqd else .feqs, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs1 },
+                            .{ .reg = rs2 },
+                        });
                     },
-                    .vector => return lower.fail("TODO: lowerMir pseudo_cmp vector", .{}),
-                }
-            },
-
-            .pseudo_not => {
-                const rr = inst.data.rr;
-                assert(rr.rs.class() == .int and rr.rd.class() == .int);
-
-                // mask out any other bits that aren't the boolean
-                try lower.emit(.andi, &.{
-                    .{ .reg = rr.rs },
-                    .{ .reg = rr.rs },
-                    .{ .imm = Immediate.s(1) },
-                });
-
-                try lower.emit(.sltiu, &.{
-                    .{ .reg = rr.rd },
-                    .{ .reg = rr.rs },
-                    .{ .imm = Immediate.s(1) },
-                });
-            },
-
-            .pseudo_extern_fn_reloc => {
-                const inst_reloc = inst.data.reloc;
-
-                try lower.emit(.auipc, &.{
-                    .{ .reg = .ra },
-                    .{ .imm = lower.reloc(
-                        .{ .call_extern_fn_reloc = .{
-                            .atom_index = inst_reloc.atom_index,
-                            .sym_index = inst_reloc.sym_index,
-                        } },
-                    ) },
-                });
-
-                try lower.emit(.jalr, &.{
-                    .{ .reg = .ra },
-                    .{ .reg = .ra },
-                    .{ .imm = Immediate.s(0) },
-                });
-            },
-
-            .pseudo_amo => {
-                const amo = inst.data.amo;
-                const is_d = amo.ty.abiSize(pt) == 8;
-                const is_un = amo.ty.isUnsignedInt(pt.zcu);
-
-                const mnem: Encoding.Mnemonic = switch (amo.op) {
-                    // zig fmt: off
-                    .SWAP => if (is_d) .amoswapd  else .amoswapw,
-                    .ADD  => if (is_d) .amoaddd   else .amoaddw,
-                    .AND  => if (is_d) .amoandd   else .amoandw,
-                    .OR   => if (is_d) .amoord    else .amoorw,
-                    .XOR  => if (is_d) .amoxord   else .amoxorw,
-                    .MAX  => if (is_d) if (is_un) .amomaxud else .amomaxd else if (is_un) .amomaxuw else .amomaxw,
-                    .MIN  => if (is_d) if (is_un) .amominud else .amomind else if (is_un) .amominuw else .amominw,
-                    // zig fmt: on
-                };
-
-                try lower.emit(mnem, &.{
-                    .{ .reg = inst.data.amo.rd },
-                    .{ .reg = inst.data.amo.rs1 },
-                    .{ .reg = inst.data.amo.rs2 },
-                    .{ .barrier = inst.data.amo.rl },
-                    .{ .barrier = inst.data.amo.aq },
-                });
-            },
-
-            .pseudo_fence => {
-                const fence = inst.data.fence;
-
-                try lower.emit(switch (fence.fm) {
-                    .tso => .fencetso,
-                    .none => .fence,
-                }, &.{
-                    .{ .barrier = fence.succ },
-                    .{ .barrier = fence.pred },
-                });
-            },
-
-            else => return lower.fail("TODO lower: psuedo {s}", .{@tagName(inst.ops)}),
+                    // !(eq)
+                    .neq => {
+                        try lower.emit(if (size == 64) .feqd else .feqs, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs1 },
+                            .{ .reg = rs2 },
+                        });
+                        try lower.emit(.xori, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rd },
+                            .{ .imm = Immediate.s(1) },
+                        });
+                    },
+                    .lt => {
+                        try lower.emit(if (size == 64) .fltd else .flts, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs1 },
+                            .{ .reg = rs2 },
+                        });
+                    },
+                    .lte => {
+                        try lower.emit(if (size == 64) .fled else .fles, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs1 },
+                            .{ .reg = rs2 },
+                        });
+                    },
+                    .gt => {
+                        try lower.emit(if (size == 64) .fltd else .flts, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs2 },
+                            .{ .reg = rs1 },
+                        });
+                    },
+                    .gte => {
+                        try lower.emit(if (size == 64) .fled else .fles, &.{
+                            .{ .reg = rd },
+                            .{ .reg = rs2 },
+                            .{ .reg = rs1 },
+                        });
+                    },
+                },
+                .vector => return lower.fail("TODO: lowerMir pseudo_cmp vector", .{}),
+            }
+        },
+
+        .pseudo_not => {
+            const rr = inst.data.rr;
+            assert(rr.rs.class() == .int and rr.rd.class() == .int);
+
+            // mask out any other bits that aren't the boolean
+            try lower.emit(.andi, &.{
+                .{ .reg = rr.rs },
+                .{ .reg = rr.rs },
+                .{ .imm = Immediate.s(1) },
+            });
+
+            try lower.emit(.sltiu, &.{
+                .{ .reg = rr.rd },
+                .{ .reg = rr.rs },
+                .{ .imm = Immediate.s(1) },
+            });
+        },
+
+        .pseudo_extern_fn_reloc => {
+            const inst_reloc = inst.data.reloc;
+
+            try lower.emit(.auipc, &.{
+                .{ .reg = .ra },
+                .{ .imm = lower.reloc(
+                    .{ .call_extern_fn_reloc = .{
+                        .atom_index = inst_reloc.atom_index,
+                        .sym_index = inst_reloc.sym_index,
+                    } },
+                ) },
+            });
+
+            try lower.emit(.jalr, &.{
+                .{ .reg = .ra },
+                .{ .reg = .ra },
+                .{ .imm = Immediate.s(0) },
+            });
+        },
+
+        .pseudo_amo => {
+            const amo = inst.data.amo;
+            const is_d = amo.ty.abiSize(pt) == 8;
+            const is_un = amo.ty.isUnsignedInt(pt.zcu);
+
+            const mnem: Mnemonic = switch (amo.op) {
+                // zig fmt: off
+                .SWAP => if (is_d) .amoswapd  else .amoswapw,
+                .ADD  => if (is_d) .amoaddd   else .amoaddw,
+                .AND  => if (is_d) .amoandd   else .amoandw,
+                .OR   => if (is_d) .amoord    else .amoorw,
+                .XOR  => if (is_d) .amoxord   else .amoxorw,
+                .MAX  => if (is_d) if (is_un) .amomaxud else .amomaxd else if (is_un) .amomaxuw else .amomaxw,
+                .MIN  => if (is_d) if (is_un) .amominud else .amomind else if (is_un) .amominuw else .amominw,
+                // zig fmt: on
+            };
+
+            try lower.emit(mnem, &.{
+                .{ .reg = inst.data.amo.rd },
+                .{ .reg = inst.data.amo.rs1 },
+                .{ .reg = inst.data.amo.rs2 },
+                .{ .barrier = inst.data.amo.rl },
+                .{ .barrier = inst.data.amo.aq },
+            });
+        },
+
+        .pseudo_fence => {
+            const fence = inst.data.fence;
+
+            try lower.emit(switch (fence.fm) {
+                .tso => .fencetso,
+                .none => .fence,
+            }, &.{
+                .{ .barrier = fence.succ },
+                .{ .barrier = fence.pred },
+            });
         },
     }
 
@@ -516,49 +492,46 @@ pub fn lowerMir(lower: *Lower, index: Mir.Inst.Index, options: struct {
 }
 
 fn generic(lower: *Lower, inst: Mir.Inst) Error!void {
-    const mnemonic = std.meta.stringToEnum(Encoding.Mnemonic, @tagName(inst.tag)) orelse {
-        return lower.fail("generic inst name '{s}' with op {s} doesn't match with a mnemonic", .{
-            @tagName(inst.tag),
-            @tagName(inst.ops),
-        });
-    };
-    try lower.emit(mnemonic, switch (inst.ops) {
+    const mnemonic = inst.tag;
+    try lower.emit(mnemonic, switch (inst.data) {
         .none => &.{},
-        .ri => &.{
-            .{ .reg = inst.data.u_type.rd },
-            .{ .imm = inst.data.u_type.imm20 },
+        .u_type => |u| &.{
+            .{ .reg = u.rd },
+            .{ .imm = u.imm20 },
         },
-        .rr => &.{
-            .{ .reg = inst.data.rr.rd },
-            .{ .reg = inst.data.rr.rs },
+        .i_type => |i| &.{
+            .{ .reg = i.rd },
+            .{ .reg = i.rs1 },
+            .{ .imm = i.imm12 },
         },
-        .rri => &.{
-            .{ .reg = inst.data.i_type.rd },
-            .{ .reg = inst.data.i_type.rs1 },
-            .{ .imm = inst.data.i_type.imm12 },
+        .rr => |rr| &.{
+            .{ .reg = rr.rd },
+            .{ .reg = rr.rs },
         },
-        .rr_inst => &.{
-            .{ .reg = inst.data.b_type.rs1 },
-            .{ .reg = inst.data.b_type.rs2 },
-            .{ .imm = lower.reloc(.{ .inst = inst.data.b_type.inst }) },
+        .b_type => |b| &.{
+            .{ .reg = b.rs1 },
+            .{ .reg = b.rs2 },
+            .{ .imm = lower.reloc(.{ .inst = b.inst }) },
         },
-        .rrr => &.{
-            .{ .reg = inst.data.r_type.rd },
-            .{ .reg = inst.data.r_type.rs1 },
-            .{ .reg = inst.data.r_type.rs2 },
+        .r_type => |r| &.{
+            .{ .reg = r.rd },
+            .{ .reg = r.rs1 },
+            .{ .reg = r.rs2 },
         },
-        .csr => &.{
-            .{ .csr = inst.data.csr.csr },
-            .{ .reg = inst.data.csr.rs1 },
-            .{ .reg = inst.data.csr.rd },
+        .csr => |csr| &.{
+            .{ .csr = csr.csr },
+            .{ .reg = csr.rs1 },
+            .{ .reg = csr.rd },
         },
-        else => return lower.fail("TODO: generic lower ops {s}", .{@tagName(inst.ops)}),
+        else => return lower.fail("TODO: generic lower {s}", .{@tagName(mnemonic)}),
     });
 }
 
-fn emit(lower: *Lower, mnemonic: Encoding.Mnemonic, ops: []const Instruction.Operand) !void {
-    lower.result_insts[lower.result_insts_len] =
-        try Instruction.new(mnemonic, ops);
+fn emit(lower: *Lower, mnemonic: Mnemonic, ops: []const Instruction.Operand) !void {
+    const lir = encoding.Lir.fromMnem(mnemonic);
+    const inst = Instruction.fromLir(lir, ops);
+
+    lower.result_insts[lower.result_insts_len] = inst;
     lower.result_insts_len += 1;
 }
 
@@ -580,7 +553,7 @@ fn pushPopRegList(lower: *Lower, comptime spilling: bool, reg_list: Mir.Register
         const reg = abi.Registers.all_preserved[i];
 
         const reg_class = reg.class();
-        const load_inst: Encoding.Mnemonic, const store_inst: Encoding.Mnemonic = switch (reg_class) {
+        const load_inst: Mnemonic, const store_inst: Mnemonic = switch (reg_class) {
             .int => .{ .ld, .sd },
             .float => .{ .fld, .fsd },
             .vector => unreachable,
@@ -618,20 +591,22 @@ fn hasFeature(lower: *Lower, feature: std.Target.riscv.Feature) bool {
 }
 
 const Lower = @This();
-
-const abi = @import("abi.zig");
-const assert = std.debug.assert;
-const bits = @import("bits.zig");
-const encoder = @import("encoder.zig");
-const link = @import("../../link.zig");
-const Encoding = @import("Encoding.zig");
 const std = @import("std");
+const assert = std.debug.assert;
 const log = std.log.scoped(.lower);
 
-const Air = @import("../../Air.zig");
 const Allocator = std.mem.Allocator;
 const ErrorMsg = Zcu.ErrorMsg;
-const Mir = @import("Mir.zig");
+
+const link = @import("../../link.zig");
+const Air = @import("../../Air.zig");
 const Zcu = @import("../../Zcu.zig");
-const Instruction = encoder.Instruction;
+
+const Mir = @import("Mir.zig");
+const abi = @import("abi.zig");
+const bits = @import("bits.zig");
+const encoding = @import("encoding.zig");
+
+const Mnemonic = @import("mnem.zig").Mnemonic;
 const Immediate = bits.Immediate;
+const Instruction = encoding.Instruction;
src/arch/riscv64/Mir.zig
@@ -1,170 +1,17 @@
 //! Machine Intermediate Representation.
-//! This data is produced by RISCV64 Codegen or RISCV64 assembly parsing
-//! These instructions have a 1:1 correspondence with machine code instructions
-//! for the target. MIR can be lowered to source-annotated textual assembly code
-//! instructions, or it can be lowered to machine code.
-//! 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.
+//! This data is produced by CodeGen.zig
 
 instructions: std.MultiArrayList(Inst).Slice,
-/// The meaning of this data is determined by `Inst.Tag` value.
-extra: []const u32,
 frame_locs: std.MultiArrayList(FrameLoc).Slice,
 
 pub const Inst = struct {
-    tag: Tag,
+    tag: Mnemonic,
     data: Data,
-    ops: Ops,
 
-    /// The position of an MIR instruction within the `Mir` instructions array.
     pub const Index = u32;
 
-    pub const Tag = enum(u16) {
-
-        // base extension
-        addi,
-        addiw,
-
-        jalr,
-        lui,
-
-        @"and",
-        andi,
-
-        xori,
-        xor,
-        @"or",
-
-        ebreak,
-        ecall,
-        unimp,
-
-        add,
-        addw,
-        sub,
-        subw,
-
-        sltu,
-        slt,
-
-        slli,
-        srli,
-        srai,
-
-        slliw,
-        srliw,
-        sraiw,
-
-        sll,
-        srl,
-        sra,
-
-        sllw,
-        srlw,
-        sraw,
-
-        jal,
-
-        beq,
-        bne,
-
-        nop,
-
-        ld,
-        lw,
-        lh,
-        lb,
-
-        sd,
-        sw,
-        sh,
-        sb,
-
-        // M extension
-        mul,
-        mulw,
-
-        div,
-        divu,
-        divw,
-        divuw,
-
-        rem,
-        remu,
-        remw,
-        remuw,
-
-        // F extension (32-bit float)
-        fadds,
-        fsubs,
-        fmuls,
-        fdivs,
-
-        fabss,
-
-        fmins,
-        fmaxs,
-
-        fsqrts,
-
-        flw,
-        fsw,
-
-        feqs,
-        flts,
-        fles,
-
-        // D extension (64-bit float)
-        faddd,
-        fsubd,
-        fmuld,
-        fdivd,
-
-        fabsd,
-
-        fmind,
-        fmaxd,
-
-        fsqrtd,
-
-        fld,
-        fsd,
-
-        feqd,
-        fltd,
-        fled,
-
-        // Zicsr Extension Instructions
-        csrrs,
-
-        // V Extension Instructions
-        vsetvli,
-        vsetivli,
-        vsetvl,
-        vaddvv,
-        vfaddvv,
-        vsubvv,
-        vfsubvv,
-        vmulvv,
-        vfmulvv,
-        vslidedownvx,
-
-        // Zbb Extension Instructions
-        clz,
-        clzw,
-
-        /// A pseudo-instruction. Used for anything that isn't 1:1 with an
-        /// assembly instruction.
-        pseudo,
-    };
-
-    /// All instructions have a 4-byte payload, which is contained within
-    /// this union. `Ops` determines which union field is active, as well as
-    /// how to interpret the data within.
-    pub const Data = union {
-        nop: void,
-        inst: Index,
-        payload: u32,
+    pub const Data = union(enum) {
+        none: void,
         r_type: struct {
             rd: Register,
             rs1: Register,
@@ -194,10 +41,6 @@ pub const Inst = struct {
             rd: Register,
             inst: Inst.Index,
         },
-        pseudo_dbg_line_column: struct {
-            line: u32,
-            column: u32,
-        },
         rm: struct {
             r: Register,
             m: Memory,
@@ -208,11 +51,6 @@ pub const Inst = struct {
             rd: Register,
             rs: Register,
         },
-        fabs: struct {
-            rd: Register,
-            rs: Register,
-            bits: u16,
-        },
         compare: struct {
             rd: Register,
             rs1: Register,
@@ -228,6 +66,7 @@ pub const Inst = struct {
             ty: Type,
         },
         reloc: struct {
+            register: Register,
             atom_index: u32,
             sym_index: u32,
         },
@@ -253,115 +92,26 @@ pub const Inst = struct {
             rs1: Register,
             rd: Register,
         },
-    };
-
-    pub const Ops = enum {
-        /// No data associated with this instruction (only mnemonic is used).
-        none,
-        /// Two registers
-        rr,
-        /// Three registers
-        rrr,
-
-        /// Two registers + immediate, uses the i_type payload.
-        rri,
-        //extern_fn_reloc/ Two registers + another instruction.
-        rr_inst,
-
-        /// Register + Memory
-        rm,
-
-        /// Register + Immediate
-        ri,
-
-        /// Another instruction.
-        inst,
-
-        /// Control and Status Register Instruction.
-        csr,
-
-        /// Pseudo-instruction that will generate a backpatched
-        /// function prologue.
-        pseudo_prologue,
-        /// Pseudo-instruction that will generate a backpatched
-        /// function epilogue
-        pseudo_epilogue,
-
-        /// Pseudo-instruction: End of prologue
-        pseudo_dbg_prologue_end,
-        /// Pseudo-instruction: Beginning of epilogue
-        pseudo_dbg_epilogue_begin,
-        /// Pseudo-instruction: Update debug line
-        pseudo_dbg_line_column,
-
-        /// Pseudo-instruction that loads from memory into a register.
-        ///
-        /// Uses `rm` payload.
-        pseudo_load_rm,
-        /// Pseudo-instruction that stores from a register into memory
-        ///
-        /// Uses `rm` payload.
-        pseudo_store_rm,
-
-        /// Pseudo-instruction that loads the address of memory into a register.
-        ///
-        /// Uses `rm` payload.
-        pseudo_lea_rm,
-
-        /// Jumps. Uses `inst` payload.
-        pseudo_j,
-
-        /// Floating point absolute value.
-        pseudo_fabs,
-
-        /// Dead inst, ignored by the emitter.
-        pseudo_dead,
-
-        /// Loads the address of a value that hasn't yet been allocated in memory.
-        ///
-        /// uses the Mir.LoadSymbolPayload payload.
-        pseudo_load_symbol,
-
-        /// Moves the value of rs1 to rd.
-        ///
-        /// uses the `rr` payload.
-        pseudo_mv,
-
-        pseudo_restore_regs,
-        pseudo_spill_regs,
-
-        pseudo_compare,
-
-        /// NOT operation on booleans. Does an `andi reg, reg, 1` to mask out any other bits from the boolean.
-        pseudo_not,
-
-        /// Generates an auipc + jalr pair, with a R_RISCV_CALL_PLT reloc
-        pseudo_extern_fn_reloc,
-
-        /// IORW, IORW
-        pseudo_fence,
-
-        /// Ordering, Src, Addr, Dest
-        pseudo_amo,
+        pseudo_dbg_line_column: struct {
+            line: u32,
+            column: u32,
+        },
     };
 
     pub fn format(
         inst: Inst,
         comptime fmt: []const u8,
-        options: std.fmt.FormatOptions,
+        _: std.fmt.FormatOptions,
         writer: anytype,
     ) !void {
         assert(fmt.len == 0);
-        _ = options;
-
-        try writer.print("Tag: {s}, Ops: {s}", .{ @tagName(inst.tag), @tagName(inst.ops) });
+        try writer.print("Tag: {s}, Data: {s}", .{ @tagName(inst.tag), @tagName(inst.data) });
     }
 };
 
 pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
     mir.instructions.deinit(gpa);
     mir.frame_locs.deinit(gpa);
-    gpa.free(mir.extra);
     mir.* = undefined;
 }
 
@@ -392,25 +142,12 @@ pub const AmoOp = enum(u5) {
     MIN,
 };
 
-/// Returns the requested data, as well as the new index which is at the start of the
-/// trailers for the object.
-pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
-    const fields = std.meta.fields(T);
-    var i: usize = index;
-    var result: T = undefined;
-    inline for (fields) |field| {
-        @field(result, field.name) = switch (field.type) {
-            u32 => mir.extra[i],
-            i32 => @as(i32, @bitCast(mir.extra[i])),
-            else => @compileError("bad field type"),
-        };
-        i += 1;
-    }
-    return .{
-        .data = result,
-        .end = i,
-    };
-}
+pub const FcvtOp = enum(u5) {
+    w = 0b00000,
+    wu = 0b00001,
+    l = 0b00010,
+    lu = 0b00011,
+};
 
 pub const LoadSymbolPayload = struct {
     register: u32,
@@ -459,10 +196,10 @@ const Mir = @This();
 const std = @import("std");
 const builtin = @import("builtin");
 const Type = @import("../../Type.zig");
+const bits = @import("bits.zig");
 
 const assert = std.debug.assert;
 
-const bits = @import("bits.zig");
 const Register = bits.Register;
 const CSR = bits.CSR;
 const Immediate = bits.Immediate;
@@ -470,3 +207,4 @@ const Memory = bits.Memory;
 const FrameIndex = bits.FrameIndex;
 const FrameAddr = @import("CodeGen.zig").FrameAddr;
 const IntegerBitSet = std.bit_set.IntegerBitSet;
+const Mnemonic = @import("mnem.zig").Mnemonic;
src/arch/riscv64/mnem.zig
@@ -0,0 +1,232 @@
+pub const Mnemonic = enum(u16) {
+    // Arithmetics
+    addi,
+    add,
+    addw,
+
+    sub,
+    subw,
+
+    // Bits
+    xori,
+    xor,
+    @"or",
+
+    @"and",
+    andi,
+
+    slt,
+    sltu,
+    sltiu,
+
+    slli,
+    srli,
+    srai,
+
+    slliw,
+    srliw,
+    sraiw,
+
+    sll,
+    srl,
+    sra,
+
+    sllw,
+    srlw,
+    sraw,
+
+    // Control Flow
+    jalr,
+    jal,
+
+    beq,
+
+    // Memory
+    lui,
+    auipc,
+
+    ld,
+    lw,
+    lh,
+    lb,
+    lbu,
+    lhu,
+    lwu,
+
+    sd,
+    sw,
+    sh,
+    sb,
+
+    // System
+    ebreak,
+    ecall,
+    unimp,
+    nop,
+
+    // M extension
+    mul,
+    mulh,
+    mulhu,
+    mulhsu,
+    mulw,
+
+    div,
+    divu,
+    divw,
+    divuw,
+
+    rem,
+    remu,
+    remw,
+    remuw,
+
+    // F extension (32-bit float)
+    fadds,
+    fsubs,
+    fmuls,
+    fdivs,
+
+    fmins,
+    fmaxs,
+
+    fsqrts,
+
+    flw,
+    fsw,
+
+    feqs,
+    flts,
+    fles,
+
+    // D extension (64-bit float)
+    faddd,
+    fsubd,
+    fmuld,
+    fdivd,
+
+    fmind,
+    fmaxd,
+
+    fsqrtd,
+
+    fld,
+    fsd,
+
+    feqd,
+    fltd,
+    fled,
+
+    fcvtws,
+    fcvtwus,
+    fcvtls,
+    fcvtlus,
+
+    fcvtwd,
+    fcvtwud,
+    fcvtld,
+    fcvtlud,
+
+    fsgnjns,
+    fsgnjnd,
+
+    fsgnjxs,
+    fsgnjxd,
+
+    // Zicsr Extension Instructions
+    csrrs,
+
+    // V Extension Instructions
+    vsetvli,
+    vsetivli,
+    vaddvv,
+    vfaddvv,
+    vsubvv,
+    vfsubvv,
+    vmulvv,
+    vfmulvv,
+    vslidedownvx,
+
+    vle8v,
+    vle16v,
+    vle32v,
+    vle64v,
+
+    vse8v,
+    vse16v,
+    vse32v,
+    vse64v,
+
+    vadcvv,
+    vmvvx,
+
+    // Zbb Extension Instructions
+    clz,
+    clzw,
+
+    // A Extension Instructions
+    fence,
+    fencetso,
+
+    amoswapw,
+    amoaddw,
+    amoandw,
+    amoorw,
+    amoxorw,
+    amomaxw,
+    amominw,
+    amomaxuw,
+    amominuw,
+
+    amoswapd,
+    amoaddd,
+    amoandd,
+    amoord,
+    amoxord,
+    amomaxd,
+    amomind,
+    amomaxud,
+    amominud,
+
+    // Pseudo-instructions. Used for anything that isn't 1:1 with an
+    // assembly instruction.
+
+    /// Pseudo-instruction that will generate a backpatched
+    /// function prologue.
+    pseudo_prologue,
+    /// Pseudo-instruction that will generate a backpatched
+    /// function epilogue
+    pseudo_epilogue,
+
+    /// Pseudo-instruction: End of prologue
+    pseudo_dbg_prologue_end,
+    /// Pseudo-instruction: Beginning of epilogue
+    pseudo_dbg_epilogue_begin,
+    /// Pseudo-instruction: Update debug line
+    pseudo_dbg_line_column,
+
+    /// Pseudo-instruction that loads from memory into a register.
+    pseudo_load_rm,
+    /// Pseudo-instruction that stores from a register into memory
+    pseudo_store_rm,
+    /// Pseudo-instruction that loads the address of memory into a register.
+    pseudo_lea_rm,
+    /// Jumps. Uses `inst` payload.
+    pseudo_j,
+    /// Dead inst, ignored by the emitter.
+    pseudo_dead,
+    /// Loads the address of a value that hasn't yet been allocated in memory.
+    pseudo_load_symbol,
+
+    /// Moves the value of rs1 to rd.
+    pseudo_mv,
+
+    pseudo_restore_regs,
+    pseudo_spill_regs,
+
+    pseudo_compare,
+    pseudo_not,
+    pseudo_extern_fn_reloc,
+    pseudo_fence,
+    pseudo_amo,
+};
src/link/riscv.zig
@@ -25,47 +25,27 @@ pub fn writeAddend(
 }
 
 pub fn writeInstU(code: *[4]u8, value: u32) void {
-    var data = Encoding.Data{
-        .U = mem.bytesToValue(std.meta.TagPayload(
-            Encoding.Data,
-            Encoding.Data.U,
-        ), code),
-    };
+    var data: Instruction = .{ .U = mem.bytesToValue(std.meta.TagPayload(Instruction, .U), code) };
     const compensated: u32 = @bitCast(@as(i32, @bitCast(value)) + 0x800);
     data.U.imm12_31 = bitSlice(compensated, 31, 12);
     mem.writeInt(u32, code, data.toU32(), .little);
 }
 
 pub fn writeInstI(code: *[4]u8, value: u32) void {
-    var data = Encoding.Data{
-        .I = mem.bytesToValue(std.meta.TagPayload(
-            Encoding.Data,
-            Encoding.Data.I,
-        ), code),
-    };
+    var data: Instruction = .{ .I = mem.bytesToValue(std.meta.TagPayload(Instruction, .I), code) };
     data.I.imm0_11 = bitSlice(value, 11, 0);
     mem.writeInt(u32, code, data.toU32(), .little);
 }
 
 pub fn writeInstS(code: *[4]u8, value: u32) void {
-    var data = Encoding.Data{
-        .S = mem.bytesToValue(std.meta.TagPayload(
-            Encoding.Data,
-            Encoding.Data.S,
-        ), code),
-    };
+    var data: Instruction = .{ .S = mem.bytesToValue(std.meta.TagPayload(Instruction, .S), code) };
     data.S.imm0_4 = bitSlice(value, 4, 0);
     data.S.imm5_11 = bitSlice(value, 11, 5);
     mem.writeInt(u32, code, data.toU32(), .little);
 }
 
 pub fn writeInstJ(code: *[4]u8, value: u32) void {
-    var data = Encoding.Data{
-        .J = mem.bytesToValue(std.meta.TagPayload(
-            Encoding.Data,
-            Encoding.Data.J,
-        ), code),
-    };
+    var data: Instruction = .{ .J = mem.bytesToValue(std.meta.TagPayload(Instruction, .J), code) };
     data.J.imm1_10 = bitSlice(value, 10, 1);
     data.J.imm11 = bitSlice(value, 11, 11);
     data.J.imm12_19 = bitSlice(value, 19, 12);
@@ -74,12 +54,7 @@ pub fn writeInstJ(code: *[4]u8, value: u32) void {
 }
 
 pub fn writeInstB(code: *[4]u8, value: u32) void {
-    var data = Encoding.Data{
-        .B = mem.bytesToValue(std.meta.TagPayload(
-            Encoding.Data,
-            Encoding.Data.B,
-        ), code),
-    };
+    var data: Instruction = .{ .B = mem.bytesToValue(std.meta.TagPayload(Instruction, .B), code) };
     data.B.imm1_4 = bitSlice(value, 4, 1);
     data.B.imm5_10 = bitSlice(value, 10, 5);
     data.B.imm11 = bitSlice(value, 11, 11);
@@ -109,9 +84,8 @@ pub const RiscvEflags = packed struct(u32) {
     _unused: u8,
 };
 
-const encoder = @import("../arch/riscv64/encoder.zig");
-const Encoding = @import("../arch/riscv64/Encoding.zig");
 const mem = std.mem;
 const std = @import("std");
 
-pub const Instruction = encoder.Instruction;
+const encoding = @import("../arch/riscv64/encoding.zig");
+const Instruction = encoding.Instruction;
test/behavior/align.zig
@@ -510,7 +510,6 @@ test "read 128-bit field from default aligned struct in global memory" {
 }
 
 test "struct field explicit alignment" {
-    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
test/behavior/byteswap.zig
@@ -100,6 +100,7 @@ test "@byteSwap vectors u8" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     try comptime vector8();
     try vector8();
test/behavior/defer.zig
@@ -116,6 +116,7 @@ test "errdefer with payload" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         fn foo() !i32 {
@@ -138,6 +139,7 @@ test "reference to errdefer payload" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         fn foo() !i32 {
test/behavior/optional.zig
@@ -591,6 +591,7 @@ test "cast slice to const slice nested in error union and optional" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         fn inner() !?[]u8 {
test/behavior/pointers.zig
@@ -228,6 +228,7 @@ test "implicit cast error unions with non-optional to optional pointer" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         fn doTheTest() !void {
test/behavior/switch.zig
@@ -427,6 +427,7 @@ test "else prong of switch on error set excludes other cases" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         fn doTheTest() !void {
@@ -462,6 +463,7 @@ test "switch prongs with error set cases make a new error set type for capture v
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         fn doTheTest() !void {
test/behavior/try.zig
@@ -51,6 +51,7 @@ test "`try`ing an if/else expression" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const S = struct {
         fn getError() !void {
CMakeLists.txt
@@ -539,10 +539,10 @@ set(ZIG_STAGE2_SOURCES
     src/arch/riscv64/bits.zig
     src/arch/riscv64/CodeGen.zig
     src/arch/riscv64/Emit.zig
-    src/arch/riscv64/encoder.zig
-    src/arch/riscv64/Encoding.zig
+    src/arch/riscv64/encoding.zig
     src/arch/riscv64/Lower.zig
     src/arch/riscv64/Mir.zig
+    src/arch/riscv64/mnem.zig
     src/arch/sparc64/CodeGen.zig
     src/arch/sparc64/Emit.zig
     src/arch/sparc64/Mir.zig