Commit 817fb263b5

Jakub Konka <kubkon@jakubkonka.com>
2023-03-05 17:40:53
x86_64: downstream table-driven instruction encoder
1 parent 4ea2f44
src/arch/x86_64/bits.zig
@@ -1,9 +1,9 @@
 const std = @import("std");
-const testing = std.testing;
-const mem = std.mem;
 const assert = std.debug.assert;
-const ArrayList = std.ArrayList;
+const expect = std.testing.expect;
+
 const Allocator = std.mem.Allocator;
+const ArrayList = std.ArrayList;
 const DW = std.dwarf;
 
 /// EFLAGS condition codes
@@ -135,960 +135,357 @@ pub const Condition = enum(u5) {
     }
 };
 
-/// Definitions of all of the general purpose x64 registers. The order is semantically meaningful.
-/// The registers are defined such that IDs go in descending order of 64-bit,
-/// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen
-/// registers. This results in some useful properties:
-///
-/// Any 64-bit register can be turned into its 32-bit form by adding 16, and
-/// vice versa. This also works between 32-bit and 16-bit forms. With 8-bit, it
-/// works for all except for sp, bp, si, and di, which do *not* have an 8-bit
-/// form.
-///
-/// If (register & 8) is set, the register is extended.
-///
-/// The ID can be easily determined by figuring out what range the register is
-/// in, and then subtracting the base.
 pub const Register = enum(u7) {
     // zig fmt: off
-    // 0 through 15, 64-bit registers. 8-15 are extended.
-    // id is just the int value.
     rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi,
     r8, r9, r10, r11, r12, r13, r14, r15,
 
-    // 16 through 31, 32-bit registers. 24-31 are extended.
-    // id is int value - 16.
     eax, ecx, edx, ebx, esp, ebp, esi, edi,
     r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d,
 
-    // 32-47, 16-bit registers. 40-47 are extended.
-    // id is int value - 32.
     ax, cx, dx, bx, sp, bp, si, di,
     r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w,
 
-    // 48-63, 8-bit registers. 56-63 are extended.
-    // id is int value - 48.
-    al, cl, dl, bl, ah, ch, dh, bh,
+    al, cl, dl, bl, spl, bpl, sil, dil,
     r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,
 
-    // 64-79, 256-bit registers.
-    // id is int value - 64.
+    ah, ch, dh, bh,
+
     ymm0, ymm1, ymm2,  ymm3,  ymm4,  ymm5,  ymm6,  ymm7,
     ymm8, ymm9, ymm10, ymm11, ymm12, ymm13, ymm14, ymm15,
 
-    // 80-95, 128-bit registers.
-    // id is int value - 80.
     xmm0, xmm1, xmm2,  xmm3,  xmm4,  xmm5,  xmm6,  xmm7,
     xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15,
 
-    // Pseudo-value for MIR instructions.
+    es, cs, ss, ds, fs, gs,
+
     none,
     // zig fmt: on
 
-    pub fn id(self: Register) u7 {
-        return switch (@enumToInt(self)) {
-            0...63 => @as(u7, @truncate(u4, @enumToInt(self))),
-            64...79 => @enumToInt(self),
-            else => unreachable,
-        };
-    }
-
-    /// Returns the bit-width of the register.
-    pub fn size(self: Register) u9 {
-        return switch (@enumToInt(self)) {
-            0...15 => 64,
-            16...31 => 32,
-            32...47 => 16,
-            48...63 => 8,
-            64...79 => 256,
-            80...95 => 128,
-            else => unreachable,
-        };
-    }
-
-    /// Returns whether the register is *extended*. Extended registers are the
-    /// new registers added with amd64, r8 through r15. This also includes any
-    /// other variant of access to those registers, such as r8b, r15d, and so
-    /// on. This is needed because access to these registers requires special
-    /// handling via the REX prefix, via the B or R bits, depending on context.
-    pub fn isExtended(self: Register) bool {
-        return @enumToInt(self) & 0x08 != 0;
-    }
-
-    /// This returns the 4-bit register ID, which is used in practically every
-    /// opcode. Note that bit 3 (the highest bit) is *never* used directly in
-    /// an instruction (@see isExtended), and requires special handling. The
-    /// lower three bits are often embedded directly in instructions (such as
-    /// the B8 variant of moves), or used in R/M bytes.
-    pub fn enc(self: Register) u4 {
-        return @truncate(u4, @enumToInt(self));
-    }
-
-    /// Like enc, but only returns the lower 3 bits.
-    pub fn lowEnc(self: Register) u3 {
-        return @truncate(u3, @enumToInt(self));
-    }
-
-    pub fn to256(self: Register) Register {
-        return @intToEnum(Register, @as(u8, self.enc()) + 64);
-    }
+    pub const Class = enum(u2) {
+        general_purpose,
+        floating_point,
+        segment,
+    };
 
-    pub fn to128(self: Register) Register {
-        return @intToEnum(Register, @as(u8, self.enc()) + 80);
-    }
+    pub fn class(reg: Register) Class {
+        return switch (@enumToInt(reg)) {
+            // zig fmt: off
+            @enumToInt(Register.rax)  ... @enumToInt(Register.r15)   => .general_purpose,
+            @enumToInt(Register.eax)  ... @enumToInt(Register.r15d)  => .general_purpose,
+            @enumToInt(Register.ax)   ... @enumToInt(Register.r15w)  => .general_purpose,
+            @enumToInt(Register.al)   ... @enumToInt(Register.r15b)  => .general_purpose,
+            @enumToInt(Register.ah)   ... @enumToInt(Register.bh)    => .general_purpose,
 
-    /// Convert from any register to its 64 bit alias.
-    pub fn to64(self: Register) Register {
-        return @intToEnum(Register, self.enc());
-    }
+            @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => .floating_point,
+            @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => .floating_point,
 
-    /// Convert from any register to its 32 bit alias.
-    pub fn to32(self: Register) Register {
-        return @intToEnum(Register, @as(u8, self.enc()) + 16);
-    }
-
-    /// Convert from any register to its 16 bit alias.
-    pub fn to16(self: Register) Register {
-        return @intToEnum(Register, @as(u8, self.enc()) + 32);
-    }
+            @enumToInt(Register.es)   ... @enumToInt(Register.gs)    => .segment,
 
-    /// Convert from any register to its 8 bit alias.
-    pub fn to8(self: Register) Register {
-        return @intToEnum(Register, @as(u8, self.enc()) + 48);
+            else => unreachable,
+            // zig fmt: on
+        };
     }
 
-    pub fn dwarfLocOp(self: Register) u8 {
-        switch (@enumToInt(self)) {
-            0...63 => return switch (self.to64()) {
-                .rax => DW.OP.reg0,
-                .rdx => DW.OP.reg1,
-                .rcx => DW.OP.reg2,
-                .rbx => DW.OP.reg3,
-                .rsi => DW.OP.reg4,
-                .rdi => DW.OP.reg5,
-                .rbp => DW.OP.reg6,
-                .rsp => DW.OP.reg7,
-
-                .r8 => DW.OP.reg8,
-                .r9 => DW.OP.reg9,
-                .r10 => DW.OP.reg10,
-                .r11 => DW.OP.reg11,
-                .r12 => DW.OP.reg12,
-                .r13 => DW.OP.reg13,
-                .r14 => DW.OP.reg14,
-                .r15 => DW.OP.reg15,
+    pub fn id(reg: Register) u6 {
+        const base = switch (@enumToInt(reg)) {
+            // zig fmt: off
+            @enumToInt(Register.rax)  ... @enumToInt(Register.r15)   => @enumToInt(Register.rax),
+            @enumToInt(Register.eax)  ... @enumToInt(Register.r15d)  => @enumToInt(Register.eax),
+            @enumToInt(Register.ax)   ... @enumToInt(Register.r15w)  => @enumToInt(Register.ax),
+            @enumToInt(Register.al)   ... @enumToInt(Register.r15b)  => @enumToInt(Register.al),
+            @enumToInt(Register.ah)   ... @enumToInt(Register.bh)    => @enumToInt(Register.ah) - 4,
 
-                else => unreachable,
-            },
+            @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => @enumToInt(Register.ymm0) - 16,
+            @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => @enumToInt(Register.xmm0) - 16,
 
-            64...79 => return @as(u8, self.enc()) + DW.OP.reg17,
+            @enumToInt(Register.es)   ... @enumToInt(Register.gs)    => @enumToInt(Register.es) - 32,
 
             else => unreachable,
-        }
+            // zig fmt: on
+        };
+        return @intCast(u6, @enumToInt(reg) - base);
     }
 
-    /// DWARF encodings that push a value onto the DWARF stack that is either
-    /// the contents of a register or the result of adding the contents a given
-    /// register to a given signed offset.
-    pub fn dwarfLocOpDeref(self: Register) u8 {
-        switch (@enumToInt(self)) {
-            0...63 => return switch (self.to64()) {
-                .rax => DW.OP.breg0,
-                .rdx => DW.OP.breg1,
-                .rcx => DW.OP.breg2,
-                .rbx => DW.OP.breg3,
-                .rsi => DW.OP.breg4,
-                .rdi => DW.OP.breg5,
-                .rbp => DW.OP.breg6,
-                .rsp => DW.OP.fbreg,
-
-                .r8 => DW.OP.breg8,
-                .r9 => DW.OP.breg9,
-                .r10 => DW.OP.breg10,
-                .r11 => DW.OP.breg11,
-                .r12 => DW.OP.breg12,
-                .r13 => DW.OP.breg13,
-                .r14 => DW.OP.breg14,
-                .r15 => DW.OP.breg15,
+    pub fn size(reg: Register) u32 {
+        return switch (@enumToInt(reg)) {
+            // zig fmt: off
+            @enumToInt(Register.rax)  ... @enumToInt(Register.r15)   => 64,
+            @enumToInt(Register.eax)  ... @enumToInt(Register.r15d)  => 32,
+            @enumToInt(Register.ax)   ... @enumToInt(Register.r15w)  => 16,
+            @enumToInt(Register.al)   ... @enumToInt(Register.r15b)  => 8,
+            @enumToInt(Register.ah)   ... @enumToInt(Register.bh)    => 8,
 
-                else => unreachable,
-            },
+            @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => 256,
+            @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => 128,
 
-            64...79 => return @as(u8, self.enc()) + DW.OP.breg17,
+            @enumToInt(Register.es)   ... @enumToInt(Register.gs)    => 16,
 
             else => unreachable,
-        }
-    }
-};
-
-// zig fmt: on
-
-/// Encoding helper functions for x86_64 instructions
-///
-/// Many of these helpers do very little, but they can help make things
-/// slightly more readable with more descriptive field names / function names.
-///
-/// Some of them also have asserts to ensure that we aren't doing dumb things.
-/// For example, trying to use register 4 (esp) in an indirect modr/m byte is illegal,
-/// you need to encode it with an SIB byte.
-///
-/// Note that ALL of these helper functions will assume capacity,
-/// so ensure that the `code` has sufficient capacity before using them.
-/// The `init` method is the recommended way to ensure capacity.
-pub const Encoder = struct {
-    /// Non-owning reference to the code array
-    code: *ArrayList(u8),
-
-    const Self = @This();
-
-    /// Wrap `code` in Encoder to make it easier to call these helper functions
-    ///
-    /// maximum_inst_size should contain the maximum number of bytes
-    /// that the encoded instruction will take.
-    /// This is because the helper functions will assume capacity
-    /// in order to avoid bounds checking.
-    pub fn init(code: *ArrayList(u8), maximum_inst_size: u8) !Self {
-        try code.ensureUnusedCapacity(maximum_inst_size);
-        return Self{ .code = code };
-    }
-
-    /// Directly write a number to the code array with big endianness
-    pub fn writeIntBig(self: Self, comptime T: type, value: T) void {
-        mem.writeIntBig(
-            T,
-            self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)),
-            value,
-        );
+            // zig fmt: on
+        };
     }
 
-    /// Directly write a number to the code array with little endianness
-    pub fn writeIntLittle(self: Self, comptime T: type, value: T) void {
-        mem.writeIntLittle(
-            T,
-            self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)),
-            value,
-        );
-    }
+    pub fn isExtended(reg: Register) bool {
+        return switch (@enumToInt(reg)) {
+            // zig fmt: off
+            @enumToInt(Register.r8)  ... @enumToInt(Register.r15)    => true,
+            @enumToInt(Register.r8d) ... @enumToInt(Register.r15d)   => true,
+            @enumToInt(Register.r8w) ... @enumToInt(Register.r15w)   => true,
+            @enumToInt(Register.r8b) ... @enumToInt(Register.r15b)   => true,
 
-    // --------
-    // Prefixes
-    // --------
-
-    pub const LegacyPrefixes = packed struct {
-        /// LOCK
-        prefix_f0: bool = false,
-        /// REPNZ, REPNE, REP, Scalar Double-precision
-        prefix_f2: bool = false,
-        /// REPZ, REPE, REP, Scalar Single-precision
-        prefix_f3: bool = false,
-
-        /// CS segment override or Branch not taken
-        prefix_2e: bool = false,
-        /// DS segment override
-        prefix_36: bool = false,
-        /// ES segment override
-        prefix_26: bool = false,
-        /// FS segment override
-        prefix_64: bool = false,
-        /// GS segment override
-        prefix_65: bool = false,
-
-        /// Branch taken
-        prefix_3e: bool = false,
-
-        /// Operand size override (enables 16 bit operation)
-        prefix_66: bool = false,
-
-        /// Address size override (enables 16 bit address size)
-        prefix_67: bool = false,
-
-        padding: u5 = 0,
-    };
+            @enumToInt(Register.ymm8) ... @enumToInt(Register.ymm15) => true,
+            @enumToInt(Register.xmm8) ... @enumToInt(Register.xmm15) => true,
 
-    /// Encodes legacy prefixes
-    pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) void {
-        if (@bitCast(u16, prefixes) != 0) {
-            // Hopefully this path isn't taken very often, so we'll do it the slow way for now
-
-            // LOCK
-            if (prefixes.prefix_f0) self.code.appendAssumeCapacity(0xf0);
-            // REPNZ, REPNE, REP, Scalar Double-precision
-            if (prefixes.prefix_f2) self.code.appendAssumeCapacity(0xf2);
-            // REPZ, REPE, REP, Scalar Single-precision
-            if (prefixes.prefix_f3) self.code.appendAssumeCapacity(0xf3);
-
-            // CS segment override or Branch not taken
-            if (prefixes.prefix_2e) self.code.appendAssumeCapacity(0x2e);
-            // DS segment override
-            if (prefixes.prefix_36) self.code.appendAssumeCapacity(0x36);
-            // ES segment override
-            if (prefixes.prefix_26) self.code.appendAssumeCapacity(0x26);
-            // FS segment override
-            if (prefixes.prefix_64) self.code.appendAssumeCapacity(0x64);
-            // GS segment override
-            if (prefixes.prefix_65) self.code.appendAssumeCapacity(0x65);
-
-            // Branch taken
-            if (prefixes.prefix_3e) self.code.appendAssumeCapacity(0x3e);
-
-            // Operand size override
-            if (prefixes.prefix_66) self.code.appendAssumeCapacity(0x66);
-
-            // Address size override
-            if (prefixes.prefix_67) self.code.appendAssumeCapacity(0x67);
-        }
+            else => false,
+            // zig fmt: on
+        };
     }
 
-    /// Use 16 bit operand size
-    ///
-    /// Note that this flag is overridden by REX.W, if both are present.
-    pub fn prefix16BitMode(self: Self) void {
-        self.code.appendAssumeCapacity(0x66);
+    pub fn isRexInvalid(reg: Register) bool {
+        return switch (@enumToInt(reg)) {
+            @enumToInt(Register.ah)...@enumToInt(Register.bh) => true,
+            else => false,
+        };
     }
 
-    pub const Vex = struct {
-        rex_prefix: Rex = .{},
-        lead_opc: u5 = 0b0_0001,
-        register: u4 = 0b1111,
-        length: u1 = 0b0,
-        simd_prefix: u2 = 0b00,
-        wig_desc: bool = false,
-        lig_desc: bool = false,
-        lz_desc: bool = false,
-
-        pub fn rex(self: *Vex, r: Rex) void {
-            self.rex_prefix = r;
-        }
-
-        pub fn lead_opc_0f(self: *Vex) void {
-            self.lead_opc = 0b0_0001;
-        }
-
-        pub fn lead_opc_0f_38(self: *Vex) void {
-            self.lead_opc = 0b0_0010;
-        }
-
-        pub fn lead_opc_0f_3a(self: *Vex) void {
-            self.lead_opc = 0b0_0011;
-        }
-
-        pub fn reg(self: *Vex, register: u4) void {
-            self.register = ~register;
-        }
-
-        pub fn len_128(self: *Vex) void {
-            self.length = 0;
-        }
-
-        pub fn len_256(self: *Vex) void {
-            assert(!self.lz_desc);
-            self.length = 1;
-        }
-
-        pub fn simd_prefix_66(self: *Vex) void {
-            self.simd_prefix = 0b01;
-        }
-
-        pub fn simd_prefix_f3(self: *Vex) void {
-            self.simd_prefix = 0b10;
-        }
-
-        pub fn simd_prefix_f2(self: *Vex) void {
-            self.simd_prefix = 0b11;
-        }
-
-        pub fn wig(self: *Vex) void {
-            self.wig_desc = true;
-        }
-
-        pub fn lig(self: *Vex) void {
-            self.lig_desc = true;
-        }
-
-        pub fn lz(self: *Vex) void {
-            self.lz_desc = true;
-        }
+    pub fn enc(reg: Register) u4 {
+        const base = switch (@enumToInt(reg)) {
+            // zig fmt: off
+            @enumToInt(Register.rax)  ... @enumToInt(Register.r15)   => @enumToInt(Register.rax),
+            @enumToInt(Register.eax)  ... @enumToInt(Register.r15d)  => @enumToInt(Register.eax),
+            @enumToInt(Register.ax)   ... @enumToInt(Register.r15w)  => @enumToInt(Register.ax),
+            @enumToInt(Register.al)   ... @enumToInt(Register.r15b)  => @enumToInt(Register.al),
+            @enumToInt(Register.ah)   ... @enumToInt(Register.bh)    => @enumToInt(Register.ah) - 4,
 
-        pub fn write(self: Vex, writer: anytype) usize {
-            var buf: [3]u8 = .{0} ** 3;
-            const form_3byte: bool = blk: {
-                if (self.rex_prefix.w and !self.wig_desc) break :blk true;
-                if (self.rex_prefix.x or self.rex_prefix.b) break :blk true;
-                break :blk self.lead_opc != 0b0_0001;
-            };
+            @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => @enumToInt(Register.ymm0),
+            @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => @enumToInt(Register.xmm0),
 
-            if (self.lz_desc) {
-                assert(self.length == 0);
-            }
-
-            if (form_3byte) {
-                // First byte
-                buf[0] = 0xc4;
-                // Second byte
-                const rxb_mask: u3 = @intCast(u3, @boolToInt(!self.rex_prefix.r)) << 2 |
-                    @intCast(u2, @boolToInt(!self.rex_prefix.x)) << 1 |
-                    @boolToInt(!self.rex_prefix.b);
-                buf[1] |= @intCast(u8, rxb_mask) << 5;
-                buf[1] |= self.lead_opc;
-                // Third byte
-                buf[2] |= @intCast(u8, @boolToInt(!self.rex_prefix.w)) << 7;
-                buf[2] |= @intCast(u7, self.register) << 3;
-                buf[2] |= @intCast(u3, self.length) << 2;
-                buf[2] |= self.simd_prefix;
-            } else {
-                // First byte
-                buf[0] = 0xc5;
-                // Second byte
-                buf[1] |= @intCast(u8, @boolToInt(!self.rex_prefix.r)) << 7;
-                buf[1] |= @intCast(u7, self.register) << 3;
-                buf[1] |= @intCast(u3, self.length) << 2;
-                buf[1] |= self.simd_prefix;
-            }
-
-            const count: usize = if (form_3byte) 3 else 2;
-            _ = writer.writeAll(buf[0..count]) catch unreachable;
-            return count;
-        }
-    };
+            @enumToInt(Register.es)   ... @enumToInt(Register.gs)    => @enumToInt(Register.es),
 
-    pub fn vex(self: Self, prefix: Vex) void {
-        _ = prefix.write(self.code.writer());
+            else => unreachable,
+            // zig fmt: on
+        };
+        return @truncate(u4, @enumToInt(reg) - base);
     }
 
-    /// From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB
-    pub const Rex = struct {
-        /// Wide, enables 64-bit operation
-        w: bool = false,
-        /// Extends the reg field in the ModR/M byte
-        r: bool = false,
-        /// Extends the index field in the SIB byte
-        x: bool = false,
-        /// Extends the r/m field in the ModR/M byte,
-        ///      or the base field in the SIB byte,
-        ///      or the reg field in the Opcode byte
-        b: bool = false,
-    };
-
-    /// Encodes a REX prefix byte given all the fields
-    ///
-    /// Use this byte whenever you need 64 bit operation,
-    /// or one of reg, index, r/m, base, or opcode-reg might be extended.
-    ///
-    /// See struct `Rex` for a description of each field.
-    ///
-    /// Does not add a prefix byte if none of the fields are set!
-    pub fn rex(self: Self, byte: Rex) void {
-        var value: u8 = 0b0100_0000;
-
-        if (byte.w) value |= 0b1000;
-        if (byte.r) value |= 0b0100;
-        if (byte.x) value |= 0b0010;
-        if (byte.b) value |= 0b0001;
-
-        if (value != 0b0100_0000) {
-            self.code.appendAssumeCapacity(value);
-        }
+    pub fn lowEnc(reg: Register) u3 {
+        return @truncate(u3, reg.enc());
     }
 
-    // ------
-    // Opcode
-    // ------
-
-    /// Encodes a 1 byte opcode
-    pub fn opcode_1byte(self: Self, opcode: u8) void {
-        self.code.appendAssumeCapacity(opcode);
+    pub fn toSize(reg: Register, bit_size: u32) Register {
+        return switch (bit_size) {
+            8 => reg.to8(),
+            16 => reg.to16(),
+            32 => reg.to32(),
+            64 => reg.to64(),
+            128 => reg.to128(),
+            256 => reg.to256(),
+            else => unreachable,
+        };
     }
 
-    /// Encodes a 2 byte opcode
-    ///
-    /// e.g. IMUL has the opcode 0x0f 0xaf, so you use
-    ///
-    /// encoder.opcode_2byte(0x0f, 0xaf);
-    pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) void {
-        self.code.appendAssumeCapacity(prefix);
-        self.code.appendAssumeCapacity(opcode);
+    fn gpBase(reg: Register) u7 {
+        assert(reg.class() == .general_purpose);
+        return switch (@enumToInt(reg)) {
+            // zig fmt: off
+            @enumToInt(Register.rax)  ... @enumToInt(Register.r15)   => @enumToInt(Register.rax),
+            @enumToInt(Register.eax)  ... @enumToInt(Register.r15d)  => @enumToInt(Register.eax),
+            @enumToInt(Register.ax)   ... @enumToInt(Register.r15w)  => @enumToInt(Register.ax),
+            @enumToInt(Register.al)   ... @enumToInt(Register.r15b)  => @enumToInt(Register.al),
+            @enumToInt(Register.ah)   ... @enumToInt(Register.bh)    => @enumToInt(Register.ah) - 4,
+            else => unreachable,
+            // zig fmt: on
+        };
     }
 
-    /// Encodes a 3 byte opcode
-    ///
-    /// e.g. MOVSD has the opcode 0xf2 0x0f 0x10
-    ///
-    /// encoder.opcode_3byte(0xf2, 0x0f, 0x10);
-    pub fn opcode_3byte(self: Self, prefix_1: u8, prefix_2: u8, opcode: u8) void {
-        self.code.appendAssumeCapacity(prefix_1);
-        self.code.appendAssumeCapacity(prefix_2);
-        self.code.appendAssumeCapacity(opcode);
+    pub fn to64(reg: Register) Register {
+        return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.rax));
     }
 
-    /// Encodes a 1 byte opcode with a reg field
-    ///
-    /// Remember to add a REX prefix byte if reg is extended!
-    pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) void {
-        assert(opcode & 0b111 == 0);
-        self.code.appendAssumeCapacity(opcode | reg);
+    pub fn to32(reg: Register) Register {
+        return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.eax));
     }
 
-    // ------
-    // ModR/M
-    // ------
-
-    /// Construct a ModR/M byte given all the fields
-    ///
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) void {
-        self.code.appendAssumeCapacity(
-            @as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm,
-        );
+    pub fn to16(reg: Register) Register {
+        return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.ax));
     }
 
-    /// Construct a ModR/M byte using direct r/m addressing
-    /// r/m effective address: r/m
-    ///
-    /// Note reg's effective address is always just reg for the ModR/M byte.
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) void {
-        self.modRm(0b11, reg_or_opx, rm);
+    pub fn to8(reg: Register) Register {
+        return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.al));
     }
 
-    /// Construct a ModR/M byte using indirect r/m addressing
-    /// r/m effective address: [r/m]
-    ///
-    /// Note reg's effective address is always just reg for the ModR/M byte.
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) void {
-        assert(rm != 4 and rm != 5);
-        self.modRm(0b00, reg_or_opx, rm);
+    fn fpBase(reg: Register) u7 {
+        assert(reg.class() == .floating_point);
+        return switch (@enumToInt(reg)) {
+            @enumToInt(Register.ymm0)...@enumToInt(Register.ymm15) => @enumToInt(Register.ymm0),
+            @enumToInt(Register.xmm0)...@enumToInt(Register.xmm15) => @enumToInt(Register.xmm0),
+            else => unreachable,
+        };
     }
 
-    /// Construct a ModR/M byte using indirect SIB addressing
-    /// r/m effective address: [SIB]
-    ///
-    /// Note reg's effective address is always just reg for the ModR/M byte.
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) void {
-        self.modRm(0b00, reg_or_opx, 0b100);
+    pub fn to256(reg: Register) Register {
+        return @intToEnum(Register, @enumToInt(reg) - reg.fpBase() + @enumToInt(Register.ymm0));
     }
 
-    /// Construct a ModR/M byte using RIP-relative addressing
-    /// r/m effective address: [RIP + disp32]
-    ///
-    /// Note reg's effective address is always just reg for the ModR/M byte.
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) void {
-        self.modRm(0b00, reg_or_opx, 0b101);
+    pub fn to128(reg: Register) Register {
+        return @intToEnum(Register, @enumToInt(reg) - reg.fpBase() + @enumToInt(Register.xmm0));
     }
 
-    /// Construct a ModR/M byte using indirect r/m with a 8bit displacement
-    /// r/m effective address: [r/m + disp8]
-    ///
-    /// Note reg's effective address is always just reg for the ModR/M byte.
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) void {
-        assert(rm != 4);
-        self.modRm(0b01, reg_or_opx, rm);
+    pub fn dwarfLocOp(reg: Register) u8 {
+        return switch (reg.class()) {
+            .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.reg0,
+            .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.reg17,
+            else => unreachable,
+        };
     }
 
-    /// Construct a ModR/M byte using indirect SIB with a 8bit displacement
-    /// r/m effective address: [SIB + disp8]
-    ///
-    /// Note reg's effective address is always just reg for the ModR/M byte.
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) void {
-        self.modRm(0b01, reg_or_opx, 0b100);
+    /// DWARF encodings that push a value onto the DWARF stack that is either
+    /// the contents of a register or the result of adding the contents a given
+    /// register to a given signed offset.
+    pub fn dwarfLocOpDeref(reg: Register) u8 {
+        return switch (reg.class()) {
+            .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.breg0,
+            .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.breg17,
+            else => unreachable,
+        };
     }
+};
 
-    /// Construct a ModR/M byte using indirect r/m with a 32bit displacement
-    /// r/m effective address: [r/m + disp32]
-    ///
-    /// Note reg's effective address is always just reg for the ModR/M byte.
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) void {
-        assert(rm != 4);
-        self.modRm(0b10, reg_or_opx, rm);
-    }
+test "Register id - different classes" {
+    try expect(Register.al.id() == Register.ax.id());
+    try expect(Register.ah.id() == Register.spl.id());
+    try expect(Register.ax.id() == Register.eax.id());
+    try expect(Register.eax.id() == Register.rax.id());
 
-    /// Construct a ModR/M byte using indirect SIB with a 32bit displacement
-    /// r/m effective address: [SIB + disp32]
-    ///
-    /// Note reg's effective address is always just reg for the ModR/M byte.
-    /// Remember to add a REX prefix byte if reg or rm are extended!
-    pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) void {
-        self.modRm(0b10, reg_or_opx, 0b100);
-    }
+    try expect(Register.ymm0.id() == 0b10000);
+    try expect(Register.ymm0.id() != Register.rax.id());
+    try expect(Register.xmm0.id() == Register.ymm0.id());
 
-    // ---
-    // SIB
-    // ---
-
-    /// Construct a SIB byte given all the fields
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib(self: Self, scale: u2, index: u3, base: u3) void {
-        self.code.appendAssumeCapacity(
-            @as(u8, scale) << 6 | @as(u8, index) << 3 | base,
-        );
-    }
+    try expect(Register.es.id() == 0b100000);
+}
 
-    /// Construct a SIB byte with scale * index + base, no frills.
-    /// r/m effective address: [base + scale * index]
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) void {
-        assert(base != 5);
+test "Register enc - different classes" {
+    try expect(Register.al.enc() == Register.ax.enc());
+    try expect(Register.ax.enc() == Register.eax.enc());
+    try expect(Register.eax.enc() == Register.rax.enc());
+    try expect(Register.ymm0.enc() == Register.rax.enc());
+    try expect(Register.xmm0.enc() == Register.ymm0.enc());
+    try expect(Register.es.enc() == Register.rax.enc());
+}
 
-        self.sib(scale, index, base);
-    }
+test "Register classes" {
+    try expect(Register.r11.class() == .general_purpose);
+    try expect(Register.ymm11.class() == .floating_point);
+    try expect(Register.fs.class() == .segment);
+}
 
