// rexcode ยท Brendan Punsky (dotbmp@github), original author package rexcode_x86 import "core:fmt" import "../isa" // ============================================================================= // SECTION: 8. DECODER // ============================================================================= // // High-performance table-driven x64 instruction decoder. // Design goals: // - O(1) opcode lookups via precomputed tables // - Zero allocations (caller provides output buffer) // - Cache-friendly: hot tables fit in L1 (~5KB) // - Target: 400+ MB/s decode throughput // ----------------------------------------------------------------------------- // 8.1 Instruction Info (per-instruction metadata) // ----------------------------------------------------------------------------- // Instruction_Info captures extra metadata produced during decoding that isn't // part of the core Instruction struct. For batch decoding, this is stored // in a parallel array at the same index as the corresponding Instruction. Instruction_Info :: struct { offset: u32, // Byte offset from start of decoded region // Prefix info rex: u8, // REX byte (0 if none) has_lock: bool, rep: Rep, // Rep prefix (uses same enum as Instruction_Flags) segment: Register, // Segment override (NONE if none) // VEX/EVEX info vex_type: VEX_Type, vex_l: VEX_L, vex_w: VEX_W, evex_b: bool, // EVEX broadcast evex_z: bool, // EVEX zeroing opmask: u8, // EVEX opmask register (k0-k7) } // ----------------------------------------------------------------------------- // 8.2 Decoder State // ----------------------------------------------------------------------------- Decoder_State :: struct { data: []u8, // Input bytes position: int, // Current position mode: Mode, // CPU mode (._64 = long mode, ._32 = i386) // Decoded prefix state rex: u8, prefix_66: bool, prefix_f2: bool, prefix_f3: bool, prefix_67: bool, // Address size override segment: Register, has_lock: bool, // +r opcode encoding opcode_reg: u8, // Register encoded in low 3 bits of opcode // VEX/EVEX state vex_type: VEX_Type, vex_r: bool, // VEX.R (inverted) vex_x: bool, // VEX.X (inverted) vex_b: bool, // VEX.B (inverted) vex_w: bool, // VEX.W vex_l: u8, // VEX.L (0, 1, or 2 for EVEX) vex_vvvv: u8, // VEX.vvvv register vex_pp: u8, // VEX.pp (implied prefix) vex_mmmmm: u8, // VEX.mmmmm (implied escape) // EVEX specific evex_r2: bool, // EVEX.R' evex_v2: bool, // EVEX.V' evex_z: bool, // EVEX.z (zeroing) evex_b: bool, // EVEX.b (broadcast/rc/sae) evex_aaa: u8, // EVEX.aaa (opmask) } // ----------------------------------------------------------------------------- // 8.3 Prefix Decoding // ----------------------------------------------------------------------------- // Prefix type lookup table for O(1) prefix identification // 0 = not a prefix, 1 = LOCK (F0), 2 = F2 (REPNE), 3 = F3 (REP) // 4 = segment ES, 5 = segment CS, 6 = segment SS, 7 = segment DS // 8 = segment FS, 9 = segment GS, 10 = 0x66, 11 = 0x67 // 12 = REX (0x40-0x4F), 13 = VEX2 (C5), 14 = VEX3 (C4), 15 = EVEX (62) @(private="file", rodata) PREFIX_TYPE_TABLE := [256]u8{ 0x26 = 4, // ES 0x2E = 5, // CS 0x36 = 6, // SS 0x3E = 7, // DS 0x40 = 12, 0x41 = 12, 0x42 = 12, 0x43 = 12, // REX 0x44 = 12, 0x45 = 12, 0x46 = 12, 0x47 = 12, 0x48 = 12, 0x49 = 12, 0x4A = 12, 0x4B = 12, 0x4C = 12, 0x4D = 12, 0x4E = 12, 0x4F = 12, 0x62 = 15, // EVEX 0x64 = 8, // FS 0x65 = 9, // GS 0x66 = 10, // Operand size 0x67 = 11, // Address size 0xC4 = 14, // VEX 3-byte 0xC5 = 13, // VEX 2-byte 0xF0 = 1, // LOCK 0xF2 = 2, // REPNE 0xF3 = 3, // REP } // Segment register lookup for prefix types 4-9 @(rodata) PREFIX_SEGMENT_TABLE := [6]Register{ES, CS, SS, DS, FS, GS} decode_prefixes :: #force_inline proc(state: ^Decoder_State) -> Error_Code { data := state.data pos := state.position data_length := len(data) for prefix_count := 0; pos < data_length && prefix_count < 15; prefix_count += 1 { b := data[pos] ptype := PREFIX_TYPE_TABLE[b] switch ptype { case 0: // Not a prefix, done state.position = pos return .NONE case 1: state.has_lock = true pos += 1 case 2: state.prefix_f2 = true pos += 1 case 3: state.prefix_f3 = true pos += 1 case 4..=9: state.segment = PREFIX_SEGMENT_TABLE[ptype - 4] pos += 1 case 10: state.prefix_66 = true pos += 1 case 11: state.prefix_67 = true pos += 1 case 12: if state.mode == ._32 { // In i386, bytes 0x40-0x4F are short-form INC/DEC opcodes, // not REX prefixes. End the prefix-decoding loop here so // the opcode dispatcher picks them up. state.position = pos return .NONE } state.rex = b pos += 1 case 13: state.position = pos return decode_vex2(state) case 14: state.position = pos return decode_vex3(state) case 15: state.position = pos return decode_evex(state) } } state.position = pos return pos < data_length ? .NONE : .TOO_MANY_PREFIXES } // ----------------------------------------------------------------------------- // 8.4 VEX/EVEX Prefix Decoding // ----------------------------------------------------------------------------- decode_vex2 :: #force_inline proc(state: ^Decoder_State) -> Error_Code { if state.position + 2 > len(state.data) { return .BUFFER_TOO_SHORT } b1 := state.data[state.position + 1] state.position += 2 state.vex_type = .VEX state.vex_r = (b1 & 0x80) == 0 // true = extend (bit was 0) state.vex_x = false // Implied 1 in 2-byte VEX = no extend state.vex_b = false // Implied 1 in 2-byte VEX = no extend state.vex_vvvv = (b1 >> 3) & 0x0F state.vex_l = (b1 >> 2) & 0x01 state.vex_pp = b1 & 0x03 state.vex_mmmmm = 1 // Implied 0F escape state.vex_w = false // Implied 0 in 2-byte VEX return .NONE } decode_vex3 :: #force_inline proc(state: ^Decoder_State) -> Error_Code { if state.position + 3 > len(state.data) { return .BUFFER_TOO_SHORT } data := state.data pos := state.position b1 := data[pos + 1] b2 := data[pos + 2] state.position = pos + 3 state.vex_type = .VEX state.vex_r = (b1 & 0x80) == 0 // Inverted state.vex_x = (b1 & 0x40) == 0 // Inverted state.vex_b = (b1 & 0x20) == 0 // Inverted state.vex_mmmmm = b1 & 0x1F state.vex_w = (b2 & 0x80) != 0 state.vex_vvvv = (b2 >> 3) & 0x0F state.vex_l = (b2 >> 2) & 0x01 state.vex_pp = b2 & 0x03 return .NONE } decode_evex :: #force_inline proc(state: ^Decoder_State) -> Error_Code { if state.position + 4 > len(state.data) { return .BUFFER_TOO_SHORT } data := state.data pos := state.position b1 := data[pos + 1] b2 := data[pos + 2] b3 := data[pos + 3] state.position = pos + 4 state.vex_type = .EVEX // Byte 1: R, X, B, R', 0, 0, m, m state.vex_r = (b1 & 0x80) == 0 // Inverted state.vex_x = (b1 & 0x40) == 0 // Inverted state.vex_b = (b1 & 0x20) == 0 // Inverted state.evex_r2 = (b1 & 0x10) == 0 // Inverted (R') state.vex_mmmmm = b1 & 0x03 // Byte 2: W, v, v, v, v, 1, p, p state.vex_w = (b2 & 0x80) != 0 state.vex_vvvv = (b2 >> 3) & 0x0F state.vex_pp = b2 & 0x03 // Byte 3: z, L', L, b, V', a, a, a state.evex_z = (b3 & 0x80) != 0 state.vex_l = ((b3 >> 5) & 0x03) // L'L combined state.evex_b = (b3 & 0x10) != 0 state.evex_v2 = (b3 & 0x08) == 0 // Inverted (V') state.evex_aaa = b3 & 0x07 return .NONE } // ----------------------------------------------------------------------------- // 8.5 Opcode Decoding // ----------------------------------------------------------------------------- decode_opcode :: proc(state: ^Decoder_State) -> (entry: ^Decode_Entry, vex_entry: ^VEX_Decode_Entry, err: Error_Code) { if state.position >= len(state.data) { return nil, nil, .BUFFER_TOO_SHORT } // Handle VEX/EVEX encoded instructions if state.vex_type != nil { return decode_opcode_vex(state) } // Legacy instruction decoding opcode := state.data[state.position] state.position += 1 esc := Escape.NONE // Check for escape sequences if opcode == 0x0F { if state.position >= len(state.data) { return nil, nil, .BUFFER_TOO_SHORT } opcode = state.data[state.position] state.position += 1 if opcode == 0x38 { if state.position >= len(state.data) { return nil, nil, .BUFFER_TOO_SHORT } opcode = state.data[state.position] state.position += 1 esc = ._0F38 } else if opcode == 0x3A { if state.position >= len(state.data) { return nil, nil, .BUFFER_TOO_SHORT } opcode = state.data[state.position] state.position += 1 esc = ._0F3A } else { esc = ._0F } } // Determine mandatory prefix // For legacy (no escape), 0x66 is operand size override, not mandatory prefix // For 0F/0F38/0F3A, 0x66 can be mandatory prefix - try with prefix first, fallback to no prefix prefix: u8 = 0 if state.prefix_66 { prefix = 1 } else if state.prefix_f3 { prefix = 2 } else if state.prefix_f2 { prefix = 3 } // Look up in index table idx: Decode_Index switch esc { case .NONE: // For legacy instructions, 0x66 is operand size override, use prefix=0 idx = didx(DECODE_INDEX_LEGACY, 0, opcode) case ._0F: idx = didx(DECODE_INDEX_ESC_0F, prefix, opcode) // If not found with 66 prefix, try without (66 is operand size override) if idx.count == 0 && prefix == 1 { idx = didx(DECODE_INDEX_ESC_0F, 0, opcode) } case ._0F38: idx = didx(DECODE_INDEX_ESC_0F38, prefix, opcode) if idx.count == 0 && prefix == 1 { idx = didx(DECODE_INDEX_ESC_0F38, 0, opcode) } case ._0F3A: idx = didx(DECODE_INDEX_ESC_0F3A, prefix, opcode) if idx.count == 0 && prefix == 1 { idx = didx(DECODE_INDEX_ESC_0F3A, 0, opcode) } } // If not found, try +r encoding (opcode with register in low 3 bits) if idx.count == 0 && esc == .NONE { base_opcode := opcode & 0xF8 // Mask off low 3 bits idx = didx(DECODE_INDEX_LEGACY, prefix, base_opcode) // Check if this is actually an Op_R encoding if idx.count > 0 { first := &LEGACY_DECODE_ENTRIES[idx.start] if first.enc[0] == .OP_R { // Store the register number for later operand decoding state.opcode_reg = opcode & 0x07 // For Op_R with multiple entries (e.g., PUSH/POP with R64 and R16), // select based on prefix_66 and default_64 flag if idx.count > 1 { for i in 0.. 1 { // In 64-bit mode: default_64=true means 64-bit without REX.W // prefix_66 means 16-bit for i in 0.. 1 { // Multiple entries - need to disambiguate by ModR/M.reg field and operand size // Peek at ModR/M byte (don't consume it yet) if state.position >= len(state.data) { return nil, nil, .BUFFER_TOO_SHORT } modrm := state.data[state.position] modrm_reg := (modrm >> 3) & 0x07 // Determine target operand size based on prefixes // REX.W -> 64-bit, 0x66 -> 16-bit, default -> 32-bit target_size: Operand_Type = .RM32 // default if state.rex & 0x08 != 0 { // REX.W target_size = .RM64 } else if state.prefix_66 { target_size = .RM16 } // Search for matching entry based on extension field AND operand size for i in 0.. (entry: ^Decode_Entry, vex_entry: ^VEX_Decode_Entry, err: Error_Code) { if state.position >= len(state.data) { return nil, nil, .BUFFER_TOO_SHORT } opcode := state.data[state.position] state.position += 1 // Determine escape index from VEX.mmmmm (1=0F, 2=0F38, 3=0F3A -> 0, 1, 2) esc_idx := state.vex_mmmmm - 1 if esc_idx > 2 { return nil, nil, .INVALID_VEX } // Determine prefix from VEX.pp (0=none, 1=66, 2=F3, 3=F2) prefix := state.vex_pp // Use indexed lookup for O(1) opcode resolution idx: Decode_Index entries: []VEX_Decode_Entry if state.vex_type == .EVEX { switch esc_idx { case 0: idx = didx(EVEX_INDEX_0F, prefix, opcode) case 1: idx = didx(EVEX_INDEX_0F38, prefix, opcode) case 2: idx = didx(EVEX_INDEX_0F3A, prefix, opcode) } entries = EVEX_DECODE_ENTRIES[:] } else { switch esc_idx { case 0: idx = didx(VEX_INDEX_0F, prefix, opcode) case 1: idx = didx(VEX_INDEX_0F38, prefix, opcode) case 2: idx = didx(VEX_INDEX_0F3A, prefix, opcode) } entries = VEX_DECODE_ENTRIES[:] } if idx.count == 0 { return nil, nil, .INVALID_OPCODE } // Search within the indexed range for VEX.W and VEX.L match for i in 0.. (inst: Instruction, err: Error_Code) { inst.mnemonic = entry.mnemonic modrm: u8 = 0 modrm_info: ModRM_Info sib: u8 = 0 sib_info: SIB_Info has_sib := false needs_modrm := entry.flags.needs_modrm if needs_modrm { if state.position >= len(state.data) { return {}, .BUFFER_TOO_SHORT } modrm = state.data[state.position] state.position += 1 modrm_info = MODRM_TABLE[modrm] // Check for ModR/M reg extension match if entry.ext != 0xFF { if modrm_info.reg != entry.ext { return {}, .INVALID_MODRM } } // Parse SIB if needed if modrm_info.has_sib { if state.position >= len(state.data) { return {}, .BUFFER_TOO_SHORT } sib = state.data[state.position] state.position += 1 sib_info = SIB_TABLE[sib] has_sib = true } } // Decode each operand op_count := entry.flags.op_count for i in 0.. (inst: Instruction, err: Error_Code) { inst.mnemonic = entry.mnemonic // VEX instructions always have ModR/M if state.position >= len(state.data) { return {}, .BUFFER_TOO_SHORT } modrm := state.data[state.position] state.position += 1 modrm_info := MODRM_TABLE[modrm] sib: u8 = 0 sib_info: SIB_Info has_sib := false if modrm_info.has_sib { if state.position >= len(state.data) { return {}, .BUFFER_TOO_SHORT } sib = state.data[state.position] state.position += 1 sib_info = SIB_TABLE[sib] has_sib = true } // Decode each operand for op_type, i in entry.ops { if op_type == .NONE { break } op_enc := entry.enc[i] inst.ops[i] = decode_single_operand_vex(state, op_type, op_enc, modrm_info, sib_info, has_sib) or_return inst.operand_count += 1 } return } decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_enc: Operand_Encoding, modrm_info: ModRM_Info, sib_info: SIB_Info, has_sib: bool) -> (op: Operand, err: Error_Code) { switch op_enc { case .NONE: return case .REG: // Register encoded in ModR/M.reg register_number := modrm_info.reg if state.rex & 0x04 != 0 { // REX.R register_number += 8 } reg := decode_register(register_number, op_type, state.rex) op = op_reg(reg) return case .MR: // Register or memory in ModR/M.rm if modrm_info.mod == 3 { // Register register_number := modrm_info.rm if state.rex & 0x01 != 0 { // REX.B register_number += 8 } reg := decode_register(register_number, op_type, state.rex) op = op_reg(reg) return } else { // Memory return decode_memory_operand(state, modrm_info, sib_info, has_sib, op_type) } case .IB: // 8-bit immediate or rel8 if state.position >= len(state.data) { err = .BUFFER_TOO_SHORT return } immediate_value := i64(i8(state.data[state.position])) state.position += 1 op = Operand{kind = (op_type == .REL8 ? .RELATIVE : .IMMEDIATE), relative = immediate_value, size = 1} return case .IW: // 16-bit immediate if state.position + 2 > len(state.data) { err = .BUFFER_TOO_SHORT return } immediate_value := i64(i16(u16(state.data[state.position]) | u16(state.data[state.position+1]) << 8)) state.position += 2 op = Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 2} return case .ID: // 32-bit immediate or rel32 if state.position + 4 > len(state.data) { err = .BUFFER_TOO_SHORT return } immediate_value := i64(i32(u32(state.data[state.position]) | u32(state.data[state.position+1]) << 8 | u32(state.data[state.position+2]) << 16 | u32(state.data[state.position+3]) << 24)) state.position += 4 op = Operand{kind = (op_type == .REL32 ? .RELATIVE : .IMMEDIATE), relative = immediate_value, size = 4} return case .IQ: // 64-bit immediate if state.position + 8 > len(state.data) { err = .BUFFER_TOO_SHORT return } immediate_value := i64(u64(state.data[state.position]) | u64(state.data[state.position+1]) << 8 | u64(state.data[state.position+2]) << 16 | u64(state.data[state.position+3]) << 24 | u64(state.data[state.position+4]) << 32 | u64(state.data[state.position+5]) << 40 | u64(state.data[state.position+6]) << 48 | u64(state.data[state.position+7]) << 56) state.position += 8 op = Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 8} return case .IMPL: // Implicit register - decode from operand type return decode_implicit_operand(op_type), .NONE case .OP_R: // Register encoded in low 3 bits of opcode register_number := state.opcode_reg if state.rex & 0x01 != 0 { // REX.B extends the register register_number += 8 } reg := decode_register(register_number, op_type, state.rex) op = op_reg(reg) return case .VVVV: // VEX.vvvv register register_number := 15 - state.vex_vvvv if state.evex_v2 { register_number += 16 } reg := decode_register(register_number, op_type, state.rex) op = op_reg(reg) return case .IS4: // Immediate byte with register in high 4 bits if state.position >= len(state.data) { err = .BUFFER_TOO_SHORT return } immediate_byte := state.data[state.position] state.position += 1 register_number := (immediate_byte >> 4) & 0x0F reg := decode_register(register_number, op_type, state.rex) op = op_reg(reg) return case .AAA: // EVEX opmask - already decoded in state return } return } decode_single_operand_vex :: proc(state: ^Decoder_State, op_type: Operand_Type, op_enc: Operand_Encoding, modrm_info: ModRM_Info, sib_info: SIB_Info, has_sib: bool) -> (op: Operand, err: Error_Code) { #partial switch op_enc { case .REG: // Register in ModR/M.reg, extended by VEX.R // vex_r is true when the encoded bit is 0, meaning extension is active register_number := modrm_info.reg if state.vex_r { // vex_r=true means extend (bit was 0 in inverted encoding) register_number += 8 } if state.vex_type == .EVEX && state.evex_r2 { // evex_r2=true means extend register_number += 16 } reg := decode_register(register_number, op_type, 0) return op_reg(reg), .NONE case .MR: if modrm_info.mod == 3 { // Register in ModR/M.rm, extended by VEX.B // vex_b is true when the encoded bit is 0, meaning extension is active register_number := modrm_info.rm if state.vex_b { // vex_b=true means extend register_number += 8 } reg := decode_register(register_number, op_type, 0) return op_reg(reg), .NONE } else { return decode_memory_operand(state, modrm_info, sib_info, has_sib, op_type) } case .VVVV: register_number := 15 - state.vex_vvvv if state.vex_type == .EVEX && state.evex_v2 { register_number += 16 } reg := decode_register(register_number, op_type, 0) return op_reg(reg), .NONE case: // Fall back to legacy handling return decode_single_operand(state, op_type, op_enc, modrm_info, sib_info, has_sib) } } // ----------------------------------------------------------------------------- // 8.7 Memory Operand Decoding // ----------------------------------------------------------------------------- decode_memory_operand :: proc(state: ^Decoder_State, modrm_info: ModRM_Info, sib_info: SIB_Info, has_sib: bool, op_type: Operand_Type) -> (op: Operand, err: Error_Code) { base_reg := NONE index_reg := NONE scale: u8 = 1 disp: i32 = 0 // Address-register width: 32-bit in i386, 64-bit in long mode. addr_reg_from_num :: #force_inline proc(num: u8, mode: Mode) -> Register { return mode == ._32 ? gpr32_from_num(num) : gpr64_from_num(num) } if has_sib { // SIB addressing base_number := sib_info.base if state.rex & 0x01 != 0 { // REX.B base_number += 8 } // Special case: base=5 with mod=0 means no base (displacement32 only) if sib_info.base == 5 && modrm_info.mod == 0 { base_reg = NONE } else { base_reg = addr_reg_from_num(base_number, state.mode) } // Index register (0xFF means no index) if sib_info.index != 0xFF { index_number := sib_info.index if state.rex & 0x02 != 0 { // REX.X index_number += 8 } index_reg = addr_reg_from_num(index_number, state.mode) scale = sib_info.scale } } else { // Simple addressing with ModR/M.rm as base if modrm_info.mod == 0 && modrm_info.rm == 5 { // In long mode this is [RIP + disp32]; in i386 it's [disp32] // (RIP doesn't exist) -- leave base as NONE, disp // carries the absolute address. if state.mode == ._64 { base_reg = RIP } else { base_reg = NONE } } else { base_number := modrm_info.rm if state.rex & 0x01 != 0 { // REX.B base_number += 8 } base_reg = addr_reg_from_num(base_number, state.mode) } } // Read disp if modrm_info.disp_size == 1 { if state.position >= len(state.data) { return {}, .BUFFER_TOO_SHORT } disp = i32(i8(state.data[state.position])) state.position += 1 } else if modrm_info.disp_size == 4 { if state.position + 4 > len(state.data) { return {}, .BUFFER_TOO_SHORT } disp = i32(u32(state.data[state.position]) | u32(state.data[state.position+1]) << 8 | u32(state.data[state.position+2]) << 16 | u32(state.data[state.position+3]) << 24) state.position += 4 } // Determine operand size from op_type size := op_type_to_size(op_type) mem := mem_make(base_reg, index_reg, scale, disp, NONE) return op_mem(mem, size), .NONE } // ----------------------------------------------------------------------------- // 8.8 Register Decoding Helpers // ----------------------------------------------------------------------------- decode_register :: #force_inline proc "contextless" (num: u8, op_type: Operand_Type, rex: u8) -> Register { #partial switch op_type { case .R64, .RM64: return gpr64_from_num(num) case .R32, .RM32: return gpr32_from_num(num) case .R16, .RM16: return gpr16_from_num(num) case .R8, .RM8: return gpr8_from_num(num, rex != 0) case .XMM, .XMM_M32, .XMM_M64, .XMM_M128: return xmm_from_num(num) case .YMM, .YMM_M256: return ymm_from_num(num) case .ZMM, .ZMM_M512: return zmm_from_num(num) case .MM: return mm_from_num(num) case: return gpr64_from_num(num) } } decode_implicit_operand :: proc(op_type: Operand_Type) -> Operand { #partial switch op_type { case .AL_IMPL: return op_reg(AL) case .AX_IMPL: return op_reg(AX) case .EAX_IMPL: return op_reg(EAX) case .RAX_IMPL: return op_reg(RAX) case .CL_IMPL: return op_reg(CL) case: return {} } } // ============================================================================= // ----------------------------------------------------------------------------- // 8.9 Core Decode Procedure // ----------------------------------------------------------------------------- // decode: The single entry point for x64 instruction decoding. // // Parameters: // data - Input bytes to decode // instructions - Output buffer for decoded Instructions (user-provided) // inst_info - Output buffer for per-instruction metadata (parallel to instructions) // label_defs - Array where label_defs[label_id] will be set to byte offset. // On input, all entries should be LABEL_UNDEFINED. // On output, defined labels have byte offsets. // relocs - Dynamic array; unresolved relocations are appended // errors - Dynamic array; decoding errors are appended // // Returns: // Result with bytes read and success status. // // After decoding: // - instructions[0..n] contains decoded instructions // - inst_info[0..n] contains parallel metadata (offsets, prefix info, etc.) // - label_defs contains labels inferred from branch targets // // The relocs parameter is optional input - if provided, relocations are used // to give labels proper names. If empty or missing info, labels get placeholder // names based on offset when printed. // decode :: proc( data: []u8, relocs: []Relocation, instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, errors: ^[dynamic]Error, mode: Mode = ._64, ) -> (byte_count: u32, ok: bool) { if mode == ._16 { // Real-mode decoding is not implemented; the ModRM addressing // model differs from protected/long mode and needs a separate // decode path. See Mode enum comment in encoding_types.odin. fmt.panicf("x64.decode: Mode._16 (real mode) is not yet supported") } ok = true if len(data) == 0 { return } data_length := u32(len(data)) // Track branch targets for label inference (resolved in pass 2 by isa). pending_branches: [dynamic]isa.Branch_Target defer delete(pending_branches) // ========================================================================= // PASS 1: Decode all instructions, collect branch targets // ========================================================================= for byte_count < data_length { inst: Instruction info: Instruction_Info // Record offset info.offset = byte_count // Initialize decoder state state := Decoder_State{ data = data[byte_count:], position = 0, mode = mode, segment = NONE, } // Phase 1: Parse prefixes err := decode_prefixes(&state) if err != nil { append(errors, Error{inst_idx = u32(len(instructions)), code = err}) ok = false break } // Phase 1.5: i386 short-form INC/DEC (0x40-0x4F). These bytes are // REX prefixes in long mode (handled in decode_prefixes), but in // i386 they are 1-byte INC/DEC EAX..EDI (or AX..DI with 0x66). // ENCODING_TABLE doesn't carry these forms (they collide with REX // in 64-bit and the table is shared), so we materialise the // instruction inline here. if state.mode == ._32 && state.position < len(state.data) { b := state.data[state.position] if b >= 0x40 && b <= 0x4F { state.position += 1 reg_num := b & 0x07 is_dec := (b & 0x08) != 0 reg: Register = state.prefix_66 ? gpr16_from_num(reg_num) : gpr32_from_num(reg_num) inst.mnemonic = is_dec ? .DEC : .INC inst.operand_count = 1 inst.ops[0] = op_reg(reg) inst.length = u8(state.position) inst.flags.lock = state.has_lock inst.flags.rep = state.prefix_f2 ? .REPNE : (state.prefix_f3 ? .REP : .NONE) inst.flags.addr32 = state.prefix_67 inst.flags.segment = state.segment != NONE ? u8(reg_hw(state.segment)) + 1 : 0 info.has_lock = state.has_lock info.rep = inst.flags.rep info.segment = state.segment append(instructions, inst) append(inst_info, info) byte_count += u32(state.position) continue } } // Phase 2: Decode opcode and find instruction entry entry: ^Decode_Entry vex_entry: ^VEX_Decode_Entry entry, vex_entry, err = decode_opcode(&state) if err != nil { append(errors, Error{inst_idx = u32(len(instructions)), code = err}) ok = false break } // Phase 3: Decode operands if vex_entry != nil { inst, err = decode_operands_vex(&state, vex_entry) } else if entry != nil { inst, err = decode_operands(&state, entry) } else { append(errors, Error{inst_idx = u32(len(instructions)), code = .INVALID_OPCODE}) ok = false break } if err != nil { append(errors, Error{inst_idx = u32(len(instructions)), code = err}) ok = false break } // Fill instruction length and flags (for round-trip encode/decode) inst.length = u8(state.position) inst.flags.lock = state.has_lock inst.flags.rep = state.prefix_f2 ? .REPNE : (state.prefix_f3 ? .REP : .NONE) inst.flags.addr32 = state.prefix_67 // Segment: 0=none, 1=ES, 2=CS, 3=SS, 4=DS, 5=FS, 6=GS inst.flags.segment = state.segment != NONE ? u8(reg_hw(state.segment)) + 1 : 0 // Fill instruction info (extended metadata) info.rex = state.rex info.has_lock = state.has_lock info.rep = inst.flags.rep info.segment = state.segment info.vex_type = state.vex_type if state.vex_type != nil && vex_entry != nil { // Use encoding requirements to distinguish LIG/WIG from L0/W0 // If encoding says LIG, the actual L value doesn't matter for re-encoding // If encoding says L0/L1/L2, we should preserve the actual value if vex_entry.vex_l == .LIG { info.vex_l = .LIG } else { info.vex_l = state.vex_l == 0 ? .L0 : (state.vex_l == 1 ? .L1 : .L2) } if vex_entry.vex_w == .WIG { info.vex_w = .WIG } else { info.vex_w = state.vex_w ? .W1 : .W0 } info.evex_b = state.evex_b info.evex_z = state.evex_z info.opmask = state.evex_aaa } else if state.vex_type != nil { // Fallback when vex_entry is nil (shouldn't happen normally) info.vex_l = state.vex_l == 0 ? .L0 : (state.vex_l == 1 ? .L1 : .L2) info.vex_w = state.vex_w ? .W1 : .W0 info.evex_b = state.evex_b info.evex_z = state.evex_z info.opmask = state.evex_aaa } // Check for relative operands and record pending branch targets inst_end := byte_count + u32(state.position) for op_idx in 0..= 0 { append(&pending_branches, isa.Branch_Target{ inst_idx = u32(len(instructions)), op_idx = op_idx, target = u32(target), }) } } } append(instructions, inst) append(inst_info, info) byte_count += u32(state.position) } // ========================================================================= // PASS 2: Infer labels from branch targets within the decoded region // ========================================================================= isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs) return }