mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-19 16:42:33 +00:00
Update doc files
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 K1–K7
|
||||
zeroing: bool | 1, // merge vs zero masking
|
||||
broadcast: Broadcast | 3,
|
||||
er_sae: u8 | 2, // embedded rounding / SAE
|
||||
mask: u8 | 3, // opmask K1–K7
|
||||
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{ … }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user