-    /// Construct a SIB byte with scale * index + disp32
-    /// r/m effective address: [scale * index + disp32]
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) void {
-        assert(index != 4);
-
-        // scale is actually ignored
-        // index = 4 means no index
-        // base = 5 means no base, if mod == 0.
-        self.sib(scale, index, 5);
-    }
+pub const Memory = union(enum) {
+    sib: Sib,
+    rip: Rip,
+    moffs: Moffs,
 
-    /// Construct a SIB byte with just base
-    /// r/m effective address: [base]
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib_base(self: Self, base: u3) void {
-        assert(base != 5);
+    pub const ScaleIndex = packed struct {
+        scale: u4,
+        index: Register,
+    };
 
-        // scale is actually ignored
-        // index = 4 means no index
-        self.sib(0, 4, base);
-    }
+    pub const PtrSize = enum {
+        byte,
+        word,
+        dword,
+        qword,
+        tbyte,
+
+        pub fn fromSize(bit_size: u32) PtrSize {
+            return switch (bit_size) {
+                8 => .byte,
+                16 => .word,
+                32 => .dword,
+                64 => .qword,
+                80 => .tbyte,
+                else => unreachable,
+            };
+        }
 
-    /// Construct a SIB byte with just disp32
-    /// r/m effective address: [disp32]
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib_disp32(self: Self) void {
-        // scale is actually ignored
-        // index = 4 means no index
-        // base = 5 means no base, if mod == 0.
-        self.sib(0, 4, 5);
-    }
+        pub fn size(s: PtrSize) u32 {
+            return switch (s) {
+                .byte => 8,
+                .word => 16,
+                .dword => 32,
+                .qword => 64,
+                .tbyte => 80,
+            };
+        }
+    };
 
-    /// Construct a SIB byte with scale * index + base + disp8
-    /// r/m effective address: [base + scale * index + disp8]
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) void {
-        self.sib(scale, index, base);
-    }
+    pub const Sib = struct {
+        ptr_size: PtrSize,
+        base: ?Register,
+        scale_index: ?ScaleIndex,
+        disp: i32,
+    };
 
-    /// Construct a SIB byte with base + disp8, no index
-    /// r/m effective address: [base + disp8]
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib_baseDisp8(self: Self, base: u3) void {
-        // scale is ignored
-        // index = 4 means no index
-        self.sib(0, 4, base);
-    }
+    pub const Rip = struct {
+        ptr_size: PtrSize,
+        disp: i32,
+    };
 
-    /// Construct a SIB byte with scale * index + base + disp32
-    /// r/m effective address: [base + scale * index + disp32]
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) void {
-        self.sib(scale, index, base);
-    }
+    pub const Moffs = struct {
+        seg: Register,
+        offset: u64,
+    };
 
-    /// Construct a SIB byte with base + disp32, no index
-    /// r/m effective address: [base + disp32]
-    ///
-    /// Remember to add a REX prefix byte if index or base are extended!
-    pub fn sib_baseDisp32(self: Self, base: u3) void {
-        // scale is ignored
-        // index = 4 means no index
-        self.sib(0, 4, base);
+    pub fn moffs(reg: Register, offset: u64) Memory {
+        assert(reg.class() == .segment);
+        return .{ .moffs = .{ .seg = reg, .offset = offset } };
     }
 
-    // -------------------------
-    // Trivial (no bit fiddling)
-    // -------------------------
-
-    /// Encode an 8 bit immediate
-    ///
-    /// It is sign-extended to 64 bits by the cpu.
-    pub fn imm8(self: Self, imm: i8) void {
-        self.code.appendAssumeCapacity(@bitCast(u8, imm));
+    pub fn sib(ptr_size: PtrSize, args: struct {
+        disp: i32,
+        base: ?Register = null,
+        scale_index: ?ScaleIndex = null,
+    }) Memory {
+        return .{ .sib = .{
+            .base = args.base,
+            .disp = args.disp,
+            .ptr_size = ptr_size,
+            .scale_index = args.scale_index,
+        } };
     }
 
-    /// Encode an 8 bit displacement
-    ///
-    /// It is sign-extended to 64 bits by the cpu.
-    pub fn disp8(self: Self, disp: i8) void {
-        self.code.appendAssumeCapacity(@bitCast(u8, disp));
+    pub fn rip(ptr_size: PtrSize, disp: i32) Memory {
+        return .{ .rip = .{ .ptr_size = ptr_size, .disp = disp } };
     }
 
-    /// Encode an 16 bit immediate
-    ///
-    /// It is sign-extended to 64 bits by the cpu.
-    pub fn imm16(self: Self, imm: i16) void {
-        self.writeIntLittle(i16, imm);
+    pub fn isSegmentRegister(mem: Memory) bool {
+        return switch (mem) {
+            .moffs => true,
+            .rip => false,
+            .sib => |s| if (s.base) |r| r.class() == .segment else false,
+        };
     }
 
-    /// Encode an 32 bit immediate
-    ///
-    /// It is sign-extended to 64 bits by the cpu.
-    pub fn imm32(self: Self, imm: i32) void {
-        self.writeIntLittle(i32, imm);
+    pub fn base(mem: Memory) ?Register {
+        return switch (mem) {
+            .moffs => |m| m.seg,
+            .sib => |s| s.base,
+            .rip => null,
+        };
     }
 
-    /// Encode an 32 bit displacement
-    ///
-    /// It is sign-extended to 64 bits by the cpu.
-    pub fn disp32(self: Self, disp: i32) void {
-        self.writeIntLittle(i32, disp);
+    pub fn scaleIndex(mem: Memory) ?ScaleIndex {
+        return switch (mem) {
+            .moffs, .rip => null,
+            .sib => |s| s.scale_index,
+        };
     }
 
-    /// Encode an 64 bit immediate
-    ///
-    /// It is sign-extended to 64 bits by the cpu.
-    pub fn imm64(self: Self, imm: u64) void {
-        self.writeIntLittle(u64, imm);
+    pub fn size(mem: Memory) u32 {
+        return switch (mem) {
+            .rip => |r| r.ptr_size.size(),
+            .sib => |s| s.ptr_size.size(),
+            .moffs => unreachable,
+        };
     }
 };
-
-test "Encoder helpers - general purpose registers" {
-    var code = ArrayList(u8).init(testing.allocator);
-    defer code.deinit();
-
-    // simple integer multiplication
-
-    // imul eax,edi
-    // 0faf   c7
-    {
-        try code.resize(0);
-        const encoder = try Encoder.init(&code, 4);
-        encoder.rex(.{
-            .r = Register.eax.isExtended(),
-            .b = Register.edi.isExtended(),
-        });
-        encoder.opcode_2byte(0x0f, 0xaf);
-        encoder.modRm_direct(
-            Register.eax.lowEnc(),
-            Register.edi.lowEnc(),
-        );
-
-        try testing.expectEqualSlices(u8, &[_]u8{ 0x0f, 0xaf, 0xc7 }, code.items);
-    }
-
-    // simple mov
-
-    // mov eax,edi
-    // 89    f8
-    {
-        try code.resize(0);
-        const encoder = try Encoder.init(&code, 3);
-        encoder.rex(.{
-            .r = Register.edi.isExtended(),
-            .b = Register.eax.isExtended(),
-        });
-        encoder.opcode_1byte(0x89);
-        encoder.modRm_direct(
-            Register.edi.lowEnc(),
-            Register.eax.lowEnc(),
-        );
-
-        try testing.expectEqualSlices(u8, &[_]u8{ 0x89, 0xf8 }, code.items);
-    }
-
-    // signed integer addition of 32-bit sign extended immediate to 64 bit register
-
-    // add rcx, 2147483647
-    //
-    // Using the following opcode: REX.W + 81 /0 id, we expect the following encoding
-    //
-    // 48       :  REX.W set for 64 bit operand (*r*cx)
-    // 81       :  opcode for "<arithmetic> with immediate"
-    // c1       :  id = rcx,
-    //          :  c1 = 11  <-- mod = 11 indicates r/m is register (rcx)
-    //          :       000 <-- opcode_extension = 0 because opcode extension is /0. /0 specifies ADD
-    //          :       001 <-- 001 is rcx
-    // ffffff7f :  2147483647
-    {
-        try code.resize(0);
-        const encoder = try Encoder.init(&code, 7);
-        encoder.rex(.{ .w = true }); // use 64 bit operation
-        encoder.opcode_1byte(0x81);
-        encoder.modRm_direct(
-            0,
-            Register.rcx.lowEnc(),
-        );
-        encoder.imm32(2147483647);
-
-        try testing.expectEqualSlices(u8, &[_]u8{ 0x48, 0x81, 0xc1, 0xff, 0xff, 0xff, 0x7f }, code.items);
-    }
-}
-
-test "Encoder helpers - Vex prefix" {
-    var buf: [3]u8 = undefined;
-    var stream = std.io.fixedBufferStream(&buf);
-    const writer = stream.writer();
-
-    {
-        var vex_prefix = Encoder.Vex{};
-        vex_prefix.rex(.{
-            .r = true,
-        });
-        const nwritten = vex_prefix.write(writer);
-        try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x78 }, buf[0..nwritten]);
-    }
-
-    {
-        stream.reset();
-        var vex_prefix = Encoder.Vex{};
-        vex_prefix.reg(Register.xmm15.enc());
-        const nwritten = vex_prefix.write(writer);
-        try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x80 }, buf[0..nwritten]);
-    }
-
-    {
-        stream.reset();
-        var vex_prefix = Encoder.Vex{};
-        vex_prefix.rex(.{
-            .w = true,
-            .x = true,
-        });
-        const nwritten = vex_prefix.write(writer);
-        try testing.expectEqualSlices(u8, &[_]u8{ 0xc4, 0b101_0_0001, 0b0_1111_0_00 }, buf[0..nwritten]);
-    }
-
-    {
-        stream.reset();
-        var vex_prefix = Encoder.Vex{};
-        vex_prefix.rex(.{
-            .w = true,
-            .r = true,
-        });
-        vex_prefix.len_256();
-        vex_prefix.lead_opc_0f();
-        vex_prefix.simd_prefix_66();
-        const nwritten = vex_prefix.write(writer);
-        try testing.expectEqualSlices(u8, &[_]u8{ 0xc4, 0b011_0_0001, 0b0_1111_1_01 }, buf[0..nwritten]);
-    }
-
-    var code = ArrayList(u8).init(testing.allocator);
-    defer code.deinit();
-
-    {
-        // vmovapd xmm1, xmm2
-        const encoder = try Encoder.init(&code, 4);
-        var vex = Encoder.Vex{};
-        vex.simd_prefix_66();
-        encoder.vex(vex); // use 64 bit operation
-        encoder.opcode_1byte(0x28);
-        encoder.modRm_direct(0, Register.xmm1.lowEnc());
-        try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0xF9, 0x28, 0xC1 }, code.items);
-    }
-
-    {
-        try code.resize(0);
-
-        // vmovhpd xmm13, xmm1, qword ptr [rip]
-        const encoder = try Encoder.init(&code, 9);
-        var vex = Encoder.Vex{};
-        vex.len_128();
-        vex.simd_prefix_66();
-        vex.lead_opc_0f();
-        vex.rex(.{ .r = true });
-        vex.reg(Register.xmm1.enc());
-        encoder.vex(vex);
-        encoder.opcode_1byte(0x16);
-        encoder.modRm_RIPDisp32(Register.xmm13.lowEnc());
-        encoder.disp32(0);
-        try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0x71, 0x16, 0x2D, 0x00, 0x00, 0x00, 0x00 }, code.items);
-    }
-}
-
-// TODO add these registers to the enum and populate dwarfLocOp
-//    // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register.
-//    RA = (16, "RA"),
-//
-//    XMM0 = (17, "xmm0"),
-//    XMM1 = (18, "xmm1"),
-//    XMM2 = (19, "xmm2"),
-//    XMM3 = (20, "xmm3"),
-//    XMM4 = (21, "xmm4"),
-//    XMM5 = (22, "xmm5"),
-//    XMM6 = (23, "xmm6"),
-//    XMM7 = (24, "xmm7"),
-//
-//    XMM8 = (25, "xmm8"),
-//    XMM9 = (26, "xmm9"),
-//    XMM10 = (27, "xmm10"),
-//    XMM11 = (28, "xmm11"),
-//    XMM12 = (29, "xmm12"),
-//    XMM13 = (30, "xmm13"),
-//    XMM14 = (31, "xmm14"),
-//    XMM15 = (32, "xmm15"),
-//
-//    ST0 = (33, "st0"),
-//    ST1 = (34, "st1"),
-//    ST2 = (35, "st2"),
-//    ST3 = (36, "st3"),
-//    ST4 = (37, "st4"),
-//    ST5 = (38, "st5"),
-//    ST6 = (39, "st6"),
-//    ST7 = (40, "st7"),
-//
-//    MM0 = (41, "mm0"),
-//    MM1 = (42, "mm1"),
-//    MM2 = (43, "mm2"),
-//    MM3 = (44, "mm3"),
-//    MM4 = (45, "mm4"),
-//    MM5 = (46, "mm5"),
-//    MM6 = (47, "mm6"),
-//    MM7 = (48, "mm7"),
-//
-//    RFLAGS = (49, "rFLAGS"),
-//    ES = (50, "es"),
-//    CS = (51, "cs"),
-//    SS = (52, "ss"),
-//    DS = (53, "ds"),
-//    FS = (54, "fs"),
-//    GS = (55, "gs"),
-//
-//    FS_BASE = (58, "fs.base"),
-//    GS_BASE = (59, "gs.base"),
-//
-//    TR = (62, "tr"),
-//    LDTR = (63, "ldtr"),
-//    MXCSR = (64, "mxcsr"),
-//    FCW = (65, "fcw"),
-//    FSW = (66, "fsw"),
-//
-//    XMM16 = (67, "xmm16"),
-//    XMM17 = (68, "xmm17"),
-//    XMM18 = (69, "xmm18"),
-//    XMM19 = (70, "xmm19"),
-//    XMM20 = (71, "xmm20"),
-//    XMM21 = (72, "xmm21"),
-//    XMM22 = (73, "xmm22"),
-//    XMM23 = (74, "xmm23"),
-//    XMM24 = (75, "xmm24"),
-//    XMM25 = (76, "xmm25"),
-//    XMM26 = (77, "xmm26"),
-//    XMM27 = (78, "xmm27"),
-//    XMM28 = (79, "xmm28"),
-//    XMM29 = (80, "xmm29"),
-//    XMM30 = (81, "xmm30"),
-//    XMM31 = (82, "xmm31"),
-//
-//    K0 = (118, "k0"),
-//    K1 = (119, "k1"),
-//    K2 = (120, "k2"),
-//    K3 = (121, "k3"),
-//    K4 = (122, "k4"),
-//    K5 = (123, "k5"),
-//    K6 = (124, "k6"),
-//    K7 = (125, "k7"),
src/arch/x86_64/CodeGen.zig
@@ -303,7 +303,12 @@ pub fn generate(
     var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) {
         error.CodegenFail => return Result{ .fail = function.err_msg.? },
         error.OutOfRegisters => return Result{
-            .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}),
+            .fail = try ErrorMsg.create(
+                bin_file.allocator,
+                src_loc,
+                "CodeGen ran out of registers. This is a bug in the Zig compiler.",
+                .{},
+            ),
         },
         else => |e| return e,
     };
@@ -342,6 +347,20 @@ pub fn generate(
     defer emit.deinit();
     emit.lowerMir() catch |err| switch (err) {
         error.EmitFail => return Result{ .fail = emit.err_msg.? },
+        error.InvalidInstruction, error.CannotEncode => |e| {
+            const msg = switch (e) {
+                error.InvalidInstruction => "CodeGen failed to find a viable instruction.",
+                error.CannotEncode => "CodeGen failed to encode the instruction.",
+            };
+            return Result{
+                .fail = try ErrorMsg.create(
+                    bin_file.allocator,
+                    src_loc,
+                    "{s} This is a bug in the Zig compiler.",
+                    .{msg},
+                ),
+            };
+        },
         else => |e| return e,
     };
 
@@ -1687,7 +1706,7 @@ fn genIntMulDivOpMir(
                         else => unreachable,
                     },
                 }),
-                .data = .{ .imm = @bitCast(u32, -off) },
+                .data = .{ .disp = -off },
             });
         },
         else => unreachable,
@@ -2191,7 +2210,7 @@ fn genSliceElemPtr(self: *Self, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref) !MCValue {
                     .reg2 = .rbp,
                     .flags = 0b01,
                 }),
-                .data = .{ .imm = @bitCast(u32, -@intCast(i32, off)) },
+                .data = .{ .disp = -@intCast(i32, off) },
             });
         },
         else => return self.fail("TODO implement slice_elem_ptr when slice is {}", .{slice_mcv}),
@@ -2275,7 +2294,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void {
                     .reg1 = addr_reg.to64(),
                     .reg2 = .rbp,
                 }),
-                .data = .{ .imm = @bitCast(u32, -off) },
+                .data = .{ .disp = -off },
             });
         },
         .stack_offset => |off| {
@@ -2286,7 +2305,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void {
                     .reg1 = addr_reg.to64(),
                     .reg2 = .rbp,
                 }),
-                .data = .{ .imm = @bitCast(u32, -off) },
+                .data = .{ .disp = -off },
             });
         },
         .memory, .linker_load => {
@@ -2352,7 +2371,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
                     .reg2 = dst_mcv.register,
                     .flags = 0b01,
                 }),
-                .data = .{ .imm = 0 },
+                .data = .{ .disp = 0 },
             });
             break :result .{ .register = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)) };
         }
@@ -2615,7 +2634,7 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo
                             .reg2 = reg,
                             .flags = 0b01,
                         }),
-                        .data = .{ .imm = 0 },
+                        .data = .{ .disp = 0 },
                     });
                 },
                 .stack_offset => |off| {
@@ -2842,7 +2861,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
                     .reg2 = addr_reg.to64(),
                     .flags = 0b01,
                 }),
-                .data = .{ .imm = 0 },
+                .data = .{ .disp = 0 },
             });
 
             const new_ptr = MCValue{ .register = addr_reg.to64() };
@@ -2903,7 +2922,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
                                 .reg2 = tmp_reg,
                                 .flags = 0b01,
                             }),
-                            .data = .{ .imm = 0 },
+                            .data = .{ .disp = 0 },
                         });
                         return self.store(new_ptr, .{ .register = tmp_reg }, ptr_ty, value_ty);
                     }
@@ -3542,25 +3561,13 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu
                         if (intrinsicsAllowed(self.target.*, dst_ty)) {
                             const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) {
                                 .f32 => switch (mir_tag) {
-                                    .add => if (hasAvxSupport(self.target.*))
-                                        Mir.Inst.Tag.add_f32_avx
-                                    else
-                                        Mir.Inst.Tag.add_f32_sse,
-                                    .cmp => if (hasAvxSupport(self.target.*))
-                                        Mir.Inst.Tag.cmp_f32_avx
-                                    else
-                                        Mir.Inst.Tag.cmp_f32_sse,
+                                    .add => Mir.Inst.Tag.add_f32,
+                                    .cmp => Mir.Inst.Tag.cmp_f32,
                                     else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}),
                                 },
                                 .f64 => switch (mir_tag) {
-                                    .add => if (hasAvxSupport(self.target.*))
-                                        Mir.Inst.Tag.add_f64_avx
-                                    else
-                                        Mir.Inst.Tag.add_f64_sse,
-                                    .cmp => if (hasAvxSupport(self.target.*))
-                                        Mir.Inst.Tag.cmp_f64_avx
-                                    else
-                                        Mir.Inst.Tag.cmp_f64_sse,
+                                    .add => Mir.Inst.Tag.add_f64,
+                                    .cmp => Mir.Inst.Tag.cmp_f64,
                                     else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}),
                                 },
                                 else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}),
@@ -3618,7 +3625,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu
                             .reg2 = .rbp,
                             .flags = 0b01,
                         }),
-                        .data = .{ .imm = @bitCast(u32, -off) },
+                        .data = .{ .disp = -off },
                     });
                 },
             }
@@ -3644,7 +3651,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu
                             .reg2 = registerAlias(src_reg, abi_size),
                             .flags = 0b10,
                         }),
-                        .data = .{ .imm = @bitCast(u32, -off) },
+                        .data = .{ .disp = -off },
                     });
                 },
                 .immediate => |imm| {
@@ -3665,7 +3672,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu
                         else => unreachable,
                     };
                     const payload = try self.addExtra(Mir.ImmPair{
-                        .dest_off = @bitCast(u32, -off),
+                        .dest_off = -off,
                         .operand = @truncate(u32, imm),
                     });
                     _ = try self.addInst(.{
@@ -3756,7 +3763,7 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M
                             .reg2 = .rbp,
                             .flags = 0b01,
                         }),
-                        .data = .{ .imm = @bitCast(u32, -off) },
+                        .data = .{ .disp = -off },
                     });
                 },
                 .memory => {
@@ -5360,7 +5367,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE
                     // offset from rbp, which is at the top of the stack frame.
                     // mov [rbp+offset], immediate
                     const payload = try self.addExtra(Mir.ImmPair{
-                        .dest_off = @bitCast(u32, -stack_offset),
+                        .dest_off = -stack_offset,
                         .operand = @truncate(u32, imm),
                     });
                     _ = try self.addInst(.{
@@ -5400,14 +5407,8 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE
                 .Float => {
                     if (intrinsicsAllowed(self.target.*, ty)) {
                         const tag: Mir.Inst.Tag = switch (ty.tag()) {
-                            .f32 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f32_avx
-                            else
-                                Mir.Inst.Tag.mov_f32_sse,
-                            .f64 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f64_avx
-                            else
-                                Mir.Inst.Tag.mov_f64_sse,
+                            .f32 => Mir.Inst.Tag.mov_f32,
+                            .f64 => Mir.Inst.Tag.mov_f64,
                             else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}),
                         };
                         _ = try self.addInst(.{
@@ -5421,7 +5422,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE
                                 .reg2 = reg.to128(),
                                 .flags = 0b01,
                             }),
-                            .data = .{ .imm = @bitCast(u32, -stack_offset) },
+                            .data = .{ .disp = -stack_offset },
                         });
                         return;
                     }
@@ -5436,7 +5437,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE
                             .reg2 = registerAlias(reg, @intCast(u32, abi_size)),
                             .flags = 0b10,
                         }),
-                        .data = .{ .imm = @bitCast(u32, -stack_offset) },
+                        .data = .{ .disp = -stack_offset },
                     });
                 },
             }
@@ -5516,7 +5517,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl
                 0 => {
                     assert(ty.isError());
                     const payload = try self.addExtra(Mir.ImmPair{
-                        .dest_off = @bitCast(u32, -stack_offset),
+                        .dest_off = -stack_offset,
                         .operand = @truncate(u32, x_big),
                     });
                     _ = try self.addInst(.{
@@ -5530,7 +5531,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl
                 },
                 1, 2, 4 => {
                     const payload = try self.addExtra(Mir.ImmPair{
-                        .dest_off = @bitCast(u32, -stack_offset),
+                        .dest_off = -stack_offset,
                         .operand = @truncate(u32, x_big),
                     });
                     _ = try self.addInst(.{
@@ -5552,7 +5553,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl
                     // insted just use two 32 bit writes to avoid register allocation
                     {
                         const payload = try self.addExtra(Mir.ImmPair{
-                            .dest_off = @bitCast(u32, -stack_offset + 4),
+                            .dest_off = -stack_offset + 4,
                             .operand = @truncate(u32, x_big >> 32),
                         });
                         _ = try self.addInst(.{
@@ -5566,7 +5567,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl
                     }
                     {
                         const payload = try self.addExtra(Mir.ImmPair{
-                            .dest_off = @bitCast(u32, -stack_offset),
+                            .dest_off = -stack_offset,
                             .operand = @truncate(u32, x_big),
                         });
                         _ = try self.addInst(.{
@@ -5595,14 +5596,8 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl
                 .Float => {
                     if (intrinsicsAllowed(self.target.*, ty)) {
                         const tag: Mir.Inst.Tag = switch (ty.tag()) {
-                            .f32 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f32_avx
-                            else
-                                Mir.Inst.Tag.mov_f32_sse,
-                            .f64 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f64_avx
-                            else
-                                Mir.Inst.Tag.mov_f64_sse,
+                            .f32 => Mir.Inst.Tag.mov_f32,
+                            .f64 => Mir.Inst.Tag.mov_f64,
                             else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}),
                         };
                         _ = try self.addInst(.{
@@ -5616,7 +5611,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl
                                 .reg2 = reg.to128(),
                                 .flags = 0b01,
                             }),
-                            .data = .{ .imm = @bitCast(u32, -stack_offset) },
+                            .data = .{ .disp = -stack_offset },
                         });
                         return;
                     }
@@ -5691,7 +5686,7 @@ fn genInlineMemcpyRegisterRegister(
                     .reg2 = registerAlias(tmp_reg, nearest_power_of_two),
                     .flags = 0b10,
                 }),
-                .data = .{ .imm = @bitCast(u32, -next_offset) },
+                .data = .{ .disp = -next_offset },
             });
 
             if (nearest_power_of_two > 1) {
@@ -5711,7 +5706,7 @@ fn genInlineMemcpyRegisterRegister(
                 .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)),
                 .flags = 0b10,
             }),
-            .data = .{ .imm = @bitCast(u32, -offset) },
+            .data = .{ .disp = -offset },
         });
     }
 }
@@ -5758,7 +5753,7 @@ fn genInlineMemcpy(
                     .reg1 = dst_addr_reg.to64(),
                     .reg2 = opts.dest_stack_base orelse .rbp,
                 }),
-                .data = .{ .imm = @bitCast(u32, -off) },
+                .data = .{ .disp = -off },
             });
         },
         .register => |reg| {
@@ -5787,7 +5782,7 @@ fn genInlineMemcpy(
                     .reg1 = src_addr_reg.to64(),
                     .reg2 = opts.source_stack_base orelse .rbp,
                 }),
-                .data = .{ .imm = @bitCast(u32, -off) },
+                .data = .{ .disp = -off },
             });
         },
         .register => |reg| {
@@ -5911,7 +5906,7 @@ fn genInlineMemset(
                     .reg1 = addr_reg.to64(),
                     .reg2 = opts.dest_stack_base orelse .rbp,
                 }),
-                .data = .{ .imm = @bitCast(u32, -off) },
+                .data = .{ .disp = -off },
             });
         },
         .register => |reg| {
@@ -5998,7 +5993,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                     .reg1 = registerAlias(reg, abi_size),
                     .reg2 = .rbp,
                 }),
-                .data = .{ .imm = @bitCast(u32, -off) },
+                .data = .{ .disp = -off },
             });
         },
         .unreach, .none => return, // Nothing to do.
@@ -6097,14 +6092,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                 .Float => {
                     if (intrinsicsAllowed(self.target.*, ty)) {
                         const tag: Mir.Inst.Tag = switch (ty.tag()) {
-                            .f32 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f32_avx
-                            else
-                                Mir.Inst.Tag.mov_f32_sse,
-                            .f64 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f64_avx
-                            else
-                                Mir.Inst.Tag.mov_f64_sse,
+                            .f32 => Mir.Inst.Tag.mov_f32,
+                            .f64 => Mir.Inst.Tag.mov_f64,
                             else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}),
                         };
                         _ = try self.addInst(.{
@@ -6141,14 +6130,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
 
                     if (intrinsicsAllowed(self.target.*, ty)) {
                         const tag: Mir.Inst.Tag = switch (ty.tag()) {
-                            .f32 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f32_avx
-                            else
-                                Mir.Inst.Tag.mov_f32_sse,
-                            .f64 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f64_avx
-                            else
-                                Mir.Inst.Tag.mov_f64_sse,
+                            .f32 => Mir.Inst.Tag.mov_f32,
+                            .f64 => Mir.Inst.Tag.mov_f64,
                             else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}),
                         };
 
@@ -6162,7 +6145,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                                     else => unreachable,
                                 },
                             }),
-                            .data = .{ .imm = 0 },
+                            .data = .{ .disp = 0 },
                         });
                         return;
                     }
@@ -6178,7 +6161,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                             .reg2 = reg.to64(),
                             .flags = 0b01,
                         }),
-                        .data = .{ .imm = 0 },
+                        .data = .{ .disp = 0 },
                     });
                 },
             }
@@ -6190,14 +6173,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
 
                 if (intrinsicsAllowed(self.target.*, ty)) {
                     const tag: Mir.Inst.Tag = switch (ty.tag()) {
-                        .f32 => if (hasAvxSupport(self.target.*))
-                            Mir.Inst.Tag.mov_f32_avx
-                        else
-                            Mir.Inst.Tag.mov_f32_sse,
-                        .f64 => if (hasAvxSupport(self.target.*))
-                            Mir.Inst.Tag.mov_f64_avx
-                        else
-                            Mir.Inst.Tag.mov_f64_sse,
+                        .f32 => Mir.Inst.Tag.mov_f32,
+                        .f64 => Mir.Inst.Tag.mov_f64,
                         else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}),
                     };
 
@@ -6211,7 +6188,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                                 else => unreachable,
                             },
                         }),
-                        .data = .{ .imm = 0 },
+                        .data = .{ .disp = 0 },
                     });
                     return;
                 }
@@ -6255,7 +6232,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                                 .reg2 = reg.to64(),
                                 .flags = 0b01,
                             }),
-                            .data = .{ .imm = 0 },
+                            .data = .{ .disp = 0 },
                         });
                     }
                 }
@@ -6283,7 +6260,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                                     .reg2 = .rbp,
                                     .flags = flags,
                                 }),
-                                .data = .{ .imm = @bitCast(u32, -off) },
+                                .data = .{ .disp = -off },
                             });
                             return;
                         }
@@ -6302,7 +6279,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                                     .reg2 = .rbp,
                                     .flags = flags,
                                 }),
-                                .data = .{ .imm = @bitCast(u32, -off) },
+                                .data = .{ .disp = -off },
                             });
                             return;
                         }
@@ -6311,14 +6288,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                 .Float => {
                     if (intrinsicsAllowed(self.target.*, ty)) {
                         const tag: Mir.Inst.Tag = switch (ty.tag()) {
-                            .f32 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f32_avx
-                            else
-                                Mir.Inst.Tag.mov_f32_sse,
-                            .f64 => if (hasAvxSupport(self.target.*))
-                                Mir.Inst.Tag.mov_f64_avx
-                            else
-                                Mir.Inst.Tag.mov_f64_sse,
+                            .f32 => Mir.Inst.Tag.mov_f32,
+                            .f64 => Mir.Inst.Tag.mov_f64,
                             else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}),
                         };
                         _ = try self.addInst(.{
@@ -6331,7 +6302,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                                     else => unreachable,
                                 },
                             }),
-                            .data = .{ .imm = @bitCast(u32, -off) },
+                            .data = .{ .disp = -off },
                         });
                         return;
                     }
@@ -6347,7 +6318,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                     .reg2 = .rbp,
                     .flags = 0b01,
                 }),
-                .data = .{ .imm = @bitCast(u32, -off) },
+                .data = .{ .disp = -off },
             });
         },
     }
@@ -6436,7 +6407,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void {
                 else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}),
             },
         }),
-        .data = .{ .imm = @bitCast(u32, -stack_offset) },
+        .data = .{ .disp = -stack_offset },
     });
 
     // convert
@@ -6452,7 +6423,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void {
                 else => |size| return self.fail("TODO convert float with abiSize={}", .{size}),
             },
         }),
