Update doc files

This commit is contained in:
gingerBill
2026-06-14 18:24:59 +01:00
parent a116f69b7e
commit c49e296f5e
6 changed files with 253 additions and 252 deletions

View File

@@ -42,18 +42,17 @@ run_sweep_tests :: proc() {
for mn in a.Mnemonic {
forms := a.ENCODING_TABLE[mn]
for idx in 0..<len(forms) {
f := &forms[idx]
for &f, idx in forms {
ilen := a.inst_size_from_bits(f.bits, f.mode)
// Canonical word: form.bits | safe-fill operand bits.
word := f.bits
for k in 0..<4 { word |= sweep_safe_fill(f.enc[k]) }
for _, k in f.enc { word |= sweep_safe_fill(f.enc[k]) }
// Operand-type-driven extras: GPR_RSR needs a non-zero Rs in bits
// 11..8 to disambiguate from GPR_SHIFTED on decode/re-encode; the
// base bits already set bit 4 (the RSR flag), but Rs=0 would alias
// with R0 and the shape_matches predicate requires shift_amt != 0.
for k in 0..<4 {
for _, k in f.enc {
if f.ops[k] == .GPR_RSR && (f.enc[k] == .RM_A32 || f.enc[k] == .RM_T32) {
word |= u32(4) << 8
}

View File

@@ -52,9 +52,9 @@ Measured on AMD Ryzen 3950X.
import "x86"
instructions := []x86.Instruction{
x86.inst_r_r(.MOV, x86.RAX, x86.RDI),
x86.inst_r_r(.ADD, x86.RAX, x86.RSI),
x86.inst_none(.RET),
x86.inst_r_r(.MOV, x86.RAX, x86.RDI),
x86.inst_r_r(.ADD, x86.RAX, x86.RSI),
x86.inst_none(.RET),
}
code: [4096]u8
@@ -138,16 +138,16 @@ x86.print(decoded_insts[:], decoded_info[:], lm.labels[:], label_names = &id_to_
Each package has its own test suite:
```sh
odin test x86/tests
odin test arm32/tests
odin test arm64/tests
odin test mips/tests
odin test mos6502/tests
odin test mos65816/tests
odin test ppc/tests
odin test ppc_vle/tests
odin test riscv/tests
odin test rsp/tests
odin run x86/tests
odin run arm32/tests
odin run arm64/tests
odin run mips/tests
odin run mos6502/tests
odin run mos65816/tests
odin run ppc/tests
odin run ppc_vle/tests
odin run riscv/tests
odin run rsp/tests
```
## Verification harnesses
@@ -162,37 +162,37 @@ Each arch has a verification harness under `<arch>/tools/`:
```
rexcode/
isa/ # shared core: labels, status, print framework, label-inference
docs/ # cross-arch design + per-arch design docs
x86/ # x86-64 / i386
arm32/ # AArch32
arm64/ # AArch64
mips/ # MIPS (R1..R6 + ASEs + coprocessors)
mos6502/ # NMOS 6502 family
mos65816/ # W65C816S
ppc/ # PowerPC (Power ISA 3.1)
ppc_vle/ # Freescale VLE (sibling of ppc)
riscv/ # RISC-V
rsp/ # N64 RSP
isa/ # shared core: labels, status, print framework, label-inference
docs/ # cross-arch design + per-arch design docs
x86/ # x86-64 / i386
arm32/ # AArch32
arm64/ # AArch64
mips/ # MIPS (R1..R6 + ASEs + coprocessors)
mos6502/ # NMOS 6502 family
mos65816/ # W65C816S
ppc/ # PowerPC (Power ISA 3.1)
ppc_vle/ # Freescale VLE (sibling of ppc)
riscv/ # RISC-V
rsp/ # N64 RSP
```
Per-package layout (canonical, enforced by the cross-arch contract):
```
<arch>/
encoder.odin # encode() two-pass, label/reloc-aware
decoder.odin # decode()
printer.odin # sb/sbln/print/println/aprint/aprintln/tprint/tprintln/bprint/bprintln/fprint/fprintln/wprint/wprintln
registers.odin # Register, REG_* classes, typed enums
operands.odin # Operand, Memory, Operand_Kind, op_* constructors
instructions.odin # Instruction, inst_* builders
encoding_types.odin # Encoding, Encoding_Flags, isa re-exports
encoding_table.odin # ENCODING_TABLE: [Mnemonic][]Encoding
decoding_tables.odin # generated dispatch tables
mnemonics.odin # Mnemonic enum (u16, INVALID=0)
reloc.odin # Relocation_Type + Relocation
tests/ # smoke, pipeline_smoke, sweep
tools/ # gen_decode_tables, dump_verify_input, verify_against_*
encoder.odin # encode() two-pass, label/reloc-aware
decoder.odin # decode()
printer.odin # sb/sbln/print/println/aprint/aprintln/tprint/tprintln/bprint/bprintln/fprint/fprintln/wprint/wprintln
registers.odin # Register, REG_* classes, typed enums
operands.odin # Operand, Memory, Operand_Kind, op_* constructors
instructions.odin # Instruction, inst_* builders
encoding_types.odin # Encoding, Encoding_Flags, isa re-exports
encoding_table.odin # ENCODING_TABLE: [Mnemonic][]Encoding
decoding_tables.odin # generated dispatch tables
mnemonics.odin # Mnemonic enum (u16, INVALID=0)
reloc.odin # Relocation_Type + Relocation
tests/ # smoke, pipeline_smoke, sweep
tools/ # gen_decode_tables, dump_verify_input, verify_against_*
```
## Cross-architecture API design

View File

@@ -207,25 +207,25 @@ for an **opt-in** facade (§5.3) that only multi-target *tools* pay for.
```
rexcode/
isa/ # shared, architecture-independent core
labels.odin # Label, Label_Definition, Label_Map, resolution
reloc.odin # Relocation (type field is generic/u8)
status.odin # Result, Error, shared Error_Code core
print.odin # Token, Token_Kind, Print_Options, sinks, num-fmt
register.odin # distinct-u16 layout convention + reg_hw/reg_class
pipeline.odin # parametric encode_stream/decode_stream (§7)
target.odin # optional runtime Target vtable (§5.3)
labels.odin # Label, Label_Definition, Label_Map, resolution
reloc.odin # Relocation (type field is generic/u8)
status.odin # Result, Error, shared Error_Code core
print.odin # Token, Token_Kind, Print_Options, sinks, num-fmt
register.odin # distinct-u16 layout convention + reg_hw/reg_class
pipeline.odin # parametric encode_stream/decode_stream (§7)
target.odin # optional runtime Target vtable (§5.3)
x86/ # exists today; refactor to import isa
registers.odin operands.odin instructions.odin mnemonics.odin
encoding_types.odin encoder.odin decoder.odin printer.odin
encoding_table.odin decoding_tables.odin mnemonic_builders.odin
tests/ tools/
registers.odin operands.odin instructions.odin mnemonics.odin
encoding_types.odin encoder.odin decoder.odin printer.odin
encoding_table.odin decoding_tables.odin mnemonic_builders.odin
tests/ tools/
riscv/ # next: same shape as x86/
registers.odin operands.odin instructions.odin mnemonics.odin
encoding_types.odin encoder.odin decoder.odin printer.odin
encoding_table.odin decoding_tables.odin mnemonic_builders.odin
tests/ tools/
registers.odin operands.odin instructions.odin mnemonics.odin
encoding_types.odin encoder.odin decoder.odin printer.odin
encoding_table.odin decoding_tables.odin mnemonic_builders.odin
tests/ tools/
arm64/ mips/ … # future, same template
```
@@ -269,11 +269,11 @@ provides a vtable populated by each arch:
```odin
// isa/target.odin
Target :: struct {
name: string,
decode: proc(data: []u8, out: ^Decoded) -> Result, // bytes → generic Decoded
print: proc(d: ^Decoded, opts: ^Print_Options) -> string,
inst_align: u32, // 1 for x86, 4 for riscv/arm64/mips
max_inst: u32, // 15 for x86, 4 for riscv (8 for C-pairs), 4 for arm64
name: string,
decode: proc(data: []u8, out: ^Decoded) -> Result, // bytes → generic Decoded
print: proc(d: ^Decoded, opts: ^Print_Options) -> string,
inst_align: u32, // 1 for x86, 4 for riscv/arm64/mips
max_inst: u32, // 15 for x86, 4 for riscv (8 for C-pairs), 4 for arm64
}
// each arch: x86.TARGET: isa.Target = { … }
```
@@ -323,7 +323,7 @@ Builder names spell out each operand kind separated by underscores
```
inst_none / inst_r / inst_r_r / inst_r_i / inst_r_m / inst_m_r / …
emit_none / emit_r / emit_rr / emit_ri / emit_rm / emit_mr / …
# NB: emit_* uses concatenated suffixes (legacy x86 spelling)
# NB: emit_* uses concatenated suffixes (legacy x86 spelling)
inst_<mnemonic>(…) / emit_<mnemonic>(…) # generated typed overloads
```
@@ -339,8 +339,8 @@ decode(data: []u8, relocs: []Relocation,
label_defs: ^[dynamic]Label_Definition, errors: ^[dynamic]Error) -> Result
print/println/aprint/tprint/bprint/fprint/wprint(+ln)(
instructions: []Instruction, inst_info: []Instruction_Info,
label_defs: []Label_Definition, tokens=nil, options=nil, label_names=nil)
instructions: []Instruction, inst_info: []Instruction_Info,
label_defs: []Label_Definition, tokens=nil, options=nil, label_names=nil)
```
### Register/label/print helpers
@@ -366,18 +366,19 @@ these at compile time → **no runtime cost, real code sharing.**
```odin
// isa/pipeline.odin (sketch)
encode_stream :: proc(
instructions: []$I,
label_defs: []Label_Definition,
code: []u8,
relocs: ^[dynamic]Relocation,
errors: ^[dynamic]Error,
encode_one: proc(inst: ^I, out: []u8, code_pos: u32,
relocs: ^[dynamic]Relocation, errors: ^[dynamic]Error) -> (n: u32, ok: bool),
resolve := true, base_address: u64 = 0,
instructions: []$I,
label_defs: []Label_Definition,
code: []u8,
relocs: ^[dynamic]Relocation,
errors: ^[dynamic]Error,
encode_one: proc(inst: ^I, out: []u8, code_pos: u32,
relocs: ^[dynamic]Relocation, errors: ^[dynamic]Error) -> (n: u32, ok: bool),
resolve := true,
base_address: u64 = 0,
) -> Result {
// PASS 1: for each inst → record offset, call encode_one, advance
// PASS 1.5: rewrite label_defs inst-index → byte-offset (identical on every arch)
// PASS 2: resolve relocations / patch / spill unresolved (identical on every arch)
// PASS 1: for each inst → record offset, call encode_one, advance
// PASS 1.5: rewrite label_defs inst-index → byte-offset (identical on every arch)
// PASS 2: resolve relocations / patch / spill unresolved (identical on every arch)
}
```

View File

@@ -110,16 +110,16 @@ Operand_Kind :: enum u8 { NONE, REGISTER, MEMORY, IMMEDIATE, RELATIVE }
```odin
Memory :: bit_field u64 {
base_hw: u8 | 5,
base_ext: bool | 1,
index_hw: u8 | 5,
index_ext: bool | 1,
scale_enc: u8 | 2,
displacement: i32 | 32,
segment: u8 | 3,
addr_size_override: bool | 1,
base_class: u8 | 5,
index_class: u8 | 5,
base_hw: u8 | 5,
base_ext: bool | 1,
index_hw: u8 | 5,
index_ext: bool | 1,
scale_enc: u8 | 2,
displacement: i32 | 32,
segment: u8 | 3,
addr_size_override: bool | 1,
base_class: u8 | 5,
index_class: u8 | 5,
}
MEM_BASE_RIP :: 30 MEM_BASE_NONE :: 31 MEM_INDEX_NONE :: 31
```
@@ -143,25 +143,25 @@ MEM_BASE_RIP :: 30 MEM_BASE_NONE :: 31 MEM_INDEX_NONE :: 31
```odin
Operand :: struct #packed { // 16 bytes
using _: struct #raw_union {
reg: Register,
mem: Memory,
immediate: i64,
relative: i64, // offset or label id
},
kind: Operand_Kind,
size: u8, // operand size in bytes (1,2,4,8,16,32,64)
flags: Operand_Flags,
_pad: [4]u8,
using _: struct #raw_union {
reg: Register,
mem: Memory,
immediate: i64,
relative: i64, // offset or label id
},
kind: Operand_Kind,
size: u8, // operand size in bytes (1,2,4,8,16,32,64)
flags: Operand_Flags,
_: [4]u8,
}
Broadcast :: enum u8 { NONE, B1TO2, B1TO4, B1TO8, B1TO16 } // EVEX
Operand_Flags :: bit_field u16 { // EVEX-specific
mask: u8 | 3, // opmask K1K7
zeroing: bool | 1, // merge vs zero masking
broadcast: Broadcast | 3,
er_sae: u8 | 2, // embedded rounding / SAE
mask: u8 | 3, // opmask K1K7
zeroing: bool | 1, // merge vs zero masking
broadcast: Broadcast | 3,
er_sae: u8 | 2, // embedded rounding / SAE
}
```
@@ -189,12 +189,12 @@ Instruction_Flags :: bit_field u8 {
}
Instruction :: struct #packed { // 72 bytes
ops: [4]Operand,
mnemonic: Mnemonic,
operand_count: u8,
flags: Instruction_Flags,
length: u8, // filled by decoder
_pad: [3]u8,
ops: [4]Operand,
mnemonic: Mnemonic,
operand_count: u8,
flags: Instruction_Flags,
length: u8, // filled by decoder
_: [3]u8,
}
```
@@ -278,16 +278,16 @@ These describe **how** an instruction is encoded; they are the schema of
```odin
Operand_Type :: enum u8 { // ~70 values
NONE, R8,R16,R32,R64, RM8,RM16,RM32,RM64, M,M8..M512,
IMM8,IMM16,IMM32,IMM64, IMM8SX, REL8,REL32,
AL_IMPL,AX_IMPL,EAX_IMPL,RAX_IMPL,CL_IMPL,DX_IMPL,ONE_IMPL,
SREG, CR, DR, XMM,YMM,ZMM, XMM_M32,XMM_M64,XMM_M128,YMM_M256,ZMM_M512,
MM,MM_M64, ST0_IMPL,STI, XMM0_IMPL, K,K_M8..K_M64,
MOFFS8..MOFFS64, PTR16_16,PTR16_32,PTR16_64, M16_16,M16_32,M16_64,
NONE, R8,R16,R32,R64, RM8,RM16,RM32,RM64, M,M8..M512,
IMM8,IMM16,IMM32,IMM64, IMM8SX, REL8,REL32,
AL_IMPL,AX_IMPL,EAX_IMPL,RAX_IMPL,CL_IMPL,DX_IMPL,ONE_IMPL,
SREG, CR, DR, XMM,YMM,ZMM, XMM_M32,XMM_M64,XMM_M128,YMM_M256,ZMM_M512,
MM,MM_M64, ST0_IMPL,STI, XMM0_IMPL, K,K_M8..K_M64,
MOFFS8..MOFFS64, PTR16_16,PTR16_32,PTR16_64, M16_16,M16_32,M16_64,
}
Operand_Encoding :: enum u8 { // where an operand's bits go
NONE, MR, REG, VVVV, OP_R, IB,IW,ID,IQ, IMPL, IS4, AAA,
NONE, MR, REG, VVVV, OP_R, IB,IW,ID,IQ, IMPL, IS4, AAA,
}
Escape :: enum u8 { NONE, _0F, _0F38, _0F3A }
@@ -296,14 +296,26 @@ VEX_W :: enum u8 { WIG, W0, W1 }
VEX_L :: enum u8 { LIG, L0, L1, L2 }
Encoding_Flags :: bit_field u16 {
esc: Escape|2, prefix: u8|2, vex_type: VEX_Type|2, vex_w: VEX_W|2,
vex_l: VEX_L|2, default_64: bool|1, force_rex_w: bool|1, no_rex: bool|1,
lock_ok: bool|1, rep_ok: bool|1, modrm_reg_ext: bool|1,
esc: Escape | 2,
prefix: u8 | 2,
vex_type: VEX_Type | 2,
vex_w: VEX_W | 2,
vex_l: VEX_L | 2,
default_64: bool | 1,
force_rex_w: bool | 1,
no_rex: bool | 1,
lock_ok: bool | 1,
rep_ok: bool | 1,
modrm_reg_ext: bool | 1,
}
Encoding :: struct #packed { // 14 bytes — one encoding form
mnemonic: Mnemonic, ops: [4]Operand_Type, enc: [4]Operand_Encoding,
opcode: u8, ext: u8, flags: Encoding_Flags,
mnemonic: Mnemonic,
ops: [4]Operand_Type,
enc: [4]Operand_Encoding,
opcode: u8,
ext: u8,
flags: Encoding_Flags,
}
PREFIX_66 :: 1 PREFIX_F3 :: 2 PREFIX_F2 :: 3
```
@@ -314,19 +326,19 @@ Helper: `encoding_flags(esc=…, prefix=…, …) -> Encoding_Flags`.
```odin
Relocation_Type :: enum u8 { NONE, REL8, REL32, ABS32, ABS64 }
Relocation :: struct #packed { // 16 bytes (ELF-rela-like)
offset: u32, label_id: u32, addend: i32,
type: Relocation_Type, size: u8, inst_idx: u16,
offset: u32, label_id: u32, addend: i32,
type: Relocation_Type, size: u8, inst_idx: u16,
}
Error_Code :: enum u8 {
NONE,
// encode
INVALID_MNEMONIC, NO_MATCHING_ENCODING, OPERAND_MISMATCH,
IMMEDIATE_OUT_OF_RANGE, BUFFER_OVERFLOW, LABEL_OUT_OF_RANGE,
INVALID_OPERAND_COUNT,
// decode
BUFFER_TOO_SHORT, INVALID_OPCODE, INVALID_MODRM, INVALID_SIB,
INVALID_PREFIX, INVALID_VEX, INVALID_EVEX, TOO_MANY_PREFIXES,
NONE,
// encode
INVALID_MNEMONIC, NO_MATCHING_ENCODING, OPERAND_MISMATCH,
IMMEDIATE_OUT_OF_RANGE, BUFFER_OVERFLOW, LABEL_OUT_OF_RANGE,
INVALID_OPERAND_COUNT,
// decode
BUFFER_TOO_SHORT, INVALID_OPCODE, INVALID_MODRM, INVALID_SIB,
INVALID_PREFIX, INVALID_VEX, INVALID_EVEX, TOO_MANY_PREFIXES,
}
Error :: struct #packed { inst_idx: u32, code: Error_Code, _pad: [3]u8 } // 8 bytes
Result :: struct { byte_count: u32, success: bool }
@@ -341,13 +353,13 @@ Helper: `op_type_to_size(Operand_Type) -> u8`.
MAX_INST_SIZE :: 15
encode :: proc(
instructions: []Instruction,
label_defs: []Label_Definition, // in: inst index; MODIFIED to byte offsets
code: []u8, // output machine code
relocs: ^[dynamic]Relocation, // unresolved relocations appended
errors: ^[dynamic]Error,
resolve: bool = true, // patch resolvable relocs in place
base_address: u64 = 0, // for ABS relocations
instructions: []Instruction,
label_defs: []Label_Definition, // in: inst index; MODIFIED to byte offsets
code: []u8, // output machine code
relocs: ^[dynamic]Relocation, // unresolved relocations appended
errors: ^[dynamic]Error,
resolve: bool = true, // patch resolvable relocs in place
base_address: u64 = 0, // for ABS relocations
) -> Result
```
@@ -371,19 +383,19 @@ Internal matcher (file-local, inlined): `encoding_matches_inline`,
```odin
Instruction_Info :: struct { // parallel metadata, one per decoded inst
offset: u32,
rex: u8, has_lock: bool, rep: Rep, segment: Register,
vex_type: VEX_Type, vex_l: VEX_L, vex_w: VEX_W,
evex_b: bool, evex_z: bool, opmask: u8,
offset: u32,
rex: u8, has_lock: bool, rep: Rep, segment: Register,
vex_type: VEX_Type, vex_l: VEX_L, vex_w: VEX_W,
evex_b: bool, evex_z: bool, opmask: u8,
}
decode :: proc(
data: []u8,
relocs: []Relocation, // optional in: name labels
instructions: ^[dynamic]Instruction, // out
inst_info: ^[dynamic]Instruction_Info, // out (parallel)
label_defs: ^[dynamic]Label_Definition, // out: inferred branch labels
errors: ^[dynamic]Error,
data: []u8,
relocs: []Relocation, // optional in: name labels
instructions: ^[dynamic]Instruction, // out
inst_info: ^[dynamic]Instruction_Info, // out (parallel)
label_defs: ^[dynamic]Label_Definition, // out: inferred branch labels
errors: ^[dynamic]Error,
) -> Result
```
@@ -406,15 +418,15 @@ Modified Intel syntax: size suffix on the mnemonic (`.b .w .d .q .x .y
```odin
Token_Kind :: enum u8 { WHITESPACE, NEWLINE, LABEL_DEF, LABEL_REF, OFFSET,
MNEMONIC, REGISTER, IMMEDIATE, MEMORY_BRACKET, MEMORY_OPERATOR,
MEMORY_DISP, MEMORY_SCALE, PUNCTUATION, COMMENT }
MNEMONIC, REGISTER, IMMEDIATE, MEMORY_BRACKET, MEMORY_OPERATOR,
MEMORY_DISP, MEMORY_SCALE, PUNCTUATION, COMMENT }
Token :: struct { offset: u32, length: u16, kind: Token_Kind, instruction_index: u16 }
Print_Options :: struct {
uppercase: bool, hex_prefix: string, hex_lowercase: bool,
label_prefix: string, show_offsets: bool, indent: string,
separator: string, space_after_comma: bool,
uppercase: bool, hex_prefix: string, hex_lowercase: bool,
label_prefix: string, show_offsets: bool, indent: string,
separator: string, space_after_comma: bool,
}
DEFAULT_PRINT_OPTIONS :: Print_Options{ }

View File

@@ -22,21 +22,21 @@ import "../isa"
// 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
offset: u32, // Byte offset from start of decoded region
// Prefix info
rex: u8, // REX byte (0 if none)
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)
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)
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)
}
@@ -46,39 +46,39 @@ Instruction_Info :: struct {
// -----------------------------------------------------------------------------
Decoder_State :: struct {
data: []u8, // Input bytes
position: int, // Current position
mode: Mode, // CPU mode (._64 = long mode, ._32 = i386)
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,
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)
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)
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)
}
@@ -114,6 +114,7 @@ PREFIX_TYPE_TABLE := [256]u8{
}
// 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 {
@@ -186,15 +187,15 @@ decode_vex2 :: #force_inline proc(state: ^Decoder_State) -> Error_Code {
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
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
}
@@ -205,20 +206,20 @@ decode_vex3 :: #force_inline proc(state: ^Decoder_State) -> Error_Code {
}
data := state.data
pos := state.position
b1 := data[pos + 1]
b2 := data[pos + 2]
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
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
}
@@ -237,20 +238,20 @@ decode_evex :: #force_inline proc(state: ^Decoder_State) -> Error_Code {
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
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_w = (b2 & 0x80) != 0
state.vex_vvvv = (b2 >> 3) & 0x0F
state.vex_pp = b2 & 0x03
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_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
@@ -572,7 +573,7 @@ decode_operands :: proc(state: ^Decoder_State, entry: ^Decode_Entry) -> (inst: I
// Check if we need ModR/M
needs_modrm := false
for i in 0..<4 {
for _, i in entry.enc {
enc := entry.enc[i]
if enc == .MR || enc == .REG || enc == .VVVV {
needs_modrm = true
@@ -614,7 +615,7 @@ decode_operands :: proc(state: ^Decoder_State, entry: ^Decode_Entry) -> (inst: I
}
// Decode each operand
for i in 0..<4 {
for _, i in entry.ops {
op_type := entry.ops[i]
op_enc := entry.enc[i]
@@ -626,7 +627,7 @@ decode_operands :: proc(state: ^Decoder_State, entry: ^Decode_Entry) -> (inst: I
// 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 != .NONE {
if err != nil {
return {}, err
}
inst.operand_count += 1
@@ -661,7 +662,7 @@ decode_operands_vex :: proc(state: ^Decoder_State, entry: ^VEX_Decode_Entry) ->
}
// Decode each operand
for i in 0..<4 {
for _, i in entry.ops {
op_type := entry.ops[i]
op_enc := entry.enc[i]
@@ -670,7 +671,7 @@ decode_operands_vex :: proc(state: ^Decoder_State, entry: ^VEX_Decode_Entry) ->
}
inst.ops[i], err = decode_single_operand_vex(state, op_type, op_enc, modrm_info, sib_info, has_sib)
if err != .NONE {
if err != nil {
return {}, err
}
inst.operand_count += 1
@@ -1058,7 +1059,7 @@ decode :: proc(
// Phase 1: Parse prefixes
err := decode_prefixes(&state)
if err != .NONE {
if err != nil {
append(errors, Error{inst_idx = u32(len(instructions)), code = err})
has_errors = true
break
@@ -1102,7 +1103,7 @@ decode :: proc(
entry: ^Decode_Entry
vex_entry: ^VEX_Decode_Entry
entry, vex_entry, err = decode_opcode(&state)
if err != .NONE {
if err != nil {
append(errors, Error{inst_idx = u32(len(instructions)), code = err})
has_errors = true
break
@@ -1118,7 +1119,7 @@ decode :: proc(
has_errors = true
break
}
if err != .NONE {
if err != nil {
append(errors, Error{inst_idx = u32(len(instructions)), code = err})
has_errors = true
break

View File

@@ -313,9 +313,7 @@ run_test :: proc(t: Test) -> bool {
// Make mutable copy of labels
labels_copy: [256]x86.Label_Definition
for i in 0..<len(t.labels) {
labels_copy[i] = t.labels[i]
}
copy(labels_copy[:], t.labels)
relocs: [dynamic]x86.Relocation
defer delete(relocs)
@@ -379,9 +377,7 @@ run_test :: proc(t: Test) -> bool {
defer free_exec(exec_buf)
// Copy code
for i in 0..<len(code_to_decode) {
exec_buf[i] = code_to_decode[i]
}
copy(exec_buf, code_to_decode)
// Run all test cases
for tc, case_idx in t.cases {
@@ -559,7 +555,7 @@ run_test :: proc(t: Test) -> bool {
}
// Verify mnemonics
for i in 0..<len(t.instructions) {
for _, i in t.instructions {
if !mnemonics_eq(decoded_insts[i].mnemonic, t.instructions[i].mnemonic) {
fmt.printf("%s[FAIL]%s %s - inst %d mnemonic %v != expected %v\n",
RED, RESET, t.name, i, decoded_insts[i].mnemonic, t.instructions[i].mnemonic)
@@ -2936,13 +2932,13 @@ run_prime_sieve_test :: proc() {
run_decode_only_tests :: proc() {
tests := []Test{
{name = "decode: gcc prologue", test_type = .Decode_Only, input_code = {0x55, 0x48, 0x89, 0xE5, 0x48, 0x83, 0xEC, 0x10}},
{name = "decode: xor zero idiom", test_type = .Decode_Only, input_code = {0x31, 0xC0}},
{name = "decode: rip-relative lea", test_type = .Decode_Only, input_code = {0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}},
{name = "decode: gcc prologue", test_type = .Decode_Only, input_code = {0x55, 0x48, 0x89, 0xE5, 0x48, 0x83, 0xEC, 0x10}},
{name = "decode: xor zero idiom", test_type = .Decode_Only, input_code = {0x31, 0xC0}},
{name = "decode: rip-relative lea", test_type = .Decode_Only, input_code = {0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}},
{name = "decode: conditional branch", test_type = .Decode_Only, input_code = {0x85, 0xC0, 0x74, 0x02, 0xFF, 0xC0, 0xC3}},
{name = "decode: sse", test_type = .Decode_Only, input_code = {0x0F, 0x57, 0xC0, 0x0F, 0x28, 0xC1, 0x0F, 0x58, 0xC2}},
{name = "decode: vex", test_type = .Decode_Only, input_code = {0xC5, 0xF8, 0x57, 0xC0, 0xC5, 0xF8, 0x28, 0xC1}},
{name = "decode: call/jmp", test_type = .Decode_Only, input_code = {0xE8, 0x00, 0x00, 0x00, 0x00, 0xEB, 0xF9}},
{name = "decode: sse", test_type = .Decode_Only, input_code = {0x0F, 0x57, 0xC0, 0x0F, 0x28, 0xC1, 0x0F, 0x58, 0xC2}},
{name = "decode: vex", test_type = .Decode_Only, input_code = {0xC5, 0xF8, 0x57, 0xC0, 0xC5, 0xF8, 0x28, 0xC1}},
{name = "decode: call/jmp", test_type = .Decode_Only, input_code = {0xE8, 0x00, 0x00, 0x00, 0x00, 0xEB, 0xF9}},
}
for t in tests { run_test(t) }
}
@@ -3071,22 +3067,18 @@ run_benchmarks :: proc() {
enc_start := time.now()
enc_bytes := 0
for _ in 0..<ITERATIONS {
relocs: [dynamic]x86.Relocation
errs: [dynamic]x86.Error
relocs: [dynamic]x86.Relocation; defer delete(relocs)
errs: [dynamic]x86.Error; defer delete(errs)
result := x86.encode(bench_insts, labels[:], code_buf[:], &relocs, &errs, true, 0)
enc_bytes += int(result.byte_count)
delete(relocs)
delete(errs)
}
enc_dur := time.duration_microseconds(time.since(enc_start))
// Get encoded length for decode
encoded_len: u32
{
relocs: [dynamic]x86.Relocation
errs: [dynamic]x86.Error
defer delete(relocs)
defer delete(errs)
relocs: [dynamic]x86.Relocation; defer delete(relocs)
errs: [dynamic]x86.Error; defer delete(errs)
result := x86.encode(bench_insts, labels[:], code_buf[:], &relocs, &errs, true, 0)
encoded_len = result.byte_count
}
@@ -3095,16 +3087,12 @@ run_benchmarks :: proc() {
dec_start := time.now()
dec_insts := 0
for _ in 0..<ITERATIONS {
insts: [dynamic]x86.Instruction
info: [dynamic]x86.Instruction_Info
lbls: [dynamic]x86.Label_Definition
errs: [dynamic]x86.Error
insts: [dynamic]x86.Instruction; defer delete(insts)
info: [dynamic]x86.Instruction_Info; defer delete(info)
lbls: [dynamic]x86.Label_Definition; defer delete(lbls)
errs: [dynamic]x86.Error; defer delete(errs)
x86.decode(code_buf[:encoded_len], nil, &insts, &info, &lbls, &errs)
dec_insts += len(insts)
delete(insts)
delete(info)
delete(lbls)
delete(errs)
}
dec_dur := time.duration_microseconds(time.since(dec_start))