mirror of
https://github.com/odin-lang/Odin.git
synced 2026-09-02 10:13:35 +00:00
The arm64 pass turned up the same class of bug elsewhere: mnemonics named
after an encoding rather than after what an assembler accepts, and forms
that no caller can reach because the thing that tells them apart is not
checked.
mips
* The printer mapped every `_` to `.`, but MSA spells the sign qualifier
with an underscore and only the element size with a dot: `adds_s.b`,
`max_s.h`, `copy_u.w`. `adds.s.b` is rejected by an assembler. 91
mnemonics were printing text that would not reassemble. The name alone
cannot decide it -- MSA's ADDS_S_D and the FP convert CVT_S_D have the
same shape and want opposite treatment -- so the family is read off the
form's feature.
* `encode` now takes `features: Feature_Set = FEATURES_ALL` and skips
forms outside it, mirroring `decode`, which has had that parameter all
along. That asymmetry was the reason 12 mnemonics carried an ISA-variant
suffix: with no way to say which MIPS you were targeting, the pre-R6 and
R6 encodings of `mul` had to be two enum members. They are now one
mnemonic with two forms. Eight of the twelve did not even need the
feature filter -- pre-R6 MADD takes rs,rt while the PS2 MMI MADD takes
rd,rs,rt, so operand matching alone separates them. Verified against
llvm-mc: pre-R6 `mul` 712a4002, R6 `mul` 012a4098, `madd $t1,$t2`
712a0000. The printer's hand-written override table is gone.
arm32
* 20 `*_LANE` mnemonics folded into their base. The lane form differs from
the base in an operand TYPE already (DPR_ELEM vs DPR), so the matcher
could always tell them apart; the split only cost us the printed name,
which was the enum name verbatim -- `vqdmulh_lane`, which no assembler
takes. VMOV/VLD1-4/VST1-4 are left alone: their lane forms collide with
the base because register lists and lane indices are not modelled.
riscv
* ZEXT_H and REV8 each carry an RV32 and an RV64 encoding with identical
operands, and the forms were already tagged rv32_only / rv64_only -- the
encoder just never looked. `encode` now takes `xlen: XLEN = .RV64` and
filters, so the RV64 encodings are reachable at all: zext.h 0805c53b and
rev8 6b85d513, both confirmed against llvm-mc.
mos6502
* SAX_NMOS folded into SAX. The undocumented NMOS store-A&X and the
HuC6280 register swap share the mnemonic `sax`; one takes a memory
operand and the other takes none, so they are just two form sets.
Verified: every rexcode suite matches HEAD exactly, all 13 packages build,
and MIPS mnemonics llvm-mc does not recognise drop from 448 to 354.
Still open: arm32 has 201 form signatures no caller can select, because the
NEON data type (.i8/.i16/.f32) is not an operand -- `inst_vadd(d0,d1,d2)`
always yields the first form, and only a decoder-supplied form_id hint can
pick another. 38 arm32 mnemonics still carry encoding-shaped names
(VPADD_F, VCEQ_Z, VLDRB_GATHER, VMOV_Q_R, ...).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
362 lines
13 KiB
Odin
362 lines
13 KiB
Odin
// rexcode · Brendan Punsky (dotbmp@github), original author
|
|
|
|
package rexcode_riscv
|
|
|
|
import "core:rexcode/isa"
|
|
|
|
// =============================================================================
|
|
// RISC-V DECODER
|
|
// =============================================================================
|
|
//
|
|
// Two passes, mirroring mips/decoder.odin. The RISC-V-specific bits:
|
|
//
|
|
// * Two-level dispatch: primary opcode (bits 6-0) directly indexes
|
|
// DECODE_INDEX_OPCODE[128]; opcode 0x53 (OP-FP) is sub-bucketed
|
|
// further by funct7 (bits 31-25). Within each bucket, linear scan
|
|
// of `(word & e.mask) == e.bits`.
|
|
//
|
|
// * XLEN filter: the decoder takes an XLEN parameter (.RV32 / .RV64);
|
|
// entries flagged `rv32_only` are skipped when decoding RV64, and
|
|
// vice versa.
|
|
//
|
|
// * Scattered immediates: B-type and J-type targets are reconstructed
|
|
// via gather_b / gather_j helpers in encoder.odin and emitted as
|
|
// RELATIVE-kind operands carrying the absolute byte target.
|
|
|
|
XLEN :: enum u8 {
|
|
RV32,
|
|
RV64,
|
|
}
|
|
|
|
Instruction_Info :: struct {
|
|
offset: u32,
|
|
decode_entry: u16,
|
|
_: u16,
|
|
}
|
|
#assert(size_of(Instruction_Info) == 8)
|
|
|
|
decode :: proc(
|
|
data: []u8,
|
|
relocs: []Relocation,
|
|
instructions: ^[dynamic]Instruction,
|
|
inst_info: ^[dynamic]Instruction_Info,
|
|
label_defs: ^[dynamic]Label_Definition,
|
|
errors: ^[dynamic]Error,
|
|
xlen: XLEN = .RV64,
|
|
) -> (byte_count: u32, ok: bool) {
|
|
n_bytes := u32(len(data)) & ~u32(1) // align to halfword (RVC is 2-byte)
|
|
errors_start := u32(len(errors))
|
|
|
|
pending_branches: [dynamic]isa.Branch_Target
|
|
defer delete(pending_branches)
|
|
|
|
for byte_count < n_bytes {
|
|
// Read the first halfword; bits[1:0] != 11 means compressed (2 bytes).
|
|
hword_lo := read_u16_le(data, byte_count)
|
|
ilen: u32 = 4
|
|
word: u32
|
|
if (hword_lo & 0x3) != 0x3 {
|
|
ilen = 2
|
|
word = u32(hword_lo)
|
|
} else {
|
|
if byte_count + 4 > n_bytes { break }
|
|
word = read_u32_le(data, byte_count)
|
|
}
|
|
|
|
inst: Instruction
|
|
info: Instruction_Info
|
|
entry_idx := decode_one_inline(word, byte_count, xlen, ilen == 2, &inst, &info)
|
|
|
|
if entry_idx < 0 {
|
|
append(errors, Error{inst_idx = byte_count, code = .INVALID_OPCODE})
|
|
inst = Instruction{mnemonic = .INVALID, length = u8(ilen)}
|
|
info = Instruction_Info{offset = byte_count}
|
|
} else {
|
|
inst.length = u8(ilen)
|
|
inst_idx_for_branches := u32(len(instructions))
|
|
for slot in 0..<inst.operand_count {
|
|
op := &inst.ops[slot]
|
|
if op.kind == .RELATIVE && op.relative >= 0 {
|
|
append(&pending_branches, isa.Branch_Target{
|
|
inst_idx = inst_idx_for_branches,
|
|
op_idx = slot,
|
|
target = u32(op.relative),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
append(instructions, inst)
|
|
append(inst_info, info)
|
|
byte_count += ilen
|
|
}
|
|
|
|
isa.infer_labels_from_branches(pending_branches[:], byte_count, label_defs, relocs)
|
|
ok = u32(len(errors)) == errors_start
|
|
return
|
|
}
|
|
|
|
// =============================================================================
|
|
// Internal
|
|
// =============================================================================
|
|
|
|
@(private="file")
|
|
decode_one_inline :: #force_inline proc "contextless" (
|
|
word: u32, pc: u32, xlen: XLEN, compressed: bool,
|
|
inst: ^Instruction, info: ^Instruction_Info,
|
|
) -> int {
|
|
range: Decode_Index
|
|
if compressed {
|
|
// RVC: 5-bit dispatch key from (op[1:0], funct3[15:13]).
|
|
// op==0,1,2 + funct3=0..7 -> 24 buckets.
|
|
key := (word & 0x3) | ((word >> 13) & 0x7) << 2
|
|
range = DECODE_INDEX_RVC[key]
|
|
} else {
|
|
opcode := u8(word & 0x7F)
|
|
if opcode == 0x53 {
|
|
funct7 := u8((word >> 25) & 0x7F)
|
|
range = DECODE_INDEX_OP_FP[funct7]
|
|
} else {
|
|
range = DECODE_INDEX_OPCODE[opcode]
|
|
}
|
|
}
|
|
|
|
if range.count == 0 { return -1 }
|
|
|
|
base := int(range.start)
|
|
cnt := int(range.count)
|
|
matched_idx := -1
|
|
for i in 0..<cnt {
|
|
e := &DECODE_ENTRIES[base + i]
|
|
if !xlen_accepts(xlen, e.flags) { continue }
|
|
if (word & e.mask) == e.bits {
|
|
matched_idx = base + i
|
|
break
|
|
}
|
|
}
|
|
if matched_idx < 0 { return -1 }
|
|
|
|
entry := &DECODE_ENTRIES[matched_idx]
|
|
inst.mnemonic = entry.mnemonic
|
|
inst.length = 4
|
|
inst.flags = {}
|
|
|
|
cnt_used: u8 = 0
|
|
if entry.ops[0] != .NONE {
|
|
inst.ops[0] = extract_operand_inline(word, pc, entry.ops[0], entry.enc[0])
|
|
cnt_used = 1
|
|
if entry.ops[1] != .NONE {
|
|
inst.ops[1] = extract_operand_inline(word, pc, entry.ops[1], entry.enc[1])
|
|
cnt_used = 2
|
|
if entry.ops[2] != .NONE {
|
|
inst.ops[2] = extract_operand_inline(word, pc, entry.ops[2], entry.enc[2])
|
|
cnt_used = 3
|
|
if entry.ops[3] != .NONE {
|
|
inst.ops[3] = extract_operand_inline(word, pc, entry.ops[3], entry.enc[3])
|
|
cnt_used = 4
|
|
}
|
|
}
|
|
}
|
|
}
|
|
inst.operand_count = cnt_used
|
|
|
|
info.offset = pc
|
|
info.decode_entry = u16(matched_idx)
|
|
return matched_idx
|
|
}
|
|
|
|
// Shared with the encoder: an rv32_only / rv64_only form is only selectable
|
|
// when the target XLEN matches.
|
|
@(private)
|
|
xlen_accepts :: #force_inline proc "contextless" (xlen: XLEN, f: Encoding_Flags) -> bool {
|
|
if f.rv32_only && xlen != .RV32 { return false }
|
|
if f.rv64_only && xlen != .RV64 { return false }
|
|
return true
|
|
}
|
|
|
|
@(private="file")
|
|
extract_operand_inline :: #force_inline proc "contextless" (
|
|
word: u32, pc: u32, ot: Operand_Type, en: Operand_Encoding,
|
|
) -> Operand {
|
|
switch en {
|
|
case .NONE:
|
|
return {}
|
|
|
|
// ---- Register slots ----------------------------------------------------
|
|
case .RD:
|
|
return reg_operand(decode_reg(word, 7, ot))
|
|
case .RS1:
|
|
return reg_operand(decode_reg(word, 15, ot))
|
|
case .RS2:
|
|
return reg_operand(decode_reg(word, 20, ot))
|
|
case .RS3:
|
|
return reg_operand(decode_reg(word, 27, ot))
|
|
|
|
// ---- Shift amounts -----------------------------------------------------
|
|
case .SHAMT5:
|
|
return Operand{immediate = i64((word >> 20) & 0x1F), kind = .IMMEDIATE, size = 1}
|
|
case .SHAMT6:
|
|
return Operand{immediate = i64((word >> 20) & 0x3F), kind = .IMMEDIATE, size = 1}
|
|
|
|
// ---- Immediates --------------------------------------------------------
|
|
case .IMM_I:
|
|
return Operand{immediate = i64(gather_i(word)), kind = .IMMEDIATE, size = 2}
|
|
case .IMM_S:
|
|
return Operand{immediate = i64(gather_s(word)), kind = .IMMEDIATE, size = 2}
|
|
case .IMM_U:
|
|
return Operand{immediate = i64((word >> 12) & 0xFFFFF), kind = .IMMEDIATE, size = 4}
|
|
case .IMM_B:
|
|
target := u32(i32(pc) + gather_b(word))
|
|
return Operand{relative = i64(target), kind = .RELATIVE, size = 2}
|
|
case .IMM_J:
|
|
target := u32(i32(pc) + gather_j(word))
|
|
return Operand{relative = i64(target), kind = .RELATIVE, size = 4}
|
|
|
|
// ---- Memory operand variants ------------------------------------------
|
|
case .OFFSET_BASE_I:
|
|
base := decode_reg(word, 15, .GPR)
|
|
disp := gather_i(word)
|
|
return Operand{mem = Memory{base = base, disp = disp}, kind = .MEMORY, size = 4}
|
|
case .OFFSET_BASE_S:
|
|
base := decode_reg(word, 15, .GPR)
|
|
disp := gather_s(word)
|
|
return Operand{mem = Memory{base = base, disp = disp}, kind = .MEMORY, size = 4}
|
|
case .OFFSET_BASE_A:
|
|
base := decode_reg(word, 15, .GPR)
|
|
return Operand{mem = Memory{base = base, disp = 0}, kind = .MEMORY, size = 4}
|
|
|
|
// ---- Specialty fields --------------------------------------------------
|
|
case .CSR_FIELD:
|
|
return Operand{immediate = i64((word >> 20) & 0xFFF), kind = .IMMEDIATE, size = 2}
|
|
case .ZIMM_FIELD:
|
|
return Operand{immediate = i64((word >> 15) & 0x1F), kind = .IMMEDIATE, size = 1}
|
|
case .FENCE_PRED:
|
|
return Operand{immediate = i64((word >> 24) & 0xF), kind = .IMMEDIATE, size = 1}
|
|
case .FENCE_SUCC:
|
|
return Operand{immediate = i64((word >> 20) & 0xF), kind = .IMMEDIATE, size = 1}
|
|
case .ROUND_FIELD:
|
|
return Operand{immediate = i64((word >> 12) & 0x7), kind = .IMMEDIATE, size = 1}
|
|
case .AQRL:
|
|
return Operand{immediate = i64((word >> 25) & 0x3), kind = .IMMEDIATE, size = 1}
|
|
|
|
// ---- C extension register slots ---------------------------------------
|
|
case .C_RD_RS1:
|
|
return reg_operand(decode_reg(word, 7, ot))
|
|
case .C_RS2:
|
|
return reg_operand(decode_reg(word, 2, ot))
|
|
case .C_RD_PRIMED, .C_RS2_PRIMED:
|
|
hw := u16((word >> 2) & 0x7) + 8
|
|
cls := u16(REG_GPR)
|
|
if ot == .FPR_C { cls = REG_FPR }
|
|
return reg_operand(Register(cls | hw))
|
|
case .C_RS1_PRIMED, .C_RD_RS1_PRIMED:
|
|
hw := u16((word >> 7) & 0x7) + 8
|
|
cls := u16(REG_GPR)
|
|
if ot == .FPR_C { cls = REG_FPR }
|
|
return reg_operand(Register(cls | hw))
|
|
|
|
// ---- C extension immediates -------------------------------------------
|
|
case .C_IMM_CI_S:
|
|
v := i32(((word >> 12) & 0x1) << 5 | ((word >> 2) & 0x1F))
|
|
if v & 0x20 != 0 { v |= ~i32(0x3F) }
|
|
return Operand{immediate = i64(v), kind = .IMMEDIATE, size = 1}
|
|
case .C_IMM_CI_U:
|
|
v := u32(((word >> 12) & 0x1) << 5 | ((word >> 2) & 0x1F))
|
|
return Operand{immediate = i64(v), kind = .IMMEDIATE, size = 1}
|
|
case .C_IMM_CIW:
|
|
v := i64(((word >> 11) & 0x3) << 4 |
|
|
((word >> 7) & 0xF) << 6 |
|
|
((word >> 6) & 0x1) << 2 |
|
|
((word >> 5) & 0x1) << 3)
|
|
return Operand{immediate = v, kind = .IMMEDIATE, size = 2}
|
|
case .C_IMM_LUI:
|
|
v := i32(((word >> 12) & 0x1) << 17 | ((word >> 2) & 0x1F) << 12)
|
|
if v & 0x20000 != 0 { v |= ~i32(0x3FFFF) }
|
|
return Operand{immediate = i64(v), kind = .IMMEDIATE, size = 4}
|
|
case .C_IMM_ADDI16SP:
|
|
v := i32(((word >> 12) & 0x1) << 9 |
|
|
((word >> 6) & 0x1) << 4 |
|
|
((word >> 5) & 0x1) << 6 |
|
|
((word >> 3) & 0x3) << 7 |
|
|
((word >> 2) & 0x1) << 5)
|
|
if v & 0x200 != 0 { v |= ~i32(0x3FF) }
|
|
return Operand{immediate = i64(v), kind = .IMMEDIATE, size = 2}
|
|
case .C_IMM_CSS_W:
|
|
v := i64(((word >> 9) & 0xF) << 2 | ((word >> 7) & 0x3) << 6)
|
|
return Operand{immediate = v, kind = .IMMEDIATE, size = 1}
|
|
case .C_IMM_CSS_D:
|
|
v := i64(((word >> 10) & 0x7) << 3 | ((word >> 7) & 0x7) << 6)
|
|
return Operand{immediate = v, kind = .IMMEDIATE, size = 2}
|
|
case .C_IMM_CL_W:
|
|
v := i64(((word >> 10) & 0x7) << 3 | ((word >> 6) & 0x1) << 2 | ((word >> 5) & 0x1) << 6)
|
|
return Operand{immediate = v, kind = .IMMEDIATE, size = 1}
|
|
case .C_IMM_CL_D:
|
|
v := i64(((word >> 10) & 0x7) << 3 | ((word >> 5) & 0x3) << 6)
|
|
return Operand{immediate = v, kind = .IMMEDIATE, size = 2}
|
|
|
|
// ---- C extension memory operands --------------------------------------
|
|
case .C_OFFSET_BASE_W:
|
|
base := Register(REG_GPR | u16(((word >> 7) & 0x7) + 8))
|
|
disp := i32(((word >> 10) & 0x7) << 3 | ((word >> 6) & 0x1) << 2 | ((word >> 5) & 0x1) << 6)
|
|
return Operand{mem = Memory{base = base, disp = disp}, kind = .MEMORY, size = 4}
|
|
case .C_OFFSET_BASE_D:
|
|
base := Register(REG_GPR | u16(((word >> 7) & 0x7) + 8))
|
|
disp := i32(((word >> 10) & 0x7) << 3 | ((word >> 5) & 0x3) << 6)
|
|
return Operand{mem = Memory{base = base, disp = disp}, kind = .MEMORY, size = 4}
|
|
case .C_SP_OFFSET_W:
|
|
disp := i32(((word >> 12) & 0x1) << 5 | ((word >> 4) & 0x7) << 2 | ((word >> 2) & 0x3) << 6)
|
|
return Operand{mem = Memory{base = SP, disp = disp}, kind = .MEMORY, size = 4}
|
|
case .C_SP_OFFSET_D:
|
|
disp := i32(((word >> 12) & 0x1) << 5 | ((word >> 5) & 0x3) << 3 | ((word >> 2) & 0x7) << 6)
|
|
return Operand{mem = Memory{base = SP, disp = disp}, kind = .MEMORY, size = 4}
|
|
|
|
// ---- C extension branches/jumps ---------------------------------------
|
|
case .C_BRANCH9:
|
|
target := u32(i32(pc) + gather_c_branch(word))
|
|
return Operand{relative = i64(target), kind = .RELATIVE, size = 2}
|
|
case .C_BRANCH12:
|
|
target := u32(i32(pc) + gather_c_jump(word))
|
|
return Operand{relative = i64(target), kind = .RELATIVE, size = 2}
|
|
}
|
|
return {}
|
|
}
|
|
|
|
@(private="file")
|
|
decode_reg :: #force_inline proc "contextless" (word: u32, shift: u8, ot: Operand_Type) -> Register {
|
|
hw := u16((word >> shift) & 0x1F)
|
|
if ot == .FPR { return Register(REG_FPR | hw) }
|
|
return Register(REG_GPR | hw)
|
|
}
|
|
|
|
@(private="file")
|
|
reg_operand :: #force_inline proc "contextless" (r: Register) -> Operand {
|
|
return Operand{reg = r, kind = .REGISTER, size = 4}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Buffer-Sizing Helpers (let callers pre-size so the decode hot path never
|
|
// reallocates; allocates no new buffers -- only the caller's arrays grow).
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Instruction-count ceiling for `data` (base is 4 bytes, compressed (C) 2; minimum 2).
|
|
@(require_results)
|
|
decode_max_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int {
|
|
return len(data) / 2
|
|
}
|
|
|
|
// Typical-case estimate of the instruction count for `data`.
|
|
@(require_results)
|
|
decode_estimate_instruction_count :: #force_inline proc "contextless" (data: []u8) -> int {
|
|
return len(data) / 4 + 8
|
|
}
|
|
|
|
// Pre-size the caller's decode output arrays for `data` (reserves on top of any
|
|
// existing elements; nil to skip; exact=true for the ceiling, else the estimate).
|
|
decode_reserve :: proc(instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, label_defs: ^[dynamic]Label_Definition, data: []u8, exact: bool = false) {
|
|
n := exact ? decode_max_instruction_count(data) : decode_estimate_instruction_count(data)
|
|
if instructions != nil { reserve(instructions, len(instructions) + n) }
|
|
if inst_info != nil { reserve(inst_info, len(inst_info) + n) }
|
|
if label_defs != nil { reserve(label_defs, len(label_defs) + n) }
|
|
}
|