-        .data = .{ .imm = @bitCast(u32, -stack_dst.stack_offset) },
+        .data = .{ .disp = -stack_dst.stack_offset },
     });
 
     return self.finishAir(inst, stack_dst, .{ ty_op.operand, .none, .none });
@@ -6551,7 +6522,7 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
                         .reg2 = reg,
                         .flags = 0b01,
                     }),
-                    .data = .{ .imm = 0 },
+                    .data = .{ .disp = 0 },
                 });
                 break :blk MCValue{ .register = reg };
             },
src/arch/x86_64/Emit.zig
@@ -1,3 +1,4 @@
+//!
 //! This file contains the functionality for lowering x86_64 MIR into
 //! machine code
 
@@ -7,6 +8,7 @@ const std = @import("std");
 const assert = std.debug.assert;
 const bits = @import("bits.zig");
 const abi = @import("abi.zig");
+const encoder = @import("encoder.zig");
 const link = @import("../../link.zig");
 const log = std.log.scoped(.codegen);
 const math = std.math;
@@ -19,12 +21,13 @@ const CodeGen = @import("CodeGen.zig");
 const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
 const Encoder = bits.Encoder;
 const ErrorMsg = Module.ErrorMsg;
+const Instruction = encoder.Instruction;
 const MCValue = @import("CodeGen.zig").MCValue;
+const Memory = bits.Memory;
 const Mir = @import("Mir.zig");
 const Module = @import("../../Module.zig");
-const Instruction = bits.Instruction;
-const Type = @import("../../type.zig").Type;
 const Register = bits.Register;
+const Type = @import("../../type.zig").Type;
 
 mir: Mir,
 bin_file: *link.File,
@@ -45,6 +48,8 @@ relocs: std.ArrayListUnmanaged(Reloc) = .{},
 const InnerError = error{
     OutOfMemory,
     EmitFail,
+    InvalidInstruction,
+    CannotEncode,
 };
 
 const Reloc = struct {
@@ -153,8 +158,8 @@ pub fn lowerMir(emit: *Emit) InnerError!void {
             .push => try emit.mirPushPop(.push, inst),
             .pop => try emit.mirPushPop(.pop, inst),
 
-            .jmp => try emit.mirJmpCall(.jmp_near, inst),
-            .call => try emit.mirJmpCall(.call_near, inst),
+            .jmp => try emit.mirJmpCall(.jmp, inst),
+            .call => try emit.mirJmpCall(.call, inst),
 
             .cond_jmp => try emit.mirCondJmp(inst),
             .cond_set_byte => try emit.mirCondSetByte(inst),
@@ -170,25 +175,15 @@ pub fn lowerMir(emit: *Emit) InnerError!void {
             .interrupt => try emit.mirInterrupt(inst),
             .nop => {}, // just skip it
 
-            // SSE instructions
-            .mov_f64_sse => try emit.mirMovFloatSse(.movsd, inst),
-            .mov_f32_sse => try emit.mirMovFloatSse(.movss, inst),
-
-            .add_f64_sse => try emit.mirAddFloatSse(.addsd, inst),
-            .add_f32_sse => try emit.mirAddFloatSse(.addss, inst),
-
-            .cmp_f64_sse => try emit.mirCmpFloatSse(.ucomisd, inst),
-            .cmp_f32_sse => try emit.mirCmpFloatSse(.ucomiss, inst),
+            // SSE/AVX instructions
+            .mov_f64 => try emit.mirMovFloat(.movsd, inst),
+            .mov_f32 => try emit.mirMovFloat(.movss, inst),
 
-            // AVX instructions
-            .mov_f64_avx => try emit.mirMovFloatAvx(.vmovsd, inst),
-            .mov_f32_avx => try emit.mirMovFloatAvx(.vmovss, inst),
+            .add_f64 => try emit.mirAddFloat(.addsd, inst),
+            .add_f32 => try emit.mirAddFloat(.addss, inst),
 
-            .add_f64_avx => try emit.mirAddFloatAvx(.vaddsd, inst),
-            .add_f32_avx => try emit.mirAddFloatAvx(.vaddss, inst),
-
-            .cmp_f64_avx => try emit.mirCmpFloatAvx(.vucomisd, inst),
-            .cmp_f32_avx => try emit.mirCmpFloatAvx(.vucomiss, inst),
+            .cmp_f64 => try emit.mirCmpFloat(.ucomisd, inst),
+            .cmp_f32 => try emit.mirCmpFloat(.ucomiss, inst),
 
             // Pseudo-instructions
             .call_extern => try emit.mirCallExtern(inst),
@@ -235,8 +230,23 @@ fn fixupRelocs(emit: *Emit) InnerError!void {
     }
 }
 
+fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct {
+    op1: Instruction.Operand = .none,
+    op2: Instruction.Operand = .none,
+    op3: Instruction.Operand = .none,
+    op4: Instruction.Operand = .none,
+}) InnerError!void {
+    const inst = try Instruction.new(mnemonic, .{
+        .op1 = ops.op1,
+        .op2 = ops.op2,
+        .op3 = ops.op3,
+        .op4 = ops.op4,
+    });
+    return inst.encode(emit.code.writer());
+}
+
 fn mirUndefinedInstruction(emit: *Emit) InnerError!void {
-    return lowerToZoEnc(.ud2, emit.code);
+    return emit.encode(.ud2, .{});
 }
 
 fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
@@ -244,45 +254,43 @@ fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     assert(tag == .interrupt);
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
-        0b00 => return lowerToZoEnc(.int3, emit.code),
+        0b00 => return emit.encode(.int3, .{}),
         else => return emit.fail("TODO handle variant 0b{b} of interrupt instruction", .{ops.flags}),
     }
 }
 
 fn mirSyscall(emit: *Emit) InnerError!void {
-    return lowerToZoEnc(.syscall, emit.code);
+    return emit.encode(.syscall, .{});
 }
 
-fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirPushPop(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
-            // PUSH/POP reg
-            return lowerToOEnc(tag, ops.reg1, emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+            });
         },
         0b01 => {
-            // PUSH/POP r/m64
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) {
-                16 => .word_ptr,
-                else => .qword_ptr,
-            };
-            return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{
-                .disp = imm,
-                .base = ops.reg1,
-            }), emit.code);
+            const disp = emit.mir.instructions.items(.data)[inst].disp;
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .mem = Memory.sib(.qword, .{
+                    .base = ops.reg1,
+                    .disp = disp,
+                }) },
+            });
         },
         0b10 => {
-            // PUSH imm32
-            assert(tag == .push);
             const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToIEnc(.push, imm, emit.code);
+            return emit.encode(.push, .{
+                .op1 = .{ .imm = imm },
+            });
         },
         0b11 => unreachable,
     }
 }
 
-fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirPushPopRegisterList(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     const payload = emit.mir.instructions.items(.data)[inst].payload;
     const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data;
@@ -291,15 +299,20 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerErro
     const callee_preserved_regs = abi.getCalleePreservedRegs(emit.target.*);
     for (callee_preserved_regs) |reg| {
         if (reg_list.isSet(callee_preserved_regs, reg)) {
-            switch (tag) {
-                .push => try lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{
-                    .disp = @bitCast(u32, disp),
-                    .base = ops.reg1,
-                }), reg, emit.code),
-                .pop => try lowerToRmEnc(.mov, reg, RegisterOrMemory.mem(.qword_ptr, .{
-                    .disp = @bitCast(u32, disp),
-                    .base = ops.reg1,
-                }), emit.code),
+            const op1: Instruction.Operand = .{ .mem = Memory.sib(.qword, .{
+                .base = ops.reg1,
+                .disp = disp,
+            }) };
+            const op2: Instruction.Operand = .{ .reg = reg };
+            switch (mnemonic) {
+                .push => try emit.encode(.mov, .{
+                    .op1 = op1,
+                    .op2 = op2,
+                }),
+                .pop => try emit.encode(.mov, .{
+                    .op1 = op2,
+                    .op2 = op1,
+                }),
                 else => unreachable,
             }
             disp += 8;
@@ -307,13 +320,17 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerErro
     }
 }
 
-fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
             const target = emit.mir.instructions.items(.data)[inst].inst;
             const source = emit.code.items.len;
-            try lowerToDEnc(tag, 0, emit.code);
+            try emit.encode(mnemonic, .{
+                .op1 = .{
+                    .imm = 0,
+                },
+            });
             try emit.relocs.append(emit.bin_file.allocator, .{
                 .source = source,
                 .target = target,
@@ -323,34 +340,33 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
         },
         0b01 => {
             if (ops.reg1 == .none) {
-                // JMP/CALL [imm]
                 const imm = emit.mir.instructions.items(.data)[inst].imm;
-                const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) {
-                    16 => .word_ptr,
-                    else => .qword_ptr,
-                };
-                return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ .disp = imm }), emit.code);
+                return emit.encode(mnemonic, .{
+                    .op1 = .{ .imm = imm },
+                });
             }
-            // JMP/CALL reg
-            return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+            });
         },
         0b10 => {
-            // JMP/CALL r/m64
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToMEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
-                .disp = imm,
-                .base = ops.reg1,
-            }), emit.code);
+            const disp = emit.mir.instructions.items(.data)[inst].disp;
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .mem = Memory.sib(.qword, .{
+                    .base = ops.reg1,
+                    .disp = disp,
+                }) },
+            });
         },
         0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}),
     }
 }
 
 fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
-    const mir_tag = emit.mir.instructions.items(.tag)[inst];
-    assert(mir_tag == .cond_jmp);
+    const tag = emit.mir.instructions.items(.tag)[inst];
+    assert(tag == .cond_jmp);
     const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc;
-    const tag: Tag = switch (inst_cc.cc) {
+    const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) {
         .a => .ja,
         .ae => .jae,
         .b => .jb,
@@ -383,7 +399,9 @@ fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
         .z => .jz,
     };
     const source = emit.code.items.len;
-    try lowerToDEnc(tag, 0, emit.code);
+    try emit.encode(mnemonic, .{
+        .op1 = .{ .imm = 0 },
+    });
     try emit.relocs.append(emit.bin_file.allocator, .{
         .source = source,
         .target = inst_cc.inst,
@@ -393,11 +411,11 @@ fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
 }
 
 fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
-    const mir_tag = emit.mir.instructions.items(.tag)[inst];
-    assert(mir_tag == .cond_set_byte);
+    const tag = emit.mir.instructions.items(.tag)[inst];
+    assert(tag == .cond_set_byte);
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     const cc = emit.mir.instructions.items(.data)[inst].cc;
-    const tag: Tag = switch (cc) {
+    const mnemonic: Instruction.Mnemonic = switch (cc) {
         .a => .seta,
         .ae => .setae,
         .b => .setb,
@@ -429,15 +447,15 @@ fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
         .s => .sets,
         .z => .setz,
     };
-    return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1.to8()), emit.code);
+    return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } });
 }
 
 fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
-    const mir_tag = emit.mir.instructions.items(.tag)[inst];
-    assert(mir_tag == .cond_mov);
+    const tag = emit.mir.instructions.items(.tag)[inst];
+    assert(tag == .cond_mov);
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     const cc = emit.mir.instructions.items(.data)[inst].cc;
-    const tag: Tag = switch (cc) {
+    const mnemonic: Instruction.Mnemonic = switch (cc) {
         .a => .cmova,
         .ae => .cmovae,
         .b => .cmovb,
@@ -469,21 +487,28 @@ fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
         .s => .cmovs,
         .z => .cmovz,
     };
+    const op1: Instruction.Operand = .{ .reg = ops.reg1 };
 
     if (ops.flags == 0b00) {
-        return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
+        return emit.encode(mnemonic, .{
+            .op1 = op1,
+            .op2 = .{ .reg = ops.reg2 },
+        });
     }
-    const imm = emit.mir.instructions.items(.data)[inst].imm;
+    const disp = emit.mir.instructions.items(.data)[inst].disp;
     const ptr_size: Memory.PtrSize = switch (ops.flags) {
         0b00 => unreachable,
-        0b01 => .word_ptr,
-        0b10 => .dword_ptr,
-        0b11 => .qword_ptr,
+        0b01 => .word,
+        0b10 => .dword,
+        0b11 => .qword,
     };
-    return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ptr_size, .{
-        .disp = imm,
-        .base = ops.reg2,
-    }), emit.code);
+    return emit.encode(mnemonic, .{
+        .op1 = op1,
+        .op2 = .{ .mem = Memory.sib(ptr_size, .{
+            .base = ops.reg2,
+            .disp = disp,
+        }) },
+    });
 }
 
 fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
@@ -493,18 +518,16 @@ fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     switch (ops.flags) {
         0b00 => {
             if (ops.reg2 == .none) {
-                // TEST r/m64, imm32
-                // MI
                 const imm = emit.mir.instructions.items(.data)[inst].imm;
-                if (ops.reg1.to64() == .rax) {
-                    // TEST rax, imm32
-                    // I
-                    return lowerToIEnc(.@"test", imm, emit.code);
-                }
-                return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, emit.code);
+                return emit.encode(.@"test", .{
+                    .op1 = .{ .reg = ops.reg1 },
+                    .op2 = .{ .imm = imm },
+                });
             }
-            // TEST r/m64, r64
-            return lowerToMrEnc(.@"test", RegisterOrMemory.reg(ops.reg1), ops.reg2, emit.code);
+            return emit.encode(.@"test", .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
         else => return emit.fail("TODO more TEST alternatives", .{}),
     }
@@ -515,62 +538,59 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     assert(tag == .ret);
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
-        0b00 => {
-            // RETF imm16
-            // I
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToIEnc(.ret_far, imm, emit.code);
-        },
-        0b01 => {
-            return lowerToZoEnc(.ret_far, emit.code);
-        },
+        0b00 => unreachable,
+        0b01 => unreachable,
         0b10 => {
-            // RET imm16
-            // I
             const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToIEnc(.ret_near, imm, emit.code);
+            return emit.encode(.ret, .{
+                .op1 = .{ .imm = imm },
+            });
         },
         0b11 => {
-            return lowerToZoEnc(.ret_near, emit.code);
+            return emit.encode(.ret, .{});
         },
     }
 }
 
-fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
             if (ops.reg2 == .none) {
-                // mov reg1, imm32
-                // MI
                 const imm = emit.mir.instructions.items(.data)[inst].imm;
-                return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code);
+                return emit.encode(mnemonic, .{
+                    .op1 = .{ .reg = ops.reg1 },
+                    .op2 = .{ .imm = imm },
+                });
             }
-            // mov reg1, reg2
-            // RM
-            return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
         0b01 => {
-            // mov reg1, [reg2 + imm32]
-            // RM
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null;
-            return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
-                .disp = imm,
-                .base = src_reg,
-            }), emit.code);
+            const disp = emit.mir.instructions.items(.data)[inst].disp;
+            const base: ?Register = if (ops.reg2 != .none) ops.reg2 else null;
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{
+                    .base = base,
+                    .disp = disp,
+                }) },
+            });
         },
         0b10 => {
             if (ops.reg2 == .none) {
                 return emit.fail("TODO unused variant: mov reg1, none, 0b10", .{});
             }
-            // mov [reg1 + imm32], reg2
-            // MR
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{
-                .disp = imm,
-                .base = ops.reg1,
-            }), ops.reg2, emit.code);
+            const disp = emit.mir.instructions.items(.data)[inst].disp;
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{
+                    .base = ops.reg1,
+                    .disp = disp,
+                }) },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
         0b11 => {
             return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{});
@@ -578,169 +598,165 @@ fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
     }
 }
 
-fn mirArithMemImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     assert(ops.reg2 == .none);
     const payload = emit.mir.instructions.items(.data)[inst].payload;
     const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data;
     const ptr_size: Memory.PtrSize = switch (ops.flags) {
-        0b00 => .byte_ptr,
-        0b01 => .word_ptr,
-        0b10 => .dword_ptr,
-        0b11 => .qword_ptr,
-    };
-    return lowerToMiEnc(tag, RegisterOrMemory.mem(ptr_size, .{
-        .disp = imm_pair.dest_off,
-        .base = ops.reg1,
-    }), imm_pair.operand, emit.code);
-}
-
-inline fn setRexWRegister(reg: Register) bool {
-    if (reg.size() > 64) return false;
-    if (reg.size() == 64) return true;
-    return switch (reg) {
-        .ah, .ch, .dh, .bh => true,
-        else => false,
+        0b00 => .byte,
+        0b01 => .word,
+        0b10 => .dword,
+        0b11 => .qword,
     };
+    return emit.encode(mnemonic, .{
+        .op1 = .{ .mem = Memory.sib(ptr_size, .{
+            .disp = imm_pair.dest_off,
+            .base = ops.reg1,
+        }) },
+        .op2 = .{ .imm = imm_pair.operand },
+    });
 }
 
-inline fn immOpSize(u_imm: u32) u6 {
-    const imm = @bitCast(i32, u_imm);
-    if (math.minInt(i8) <= imm and imm <= math.maxInt(i8)) {
-        return 8;
-    }
-    if (math.minInt(i16) <= imm and imm <= math.maxInt(i16)) {
-        return 16;
-    }
-    return 32;
-}
-
-fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     const scale = ops.flags;
     const payload = emit.mir.instructions.items(.data)[inst].payload;
     const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode();
-    // OP reg1, [reg2 + scale*index + imm32]
-    const scale_index = ScaleIndex{
+    const scale_index = Memory.ScaleIndex{
         .scale = scale,
         .index = index_reg_disp.index,
     };
-    return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
-        .disp = index_reg_disp.disp,
-        .base = ops.reg2,
-        .scale_index = scale_index,
-    }), emit.code);
+    return emit.encode(mnemonic, .{
+        .op1 = .{ .reg = ops.reg1 },
+        .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{
+            .base = ops.reg2,
+            .scale_index = scale_index,
+            .disp = index_reg_disp.disp,
+        }) },
+    });
 }
 
-fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     const scale = ops.flags;
     const payload = emit.mir.instructions.items(.data)[inst].payload;
     const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode();
-    const scale_index = ScaleIndex{
+    const scale_index = Memory.ScaleIndex{
         .scale = scale,
         .index = index_reg_disp.index,
     };
     assert(ops.reg2 != .none);
-    // OP [reg1 + scale*index + imm32], reg2
-    return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{
-        .disp = index_reg_disp.disp,
-        .base = ops.reg1,
-        .scale_index = scale_index,
-    }), ops.reg2, emit.code);
+    return emit.encode(mnemonic, .{
+        .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{
+            .base = ops.reg1,
+            .scale_index = scale_index,
+            .disp = index_reg_disp.disp,
+        }) },
+        .op2 = .{ .reg = ops.reg2 },
+    });
 }
 
-fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     const scale = ops.flags;
     const payload = emit.mir.instructions.items(.data)[inst].payload;
     const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode();
-    const scale_index = ScaleIndex{
+    const scale_index = Memory.ScaleIndex{
         .scale = scale,
         .index = index_reg_disp_imm.index,
     };
-    // OP qword ptr [reg1 + scale*index + imm32], imm32
-    return lowerToMiEnc(tag, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = index_reg_disp_imm.disp,
-        .base = ops.reg1,
-        .scale_index = scale_index,
-    }), index_reg_disp_imm.imm, emit.code);
+    return emit.encode(mnemonic, .{
+        .op1 = .{ .mem = Memory.sib(.qword, .{
+            .base = ops.reg1,
+            .disp = index_reg_disp_imm.disp,
+            .scale_index = scale_index,
+        }) },
+        .op2 = .{ .imm = index_reg_disp_imm.imm },
+    });
 }
 
-fn mirArithMemIndexImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     assert(ops.reg2 == .none);
     const payload = emit.mir.instructions.items(.data)[inst].payload;
     const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode();
     const ptr_size: Memory.PtrSize = switch (ops.flags) {
-        0b00 => .byte_ptr,
-        0b01 => .word_ptr,
-        0b10 => .dword_ptr,
-        0b11 => .qword_ptr,
+        0b00 => .byte,
+        0b01 => .word,
+        0b10 => .dword,
+        0b11 => .qword,
     };
-    const scale_index = ScaleIndex{
+    const scale_index = Memory.ScaleIndex{
         .scale = 0,
         .index = index_reg_disp_imm.index,
     };
-    // OP ptr [reg1 + index + imm32], imm32
-    return lowerToMiEnc(tag, RegisterOrMemory.mem(ptr_size, .{
-        .disp = index_reg_disp_imm.disp,
-        .base = ops.reg1,
-        .scale_index = scale_index,
-    }), index_reg_disp_imm.imm, emit.code);
+    return emit.encode(mnemonic, .{
+        .op1 = .{ .mem = Memory.sib(ptr_size, .{
+            .disp = index_reg_disp_imm.disp,
+            .base = ops.reg1,
+            .scale_index = scale_index,
+        }) },
+        .op2 = .{ .imm = index_reg_disp_imm.imm },
+    });
 }
 
 fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
-    const mir_tag = emit.mir.instructions.items(.tag)[inst];
-    assert(mir_tag == .mov_sign_extend);
+    const tag = emit.mir.instructions.items(.tag)[inst];
+    assert(tag == .mov_sign_extend);
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
-    const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined;
+    const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined;
     switch (ops.flags) {
         0b00 => {
-            const tag: Tag = if (ops.reg2.size() == 32) .movsxd else .movsx;
-            return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
-        },
-        0b01 => {
-            return lowerToRmEnc(.movsx, ops.reg1, RegisterOrMemory.mem(.byte_ptr, .{
-                .disp = imm,
-                .base = ops.reg2,
-            }), emit.code);
-        },
-        0b10 => {
-            return lowerToRmEnc(.movsx, ops.reg1, RegisterOrMemory.mem(.word_ptr, .{
-                .disp = imm,
-                .base = ops.reg2,
-            }), emit.code);
+            const mnemonic: Instruction.Mnemonic = if (ops.reg2.size() == 32) .movsxd else .movsx;
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
-        0b11 => {
-            return lowerToRmEnc(.movsxd, ops.reg1, RegisterOrMemory.mem(.dword_ptr, .{
-                .disp = imm,
-                .base = ops.reg2,
-            }), emit.code);
+        else => {
+            const ptr_size: Memory.PtrSize = switch (ops.flags) {
+                0b01 => .byte,
+                0b10 => .word,
+                0b11 => .qword,
+                else => unreachable,
+            };
+            return emit.encode(.movsx, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.sib(ptr_size, .{
+                    .disp = disp,
+                    .base = ops.reg2,
+                }) },
+            });
         },
     }
 }
 
 fn mirMovZeroExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
-    const mir_tag = emit.mir.instructions.items(.tag)[inst];
-    assert(mir_tag == .mov_zero_extend);
+    const tag = emit.mir.instructions.items(.tag)[inst];
+    assert(tag == .mov_zero_extend);
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
-    const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined;
+    const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined;
     switch (ops.flags) {
         0b00 => {
-            return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
-        },
-        0b01 => {
-            return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.mem(.byte_ptr, .{
-                .disp = imm,
-                .base = ops.reg2,
-            }), emit.code);
+            return emit.encode(.movzx, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
-        0b10 => {
-            return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.mem(.word_ptr, .{
-                .disp = imm,
-                .base = ops.reg2,
-            }), emit.code);
+        0b01, 0b10 => {
+            const ptr_size: Memory.PtrSize = switch (ops.flags) {
+                0b01 => .byte,
+                0b10 => .word,
+                else => unreachable,
+            };
+            return emit.encode(.movzx, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.sib(ptr_size, .{
+                    .disp = disp,
+                    .base = ops.reg2,
+                }) },
+            });
         },
         0b11 => {
             return emit.fail("TODO unused variant: movzx 0b11", .{});
@@ -759,9 +775,10 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
                 const imm = emit.mir.extraData(Mir.Imm64, payload).data;
                 break :blk imm.decode();
             } else emit.mir.instructions.items(.data)[inst].imm;
-            // movabs reg, imm64
-            // OI
-            return lowerToOiEnc(.mov, ops.reg1, imm, emit.code);
+            return emit.encode(.mov, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .imm = @bitCast(i64, imm) },
+            });
         },
         0b01 => {
             if (ops.reg1 == .none) {
@@ -770,18 +787,20 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
                     const imm = emit.mir.extraData(Mir.Imm64, payload).data;
                     break :blk imm.decode();
                 } else emit.mir.instructions.items(.data)[inst].imm;
-                // movabs moffs64, rax
-                // TD
-                return lowerToTdEnc(.mov, imm, ops.reg2, emit.code);
+                return emit.encode(.mov, .{
+                    .op1 = .{ .mem = Memory.moffs(ops.reg2, imm) },
+                    .op2 = .{ .reg = .rax },
+                });
             }
             const imm: u64 = if (ops.reg1.size() == 64) blk: {
                 const payload = emit.mir.instructions.items(.data)[inst].payload;
                 const imm = emit.mir.extraData(Mir.Imm64, payload).data;
                 break :blk imm.decode();
             } else emit.mir.instructions.items(.data)[inst].imm;
-            // movabs rax, moffs64
-            // FD
-            return lowerToFdEnc(.mov, ops.reg1, imm, emit.code);
+            return emit.encode(.mov, .{
+                .op1 = .{ .reg = .rax },
+                .op2 = .{ .mem = Memory.moffs(ops.reg1, imm) },
+            });
         },
         else => return emit.fail("TODO unused movabs variant", .{}),
     }
@@ -791,63 +810,58 @@ fn mirFisttp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     const tag = emit.mir.instructions.items(.tag)[inst];
     assert(tag == .fisttp);
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
-
-    // the selecting between operand sizes for this particular `fisttp` instruction
-    // is done via opcode instead of the usual prefixes.
-
-    const opcode: Tag = switch (ops.flags) {
-        0b00 => .fisttp16,
-        0b01 => .fisttp32,
-        0b10 => .fisttp64,
+    const ptr_size: Memory.PtrSize = switch (ops.flags) {
+        0b00 => .word,
+        0b01 => .dword,
+        0b10 => .qword,
         else => unreachable,
     };
-    const mem_or_reg = Memory{
-        .base = ops.reg1,
-        .disp = emit.mir.instructions.items(.data)[inst].imm,
-        .ptr_size = Memory.PtrSize.dword_ptr, // to prevent any prefix from being used
-    };
-    return lowerToMEnc(opcode, .{ .memory = mem_or_reg }, emit.code);
+    return emit.encode(.fisttp, .{
+        .op1 = .{ .mem = Memory.sib(ptr_size, .{
+            .base = ops.reg1,
+            .disp = emit.mir.instructions.items(.data)[inst].disp,
+        }) },
+    });
 }
 
 fn mirFld(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     const tag = emit.mir.instructions.items(.tag)[inst];
     assert(tag == .fld);
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
-
-    // the selecting between operand sizes for this particular `fisttp` instruction
-    // is done via opcode instead of the usual prefixes.
-
-    const opcode: Tag = switch (ops.flags) {
-        0b01 => .fld32,
-        0b10 => .fld64,
+    const ptr_size: Memory.PtrSize = switch (ops.flags) {
+        0b01 => .dword,
+        0b10 => .qword,
         else => unreachable,
     };
-    const mem_or_reg = Memory{
-        .base = ops.reg1,
-        .disp = emit.mir.instructions.items(.data)[inst].imm,
-        .ptr_size = Memory.PtrSize.dword_ptr, // to prevent any prefix from being used
-    };
-    return lowerToMEnc(opcode, .{ .memory = mem_or_reg }, emit.code);
+    return emit.encode(.fld, .{
+        .op1 = .{ .mem = Memory.sib(ptr_size, .{
+            .base = ops.reg1,
+            .disp = emit.mir.instructions.items(.data)[inst].disp,
+        }) },
+    });
 }
 
-fn mirShift(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
-            // sal reg1, 1
-            // M1
-            return lowerToM1Enc(tag, RegisterOrMemory.reg(ops.reg1), emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .imm = 1 },
+            });
         },
         0b01 => {
-            // sal reg1, .cl
-            // MC
-            return lowerToMcEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = .cl },
+            });
         },
         0b10 => {
-            // sal reg1, imm8
-            // MI
             const imm = @truncate(u8, emit.mir.instructions.items(.data)[inst].imm);
-            return lowerToMiImm8Enc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .imm = imm },
+            });
         },
         0b11 => {
             return emit.fail("TODO unused variant: SHIFT reg1, 0b11", .{});
@@ -855,24 +869,28 @@ fn mirShift(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
     }
 }
 
-fn mirMulDiv(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirMulDiv(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     if (ops.reg1 != .none) {
         assert(ops.reg2 == .none);
-        return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code);
+        return emit.encode(mnemonic, .{
+            .op1 = .{ .reg = ops.reg1 },
+        });
     }
     assert(ops.reg2 != .none);
-    const imm = emit.mir.instructions.items(.data)[inst].imm;
+    const disp = emit.mir.instructions.items(.data)[inst].disp;
     const ptr_size: Memory.PtrSize = switch (ops.flags) {
-        0b00 => .byte_ptr,
-        0b01 => .word_ptr,
-        0b10 => .dword_ptr,
-        0b11 => .qword_ptr,
+        0b00 => .byte,
+        0b01 => .word,
+        0b10 => .dword,
+        0b11 => .qword,
     };
-    return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{
-        .disp = imm,
-        .base = ops.reg2,
-    }), emit.code);
+    return emit.encode(mnemonic, .{
+        .op1 = .{ .mem = Memory.sib(ptr_size, .{
+            .base = ops.reg2,
+            .disp = disp,
+        }) },
+    });
 }
 
 fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
@@ -881,40 +899,54 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
-            return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
+            return emit.encode(.imul, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
         0b01 => {
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
+            const disp = emit.mir.instructions.items(.data)[inst].disp;
             const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null;
-            return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.mem(.qword_ptr, .{
-                .disp = imm,
-                .base = src_reg,
-            }), emit.code);
+            return emit.encode(.imul, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.sib(.qword, .{
+                    .base = src_reg,
+                    .disp = disp,
+                }) },
+            });
         },
         0b10 => {
             const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, emit.code);
+            return emit.encode(.imul, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+                .op3 = .{ .imm = imm },
+            });
         },
         0b11 => {
             const payload = emit.mir.instructions.items(.data)[inst].payload;
             const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data;
-            return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.mem(.qword_ptr, .{
-                .disp = imm_pair.dest_off,
-                .base = ops.reg2,
-            }), imm_pair.operand, emit.code);
+            return emit.encode(.imul, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.sib(.qword, .{
+                    .base = ops.reg2,
+                    .disp = imm_pair.dest_off,
+                }) },
+                .op3 = .{ .imm = imm_pair.operand },
+            });
         },
     }
 }
 
 fn mirCwd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
