const std = @import("std");
const testing = std.testing;
const mem = std.mem;
const assert = std.debug.assert;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const DW = std.dwarf;

/// EFLAGS condition codes
pub const Condition = enum(u5) {
    /// above
    a,
    /// above or equal
    ae,
    /// below
    b,
    /// below or equal
    be,
    /// carry
    c,
    /// equal
    e,
    /// greater
    g,
    /// greater or equal
    ge,
    /// less
    l,
    /// less or equal
    le,
    /// not above
    na,
    /// not above or equal
    nae,
    /// not below
    nb,
    /// not below or equal
    nbe,
    /// not carry
    nc,
    /// not equal
    ne,
    /// not greater
    ng,
    /// not greater or equal
    nge,
    /// not less
    nl,
    /// not less or equal
    nle,
    /// not overflow
    no,
    /// not parity
    np,
    /// not sign
    ns,
    /// not zero
    nz,
    /// overflow
    o,
    /// parity
    p,
    /// parity even
    pe,
    /// parity odd
    po,
    /// sign
    s,
    /// zero
    z,

    /// Converts a std.math.CompareOperator into a condition flag,
    /// i.e. returns the condition that is true iff the result of the
    /// comparison is true. Assumes signed comparison
    pub fn fromCompareOperatorSigned(op: std.math.CompareOperator) Condition {
        return switch (op) {
            .gte => .ge,
            .gt => .g,
            .neq => .ne,
            .lt => .l,
            .lte => .le,
            .eq => .e,
        };
    }

    /// Converts a std.math.CompareOperator into a condition flag,
    /// i.e. returns the condition that is true iff the result of the
    /// comparison is true. Assumes unsigned comparison
    pub fn fromCompareOperatorUnsigned(op: std.math.CompareOperator) Condition {
        return switch (op) {
            .gte => .ae,
            .gt => .a,
            .neq => .ne,
            .lt => .b,
            .lte => .be,
            .eq => .e,
        };
    }

    /// Returns the condition which is true iff the given condition is
    /// false (if such a condition exists)
    pub fn negate(cond: Condition) Condition {
        return switch (cond) {
            .a => .na,
            .ae => .nae,
            .b => .nb,
            .be => .nbe,
            .c => .nc,
            .e => .ne,
            .g => .ng,
            .ge => .nge,
            .l => .nl,
            .le => .nle,
            .na => .a,
            .nae => .ae,
            .nb => .b,
            .nbe => .be,
            .nc => .c,
            .ne => .e,
            .ng => .g,
            .nge => .ge,
            .nl => .l,
            .nle => .le,
            .no => .o,
            .np => .p,
            .ns => .s,
            .nz => .z,
            .o => .no,
            .p => .np,
            .pe => unreachable,
            .po => unreachable,
            .s => .ns,
            .z => .nz,
        };
    }
};

// zig fmt: off

/// 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) {
    // 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,
    r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,

    // 64-79, 256-bit registers.
    // id is int value - 64.
    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.
    none,

    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 fn to128(self: Register) Register {
        return @intToEnum(Register, @as(u8, self.enc()) + 80);
    }

    /// Convert from any register to its 64 bit alias.
    pub fn to64(self: Register) Register {
        return @intToEnum(Register, self.enc());
    }

    /// 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);
    }

    /// Convert from any register to its 8 bit alias.
    pub fn to8(self: Register) Register {
        return @intToEnum(Register, @as(u8, self.enc()) + 48);
    }

    pub fn dwarfLocOp(self: Register) u8 {
        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,

            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,
        );
    }

    /// 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,
        );
    }

    // --------
    // 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,
    };

    /// 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);
        }
    }

    /// 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 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 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;
            };

            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;
        }
    };

    pub fn vex(self: Self, prefix: Vex) void {
        _ = prefix.write(self.code.writer());
    }

    /// 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);
        }
    }

    // ------
    // Opcode
    // ------

    /// Encodes a 1 byte opcode
    pub fn opcode_1byte(self: Self, opcode: u8) void {
        self.code.appendAssumeCapacity(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 {
        self.code.appendAssumeCapacity(prefix);
        self.code.appendAssumeCapacity(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 {
        self.code.appendAssumeCapacity(prefix_1);
        self.code.appendAssumeCapacity(prefix_2);
        self.code.appendAssumeCapacity(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);
        self.code.appendAssumeCapacity(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 {
        self.code.appendAssumeCapacity(
            @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 {
        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);
        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 {
        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 {
        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);
        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 {
        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);
        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 {
        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 {
        self.code.appendAssumeCapacity(
            @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);

        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 {
        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);
    }

    /// 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
        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.
        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 {
        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
        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 {
        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
        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 {
        self.code.appendAssumeCapacity(@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 {
        self.code.appendAssumeCapacity(@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 {
        self.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 {
        self.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 {
        self.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 {
        self.writeIntLittle(u64, imm);
    }
};

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"),
