From 202be0601bd88622f947f0fa711ac841ad4b9b4c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jun 2026 20:36:11 +0100 Subject: [PATCH] Minor changes in performance --- core/rexcode/doc.odin | 9 --- core/rexcode/x86/decoder.odin | 111 +++++++++++++++---------------- core/rexcode/x86/encoder.odin | 14 ++-- core/rexcode/x86/registers.odin | 21 +++--- core/rexcode/x86/tests/test.odin | 46 +++++++------ 5 files changed, 98 insertions(+), 103 deletions(-) diff --git a/core/rexcode/doc.odin b/core/rexcode/doc.odin index d22442873..abc5e2d54 100644 --- a/core/rexcode/doc.odin +++ b/core/rexcode/doc.odin @@ -51,17 +51,8 @@ compile time — no table is built during a normal library build: odin run /tablegen # ENCODING_TABLE -> generated Odin + /tables.odin odin run /tablegen/generated # -> /tables/.*.bin ``` - Regenerate after editing `ENCODING_TABLE`. See `docs/table_migration.md`. -## Performance (x86) - -With `-o:speed -microarch:native -no-bounds-check`: -- Encoder: ~17 M instructions/sec (~56 MB/s) -- Decoder: ~16 M instructions/sec (~54 MB/s) - -Measured on AMD Ryzen 3950X. - ## Usage ```odin diff --git a/core/rexcode/x86/decoder.odin b/core/rexcode/x86/decoder.odin index fc797898f..b37bb611e 100644 --- a/core/rexcode/x86/decoder.odin +++ b/core/rexcode/x86/decoder.odin @@ -269,7 +269,7 @@ decode_opcode :: proc(state: ^Decoder_State) -> (entry: ^Decode_Entry, vex_entry } // Handle VEX/EVEX encoded instructions - if state.vex_type != .NONE { + if state.vex_type != nil { return decode_opcode_vex(state) } @@ -575,8 +575,7 @@ decode_operands :: proc(state: ^Decoder_State, entry: ^Decode_Entry) -> (inst: I // Check if we need ModR/M needs_modrm := false - for _, i in entry.enc { - enc := entry.enc[i] + for enc in entry.enc { if enc == .MR || enc == .REG || enc == .VVVV { needs_modrm = true break @@ -617,25 +616,20 @@ decode_operands :: proc(state: ^Decoder_State, entry: ^Decode_Entry) -> (inst: I } // Decode each operand - for _, i in entry.ops { - op_type := entry.ops[i] - op_enc := entry.enc[i] - + for op_type, i in entry.ops { if op_type == .NONE { break } + op_enc := entry.enc[i] // i386: default_64 entries have R64/RM64 operand types but // really mean R32/RM32 in 32-bit mode (same encoded bytes). effective := mode_rewrite_op_type(op_type, state.mode, entry.flags.default_64) - inst.ops[i], err = decode_single_operand(state, effective, op_enc, modrm_info, sib_info, has_sib) - if err != nil { - return {}, err - } + inst.ops[i] = decode_single_operand(state, effective, op_enc, modrm_info, sib_info, has_sib) or_return inst.operand_count += 1 } - return inst, .NONE + return } decode_operands_vex :: proc(state: ^Decoder_State, entry: ^VEX_Decode_Entry) -> (inst: Instruction, err: Error_Code) { @@ -664,30 +658,25 @@ decode_operands_vex :: proc(state: ^Decoder_State, entry: ^VEX_Decode_Entry) -> } // Decode each operand - for _, i in entry.ops { - op_type := entry.ops[i] - op_enc := entry.enc[i] - + for op_type, i in entry.ops { if op_type == .NONE { break } + op_enc := entry.enc[i] - inst.ops[i], err = decode_single_operand_vex(state, op_type, op_enc, modrm_info, sib_info, has_sib) - if err != nil { - return {}, err - } + 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 inst, .NONE + 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) { + modrm_info: ModRM_Info, sib_info: SIB_Info, has_sib: bool) -> (op: Operand, err: Error_Code) { switch op_enc { case .NONE: - return {}, .NONE + return case .REG: // Register encoded in ModR/M.reg @@ -696,7 +685,8 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e register_number += 8 } reg := decode_register(register_number, op_type, state.rex) - return op_reg(reg), .NONE + op = op_reg(reg) + return case .MR: // Register or memory in ModR/M.rm @@ -707,7 +697,8 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e register_number += 8 } reg := decode_register(register_number, op_type, state.rex) - return op_reg(reg), .NONE + op = op_reg(reg) + return } else { // Memory return decode_memory_operand(state, modrm_info, sib_info, has_sib, op_type) @@ -716,43 +707,44 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e case .IB: // 8-bit immediate or rel8 if state.position >= len(state.data) { - return {}, .BUFFER_TOO_SHORT + err = .BUFFER_TOO_SHORT + return } immediate_value := i64(i8(state.data[state.position])) state.position += 1 - if op_type == .REL8 { - return Operand{kind = .RELATIVE, relative = immediate_value, size = 1}, .NONE - } - return Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 1}, .NONE + 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) { - return {}, .BUFFER_TOO_SHORT + err = .BUFFER_TOO_SHORT + return } immediate_value := i64(i16(u16(state.data[state.position]) | u16(state.data[state.position+1]) << 8)) state.position += 2 - return Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 2}, .NONE + op = Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 2} + return case .ID: // 32-bit immediate or rel32 if state.position + 4 > len(state.data) { - return {}, .BUFFER_TOO_SHORT + 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 - if op_type == .REL32 { - return Operand{kind = .RELATIVE, relative = immediate_value, size = 4}, .NONE - } - return Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 4}, .NONE + 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) { - return {}, .BUFFER_TOO_SHORT + err = .BUFFER_TOO_SHORT + return } immediate_value := i64(u64(state.data[state.position]) | u64(state.data[state.position+1]) << 8 | @@ -763,7 +755,8 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e u64(state.data[state.position+6]) << 48 | u64(state.data[state.position+7]) << 56) state.position += 8 - return Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 8}, .NONE + op = Operand{kind = .IMMEDIATE, immediate = immediate_value, size = 8} + return case .IMPL: // Implicit register - decode from operand type @@ -776,7 +769,8 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e register_number += 8 } reg := decode_register(register_number, op_type, state.rex) - return op_reg(reg), .NONE + op = op_reg(reg) + return case .VVVV: // VEX.vvvv register @@ -785,25 +779,28 @@ decode_single_operand :: proc(state: ^Decoder_State, op_type: Operand_Type, op_e register_number += 16 } reg := decode_register(register_number, op_type, state.rex) - return op_reg(reg), .NONE + op = op_reg(reg) + return case .IS4: // Immediate byte with register in high 4 bits if state.position >= len(state.data) { - return {}, .BUFFER_TOO_SHORT + 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) - return op_reg(reg), .NONE + op = op_reg(reg) + return case .AAA: // EVEX opmask - already decoded in state - return {}, .NONE + return } - return {}, .NONE + return } decode_single_operand_vex :: proc(state: ^Decoder_State, op_type: Operand_Type, op_enc: Operand_Encoding, @@ -940,7 +937,7 @@ decode_memory_operand :: proc(state: ^Decoder_State, modrm_info: ModRM_Info, // 8.8 Register Decoding Helpers // ----------------------------------------------------------------------------- -decode_register :: proc(num: u8, op_type: Operand_Type, rex: u8) -> Register { +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) @@ -1032,7 +1029,7 @@ decode :: proc( } data_length := len(data) - pos: u32 = 0 + pos := 0 has_errors := false // Track branch targets for label inference (resolved in pass 2 by isa). @@ -1043,12 +1040,12 @@ decode :: proc( // PASS 1: Decode all instructions, collect branch targets // ========================================================================= - for pos < u32(data_length) { + for pos < data_length { inst: Instruction info: Instruction_Info // Record offset - info.offset = pos + info.offset = u32(pos) // Initialize decoder state state := Decoder_State{ @@ -1080,7 +1077,7 @@ decode :: proc( is_dec := (b & 0x08) != 0 reg: Register = state.prefix_66 ? gpr16_from_num(reg_num) : gpr32_from_num(reg_num) - inst.mnemonic = is_dec ? Mnemonic.DEC : Mnemonic.INC + inst.mnemonic = is_dec ? .DEC : .INC inst.operand_count = 1 inst.ops[0] = op_reg(reg) inst.length = u8(state.position) @@ -1095,7 +1092,7 @@ decode :: proc( append(instructions, inst) append(inst_info, info) - pos += u32(state.position) + pos += state.position continue } } @@ -1140,7 +1137,7 @@ decode :: proc( info.rep = inst.flags.rep info.segment = state.segment info.vex_type = state.vex_type - if state.vex_type != .NONE && vex_entry != nil { + 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 @@ -1157,7 +1154,7 @@ decode :: proc( info.evex_b = state.evex_b info.evex_z = state.evex_z info.opmask = state.evex_aaa - } else if state.vex_type != .NONE { + } 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 @@ -1167,7 +1164,7 @@ decode :: proc( } // Check for relative operands and record pending branch targets - inst_end := pos + u32(state.position) + inst_end := pos + state.position for op_idx in 0.. bool { +encoding_matches_inline :: proc "contextless" (inst: ^Instruction, enc: ^Encoding, mode: Mode) -> bool { // Mode gate: skip i386-only encodings (short-form INC/DEC at 0x40-0x4F) // when not in Mode._32. if enc.flags.mode_32_only && mode != ._32 { return false } @@ -740,7 +740,7 @@ encoding_matches_inline :: #force_inline proc "contextless" (inst: ^Instruction, if user_idx >= inst.operand_count - 1 { return false } effective_op_type := mode_rewrite_op_type(op_type, mode, enc.flags.default_64) - if !operand_matches_inline(&inst.ops[user_idx], effective_op_type) { return false } + operand_matches_inline(&inst.ops[user_idx], effective_op_type) or_return user_idx += 1 } return user_idx == inst.operand_count - 1 @@ -757,7 +757,7 @@ encoding_matches_inline :: #force_inline proc "contextless" (inst: ^Instruction, if user_idx >= inst.operand_count { return false } effective_op_type := mode_rewrite_op_type(op_type, mode, enc.flags.default_64) - if !operand_matches_inline(&inst.ops[user_idx], effective_op_type) { return false } + operand_matches_inline(&inst.ops[user_idx], effective_op_type) or_return user_idx += 1 } @@ -861,16 +861,16 @@ imm_matches_inline :: #force_inline proc "contextless" (op: ^Operand, op_type: O #partial switch op_type { case .IMM8: // Full 8-bit range: signed [-128, 127] OR unsigned [0, 255] - return op.immediate >= -128 && op.immediate <= 255 + return -128 <= op.immediate && op.immediate <= 255 case .IMM8SX: // Sign-extended 8-bit: must be in signed 8-bit range - return op.immediate >= -128 && op.immediate <= 127 + return -128 <= op.immediate && op.immediate <= 127 case .IMM16: // Full 16-bit range: signed [-32768, 32767] OR unsigned [0, 65535] - return op.immediate >= -32768 && op.immediate <= 65535 + return -32768 <= op.immediate && op.immediate <= 65535 case .IMM32: // Full 32-bit range: signed [-2147483648, 2147483647] OR unsigned [0, 4294967295] - return op.immediate >= -2147483648 && op.immediate <= 4294967295 + return -2147483648 <= op.immediate && op.immediate <= 4294967295 case .IMM64: return true // Any i64 value fits } diff --git a/core/rexcode/x86/registers.odin b/core/rexcode/x86/registers.odin index b9f6eaa44..a3554f5ab 100644 --- a/core/rexcode/x86/registers.odin +++ b/core/rexcode/x86/registers.odin @@ -324,13 +324,13 @@ reg_needs_evex :: #force_inline proc "contextless" (r: Register) -> bool { @(require_results) reg_is_gpr :: #force_inline proc "contextless" (r: Register) -> bool { c := reg_class(r) - return c >= REG_GPR64 && c <= REG_GPR8H + return REG_GPR64 <= c && c <= REG_GPR8H } @(require_results) reg_is_vector :: #force_inline proc "contextless" (r: Register) -> bool { c := reg_class(r) - return c >= REG_XMM && c <= REG_ZMM + return REG_XMM <= c && c <= REG_ZMM } @(require_results) @@ -340,7 +340,7 @@ reg_is_high_byte :: #force_inline proc "contextless" (r: Register) -> bool { // Size in bits for register @(require_results) -reg_size :: proc "contextless" (r: Register) -> u16 { +reg_size :: #force_inline proc "contextless" (r: Register) -> u16 { switch reg_class(r) { case REG_GPR64: return 64 case REG_GPR32: return 32 @@ -382,19 +382,18 @@ gpr16_from_num :: #force_inline proc "contextless" (num: u8) -> Register { return num < 16 ? Register(REG_GPR16 | u16(num)) : NONE } -gpr8_from_num :: proc(num: u8, has_rex: bool) -> Register { +@(require_results) +gpr8_from_num :: #force_inline proc "contextless" (num: u8, has_rex: bool) -> Register { // Without REX prefix, nums 4-7 encode AH/CH/DH/BH (high byte legacy regs) // With REX prefix, nums 4-7 encode SPL/BPL/SIL/DIL (low byte regs) if has_rex { return num < 16 ? Register(REG_GPR8 | u16(num)) : NONE - } else { - if num < 4 { - return Register(REG_GPR8 | u16(num)) // AL, CL, DL, BL - } else if num < 8 { - return Register(REG_GPR8H | u16(num)) // AH, CH, DH, BH (hw num 4-7) - } - return NONE + } else if num < 4 { + return Register(REG_GPR8 | u16(num)) // AL, CL, DL, BL + } else if num < 8 { + return Register(REG_GPR8H | u16(num)) // AH, CH, DH, BH (hw num 4-7) } + return NONE } @(require_results) diff --git a/core/rexcode/x86/tests/test.odin b/core/rexcode/x86/tests/test.odin index f5c76623c..552d8ec01 100644 --- a/core/rexcode/x86/tests/test.odin +++ b/core/rexcode/x86/tests/test.odin @@ -6,7 +6,6 @@ import x86 "../" import "../../isa" import "core:fmt" import "core:time" -import "core:slice" import "core:strings" import "core:mem/virtual" import "core:math" @@ -3051,7 +3050,7 @@ run_benchmarks :: proc() { bench_insts := make([dynamic]x86.Instruction) defer delete(bench_insts) - for _ in 0..<1000 { + for _ in 0..<100 { insts := []x86.Instruction{ x86.inst_r(.PUSH, x86.RBP), x86.inst_r_r(.MOV, x86.RBP, x86.RSP), @@ -3071,25 +3070,34 @@ run_benchmarks :: proc() { append(&bench_insts, ..insts) } - code_buf: [16 * 1024]u8 + code_buf := make([]byte, 1<<16) + defer delete(code_buf) + labels: [4]x86.Label_Definition + relocs: [dynamic]x86.Relocation; defer delete(relocs) + errs: [dynamic]x86.Error; defer delete(relocs) + + insts: [dynamic]x86.Instruction; defer delete(insts) + info: [dynamic]x86.Instruction_Info; defer delete(info) + lbls: [dynamic]x86.Label_Definition; defer delete(lbls) + // Encode enc_start := time.now() enc_bytes := 0 for _ in 0..