-    const tag: Tag = switch (ops.flags) {
+    const mnemonic: Instruction.Mnemonic = switch (ops.flags) {
         0b00 => .cbw,
         0b01 => .cwd,
         0b10 => .cdq,
         0b11 => .cqo,
     };
-    return lowerToZoEnc(tag, emit.code);
+    return emit.encode(mnemonic, .{});
 }
 
 fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
@@ -923,30 +955,22 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
-            // lea reg1, [reg2 + imm32]
-            // RM
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
+            const disp = emit.mir.instructions.items(.data)[inst].disp;
             const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null;
-            return lowerToRmEnc(
-                .lea,
-                ops.reg1,
-                RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
-                    .disp = imm,
+            return emit.encode(.lea, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{
                     .base = src_reg,
-                }),
-                emit.code,
-            );
+                    .disp = disp,
+                }) },
+            });
         },
         0b01 => {
-            // lea reg1, [rip + imm32]
-            // RM
             const start_offset = emit.code.items.len;
-            try lowerToRmEnc(
-                .lea,
-                ops.reg1,
-                RegisterOrMemory.rip(Memory.PtrSize.new(ops.reg1.size()), 0),
-                emit.code,
-            );
+            try emit.encode(.lea, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) },
+            });
             const end_offset = emit.code.items.len;
             // Backpatch the displacement
             const payload = emit.mir.instructions.items(.data)[inst].payload;
@@ -955,24 +979,21 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
             mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp);
         },
         0b10 => {
-            // lea reg, [rbp + index + imm32]
             const payload = emit.mir.instructions.items(.data)[inst].payload;
             const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode();
             const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null;
-            const scale_index = ScaleIndex{
+            const scale_index = Memory.ScaleIndex{
                 .scale = 0,
                 .index = index_reg_disp.index,
             };
-            return lowerToRmEnc(
-                .lea,
-                ops.reg1,
-                RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
-                    .disp = index_reg_disp.disp,
+            return emit.encode(.lea, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{
                     .base = src_reg,
                     .scale_index = scale_index,
-                }),
-                emit.code,
-            );
+                    .disp = index_reg_disp.disp,
+                }) },
+            });
         },
         0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}),
     }
@@ -989,14 +1010,10 @@ fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
         else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}),
     }
 
-    // lea reg1, [rip + reloc]
-    // RM
-    try lowerToRmEnc(
-        .lea,
-        ops.reg1,
-        RegisterOrMemory.rip(Memory.PtrSize.new(ops.reg1.size()), 0),
-        emit.code,
-    );
+    try emit.encode(.lea, .{
+        .op1 = .{ .reg = ops.reg1 },
+        .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) },
+    });
 
     const end_offset = emit.code.items.len;
 
@@ -1039,94 +1056,64 @@ fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
     }
 }
 
-// SSE instructions
-
-fn mirMovFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
-    const ops = emit.mir.instructions.items(.ops)[inst].decode();
-    switch (ops.flags) {
-        0b00 => {
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{
-                .disp = imm,
-                .base = ops.reg2,
-            }), emit.code);
-        },
-        0b01 => {
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
-                .disp = imm,
-                .base = ops.reg1,
-            }), ops.reg2, emit.code);
-        },
-        0b10 => {
-            return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
-        },
-        else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }),
-    }
-}
-
-fn mirAddFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
-    const ops = emit.mir.instructions.items(.ops)[inst].decode();
-    switch (ops.flags) {
-        0b00 => {
-            return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
-        },
-        else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }),
-    }
-}
-
-fn mirCmpFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
-    const ops = emit.mir.instructions.items(.ops)[inst].decode();
-    switch (ops.flags) {
-        0b00 => {
-            return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
-        },
-        else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }),
-    }
-}
-// AVX instructions
+// SSE/AVX instructions
 
-fn mirMovFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToVmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{
-                .disp = imm,
-                .base = ops.reg2,
-            }), emit.code);
+            const disp = emit.mir.instructions.items(.data)[inst].disp;
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{
+                    .base = ops.reg2,
+                    .disp = disp,
+                }) },
+            });
         },
         0b01 => {
-            const imm = emit.mir.instructions.items(.data)[inst].imm;
-            return lowerToMvEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
-                .disp = imm,
-                .base = ops.reg1,
-            }), ops.reg2, emit.code);
+            const disp = emit.mir.instructions.items(.data)[inst].disp;
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{
+                    .base = ops.reg1,
+                    .disp = disp,
+                }) },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
         0b10 => {
-            return lowerToRvmEnc(tag, ops.reg1, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
-        else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }),
+        else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }),
     }
 }
 
-fn mirAddFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirAddFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
-            return lowerToRvmEnc(tag, ops.reg1, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
-        else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }),
+        else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }),
     }
 }
 
-fn mirCmpFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
+fn mirCmpFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void {
     const ops = emit.mir.instructions.items(.ops)[inst].decode();
     switch (ops.flags) {
         0b00 => {
-            return lowerToVmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
+            return emit.encode(mnemonic, .{
+                .op1 = .{ .reg = ops.reg1 },
+                .op2 = .{ .reg = ops.reg2 },
+            });
         },
-        else => return emit.fail("TODO unused variant 0b{b} for mov_f64", .{ops.flags}),
+        else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }),
     }
 }
 
@@ -1139,7 +1126,9 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
 
     const offset = blk: {
         // callq
-        try lowerToDEnc(.call_near, 0, emit.code);
+        try emit.encode(.call, .{
+            .op1 = .{ .imm = 0 },
+        });
         break :blk @intCast(u32, emit.code.items.len) - 4;
     };
 
@@ -1264,1841 +1253,3 @@ fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
         .none => {},
     }
 }
-
-const Tag = enum {
-    adc,
-    add,
-    sub,
-    xor,
-    @"and",
-    @"or",
-    sbb,
-    cmp,
-    mov,
-    movsx,
-    movsxd,
-    movzx,
-    lea,
-    jmp_near,
-    call_near,
-    push,
-    pop,
-    @"test",
-    ud2,
-    int3,
-    nop,
-    imul,
-    mul,
-    idiv,
-    div,
-    syscall,
-    ret_near,
-    ret_far,
-    fisttp16,
-    fisttp32,
-    fisttp64,
-    fld32,
-    fld64,
-    jo,
-    jno,
-    jb,
-    jbe,
-    jc,
-    jnae,
-    jnc,
-    jae,
-    je,
-    jz,
-    jne,
-    jnz,
-    jna,
-    jnb,
-    jnbe,
-    ja,
-    js,
-    jns,
-    jpe,
-    jp,
-    jpo,
-    jnp,
-    jnge,
-    jl,
-    jge,
-    jnl,
-    jle,
-    jng,
-    jg,
-    jnle,
-    seto,
-    setno,
-    setb,
-    setc,
-    setnae,
-    setnb,
-    setnc,
-    setae,
-    sete,
-    setz,
-    setne,
-    setnz,
-    setbe,
-    setna,
-    seta,
-    setnbe,
-    sets,
-    setns,
-    setp,
-    setpe,
-    setnp,
-    setpo,
-    setl,
-    setnge,
-    setnl,
-    setge,
-    setle,
-    setng,
-    setnle,
-    setg,
-    cmovo,
-    cmovno,
-    cmovb,
-    cmovc,
-    cmovnae,
-    cmovnb,
-    cmovnc,
-    cmovae,
-    cmove,
-    cmovz,
-    cmovne,
-    cmovnz,
-    cmovbe,
-    cmovna,
-    cmova,
-    cmovnbe,
-    cmovs,
-    cmovns,
-    cmovp,
-    cmovpe,
-    cmovnp,
-    cmovpo,
-    cmovl,
-    cmovnge,
-    cmovnl,
-    cmovge,
-    cmovle,
-    cmovng,
-    cmovnle,
-    cmovg,
-    shl,
-    sal,
-    shr,
-    sar,
-    cbw,
-    cwd,
-    cdq,
-    cqo,
-    movsd,
-    movss,
-    addsd,
-    addss,
-    cmpsd,
-    cmpss,
-    ucomisd,
-    ucomiss,
-    vmovsd,
-    vmovss,
-    vaddsd,
-    vaddss,
-    vcmpsd,
-    vcmpss,
-    vucomisd,
-    vucomiss,
-
-    fn isSse(tag: Tag) bool {
-        return switch (tag) {
-            .movsd,
-            .movss,
-            .addsd,
-            .addss,
-            .cmpsd,
-            .cmpss,
-            .ucomisd,
-            .ucomiss,
-            => true,
-
-            else => false,
-        };
-    }
-
-    fn isAvx(tag: Tag) bool {
-        return switch (tag) {
-            .vmovsd,
-            .vmovss,
-            .vaddsd,
-            .vaddss,
-            .vcmpsd,
-            .vcmpss,
-            .vucomisd,
-            .vucomiss,
-            => true,
-
-            else => false,
-        };
-    }
-
-    fn isSetCC(tag: Tag) bool {
-        return switch (tag) {
-            .seto,
-            .setno,
-            .setb,
-            .setc,
-            .setnae,
-            .setnb,
-            .setnc,
-            .setae,
-            .sete,
-            .setz,
-            .setne,
-            .setnz,
-            .setbe,
-            .setna,
-            .seta,
-            .setnbe,
-            .sets,
-            .setns,
-            .setp,
-            .setpe,
-            .setnp,
-            .setpo,
-            .setl,
-            .setnge,
-            .setnl,
-            .setge,
-            .setle,
-            .setng,
-            .setnle,
-            .setg,
-            => true,
-            else => false,
-        };
-    }
-};
-
-const Encoding = enum {
-    /// OP
-    zo,
-
-    /// OP rel32
-    d,
-
-    /// OP r/m64
-    m,
-
-    /// OP r64
-    o,
-
-    /// OP imm32
-    i,
-
-    /// OP r/m64, 1
-    m1,
-
-    /// OP r/m64, .cl
-    mc,
-
-    /// OP r/m64, imm32
-    mi,
-
-    /// OP r/m64, imm8
-    mi8,
-
-    /// OP r/m64, r64
-    mr,
-
-    /// OP r64, r/m64
-    rm,
-
-    /// OP r64, imm64
-    oi,
-
-    /// OP al/ax/eax/rax, moffs
-    fd,
-
-    /// OP moffs, al/ax/eax/rax
-    td,
-
-    /// OP r64, r/m64, imm32
-    rmi,
-
-    /// OP xmm1, xmm2/m64
-    vm,
-
-    /// OP m64, xmm1
-    mv,
-
-    /// OP xmm1, xmm2, xmm3/m64
-    rvm,
-
-    /// OP xmm1, xmm2, xmm3/m64, imm8
-    rvmi,
-};
-
-const OpCode = struct {
-    bytes: [3]u8,
-    count: usize,
-
-    fn init(comptime in_bytes: []const u8) OpCode {
-        comptime assert(in_bytes.len <= 3);
-        comptime var bytes: [3]u8 = undefined;
-        inline for (in_bytes, 0..) |x, i| {
-            bytes[i] = x;
-        }
-        return .{ .bytes = bytes, .count = in_bytes.len };
-    }
-
-    fn encode(opc: OpCode, encoder: Encoder) void {
-        switch (opc.count) {
-            1 => encoder.opcode_1byte(opc.bytes[0]),
-            2 => encoder.opcode_2byte(opc.bytes[0], opc.bytes[1]),
-            3 => encoder.opcode_3byte(opc.bytes[0], opc.bytes[1], opc.bytes[2]),
-            else => unreachable,
-        }
-    }
-
-    fn encodeWithReg(opc: OpCode, encoder: Encoder, reg: Register) void {
-        assert(opc.count == 1);
-        encoder.opcode_withReg(opc.bytes[0], reg.lowEnc());
-    }
-};
-
-inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) OpCode {
-    // zig fmt: off
-    switch (enc) {
-        .zo => return switch (tag) {
-            .ret_near => OpCode.init(&.{0xc3}),
-            .ret_far  => OpCode.init(&.{0xcb}),
-            .ud2      => OpCode.init(&.{ 0x0F, 0x0B }),
-            .int3     => OpCode.init(&.{0xcc}),
-            .nop      => OpCode.init(&.{0x90}),
-            .syscall  => OpCode.init(&.{ 0x0f, 0x05 }),
-            .cbw      => OpCode.init(&.{0x98}),
-            .cwd,
-            .cdq,
-            .cqo      => OpCode.init(&.{0x99}),
-            else      => unreachable,
-        },
-        .d => return switch (tag) {
-            .jmp_near  =>                  OpCode.init(&.{0xe9}),
-            .call_near =>                  OpCode.init(&.{0xe8}),
-
-            .jo        => if (is_one_byte) OpCode.init(&.{0x70}) else OpCode.init(&.{0x0f,0x80}),
-
-            .jno       => if (is_one_byte) OpCode.init(&.{0x71}) else OpCode.init(&.{0x0f,0x81}),
-
-            .jb,
-            .jc,
-            .jnae      => if (is_one_byte) OpCode.init(&.{0x72}) else OpCode.init(&.{0x0f,0x82}),
-
-            .jnb,
-            .jnc,
-            .jae       => if (is_one_byte) OpCode.init(&.{0x73}) else OpCode.init(&.{0x0f,0x83}),
-
-            .je,
-            .jz        => if (is_one_byte) OpCode.init(&.{0x74}) else OpCode.init(&.{0x0f,0x84}),
-
-            .jne,
-            .jnz       => if (is_one_byte) OpCode.init(&.{0x75}) else OpCode.init(&.{0x0f,0x85}),
-
-            .jna,
-            .jbe       => if (is_one_byte) OpCode.init(&.{0x76}) else OpCode.init(&.{0x0f,0x86}),
-
-            .jnbe,
-            .ja        => if (is_one_byte) OpCode.init(&.{0x77}) else OpCode.init(&.{0x0f,0x87}),
-
-            .js        => if (is_one_byte) OpCode.init(&.{0x78}) else OpCode.init(&.{0x0f,0x88}),
-
-            .jns       => if (is_one_byte) OpCode.init(&.{0x79}) else OpCode.init(&.{0x0f,0x89}),
-
-            .jpe,
-            .jp        => if (is_one_byte) OpCode.init(&.{0x7a}) else OpCode.init(&.{0x0f,0x8a}),
-
-            .jpo,
-            .jnp       => if (is_one_byte) OpCode.init(&.{0x7b}) else OpCode.init(&.{0x0f,0x8b}),
-
-            .jnge,
-            .jl        => if (is_one_byte) OpCode.init(&.{0x7c}) else OpCode.init(&.{0x0f,0x8c}),
-
-            .jge,
-            .jnl       => if (is_one_byte) OpCode.init(&.{0x7d}) else OpCode.init(&.{0x0f,0x8d}),
-
-            .jle,
-            .jng       => if (is_one_byte) OpCode.init(&.{0x7e}) else OpCode.init(&.{0x0f,0x8e}),
-
-            .jg,
-            .jnle      => if (is_one_byte) OpCode.init(&.{0x7f}) else OpCode.init(&.{0x0f,0x8f}),
-
-            else       => unreachable,
-        },
-        .m => return switch (tag) {
-            .jmp_near,
-            .call_near,
-            .push       =>                  OpCode.init(&.{0xff}),
-
-            .pop        =>                  OpCode.init(&.{0x8f}),
-            .seto       =>                  OpCode.init(&.{0x0f,0x90}),
-            .setno      =>                  OpCode.init(&.{0x0f,0x91}),
-
-            .setb,
-            .setc,
-            .setnae     =>                  OpCode.init(&.{0x0f,0x92}),
-
-            .setnb,
-            .setnc,
-            .setae      =>                  OpCode.init(&.{0x0f,0x93}),
-
-            .sete,
-            .setz       =>                  OpCode.init(&.{0x0f,0x94}),
-
-            .setne,
-            .setnz      =>                  OpCode.init(&.{0x0f,0x95}),
-
-            .setbe,
-            .setna      =>                  OpCode.init(&.{0x0f,0x96}),
-
-            .seta,
-            .setnbe     =>                  OpCode.init(&.{0x0f,0x97}),
-
-            .sets       =>                  OpCode.init(&.{0x0f,0x98}),
-            .setns      =>                  OpCode.init(&.{0x0f,0x99}),
-
-            .setp,
-            .setpe      =>                  OpCode.init(&.{0x0f,0x9a}),
-
-            .setnp,
-            .setpo      =>                  OpCode.init(&.{0x0f,0x9b}),
-
-            .setl,
-            .setnge     =>                  OpCode.init(&.{0x0f,0x9c}),
-
-            .setnl,
-            .setge      =>                  OpCode.init(&.{0x0f,0x9d}),
-
-            .setle,
-            .setng      =>                  OpCode.init(&.{0x0f,0x9e}),
-
-            .setnle,
-            .setg       =>                  OpCode.init(&.{0x0f,0x9f}),
-
-            .idiv,
-            .div,
-            .imul,
-            .mul        => if (is_one_byte) OpCode.init(&.{0xf6}) else OpCode.init(&.{0xf7}),
-
-            .fisttp16   =>                  OpCode.init(&.{0xdf}),
-            .fisttp32   =>                  OpCode.init(&.{0xdb}),
-            .fisttp64   =>                  OpCode.init(&.{0xdd}),
-            .fld32      =>                  OpCode.init(&.{0xd9}),
-            .fld64      =>                  OpCode.init(&.{0xdd}),
-            else        => unreachable,
-        },
-        .o => return switch (tag) {
-            .push => OpCode.init(&.{0x50}),
-            .pop  => OpCode.init(&.{0x58}),
-            else  => unreachable,
-        },
-        .i => return switch (tag) {
-            .push     => if (is_one_byte) OpCode.init(&.{0x6a}) else OpCode.init(&.{0x68}),
-            .@"test"  => if (is_one_byte) OpCode.init(&.{0xa8}) else OpCode.init(&.{0xa9}),
-            .ret_near => OpCode.init(&.{0xc2}),
-            .ret_far  => OpCode.init(&.{0xca}),
-            else      => unreachable,
-        },
-        .m1 => return switch (tag) {
-            .shl, .sal,
-            .shr, .sar  => if (is_one_byte) OpCode.init(&.{0xd0}) else OpCode.init(&.{0xd1}),
-            else        => unreachable,
-        },
-        .mc => return switch (tag) {
-            .shl, .sal,
-            .shr, .sar  => if (is_one_byte) OpCode.init(&.{0xd2}) else OpCode.init(&.{0xd3}),
-            else        => unreachable,
-        },
-        .mi => return switch (tag) {
-            .adc, .add,
-            .sub, .xor,
-            .@"and", .@"or",
-            .sbb, .cmp       => if (is_one_byte) OpCode.init(&.{0x80}) else OpCode.init(&.{0x81}),
-            .mov             => if (is_one_byte) OpCode.init(&.{0xc6}) else OpCode.init(&.{0xc7}),
-            .@"test"         => if (is_one_byte) OpCode.init(&.{0xf6}) else OpCode.init(&.{0xf7}),
-            else             => unreachable,
-        },
-        .mi8 => return switch (tag) {
-            .adc, .add,
-            .sub, .xor,
-            .@"and", .@"or",
-            .sbb, .cmp        =>                  OpCode.init(&.{0x83}),
-            .shl, .sal,
-            .shr, .sar        => if (is_one_byte) OpCode.init(&.{0xc0}) else OpCode.init(&.{0xc1}),
-            else              => unreachable,
-        },
-        .mr => return switch (tag) {
-            .adc     => if (is_one_byte) OpCode.init(&.{0x10}) else OpCode.init(&.{0x11}),
-            .add     => if (is_one_byte) OpCode.init(&.{0x00}) else OpCode.init(&.{0x01}),
-            .sub     => if (is_one_byte) OpCode.init(&.{0x28}) else OpCode.init(&.{0x29}),
-            .xor     => if (is_one_byte) OpCode.init(&.{0x30}) else OpCode.init(&.{0x31}),
-            .@"and"  => if (is_one_byte) OpCode.init(&.{0x20}) else OpCode.init(&.{0x21}),
-            .@"or"   => if (is_one_byte) OpCode.init(&.{0x08}) else OpCode.init(&.{0x09}),
-            .sbb     => if (is_one_byte) OpCode.init(&.{0x18}) else OpCode.init(&.{0x19}),
-            .cmp     => if (is_one_byte) OpCode.init(&.{0x38}) else OpCode.init(&.{0x39}),
-            .mov     => if (is_one_byte) OpCode.init(&.{0x88}) else OpCode.init(&.{0x89}),
-            .@"test" => if (is_one_byte) OpCode.init(&.{0x84}) else OpCode.init(&.{0x85}),
-            .movsd   =>                  OpCode.init(&.{0xf2,0x0f,0x11}),
-            .movss   =>                  OpCode.init(&.{0xf3,0x0f,0x11}),
-            else     => unreachable,
-        },
-        .rm => return switch (tag) {
-            .adc      => if (is_one_byte) OpCode.init(&.{0x12})      else OpCode.init(&.{0x13}),
-            .add      => if (is_one_byte) OpCode.init(&.{0x02})      else OpCode.init(&.{0x03}),
-            .sub      => if (is_one_byte) OpCode.init(&.{0x2a})      else OpCode.init(&.{0x2b}),
-            .xor      => if (is_one_byte) OpCode.init(&.{0x32})      else OpCode.init(&.{0x33}),
-            .@"and"   => if (is_one_byte) OpCode.init(&.{0x22})      else OpCode.init(&.{0x23}),
-            .@"or"    => if (is_one_byte) OpCode.init(&.{0x0a})      else OpCode.init(&.{0x0b}),
-            .sbb      => if (is_one_byte) OpCode.init(&.{0x1a})      else OpCode.init(&.{0x1b}),
-            .cmp      => if (is_one_byte) OpCode.init(&.{0x3a})      else OpCode.init(&.{0x3b}),
-            .mov      => if (is_one_byte) OpCode.init(&.{0x8a})      else OpCode.init(&.{0x8b}),
-            .movsx    => if (is_one_byte) OpCode.init(&.{0x0f,0xbe}) else OpCode.init(&.{0x0f,0xbf}),
-            .movsxd   =>                  OpCode.init(&.{0x63}),
-            .movzx    => if (is_one_byte) OpCode.init(&.{0x0f,0xb6}) else OpCode.init(&.{0x0f,0xb7}),
-            .lea      => if (is_one_byte) OpCode.init(&.{0x8c})      else OpCode.init(&.{0x8d}),
-            .imul     =>                  OpCode.init(&.{0x0f,0xaf}),
-
-            .cmova,
-            .cmovnbe, =>                  OpCode.init(&.{0x0f,0x47}),
-
-            .cmovae,
-            .cmovnb,  =>                  OpCode.init(&.{0x0f,0x43}),
-
-            .cmovb,
-            .cmovc,
-            .cmovnae  =>                  OpCode.init(&.{0x0f,0x42}),
-
-            .cmovbe,
-            .cmovna,  =>                  OpCode.init(&.{0x0f,0x46}),
-
-            .cmove,
-            .cmovz,   =>                  OpCode.init(&.{0x0f,0x44}),
-
-            .cmovg,
-            .cmovnle, =>                  OpCode.init(&.{0x0f,0x4f}),
-
-            .cmovge,
-            .cmovnl,  =>                  OpCode.init(&.{0x0f,0x4d}),
-
-            .cmovl,
-            .cmovnge, =>                  OpCode.init(&.{0x0f,0x4c}),
-
-            .cmovle,
-            .cmovng,  =>                  OpCode.init(&.{0x0f,0x4e}),
-
-            .cmovne,
-            .cmovnz,  =>                  OpCode.init(&.{0x0f,0x45}),
-
-            .cmovno   =>                  OpCode.init(&.{0x0f,0x41}),
-
-            .cmovnp,
-            .cmovpo,  =>                  OpCode.init(&.{0x0f,0x4b}),
-
-            .cmovns   =>                  OpCode.init(&.{0x0f,0x49}),
-
-            .cmovo    =>                  OpCode.init(&.{0x0f,0x40}),
-
-            .cmovp,
-            .cmovpe,  =>                  OpCode.init(&.{0x0f,0x4a}),
-
-            .cmovs    =>                  OpCode.init(&.{0x0f,0x48}),
-
-            .movsd    =>                  OpCode.init(&.{0xf2,0x0f,0x10}),
-            .movss    =>                  OpCode.init(&.{0xf3,0x0f,0x10}),
-            .addsd    =>                  OpCode.init(&.{0xf2,0x0f,0x58}),
-            .addss    =>                  OpCode.init(&.{0xf3,0x0f,0x58}),
-            .ucomisd  =>                  OpCode.init(&.{0x66,0x0f,0x2e}),
-            .ucomiss  =>                  OpCode.init(&.{0x0f,0x2e}),
-            else => unreachable,
-        },
-        .oi => return switch (tag) {
-            .mov => if (is_one_byte) OpCode.init(&.{0xb0}) else OpCode.init(&.{0xb8}),
-            else => unreachable,
-        },
-        .fd => return switch (tag) {
-            .mov => if (is_one_byte) OpCode.init(&.{0xa0}) else OpCode.init(&.{0xa1}),
-            else => unreachable,
-        },
-        .td => return switch (tag) {
-            .mov => if (is_one_byte) OpCode.init(&.{0xa2}) else OpCode.init(&.{0xa3}),
-            else => unreachable,
-        },
-        .rmi => return switch (tag) {
-            .imul => if (is_one_byte) OpCode.init(&.{0x6b}) else OpCode.init(&.{0x69}),
-            else  => unreachable,
-        },
-        .mv => return switch (tag) {
-            .vmovsd,
-            .vmovss => OpCode.init(&.{0x11}),
-            else => unreachable,
-        },
-        .vm => return switch (tag) {
-            .vmovsd,
-            .vmovss   => OpCode.init(&.{0x10}),
-            .vucomisd,
-            .vucomiss => OpCode.init(&.{0x2e}),
-            else => unreachable,
-        },
-        .rvm => return switch (tag) {
-            .vaddsd,
-            .vaddss  => OpCode.init(&.{0x58}),
-            .vmovsd,
-            .vmovss  => OpCode.init(&.{0x10}),
-            else => unreachable,
-        },
-        .rvmi => return switch (tag) {
-            .vcmpsd,
-            .vcmpss  => OpCode.init(&.{0xc2}),
-            else     => unreachable,
-        },
-    }
-    // zig fmt: on
-}
-
-inline fn getModRmExt(tag: Tag) u3 {
-    return switch (tag) {
-        .adc => 0x2,
-        .add => 0x0,
-        .sub => 0x5,
-        .xor => 0x6,
-        .@"and" => 0x4,
-        .@"or" => 0x1,
-        .sbb => 0x3,
-        .cmp => 0x7,
-        .mov => 0x0,
-        .jmp_near => 0x4,
-        .call_near => 0x2,
-        .push => 0x6,
-        .pop => 0x0,
-        .@"test" => 0x0,
-        .seto,
-        .setno,
-        .setb,
-        .setc,
-        .setnae,
-        .setnb,
-        .setnc,
-        .setae,
-        .sete,
-        .setz,
-        .setne,
-        .setnz,
-        .setbe,
-        .setna,
-        .seta,
-        .setnbe,
-        .sets,
-        .setns,
-        .setp,
-        .setpe,
-        .setnp,
-        .setpo,
-        .setl,
-        .setnge,
-        .setnl,
-        .setge,
-        .setle,
-        .setng,
-        .setnle,
-        .setg,
-        => 0x0,
-        .shl,
-        .sal,
-        => 0x4,
-        .shr => 0x5,
-        .sar => 0x7,
-        .mul => 0x4,
-        .imul => 0x5,
-        .div => 0x6,
-        .idiv => 0x7,
-        .fisttp16 => 0x1,
-        .fisttp32 => 0x1,
-        .fisttp64 => 0x1,
-        .fld32 => 0x0,
-        .fld64 => 0x0,
-        else => unreachable,
-    };
-}
-
-const VexEncoding = struct {
-    prefix: Encoder.Vex,
-    reg: ?enum {
-        ndd,
-        nds,
-        dds,
-    },
-};
-
-inline fn getVexEncoding(tag: Tag, enc: Encoding) VexEncoding {
-    const desc: struct {
-        reg: enum {
-            none,
-            ndd,
-            nds,
-            dds,
-        } = .none,
-        len_256: bool = false,
-        wig: bool = false,
-        lig: bool = false,
-        lz: bool = false,
-        lead_opc: enum {
-            l_0f,
-            l_0f_3a,
-            l_0f_38,
-        } = .l_0f,
-        simd_prefix: enum {
-            none,
-            p_66,
-            p_f2,
-            p_f3,
-        } = .none,
-    } = blk: {
-        switch (enc) {
-            .mv => switch (tag) {
-                .vmovsd => break :blk .{ .lig = true, .simd_prefix = .p_f2, .wig = true },
-                .vmovss => break :blk .{ .lig = true, .simd_prefix = .p_f3, .wig = true },
-                else => unreachable,
-            },
-            .vm => switch (tag) {
-                .vmovsd => break :blk .{ .lig = true, .simd_prefix = .p_f2, .wig = true },
-                .vmovss => break :blk .{ .lig = true, .simd_prefix = .p_f3, .wig = true },
-                .vucomisd => break :blk .{ .lig = true, .simd_prefix = .p_66, .wig = true },
-                .vucomiss => break :blk .{ .lig = true, .wig = true },
-                else => unreachable,
-            },
-            .rvm => switch (tag) {
-                .vaddsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true },
-                .vaddss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true },
-                .vmovsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true },
-                .vmovss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true },
-                else => unreachable,
-            },
-            .rvmi => switch (tag) {
-                .vcmpsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true },
-                .vcmpss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true },
-                else => unreachable,
-            },
-            else => unreachable,
-        }
-    };
-
-    var vex: Encoder.Vex = .{};
-
-    if (desc.len_256) vex.len_256();
-    if (desc.wig) vex.wig();
-    if (desc.lig) vex.lig();
-    if (desc.lz) vex.lz();
-
-    switch (desc.lead_opc) {
-        .l_0f => {},
-        .l_0f_3a => vex.lead_opc_0f_3a(),
-        .l_0f_38 => vex.lead_opc_0f_38(),
-    }
-
-    switch (desc.simd_prefix) {
-        .none => {},
-        .p_66 => vex.simd_prefix_66(),
-        .p_f2 => vex.simd_prefix_f2(),
-        .p_f3 => vex.simd_prefix_f3(),
-    }
-
-    return VexEncoding{ .prefix = vex, .reg = switch (desc.reg) {
-        .none => null,
-        .nds => .nds,
-        .dds => .dds,
-        .ndd => .ndd,
-    } };
-}
-
-const ScaleIndex = packed struct {
-    scale: u2,
-    index: Register,
-};
-
-const Memory = struct {
-    base: ?Register,
-    rip: bool = false,
-    disp: u32,
-    ptr_size: PtrSize,
-    scale_index: ?ScaleIndex = null,
-
-    const PtrSize = enum(u2) {
-        byte_ptr = 0b00,
-        word_ptr = 0b01,
-        dword_ptr = 0b10,
-        qword_ptr = 0b11,
-
-        fn new(bit_size: u64) PtrSize {
-            return @intToEnum(PtrSize, math.log2_int(u4, @intCast(u4, @divExact(bit_size, 8))));
-        }
-
-        /// Returns size in bits.
-        fn size(ptr_size: PtrSize) u64 {
-            return 8 * (math.powi(u8, 2, @enumToInt(ptr_size)) catch unreachable);
-        }
-    };
-
-    fn encode(mem_op: Memory, encoder: Encoder, operand: u3) void {
-        if (mem_op.base) |base| {
-            const dst = base.lowEnc();
-            const src = operand;
-            if (dst == 4 or mem_op.scale_index != null) {
-                if (mem_op.disp == 0 and dst != 5) {
-                    encoder.modRm_SIBDisp0(src);
-                    if (mem_op.scale_index) |si| {
-                        encoder.sib_scaleIndexBase(si.scale, si.index.lowEnc(), dst);
-                    } else {
-                        encoder.sib_base(dst);
-                    }
-                } else if (immOpSize(mem_op.disp) == 8) {
-                    encoder.modRm_SIBDisp8(src);
-                    if (mem_op.scale_index) |si| {
-                        encoder.sib_scaleIndexBaseDisp8(si.scale, si.index.lowEnc(), dst);
-                    } else {
-                        encoder.sib_baseDisp8(dst);
-                    }
-                    encoder.disp8(@bitCast(i8, @truncate(u8, mem_op.disp)));
-                } else {
-                    encoder.modRm_SIBDisp32(src);
-                    if (mem_op.scale_index) |si| {
-                        encoder.sib_scaleIndexBaseDisp32(si.scale, si.index.lowEnc(), dst);
-                    } else {
-                        encoder.sib_baseDisp32(dst);
-                    }
-                    encoder.disp32(@bitCast(i32, mem_op.disp));
-                }
-            } else {
-                if (mem_op.disp == 0 and dst != 5) {
-                    encoder.modRm_indirectDisp0(src, dst);
-                } else if (immOpSize(mem_op.disp) == 8) {
-                    encoder.modRm_indirectDisp8(src, dst);
-                    encoder.disp8(@bitCast(i8, @truncate(u8, mem_op.disp)));
-                } else {
-                    encoder.modRm_indirectDisp32(src, dst);
-                    encoder.disp32(@bitCast(i32, mem_op.disp));
-                }
-            }
-        } else {
-            if (mem_op.rip) {
-                encoder.modRm_RIPDisp32(operand);
-            } else {
-                encoder.modRm_SIBDisp0(operand);
-                if (mem_op.scale_index) |si| {
-                    encoder.sib_scaleIndexDisp32(si.scale, si.index.lowEnc());
-                } else {
-                    encoder.sib_disp32();
-                }
-            }
-            encoder.disp32(@bitCast(i32, mem_op.disp));
-        }
-    }
-
-    /// Returns size in bits.
-    fn size(memory: Memory) u64 {
-        return memory.ptr_size.size();
-    }
-};
-
-fn encodeImm(encoder: Encoder, imm: u32, size: u64) void {
-    switch (size) {
-        8 => encoder.imm8(@bitCast(i8, @truncate(u8, imm))),
-        16 => encoder.imm16(@bitCast(i16, @truncate(u16, imm))),
-        32, 64 => encoder.imm32(@bitCast(i32, imm)),
-        else => unreachable,
-    }
-}
-
-const RegisterOrMemory = union(enum) {
-    register: Register,
-    memory: Memory,
-
-    fn reg(register: Register) RegisterOrMemory {
-        return .{ .register = register };
-    }
-
-    fn mem(ptr_size: Memory.PtrSize, args: struct {
-        disp: u32,
-        base: ?Register = null,
-        scale_index: ?ScaleIndex = null,
-    }) RegisterOrMemory {
-        return .{
-            .memory = .{
-                .base = args.base,
-                .disp = args.disp,
-                .ptr_size = ptr_size,
-                .scale_index = args.scale_index,
-            },
-        };
-    }
-
-    fn rip(ptr_size: Memory.PtrSize, disp: u32) RegisterOrMemory {
-        return .{
-            .memory = .{
-                .base = null,
-                .rip = true,
-                .disp = disp,
-                .ptr_size = ptr_size,
-            },
-        };
-    }
-
-    /// Returns size in bits.
-    fn size(reg_or_mem: RegisterOrMemory) u64 {
-        return switch (reg_or_mem) {
-            .register => |register| register.size(),
-            .memory => |memory| memory.size(),
-        };
-    }
-};
-
-fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = getOpCode(tag, .zo, false);
-    const encoder = try Encoder.init(code, 2);
-    switch (tag) {
-        .cqo => {
-            encoder.rex(.{
-                .w = true,
-            });
-        },
-        else => {},
-    }
-    opc.encode(encoder);
-}
-
-fn lowerToIEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void {
-    assert(!tag.isAvx());
-    if (tag == .ret_far or tag == .ret_near) {
-        const encoder = try Encoder.init(code, 3);
-        const opc = getOpCode(tag, .i, false);
-        opc.encode(encoder);
-        encoder.imm16(@bitCast(i16, @truncate(u16, imm)));
-        return;
-    }
-    const opc = getOpCode(tag, .i, immOpSize(imm) == 8);
-    const encoder = try Encoder.init(code, 5);
-    if (immOpSize(imm) == 16) {
-        encoder.prefix16BitMode();
-    }
-    opc.encode(encoder);
-    encodeImm(encoder, imm, immOpSize(imm));
-}
-
-fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = getOpCode(tag, .o, false);
-    const encoder = try Encoder.init(code, 3);
-    if (reg.size() == 16) {
-        encoder.prefix16BitMode();
-    }
-    encoder.rex(.{
-        .w = false,
-        .b = reg.isExtended(),
-    });
-    opc.encodeWithReg(encoder, reg);
-}
-
-fn lowerToDEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = getOpCode(tag, .d, false);
-    const encoder = try Encoder.init(code, 6);
-    opc.encode(encoder);
-    encoder.imm32(@bitCast(i32, imm));
-}
-
-fn lowerToMxEnc(tag: Tag, reg_or_mem: RegisterOrMemory, enc: Encoding, code: *std.ArrayList(u8)) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = getOpCode(tag, enc, reg_or_mem.size() == 8);
-    const modrm_ext = getModRmExt(tag);
-    switch (reg_or_mem) {
-        .register => |reg| {
-            const encoder = try Encoder.init(code, 4);
-            if (reg.size() == 16) {
-                encoder.prefix16BitMode();
-            }
-            const wide = if (tag == .jmp_near) false else setRexWRegister(reg);
-            encoder.rex(.{
-                .w = wide,
-                .b = reg.isExtended(),
-            });
-            opc.encode(encoder);
-            encoder.modRm_direct(modrm_ext, reg.lowEnc());
-        },
-        .memory => |mem_op| {
-            const encoder = try Encoder.init(code, 8);
-            if (mem_op.ptr_size == .word_ptr) {
-                encoder.prefix16BitMode();
-            }
-            if (mem_op.base) |base| {
-                const wide = if (tag == .jmp_near) false else mem_op.ptr_size == .qword_ptr;
-                encoder.rex(.{
-                    .w = wide,
-                    .b = base.isExtended(),
-                    .x = if (mem_op.scale_index) |si| si.index.isExtended() else false,
-                });
-            }
-            opc.encode(encoder);
-            mem_op.encode(encoder, modrm_ext);
-        },
-    }
-}
-
-fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void {
-    return lowerToMxEnc(tag, reg_or_mem, .m, code);
-}
-
-fn lowerToM1Enc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void {
-    return lowerToMxEnc(tag, reg_or_mem, .m1, code);
-}
-
-fn lowerToMcEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void {
-    return lowerToMxEnc(tag, reg_or_mem, .mc, code);
-}
-
-fn lowerToTdEnc(tag: Tag, moffs: u64, reg: Register, code: *std.ArrayList(u8)) InnerError!void {
-    return lowerToTdFdEnc(tag, reg, moffs, code, true);
-}
-
-fn lowerToFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8)) InnerError!void {
-    return lowerToTdFdEnc(tag, reg, moffs, code, false);
-}
-
-fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8), td: bool) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = if (td) getOpCode(tag, .td, reg.size() == 8) else getOpCode(tag, .fd, reg.size() == 8);
-    const encoder = try Encoder.init(code, 10);
-    if (reg.size() == 16) {
-        encoder.prefix16BitMode();
-    }
-    encoder.rex(.{
-        .w = setRexWRegister(reg),
-    });
-    opc.encode(encoder);
-    switch (reg.size()) {
-        8 => encoder.imm8(@bitCast(i8, @truncate(u8, moffs))),
-        16 => encoder.imm16(@bitCast(i16, @truncate(u16, moffs))),
-        32 => encoder.imm32(@bitCast(i32, @truncate(u32, moffs))),
-        64 => encoder.imm64(moffs),
-        else => unreachable,
-    }
-}
-
-fn lowerToOiEnc(tag: Tag, reg: Register, imm: u64, code: *std.ArrayList(u8)) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = getOpCode(tag, .oi, reg.size() == 8);
-    const encoder = try Encoder.init(code, 10);
-    if (reg.size() == 16) {
-        encoder.prefix16BitMode();
-    }
-    encoder.rex(.{
-        .w = setRexWRegister(reg),
-        .b = reg.isExtended(),
-    });
-    opc.encodeWithReg(encoder, reg);
-    switch (reg.size()) {
-        8 => encoder.imm8(@bitCast(i8, @truncate(u8, imm))),
-        16 => encoder.imm16(@bitCast(i16, @truncate(u16, imm))),
-        32 => encoder.imm32(@bitCast(i32, @truncate(u32, imm))),
-        64 => encoder.imm64(imm),
-        else => unreachable,
-    }
-}
-
-fn lowerToMiXEnc(
-    tag: Tag,
-    reg_or_mem: RegisterOrMemory,
-    imm: u32,
-    enc: Encoding,
-    code: *std.ArrayList(u8),
-) InnerError!void {
-    assert(!tag.isAvx());
-    const modrm_ext = getModRmExt(tag);
-    const opc = getOpCode(tag, enc, reg_or_mem.size() == 8);
-    switch (reg_or_mem) {
-        .register => |dst_reg| {
-            const encoder = try Encoder.init(code, 7);
-            if (dst_reg.size() == 16) {
-                // 0x66 prefix switches to the non-default size; here we assume a switch from
-                // the default 32bits to 16bits operand-size.
-                // More info: https://www.cs.uni-potsdam.de/desn/lehre/ss15/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf#page=32&zoom=auto,-159,773
-                encoder.prefix16BitMode();
-            }
-            encoder.rex(.{
-                .w = setRexWRegister(dst_reg),
-                .b = dst_reg.isExtended(),
-            });
-            opc.encode(encoder);
-            encoder.modRm_direct(modrm_ext, dst_reg.lowEnc());
-            encodeImm(encoder, imm, if (enc == .mi8) 8 else dst_reg.size());
-        },
-        .memory => |dst_mem| {
-            const encoder = try Encoder.init(code, 12);
-            if (dst_mem.ptr_size == .word_ptr) {
-                encoder.prefix16BitMode();
-            }
-            if (dst_mem.base) |base| {
-                encoder.rex(.{
-                    .w = dst_mem.ptr_size == .qword_ptr,
-                    .b = base.isExtended(),
-                    .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            } else {
-                encoder.rex(.{
-                    .w = dst_mem.ptr_size == .qword_ptr,
-                    .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            }
-            opc.encode(encoder);
-            dst_mem.encode(encoder, modrm_ext);
-            encodeImm(encoder, imm, if (enc == .mi8) 8 else dst_mem.ptr_size.size());
-        },
-    }
-}
-
-fn lowerToMiImm8Enc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u8, code: *std.ArrayList(u8)) InnerError!void {
-    return lowerToMiXEnc(tag, reg_or_mem, imm, .mi8, code);
-}
-
-fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.ArrayList(u8)) InnerError!void {
-    return lowerToMiXEnc(tag, reg_or_mem, imm, .mi, code);
-}
-
-fn lowerToRmEnc(
-    tag: Tag,
-    reg: Register,
-    reg_or_mem: RegisterOrMemory,
-    code: *std.ArrayList(u8),
-) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = getOpCode(tag, .rm, reg.size() == 8 or reg_or_mem.size() == 8);
-    switch (reg_or_mem) {
-        .register => |src_reg| {
-            const encoder = try Encoder.init(code, 5);
-            if (reg.size() == 16) {
-                encoder.prefix16BitMode();
-            }
-            encoder.rex(.{
-                .w = setRexWRegister(reg) or setRexWRegister(src_reg),
-                .r = reg.isExtended(),
-                .b = src_reg.isExtended(),
-            });
-            opc.encode(encoder);
-            encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc());
-        },
-        .memory => |src_mem| {
-            const encoder = try Encoder.init(code, 9);
-            if (reg.size() == 16) {
-                encoder.prefix16BitMode();
-            }
-            if (src_mem.base) |base| {
-                // TODO handle 32-bit base register - requires prefix 0x67
-                // Intel Manual, Vol 1, chapter 3.6 and 3.6.1
-                encoder.rex(.{
-                    .w = setRexWRegister(reg),
-                    .r = reg.isExtended(),
-                    .b = base.isExtended(),
-                    .x = if (src_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            } else {
-                encoder.rex(.{
-                    .w = setRexWRegister(reg),
-                    .r = reg.isExtended(),
-                    .x = if (src_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            }
-            opc.encode(encoder);
-            src_mem.encode(encoder, reg.lowEnc());
-        },
-    }
-}
-
-fn lowerToMrEnc(
-    tag: Tag,
-    reg_or_mem: RegisterOrMemory,
-    reg: Register,
-    code: *std.ArrayList(u8),
-) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = getOpCode(tag, .mr, reg.size() == 8 or reg_or_mem.size() == 8);
-    switch (reg_or_mem) {
-        .register => |dst_reg| {
-            const encoder = try Encoder.init(code, 4);
-            if (dst_reg.size() == 16) {
-                encoder.prefix16BitMode();
-            }
-            encoder.rex(.{
-                .w = setRexWRegister(dst_reg) or setRexWRegister(reg),
-                .r = reg.isExtended(),
-                .b = dst_reg.isExtended(),
-            });
-            opc.encode(encoder);
-            encoder.modRm_direct(reg.lowEnc(), dst_reg.lowEnc());
-        },
-        .memory => |dst_mem| {
-            const encoder = try Encoder.init(code, 9);
-            if (reg.size() == 16) {
-                encoder.prefix16BitMode();
-            }
-            if (dst_mem.base) |base| {
-                encoder.rex(.{
-                    .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg),
-                    .r = reg.isExtended(),
-                    .b = base.isExtended(),
-                    .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            } else {
-                encoder.rex(.{
-                    .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg),
-                    .r = reg.isExtended(),
-                    .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            }
-            opc.encode(encoder);
-            dst_mem.encode(encoder, reg.lowEnc());
-        },
-    }
-}
-
-fn lowerToRmiEnc(
-    tag: Tag,
-    reg: Register,
-    reg_or_mem: RegisterOrMemory,
-    imm: u32,
-    code: *std.ArrayList(u8),
-) InnerError!void {
-    assert(!tag.isAvx());
-    const opc = getOpCode(tag, .rmi, false);
-    const encoder = try Encoder.init(code, 13);
-    if (reg.size() == 16) {
-        encoder.prefix16BitMode();
-    }
-    switch (reg_or_mem) {
-        .register => |src_reg| {
-            encoder.rex(.{
-                .w = setRexWRegister(reg) or setRexWRegister(src_reg),
-                .r = reg.isExtended(),
-                .b = src_reg.isExtended(),
-            });
-            opc.encode(encoder);
-            encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc());
-        },
-        .memory => |src_mem| {
-            if (src_mem.base) |base| {
-                // TODO handle 32-bit base register - requires prefix 0x67
-                // Intel Manual, Vol 1, chapter 3.6 and 3.6.1
-                encoder.rex(.{
-                    .w = setRexWRegister(reg),
-                    .r = reg.isExtended(),
-                    .b = base.isExtended(),
-                    .x = if (src_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            } else {
-                encoder.rex(.{
-                    .w = setRexWRegister(reg),
-                    .r = reg.isExtended(),
-                    .x = if (src_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            }
-            opc.encode(encoder);
-            src_mem.encode(encoder, reg.lowEnc());
-        },
-    }
-    encodeImm(encoder, imm, reg.size());
-}
-
-/// Also referred to as XM encoding in Intel manual.
-fn lowerToVmEnc(
-    tag: Tag,
-    reg: Register,
-    reg_or_mem: RegisterOrMemory,
-    code: *std.ArrayList(u8),
-) InnerError!void {
-    const opc = getOpCode(tag, .vm, false);
-    var enc = getVexEncoding(tag, .vm);
-    const vex = &enc.prefix;
-    switch (reg_or_mem) {
-        .register => |src_reg| {
-            const encoder = try Encoder.init(code, 5);
-            vex.rex(.{
-                .r = reg.isExtended(),
-                .b = src_reg.isExtended(),
-            });
-            encoder.vex(enc.prefix);
-            opc.encode(encoder);
-            encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc());
-        },
-        .memory => |src_mem| {
-            const encoder = try Encoder.init(code, 10);
-            if (src_mem.base) |base| {
-                vex.rex(.{
-                    .r = reg.isExtended(),
-                    .b = base.isExtended(),
-                    .x = if (src_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            } else {
-                vex.rex(.{
-                    .r = reg.isExtended(),
-                    .x = if (src_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            }
-            encoder.vex(enc.prefix);
-            opc.encode(encoder);
-            src_mem.encode(encoder, reg.lowEnc());
-        },
-    }
-}
-
-/// Usually referred to as MR encoding with V/V in Intel manual.
-fn lowerToMvEnc(
-    tag: Tag,
-    reg_or_mem: RegisterOrMemory,
-    reg: Register,
-    code: *std.ArrayList(u8),
-) InnerError!void {
-    const opc = getOpCode(tag, .mv, false);
-    var enc = getVexEncoding(tag, .mv);
-    const vex = &enc.prefix;
-    switch (reg_or_mem) {
-        .register => |dst_reg| {
-            const encoder = try Encoder.init(code, 4);
-            vex.rex(.{
-                .r = reg.isExtended(),
-                .b = dst_reg.isExtended(),
-            });
-            encoder.vex(enc.prefix);
-            opc.encode(encoder);
-            encoder.modRm_direct(reg.lowEnc(), dst_reg.lowEnc());
-        },
-        .memory => |dst_mem| {
-            const encoder = try Encoder.init(code, 10);
-            if (dst_mem.base) |base| {
-                vex.rex(.{
-                    .r = reg.isExtended(),
-                    .b = base.isExtended(),
-                    .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            } else {
-                vex.rex(.{
-                    .r = reg.isExtended(),
-                    .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false,
-                });
-            }
-            encoder.vex(enc.prefix);
-            opc.encode(encoder);
-            dst_mem.encode(encoder, reg.lowEnc());
-        },
-    }
-}
-
-fn lowerToRvmEnc(
-    tag: Tag,
-    reg1: Register,
-    reg2: Register,
-    reg_or_mem: RegisterOrMemory,
-    code: *std.ArrayList(u8),
-) InnerError!void {
-    const opc = getOpCode(tag, .rvm, false);
-    var enc = getVexEncoding(tag, .rvm);
-    const vex = &enc.prefix;
-    switch (reg_or_mem) {
-        .register => |reg3| {
-            if (enc.reg) |vvvv| {
-                switch (vvvv) {
-                    .nds => vex.reg(reg2.enc()),
-                    else => unreachable, // TODO
-                }
-            }
-            const encoder = try Encoder.init(code, 5);
-            vex.rex(.{
-                .r = reg1.isExtended(),
-                .b = reg3.isExtended(),
-            });
-            encoder.vex(enc.prefix);
-            opc.encode(encoder);
-            encoder.modRm_direct(reg1.lowEnc(), reg3.lowEnc());
-        },
-        .memory => |dst_mem| {
-            _ = dst_mem;
-            unreachable; // TODO
-        },
-    }
-}
-
-fn lowerToRvmiEnc(
-    tag: Tag,
-    reg1: Register,
-    reg2: Register,
-    reg_or_mem: RegisterOrMemory,
-    imm: u32,
-    code: *std.ArrayList(u8),
-) InnerError!void {
-    const opc = getOpCode(tag, .rvmi, false);
-    var enc = getVexEncoding(tag, .rvmi);
-    const vex = &enc.prefix;
-    const encoder: Encoder = blk: {
-        switch (reg_or_mem) {
-            .register => |reg3| {
-                if (enc.reg) |vvvv| {
-                    switch (vvvv) {
-                        .nds => vex.reg(reg2.enc()),
-                        else => unreachable, // TODO
-                    }
-                }
-                const encoder = try Encoder.init(code, 5);
-                vex.rex(.{
-                    .r = reg1.isExtended(),
-                    .b = reg3.isExtended(),
-                });
-                encoder.vex(enc.prefix);
-                opc.encode(encoder);
-                encoder.modRm_direct(reg1.lowEnc(), reg3.lowEnc());
-                break :blk encoder;
-            },
-            .memory => |dst_mem| {
-                _ = dst_mem;
-                unreachable; // TODO
-            },
-        }
-    };
-    encodeImm(encoder, imm, 8); // TODO
-}
-
-fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void {
-    assert(expected.len > 0);
-    if (mem.eql(u8, expected, given)) return;
-    const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)});
-    defer testing.allocator.free(expected_fmt);
-    const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)});
-    defer testing.allocator.free(given_fmt);
-    const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?;
-    var padding = try testing.allocator.alloc(u8, idx + 5);
-    defer testing.allocator.free(padding);
-    mem.set(u8, padding, ' ');
-    std.debug.print("\nASM: {s}\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{
-        assembly,
-        expected_fmt,
-        given_fmt,
-        padding,
-    });
-    return error.TestFailed;
-}
-
-const TestEmit = struct {
-    code_buffer: std.ArrayList(u8),
-    next: usize = 0,
-
-    fn init() TestEmit {
-        return .{
-            .code_buffer = std.ArrayList(u8).init(testing.allocator),
-        };
-    }
-
-    fn deinit(emit: *TestEmit) void {
-        emit.code_buffer.deinit();
-        emit.next = undefined;
-    }
-
-    fn code(emit: *TestEmit) *std.ArrayList(u8) {
-        emit.next = emit.code_buffer.items.len;
-        return &emit.code_buffer;
-    }
-
-    fn lowered(emit: TestEmit) []const u8 {
-        return emit.code_buffer.items[emit.next..];
-    }
-};
-
-test "lower MI encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToMiEnc(.mov, RegisterOrMemory.reg(.rax), 0x10, emit.code());
-    try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", emit.lowered(), "mov rax, 0x10");
-    try lowerToMiEnc(.mov, RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0, .base = .r11 }), 0x10, emit.code());
-    try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", emit.lowered(), "mov dword ptr [r11 + 0], 0x10");
-    try lowerToMiEnc(.add, RegisterOrMemory.mem(.dword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -8)),
-        .base = .rdx,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings("\x81\x42\xF8\x10\x00\x00\x00", emit.lowered(), "add dword ptr [rdx - 8], 0x10");
-    try lowerToMiEnc(.sub, RegisterOrMemory.mem(.dword_ptr, .{
-        .disp = 0x10000000,
-        .base = .r11,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings(
-        "\x41\x81\xab\x00\x00\x00\x10\x10\x00\x00\x00",
-        emit.lowered(),
-        "sub dword ptr [r11 + 0x10000000], 0x10",
-    );
-    try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0x10000000 }), 0x10, emit.code());
-    try expectEqualHexStrings(
-        "\x81\x24\x25\x00\x00\x00\x10\x10\x00\x00\x00",
-        emit.lowered(),
-        "and dword ptr [ds:0x10000000], 0x10",
-    );
-    try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.dword_ptr, .{
-        .disp = 0x10000000,
-        .base = .r12,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings(
-        "\x41\x81\xA4\x24\x00\x00\x00\x10\x10\x00\x00\x00",
-        emit.lowered(),
-        "and dword ptr [r12 + 0x10000000], 0x10",
-    );
-    try lowerToMiEnc(.mov, RegisterOrMemory.rip(.qword_ptr, 0x10), 0x10, emit.code());
-    try expectEqualHexStrings(
-        "\x48\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00",
-        emit.lowered(),
-        "mov qword ptr [rip + 0x10], 0x10",
-    );
-    try lowerToMiEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -8)),
-        .base = .rbp,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings(
-        "\x48\xc7\x45\xf8\x10\x00\x00\x00",
-        emit.lowered(),
-        "mov qword ptr [rbp - 8], 0x10",
-    );
-    try lowerToMiEnc(.mov, RegisterOrMemory.mem(.word_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -2)),
-        .base = .rbp,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings("\x66\xC7\x45\xFE\x10\x00", emit.lowered(), "mov word ptr [rbp - 2], 0x10");
-    try lowerToMiEnc(.mov, RegisterOrMemory.mem(.byte_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -1)),
-        .base = .rbp,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings("\xC6\x45\xFF\x10", emit.lowered(), "mov byte ptr [rbp - 1], 0x10");
-    try lowerToMiEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = 0x10000000,
-        .scale_index = .{
-            .scale = 1,
-            .index = .rcx,
-        },
-    }), 0x10, emit.code());
-    try expectEqualHexStrings(
-        "\x48\xC7\x04\x4D\x00\x00\x00\x10\x10\x00\x00\x00",
-        emit.lowered(),
-        "mov qword ptr [rcx*2 + 0x10000000], 0x10",
-    );
-
-    try lowerToMiImm8Enc(.add, RegisterOrMemory.reg(.rax), 0x10, emit.code());
-    try expectEqualHexStrings("\x48\x83\xC0\x10", emit.lowered(), "add rax, 0x10");
-}
-
-test "lower RM encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToRmEnc(.mov, .rax, RegisterOrMemory.reg(.rbx), emit.code());
-    try expectEqualHexStrings("\x48\x8b\xc3", emit.lowered(), "mov rax, rbx");
-    try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0, .base = .r11 }), emit.code());
-    try expectEqualHexStrings("\x49\x8b\x03", emit.lowered(), "mov rax, qword ptr [r11 + 0]");
-    try lowerToRmEnc(.add, .r11, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10000000 }), emit.code());
-    try expectEqualHexStrings(
-        "\x4C\x03\x1C\x25\x00\x00\x00\x10",
-        emit.lowered(),
-        "add r11, qword ptr [ds:0x10000000]",
-    );
-    try lowerToRmEnc(.add, .r12b, RegisterOrMemory.mem(.byte_ptr, .{ .disp = 0x10000000 }), emit.code());
-    try expectEqualHexStrings(
-        "\x44\x02\x24\x25\x00\x00\x00\x10",
-        emit.lowered(),
-        "add r11b, byte ptr [ds:0x10000000]",
-    );
-    try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = 0x10000000,
-        .base = .r13,
-    }), emit.code());
-    try expectEqualHexStrings(
-        "\x4D\x2B\x9D\x00\x00\x00\x10",
-        emit.lowered(),
-        "sub r11, qword ptr [r13 + 0x10000000]",
-    );
-    try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = 0x10000000,
-        .base = .r12,
-    }), emit.code());
-    try expectEqualHexStrings(
-        "\x4D\x2B\x9C\x24\x00\x00\x00\x10",
-        emit.lowered(),
-        "sub r11, qword ptr [r12 + 0x10000000]",
-    );
-    try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -4)),
-        .base = .rbp,
-    }), emit.code());
-    try expectEqualHexStrings("\x48\x8B\x45\xFC", emit.lowered(), "mov rax, qword ptr [rbp - 4]");
-    try lowerToRmEnc(.lea, .rax, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code());
-    try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", emit.lowered(), "lea rax, [rip + 0x10]");
-    try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -8)),
-        .base = .rbp,
-        .scale_index = .{
-            .scale = 0,
-            .index = .rcx,
-        },
-    }), emit.code());
-    try expectEqualHexStrings("\x48\x8B\x44\x0D\xF8", emit.lowered(), "mov rax, qword ptr [rbp + rcx*1 - 8]");
-    try lowerToRmEnc(.mov, .eax, RegisterOrMemory.mem(.dword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -4)),
-        .base = .rbp,
-        .scale_index = .{
-            .scale = 2,
-            .index = .rdx,
-        },
-    }), emit.code());
-    try expectEqualHexStrings("\x8B\x44\x95\xFC", emit.lowered(), "mov eax, dword ptr [rbp + rdx*4 - 4]");
-    try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -8)),
-        .base = .rbp,
-        .scale_index = .{
-            .scale = 3,
-            .index = .rcx,
-        },
-    }), emit.code());
-    try expectEqualHexStrings("\x48\x8B\x44\xCD\xF8", emit.lowered(), "mov rax, qword ptr [rbp + rcx*8 - 8]");
-    try lowerToRmEnc(.mov, .r8b, RegisterOrMemory.mem(.byte_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -24)),
-        .base = .rsi,
-        .scale_index = .{
-            .scale = 0,
-            .index = .rcx,
-        },
-    }), emit.code());
-    try expectEqualHexStrings("\x44\x8A\x44\x0E\xE8", emit.lowered(), "mov r8b, byte ptr [rsi + rcx*1 - 24]");
-    try lowerToRmEnc(.lea, .rsi, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = 0,
-        .base = .rbp,
-        .scale_index = .{
-            .scale = 0,
-            .index = .rcx,
-        },
-    }), emit.code());
-    try expectEqualHexStrings("\x48\x8D\x74\x0D\x00", emit.lowered(), "lea rsi, qword ptr [rbp + rcx*1 + 0]");
-}
-
-test "lower MR encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToMrEnc(.mov, RegisterOrMemory.reg(.rax), .rbx, emit.code());
-    try expectEqualHexStrings("\x48\x89\xd8", emit.lowered(), "mov rax, rbx");
-    try lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -4)),
-        .base = .rbp,
-    }), .r11, emit.code());
-    try expectEqualHexStrings("\x4c\x89\x5d\xfc", emit.lowered(), "mov qword ptr [rbp - 4], r11");
-    try lowerToMrEnc(.add, RegisterOrMemory.mem(.byte_ptr, .{ .disp = 0x10000000 }), .r12b, emit.code());
-    try expectEqualHexStrings(
-        "\x44\x00\x24\x25\x00\x00\x00\x10",
-        emit.lowered(),
-        "add byte ptr [ds:0x10000000], r12b",
-    );
-    try lowerToMrEnc(.add, RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0x10000000 }), .r12d, emit.code());
-    try expectEqualHexStrings(
-        "\x44\x01\x24\x25\x00\x00\x00\x10",
-        emit.lowered(),
-        "add dword ptr [ds:0x10000000], r12d",
-    );
-    try lowerToMrEnc(.sub, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = 0x10000000,
-        .base = .r11,
-    }), .r12, emit.code());
-    try expectEqualHexStrings(
-        "\x4D\x29\xA3\x00\x00\x00\x10",
-        emit.lowered(),
-        "sub qword ptr [r11 + 0x10000000], r12",
-    );
-    try lowerToMrEnc(.mov, RegisterOrMemory.rip(.qword_ptr, 0x10), .r12, emit.code());
-    try expectEqualHexStrings("\x4C\x89\x25\x10\x00\x00\x00", emit.lowered(), "mov qword ptr [rip + 0x10], r12");
-}
-
-test "lower OI encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToOiEnc(.mov, .rax, 0x1000000000000000, emit.code());
-    try expectEqualHexStrings(
-        "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10",
-        emit.lowered(),
-        "movabs rax, 0x1000000000000000",
-    );
-    try lowerToOiEnc(.mov, .r11, 0x1000000000000000, emit.code());
-    try expectEqualHexStrings(
-        "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10",
-        emit.lowered(),
-        "movabs r11, 0x1000000000000000",
-    );
-    try lowerToOiEnc(.mov, .r11d, 0x10000000, emit.code());
-    try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", emit.lowered(), "mov r11d, 0x10000000");
-    try lowerToOiEnc(.mov, .r11w, 0x1000, emit.code());
-    try expectEqualHexStrings("\x66\x41\xBB\x00\x10", emit.lowered(), "mov r11w, 0x1000");
-    try lowerToOiEnc(.mov, .r11b, 0x10, emit.code());
-    try expectEqualHexStrings("\x41\xB3\x10", emit.lowered(), "mov r11b, 0x10");
-}
-
-test "lower FD/TD encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToFdEnc(.mov, .rax, 0x1000000000000000, emit.code());
-    try expectEqualHexStrings(
-        "\x48\xa1\x00\x00\x00\x00\x00\x00\x00\x10",
-        emit.lowered(),
-        "mov rax, ds:0x1000000000000000",
-    );
-    try lowerToFdEnc(.mov, .eax, 0x10000000, emit.code());
-    try expectEqualHexStrings("\xa1\x00\x00\x00\x10", emit.lowered(), "mov eax, ds:0x10000000");
-    try lowerToFdEnc(.mov, .ax, 0x1000, emit.code());
-    try expectEqualHexStrings("\x66\xa1\x00\x10", emit.lowered(), "mov ax, ds:0x1000");
-    try lowerToFdEnc(.mov, .al, 0x10, emit.code());
-    try expectEqualHexStrings("\xa0\x10", emit.lowered(), "mov al, ds:0x10");
-}
-
-test "lower M encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12), emit.code());
-    try expectEqualHexStrings("\x41\xFF\xE4", emit.lowered(), "jmp r12");
-    try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12w), emit.code());
-    try expectEqualHexStrings("\x66\x41\xFF\xE4", emit.lowered(), "jmp r12w");
-    try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0, .base = .r12 }), emit.code());
-    try expectEqualHexStrings("\x41\xFF\x24\x24", emit.lowered(), "jmp qword ptr [r12]");
-    try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.word_ptr, .{ .disp = 0, .base = .r12 }), emit.code());
-    try expectEqualHexStrings("\x66\x41\xFF\x24\x24", emit.lowered(), "jmp word ptr [r12]");
-    try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10, .base = .r12 }), emit.code());
-    try expectEqualHexStrings("\x41\xFF\x64\x24\x10", emit.lowered(), "jmp qword ptr [r12 + 0x10]");
-    try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = 0x1000,
-        .base = .r12,
-    }), emit.code());
-    try expectEqualHexStrings(
-        "\x41\xFF\xA4\x24\x00\x10\x00\x00",
-        emit.lowered(),
-        "jmp qword ptr [r12 + 0x1000]",
-    );
-    try lowerToMEnc(.jmp_near, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code());
-    try expectEqualHexStrings("\xFF\x25\x10\x00\x00\x00", emit.lowered(), "jmp qword ptr [rip + 0x10]");
-    try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10 }), emit.code());
-    try expectEqualHexStrings("\xFF\x24\x25\x10\x00\x00\x00", emit.lowered(), "jmp qword ptr [ds:0x10]");
-    try lowerToMEnc(.seta, RegisterOrMemory.reg(.r11b), emit.code());
-    try expectEqualHexStrings("\x41\x0F\x97\xC3", emit.lowered(), "seta r11b");
-    try lowerToMEnc(.idiv, RegisterOrMemory.reg(.rax), emit.code());
-    try expectEqualHexStrings("\x48\xF7\xF8", emit.lowered(), "idiv rax");
-    try lowerToMEnc(.imul, RegisterOrMemory.reg(.al), emit.code());
-    try expectEqualHexStrings("\xF6\xE8", emit.lowered(), "imul al");
-}
-
-test "lower M1 and MC encodings" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12), emit.code());
-    try expectEqualHexStrings("\x49\xD1\xE4", emit.lowered(), "sal r12, 1");
-    try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12d), emit.code());
-    try expectEqualHexStrings("\x41\xD1\xE4", emit.lowered(), "sal r12d, 1");
-    try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12w), emit.code());
-    try expectEqualHexStrings("\x66\x41\xD1\xE4", emit.lowered(), "sal r12w, 1");
-    try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12b), emit.code());
-    try expectEqualHexStrings("\x41\xD0\xE4", emit.lowered(), "sal r12b, 1");
-    try lowerToM1Enc(.sal, RegisterOrMemory.reg(.rax), emit.code());
-    try expectEqualHexStrings("\x48\xD1\xE0", emit.lowered(), "sal rax, 1");
-    try lowerToM1Enc(.sal, RegisterOrMemory.reg(.eax), emit.code());
-    try expectEqualHexStrings("\xD1\xE0", emit.lowered(), "sal eax, 1");
-    try lowerToM1Enc(.sal, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -0x10)),
-        .base = .rbp,
-    }), emit.code());
-    try expectEqualHexStrings("\x48\xD1\x65\xF0", emit.lowered(), "sal qword ptr [rbp - 0x10], 1");
-    try lowerToM1Enc(.sal, RegisterOrMemory.mem(.dword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -0x10)),
-        .base = .rbp,
-    }), emit.code());
-    try expectEqualHexStrings("\xD1\x65\xF0", emit.lowered(), "sal dword ptr [rbp - 0x10], 1");
-
-    try lowerToMcEnc(.shr, RegisterOrMemory.reg(.r12), emit.code());
-    try expectEqualHexStrings("\x49\xD3\xEC", emit.lowered(), "shr r12, cl");
-    try lowerToMcEnc(.shr, RegisterOrMemory.reg(.rax), emit.code());
-    try expectEqualHexStrings("\x48\xD3\xE8", emit.lowered(), "shr rax, cl");
-
-    try lowerToMcEnc(.sar, RegisterOrMemory.reg(.rsi), emit.code());
-    try expectEqualHexStrings("\x48\xD3\xFE", emit.lowered(), "sar rsi, cl");
-}
-
-test "lower O encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToOEnc(.pop, .r12, emit.code());
-    try expectEqualHexStrings("\x41\x5c", emit.lowered(), "pop r12");
-    try lowerToOEnc(.push, .r12w, emit.code());
-    try expectEqualHexStrings("\x66\x41\x54", emit.lowered(), "push r12w");
-}
-
-test "lower RMI encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToRmiEnc(.imul, .rax, RegisterOrMemory.mem(.qword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -8)),
-        .base = .rbp,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings(
-        "\x48\x69\x45\xF8\x10\x00\x00\x00",
-        emit.lowered(),
-        "imul rax, qword ptr [rbp - 8], 0x10",
-    );
-    try lowerToRmiEnc(.imul, .eax, RegisterOrMemory.mem(.dword_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -4)),
-        .base = .rbp,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings("\x69\x45\xFC\x10\x00\x00\x00", emit.lowered(), "imul eax, dword ptr [rbp - 4], 0x10");
-    try lowerToRmiEnc(.imul, .ax, RegisterOrMemory.mem(.word_ptr, .{
-        .disp = @bitCast(u32, @as(i32, -2)),
-        .base = .rbp,
-    }), 0x10, emit.code());
-    try expectEqualHexStrings("\x66\x69\x45\xFE\x10\x00", emit.lowered(), "imul ax, word ptr [rbp - 2], 0x10");
-    try lowerToRmiEnc(.imul, .r12, RegisterOrMemory.reg(.r12), 0x10, emit.code());
-    try expectEqualHexStrings("\x4D\x69\xE4\x10\x00\x00\x00", emit.lowered(), "imul r12, r12, 0x10");
-    try lowerToRmiEnc(.imul, .r12w, RegisterOrMemory.reg(.r12w), 0x10, emit.code());
-    try expectEqualHexStrings("\x66\x45\x69\xE4\x10\x00", emit.lowered(), "imul r12w, r12w, 0x10");
-}
-
-test "lower MV encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToMvEnc(.vmovsd, RegisterOrMemory.rip(.qword_ptr, 0x10), .xmm1, emit.code());
-    try expectEqualHexStrings(
-        "\xC5\xFB\x11\x0D\x10\x00\x00\x00",
-        emit.lowered(),
-        "vmovsd qword ptr [rip + 0x10], xmm1",
-    );
-}
-
-test "lower VM encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToVmEnc(.vmovsd, .xmm1, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code());
-    try expectEqualHexStrings(
-        "\xC5\xFB\x10\x0D\x10\x00\x00\x00",
-        emit.lowered(),
-        "vmovsd xmm1, qword ptr [rip + 0x10]",
-    );
-}
-
-test "lower to RVM encoding" {
-    var emit = TestEmit.init();
-    defer emit.deinit();
-    try lowerToRvmEnc(.vaddsd, .xmm0, .xmm1, RegisterOrMemory.reg(.xmm2), emit.code());
-    try expectEqualHexStrings("\xC5\xF3\x58\xC2", emit.lowered(), "vaddsd xmm0, xmm1, xmm2");
-    try lowerToRvmEnc(.vaddsd, .xmm0, .xmm0, RegisterOrMemory.reg(.xmm1), emit.code());
-    try expectEqualHexStrings("\xC5\xFB\x58\xC1", emit.lowered(), "vaddsd xmm0, xmm0, xmm1");
-}
src/arch/x86_64/encoder.zig
@@ -0,0 +1,794 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const math = std.math;
+
+const bits = @import("bits.zig");
+const Encoding = @import("Encoding.zig");
+const Memory = bits.Memory;
+const Moffs = bits.Moffs;
+const PtrSize = bits.PtrSize;
+const Register = bits.Register;
+
+pub const Instruction = struct {
+    op1: Operand = .none,
+    op2: Operand = .none,
+    op3: Operand = .none,
+    op4: Operand = .none,
+    encoding: Encoding,
+
+    pub const Mnemonic = Encoding.Mnemonic;
+
+    pub const Operand = union(enum) {
+        none,
+        reg: Register,
+        mem: Memory,
+        imm: i64,
+
+        /// Returns the bitsize of the operand.
+        /// Asserts the operand is either register or memory.
+        pub fn size(op: Operand) u64 {
+            return switch (op) {
+                .none => unreachable,
+                .reg => |reg| reg.size(),
+                .mem => |mem| mem.size(),
+                .imm => unreachable,
+            };
+        }
+
+        /// Returns true if the operand is a segment register.
+        /// Asserts the operand is either register or memory.
+        pub fn isSegmentRegister(op: Operand) bool {
+            return switch (op) {
+                .none => unreachable,
+                .reg => |reg| reg.class() == .segment,
+                .mem => |mem| mem.isSegmentRegister(),
+                .imm => unreachable,
+            };
+        }
+
+        pub fn fmtPrint(op: Operand, enc_op: Encoding.Op, writer: anytype) !void {
+            switch (op) {
+                .none => {},
+                .reg => |reg| try writer.writeAll(@tagName(reg)),
+                .mem => |mem| switch (mem) {
+                    .rip => |rip| {
+                        try writer.print("{s} ptr [rip", .{@tagName(rip.ptr_size)});
+                        if (rip.disp != 0) {
+                            const sign_bit = if (sign(rip.disp) < 0) "-" else "+";
+                            const disp_abs = try std.math.absInt(rip.disp);
+                            try writer.print(" {s} 0x{x}", .{ sign_bit, disp_abs });
+                        }
+                        try writer.writeByte(']');
+                    },
+                    .sib => |sib| {
+                        try writer.print("{s} ptr ", .{@tagName(sib.ptr_size)});
+
+                        if (mem.isSegmentRegister()) {
+                            return writer.print("{s}:0x{x}", .{ @tagName(sib.base.?), sib.disp });
+                        }
+
+                        try writer.writeByte('[');
+
+                        if (sib.base) |base| {
+                            try writer.print("{s}", .{@tagName(base)});
+                        }
+                        if (sib.scale_index) |si| {
+                            if (sib.base != null) {
+                                try writer.writeAll(" + ");
+                            }
+                            try writer.print("{s} * {d}", .{ @tagName(si.index), si.scale });
+                        }
+                        if (sib.disp != 0) {
+                            if (sib.base != null or sib.scale_index != null) {
+                                try writer.writeByte(' ');
+                            }
+                            try writer.writeByte(if (sign(sib.disp) < 0) '-' else '+');
+                            const disp_abs = try std.math.absInt(sib.disp);
+                            try writer.print(" 0x{x}", .{disp_abs});
+                        }
+
+                        try writer.writeByte(']');
+                    },
+                    .moffs => |moffs| try writer.print("{s}:0x{x}", .{ @tagName(moffs.seg), moffs.offset }),
+                },
+                .imm => |imm| {
+                    if (enc_op == .imm64) {
+                        return writer.print("0x{x}", .{@bitCast(u64, imm)});
+                    }
+                    const imm_abs = try std.math.absInt(imm);
+                    if (sign(imm) < 0) {
+                        try writer.writeByte('-');
+                    }
+                    try writer.print("0x{x}", .{imm_abs});
+                },
+            }
+        }
+    };
+
+    pub fn new(mnemonic: Mnemonic, args: struct {
+        op1: Operand = .none,
+        op2: Operand = .none,
+        op3: Operand = .none,
+        op4: Operand = .none,
+    }) !Instruction {
+        const encoding = Encoding.findByMnemonic(mnemonic, .{
+            .op1 = args.op1,
+            .op2 = args.op2,
+            .op3 = args.op3,
+            .op4 = args.op4,
+        }) orelse return error.InvalidInstruction;
+        std.log.debug("{}", .{encoding});
+        return .{
+            .op1 = args.op1,
+            .op2 = args.op2,
+            .op3 = args.op3,
+            .op4 = args.op4,
+            .encoding = encoding,
+        };
+    }
+
+    pub fn fmtPrint(inst: Instruction, writer: anytype) !void {
+        try writer.print("{s}", .{@tagName(inst.encoding.mnemonic)});
+        const ops = [_]struct { Operand, Encoding.Op }{
+            .{ inst.op1, inst.encoding.op1 },
+            .{ inst.op2, inst.encoding.op2 },
+            .{ inst.op3, inst.encoding.op3 },
+            .{ inst.op4, inst.encoding.op4 },
+        };
+        for (&ops, 0..) |op, i| {
+            if (op[0] == .none) break;
+            if (i > 0) {
+                try writer.writeByte(',');
+            }
+            try writer.writeByte(' ');
+            try op[0].fmtPrint(op[1], writer);
+        }
+    }
+
+    pub fn encode(inst: Instruction, writer: anytype) !void {
+        const encoder = Encoder(@TypeOf(writer)){ .writer = writer };
+        const encoding = inst.encoding;
+
+        try inst.encodeLegacyPrefixes(encoder);
+        try inst.encodeMandatoryPrefix(encoder);
+        try inst.encodeRexPrefix(encoder);
+        try inst.encodeOpcode(encoder);
+
+        switch (encoding.op_en) {
+            .np, .o => {},
+            .i, .d => try encodeImm(inst.op1.imm, encoding.op1, encoder),
+            .zi, .oi => try encodeImm(inst.op2.imm, encoding.op2, encoder),
+            .fd => try encoder.imm64(inst.op2.mem.moffs.offset),
+            .td => try encoder.imm64(inst.op1.mem.moffs.offset),
+            else => {
+                const mem_op = switch (encoding.op_en) {
+                    .m, .mi, .m1, .mc, .mr => inst.op1,
+                    .rm, .rmi => inst.op2,
+                    else => unreachable,
+                };
+                switch (mem_op) {
+                    .reg => |reg| {
+                        const rm = switch (encoding.op_en) {
+                            .m, .mi, .m1, .mc => encoding.modRmExt(),
+                            .mr => inst.op2.reg.lowEnc(),
+                            .rm, .rmi => inst.op1.reg.lowEnc(),
+                            else => unreachable,
+                        };
+                        try encoder.modRm_direct(rm, reg.lowEnc());
+                    },
+                    .mem => |mem| {
+                        const op = switch (encoding.op_en) {
+                            .m, .mi, .m1, .mc => .none,
+                            .mr => inst.op2,
+                            .rm, .rmi => inst.op1,
+                            else => unreachable,
+                        };
+                        try encodeMemory(encoding, mem, op, encoder);
+                    },
+                    else => unreachable,
+                }
+
+                switch (encoding.op_en) {
+                    .mi => try encodeImm(inst.op2.imm, encoding.op2, encoder),
+                    .rmi => try encodeImm(inst.op3.imm, encoding.op3, encoder),
+                    else => {},
+                }
+            },
+        }
+    }
+
+    fn encodeOpcode(inst: Instruction, encoder: anytype) !void {
+        const opcode = inst.encoding.opcode();
+        switch (inst.encoding.op_en) {
+            .o, .oi => try encoder.opcode_withReg(opcode[0], inst.op1.reg.lowEnc()),
+            else => {
+                const index: usize = if (inst.encoding.mandatoryPrefix()) |_| 1 else 0;
+                for (opcode[index..]) |byte| {
+                    try encoder.opcode_1byte(byte);
+                }
+            },
+        }
+    }
+
+    fn encodeLegacyPrefixes(inst: Instruction, encoder: anytype) !void {
+        const enc = inst.encoding;
+        const op_en = enc.op_en;
+
+        var legacy = LegacyPrefixes{};
+        if (enc.mode == .none) {
+            const bit_size = enc.operandSize();
+            if (bit_size == 16) {
+                legacy.set16BitOverride();
+            }
+        }
+
+        const segment_override: ?Register = switch (op_en) {
+            .i, .zi, .o, .oi, .d, .np => null,
+            .fd => inst.op2.mem.base().?,
+            .td => inst.op1.mem.base().?,
+            .rm, .rmi => if (inst.op2.isSegmentRegister()) blk: {
+                break :blk switch (inst.op2) {
+                    .reg => |r| r,
+                    .mem => |m| m.base().?,
+                    else => unreachable,
+                };
+            } else null,
+            .m, .mi, .m1, .mc, .mr => if (inst.op1.isSegmentRegister()) blk: {
+                break :blk switch (inst.op1) {
+                    .reg => |r| r,
+                    .mem => |m| m.base().?,
+                    else => unreachable,
+                };
+            } else null,
+        };
+        if (segment_override) |seg| {
+            legacy.setSegmentOverride(seg);
+        }
+
+        try encoder.legacyPrefixes(legacy);
+    }
+
+    fn encodeRexPrefix(inst: Instruction, encoder: anytype) !void {
+        const op_en = inst.encoding.op_en;
+
+        // Check if we need REX and can actually encode it
+        const is_rex_invalid = for (&[_]Operand{ inst.op1, inst.op2, inst.op3, inst.op4 }) |op| switch (op) {
+            .reg => |r| if (r.isRexInvalid()) break true,
+            else => {},
+        } else false;
+
+        var rex = Rex{};
+        rex.w = inst.encoding.mode == .long;
+
+        switch (op_en) {
+            .np, .i, .zi, .fd, .td, .d => {},
+            .o, .oi => {
+                rex.b = inst.op1.reg.isExtended();
+            },
+            .m, .mi, .m1, .mc, .mr, .rm, .rmi => {
+                const r_op = switch (op_en) {
+                    .rm, .rmi => inst.op1,
+                    .mr => inst.op2,
+                    else => null,
+                };
+                if (r_op) |op| {
+                    rex.r = op.reg.isExtended();
+                }
+
+                const b_x_op = switch (op_en) {
+                    .rm, .rmi => inst.op2,
+                    .m, .mi, .m1, .mc, .mr => inst.op1,
+                    else => unreachable,
+                };
+                switch (b_x_op) {
+                    .reg => |r| {
+                        rex.b = r.isExtended();
+                    },
+                    .mem => |mem| {
+                        rex.b = if (mem.base()) |base| base.isExtended() else false;
+                        rex.x = if (mem.scaleIndex()) |si| si.index.isExtended() else false;
+                    },
+                    else => unreachable,
+                }
+            },
+        }
+
+        if (rex.isSet() and is_rex_invalid) return error.CannotEncode;
+
+        try encoder.rex(rex);
+    }
+
+    fn encodeMandatoryPrefix(inst: Instruction, encoder: anytype) !void {
+        const prefix = inst.encoding.mandatoryPrefix() orelse return;
+        try encoder.opcode_1byte(prefix);
+    }
+
+    fn encodeMemory(encoding: Encoding, mem: Memory, operand: Operand, encoder: anytype) !void {
+        const operand_enc = switch (operand) {
+            .reg => |reg| reg.lowEnc(),
+            .none => encoding.modRmExt(),
+            else => unreachable,
+        };
+
+        switch (mem) {
+            .moffs => unreachable,
+            .sib => |sib| {
+                if (sib.base) |base| {
+                    if (base.class() == .segment) {
+                        // TODO audit this wrt SIB
+                        try encoder.modRm_SIBDisp0(operand_enc);
+                        if (sib.scale_index) |si| {
+                            const scale = math.log2_int(u4, si.scale);
+                            try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc());
+                        } else {
+                            try encoder.sib_disp32();
+                        }
+                        try encoder.disp32(sib.disp);
+                    } else {
+                        assert(base.class() == .general_purpose);
+                        const dst = base.lowEnc();
+                        const src = operand_enc;
+                        if (dst == 4 or sib.scale_index != null) {
+                            if (sib.disp == 0 and dst != 5) {
+                                try encoder.modRm_SIBDisp0(src);
+                                if (sib.scale_index) |si| {
+                                    const scale = math.log2_int(u4, si.scale);
+                                    try encoder.sib_scaleIndexBase(scale, si.index.lowEnc(), dst);
+                                } else {
+                                    try encoder.sib_base(dst);
+                                }
+                            } else if (math.cast(i8, sib.disp)) |_| {
+                                try encoder.modRm_SIBDisp8(src);
+                                if (sib.scale_index) |si| {
+                                    const scale = math.log2_int(u4, si.scale);
+                                    try encoder.sib_scaleIndexBaseDisp8(scale, si.index.lowEnc(), dst);
+                                } else {
+                                    try encoder.sib_baseDisp8(dst);
+                                }
+                                try encoder.disp8(@truncate(i8, sib.disp));
+                            } else {
+                                try encoder.modRm_SIBDisp32(src);
+                                if (sib.scale_index) |si| {
+                                    const scale = math.log2_int(u4, si.scale);
+                                    try encoder.sib_scaleIndexBaseDisp32(scale, si.index.lowEnc(), dst);
+                                } else {
+                                    try encoder.sib_baseDisp32(dst);
+                                }
+                                try encoder.disp32(sib.disp);
+                            }
+                        } else {
+                            if (sib.disp == 0 and dst != 5) {
+                                try encoder.modRm_indirectDisp0(src, dst);
+                            } else if (math.cast(i8, sib.disp)) |_| {
+                                try encoder.modRm_indirectDisp8(src, dst);
+                                try encoder.disp8(@truncate(i8, sib.disp));
+                            } else {
+                                try encoder.modRm_indirectDisp32(src, dst);
+                                try encoder.disp32(sib.disp);
+                            }
+                        }
+                    }
+                } else {
+                    try encoder.modRm_SIBDisp0(operand_enc);
+                    if (sib.scale_index) |si| {
+                        const scale = math.log2_int(u4, si.scale);
+                        try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc());
+                    } else {
+                        try encoder.sib_disp32();
+                    }
+                    try encoder.disp32(sib.disp);
+                }
+            },
+            .rip => |rip| {
+                try encoder.modRm_RIPDisp32(operand_enc);
+                try encoder.disp32(rip.disp);
+            },
+        }
+    }
+
+    fn encodeImm(imm: i64, kind: Encoding.Op, encoder: anytype) !void {
+        switch (kind) {
+            .imm8, .rel8 => try encoder.imm8(@truncate(i8, imm)),
+            .imm16, .rel16 => try encoder.imm16(@truncate(i16, imm)),
+            .imm32, .rel32 => try encoder.imm32(@truncate(i32, imm)),
+            .imm64 => try encoder.imm64(@bitCast(u64, imm)),
+            else => unreachable,
+        }
+    }
+};
+
+inline fn sign(i: anytype) @TypeOf(i) {
+    return @as(@TypeOf(i), @boolToInt(i > 0)) - @boolToInt(i < 0);
+}
+
+pub const LegacyPrefixes = packed struct {
+    /// LOCK
+    prefix_f0: bool = false,
+    /// REPNZ, REPNE, REP, Scalar Double-precision
+    prefix_f2: bool = false,
+    /// REPZ, REPE, REP, Scalar Single-precision
+    prefix_f3: bool = false,
+
+    /// CS segment override or Branch not taken
+    prefix_2e: bool = false,
+    /// SS segment override
+    prefix_36: bool = false,
+    /// ES segment override
+    prefix_26: bool = false,
+    /// FS segment override
+    prefix_64: bool = false,
+    /// GS segment override
+    prefix_65: bool = false,
+
+    /// Branch taken
+    prefix_3e: bool = false,
+
+    /// Address size override (enables 16 bit address size)
+    prefix_67: bool = false,
+
+    /// Operand size override (enables 16 bit operation)
+    prefix_66: bool = false,
+
+    padding: u5 = 0,
+
+    pub fn setSegmentOverride(self: *LegacyPrefixes, reg: Register) void {
+        assert(reg.class() == .segment);
+        switch (reg) {
+            .cs => self.prefix_2e = true,
+            .ss => self.prefix_36 = true,
+            .es => self.prefix_26 = true,
+            .fs => self.prefix_64 = true,
+            .gs => self.prefix_65 = true,
+            .ds => {},
+            else => unreachable,
+        }
+    }
+
+    pub fn set16BitOverride(self: *LegacyPrefixes) void {
+        self.prefix_66 = true;
+    }
+};
+
+fn Encoder(comptime T: type) type {
+    return struct {
+        writer: T,
+
+        const Self = @This();
+
+        // --------
+        // Prefixes
+        // --------
+
+        /// Encodes legacy prefixes
+        pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) !void {
+            if (@bitCast(u16, prefixes) != 0) {
+                // Hopefully this path isn't taken very often, so we'll do it the slow way for now
+
+                // LOCK
+                if (prefixes.prefix_f0) try self.writer.writeByte(0xf0);
+                // REPNZ, REPNE, REP, Scalar Double-precision
+                if (prefixes.prefix_f2) try self.writer.writeByte(0xf2);
+                // REPZ, REPE, REP, Scalar Single-precision
+                if (prefixes.prefix_f3) try self.writer.writeByte(0xf3);
+
+                // CS segment override or Branch not taken
+                if (prefixes.prefix_2e) try self.writer.writeByte(0x2e);
+                // DS segment override
+                if (prefixes.prefix_36) try self.writer.writeByte(0x36);
+                // ES segment override
+                if (prefixes.prefix_26) try self.writer.writeByte(0x26);
+                // FS segment override
+                if (prefixes.prefix_64) try self.writer.writeByte(0x64);
+                // GS segment override
+                if (prefixes.prefix_65) try self.writer.writeByte(0x65);
+
+                // Branch taken
+                if (prefixes.prefix_3e) try self.writer.writeByte(0x3e);
+
+                // Operand size override
+                if (prefixes.prefix_66) try self.writer.writeByte(0x66);
+
+                // Address size override
+                if (prefixes.prefix_67) try self.writer.writeByte(0x67);
+            }
+        }
+
+        /// Use 16 bit operand size
+        ///
+        /// Note that this flag is overridden by REX.W, if both are present.
+        pub fn prefix16BitMode(self: Self) !void {
+            try self.writer.writeByte(0x66);
+        }
+
+        /// Encodes a REX prefix byte given all the fields
+        ///
+        /// Use this byte whenever you need 64 bit operation,
+        /// or one of reg, index, r/m, base, or opcode-reg might be extended.
+        ///
+        /// See struct `Rex` for a description of each field.
+        ///
+        /// Does not add a prefix byte if none of the fields are set!
+        pub fn rex(self: Self, byte: Rex) !void {
+            var value: u8 = 0b0100_0000;
+
+            if (byte.w) value |= 0b1000;
+            if (byte.r) value |= 0b0100;
+            if (byte.x) value |= 0b0010;
+            if (byte.b) value |= 0b0001;
+
+            if (value != 0b0100_0000) {
+                try self.writer.writeByte(value);
+            }
+        }
+
+        // ------
+        // Opcode
+        // ------
+
+        /// Encodes a 1 byte opcode
+        pub fn opcode_1byte(self: Self, opcode: u8) !void {
+            try self.writer.writeByte(opcode);
+        }
+
+        /// Encodes a 2 byte opcode
+        ///
+        /// e.g. IMUL has the opcode 0x0f 0xaf, so you use
+        ///
+        /// encoder.opcode_2byte(0x0f, 0xaf);
+        pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) !void {
+            try self.writer.writeAll(&.{ prefix, opcode });
+        }
+
+        /// Encodes a 3 byte opcode
+        ///
+        /// e.g. MOVSD has the opcode 0xf2 0x0f 0x10
+        ///
+        /// encoder.opcode_3byte(0xf2, 0x0f, 0x10);
+        pub fn opcode_3byte(self: Self, prefix_1: u8, prefix_2: u8, opcode: u8) !void {
+            try self.writer.writeAll(&.{ prefix_1, prefix_2, opcode });
+        }
+
+        /// Encodes a 1 byte opcode with a reg field
+        ///
+        /// Remember to add a REX prefix byte if reg is extended!
+        pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) !void {
+            assert(opcode & 0b111 == 0);
+            try self.writer.writeByte(opcode | reg);
+        }
+
+        // ------
+        // ModR/M
+        // ------
+
+        /// Construct a ModR/M byte given all the fields
+        ///
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) !void {
+            try self.writer.writeByte(@as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm);
+        }
+
+        /// Construct a ModR/M byte using direct r/m addressing
+        /// r/m effective address: r/m
+        ///
+        /// Note reg's effective address is always just reg for the ModR/M byte.
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) !void {
+            try self.modRm(0b11, reg_or_opx, rm);
+        }
+
+        /// Construct a ModR/M byte using indirect r/m addressing
+        /// r/m effective address: [r/m]
+        ///
+        /// Note reg's effective address is always just reg for the ModR/M byte.
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) !void {
+            assert(rm != 4 and rm != 5);
+            try self.modRm(0b00, reg_or_opx, rm);
+        }
+
+        /// Construct a ModR/M byte using indirect SIB addressing
+        /// r/m effective address: [SIB]
+        ///
+        /// Note reg's effective address is always just reg for the ModR/M byte.
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) !void {
+            try self.modRm(0b00, reg_or_opx, 0b100);
+        }
+
+        /// Construct a ModR/M byte using RIP-relative addressing
+        /// r/m effective address: [RIP + disp32]
+        ///
+        /// Note reg's effective address is always just reg for the ModR/M byte.
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) !void {
+            try self.modRm(0b00, reg_or_opx, 0b101);
+        }
+
+        /// Construct a ModR/M byte using indirect r/m with a 8bit displacement
+        /// r/m effective address: [r/m + disp8]
+        ///
+        /// Note reg's effective address is always just reg for the ModR/M byte.
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) !void {
+            assert(rm != 4);
+            try self.modRm(0b01, reg_or_opx, rm);
+        }
+
+        /// Construct a ModR/M byte using indirect SIB with a 8bit displacement
+        /// r/m effective address: [SIB + disp8]
+        ///
+        /// Note reg's effective address is always just reg for the ModR/M byte.
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) !void {
+            try self.modRm(0b01, reg_or_opx, 0b100);
+        }
+
+        /// Construct a ModR/M byte using indirect r/m with a 32bit displacement
+        /// r/m effective address: [r/m + disp32]
+        ///
+        /// Note reg's effective address is always just reg for the ModR/M byte.
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) !void {
+            assert(rm != 4);
+            try self.modRm(0b10, reg_or_opx, rm);
+        }
+
+        /// Construct a ModR/M byte using indirect SIB with a 32bit displacement
+        /// r/m effective address: [SIB + disp32]
+        ///
+        /// Note reg's effective address is always just reg for the ModR/M byte.
+        /// Remember to add a REX prefix byte if reg or rm are extended!
+        pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) !void {
+            try self.modRm(0b10, reg_or_opx, 0b100);
+        }
+
+        // ---
+        // SIB
+        // ---
+
+        /// Construct a SIB byte given all the fields
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib(self: Self, scale: u2, index: u3, base: u3) !void {
+            try self.writer.writeByte(@as(u8, scale) << 6 | @as(u8, index) << 3 | base);
+        }
+
+        /// Construct a SIB byte with scale * index + base, no frills.
+        /// r/m effective address: [base + scale * index]
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) !void {
+            assert(base != 5);
+
+            try self.sib(scale, index, base);
+        }
+
+        /// Construct a SIB byte with scale * index + disp32
+        /// r/m effective address: [scale * index + disp32]
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) !void {
+            // scale is actually ignored
+            // index = 4 means no index if and only if we haven't extended the register
+            // TODO enforce this
+            // base = 5 means no base, if mod == 0.
+            try self.sib(scale, index, 5);
+        }
+
+        /// Construct a SIB byte with just base
+        /// r/m effective address: [base]
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib_base(self: Self, base: u3) !void {
+            assert(base != 5);
+
+            // scale is actually ignored
+            // index = 4 means no index
+            try self.sib(0, 4, base);
+        }
+
+        /// Construct a SIB byte with just disp32
+        /// r/m effective address: [disp32]
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib_disp32(self: Self) !void {
+            // scale is actually ignored
+            // index = 4 means no index
+            // base = 5 means no base, if mod == 0.
+            try self.sib(0, 4, 5);
+        }
+
+        /// Construct a SIB byte with scale * index + base + disp8
+        /// r/m effective address: [base + scale * index + disp8]
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) !void {
+            try self.sib(scale, index, base);
+        }
+
+        /// Construct a SIB byte with base + disp8, no index
+        /// r/m effective address: [base + disp8]
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib_baseDisp8(self: Self, base: u3) !void {
+            // scale is ignored
+            // index = 4 means no index
+            try self.sib(0, 4, base);
+        }
+
+        /// Construct a SIB byte with scale * index + base + disp32
+        /// r/m effective address: [base + scale * index + disp32]
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) !void {
+            try self.sib(scale, index, base);
+        }
+
+        /// Construct a SIB byte with base + disp32, no index
+        /// r/m effective address: [base + disp32]
+        ///
+        /// Remember to add a REX prefix byte if index or base are extended!
+        pub fn sib_baseDisp32(self: Self, base: u3) !void {
+            // scale is ignored
+            // index = 4 means no index
+            try self.sib(0, 4, base);
+        }
+
+        // -------------------------
+        // Trivial (no bit fiddling)
+        // -------------------------
+
+        /// Encode an 8 bit immediate
+        ///
+        /// It is sign-extended to 64 bits by the cpu.
+        pub fn imm8(self: Self, imm: i8) !void {
+            try self.writer.writeByte(@bitCast(u8, imm));
+        }
+
+        /// Encode an 8 bit displacement
+        ///
+        /// It is sign-extended to 64 bits by the cpu.
+        pub fn disp8(self: Self, disp: i8) !void {
+            try self.writer.writeByte(@bitCast(u8, disp));
+        }
+
+        /// Encode an 16 bit immediate
+        ///
+        /// It is sign-extended to 64 bits by the cpu.
+        pub fn imm16(self: Self, imm: i16) !void {
+            try self.writer.writeIntLittle(i16, imm);
+        }
+
+        /// Encode an 32 bit immediate
+        ///
+        /// It is sign-extended to 64 bits by the cpu.
+        pub fn imm32(self: Self, imm: i32) !void {
+            try self.writer.writeIntLittle(i32, imm);
+        }
+
+        /// Encode an 32 bit displacement
+        ///
+        /// It is sign-extended to 64 bits by the cpu.
+        pub fn disp32(self: Self, disp: i32) !void {
+            try self.writer.writeIntLittle(i32, disp);
+        }
+
+        /// Encode an 64 bit immediate
+        ///
+        /// It is sign-extended to 64 bits by the cpu.
+        pub fn imm64(self: Self, imm: u64) !void {
+            try self.writer.writeIntLittle(u64, imm);
+        }
+    };
+}
+
+pub const Rex = struct {
+    w: bool = false,
+    r: bool = false,
+    x: bool = false,
+    b: bool = false,
+
+    pub fn isSet(rex: Rex) bool {
+        return rex.w or rex.r or rex.x or rex.b;
+    }
+};
src/arch/x86_64/Encoding.zig
@@ -0,0 +1,521 @@
+const Encoding = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const math = std.math;
+
+const bits = @import("bits.zig");
+const encoder = @import("encoder.zig");
+const Instruction = encoder.Instruction;
+const Register = bits.Register;
+const Rex = encoder.Rex;
+const LegacyPrefixes = encoder.LegacyPrefixes;
+
+const table = @import("encodings.zig").table;
+
+mnemonic: Mnemonic,
+op_en: OpEn,
+op1: Op,
+op2: Op,
+op3: Op,
+op4: Op,
+opc_len: u2,
+opc: [3]u8,
+modrm_ext: u3,
+mode: Mode,
+
+pub fn findByMnemonic(mnemonic: Mnemonic, args: struct {
+    op1: Instruction.Operand,
+    op2: Instruction.Operand,
+    op3: Instruction.Operand,
+    op4: Instruction.Operand,
+}) ?Encoding {
+    const input_op1 = Op.fromOperand(args.op1);
+    const input_op2 = Op.fromOperand(args.op2);
+    const input_op3 = Op.fromOperand(args.op3);
+    const input_op4 = Op.fromOperand(args.op4);
+
+    // TODO work out what is the maximum number of variants we can actually find in one swoop.
+    var candidates: [10]Encoding = undefined;
+    var count: usize = 0;
+    inline for (table) |entry| {
+        const enc = Encoding{
+            .mnemonic = entry[0],
+            .op_en = entry[1],
+            .op1 = entry[2],
+            .op2 = entry[3],
+            .op3 = entry[4],
+            .op4 = entry[5],
+            .opc_len = entry[6],
+            .opc = .{ entry[7], entry[8], entry[9] },
+            .modrm_ext = entry[10],
+            .mode = entry[11],
+        };
+        if (enc.mnemonic == mnemonic and
+            input_op1.isSubset(enc.op1, enc.mode) and
+            input_op2.isSubset(enc.op2, enc.mode) and
+            input_op3.isSubset(enc.op3, enc.mode) and
+            input_op4.isSubset(enc.op4, enc.mode))
+        {
+            candidates[count] = enc;
+            count += 1;
+        }
+    }
+
+    if (count == 0) return null;
+    if (count == 1) return candidates[0];
+
+    const EncodingLength = struct {
+        fn estimate(encoding: Encoding, params: struct {
+            op1: Instruction.Operand,
+            op2: Instruction.Operand,
+            op3: Instruction.Operand,
+            op4: Instruction.Operand,
+        }) usize {
+            var inst = Instruction{
+                .op1 = params.op1,
+                .op2 = params.op2,
+                .op3 = params.op3,
+                .op4 = params.op4,
+                .encoding = encoding,
+            };
+            var cwriter = std.io.countingWriter(std.io.null_writer);
+            inst.encode(cwriter.writer()) catch unreachable;
+            return cwriter.bytes_written;
+        }
+    };
+
+    var shortest_encoding: ?struct {
+        index: usize,
+        len: usize,
+    } = null;
+    var i: usize = 0;
+    while (i < count) : (i += 1) {
+        const len = EncodingLength.estimate(candidates[i], .{
+            .op1 = args.op1,
+            .op2 = args.op2,
+            .op3 = args.op3,
+            .op4 = args.op4,
+        });
+        const current = shortest_encoding orelse {
+            shortest_encoding = .{ .index = i, .len = len };
+            continue;
+        };
+        if (len < current.len) {
+            shortest_encoding = .{ .index = i, .len = len };
+        }
+    }
+
+    return candidates[shortest_encoding.?.index];
+}
+
+/// Returns first matching encoding by opcode.
+pub fn findByOpcode(opc: []const u8, prefixes: struct {
+    legacy: LegacyPrefixes,
+    rex: Rex,
+}, modrm_ext: ?u3) ?Encoding {
+    inline for (table) |entry| {
+        const enc = Encoding{
+            .mnemonic = entry[0],
+            .op_en = entry[1],
+            .op1 = entry[2],
+            .op2 = entry[3],
+            .op3 = entry[4],
+            .op4 = entry[5],
+            .opc_len = entry[6],
+            .opc = .{ entry[7], entry[8], entry[9] },
+            .modrm_ext = entry[10],
+            .mode = entry[11],
+        };
+        const match = match: {
+            if (modrm_ext) |ext| {
+                break :match ext == enc.modrm_ext and std.mem.eql(u8, enc.opcode(), opc);
+            }
+            break :match std.mem.eql(u8, enc.opcode(), opc);
+        };
+        if (match) {
+            if (prefixes.rex.w) {
+                switch (enc.mode) {
+                    .fpu, .sse, .sse2 => {},
+                    .long => return enc,
+                    .none => {
+                        // TODO this is a hack to allow parsing of instructions which contain
+                        // spurious prefix bytes such as
+                        // rex.W mov dil, 0x1
+                        // Here, rex.W is not needed.
+                        const rex_w_allowed = blk: {
+                            const bit_size = enc.operandSize();
+                            break :blk bit_size == 64 or bit_size == 8;
+                        };
+                        if (rex_w_allowed) return enc;
+                    },
+                }
+            } else if (prefixes.legacy.prefix_66) {
+                switch (enc.operandSize()) {
+                    16 => return enc,
+                    else => {},
+                }
+            } else {
+                if (enc.mode == .none) {
+                    switch (enc.operandSize()) {
+                        16 => {},
+                        else => return enc,
+                    }
+                }
+            }
+        }
+    }
+    return null;
+}
+
+pub fn opcode(encoding: *const Encoding) []const u8 {
+    return encoding.opc[0..encoding.opc_len];
+}
+
+pub fn mandatoryPrefix(encoding: *const Encoding) ?u8 {
+    const prefix = encoding.opc[0];
+    return switch (prefix) {
+        0x66, 0xf2, 0xf3 => prefix,
+        else => null,
+    };
+}
+
+pub fn modRmExt(encoding: Encoding) u3 {
+    return switch (encoding.op_en) {
+        .m, .mi, .m1, .mc => encoding.modrm_ext,
+        else => unreachable,
+    };
+}
+
+pub fn operandSize(encoding: Encoding) u32 {
+    if (encoding.mode == .long) return 64;
+    const bit_size: u32 = switch (encoding.op_en) {
+        .np => switch (encoding.op1) {
+            .o16 => 16,
+            .o32 => 32,
+            .o64 => 64,
+            else => 32,
+        },
+        .td => encoding.op2.size(),
+        else => encoding.op1.size(),
+    };
+    return bit_size;
+}
+
+pub fn format(
+    encoding: Encoding,
+    comptime fmt: []const u8,
+    options: std.fmt.FormatOptions,
+    writer: anytype,
+) !void {
+    _ = options;
+    _ = fmt;
+    switch (encoding.mode) {
+        .long => try writer.writeAll("REX.W + "),
+        else => {},
+    }
+
+    for (encoding.opcode()) |byte| {
+        try writer.print("{x:0>2} ", .{byte});
+    }
+
+    switch (encoding.op_en) {
+        .np, .fd, .td, .i, .zi, .d => {},
+        .o, .oi => {
+            const tag = switch (encoding.op1) {
+                .r8 => "rb",
+                .r16 => "rw",
+                .r32 => "rd",
+                .r64 => "rd",
+                else => unreachable,
+            };
+            try writer.print("+{s} ", .{tag});
+        },
+        .m, .mi, .m1, .mc => try writer.print("/{d} ", .{encoding.modRmExt()}),
+        .mr, .rm, .rmi => try writer.writeAll("/r "),
+    }
+
+    switch (encoding.op_en) {
+        .i, .d, .zi, .oi, .mi, .rmi => {
+            const op = switch (encoding.op_en) {
+                .i, .d => encoding.op1,
+                .zi, .oi, .mi => encoding.op2,
+                .rmi => encoding.op3,
+                else => unreachable,
+            };
+            const tag = switch (op) {
+                .imm8 => "ib",
+                .imm16 => "iw",
+                .imm32 => "id",
+                .imm64 => "io",
+                .rel8 => "cb",
+                .rel16 => "cw",
+                .rel32 => "cd",
+                else => unreachable,
+            };
+            try writer.print("{s} ", .{tag});
+        },
+        .np, .fd, .td, .o, .m, .m1, .mc, .mr, .rm => {},
+    }
+
+    try writer.print("{s} ", .{@tagName(encoding.mnemonic)});
+
+    const ops = &[_]Op{ encoding.op1, encoding.op2, encoding.op3, encoding.op4 };
+    for (ops) |op| switch (op) {
+        .none, .o16, .o32, .o64 => break,
+        else => try writer.print("{s} ", .{@tagName(op)}),
+    };
+
+    const op_en = switch (encoding.op_en) {
+        .zi => .i,
+        else => |op_en| op_en,
+    };
+    try writer.print("{s}", .{@tagName(op_en)});
+}
+
+pub const Mnemonic = enum {
+    // zig fmt: off
+    // General-purpose
+    adc, add, @"and",
+    call, cbw, cwde, cdqe, cwd, cdq, cqo, cmp,
+    cmova, cmovae, cmovb, cmovbe, cmovc, cmove, cmovg, cmovge, cmovl, cmovle, cmovna,
+    cmovnae, cmovnb, cmovnbe, cmovnc, cmovne, cmovng, cmovnge, cmovnl, cmovnle, cmovno,
+    cmovnp, cmovns, cmovnz, cmovo, cmovp, cmovpe, cmovpo, cmovs, cmovz,
+    div,
+    fisttp, fld,
+    idiv, imul, int3,
+    ja, jae, jb, jbe, jc, jrcxz, je, jg, jge, jl, jle, jna, jnae, jnb, jnbe,
+    jnc, jne, jng, jnge, jnl, jnle, jno, jnp, jns, jnz, jo, jp, jpe, jpo, js, jz,
+    jmp, 
+    lea,
+    mov, movsx, movsxd, movzx, mul,
+    nop,
+    @"or",
+    pop, push,
+    ret,
+    sal, sar, sbb, shl, shr, sub, syscall,
+    seta, setae, setb, setbe, setc, sete, setg, setge, setl, setle, setna, setnae,
+    setnb, setnbe, setnc, setne, setng, setnge, setnl, setnle, setno, setnp, setns,
+    setnz, seto, setp, setpe, setpo, sets, setz,
+    @"test",
+    ud2,
+    xor,
+    // SSE
+    addss,
+    cmpss,
+    movss,
+    ucomiss,
+    // SSE2
+    addsd,
+    cmpsd,
+    movq, movsd,
+    ucomisd,
+    // zig fmt: on
+};
+
+pub const OpEn = enum {
+    // zig fmt: off
+    np,
+    o, oi,
+    i, zi,
+    d, m,
+    fd, td,
+    m1, mc, mi, mr, rm, rmi,
+    // zig fmt: on
+};
+
+pub const Op = enum {
+    // zig fmt: off
+    none,
+    o16, o32, o64,
+    unity,
+    imm8, imm16, imm32, imm64,
+    al, ax, eax, rax,
+    cl,
+    r8, r16, r32, r64,
+    rm8, rm16, rm32, rm64,
+    m8, m16, m32, m64, m80,
+    rel8, rel16, rel32,
+    m,
+    moffs,
+    sreg,
+    xmm, xmm_m32, xmm_m64,
+    // zig fmt: on
+
+    pub fn fromOperand(operand: Instruction.Operand) Op {
+        switch (operand) {
+            .none => return .none,
+
+            .reg => |reg| {
+                switch (reg.class()) {
+                    .segment => return .sreg,
+                    .floating_point => return switch (reg.size()) {
+                        128 => .xmm,
+                        else => unreachable,
+                    },
+                    .general_purpose => {
+                        if (reg.to64() == .rax) return switch (reg) {
+                            .al => .al,
+                            .ax => .ax,
+                            .eax => .eax,
+                            .rax => .rax,
+                            else => unreachable,
+                        };
+                        if (reg == .cl) return .cl;
+                        return switch (reg.size()) {
+                            8 => .r8,
+                            16 => .r16,
+                            32 => .r32,
+                            64 => .r64,
+                            else => unreachable,
+                        };
+                    },
+                }
+            },
+
+            .mem => |mem| switch (mem) {
+                .moffs => return .moffs,
+                .sib, .rip => {
+                    const bit_size = mem.size();
+                    return switch (bit_size) {
+                        8 => .m8,
+                        16 => .m16,
+                        32 => .m32,
+                        64 => .m64,
+                        80 => .m80,
+                        else => unreachable,
+                    };
+                },
+            },
+
+            .imm => |imm| {
+                if (imm == 1) return .unity;
+                if (math.cast(i8, imm)) |_| return .imm8;
+                if (math.cast(i16, imm)) |_| return .imm16;
+                if (math.cast(i32, imm)) |_| return .imm32;
+                return .imm64;
+            },
+        }
+    }
+
+    pub fn size(op: Op) u32 {
+        return switch (op) {
+            .none, .o16, .o32, .o64, .moffs, .m, .sreg, .unity => unreachable,
+            .imm8, .al, .cl, .r8, .m8, .rm8, .rel8 => 8,
+            .imm16, .ax, .r16, .m16, .rm16, .rel16 => 16,
+            .imm32, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32,
+            .imm64, .rax, .r64, .m64, .rm64, .xmm_m64 => 64,
+            .m80 => 80,
+            .xmm => 128,
+        };
+    }
+
+    pub fn isRegister(op: Op) bool {
+        // zig fmt: off
+        return switch (op) {
+            .cl,
+            .al, .ax, .eax, .rax,
+            .r8, .r16, .r32, .r64,
+            .rm8, .rm16, .rm32, .rm64,
+            .xmm, .xmm_m32, .xmm_m64,
+            =>  true,
+            else => false,
+        };
+        // zig fmt: on
+    }
+
+    pub fn isImmediate(op: Op) bool {
+        // zig fmt: off
+        return switch (op) {
+            .imm8, .imm16, .imm32, .imm64, 
+            .rel8, .rel16, .rel32,
+            .unity,
+            =>  true,
+            else => false,
+        };
+        // zig fmt: on
+    }
+
+    pub fn isMemory(op: Op) bool {
+        // zig fmt: off
+        return switch (op) {
+            .rm8, .rm16, .rm32, .rm64,
+            .m8, .m16, .m32, .m64, .m80,
+            .m,
+            .xmm_m32, .xmm_m64,
+            =>  true,
+            else => false,
+        };
+        // zig fmt: on
+    }
+
+    pub fn isSegmentRegister(op: Op) bool {
+        return switch (op) {
+            .moffs, .sreg => true,
+            else => false,
+        };
+    }
+
+    pub fn isFloatingPointRegister(op: Op) bool {
+        return switch (op) {
+            .xmm, .xmm_m32, .xmm_m64 => true,
+            else => false,
+        };
+    }
+
+    /// Given an operand `op` checks if `target` is a subset for the purposes
+    /// of the encoding.
+    pub fn isSubset(op: Op, target: Op, mode: Mode) bool {
+        switch (op) {
+            .m, .o16, .o32, .o64 => unreachable,
+            .moffs, .sreg => return op == target,
+            .none => switch (target) {
+                .o16, .o32, .o64, .none => return true,
+                else => return false,
+            },
+            else => {
+                if (op.isRegister() and target.isRegister()) {
+                    switch (mode) {
+                        .sse, .sse2 => return op.isFloatingPointRegister() and target.isFloatingPointRegister(),
+                        else => switch (target) {
+                            .cl, .al, .ax, .eax, .rax => return op == target,
+                            else => return op.size() == target.size(),
+                        },
+                    }
+                }
+                if (op.isMemory() and target.isMemory()) {
+                    switch (target) {
+                        .m => return true,
+                        else => return op.size() == target.size(),
+                    }
+                }
+                if (op.isImmediate() and target.isImmediate()) {
+                    switch (target) {
+                        .imm32, .rel32 => switch (op) {
+                            .unity, .imm8, .imm16, .imm32 => return true,
+                            else => return op == target,
+                        },
+                        .imm16, .rel16 => switch (op) {
+                            .unity, .imm8, .imm16 => return true,
+                            else => return op == target,
+                        },
+                        .imm8, .rel8 => switch (op) {
+                            .unity, .imm8 => return true,
+                            else => return op == target,
+                        },
+                        else => return op == target,
+                    }
+                }
+                return false;
+            },
+        }
+    }
+};
+
+pub const Mode = enum {
+    none,
+    fpu,
+    long,
+    sse,
+    sse2,
+};
src/arch/x86_64/encodings.zig
@@ -0,0 +1,542 @@
+const Encoding = @import("Encoding.zig");
+const Mnemonic = Encoding.Mnemonic;
+const OpEn = Encoding.OpEn;
+const Op = Encoding.Op;
+const Mode = Encoding.Mode;
+
+const opcode_len = u2;
+const modrm_ext = u3;
+
+const Entry = struct { Mnemonic, OpEn, Op, Op, Op, Op, opcode_len, u8, u8, u8, modrm_ext, Mode };
+
+// TODO move this into a .zon file when Zig is capable of importing .zon files
+// zig fmt: off
+pub const table = &[_]Entry{
+    // General-purpose
+    .{ .adc, .zi, .al,   .imm8,  .none, .none, 1, 0x14, 0x00, 0x00, 0, .none  },
+    .{ .adc, .zi, .ax,   .imm16, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none  },
+    .{ .adc, .zi, .eax,  .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none  },
+    .{ .adc, .zi, .rax,  .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long  },
+    .{ .adc, .mi, .rm8,  .imm8,  .none, .none, 1, 0x80, 0x00, 0x00, 2, .none  },
+    .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none  },
+    .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none  },
+    .{ .adc, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long  },
+    .{ .adc, .mi, .rm16, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 2, .none  },
+    .{ .adc, .mi, .rm32, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 2, .none  },
+    .{ .adc, .mi, .rm64, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 2, .long  },
+    .{ .adc, .mr, .rm8,  .r8,    .none, .none, 1, 0x10, 0x00, 0x00, 0, .none  },
+    .{ .adc, .mr, .rm16, .r16,   .none, .none, 1, 0x11, 0x00, 0x00, 0, .none  },
+    .{ .adc, .mr, .rm32, .r32,   .none, .none, 1, 0x11, 0x00, 0x00, 0, .none  },
+    .{ .adc, .mr, .rm64, .r64,   .none, .none, 1, 0x11, 0x00, 0x00, 0, .long  },
+    .{ .adc, .rm, .r8,   .rm8,   .none, .none, 1, 0x12, 0x00, 0x00, 0, .none  },
+    .{ .adc, .rm, .r16,  .rm16,  .none, .none, 1, 0x13, 0x00, 0x00, 0, .none  },
+    .{ .adc, .rm, .r32,  .rm32,  .none, .none, 1, 0x13, 0x00, 0x00, 0, .none  },
+    .{ .adc, .rm, .r64,  .rm64,  .none, .none, 1, 0x13, 0x00, 0x00, 0, .long  },
+
+    .{ .add, .zi, .al,   .imm8,  .none, .none, 1, 0x04, 0x00, 0x00, 0, .none  },
+    .{ .add, .zi, .ax,   .imm16, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none  },
+    .{ .add, .zi, .eax,  .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none  },
+    .{ .add, .zi, .rax,  .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long  },
+    .{ .add, .mi, .rm8,  .imm8,  .none, .none, 1, 0x80, 0x00, 0x00, 0, .none  },
+    .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none  },
+    .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none  },
+    .{ .add, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long  },
+    .{ .add, .mi, .rm16, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 0, .none  },
+    .{ .add, .mi, .rm32, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 0, .none  },
+    .{ .add, .mi, .rm64, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 0, .long  },
+    .{ .add, .mr, .rm8,  .r8,    .none, .none, 1, 0x00, 0x00, 0x00, 0, .none  },
+    .{ .add, .mr, .rm16, .r16,   .none, .none, 1, 0x01, 0x00, 0x00, 0, .none  },
+    .{ .add, .mr, .rm32, .r32,   .none, .none, 1, 0x01, 0x00, 0x00, 0, .none  },
+    .{ .add, .mr, .rm64, .r64,   .none, .none, 1, 0x01, 0x00, 0x00, 0, .long  },
+    .{ .add, .rm, .r8,   .rm8,   .none, .none, 1, 0x02, 0x00, 0x00, 0, .none  },
+    .{ .add, .rm, .r16,  .rm16,  .none, .none, 1, 0x03, 0x00, 0x00, 0, .none  },
+    .{ .add, .rm, .r32,  .rm32,  .none, .none, 1, 0x03, 0x00, 0x00, 0, .none  },
+    .{ .add, .rm, .r64,  .rm64,  .none, .none, 1, 0x03, 0x00, 0x00, 0, .long  },
+
+    .{ .@"and", .zi, .al,   .imm8,  .none, .none, 1, 0x24, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .zi, .ax,   .imm16, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .zi, .eax,  .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .zi, .rax,  .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long  },
+    .{ .@"and", .mi, .rm8,  .imm8,  .none, .none, 1, 0x80, 0x00, 0x00, 4, .none  },
+    .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none  },
+    .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none  },
+    .{ .@"and", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long  },
+    .{ .@"and", .mi, .rm16, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 4, .none  },
+    .{ .@"and", .mi, .rm32, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 4, .none  },
+    .{ .@"and", .mi, .rm64, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 4, .long  },
+    .{ .@"and", .mr, .rm8,  .r8,    .none, .none, 1, 0x20, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .mr, .rm16, .r16,   .none, .none, 1, 0x21, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .mr, .rm32, .r32,   .none, .none, 1, 0x21, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .mr, .rm64, .r64,   .none, .none, 1, 0x21, 0x00, 0x00, 0, .long  },
+    .{ .@"and", .rm, .r8,   .rm8,   .none, .none, 1, 0x22, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .rm, .r16,  .rm16,  .none, .none, 1, 0x23, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .rm, .r32,  .rm32,  .none, .none, 1, 0x23, 0x00, 0x00, 0, .none  },
+    .{ .@"and", .rm, .r64,  .rm64,  .none, .none, 1, 0x23, 0x00, 0x00, 0, .long  },
+
+    // This is M encoding according to Intel, but D makes more sense here.
+    .{ .call, .d, .rel32, .none, .none, .none, 1, 0xe8, 0x00, 0x00, 0, .none },
+    .{ .call, .m, .rm64,  .none, .none, .none, 1, 0xff, 0x00, 0x00, 2, .none },
+
+    .{ .cbw,  .np, .o16, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .none  },
+    .{ .cwde, .np, .o32, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .none  },
+    .{ .cdqe, .np, .o64, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .long  },
+
+    .{ .cwd, .np, .o16, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .none  },
+    .{ .cdq, .np, .o32, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .none  },
+    .{ .cqo, .np, .o64, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .long  },
+
+    .{ .cmova,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none  },
+    .{ .cmova,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none  },
+    .{ .cmova,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .long  },
+    .{ .cmovae,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none  },
+    .{ .cmovae,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none  },
+    .{ .cmovae,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long  },
+    .{ .cmovb,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none  },
+    .{ .cmovb,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none  },
+    .{ .cmovb,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long  },
+    .{ .cmovbe,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none  },
+    .{ .cmovbe,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none  },
+    .{ .cmovbe,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .long  },
+    .{ .cmovc,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none  },
+    .{ .cmovc,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none  },
+    .{ .cmovc,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long  },
+    .{ .cmove,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none  },
+    .{ .cmove,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none  },
+    .{ .cmove,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .long  },
+    .{ .cmovg,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none  },
+    .{ .cmovg,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none  },
+    .{ .cmovg,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .long  },
+    .{ .cmovge,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none  },
+    .{ .cmovge,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none  },
+    .{ .cmovge,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .long  },
+    .{ .cmovl,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none  },
+    .{ .cmovl,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none  },
+    .{ .cmovl,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .long  },
+    .{ .cmovle,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none  },
+    .{ .cmovle,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none  },
+    .{ .cmovle,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .long  },
+    .{ .cmovna,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none  },
+    .{ .cmovna,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none  },
+    .{ .cmovna,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .long  },
+    .{ .cmovnae, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none  },
+    .{ .cmovnae, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none  },
+    .{ .cmovnae, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long  },
+    .{ .cmovnb,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none  },
+    .{ .cmovnb,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none  },
+    .{ .cmovnb,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long  },
+    .{ .cmovnbe, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none  },
+    .{ .cmovnbe, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none  },
+    .{ .cmovnbe, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .long  },
+    .{ .cmovnc,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none  },
+    .{ .cmovnc,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none  },
+    .{ .cmovnc,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long  },
+    .{ .cmovne,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none  },
+    .{ .cmovne,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none  },
+    .{ .cmovne,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .long  },
+    .{ .cmovng,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none  },
+    .{ .cmovng,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none  },
+    .{ .cmovng,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .long  },
+    .{ .cmovnge, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none  },
+    .{ .cmovnge, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none  },
+    .{ .cmovnge, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .long  },
+    .{ .cmovnl,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none  },
+    .{ .cmovnl,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none  },
+    .{ .cmovnl,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .long  },
+    .{ .cmovnle, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none  },
+    .{ .cmovnle, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none  },
+    .{ .cmovnle, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .long  },
+    .{ .cmovno,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .none  },
+    .{ .cmovno,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .none  },
+    .{ .cmovno,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .long  },
+    .{ .cmovnp,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none  },
+    .{ .cmovnp,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none  },
+    .{ .cmovnp,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .long  },
+    .{ .cmovns,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .none  },
+    .{ .cmovns,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .none  },
+    .{ .cmovns,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .long  },
+    .{ .cmovnz,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none  },
+    .{ .cmovnz,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none  },
+    .{ .cmovnz,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .long  },
+    .{ .cmovo,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .none  },
+    .{ .cmovo,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .none  },
+    .{ .cmovo,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .long  },
+    .{ .cmovp,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none  },
+    .{ .cmovp,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none  },
+    .{ .cmovp,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .long  },
+    .{ .cmovpe,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none  },
+    .{ .cmovpe,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none  },
+    .{ .cmovpe,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .long  },
+    .{ .cmovpo,  .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none  },
+    .{ .cmovpo,  .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none  },
+    .{ .cmovpo,  .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .long  },
+    .{ .cmovs,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .none  },
+    .{ .cmovs,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .none  },
+    .{ .cmovs,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .long  },
+    .{ .cmovz,   .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none  },
+    .{ .cmovz,   .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none  },
+    .{ .cmovz,   .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .long  },
+
+    .{ .cmp, .zi, .al,   .imm8,  .none, .none, 1, 0x3c, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .zi, .ax,   .imm16, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .zi, .eax,  .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .zi, .rax,  .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long  },
+    .{ .cmp, .mi, .rm8,  .imm8,  .none, .none, 1, 0x80, 0x00, 0x00, 7, .none  },
+    .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none  },
+    .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none  },
+    .{ .cmp, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long  },
+    .{ .cmp, .mi, .rm16, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 7, .none  },
+    .{ .cmp, .mi, .rm32, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 7, .none  },
+    .{ .cmp, .mi, .rm64, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 7, .long  },
+    .{ .cmp, .mr, .rm8,  .r8,    .none, .none, 1, 0x38, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .mr, .rm16, .r16,   .none, .none, 1, 0x39, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .mr, .rm32, .r32,   .none, .none, 1, 0x39, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .mr, .rm64, .r64,   .none, .none, 1, 0x39, 0x00, 0x00, 0, .long  },
+    .{ .cmp, .rm, .r8,   .rm8,   .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .rm, .r16,  .rm16,  .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .rm, .r32,  .rm32,  .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none  },
+    .{ .cmp, .rm, .r64,  .rm64,  .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long  },
+
+    .{ .div, .m, .rm8,  .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .none  },
+    .{ .div, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none  },
+    .{ .div, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none  },
+    .{ .div, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .long  },
+
+    .{ .fisttp, .m, .m16, .none, .none, .none, 1, 0xdf, 0x00, 0x00, 1, .fpu },
+    .{ .fisttp, .m, .m32, .none, .none, .none, 1, 0xdb, 0x00, 0x00, 1, .fpu },
+    .{ .fisttp, .m, .m64, .none, .none, .none, 1, 0xdd, 0x00, 0x00, 1, .fpu },
+
+    .{ .fld, .m, .m32, .none, .none, .none, 1, 0xd9, 0x00, 0x00, 0, .fpu },
+    .{ .fld, .m, .m64, .none, .none, .none, 1, 0xdd, 0x00, 0x00, 0, .fpu },
+    .{ .fld, .m, .m80, .none, .none, .none, 1, 0xdb, 0x00, 0x00, 5, .fpu },
+
+    .{ .idiv, .m, .rm8,  .none, .none, .none, 1, 0xf6, 0x00, 0x00, 7, .none  },
+    .{ .idiv, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none  },
+    .{ .idiv, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none  },
+    .{ .idiv, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .long  },
+
+    .{ .imul, .m,   .rm8,  .none, .none,  .none, 1, 0xf6, 0x00, 0x00, 5, .none  },
+    .{ .imul, .m,   .rm16, .none, .none,  .none, 1, 0xf7, 0x00, 0x00, 5, .none  },
+    .{ .imul, .m,   .rm32, .none, .none,  .none, 1, 0xf7, 0x00, 0x00, 5, .none  },
+    .{ .imul, .m,   .rm64, .none, .none,  .none, 1, 0xf7, 0x00, 0x00, 5, .long  },
+    .{ .imul, .rm,  .r16,  .rm16, .none,  .none, 2, 0x0f, 0xaf, 0x00, 0, .none  },
+    .{ .imul, .rm,  .r32,  .rm32, .none,  .none, 2, 0x0f, 0xaf, 0x00, 0, .none  },
+    .{ .imul, .rm,  .r64,  .rm64, .none,  .none, 2, 0x0f, 0xaf, 0x00, 0, .long  },
+    .{ .imul, .rmi, .r16,  .rm16, .imm8,  .none, 1, 0x6b, 0x00, 0x00, 0, .none  },
+    .{ .imul, .rmi, .r32,  .rm32, .imm8,  .none, 1, 0x6b, 0x00, 0x00, 0, .none  },
+    .{ .imul, .rmi, .r64,  .rm64, .imm8,  .none, 1, 0x6b, 0x00, 0x00, 0, .long  },
+    .{ .imul, .rmi, .r16,  .rm16, .imm16, .none, 1, 0x69, 0x00, 0x00, 0, .none  },
+    .{ .imul, .rmi, .r32,  .rm32, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .none  },
+    .{ .imul, .rmi, .r64,  .rm64, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .long  },
+
+    .{ .int3, .np, .none, .none, .none, .none, 1, 0xcc, 0x00, 0x00, 0, .none },
+
+    .{ .ja,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x87, 0x00, 0, .none },
+    .{ .jae,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none },
+    .{ .jb,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none },
+    .{ .jbe,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x86, 0x00, 0, .none },
+    .{ .jc,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none },
+    .{ .jrcxz, .d, .rel32, .none, .none, .none, 1, 0xe3, 0x00, 0x00, 0, .none },
+    .{ .je,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x84, 0x00, 0, .none },
+    .{ .jg,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8f, 0x00, 0, .none },
+    .{ .jge,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8d, 0x00, 0, .none },
+    .{ .jl,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8c, 0x00, 0, .none },
+    .{ .jle,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8e, 0x00, 0, .none },
+    .{ .jna,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x86, 0x00, 0, .none },
+    .{ .jnae,  .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none },
+    .{ .jnb,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none },
+    .{ .jnbe,  .d, .rel32, .none, .none, .none, 2, 0x0f, 0x87, 0x00, 0, .none },
+    .{ .jnc,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none },
+    .{ .jne,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x85, 0x00, 0, .none },
+    .{ .jng,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8e, 0x00, 0, .none },
+    .{ .jnge,  .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8c, 0x00, 0, .none },
+    .{ .jnl,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8d, 0x00, 0, .none },
+    .{ .jnle,  .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8f, 0x00, 0, .none },
+    .{ .jno,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x81, 0x00, 0, .none },
+    .{ .jnp,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8b, 0x00, 0, .none },
+    .{ .jns,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x89, 0x00, 0, .none },
+    .{ .jnz,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x85, 0x00, 0, .none },
+    .{ .jo,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x80, 0x00, 0, .none },
+    .{ .jp,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8a, 0x00, 0, .none },
+    .{ .jpe,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8a, 0x00, 0, .none },
+    .{ .jpo,   .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8b, 0x00, 0, .none },
+    .{ .js,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x88, 0x00, 0, .none },
+    .{ .jz,    .d, .rel32, .none, .none, .none, 2, 0x0f, 0x84, 0x00, 0, .none },
+
+    .{ .jmp, .d, .rel32, .none, .none, .none, 1, 0xe9, 0x00, 0x00, 0, .none },
+    .{ .jmp, .m, .rm64,  .none, .none, .none, 1, 0xff, 0x00, 0x00, 4, .none },
+
+    .{ .lea, .rm, .r16, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .none  },
+    .{ .lea, .rm, .r32, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .none  },
+    .{ .lea, .rm, .r64, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .long  },
+
+    .{ .mov, .mr, .rm8,   .r8,    .none, .none, 1, 0x88, 0x00, 0x00, 0, .none  },
+    .{ .mov, .mr, .rm16,  .r16,   .none, .none, 1, 0x89, 0x00, 0x00, 0, .none  },
+    .{ .mov, .mr, .rm32,  .r32,   .none, .none, 1, 0x89, 0x00, 0x00, 0, .none  },
+    .{ .mov, .mr, .rm64,  .r64,   .none, .none, 1, 0x89, 0x00, 0x00, 0, .long  },
+    .{ .mov, .rm, .r8,    .rm8,   .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none  },
+    .{ .mov, .rm, .r16,   .rm16,  .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none  },
+    .{ .mov, .rm, .r32,   .rm32,  .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none  },
+    .{ .mov, .rm, .r64,   .rm64,  .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long  },
+    .{ .mov, .mr, .rm16,  .sreg,  .none, .none, 1, 0x8c, 0x00, 0x00, 0, .none  },
+    .{ .mov, .mr, .rm64,  .sreg,  .none, .none, 1, 0x8c, 0x00, 0x00, 0, .long  },
+    .{ .mov, .rm, .sreg,  .rm16,  .none, .none, 1, 0x8e, 0x00, 0x00, 0, .none  },
+    .{ .mov, .rm, .sreg,  .rm64,  .none, .none, 1, 0x8e, 0x00, 0x00, 0, .long  },
+    .{ .mov, .fd, .al,    .moffs, .none, .none, 1, 0xa0, 0x00, 0x00, 0, .none  },
+    .{ .mov, .fd, .ax,    .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none  },
+    .{ .mov, .fd, .eax,   .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none  },
+    .{ .mov, .fd, .rax,   .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .long  },
+    .{ .mov, .td, .moffs, .al,    .none, .none, 1, 0xa2, 0x00, 0x00, 0, .none  },
+    .{ .mov, .td, .moffs, .ax,    .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none  },
+    .{ .mov, .td, .moffs, .eax,   .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none  },
+    .{ .mov, .td, .moffs, .rax,   .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long  },
+    .{ .mov, .oi, .r8,    .imm8,  .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none  },
+    .{ .mov, .oi, .r16,   .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none  },
+    .{ .mov, .oi, .r32,   .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none  },
+    .{ .mov, .oi, .r64,   .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long  },
+    .{ .mov, .mi, .rm8,   .imm8,  .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none  },
+    .{ .mov, .mi, .rm16,  .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none  },
+    .{ .mov, .mi, .rm32,  .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none  },
+    .{ .mov, .mi, .rm64,  .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long  },
+
+    .{ .movsx, .rm, .r16, .rm8,  .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none  },
+    .{ .movsx, .rm, .r32, .rm8,  .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none  },
+    .{ .movsx, .rm, .r64, .rm8,  .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .long  },
+    .{ .movsx, .rm, .r32, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .none  },
+    .{ .movsx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .long  },
+
+    // This instruction is discouraged.
+    .{ .movsxd, .rm, .r32, .rm32, .none, .none, 1, 0x63, 0x00, 0x00, 0, .none  },
+    .{ .movsxd, .rm, .r64, .rm32, .none, .none, 1, 0x63, 0x00, 0x00, 0, .long  },
+
+    .{ .movzx, .rm, .r16, .rm8,  .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .none  },
+    .{ .movzx, .rm, .r32, .rm8,  .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .none  },
+    .{ .movzx, .rm, .r64, .rm8,  .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .long  },
+    .{ .movzx, .rm, .r32, .rm16, .none, .none, 2, 0x0f, 0xb7, 0x00, 0, .none  },
+    .{ .movzx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xb7, 0x00, 0, .long  },
+
+    .{ .mul, .m, .rm8,  .none, .none, .none, 1, 0xf6, 0x00, 0x00, 4, .none  },
+    .{ .mul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none  },
+    .{ .mul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none  },
+    .{ .mul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .long  },
+
+    .{ .nop, .np, .none, .none, .none, .none, 1, 0x90, 0x00, 0x00, 0, .none },
+
+    .{ .@"or", .zi, .al,   .imm8,  .none, .none, 1, 0x0c, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .zi, .ax,   .imm16, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .zi, .eax,  .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .zi, .rax,  .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long  },
+    .{ .@"or", .mi, .rm8,  .imm8,  .none, .none, 1, 0x80, 0x00, 0x00, 1, .none  },
+    .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none  },
+    .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none  },
+    .{ .@"or", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long  },
+    .{ .@"or", .mi, .rm16, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 1, .none  },
+    .{ .@"or", .mi, .rm32, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 1, .none  },
+    .{ .@"or", .mi, .rm64, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 1, .long  },
+    .{ .@"or", .mr, .rm8,  .r8,    .none, .none, 1, 0x08, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .mr, .rm16, .r16,   .none, .none, 1, 0x09, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .mr, .rm32, .r32,   .none, .none, 1, 0x09, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .mr, .rm64, .r64,   .none, .none, 1, 0x09, 0x00, 0x00, 0, .long  },
+    .{ .@"or", .rm, .r8,   .rm8,   .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .rm, .r16,  .rm16,  .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .rm, .r32,  .rm32,  .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none  },
+    .{ .@"or", .rm, .r64,  .rm64,  .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long  },
+
+    .{ .pop, .o, .r16,  .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none  },
+    .{ .pop, .o, .r64,  .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none  },
+    .{ .pop, .m, .rm16, .none, .none, .none, 1, 0x8f, 0x00, 0x00, 0, .none  },
+    .{ .pop, .m, .rm64, .none, .none, .none, 1, 0x8f, 0x00, 0x00, 0, .none  },
+
+    .{ .push, .o, .r16,   .none, .none, .none, 1, 0x50, 0x00, 0x00, 0, .none  },
+    .{ .push, .o, .r64,   .none, .none, .none, 1, 0x50, 0x00, 0x00, 0, .none  },
+    .{ .push, .m, .rm16,  .none, .none, .none, 1, 0xff, 0x00, 0x00, 6, .none  },
+    .{ .push, .m, .rm64,  .none, .none, .none, 1, 0xff, 0x00, 0x00, 6, .none  },
+    .{ .push, .i, .imm8,  .none, .none, .none, 1, 0x6a, 0x00, 0x00, 0, .none  },
+    .{ .push, .i, .imm16, .none, .none, .none, 1, 0x68, 0x00, 0x00, 0, .none  },
+    .{ .push, .i, .imm32, .none, .none, .none, 1, 0x68, 0x00, 0x00, 0, .none  },
+
+    .{ .ret, .np, .none, .none, .none, .none, 1, 0xc3, 0x00, 0x00, 0, .none },
+
+    .{ .sal, .m1, .rm8,  .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none  },
+    .{ .sal, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none  },
+    .{ .sal, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none  },
+    .{ .sal, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long  },
+    .{ .sal, .mc, .rm8,  .cl,    .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none  },
+    .{ .sal, .mc, .rm16, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none  },
+    .{ .sal, .mc, .rm32, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none  },
+    .{ .sal, .mc, .rm64, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long  },
+    .{ .sal, .mi, .rm8,  .imm8,  .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none  },
+    .{ .sal, .mi, .rm16, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none  },
+    .{ .sal, .mi, .rm32, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none  },
+    .{ .sal, .mi, .rm64, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long  },
+
+    .{ .sar, .m1, .rm8,  .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 7, .none  },
+    .{ .sar, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none  },
+    .{ .sar, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none  },
+    .{ .sar, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .long  },
+    .{ .sar, .mc, .rm8,  .cl,    .none, .none, 1, 0xd2, 0x00, 0x00, 7, .none  },
+    .{ .sar, .mc, .rm16, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none  },
+    .{ .sar, .mc, .rm32, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none  },
+    .{ .sar, .mc, .rm64, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 7, .long  },
+    .{ .sar, .mi, .rm8,  .imm8,  .none, .none, 1, 0xc0, 0x00, 0x00, 7, .none  },
+    .{ .sar, .mi, .rm16, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none  },
+    .{ .sar, .mi, .rm32, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none  },
+    .{ .sar, .mi, .rm64, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 7, .long  },
+
+    .{ .sbb, .zi, .al,   .imm8,  .none, .none, 1, 0x1c, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .zi, .ax,   .imm16, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .zi, .eax,  .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .zi, .rax,  .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long  },
+    .{ .sbb, .mi, .rm8,  .imm8,  .none, .none, 1, 0x80, 0x00, 0x00, 3, .none  },
+    .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none  },
+    .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none  },
+    .{ .sbb, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long  },
+    .{ .sbb, .mi, .rm16, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 3, .none  },
+    .{ .sbb, .mi, .rm32, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 3, .none  },
+    .{ .sbb, .mi, .rm64, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 3, .long  },
+    .{ .sbb, .mr, .rm8,  .r8,    .none, .none, 1, 0x18, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .mr, .rm16, .r16,   .none, .none, 1, 0x19, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .mr, .rm32, .r32,   .none, .none, 1, 0x19, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .mr, .rm64, .r64,   .none, .none, 1, 0x19, 0x00, 0x00, 0, .long  },
+    .{ .sbb, .rm, .r8,   .rm8,   .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .rm, .r16,  .rm16,  .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .rm, .r32,  .rm32,  .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none  },
+    .{ .sbb, .rm, .r64,  .rm64,  .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long  },
+
+    .{ .seta,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none },
+    .{ .setae,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none },
+    .{ .setb,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none },
+    .{ .setbe,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none },
+    .{ .setc,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none },
+    .{ .sete,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none },
+    .{ .setg,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none },
+    .{ .setge,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none },
+    .{ .setl,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none },
+    .{ .setle,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none },
+    .{ .setna,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none },
+    .{ .setnae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none },
+    .{ .setnb,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none },
+    .{ .setnbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none },
+    .{ .setnc,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none },
+    .{ .setne,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none },
+    .{ .setng,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none },
+    .{ .setnge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none },
+    .{ .setnl,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none },
+    .{ .setnle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none },
+    .{ .setno,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x91, 0x00, 0, .none },
+    .{ .setnp,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none },
+    .{ .setns,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x99, 0x00, 0, .none },
+    .{ .setnz,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none },
+    .{ .seto,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x90, 0x00, 0, .none },
+    .{ .setp,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none },
+    .{ .setpe,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none },
+    .{ .setpo,  .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none },
+    .{ .sets,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x98, 0x00, 0, .none },
+    .{ .setz,   .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none },
+
+    .{ .shl, .m1, .rm8,  .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none  },
+    .{ .shl, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none  },
+    .{ .shl, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none  },
+    .{ .shl, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long  },
+    .{ .shl, .mc, .rm8,  .cl,    .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none  },
+    .{ .shl, .mc, .rm16, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none  },
+    .{ .shl, .mc, .rm32, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none  },
+    .{ .shl, .mc, .rm64, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long  },
+    .{ .shl, .mi, .rm8,  .imm8,  .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none  },
+    .{ .shl, .mi, .rm16, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none  },
+    .{ .shl, .mi, .rm32, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none  },
+    .{ .shl, .mi, .rm64, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long  },
+
+    .{ .shr, .m1, .rm8,  .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 5, .none  },
+    .{ .shr, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none  },
+    .{ .shr, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none  },
+    .{ .shr, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .long  },
+    .{ .shr, .mc, .rm8,  .cl,    .none, .none, 1, 0xd2, 0x00, 0x00, 5, .none  },
+    .{ .shr, .mc, .rm16, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none  },
+    .{ .shr, .mc, .rm32, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none  },
+    .{ .shr, .mc, .rm64, .cl,    .none, .none, 1, 0xd3, 0x00, 0x00, 5, .long  },
+    .{ .shr, .mi, .rm8,  .imm8,  .none, .none, 1, 0xc0, 0x00, 0x00, 5, .none  },
+    .{ .shr, .mi, .rm16, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none  },
+    .{ .shr, .mi, .rm32, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none  },
+    .{ .shr, .mi, .rm64, .imm8,  .none, .none, 1, 0xc1, 0x00, 0x00, 5, .long  },
+
+    .{ .sub, .zi, .al,   .imm8,  .none, .none, 1, 0x2c, 0x00, 0x00, 0, .none  },
+    .{ .sub, .zi, .ax,   .imm16, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none  },
+    .{ .sub, .zi, .eax,  .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none  },
+    .{ .sub, .zi, .rax,  .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long  },
+    .{ .sub, .mi, .rm8,  .imm8,  .none, .none, 1, 0x80, 0x00, 0x00, 5, .none  },
+    .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none  },
+    .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none  },
+    .{ .sub, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long  },
+    .{ .sub, .mi, .rm16, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 5, .none  },
+    .{ .sub, .mi, .rm32, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 5, .none  },
+    .{ .sub, .mi, .rm64, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 5, .long  },
+    .{ .sub, .mr, .rm8,  .r8,    .none, .none, 1, 0x28, 0x00, 0x00, 0, .none  },
+    .{ .sub, .mr, .rm16, .r16,   .none, .none, 1, 0x29, 0x00, 0x00, 0, .none  },
+    .{ .sub, .mr, .rm32, .r32,   .none, .none, 1, 0x29, 0x00, 0x00, 0, .none  },
+    .{ .sub, .mr, .rm64, .r64,   .none, .none, 1, 0x29, 0x00, 0x00, 0, .long  },
+    .{ .sub, .rm, .r8,   .rm8,   .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none  },
+    .{ .sub, .rm, .r16,  .rm16,  .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none  },
+    .{ .sub, .rm, .r32,  .rm32,  .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none  },
+    .{ .sub, .rm, .r64,  .rm64,  .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long  },
+
+    .{ .syscall, .np, .none, .none, .none, .none, 2, 0x0f, 0x05, 0x00, 0, .none },
+
+    .{ .@"test", .zi, .al,   .imm8,  .none, .none, 1, 0xa8, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .zi, .ax,   .imm16, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .zi, .eax,  .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .zi, .rax,  .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long  },
+    .{ .@"test", .mi, .rm8,  .imm8,  .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .mi, .rm64, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long  },
+    .{ .@"test", .mr, .rm8,  .r8,    .none, .none, 1, 0x84, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .mr, .rm16, .r16,   .none, .none, 1, 0x85, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .mr, .rm32, .r32,   .none, .none, 1, 0x85, 0x00, 0x00, 0, .none  },
+    .{ .@"test", .mr, .rm64, .r64,   .none, .none, 1, 0x85, 0x00, 0x00, 0, .long  },
+
+    .{ .ud2, .np, .none, .none, .none, .none, 2, 0x0f, 0x0b, 0x00, 0, .none },
+
+    .{ .xor, .zi, .al,   .imm8,  .none, .none, 1, 0x34, 0x00, 0x00, 0, .none  },
+    .{ .xor, .zi, .ax,   .imm16, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none  },
+    .{ .xor, .zi, .eax,  .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none  },
+    .{ .xor, .zi, .rax,  .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long  },
+    .{ .xor, .mi, .rm8,  .imm8,  .none, .none, 1, 0x80, 0x00, 0x00, 6, .none  },
+    .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none  },
+    .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none  },
+    .{ .xor, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long  },
+    .{ .xor, .mi, .rm16, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 6, .none  },
+    .{ .xor, .mi, .rm32, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 6, .none  },
+    .{ .xor, .mi, .rm64, .imm8,  .none, .none, 1, 0x83, 0x00, 0x00, 6, .long  },
+    .{ .xor, .mr, .rm8,  .r8,    .none, .none, 1, 0x30, 0x00, 0x00, 0, .none  },
+    .{ .xor, .mr, .rm16, .r16,   .none, .none, 1, 0x31, 0x00, 0x00, 0, .none  },
+    .{ .xor, .mr, .rm32, .r32,   .none, .none, 1, 0x31, 0x00, 0x00, 0, .none  },
+    .{ .xor, .mr, .rm64, .r64,   .none, .none, 1, 0x31, 0x00, 0x00, 0, .long  },
+    .{ .xor, .rm, .r8,   .rm8,   .none, .none, 1, 0x32, 0x00, 0x00, 0, .none  },
+    .{ .xor, .rm, .r16,  .rm16,  .none, .none, 1, 0x33, 0x00, 0x00, 0, .none  },
+    .{ .xor, .rm, .r32,  .rm32,  .none, .none, 1, 0x33, 0x00, 0x00, 0, .none  },
+    .{ .xor, .rm, .r64,  .rm64,  .none, .none, 1, 0x33, 0x00, 0x00, 0, .long  },
+
+    // SSE
+    .{ .addss, .rm, .xmm, .xmm_m32, .none, .none, 3, 0xf3, 0x0f, 0x58, 0, .sse },
+
+    .{ .cmpss, .rmi, .xmm, .xmm_m32, .imm8, .none, 3, 0xf3, 0x0f, 0xc2, 0, .sse },
+
+    .{ .movss, .rm, .xmm,     .xmm_m32, .none, .none, 3, 0xf3, 0x0f, 0x10, 0, .sse },
+    .{ .movss, .mr, .xmm_m32, .xmm,     .none, .none, 3, 0xf3, 0x0f, 0x11, 0, .sse },
+
+    .{ .ucomiss, .rm, .xmm, .xmm_m32, .none, .none, 2, 0x0f, 0x2e, 0x00, 0, .sse },
+
+    // SSE2
+    .{ .addsd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0xf2, 0x0f, 0x58, 0, .sse2 },
+
+    .{ .cmpsd, .rmi, .xmm, .xmm_m64, .imm8, .none, 3, 0xf2, 0x0f, 0xc2, 0, .sse2 },
+
+    .{ .movq, .rm, .xmm,     .xmm_m64, .none, .none, 3, 0xf3, 0x0f, 0x7e, 0, .sse2 },
+    .{ .movq, .mr, .xmm_m64, .xmm,     .none, .none, 3, 0x66, 0x0f, 0xd6, 0, .sse2 },
+
+    .{ .movsd, .rm, .xmm,     .xmm_m64, .none, .none, 3, 0xf2, 0x0f, 0x10, 0, .sse2 },
+    .{ .movsd, .mr, .xmm_m64, .xmm,     .none, .none, 3, 0xf2, 0x0f, 0x11, 0, .sse2 },
+
+    .{ .ucomisd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0x66, 0x0f, 0x2e, 0, .sse2 },
+};
+// zig fmt: on
src/arch/x86_64/Mir.zig
@@ -339,41 +339,23 @@ pub const Inst = struct {
         /// Nop
         nop,
 
-        /// SSE instructions
+        /// SSE/AVX instructions
         /// ops flags:  form:
         ///       0b00  reg1, qword ptr [reg2 + imm32]
         ///       0b01  qword ptr [reg1 + imm32], reg2
         ///       0b10  reg1, reg2
-        mov_f64_sse,
-        mov_f32_sse,
+        mov_f64,
+        mov_f32,
 
         /// ops flags:  form:
         ///       0b00  reg1, reg2
-        add_f64_sse,
-        add_f32_sse,
+        add_f64,
+        add_f32,
 
         /// ops flags:  form:
         ///       0b00  reg1, reg2
-        cmp_f64_sse,
-        cmp_f32_sse,
-
-        /// AVX instructions
-        /// ops flags:  form:
-        ///       0b00  reg1, qword ptr [reg2 + imm32]
-        ///       0b01  qword ptr [reg1 + imm32], reg2
-        ///       0b10  reg1, reg1, reg2
-        mov_f64_avx,
-        mov_f32_avx,
-
-        /// ops flags:  form:
-        ///       0b00  reg1, reg1, reg2
-        add_f64_avx,
-        add_f32_avx,
-
-        /// ops flags:  form:
-        ///       0b00  reg1, reg1, reg2
-        cmp_f64_avx,
-        cmp_f32_avx,
+        cmp_f64,
+        cmp_f32,
 
         /// Pseudo-instructions
         /// call extern function
@@ -439,6 +421,8 @@ pub const Inst = struct {
         inst: Index,
         /// A 32-bit immediate value.
         imm: u32,
+        /// A 32-bit signed displacement value.
+        disp: i32,
         /// A condition code for use with EFLAGS register.
         cc: bits.Condition,
         /// Another instruction with condition code.
@@ -476,9 +460,9 @@ pub const IndexRegisterDisp = struct {
     index: u32,
 
     /// Displacement value
-    disp: u32,
+    disp: i32,
 
-    pub fn encode(index: Register, disp: u32) IndexRegisterDisp {
+    pub fn encode(index: Register, disp: i32) IndexRegisterDisp {
         return .{
             .index = @enumToInt(index),
             .disp = disp,
@@ -487,7 +471,7 @@ pub const IndexRegisterDisp = struct {
 
     pub fn decode(this: IndexRegisterDisp) struct {
         index: Register,
-        disp: u32,
+        disp: i32,
     } {
         return .{
             .index = @intToEnum(Register, this.index),
@@ -503,12 +487,12 @@ pub const IndexRegisterDispImm = struct {
     index: u32,
 
     /// Displacement value
-    disp: u32,
+    disp: i32,
 
     /// Immediate
     imm: u32,
 
-    pub fn encode(index: Register, disp: u32, imm: u32) IndexRegisterDispImm {
+    pub fn encode(index: Register, disp: i32, imm: u32) IndexRegisterDispImm {
         return .{
             .index = @enumToInt(index),
             .disp = disp,
@@ -518,7 +502,7 @@ pub const IndexRegisterDispImm = struct {
 
     pub fn decode(this: IndexRegisterDispImm) struct {
         index: Register,
-        disp: u32,
+        disp: i32,
         imm: u32,
     } {
         return .{
@@ -576,7 +560,7 @@ pub const SaveRegisterList = struct {
 };
 
 pub const ImmPair = struct {
-    dest_off: u32,
+    dest_off: i32,
     operand: u32,
 };