mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-20 00:52:33 +00:00
rexcode: 100% generated mnemonic-builder coverage; drop hand-written collisions
Every mnemonic with an encode form now has a generated inst_<mnem>/emit_<mnem> overload group. The per-arch generators map ALL operand types — nothing is skipped: arm64 gains shifted/extended registers (multi-param via op_shifted/op_extended), SVE Z-regs + predicates, SME tile/slice, NEON arrangements/lanes, bitmask/sysreg/pattern immediates and condition codes (427 -> 777 mnemonics); arm32 gains shifted/register-shifted regs, register lists, NEON lanes and all encoded-immediate subclasses (479 -> 592); x86 gains m80 and descriptor-table memory operands — FBLD/FBSTP, LGDT/SGDT/LIDT/SIDT, FLD/FSTP, far-indirect JMP/CALL, BOUND (1167 -> 1175). Mnemonic-specific builders are now fully generated, not hand-written: deleted the hand-written helpers the generated groups collided with — riscv inst_jal/inst_jalr, arm64 inst_b_cond/inst_cbz/inst_tbz/inst_csel, mos6502 inst_tst — and let the generators own those names (arm64 also gains inst_cbnz/tbnz/csinc/csinv/csneg). Updated the affected test call-sites. The generic operand-shape helpers (inst_r_r, inst_r_r_i, inst_ldst, ...) remain as delegation targets. Decode-only mnemonics with no encode form are correctly left without builders. ppc/ppc_vle/rsp/mos65816 were already complete. All 10 ISAs: structure + compile + tests pass; generators idempotent.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -15,18 +15,28 @@ package main
|
||||
//
|
||||
// arm32 has a very rich operand set (shifted/extended regs, register-shifted
|
||||
// register, NEON lane/vector forms, modified immediates, register lists,
|
||||
// coprocessor selectors, MVE/CDE classes, ...). Many of these have no clean
|
||||
// single-value constructor, so — like x86 skips far pointers / moffs — we only
|
||||
// emit a builder for a form when EVERY one of its operands is cleanly
|
||||
// constructible from a single typed parameter. The skipped operand classes are
|
||||
// listed in is_buildable_operand() below.
|
||||
// coprocessor selectors, MVE/CDE classes, ...). EVERY operand type maps to one
|
||||
// or more typed parameters here — there are no skipped operand classes — so a
|
||||
// builder is emitted for every form whose operands fit in <=4 slots (which is
|
||||
// every real form). The mapping mirrors what the encoder's pack_operand_inline
|
||||
// reads out of each Operand:
|
||||
//
|
||||
// plain reg -> op_reg(Register)
|
||||
// shifted reg (imm) -> op_reg_shifted(Register, Shift_Type, u8)
|
||||
// register-shifted reg -> op_reg_shifted(Register, Shift_Type, u8(reg_hw(Rs)))
|
||||
// register list -> op_reg_list(u16 mask)
|
||||
// NEON D/Q lane elem -> op_dpr_lane / op_qpr_lane (Register, u8 lane)
|
||||
// any immediate class -> op_imm(i64) (the encoder does the field packing:
|
||||
// modified-imm, barrier, endian, iflags, sysm, coproc,
|
||||
// saturating, PSR field, hint, cond-operand, ...)
|
||||
// memory -> op_mem(Memory)
|
||||
// PC-relative / loop -> op_rel_offset(i64)
|
||||
//
|
||||
// Note: arm32's Register is a single distinct-u16 type with NO per-class typed
|
||||
// enums (GPR / SPR / DPR / QPR all share the `Register` type). Every register
|
||||
// parameter is therefore `Register`. Two forms of one mnemonic that reduce to
|
||||
// the same parameter-type tuple would create an ambiguous overload set, so we
|
||||
// additionally dedup by the Odin parameter-type signature, keeping the first
|
||||
// (table-order) form.
|
||||
// the same ordered Odin parameter-type tuple would create an ambiguous overload
|
||||
// set, so we dedup by that tuple, keeping the first (table-order) form.
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
@@ -36,7 +46,7 @@ import a "../"
|
||||
|
||||
GEN_ATTRIB :: "// rexcode · Brendan Punsky (dotbmp@github), original author\n\n"
|
||||
|
||||
// Per-form operand signature (explicit, buildable operands only).
|
||||
// Per-form operand signature.
|
||||
Operand_Signature :: struct {
|
||||
types: [4]a.Operand_Type,
|
||||
mode: a.Mode,
|
||||
@@ -59,113 +69,171 @@ mnemonic_to_lower :: proc(m: a.Mnemonic) -> string {
|
||||
// Truly-implicit operands carry no user value and emit no bits. Note: a .NONE
|
||||
// encoding slot does NOT mean implicit — many real operands (immediates, some
|
||||
// regs) have enc == .NONE because the encoder derives their bit placement from
|
||||
// the operand TYPE rather than a named slot. Presence is keyed on the operand
|
||||
// type (ops[i] != .NONE); only enc == .IMPL is dropped. (The current arm32
|
||||
// table contains zero .IMPL operands; this guard is for forward-compat.)
|
||||
// the operand TYPE rather than a named slot (or, for COND-as-operand, the value
|
||||
// rides in a sibling field). Presence is keyed on the operand TYPE
|
||||
// (ops[i] != .NONE); only enc == .IMPL is dropped. (The current arm32 table
|
||||
// contains zero .IMPL operands; this guard is for forward-compat.)
|
||||
is_implicit_operand :: proc(enc: a.Operand_Encoding) -> bool {
|
||||
return enc == .IMPL
|
||||
}
|
||||
|
||||
// A build class describes the shape of the typed parameter(s) an operand needs
|
||||
// and which op_* constructor assembles it. Unlike the old generator, this is
|
||||
// TOTAL over Operand_Type: every type maps to exactly one class so no form is
|
||||
// ever skipped for operand reasons.
|
||||
Operand_Class :: enum {
|
||||
NONE, // not buildable
|
||||
REG, // single Register
|
||||
IMM, // i64 immediate
|
||||
MEM, // Memory operand
|
||||
REL, // branch target (raw i64 offset)
|
||||
REG, // op_reg(Register)
|
||||
IMM, // op_imm(i64) -- includes all encoded-imm subclasses
|
||||
MEM, // op_mem(Memory)
|
||||
REL, // op_rel_offset(i64)
|
||||
SHIFTED, // op_reg_shifted(Register, Shift_Type, u8) -- imm shift
|
||||
RSR, // op_reg_shifted(Register, Shift_Type, u8(reg_hw(Rs))) -- reg shift
|
||||
LIST, // op_reg_list(u16)
|
||||
LANE_D, // op_dpr_lane(Register, u8)
|
||||
LANE_Q, // op_qpr_lane(Register, u8)
|
||||
}
|
||||
|
||||
// Map an operand TYPE to a build class. Returns .NONE for operand types that
|
||||
// have no clean single-value constructor (these forms are skipped wholesale).
|
||||
//
|
||||
// Covered : plain GPR/FP/SIMD registers, plain numeric immediates, MEM,
|
||||
// PC-relative branch targets.
|
||||
// Skipped : shifted/RSR regs, register lists, NEON lane/vector elem forms,
|
||||
// modified immediates, NEON imm, condition-code operand, coprocessor
|
||||
// selectors, PSR field, MVE/CDE classes, special encoded immediates
|
||||
// (barrier/endian/iflags/banked/sysm/coproc/hint).
|
||||
// Map an operand TYPE to its build class. Total: every Operand_Type that can
|
||||
// appear in a form has a mapping. The encoder's operand_matches_inline /
|
||||
// pack_operand_inline define the contract each class must satisfy.
|
||||
operand_class :: proc(ot: a.Operand_Type) -> Operand_Class {
|
||||
#partial switch ot {
|
||||
// ---- Plain registers (single Register value) ----
|
||||
case .GPR, .GPR_NOPC, .GPR_NOSP, .GPR_LOW:
|
||||
return .REG
|
||||
case .SPR, .DPR, .QPR, .SPR_ELEM, .QPR_MVE:
|
||||
return .REG
|
||||
// ---- Shifted / register-shifted GPR ----
|
||||
case .GPR_SHIFTED:
|
||||
return .SHIFTED
|
||||
case .GPR_RSR:
|
||||
return .RSR
|
||||
|
||||
// ---- Plain numeric immediates (single i64 value) ----
|
||||
case .IMM, .IMM12, .IMM5, .IMM5_W, .IMM4, .IMM4_SAT, .IMM8, .IMM3, .IMM16_LO_HI:
|
||||
return .IMM
|
||||
// ---- Register lists (GPR/SPR/DPR/MVE-Q) ----
|
||||
case .GPR_LIST, .SPR_LIST, .DPR_LIST, .QPR_MVE_LIST:
|
||||
return .LIST
|
||||
|
||||
// ---- NEON lane elements ----
|
||||
case .DPR_ELEM:
|
||||
return .LANE_D
|
||||
case .QPR_ELEM:
|
||||
return .LANE_Q
|
||||
|
||||
// ---- Memory ----
|
||||
case .MEM:
|
||||
return .MEM
|
||||
|
||||
// ---- PC-relative branch targets ----
|
||||
case .REL24, .REL24_T32, .REL20, .REL11, .REL8:
|
||||
// ---- PC-relative branch targets & low-overhead-loop targets ----
|
||||
case .REL24, .REL24_T32, .REL20, .REL11, .REL8, .REL_LDR_LITERAL, .MVE_LOOP_TGT:
|
||||
return .REL
|
||||
|
||||
// ---- Plain registers (single Register value) ----
|
||||
case .GPR, .GPR_NOPC, .GPR_NOSP, .GPR_LOW,
|
||||
.SPR, .DPR, .QPR, .SPR_ELEM, .QPR_MVE, .VPR,
|
||||
.COPROC_REG, .COPROC_NUM, .CDE_VFP_REG:
|
||||
return .REG
|
||||
}
|
||||
|
||||
// Everything else is not cleanly buildable -> skip the form.
|
||||
return .NONE
|
||||
// Everything else is an immediate the encoder packs from op.immediate:
|
||||
// IMM/IMM12/IMM5/.../modified-imm/barrier/endian/iflags/banked/sysm/coproc/
|
||||
// coproc-op/NEON-imm/hint/PSR_FIELD/COND/MVE-size/MVE-vpt-mask/CDE-imm/
|
||||
// CDE-coproc, etc. All single i64 via op_imm.
|
||||
return .IMM
|
||||
}
|
||||
|
||||
// Suffix used in the procedure name for an operand type.
|
||||
// Suffix used in the procedure name for an operand type. Distinct per type so
|
||||
// proc names stay readable; overload ambiguity is handled separately by the
|
||||
// Odin param-type-tuple dedup.
|
||||
operand_suffix :: proc(ot: a.Operand_Type) -> string {
|
||||
#partial switch ot {
|
||||
case .GPR: return "r"
|
||||
case .GPR_NOPC: return "r"
|
||||
case .GPR_NOSP: return "r"
|
||||
case .GPR_LOW: return "rlo"
|
||||
case .SPR: return "s"
|
||||
case .SPR_ELEM: return "s"
|
||||
case .DPR: return "d"
|
||||
case .QPR: return "q"
|
||||
case .QPR_MVE: return "qm"
|
||||
case .IMM: return "imm"
|
||||
case .IMM12: return "imm12"
|
||||
case .IMM5: return "imm5"
|
||||
case .IMM5_W: return "imm5w"
|
||||
case .IMM4: return "imm4"
|
||||
case .IMM4_SAT: return "imm4s"
|
||||
case .IMM8: return "imm8"
|
||||
case .IMM3: return "imm3"
|
||||
case .IMM16_LO_HI: return "imm16"
|
||||
case .MEM: return "mem"
|
||||
case .REL24: return "rel"
|
||||
case .REL24_T32: return "rel"
|
||||
case .REL20: return "rel"
|
||||
case .REL11: return "rel"
|
||||
case .REL8: return "rel"
|
||||
case .GPR: return "r"
|
||||
case .GPR_NOPC: return "r"
|
||||
case .GPR_NOSP: return "r"
|
||||
case .GPR_LOW: return "rlo"
|
||||
case .GPR_SHIFTED: return "rsh"
|
||||
case .GPR_RSR: return "rsr"
|
||||
case .GPR_LIST: return "list"
|
||||
case .SPR: return "s"
|
||||
case .SPR_ELEM: return "s"
|
||||
case .SPR_LIST: return "slist"
|
||||
case .DPR: return "d"
|
||||
case .DPR_ELEM: return "dlane"
|
||||
case .DPR_LIST: return "dlist"
|
||||
case .QPR: return "q"
|
||||
case .QPR_ELEM: return "qlane"
|
||||
case .QPR_MVE: return "qm"
|
||||
case .QPR_MVE_LIST: return "qlist"
|
||||
case .VPR: return "vpr"
|
||||
case .IMM: return "imm"
|
||||
case .IMM_MOD: return "immm"
|
||||
case .IMM_T32_MOD: return "immtm"
|
||||
case .IMM12: return "imm12"
|
||||
case .IMM5: return "imm5"
|
||||
case .IMM5_W: return "imm5w"
|
||||
case .IMM4: return "imm4"
|
||||
case .IMM4_SAT: return "imm4s"
|
||||
case .IMM8: return "imm8"
|
||||
case .IMM3: return "imm3"
|
||||
case .IMM16_LO_HI: return "imm16"
|
||||
case .IMM_HINT: return "hint"
|
||||
case .IMM_BARRIER: return "barr"
|
||||
case .IMM_ENDIAN: return "end"
|
||||
case .IMM_IFLAGS: return "ifl"
|
||||
case .IMM_BANKED: return "bank"
|
||||
case .IMM_SYSM: return "sysm"
|
||||
case .IMM_COPROC: return "cp"
|
||||
case .IMM_COPROC_OP: return "cpop"
|
||||
case .NEON_IMM: return "nimm"
|
||||
case .COND: return "cond"
|
||||
case .MEM: return "mem"
|
||||
case .REL24: return "rel"
|
||||
case .REL24_T32: return "rel"
|
||||
case .REL20: return "rel"
|
||||
case .REL11: return "rel"
|
||||
case .REL8: return "rel"
|
||||
case .REL_LDR_LITERAL: return "rel"
|
||||
case .COPROC_REG: return "crd"
|
||||
case .COPROC_NUM: return "cpn"
|
||||
case .PSR_FIELD: return "psr"
|
||||
case .MVE_VPT_MASK: return "vpt"
|
||||
case .MVE_VCTP_SIZE: return "vsz"
|
||||
case .MVE_LOOP_TGT: return "loop"
|
||||
case .CDE_COPROC: return "cdec"
|
||||
case .CDE_IMM: return "cdei"
|
||||
case .CDE_VFP_REG: return "cdev"
|
||||
}
|
||||
return "x"
|
||||
}
|
||||
|
||||
// Odin parameter type for an operand type.
|
||||
operand_odin_type :: proc(ot: a.Operand_Type) -> string {
|
||||
switch operand_class(ot) {
|
||||
case .REG: return "Register"
|
||||
case .IMM: return "i64"
|
||||
case .MEM: return "Memory"
|
||||
case .REL: return "i64"
|
||||
case .NONE: return "unknown"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
// Static parameter-type lists (returned as slices into rodata, so they don't
|
||||
// alias a stack frame).
|
||||
@(rodata) PT_REG := []string{"Register"}
|
||||
@(rodata) PT_IMM := []string{"i64"}
|
||||
@(rodata) PT_MEM := []string{"Memory"}
|
||||
@(rodata) PT_REL := []string{"i64"}
|
||||
@(rodata) PT_SHIFTED := []string{"Register", "Shift_Type", "u8"}
|
||||
@(rodata) PT_RSR := []string{"Register", "Shift_Type", "Register"}
|
||||
@(rodata) PT_LIST := []string{"u16"}
|
||||
@(rodata) PT_LANE := []string{"Register", "u8"}
|
||||
|
||||
// Build the op_* expression for one operand.
|
||||
operand_expr :: proc(sb: ^strings.Builder, ot: a.Operand_Type, name: string) {
|
||||
// The ordered list of Odin parameter TYPES an operand expands to. Most operands
|
||||
// are a single param; shifted / register-shifted regs and NEON lane elems take
|
||||
// extra params (shift kind + amount, or lane index).
|
||||
operand_param_types :: proc(ot: a.Operand_Type) -> []string {
|
||||
switch operand_class(ot) {
|
||||
case .REG: fmt.sbprintf(sb, "op_reg(%s)", name)
|
||||
case .IMM: fmt.sbprintf(sb, "op_imm(%s)", name)
|
||||
case .MEM: fmt.sbprintf(sb, "op_mem(%s)", name)
|
||||
case .REL: fmt.sbprintf(sb, "op_rel_offset(%s)", name)
|
||||
case .NONE: strings.write_string(sb, "{}")
|
||||
case .REG: return PT_REG
|
||||
case .IMM: return PT_IMM
|
||||
case .MEM: return PT_MEM
|
||||
case .REL: return PT_REL
|
||||
case .SHIFTED: return PT_SHIFTED
|
||||
case .RSR: return PT_RSR
|
||||
case .LIST: return PT_LIST
|
||||
case .LANE_D: return PT_LANE
|
||||
case .LANE_Q: return PT_LANE
|
||||
}
|
||||
return PT_REG
|
||||
}
|
||||
|
||||
// ---- Signature extraction ---------------------------------------------------
|
||||
|
||||
// Returns ok=false if the form has any non-buildable explicit operand, or if it
|
||||
// has more than 4 explicit operands. An all-NONE form yields count=0 (e.g. NOP).
|
||||
// Returns ok=false only if the form has more than 4 explicit operands (no real
|
||||
// arm32 form does). An all-NONE form yields count=0 (e.g. NOP). No form is
|
||||
// skipped for operand-type reasons — operand_class is total.
|
||||
form_signature :: proc(form: a.Encoding) -> (sig: Operand_Signature, ok: bool) {
|
||||
sig.mode = form.mode
|
||||
sig.length = a.inst_size_from_bits(form.bits, form.mode)
|
||||
@@ -173,9 +241,6 @@ form_signature :: proc(form: a.Encoding) -> (sig: Operand_Signature, ok: bool) {
|
||||
if ot == .NONE { continue }
|
||||
if is_implicit_operand(form.enc[i]) { continue }
|
||||
|
||||
if operand_class(ot) == .NONE {
|
||||
return {}, false
|
||||
}
|
||||
if sig.count >= 4 {
|
||||
return {}, false
|
||||
}
|
||||
@@ -207,43 +272,128 @@ proc_name_for :: proc(m: a.Mnemonic, sig: Operand_Signature) -> string {
|
||||
return strings.clone(strings.to_string(sb))
|
||||
}
|
||||
|
||||
// Parameter names: dst / src / src2 / src3 for register & memory operands;
|
||||
// imm / imm2 for immediates; offset for branch targets.
|
||||
param_names :: proc(sig: Operand_Signature) -> [4]string {
|
||||
out: [4]string
|
||||
src_count := 0
|
||||
imm_count := 0
|
||||
// One concrete parameter: a name and an Odin type.
|
||||
Param :: struct {
|
||||
name: string,
|
||||
type: string,
|
||||
}
|
||||
|
||||
// Flatten a signature into the ordered list of concrete (name, type) params.
|
||||
// Names are derived per role so the generated source is readable and unique
|
||||
// within a proc:
|
||||
// register/memory dst -> dst; subsequent -> src, src2, ...
|
||||
// immediates -> imm, imm2, ...
|
||||
// branch targets -> offset
|
||||
// shift kind / amount -> shift / amount (suffixed when repeated)
|
||||
// lane index -> lane (suffixed when repeated)
|
||||
// register list mask -> regs (suffixed when repeated)
|
||||
// RSR shift register -> rs (suffixed when repeated)
|
||||
flatten_params :: proc(sig: Operand_Signature) -> [dynamic]Param {
|
||||
out: [dynamic]Param
|
||||
src_count := 0
|
||||
imm_count := 0
|
||||
off_count := 0
|
||||
shift_count := 0
|
||||
amt_count := 0
|
||||
lane_count := 0
|
||||
list_count := 0
|
||||
rs_count := 0
|
||||
|
||||
uniq :: proc(base: string, n: ^int) -> string {
|
||||
defer n^ += 1
|
||||
return n^ == 0 ? strings.clone(base) : fmt.aprintf("%s%d", base, n^ + 1)
|
||||
}
|
||||
|
||||
for i in 0..<sig.count {
|
||||
switch operand_class(sig.types[i]) {
|
||||
ot := sig.types[i]
|
||||
switch operand_class(ot) {
|
||||
case .IMM:
|
||||
out[i] = imm_count == 0 ? "imm" : fmt.tprintf("imm%d", imm_count + 1)
|
||||
imm_count += 1
|
||||
append(&out, Param{uniq("imm", &imm_count), "i64"})
|
||||
case .REL:
|
||||
out[i] = "offset"
|
||||
case .REG, .MEM:
|
||||
append(&out, Param{uniq("offset", &off_count), "i64"})
|
||||
case .MEM:
|
||||
if i == 0 {
|
||||
out[i] = "dst"
|
||||
append(&out, Param{strings.clone("dst"), "Memory"})
|
||||
} else {
|
||||
out[i] = src_count == 0 ? "src" : fmt.tprintf("src%d", src_count + 1)
|
||||
src_count += 1
|
||||
append(&out, Param{uniq("src", &src_count), "Memory"})
|
||||
}
|
||||
case .NONE:
|
||||
out[i] = "_x"
|
||||
case .REG:
|
||||
if i == 0 {
|
||||
append(&out, Param{strings.clone("dst"), "Register"})
|
||||
} else {
|
||||
append(&out, Param{uniq("src", &src_count), "Register"})
|
||||
}
|
||||
case .SHIFTED:
|
||||
rname := i == 0 ? strings.clone("dst") : uniq("src", &src_count)
|
||||
append(&out, Param{rname, "Register"})
|
||||
append(&out, Param{uniq("shift", &shift_count), "Shift_Type"})
|
||||
append(&out, Param{uniq("amount", &amt_count), "u8"})
|
||||
case .RSR:
|
||||
rname := i == 0 ? strings.clone("dst") : uniq("src", &src_count)
|
||||
append(&out, Param{rname, "Register"})
|
||||
append(&out, Param{uniq("shift", &shift_count), "Shift_Type"})
|
||||
append(&out, Param{uniq("rs", &rs_count), "Register"})
|
||||
case .LIST:
|
||||
append(&out, Param{uniq("regs", &list_count), "u16"})
|
||||
case .LANE_D, .LANE_Q:
|
||||
rname := i == 0 ? strings.clone("dst") : uniq("src", &src_count)
|
||||
append(&out, Param{rname, "Register"})
|
||||
append(&out, Param{uniq("lane", &lane_count), "u8"})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Type-signature key for overload-ambiguity dedup: the tuple of Odin parameter
|
||||
// types plus the mnemonic. Two forms with identical param-type tuples cannot
|
||||
// coexist in one overload set, so we keep only the first.
|
||||
// Build the op_* expression for one operand, consuming params from `ps` starting
|
||||
// at index `pi`; returns the next param index.
|
||||
operand_expr :: proc(sb: ^strings.Builder, ot: a.Operand_Type, ps: []Param, pi: int) -> int {
|
||||
switch operand_class(ot) {
|
||||
case .REG:
|
||||
fmt.sbprintf(sb, "op_reg(%s)", ps[pi].name)
|
||||
return pi + 1
|
||||
case .IMM:
|
||||
fmt.sbprintf(sb, "op_imm(%s)", ps[pi].name)
|
||||
return pi + 1
|
||||
case .MEM:
|
||||
fmt.sbprintf(sb, "op_mem(%s)", ps[pi].name)
|
||||
return pi + 1
|
||||
case .REL:
|
||||
fmt.sbprintf(sb, "op_rel_offset(%s)", ps[pi].name)
|
||||
return pi + 1
|
||||
case .SHIFTED:
|
||||
fmt.sbprintf(sb, "op_reg_shifted(%s, %s, %s)", ps[pi].name, ps[pi+1].name, ps[pi+2].name)
|
||||
return pi + 3
|
||||
case .RSR:
|
||||
// op_reg_shifted's amount slot carries the Rs hardware number for
|
||||
// register-shifted-register forms (see pack_operand_inline / RM_A32).
|
||||
fmt.sbprintf(sb, "op_reg_shifted(%s, %s, u8(reg_hw(%s)))", ps[pi].name, ps[pi+1].name, ps[pi+2].name)
|
||||
return pi + 3
|
||||
case .LIST:
|
||||
fmt.sbprintf(sb, "op_reg_list(%s)", ps[pi].name)
|
||||
return pi + 1
|
||||
case .LANE_D:
|
||||
fmt.sbprintf(sb, "op_dpr_lane(%s, %s)", ps[pi].name, ps[pi+1].name)
|
||||
return pi + 2
|
||||
case .LANE_Q:
|
||||
fmt.sbprintf(sb, "op_qpr_lane(%s, %s)", ps[pi].name, ps[pi+1].name)
|
||||
return pi + 2
|
||||
}
|
||||
strings.write_string(sb, "{}")
|
||||
return pi + 1
|
||||
}
|
||||
|
||||
// Type-signature key for overload-ambiguity dedup: the ordered tuple of Odin
|
||||
// parameter types plus the mnemonic. Two forms with identical param-type tuples
|
||||
// cannot coexist in one overload set, so we keep only the first.
|
||||
type_sig_key :: proc(m: a.Mnemonic, sig: Operand_Signature) -> string {
|
||||
sb := strings.builder_make()
|
||||
defer strings.builder_destroy(&sb)
|
||||
fmt.sbprintf(&sb, "%v|", m)
|
||||
for i in 0..<sig.count {
|
||||
strings.write_string(&sb, operand_odin_type(sig.types[i]))
|
||||
strings.write_byte(&sb, ',')
|
||||
for t in operand_param_types(sig.types[i]) {
|
||||
strings.write_string(&sb, t)
|
||||
strings.write_byte(&sb, ',')
|
||||
}
|
||||
}
|
||||
return strings.clone(strings.to_string(sb))
|
||||
}
|
||||
@@ -257,17 +407,17 @@ type_sig_key :: proc(m: a.Mnemonic, sig: Operand_Signature) -> string {
|
||||
// causes the encoder to reject them. Baking `length` from the form via
|
||||
// inst_size_from_bits() keeps A32 (4), T16 (2) and T32-wide (4) all correct.
|
||||
// cond defaults to 14 (AL); sets_flags / wide are left at their zero defaults.
|
||||
inst_body :: proc(sb: ^strings.Builder, entry: Proc_Entry) {
|
||||
inst_body :: proc(sb: ^strings.Builder, entry: Proc_Entry, ps: []Param) {
|
||||
sig := entry.sig
|
||||
names := param_names(sig)
|
||||
mn := fmt.tprintf("%v", entry.mnemonic)
|
||||
|
||||
ops := strings.builder_make()
|
||||
defer strings.builder_destroy(&ops)
|
||||
pi := 0
|
||||
for i in 0..<4 {
|
||||
if i > 0 { strings.write_string(&ops, ", ") }
|
||||
if i < sig.count {
|
||||
operand_expr(&ops, sig.types[i], names[i])
|
||||
pi = operand_expr(&ops, sig.types[i], ps, pi)
|
||||
} else {
|
||||
strings.write_string(&ops, "{}")
|
||||
}
|
||||
@@ -279,24 +429,24 @@ inst_body :: proc(sb: ^strings.Builder, entry: Proc_Entry) {
|
||||
|
||||
// ---- Emitters ---------------------------------------------------------------
|
||||
|
||||
write_param_list :: proc(sb: ^strings.Builder, ps: []Param) {
|
||||
for p, i in ps {
|
||||
if i > 0 { strings.write_string(sb, ", ") }
|
||||
fmt.sbprintf(sb, "%s: %s", p.name, p.type)
|
||||
}
|
||||
}
|
||||
|
||||
// inst_ procedure (compact one line). #force_inline contextless like x86.
|
||||
write_inst_proc :: proc(sb: ^strings.Builder, entry: Proc_Entry, pad: int) {
|
||||
sig := entry.sig
|
||||
names := param_names(sig)
|
||||
|
||||
params := strings.builder_make()
|
||||
defer strings.builder_destroy(¶ms)
|
||||
for i in 0..<sig.count {
|
||||
if i > 0 { strings.write_string(¶ms, ", ") }
|
||||
fmt.sbprintf(¶ms, "%s: %s", names[i], operand_odin_type(sig.types[i]))
|
||||
}
|
||||
ps := flatten_params(entry.sig)
|
||||
defer { for p in ps { delete(p.name) }; delete(ps) }
|
||||
|
||||
strings.write_string(sb, entry.proc_name)
|
||||
for n := pad - len(entry.proc_name); n > 0; n -= 1 { strings.write_byte(sb, ' ') }
|
||||
strings.write_string(sb, " :: #force_inline proc \"contextless\" (")
|
||||
strings.write_string(sb, strings.to_string(params))
|
||||
write_param_list(sb, ps[:])
|
||||
strings.write_string(sb, ") -> Instruction { return ")
|
||||
inst_body(sb, entry)
|
||||
inst_body(sb, entry, ps[:])
|
||||
strings.write_string(sb, " }\n")
|
||||
}
|
||||
|
||||
@@ -304,28 +454,24 @@ write_inst_proc :: proc(sb: ^strings.Builder, entry: Proc_Entry, pad: int) {
|
||||
// append needs context. arm32 has no encoder-level emit_* helpers, so these
|
||||
// simply wrap the inst_ builder.
|
||||
write_emit_proc :: proc(sb: ^strings.Builder, entry: Proc_Entry, pad: int) {
|
||||
sig := entry.sig
|
||||
names := param_names(sig)
|
||||
ps := flatten_params(entry.sig)
|
||||
defer { for p in ps { delete(p.name) }; delete(ps) }
|
||||
|
||||
emit_name := strings.concatenate({"emit_", entry.proc_name[5:]})
|
||||
defer delete(emit_name)
|
||||
|
||||
params := strings.builder_make()
|
||||
defer strings.builder_destroy(¶ms)
|
||||
strings.write_string(¶ms, "instructions: ^[dynamic]Instruction")
|
||||
for i in 0..<sig.count {
|
||||
fmt.sbprintf(¶ms, ", %s: %s", names[i], operand_odin_type(sig.types[i]))
|
||||
}
|
||||
|
||||
strings.write_string(sb, emit_name)
|
||||
for n := pad - len(entry.proc_name); n > 0; n -= 1 { strings.write_byte(sb, ' ') }
|
||||
strings.write_string(sb, " :: #force_inline proc(")
|
||||
strings.write_string(sb, strings.to_string(params))
|
||||
strings.write_string(sb, " :: #force_inline proc(instructions: ^[dynamic]Instruction")
|
||||
for p in ps {
|
||||
fmt.sbprintf(sb, ", %s: %s", p.name, p.type)
|
||||
}
|
||||
strings.write_string(sb, ") { append(instructions, ")
|
||||
strings.write_string(sb, entry.proc_name)
|
||||
strings.write_byte(sb, '(')
|
||||
for i in 0..<sig.count {
|
||||
for p, i in ps {
|
||||
if i > 0 { strings.write_string(sb, ", ") }
|
||||
strings.write_string(sb, names[i])
|
||||
strings.write_string(sb, p.name)
|
||||
}
|
||||
strings.write_string(sb, ")) }\n")
|
||||
}
|
||||
@@ -347,13 +493,23 @@ main :: proc() {
|
||||
defer delete(seen_names)
|
||||
defer delete(seen_type_sigs)
|
||||
|
||||
total_forms := 0
|
||||
skipped_forms := 0
|
||||
// Zero-form mnemonics (no encode forms at all) get no builder.
|
||||
zero_form: [dynamic]a.Mnemonic
|
||||
defer delete(zero_form)
|
||||
|
||||
total_forms := 0
|
||||
skipped_forms := 0 // forms dropped for >4 operands (expected: 0)
|
||||
dropped_overload := 0 // forms collapsed into an existing overload signature
|
||||
|
||||
for m in a.Mnemonic {
|
||||
if m == .INVALID { continue }
|
||||
if m == ._COUNT { continue } // enum-size sentinel, not a real mnemonic
|
||||
|
||||
_run := a.ENCODE_RUNS[u16(m)]
|
||||
if _run.count == 0 {
|
||||
append(&zero_form, m)
|
||||
continue
|
||||
}
|
||||
forms := a.ENCODE_FORMS[_run.start:][:_run.count]
|
||||
|
||||
for form in forms {
|
||||
@@ -366,12 +522,16 @@ main :: proc() {
|
||||
}
|
||||
|
||||
name := proc_name_for(m, sig)
|
||||
if name in seen_names { continue }
|
||||
if name in seen_names { delete(name); continue }
|
||||
|
||||
tkey := type_sig_key(m, sig)
|
||||
if tkey in seen_type_sigs {
|
||||
// Same param-type tuple already taken for this mnemonic; a second
|
||||
// one would make the overload set ambiguous.
|
||||
// one would make the overload set ambiguous (all arm32 register
|
||||
// classes share the single `Register` type).
|
||||
dropped_overload += 1
|
||||
delete(name)
|
||||
delete(tkey)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -462,10 +622,13 @@ main :: proc() {
|
||||
total_procs := 0
|
||||
for m in mlist { total_procs += len(procs_by_mnemonic[m]) }
|
||||
fmt.println("Generated mnemonic_builders.odin successfully!")
|
||||
fmt.printf("Mnemonics with builders: %d\n", len(mlist))
|
||||
fmt.printf("Procedures generated: %d\n", total_procs)
|
||||
fmt.printf("Forms total: %d\n", total_forms)
|
||||
fmt.printf("Forms skipped (operand): %d\n", skipped_forms)
|
||||
fmt.printf("Mnemonics with builders: %d\n", len(mlist))
|
||||
fmt.printf("Procedures generated: %d\n", total_procs)
|
||||
fmt.printf("Forms total: %d\n", total_forms)
|
||||
fmt.printf("Forms skipped (>4 ops): %d\n", skipped_forms)
|
||||
fmt.printf("Forms folded (overload): %d\n", dropped_overload)
|
||||
fmt.printf("Zero-form mnemonics: %d\n", len(zero_form))
|
||||
for zm in zero_form { fmt.printf(" %v\n", zm) }
|
||||
} else {
|
||||
fmt.eprintln("Failed to write mnemonic_builders.odin")
|
||||
os.exit(1)
|
||||
@@ -482,13 +645,18 @@ write_header :: proc(sb: ^strings.Builder) {
|
||||
// Generated by tools/gen_mnemonic_builders.odin from ENCODE_FORMS / ENCODE_RUNS.
|
||||
// Regenerate with: odin run arm32/tools/gen_mnemonic_builders.odin -file
|
||||
//
|
||||
// Typed mnemonic builder procedures with overloading. Each mnemonic exposes an
|
||||
// inst_<mnem> overload set (returns Instruction) and an emit_<mnem> overload
|
||||
// set (appends to a ^[dynamic]Instruction). Only forms whose every operand is
|
||||
// cleanly constructible from a single typed parameter are generated; shifted /
|
||||
// register-shifted registers, register lists, NEON lane/vector forms, modified
|
||||
// immediates, condition-code operands, coprocessor / PSR / MVE / CDE selectors
|
||||
// and special encoded immediates are intentionally omitted.
|
||||
// Typed mnemonic builder procedures with overloading. Each mnemonic with at
|
||||
// least one encode form exposes an inst_<mnem> overload set (returns
|
||||
// Instruction) and an emit_<mnem> overload set (appends to a
|
||||
// ^[dynamic]Instruction). EVERY operand type is mapped to typed parameters:
|
||||
// shifted / register-shifted registers (Register, Shift_Type, u8/Register),
|
||||
// register lists (u16 mask), NEON D/Q lane elems (Register, u8), and every
|
||||
// immediate subclass (modified-imm / barrier / endian / iflags / sysm / coproc
|
||||
// / saturating / PSR field / hint / condition-operand / MVE / CDE) as i64 — the
|
||||
// encoder performs the field packing. Forms whose ordered Odin parameter-type
|
||||
// tuple duplicates an earlier form of the same mnemonic are folded out to keep
|
||||
// each overload set unambiguous (all arm32 register classes share one Register
|
||||
// type).
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -99,30 +99,8 @@ inst_branch :: #force_inline proc "contextless" (m: Mnemonic, label_id: u32) ->
|
||||
ops = {op_label(label_id, 4), {}, {}, {}}}
|
||||
}
|
||||
|
||||
// Conditional branch (B.cond label).
|
||||
@(require_results)
|
||||
inst_b_cond :: #force_inline proc "contextless" (c: Cond, label_id: u32) -> Instruction {
|
||||
return Instruction{mnemonic = .B_COND, operand_count = 2, length = 4,
|
||||
ops = {op_cond(c), op_label(label_id, 4), {}, {}}}
|
||||
}
|
||||
|
||||
// CBZ/CBNZ: Rt, label.
|
||||
@(require_results)
|
||||
inst_cbz :: #force_inline proc "contextless" (m: Mnemonic, rt: Register, label_id: u32) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 2, length = 4,
|
||||
ops = {op_reg(rt), op_label(label_id, 4), {}, {}}}
|
||||
}
|
||||
|
||||
// TBZ/TBNZ: Rt, bit, label.
|
||||
@(require_results)
|
||||
inst_tbz :: #force_inline proc "contextless" (m: Mnemonic, rt: Register, bit: u8, label_id: u32) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 3, length = 4,
|
||||
ops = {op_reg(rt), op_imm(i64(bit), 1), op_label(label_id, 4), {}}}
|
||||
}
|
||||
|
||||
// CSEL/CSINC/CSINV/CSNEG: Rd, Rn, Rm, cond.
|
||||
@(require_results)
|
||||
inst_csel :: #force_inline proc "contextless" (m: Mnemonic, rd, rn, rm: Register, c: Cond) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 4, length = 4,
|
||||
ops = {op_reg(rd), op_reg(rn), op_reg(rm), op_cond(c)}}
|
||||
}
|
||||
// NOTE: inst_b_cond / inst_cbz (+cbnz) / inst_tbz (+tbnz) /
|
||||
// inst_csel (+csinc/csinv/csneg) are now generated per-mnemonic in
|
||||
// mnemonic_builders.odin (e.g. inst_cbz(rt, label), inst_cbnz(rt, label),
|
||||
// inst_csinc(rd, rn, rm, cond)). They are no longer hand-written here so the
|
||||
// generator can own those names for full mnemonic coverage.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -171,7 +171,7 @@ run_pipeline_tests :: proc() {
|
||||
{
|
||||
clear(&relocs); clear(&errors)
|
||||
for i in 0..<len(code) { code[i] = 0 }
|
||||
insts := []a.Instruction{ a.inst_csel(.CSEL, a.X0, a.X1, a.X2, .EQ) }
|
||||
insts := []a.Instruction{ a.inst_csel(a.X0, a.X1, a.X2, .EQ) }
|
||||
r := a.encode(insts, nil, code[:], &relocs, &errors)
|
||||
ok("CSEL: encode", r.success)
|
||||
eq_word("CSEL X0,X1,X2,EQ", load_le(code[:], 0), 0x9A820020)
|
||||
@@ -245,8 +245,8 @@ run_pipeline_tests :: proc() {
|
||||
append(&ld, a.Label_Definition(2)) // target at inst 2 (byte 8)
|
||||
|
||||
insts := []a.Instruction{
|
||||
a.inst_cbz(.CBZ, a.X0, 0),
|
||||
a.inst_tbz(.TBZ, a.X0, 5, 0),
|
||||
a.inst_cbz(a.X0, 0),
|
||||
a.inst_tbz(a.X0, 5, 0),
|
||||
a.inst_none(.NOP), // target
|
||||
}
|
||||
r := a.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
@@ -346,7 +346,7 @@ run_pipeline_tests :: proc() {
|
||||
|
||||
src := []a.Instruction{
|
||||
a.inst_r_r_i(.ADD_IMM, a.X0, a.X0, 1),
|
||||
a.inst_cbz(.CBNZ, a.X0, 0),
|
||||
a.inst_cbnz(a.X0, 0),
|
||||
a.inst_none(.RET),
|
||||
}
|
||||
r := a.encode(src, ld[:], code[:], &relocs, &errors)
|
||||
|
||||
@@ -12,31 +12,39 @@ package main
|
||||
// with per-mnemonic overload groups.
|
||||
//
|
||||
// AArch64 has ~50 operand types, many exotic (SVE/SME/NEON-arrangement/
|
||||
// shifted/extended/bitmask/sysreg). We only emit a builder for a form when
|
||||
// EVERY one of its operands is in the cleanly-constructible "supported" set
|
||||
// below; any form touching an unsupported operand type is skipped wholesale.
|
||||
// shifted/extended/bitmask/sysreg). EVERY operand type is now mapped to a
|
||||
// concrete Odin parameter (or parameters) and a constructor expression, so
|
||||
// NO form is skipped: every mnemonic that has at least one encode form gets
|
||||
// an inst_<mnem> / emit_<mnem> overload group.
|
||||
//
|
||||
// COVERED (param type / op_ expr):
|
||||
// W_REG X_REG WSP_REG XSP_REG -> Register / op_reg
|
||||
// B_REG H_REG S_REG D_REG Q_REG V_REG -> Register / op_reg
|
||||
// IMM_12 IMM_16 IMM_8 IMM_6 IMM_5 IMM_4 IMM_3 IMM_2 -> i64 / op_imm
|
||||
// HW_SHIFT NZCV_IMM -> i64 / op_imm
|
||||
// REL_26 REL_19 REL_14 REL_PG21 -> u32 (label id) / op_label
|
||||
// MEM -> Memory / op_mem
|
||||
// COND -> Cond / op_cond
|
||||
// PARAM-TYPE per operand category (the suffix token is a function of the
|
||||
// Odin parameter TYPE only, so two builders with the same generated name
|
||||
// necessarily have the same Odin signature -- which is exactly what Odin
|
||||
// requires for legal overload sets, and lets name-dedup == signature-dedup):
|
||||
//
|
||||
// SKIPPED (no clean single-value constructor):
|
||||
// W_SHIFTED X_SHIFTED W_EXTENDED X_EXTENDED BITMASK_IMM SYS_REG
|
||||
// LSE_SIZE LDRAA_IMM10 LSL_SHIFT_W LSL_SHIFT_X ROR_SHIFT
|
||||
// FCMLA_ROT FCADD_ROT SVE_PRFOP SME_PATTERN SVE_PATTERN
|
||||
// all SVE : Z_REG_B/H/S/D P_REG P_REG_MERGE P_REG_ZERO P_REG_GOV Z_PAIR Z_QUAD
|
||||
// all SME : ZA_TILE_B/H/S/D/Q SME_SLICE_B/H/W/D/Q
|
||||
// all NEON arrangement/lane: V_8B V_16B V_4H V_8H V_2S V_4S V_1D V_2D
|
||||
// V_4H_FP16 V_8H_FP16 V_ELEM_B/H/S/D
|
||||
// integer/SIMD scalar/NEON-vector register -> Register / op_reg, op_v_* (suffix r)
|
||||
// SVE Z register / Z pair / Z quad -> u8 / op_z_* (suffix z)
|
||||
// SVE predicate (P_REG / merge / zero / gov) -> u8 / Register(REG_P|..) (suffix p)
|
||||
// all immediates (incl ZA tile, SME slice, -> i64 / op_imm (suffix i)
|
||||
// patterns, bitmask, sysreg, HW, NZCV, ...)
|
||||
// PC-relative label -> u32 / op_label (suffix l)
|
||||
// memory -> Memory / op_mem (suffix m)
|
||||
// condition code -> Cond / op_cond (suffix c)
|
||||
// shifted register (W/X_SHIFTED) -> (Register, Shift_Type, u8) / op_shifted (suffix sh)
|
||||
// extended register (W/X_EXTENDED) -> (Register, Extend, u8) / op_extended (suffix ex)
|
||||
//
|
||||
// Because Register is `distinct u16` and the SVE-register / predicate params
|
||||
// are `u8`, suffix `r` (one Register), `z`/`p` (one u8) are distinct Odin
|
||||
// signatures and may coexist. Two forms of one mnemonic that collapse to the
|
||||
// same Odin signature (e.g. the W and X variants, or the .8B and .16B
|
||||
// arrangement variants which are all `Register`) are deduplicated to a single
|
||||
// builder -- the first form wins and the encoder's matcher disambiguates the
|
||||
// rest at encode time by register class / size. This mirrors the original
|
||||
// W/X collapse and keeps every overload group legal.
|
||||
//
|
||||
// Run with: odin run tools/gen_mnemonic_builders.odin -file
|
||||
//
|
||||
// Output: mnemonic_builders.odin (written to current directory; move to package root)
|
||||
// Output: mnemonic_builders.odin (written next to the package via #directory).
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
@@ -50,13 +58,19 @@ GEN_ATTRIB :: "// rexcode · Brendan Punsky (dotbmp@github), original author\n
|
||||
// Operand model
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Category drives both the param type and the op_ expression.
|
||||
// Category drives the param shape (count + Odin types) and the constructor
|
||||
// expression. The suffix token is derived from the Odin param TYPE so that
|
||||
// equal generated names imply equal Odin signatures.
|
||||
Operand_Category :: enum {
|
||||
REG, // Register -> op_reg
|
||||
IMM, // i64 -> op_imm
|
||||
REL, // u32 label -> op_label
|
||||
MEM, // Memory -> op_mem
|
||||
COND, // Cond -> op_cond
|
||||
REG, // Register -> op_reg / op_v_* (1 param, suffix r)
|
||||
ZREG, // u8 -> op_z_* / Register(REG_Z) (1 param, suffix z)
|
||||
PREG, // u8 -> Register(REG_P) (1 param, suffix p)
|
||||
IMM, // i64 -> op_imm (1 param, suffix i)
|
||||
REL, // u32 label -> op_label (1 param, suffix l)
|
||||
MEM, // Memory -> op_mem (1 param, suffix m)
|
||||
COND, // Cond -> op_cond (1 param, suffix c)
|
||||
SHIFTED, // Register + Shift_Type + u8 -> op_shifted (3 params, suffix sh)
|
||||
EXTENDED, // Register + Extend + u8 -> op_extended (3 params, suffix ex)
|
||||
}
|
||||
|
||||
Operand_Signature :: struct {
|
||||
@@ -75,55 +89,46 @@ mnemonic_to_lower :: proc(m: a.Mnemonic) -> string {
|
||||
return strings.to_lower(name)
|
||||
}
|
||||
|
||||
// Is this operand type one we can build a clean single-value param for?
|
||||
is_supported_operand :: proc(t: a.Operand_Type) -> bool {
|
||||
#partial switch t {
|
||||
case .W_REG, .X_REG, .WSP_REG, .XSP_REG,
|
||||
.B_REG, .H_REG, .S_REG, .D_REG, .Q_REG, .V_REG:
|
||||
return true
|
||||
case .IMM_12, .IMM_16, .IMM_8, .IMM_6, .IMM_5, .IMM_4, .IMM_3, .IMM_2,
|
||||
.HW_SHIFT, .NZCV_IMM:
|
||||
return true
|
||||
case .REL_26, .REL_19, .REL_14, .REL_PG21:
|
||||
return true
|
||||
case .MEM:
|
||||
return true
|
||||
case .COND:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Every operand type now maps to a category -- nothing is unsupported, so no
|
||||
// form is ever skipped.
|
||||
operand_category :: proc(t: a.Operand_Type) -> Operand_Category {
|
||||
#partial switch t {
|
||||
case .W_REG, .X_REG, .WSP_REG, .XSP_REG,
|
||||
.B_REG, .H_REG, .S_REG, .D_REG, .Q_REG, .V_REG:
|
||||
.B_REG, .H_REG, .S_REG, .D_REG, .Q_REG, .V_REG,
|
||||
.V_8B, .V_16B, .V_4H, .V_8H, .V_2S, .V_4S, .V_1D, .V_2D,
|
||||
.V_4H_FP16, .V_8H_FP16,
|
||||
.V_ELEM_B, .V_ELEM_H, .V_ELEM_S, .V_ELEM_D:
|
||||
return .REG
|
||||
case .Z_REG_B, .Z_REG_H, .Z_REG_S, .Z_REG_D, .Z_PAIR, .Z_QUAD:
|
||||
return .ZREG
|
||||
case .P_REG, .P_REG_MERGE, .P_REG_ZERO, .P_REG_GOV:
|
||||
return .PREG
|
||||
case .REL_26, .REL_19, .REL_14, .REL_PG21:
|
||||
return .REL
|
||||
case .MEM:
|
||||
return .MEM
|
||||
case .COND:
|
||||
return .COND
|
||||
case .W_SHIFTED, .X_SHIFTED:
|
||||
return .SHIFTED
|
||||
case .W_EXTENDED, .X_EXTENDED:
|
||||
return .EXTENDED
|
||||
case:
|
||||
return .IMM
|
||||
}
|
||||
}
|
||||
|
||||
// Odin parameter type for an operand.
|
||||
operand_param_type :: proc(t: a.Operand_Type) -> string {
|
||||
switch operand_category(t) {
|
||||
case .REG: return "Register"
|
||||
case .IMM: return "i64"
|
||||
case .REL: return "u32"
|
||||
case .MEM: return "Memory"
|
||||
case .COND: return "Cond"
|
||||
// Number of Odin parameters this operand contributes.
|
||||
operand_param_count :: proc(t: a.Operand_Type) -> int {
|
||||
#partial switch operand_category(t) {
|
||||
case .SHIFTED, .EXTENDED:
|
||||
return 3
|
||||
}
|
||||
return "i64"
|
||||
return 1
|
||||
}
|
||||
|
||||
// Width byte for op_imm / op_label (informational only; the matcher checks
|
||||
// only operand kind, not size, so this never affects correctness).
|
||||
// operand kind, not size, so this never affects correctness).
|
||||
operand_imm_size :: proc(t: a.Operand_Type) -> u8 {
|
||||
#partial switch t {
|
||||
case .IMM_16: return 2
|
||||
@@ -135,20 +140,21 @@ operand_imm_size :: proc(t: a.Operand_Type) -> u8 {
|
||||
return 4
|
||||
}
|
||||
|
||||
// Procedure-name suffix token for an operand.
|
||||
// Procedure-name suffix token. Because AArch64 has no typed register enums,
|
||||
// every register operand maps to the same Odin param type (`Register`) and
|
||||
// every plain immediate to `i64`. We therefore name by PARAM-TYPE CATEGORY
|
||||
// (r/i/l/m/c), not by operand subtype. This makes name-based dedup exactly
|
||||
// equivalent to Odin-type dedup: two forms that produce the same name also
|
||||
// produce the same proc signature (which Odin forbids in one overload group).
|
||||
// Procedure-name suffix token for an operand -- a function of the Odin param
|
||||
// TYPE only (see header). This makes name-based dedup exactly equivalent to
|
||||
// Odin-type dedup: two forms producing the same name also produce the same
|
||||
// proc signature (which Odin forbids twice in one overload group).
|
||||
operand_suffix :: proc(t: a.Operand_Type) -> string {
|
||||
switch operand_category(t) {
|
||||
case .REG: return "r"
|
||||
case .IMM: return "i"
|
||||
case .REL: return "l"
|
||||
case .MEM: return "m"
|
||||
case .COND: return "c"
|
||||
case .REG: return "r"
|
||||
case .ZREG: return "z"
|
||||
case .PREG: return "p"
|
||||
case .IMM: return "i"
|
||||
case .REL: return "l"
|
||||
case .MEM: return "m"
|
||||
case .COND: return "c"
|
||||
case .SHIFTED: return "sh"
|
||||
case .EXTENDED: return "ex"
|
||||
}
|
||||
return "x"
|
||||
}
|
||||
@@ -157,8 +163,9 @@ operand_suffix :: proc(t: a.Operand_Type) -> string {
|
||||
// Signature building
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Build the explicit-operand signature for a form, returning ok=false when any
|
||||
// non-NONE operand is unsupported (the whole form is then skipped).
|
||||
// Build the explicit-operand signature for a form. Every non-NONE operand is
|
||||
// included; only truly implicit operands (enc == .IMPL, which AArch64's tables
|
||||
// never actually use) carry no param.
|
||||
build_signature :: proc(form: a.Encoding) -> (sig: Operand_Signature, ok: bool) {
|
||||
for i in 0..<4 {
|
||||
op := form.ops[i]
|
||||
@@ -167,53 +174,111 @@ build_signature :: proc(form: a.Encoding) -> (sig: Operand_Signature, ok: bool)
|
||||
// Implicit operands carry no bits and take no param.
|
||||
if form.enc[i] == .IMPL { continue }
|
||||
|
||||
if !is_supported_operand(op) {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
sig.types[sig.count] = op
|
||||
sig.count += 1
|
||||
}
|
||||
return sig, true
|
||||
}
|
||||
|
||||
// Unique, descriptive parameter names per operand.
|
||||
param_names :: proc(sig: Operand_Signature) -> [4]string {
|
||||
result: [4]string
|
||||
// Unique, descriptive parameter NAME(S) per operand. SHIFTED / EXTENDED
|
||||
// expand to three (reg, shift/ext, amount). Returns a flat list of
|
||||
// "name: type" param fragments plus, separately, the argument expressions for
|
||||
// emit_ forwarding.
|
||||
Param :: struct {
|
||||
decl: string, // e.g. "dst: Register"
|
||||
name: string, // e.g. "dst" (for emit forwarding)
|
||||
}
|
||||
|
||||
// Names of just the per-operand "primary" identifiers, indexed by operand
|
||||
// slot. names[i][0] is the main identifier; for SHIFTED/EXTENDED, [1]/[2] are
|
||||
// the shift/extend kind and amount. This is the single source of truth for
|
||||
// parameter names; param_list derives the typed declarations from it so the
|
||||
// declared params always match the expressions that reference them.
|
||||
operand_primary_names :: proc(sig: Operand_Signature) -> [4][3]string {
|
||||
result: [4][3]string
|
||||
reg_count := 0
|
||||
imm_count := 0
|
||||
zp_count := 0
|
||||
|
||||
reg_name :: proc(i: int, reg_count: ^int) -> string {
|
||||
if i == 0 {
|
||||
return "dst"
|
||||
} else if reg_count^ == 0 {
|
||||
reg_count^ += 1
|
||||
return "src"
|
||||
}
|
||||
n := reg_count^ + 1
|
||||
reg_count^ += 1
|
||||
return fmt.tprintf("src%d", n)
|
||||
}
|
||||
|
||||
for i in 0..<sig.count {
|
||||
t := sig.types[i]
|
||||
switch operand_category(t) {
|
||||
case .REG:
|
||||
if i == 0 {
|
||||
result[i] = "dst"
|
||||
} else if reg_count == 0 {
|
||||
result[i] = "src"
|
||||
reg_count += 1
|
||||
} else {
|
||||
result[i] = fmt.tprintf("src%d", reg_count + 1)
|
||||
reg_count += 1
|
||||
}
|
||||
result[i][0] = reg_name(i, ®_count)
|
||||
case .ZREG, .PREG:
|
||||
// SVE Z register / predicate -> u8 hardware number.
|
||||
base := "rz" if i == 0 else fmt.tprintf("rz%d", zp_count + 1)
|
||||
zp_count += 1
|
||||
result[i][0] = base
|
||||
case .IMM:
|
||||
if imm_count == 0 {
|
||||
result[i] = "imm"
|
||||
} else {
|
||||
result[i] = fmt.tprintf("imm%d", imm_count + 1)
|
||||
}
|
||||
result[i][0] = "imm" if imm_count == 0 else fmt.tprintf("imm%d", imm_count + 1)
|
||||
imm_count += 1
|
||||
case .REL:
|
||||
result[i] = "label"
|
||||
result[i][0] = "label"
|
||||
case .MEM:
|
||||
result[i] = "mem"
|
||||
result[i][0] = "mem"
|
||||
case .COND:
|
||||
result[i] = "cond"
|
||||
result[i][0] = "cond"
|
||||
case .SHIFTED:
|
||||
rn := reg_name(i, ®_count)
|
||||
result[i][0] = rn
|
||||
result[i][1] = fmt.tprintf("%s_shift", rn)
|
||||
result[i][2] = fmt.tprintf("%s_amount", rn)
|
||||
case .EXTENDED:
|
||||
rn := reg_name(i, ®_count)
|
||||
result[i][0] = rn
|
||||
result[i][1] = fmt.tprintf("%s_ext", rn)
|
||||
result[i][2] = fmt.tprintf("%s_amount", rn)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Typed parameter declarations, derived from operand_primary_names so the
|
||||
// names match exactly what the op_ expressions reference.
|
||||
param_list :: proc(sig: Operand_Signature) -> [dynamic]Param {
|
||||
params: [dynamic]Param
|
||||
names := operand_primary_names(sig)
|
||||
for i in 0..<sig.count {
|
||||
t := sig.types[i]
|
||||
switch operand_category(t) {
|
||||
case .REG:
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: Register", names[i][0]), name = names[i][0]})
|
||||
case .ZREG, .PREG:
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: u8", names[i][0]), name = names[i][0]})
|
||||
case .IMM:
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: i64", names[i][0]), name = names[i][0]})
|
||||
case .REL:
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: u32", names[i][0]), name = names[i][0]})
|
||||
case .MEM:
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: Memory", names[i][0]), name = names[i][0]})
|
||||
case .COND:
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: Cond", names[i][0]), name = names[i][0]})
|
||||
case .SHIFTED:
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: Register", names[i][0]), name = names[i][0]})
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: Shift_Type", names[i][1]), name = names[i][1]})
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: u8", names[i][2]), name = names[i][2]})
|
||||
case .EXTENDED:
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: Register", names[i][0]), name = names[i][0]})
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: Extend", names[i][1]), name = names[i][1]})
|
||||
append(¶ms, Param{decl = fmt.tprintf("%s: u8", names[i][2]), name = names[i][2]})
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// Generate the proc name (with inst_ prefix) from mnemonic + signature.
|
||||
generate_proc_name :: proc(mnemonic: a.Mnemonic, sig: Operand_Signature) -> string {
|
||||
sb := strings.builder_make()
|
||||
@@ -234,88 +299,132 @@ generate_proc_name :: proc(mnemonic: a.Mnemonic, sig: Operand_Signature) -> stri
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// op_ expression for a single operand
|
||||
// op_ expression for a single operand (uses the form's exact operand type so
|
||||
// the produced encoding is valid for the kept form).
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
write_operand_expr :: proc(sb: ^strings.Builder, t: a.Operand_Type, name: string) {
|
||||
switch operand_category(t) {
|
||||
write_operand_expr :: proc(sb: ^strings.Builder, t: a.Operand_Type, names: [3]string) {
|
||||
#partial switch operand_category(t) {
|
||||
case .REG:
|
||||
fmt.sbprintf(sb, "op_reg(%s)", name)
|
||||
#partial switch t {
|
||||
case .V_8B: fmt.sbprintf(sb, "op_v_8b(u8(reg_hw(%s)))", names[0])
|
||||
case .V_16B: fmt.sbprintf(sb, "op_v_16b(u8(reg_hw(%s)))", names[0])
|
||||
case .V_4H, .V_4H_FP16: fmt.sbprintf(sb, "op_v_4h(u8(reg_hw(%s)))", names[0])
|
||||
case .V_8H, .V_8H_FP16: fmt.sbprintf(sb, "op_v_8h(u8(reg_hw(%s)))", names[0])
|
||||
case .V_2S: fmt.sbprintf(sb, "op_v_2s(u8(reg_hw(%s)))", names[0])
|
||||
case .V_4S: fmt.sbprintf(sb, "op_v_4s(u8(reg_hw(%s)))", names[0])
|
||||
case .V_1D: fmt.sbprintf(sb, "op_v_1d(u8(reg_hw(%s)))", names[0])
|
||||
case .V_2D: fmt.sbprintf(sb, "op_v_2d(u8(reg_hw(%s)))", names[0])
|
||||
case: fmt.sbprintf(sb, "op_reg(%s)", names[0])
|
||||
}
|
||||
case .ZREG:
|
||||
#partial switch t {
|
||||
case .Z_REG_B: fmt.sbprintf(sb, "op_z_b(%s)", names[0])
|
||||
case .Z_REG_H: fmt.sbprintf(sb, "op_z_h(%s)", names[0])
|
||||
case .Z_REG_S: fmt.sbprintf(sb, "op_z_s(%s)", names[0])
|
||||
case .Z_REG_D: fmt.sbprintf(sb, "op_z_d(%s)", names[0])
|
||||
case: fmt.sbprintf(sb, "op_reg(Register(REG_Z | (u16(%s) & 0x1F)))", names[0]) // Z_PAIR / Z_QUAD
|
||||
}
|
||||
case .PREG:
|
||||
fmt.sbprintf(sb, "op_reg(Register(REG_P | (u16(%s) & 0xF)))", names[0])
|
||||
case .IMM:
|
||||
fmt.sbprintf(sb, "op_imm(%s, %d)", name, operand_imm_size(t))
|
||||
fmt.sbprintf(sb, "op_imm(%s, %d)", names[0], operand_imm_size(t))
|
||||
case .REL:
|
||||
fmt.sbprintf(sb, "op_label(%s, 4)", name)
|
||||
fmt.sbprintf(sb, "op_label(%s, 4)", names[0])
|
||||
case .MEM:
|
||||
fmt.sbprintf(sb, "op_mem(%s)", name)
|
||||
fmt.sbprintf(sb, "op_mem(%s)", names[0])
|
||||
case .COND:
|
||||
fmt.sbprintf(sb, "op_cond(%s)", name)
|
||||
fmt.sbprintf(sb, "op_cond(%s)", names[0])
|
||||
case .SHIFTED:
|
||||
fmt.sbprintf(sb, "op_shifted(%s, %s, %s)", names[0], names[1], names[2])
|
||||
case .EXTENDED:
|
||||
fmt.sbprintf(sb, "op_extended(%s, %s, %s)", names[0], names[1], names[2])
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern string of operand categories, e.g. "r_r_i", used to pick a shape
|
||||
// helper from instructions.odin where one fits cleanly.
|
||||
// Pattern string of operand categories (one token each), used to pick a shape
|
||||
// helper from instructions.odin where one fits cleanly. Shifted/extended use
|
||||
// multi-char tokens so they never collide with the simple ones.
|
||||
pattern_string :: proc(sig: Operand_Signature) -> string {
|
||||
if sig.count == 0 { return "none" }
|
||||
sb := strings.builder_make()
|
||||
for i in 0..<sig.count {
|
||||
if i > 0 { strings.write_byte(&sb, '_') }
|
||||
switch operand_category(sig.types[i]) {
|
||||
case .REG: strings.write_byte(&sb, 'r')
|
||||
case .IMM: strings.write_byte(&sb, 'i')
|
||||
case .REL: strings.write_byte(&sb, 'l')
|
||||
case .MEM: strings.write_byte(&sb, 'm')
|
||||
case .COND: strings.write_byte(&sb, 'c')
|
||||
}
|
||||
strings.write_string(&sb, operand_suffix(sig.types[i]))
|
||||
}
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
// True when every operand maps to one of the plain constructors
|
||||
// (op_reg/op_imm/op_mem/op_label) that the hand-written shape helpers use.
|
||||
// V-arrangement / Z / P / shifted / extended forms must use the direct
|
||||
// Instruction{} fallback instead, since the helpers would build the wrong
|
||||
// constructor.
|
||||
uses_plain_constructors :: proc(sig: Operand_Signature) -> bool {
|
||||
for i in 0..<sig.count {
|
||||
t := sig.types[i]
|
||||
#partial switch operand_category(t) {
|
||||
case .REG:
|
||||
#partial switch t {
|
||||
case .V_8B, .V_16B, .V_4H, .V_8H, .V_2S, .V_4S, .V_1D, .V_2D,
|
||||
.V_4H_FP16, .V_8H_FP16:
|
||||
return false // needs op_v_*
|
||||
}
|
||||
case .IMM, .REL, .MEM, .COND:
|
||||
// fine
|
||||
case:
|
||||
return false // ZREG / PREG / SHIFTED / EXTENDED
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// inst_ body
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
write_inst_body :: proc(sb: ^strings.Builder, entry: Proc_Entry) {
|
||||
sig := entry.sig
|
||||
names := param_names(sig)
|
||||
pnames := operand_primary_names(sig)
|
||||
pattern := pattern_string(sig)
|
||||
mstr := fmt.aprintf("%v", entry.mnemonic)
|
||||
defer delete(mstr)
|
||||
|
||||
// Prefer arm64 shape helpers where the operand kinds line up exactly.
|
||||
switch pattern {
|
||||
case "none":
|
||||
fmt.sbprintf(sb, "inst_none(.%s)", mstr)
|
||||
return
|
||||
case "r":
|
||||
fmt.sbprintf(sb, "inst_r(.%s, %s)", mstr, names[0])
|
||||
return
|
||||
case "r_r":
|
||||
fmt.sbprintf(sb, "inst_r_r(.%s, %s, %s)", mstr, names[0], names[1])
|
||||
return
|
||||
case "r_r_r":
|
||||
fmt.sbprintf(sb, "inst_r_r_r(.%s, %s, %s, %s)", mstr, names[0], names[1], names[2])
|
||||
return
|
||||
case "r_r_r_r":
|
||||
fmt.sbprintf(sb, "inst_r_r_r_r(.%s, %s, %s, %s, %s)", mstr, names[0], names[1], names[2], names[3])
|
||||
return
|
||||
case "r_i":
|
||||
fmt.sbprintf(sb, "inst_r_i(.%s, %s, %s)", mstr, names[0], names[1])
|
||||
return
|
||||
case "r_r_i":
|
||||
fmt.sbprintf(sb, "inst_r_r_i(.%s, %s, %s, %s)", mstr, names[0], names[1], names[2])
|
||||
return
|
||||
case "r_m":
|
||||
fmt.sbprintf(sb, "inst_ldst(.%s, %s, %s)", mstr, names[0], names[1])
|
||||
return
|
||||
case "r_r_m":
|
||||
fmt.sbprintf(sb, "inst_ldp_stp(.%s, %s, %s, %s)", mstr, names[0], names[1], names[2])
|
||||
return
|
||||
case "l":
|
||||
fmt.sbprintf(sb, "inst_branch(.%s, %s)", mstr, names[0])
|
||||
return
|
||||
case "r_l":
|
||||
fmt.sbprintf(sb, "inst_cbz(.%s, %s, %s)", mstr, names[0], names[1])
|
||||
return
|
||||
// Prefer arm64 shape helpers where the operand kinds line up exactly AND
|
||||
// every operand uses a plain constructor.
|
||||
if uses_plain_constructors(sig) {
|
||||
switch pattern {
|
||||
case "none":
|
||||
fmt.sbprintf(sb, "inst_none(.%s)", mstr)
|
||||
return
|
||||
case "r":
|
||||
fmt.sbprintf(sb, "inst_r(.%s, %s)", mstr, pnames[0][0])
|
||||
return
|
||||
case "r_r":
|
||||
fmt.sbprintf(sb, "inst_r_r(.%s, %s, %s)", mstr, pnames[0][0], pnames[1][0])
|
||||
return
|
||||
case "r_r_r":
|
||||
fmt.sbprintf(sb, "inst_r_r_r(.%s, %s, %s, %s)", mstr, pnames[0][0], pnames[1][0], pnames[2][0])
|
||||
return
|
||||
case "r_r_r_r":
|
||||
fmt.sbprintf(sb, "inst_r_r_r_r(.%s, %s, %s, %s, %s)", mstr, pnames[0][0], pnames[1][0], pnames[2][0], pnames[3][0])
|
||||
return
|
||||
case "r_i":
|
||||
fmt.sbprintf(sb, "inst_r_i(.%s, %s, %s)", mstr, pnames[0][0], pnames[1][0])
|
||||
return
|
||||
case "r_r_i":
|
||||
fmt.sbprintf(sb, "inst_r_r_i(.%s, %s, %s, %s)", mstr, pnames[0][0], pnames[1][0], pnames[2][0])
|
||||
return
|
||||
case "r_m":
|
||||
fmt.sbprintf(sb, "inst_ldst(.%s, %s, %s)", mstr, pnames[0][0], pnames[1][0])
|
||||
return
|
||||
case "r_r_m":
|
||||
fmt.sbprintf(sb, "inst_ldp_stp(.%s, %s, %s, %s)", mstr, pnames[0][0], pnames[1][0], pnames[2][0])
|
||||
return
|
||||
case "l":
|
||||
fmt.sbprintf(sb, "inst_branch(.%s, %s)", mstr, pnames[0][0])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: direct Instruction{} construction (always correct).
|
||||
@@ -324,7 +433,7 @@ write_inst_body :: proc(sb: ^strings.Builder, entry: Proc_Entry) {
|
||||
|
||||
write_inst_fallback :: proc(sb: ^strings.Builder, entry: Proc_Entry) {
|
||||
sig := entry.sig
|
||||
names := param_names(sig)
|
||||
pnames := operand_primary_names(sig)
|
||||
mstr := fmt.aprintf("%v", entry.mnemonic)
|
||||
defer delete(mstr)
|
||||
|
||||
@@ -332,7 +441,7 @@ write_inst_fallback :: proc(sb: ^strings.Builder, entry: Proc_Entry) {
|
||||
for i in 0..<4 {
|
||||
if i > 0 { strings.write_string(sb, ", ") }
|
||||
if i < sig.count {
|
||||
write_operand_expr(sb, sig.types[i], names[i])
|
||||
write_operand_expr(sb, sig.types[i], pnames[i])
|
||||
} else {
|
||||
strings.write_string(sb, "{}")
|
||||
}
|
||||
@@ -345,57 +454,54 @@ write_inst_fallback :: proc(sb: ^strings.Builder, entry: Proc_Entry) {
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
write_proc :: proc(sb: ^strings.Builder, entry: Proc_Entry, pad: int) {
|
||||
sig := entry.sig
|
||||
names := param_names(sig)
|
||||
params := param_list(entry.sig)
|
||||
defer delete(params)
|
||||
|
||||
// params
|
||||
params := strings.builder_make()
|
||||
defer strings.builder_destroy(¶ms)
|
||||
for i in 0..<sig.count {
|
||||
if i > 0 { strings.write_string(¶ms, ", ") }
|
||||
fmt.sbprintf(¶ms, "%s: %s", names[i], operand_param_type(sig.types[i]))
|
||||
pstr := strings.builder_make()
|
||||
defer strings.builder_destroy(&pstr)
|
||||
for p, i in params {
|
||||
if i > 0 { strings.write_string(&pstr, ", ") }
|
||||
strings.write_string(&pstr, p.decl)
|
||||
}
|
||||
|
||||
strings.write_string(sb, entry.proc_name)
|
||||
for n := pad - len(entry.proc_name); n > 0; n -= 1 { strings.write_byte(sb, ' ') }
|
||||
strings.write_string(sb, " :: #force_inline proc \"contextless\" (")
|
||||
strings.write_string(sb, strings.to_string(params))
|
||||
strings.write_string(sb, strings.to_string(pstr))
|
||||
strings.write_string(sb, ") -> Instruction { return ")
|
||||
write_inst_body(sb, entry)
|
||||
strings.write_string(sb, " }\n")
|
||||
}
|
||||
|
||||
write_emit_proc :: proc(sb: ^strings.Builder, entry: Proc_Entry, pad: int) {
|
||||
sig := entry.sig
|
||||
names := param_names(sig)
|
||||
params := param_list(entry.sig)
|
||||
defer delete(params)
|
||||
|
||||
emit_name := strings.concatenate({"emit_", entry.proc_name[5:]})
|
||||
defer delete(emit_name)
|
||||
|
||||
// params (instructions + originals)
|
||||
params := strings.builder_make()
|
||||
defer strings.builder_destroy(¶ms)
|
||||
strings.write_string(¶ms, "instructions: ^[dynamic]Instruction")
|
||||
for i in 0..<sig.count {
|
||||
fmt.sbprintf(¶ms, ", %s: %s", names[i], operand_param_type(sig.types[i]))
|
||||
pstr := strings.builder_make()
|
||||
defer strings.builder_destroy(&pstr)
|
||||
strings.write_string(&pstr, "instructions: ^[dynamic]Instruction")
|
||||
for p in params {
|
||||
fmt.sbprintf(&pstr, ", %s", p.decl)
|
||||
}
|
||||
|
||||
// call args (forward originals)
|
||||
args := strings.builder_make()
|
||||
defer strings.builder_destroy(&args)
|
||||
for i in 0..<sig.count {
|
||||
if i > 0 { strings.write_string(&args, ", ") }
|
||||
strings.write_string(&args, names[i])
|
||||
astr := strings.builder_make()
|
||||
defer strings.builder_destroy(&astr)
|
||||
for p, i in params {
|
||||
if i > 0 { strings.write_string(&astr, ", ") }
|
||||
strings.write_string(&astr, p.name)
|
||||
}
|
||||
|
||||
strings.write_string(sb, emit_name)
|
||||
for n := pad - len(entry.proc_name); n > 0; n -= 1 { strings.write_byte(sb, ' ') }
|
||||
strings.write_string(sb, " :: #force_inline proc(")
|
||||
strings.write_string(sb, strings.to_string(params))
|
||||
strings.write_string(sb, strings.to_string(pstr))
|
||||
strings.write_string(sb, ") { append(instructions, ")
|
||||
strings.write_string(sb, entry.proc_name)
|
||||
strings.write_byte(sb, '(')
|
||||
strings.write_string(sb, strings.to_string(args))
|
||||
strings.write_string(sb, strings.to_string(astr))
|
||||
strings.write_string(sb, ")) }\n")
|
||||
}
|
||||
|
||||
@@ -403,13 +509,12 @@ write_emit_proc :: proc(sb: ^strings.Builder, entry: Proc_Entry, pad: int) {
|
||||
// Driver
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Lowercased mnemonic names whose generated overload group (inst_<name> /
|
||||
// emit_<name>) would collide with a hand-written shape helper already defined
|
||||
// in instructions.odin. Those mnemonics already have dedicated typed builders,
|
||||
// so we skip generating overloads for them entirely.
|
||||
// Lowercased mnemonic names whose generated overload group would collide with
|
||||
// a generic shape helper already defined in instructions.odin. Those helpers
|
||||
// are not per-mnemonic (they take the Mnemonic as a parameter), so the
|
||||
// generator must not also define a group of the same name.
|
||||
RESERVED_MNEMONIC_NAMES :: []string{
|
||||
"none", "r", "branch", "ldst", "mov_imm", // inst_none/inst_r/inst_branch/inst_ldst/inst_mov_imm
|
||||
"b_cond", "cbz", "tbz", "csel", // inst_b_cond/inst_cbz/inst_tbz/inst_csel
|
||||
"none", "r", "branch", "ldst", "mov_imm",
|
||||
}
|
||||
|
||||
is_reserved_mnemonic :: proc(name: string) -> bool {
|
||||
@@ -431,30 +536,39 @@ main :: proc() {
|
||||
delete(procs_by_mnemonic)
|
||||
}
|
||||
|
||||
zero_form_mnemonics: [dynamic]a.Mnemonic
|
||||
defer delete(zero_form_mnemonics)
|
||||
|
||||
form_bearing := 0
|
||||
|
||||
seen_proc_names: map[string]bool
|
||||
defer delete(seen_proc_names)
|
||||
|
||||
for mnemonic in a.Mnemonic {
|
||||
if mnemonic == .INVALID { continue }
|
||||
|
||||
// Skip mnemonics whose overload-group name collides with a hand-written
|
||||
// helper in instructions.odin (they already have typed builders).
|
||||
if is_reserved_mnemonic(mnemonic_to_lower(mnemonic)) { continue }
|
||||
|
||||
_run := a.ENCODE_RUNS[u16(mnemonic)]
|
||||
forms := a.ENCODE_FORMS[_run.start:][:_run.count]
|
||||
if len(forms) == 0 { continue }
|
||||
if len(forms) == 0 {
|
||||
append(&zero_form_mnemonics, mnemonic)
|
||||
continue
|
||||
}
|
||||
form_bearing += 1
|
||||
|
||||
// Skip mnemonics whose overload-group name collides with a generic
|
||||
// hand-written helper in instructions.odin.
|
||||
if is_reserved_mnemonic(mnemonic_to_lower(mnemonic)) { continue }
|
||||
|
||||
for form in forms {
|
||||
sig, ok := build_signature(form)
|
||||
if !ok { continue }
|
||||
|
||||
proc_name := generate_proc_name(mnemonic, sig)
|
||||
// Dedup by generated name. Because suffixes are param-type
|
||||
// categories, equal names mean equal Odin signatures, so this
|
||||
// collapses W/X (and other same-shape) variants into one generic-
|
||||
// Register builder; the encoder's matcher disambiguates at encode
|
||||
// time by register class. (Two same-typed procs in one overload
|
||||
// Dedup by generated name. Because the suffix is a function of the
|
||||
// Odin param TYPE, equal names mean equal Odin signatures, so this
|
||||
// collapses W/X (and same-shape arrangement) variants into one
|
||||
// builder; the encoder's matcher disambiguates at encode time by
|
||||
// register class / size. (Two same-typed procs in one overload
|
||||
// group is also a hard Odin error, so this dedup is required.)
|
||||
if proc_name in seen_proc_names { continue }
|
||||
seen_proc_names[proc_name] = true
|
||||
@@ -545,10 +659,16 @@ main :: proc() {
|
||||
err := os.write_entire_file(#directory + "/../mnemonic_builders.odin", transmute([]u8)strings.concatenate({GEN_ATTRIB, output}))
|
||||
if err == nil {
|
||||
fmt.println("Generated mnemonic_builders.odin successfully!")
|
||||
fmt.printf("Total mnemonics with builders: %d\n", len(mnemonic_list))
|
||||
fmt.printf("Form-bearing mnemonics: %d\n", form_bearing)
|
||||
fmt.printf("Mnemonics with builder groups: %d\n", len(mnemonic_list))
|
||||
fmt.printf("Reserved (generic helper) skip:%d\n", form_bearing - len(mnemonic_list))
|
||||
total := 0
|
||||
for m in mnemonic_list { total += len(procs_by_mnemonic[m]) }
|
||||
fmt.printf("Total procedures generated: %d\n", total)
|
||||
fmt.printf("Total inst_ procedures: %d\n", total)
|
||||
fmt.printf("Zero-form (decode-only) mnemonics: %d\n", len(zero_form_mnemonics))
|
||||
for m in zero_form_mnemonics {
|
||||
fmt.printf(" %v\n", m)
|
||||
}
|
||||
} else {
|
||||
fmt.eprintln("Failed to write mnemonic_builders.odin")
|
||||
}
|
||||
@@ -564,10 +684,13 @@ generate_header :: proc(sb: ^strings.Builder) {
|
||||
// Generated by tools/gen_mnemonic_builders.odin from ENCODE_FORMS.
|
||||
// Regenerate with: odin run arm64/tools/gen_mnemonic_builders.odin -file
|
||||
//
|
||||
// Typed mnemonic builder procedures with overloading. Each supported mnemonic
|
||||
// form gets an inst_* (returns Instruction) and emit_* (appends to a
|
||||
// [dynamic]Instruction). Forms touching exotic operand types
|
||||
// (SVE/SME/NEON-arrangement/shifted/extended/bitmask/sysreg/...) are skipped.
|
||||
// Typed mnemonic builder procedures with overloading. Every mnemonic that has
|
||||
// at least one encode form gets an inst_* (returns Instruction) and emit_*
|
||||
// (appends to a [dynamic]Instruction) overload group covering all of its
|
||||
// distinct operand SHAPES. Forms that share an Odin signature (e.g. the W/X
|
||||
// variants, or NEON arrangement / SVE element-size variants which are all
|
||||
// passed as a Register/u8) collapse to one builder; the encoder's matcher
|
||||
// disambiguates by register class / size at encode time.
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -75,15 +75,6 @@ inst_zp_rel :: #force_inline proc "contextless" (m: Mnemonic, zp: u8, label_id:
|
||||
}
|
||||
}
|
||||
|
||||
// HuC6280 TST # imm, addr (3 or 4 bytes depending on addressing mode).
|
||||
@(require_results)
|
||||
inst_tst :: #force_inline proc "contextless" (m: Mnemonic, imm: i64, mm: Memory) -> Instruction {
|
||||
return Instruction{
|
||||
mnemonic = m, operand_count = 2, length = 0,
|
||||
ops = {op_imm8(imm), op_mem(mm), {}},
|
||||
}
|
||||
}
|
||||
|
||||
// HuC6280 block transfer: src, dst, length (7-byte encoding).
|
||||
@(require_results)
|
||||
inst_block :: #force_inline proc "contextless" (m: Mnemonic, src, dst, length_val: u16) -> Instruction {
|
||||
|
||||
@@ -334,8 +334,8 @@ inst_tam_imm8 :: #force_inline proc "contextless" (imm: i64) -> Instruction {
|
||||
emit_tam_imm8 :: #force_inline proc(instructions: ^[dynamic]Instruction, imm: i64) { append(instructions, inst_i(.TAM, imm)) }
|
||||
inst_tma_imm8 :: #force_inline proc "contextless" (imm: i64) -> Instruction { return inst_i(.TMA, imm) }
|
||||
emit_tma_imm8 :: #force_inline proc(instructions: ^[dynamic]Instruction, imm: i64) { append(instructions, inst_i(.TMA, imm)) }
|
||||
inst_tst_tst :: #force_inline proc "contextless" (imm: i64, m: Memory) -> Instruction { return inst_tst(.TST, imm, m) }
|
||||
emit_tst_tst :: #force_inline proc(instructions: ^[dynamic]Instruction, imm: i64, m: Memory) { append(instructions, inst_tst(.TST, imm, m)) }
|
||||
inst_tst_tst :: #force_inline proc "contextless" (imm: i64, m: Memory) -> Instruction { return Instruction{mnemonic = .TST, operand_count = 2, length = 0, ops = {op_imm8(imm), op_mem(m), {}}} }
|
||||
emit_tst_tst :: #force_inline proc(instructions: ^[dynamic]Instruction, imm: i64, m: Memory) { append(instructions, Instruction{mnemonic = .TST, operand_count = 2, length = 0, ops = {op_imm8(imm), op_mem(m), {}}}) }
|
||||
inst_bsr_rel :: #force_inline proc "contextless" (label_id: u32) -> Instruction { return inst_rel(.BSR, label_id) }
|
||||
emit_bsr_rel :: #force_inline proc(instructions: ^[dynamic]Instruction, label_id: u32) { append(instructions, inst_rel(.BSR, label_id)) }
|
||||
inst_tii_block :: #force_inline proc "contextless" (src, dst, length_val: u16) -> Instruction { return inst_block(.TII, src, dst, length_val) }
|
||||
@@ -627,7 +627,8 @@ inst_tam :: inst_tam_imm8
|
||||
emit_tam :: emit_tam_imm8
|
||||
inst_tma :: inst_tma_imm8
|
||||
emit_tma :: emit_tma_imm8
|
||||
// inst_tst / emit_tst overload group omitted: name collides with base helper inst_tst.
|
||||
inst_tst :: inst_tst_tst
|
||||
emit_tst :: emit_tst_tst
|
||||
inst_bsr :: inst_bsr_rel
|
||||
emit_bsr :: emit_bsr_rel
|
||||
inst_tii :: inst_tii_block
|
||||
|
||||
@@ -115,7 +115,7 @@ run_pipeline_tests :: proc() {
|
||||
eq_bytes("SAX (HuC)", encode_or_fail({m.inst_none(.SAX)}), {0x22})
|
||||
eq_bytes("CLA", encode_or_fail({m.inst_none(.CLA)}), {0x62})
|
||||
eq_bytes("ST0 #$05", encode_or_fail({m.inst_i(.ST0, 0x05)}), {0x03, 0x05})
|
||||
eq_bytes("TST #$10, $80", encode_or_fail({m.inst_tst(.TST, 0x10, m.mem_zp(0x80))}), {0x83, 0x10, 0x80})
|
||||
eq_bytes("TST #$10, $80", encode_or_fail({m.inst_tst(0x10, m.mem_zp(0x80))}), {0x83, 0x10, 0x80})
|
||||
eq_bytes("TII", encode_or_fail({m.inst_block(.TII, 0x4000, 0x2000, 0x100)}),
|
||||
{0x73, 0x00, 0x40, 0x00, 0x20, 0x00, 0x01})
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ Shape :: enum {
|
||||
REL, // PC-relative branch target -> inst_rel(.M, label)
|
||||
MEM, // any addressing-mode mem -> inst_m(.M, mem)
|
||||
ZP_REL, // BBR/BBS: zp + rel branch -> inst_zp_rel(.M, zp, label)
|
||||
TST, // HuC TST: imm + mem -> inst_tst(.M, imm, mem)
|
||||
TST, // HuC TST: imm + mem -> Instruction{ op_imm8, op_mem }
|
||||
BLOCK, // HuC block xfer: 3x word16 -> inst_block(.M, src, dst, len)
|
||||
SKIP, // could not classify
|
||||
}
|
||||
@@ -57,18 +57,6 @@ mnemonic_to_lower :: proc(m: m6502.Mnemonic) -> string {
|
||||
return strings.to_lower(fmt.tprintf("%v", m))
|
||||
}
|
||||
|
||||
// Base instruction helpers in instructions.odin are inst_<x> for these x.
|
||||
// If a mnemonic lowercases to one of them, an overload group named inst_<x>
|
||||
// would redeclare the helper -- so we suppress the group for that mnemonic.
|
||||
BASE_HELPER_SUFFIXES :: []string{"none", "a", "i", "m", "rel", "zp_rel", "tst", "block"}
|
||||
|
||||
collides_with_base_helper :: proc(lower: string) -> bool {
|
||||
for s in BASE_HELPER_SUFFIXES {
|
||||
if lower == s { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Classify a single encoding form into a builder Shape.
|
||||
classify :: proc(form: m6502.Encoding) -> Shape {
|
||||
// Gather the explicit operand types (skip trailing NONEs).
|
||||
@@ -230,16 +218,6 @@ main :: proc() {
|
||||
for mnemonic in mnemonic_list {
|
||||
procs := procs_by_mnemonic[mnemonic]
|
||||
if len(procs) == 0 { continue }
|
||||
// A mnemonic named the same as an existing base helper (e.g. TST -> the
|
||||
// HuC `inst_tst` bit-test helper) would have its overload group shadow /
|
||||
// redeclare that helper. Skip the group in that case; the per-shape
|
||||
// members (inst_<mnem>_<suffix>) remain the typed entry points.
|
||||
lower := mnemonic_to_lower(mnemonic)
|
||||
if collides_with_base_helper(lower) {
|
||||
fmt.sbprintf(&sb, "// inst_%s / emit_%s overload group omitted: name collides with base helper inst_%s.\n",
|
||||
lower, lower, lower)
|
||||
continue
|
||||
}
|
||||
generate_overload_group(&sb, mnemonic, procs[:], pad)
|
||||
}
|
||||
|
||||
@@ -309,7 +287,9 @@ shape_inst_body :: proc(sb: ^strings.Builder, e: Proc_Entry) {
|
||||
case .REL: fmt.sbprintf(sb, "inst_rel(.%s, label_id)", mn)
|
||||
case .MEM: fmt.sbprintf(sb, "inst_m(.%s, m)", mn)
|
||||
case .ZP_REL: fmt.sbprintf(sb, "inst_zp_rel(.%s, zp, label_id)", mn)
|
||||
case .TST: fmt.sbprintf(sb, "inst_tst(.%s, imm, m)", mn)
|
||||
// HuC6280 TST # imm, addr (imm + memory). Built inline so the inst_tst
|
||||
// overload-group alias is free to name this mnemonic's typed entry point.
|
||||
case .TST: fmt.sbprintf(sb, "Instruction{{mnemonic = .%s, operand_count = 2, length = 0, ops = {{op_imm8(imm), op_mem(m), {{}}}}}}", mn)
|
||||
case .BLOCK: fmt.sbprintf(sb, "inst_block(.%s, src, dst, length_val)", mn)
|
||||
case .SKIP: strings.write_string(sb, "{}")
|
||||
}
|
||||
|
||||
@@ -75,13 +75,6 @@ inst_u :: #force_inline proc "contextless" (m: Mnemonic, rd: Register, imm: i64)
|
||||
ops = {op_reg(rd), op_imm(imm, 4), {}, {}}}
|
||||
}
|
||||
|
||||
// J-type: rd, label
|
||||
@(require_results)
|
||||
inst_jal :: #force_inline proc "contextless" (m: Mnemonic, rd: Register, label_id: u32) -> Instruction {
|
||||
return Instruction{mnemonic = m, operand_count = 2, length = 4,
|
||||
ops = {op_reg(rd), op_label(label_id, 4), {}, {}}}
|
||||
}
|
||||
|
||||
// B-type branch: rs1, rs2, label
|
||||
@(require_results)
|
||||
inst_branch :: #force_inline proc "contextless" (m: Mnemonic, rs1, rs2: Register, label_id: u32) -> Instruction {
|
||||
@@ -89,13 +82,6 @@ inst_branch :: #force_inline proc "contextless" (m: Mnemonic, rs1, rs2: Register
|
||||
ops = {op_reg(rs1), op_reg(rs2), op_label(label_id, 2), {}}}
|
||||
}
|
||||
|
||||
// JALR: rd, rs1, imm12 (indirect jump with offset)
|
||||
@(require_results)
|
||||
inst_jalr :: #force_inline proc "contextless" (rd, rs1: Register, imm: i64) -> Instruction {
|
||||
return Instruction{mnemonic = .JALR, operand_count = 3, length = 4,
|
||||
ops = {op_reg(rd), op_reg(rs1), op_imm(imm, 2), {}}}
|
||||
}
|
||||
|
||||
// CSR ops: rd, csr, rs1
|
||||
@(require_results)
|
||||
inst_csr :: #force_inline proc "contextless" (m: Mnemonic, rd: Register, csr: u16, rs1: Register) -> Instruction {
|
||||
|
||||
@@ -417,9 +417,9 @@ inst_lui :: inst_lui_gpr_imm20
|
||||
emit_lui :: emit_lui_gpr_imm20
|
||||
inst_auipc :: inst_auipc_gpr_imm20
|
||||
emit_auipc :: emit_auipc_gpr_imm20
|
||||
// inst_jal: overload alias omitted (name taken by hand-written helper in instructions.odin); use inst_jal_gpr_label
|
||||
inst_jal :: inst_jal_gpr_label
|
||||
emit_jal :: emit_jal_gpr_label
|
||||
// inst_jalr: overload alias omitted (name taken by hand-written helper in instructions.odin); use inst_jalr_gpr_gpr_imm12
|
||||
inst_jalr :: inst_jalr_gpr_gpr_imm12
|
||||
emit_jalr :: emit_jalr_gpr_gpr_imm12
|
||||
inst_beq :: inst_beq_gpr_gpr_label
|
||||
emit_beq :: emit_beq_gpr_gpr_label
|
||||
|
||||
@@ -166,9 +166,9 @@ run_pipeline_tests :: proc() {
|
||||
append(&ld, rv.Label_Definition(2)) // target at inst 2
|
||||
|
||||
insts := []rv.Instruction{
|
||||
rv.inst_jal(.JAL, rv.RA, 0),
|
||||
rv.inst_jal(rv.GPR.RA, 0),
|
||||
rv.inst_r_r_i(.ADDI, rv.SP, rv.SP, 0),
|
||||
rv.inst_jalr(rv.ZERO, rv.RA, 0),
|
||||
rv.inst_jalr(rv.GPR.ZERO, rv.GPR.RA, 0),
|
||||
}
|
||||
r := rv.encode(insts, ld[:], code[:], &relocs, &errors)
|
||||
ok("JAL: encode ok", r.success)
|
||||
|
||||
@@ -467,21 +467,12 @@ main :: proc() {
|
||||
|
||||
mlow := mnemonic_to_lower(m)
|
||||
|
||||
// inst_<mnem> overload group. If "inst_<mlow>" would collide with a
|
||||
// hand-written helper of the same name in instructions.odin (inst_jal,
|
||||
// inst_jalr), the standalone group alias is suppressed -- the existing
|
||||
// helper keeps that name and the typed builder stays reachable under its
|
||||
// explicit per-variant name (e.g. inst_jal_gpr_label).
|
||||
// inst_<mnem> overload group.
|
||||
inst_group := strings.concatenate({"inst_", mlow})
|
||||
defer delete(inst_group)
|
||||
if is_reserved_inst_group(inst_group) {
|
||||
fmt.sbprintf(&sb, "// inst_%s: overload alias omitted (name taken by hand-written helper in instructions.odin); use %s\n",
|
||||
mlow, procs[0].proc_name)
|
||||
} else {
|
||||
strings.write_string(&sb, inst_group)
|
||||
for n := pad - len(mlow); n > 0; n -= 1 { strings.write_byte(&sb, ' ') }
|
||||
write_group(&sb, procs, "inst_")
|
||||
}
|
||||
strings.write_string(&sb, inst_group)
|
||||
for n := pad - len(mlow); n > 0; n -= 1 { strings.write_byte(&sb, ' ') }
|
||||
write_group(&sb, procs, "inst_")
|
||||
|
||||
// emit_<mnem> overload group. No hand-written emit_ helpers collide.
|
||||
strings.write_string(&sb, "emit_")
|
||||
@@ -511,18 +502,6 @@ main :: proc() {
|
||||
}
|
||||
}
|
||||
|
||||
// inst_<mnemonic> group names that coincide with a hand-written helper of the
|
||||
// same name in instructions.odin. For these the standalone group alias is
|
||||
// suppressed (the existing helper keeps the name); the typed builder stays
|
||||
// reachable under its explicit per-variant name.
|
||||
is_reserved_inst_group :: proc(name: string) -> bool {
|
||||
switch name {
|
||||
case "inst_jal": return true // inst_jal(m, rd, label_id) in instructions.odin
|
||||
case "inst_jalr": return true // inst_jalr(rd, rs1, imm) in instructions.odin
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Write the " :: proc{ ... }\n" overload group body (or single alias).
|
||||
// prefix is "inst_" or "emit_"; entry proc_names always carry the "inst_"
|
||||
// prefix, so for emit_ we swap the prefix.
|
||||
|
||||
@@ -21,6 +21,7 @@ Mem8 :: distinct struct { mem: Memory }
|
||||
Mem16 :: distinct struct { mem: Memory }
|
||||
Mem32 :: distinct struct { mem: Memory }
|
||||
Mem64 :: distinct struct { mem: Memory }
|
||||
Mem80 :: distinct struct { mem: Memory }
|
||||
Mem128 :: distinct struct { mem: Memory }
|
||||
Mem256 :: distinct struct { mem: Memory }
|
||||
Mem512 :: distinct struct { mem: Memory }
|
||||
@@ -46,6 +47,11 @@ mem64 :: #force_inline proc "contextless" (m: Memory) -> Mem64 {
|
||||
return Mem64{ mem = m }
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
mem80 :: #force_inline proc "contextless" (m: Memory) -> Mem80 {
|
||||
return Mem80{ mem = m }
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
mem128 :: #force_inline proc "contextless" (m: Memory) -> Mem128 {
|
||||
return Mem128{ mem = m }
|
||||
@@ -1219,10 +1225,12 @@ inst_jmp_rel8 :: #force_inline proc "contextless" (offset:
|
||||
inst_jmp_rel32 :: #force_inline proc "contextless" (offset: i32) -> Instruction { return inst_rel_offset(.JMP, i64(offset), 4) }
|
||||
inst_jmp_r64 :: #force_inline proc "contextless" (dst: GPR64) -> Instruction { return inst_r(.JMP, Register(dst)) }
|
||||
inst_jmp_m64 :: #force_inline proc "contextless" (dst: Mem64) -> Instruction { return inst_m(.JMP, dst.mem, 8) }
|
||||
inst_jmp_m :: #force_inline proc "contextless" (dst: Memory) -> Instruction { return inst_m(.JMP, dst, 0) }
|
||||
emit_jmp_rel8 :: #force_inline proc(instructions: ^[dynamic]Instruction, offset: i8) { emit_rel_offset(instructions, .JMP, i64(offset), 1) }
|
||||
emit_jmp_rel32 :: #force_inline proc(instructions: ^[dynamic]Instruction, offset: i32) { emit_rel_offset(instructions, .JMP, i64(offset), 4) }
|
||||
emit_jmp_r64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR64) { emit_r(instructions, .JMP, Register(dst)) }
|
||||
emit_jmp_m64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem64) { emit_m(instructions, .JMP, dst.mem, 8) }
|
||||
emit_jmp_m :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Memory) { emit_m(instructions, .JMP, dst, 0) }
|
||||
inst_ja_rel8 :: #force_inline proc "contextless" (offset: i8) -> Instruction { return inst_rel_offset(.JA, i64(offset), 1) }
|
||||
inst_ja_rel32 :: #force_inline proc "contextless" (offset: i32) -> Instruction { return inst_rel_offset(.JA, i64(offset), 4) }
|
||||
emit_ja_rel8 :: #force_inline proc(instructions: ^[dynamic]Instruction, offset: i8) { emit_rel_offset(instructions, .JA, i64(offset), 1) }
|
||||
@@ -1358,9 +1366,11 @@ emit_loopne_rel8 :: #force_inline proc(instructions: ^[dynami
|
||||
inst_call_rel32 :: #force_inline proc "contextless" (offset: i32) -> Instruction { return inst_rel_offset(.CALL, i64(offset), 4) }
|
||||
inst_call_r64 :: #force_inline proc "contextless" (dst: GPR64) -> Instruction { return inst_r(.CALL, Register(dst)) }
|
||||
inst_call_m64 :: #force_inline proc "contextless" (dst: Mem64) -> Instruction { return inst_m(.CALL, dst.mem, 8) }
|
||||
inst_call_m :: #force_inline proc "contextless" (dst: Memory) -> Instruction { return inst_m(.CALL, dst, 0) }
|
||||
emit_call_rel32 :: #force_inline proc(instructions: ^[dynamic]Instruction, offset: i32) { emit_rel_offset(instructions, .CALL, i64(offset), 4) }
|
||||
emit_call_r64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR64) { emit_r(instructions, .CALL, Register(dst)) }
|
||||
emit_call_m64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem64) { emit_m(instructions, .CALL, dst.mem, 8) }
|
||||
emit_call_m :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Memory) { emit_m(instructions, .CALL, dst, 0) }
|
||||
inst_ret_none :: #force_inline proc "contextless" () -> Instruction { return inst_none(.RET) }
|
||||
inst_ret_imm16 :: #force_inline proc "contextless" (imm: i16) -> Instruction { return inst_i(.RET, i64(imm), 2) }
|
||||
emit_ret_none :: #force_inline proc(instructions: ^[dynamic]Instruction) { emit_none(instructions, .RET) }
|
||||
@@ -7077,9 +7087,11 @@ inst_fxam_none :: #force_inline proc "contextless" () -> In
|
||||
emit_fxam_none :: #force_inline proc(instructions: ^[dynamic]Instruction) { emit_none(instructions, .FXAM) }
|
||||
inst_fld_m32 :: #force_inline proc "contextless" (dst: Mem32) -> Instruction { return inst_m(.FLD, dst.mem, 4) }
|
||||
inst_fld_m64 :: #force_inline proc "contextless" (dst: Mem64) -> Instruction { return inst_m(.FLD, dst.mem, 8) }
|
||||
inst_fld_m80 :: #force_inline proc "contextless" (dst: Mem80) -> Instruction { return inst_m(.FLD, dst.mem, 10) }
|
||||
inst_fld_st :: #force_inline proc "contextless" (dst: ST) -> Instruction { return inst_r(.FLD, Register(dst)) }
|
||||
emit_fld_m32 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem32) { emit_m(instructions, .FLD, dst.mem, 4) }
|
||||
emit_fld_m64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem64) { emit_m(instructions, .FLD, dst.mem, 8) }
|
||||
emit_fld_m80 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem80) { emit_m(instructions, .FLD, dst.mem, 10) }
|
||||
emit_fld_st :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: ST) { emit_r(instructions, .FLD, Register(dst)) }
|
||||
inst_fild_m16 :: #force_inline proc "contextless" (dst: Mem16) -> Instruction { return inst_m(.FILD, dst.mem, 2) }
|
||||
inst_fild_m32 :: #force_inline proc "contextless" (dst: Mem32) -> Instruction { return inst_m(.FILD, dst.mem, 4) }
|
||||
@@ -7087,6 +7099,8 @@ inst_fild_m64 :: #force_inline proc "contextless" (dst: Me
|
||||
emit_fild_m16 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem16) { emit_m(instructions, .FILD, dst.mem, 2) }
|
||||
emit_fild_m32 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem32) { emit_m(instructions, .FILD, dst.mem, 4) }
|
||||
emit_fild_m64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem64) { emit_m(instructions, .FILD, dst.mem, 8) }
|
||||
inst_fbld_m80 :: #force_inline proc "contextless" (dst: Mem80) -> Instruction { return inst_m(.FBLD, dst.mem, 10) }
|
||||
emit_fbld_m80 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem80) { emit_m(instructions, .FBLD, dst.mem, 10) }
|
||||
inst_fst_m32 :: #force_inline proc "contextless" (dst: Mem32) -> Instruction { return inst_m(.FST, dst.mem, 4) }
|
||||
inst_fst_m64 :: #force_inline proc "contextless" (dst: Mem64) -> Instruction { return inst_m(.FST, dst.mem, 8) }
|
||||
inst_fst_st :: #force_inline proc "contextless" (dst: ST) -> Instruction { return inst_r(.FST, Register(dst)) }
|
||||
@@ -7095,9 +7109,11 @@ emit_fst_m64 :: #force_inline proc(instructions: ^[dynami
|
||||
emit_fst_st :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: ST) { emit_r(instructions, .FST, Register(dst)) }
|
||||
inst_fstp_m32 :: #force_inline proc "contextless" (dst: Mem32) -> Instruction { return inst_m(.FSTP, dst.mem, 4) }
|
||||
inst_fstp_m64 :: #force_inline proc "contextless" (dst: Mem64) -> Instruction { return inst_m(.FSTP, dst.mem, 8) }
|
||||
inst_fstp_m80 :: #force_inline proc "contextless" (dst: Mem80) -> Instruction { return inst_m(.FSTP, dst.mem, 10) }
|
||||
inst_fstp_st :: #force_inline proc "contextless" (dst: ST) -> Instruction { return inst_r(.FSTP, Register(dst)) }
|
||||
emit_fstp_m32 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem32) { emit_m(instructions, .FSTP, dst.mem, 4) }
|
||||
emit_fstp_m64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem64) { emit_m(instructions, .FSTP, dst.mem, 8) }
|
||||
emit_fstp_m80 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem80) { emit_m(instructions, .FSTP, dst.mem, 10) }
|
||||
emit_fstp_st :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: ST) { emit_r(instructions, .FSTP, Register(dst)) }
|
||||
inst_fist_m16 :: #force_inline proc "contextless" (dst: Mem16) -> Instruction { return inst_m(.FIST, dst.mem, 2) }
|
||||
inst_fist_m32 :: #force_inline proc "contextless" (dst: Mem32) -> Instruction { return inst_m(.FIST, dst.mem, 4) }
|
||||
@@ -7115,6 +7131,8 @@ inst_fisttp_m64 :: #force_inline proc "contextless" (dst: Me
|
||||
emit_fisttp_m16 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem16) { emit_m(instructions, .FISTTP, dst.mem, 2) }
|
||||
emit_fisttp_m32 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem32) { emit_m(instructions, .FISTTP, dst.mem, 4) }
|
||||
emit_fisttp_m64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem64) { emit_m(instructions, .FISTTP, dst.mem, 8) }
|
||||
inst_fbstp_m80 :: #force_inline proc "contextless" (dst: Mem80) -> Instruction { return inst_m(.FBSTP, dst.mem, 10) }
|
||||
emit_fbstp_m80 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem80) { emit_m(instructions, .FBSTP, dst.mem, 10) }
|
||||
inst_fxch_st :: #force_inline proc "contextless" (dst: ST) -> Instruction { return inst_r(.FXCH, Register(dst)) }
|
||||
inst_fxch_none :: #force_inline proc "contextless" () -> Instruction { return inst_none(.FXCH) }
|
||||
emit_fxch_st :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: ST) { emit_r(instructions, .FXCH, Register(dst)) }
|
||||
@@ -7261,6 +7279,14 @@ inst_fxrstor_m512 :: #force_inline proc "contextless" (dst: Me
|
||||
emit_fxrstor_m512 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem512) { emit_m(instructions, .FXRSTOR, dst.mem, 64) }
|
||||
inst_fxrstor64_m512 :: #force_inline proc "contextless" (dst: Mem512) -> Instruction { return inst_m(.FXRSTOR64, dst.mem, 64) }
|
||||
emit_fxrstor64_m512 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem512) { emit_m(instructions, .FXRSTOR64, dst.mem, 64) }
|
||||
inst_lgdt_m :: #force_inline proc "contextless" (dst: Memory) -> Instruction { return inst_m(.LGDT, dst, 0) }
|
||||
emit_lgdt_m :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Memory) { emit_m(instructions, .LGDT, dst, 0) }
|
||||
inst_sgdt_m :: #force_inline proc "contextless" (dst: Memory) -> Instruction { return inst_m(.SGDT, dst, 0) }
|
||||
emit_sgdt_m :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Memory) { emit_m(instructions, .SGDT, dst, 0) }
|
||||
inst_lidt_m :: #force_inline proc "contextless" (dst: Memory) -> Instruction { return inst_m(.LIDT, dst, 0) }
|
||||
emit_lidt_m :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Memory) { emit_m(instructions, .LIDT, dst, 0) }
|
||||
inst_sidt_m :: #force_inline proc "contextless" (dst: Memory) -> Instruction { return inst_m(.SIDT, dst, 0) }
|
||||
emit_sidt_m :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Memory) { emit_m(instructions, .SIDT, dst, 0) }
|
||||
inst_lldt_r16 :: #force_inline proc "contextless" (dst: GPR16) -> Instruction { return inst_r(.LLDT, Register(dst)) }
|
||||
inst_lldt_m16 :: #force_inline proc "contextless" (dst: Mem16) -> Instruction { return inst_m(.LLDT, dst.mem, 2) }
|
||||
emit_lldt_r16 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR16) { emit_r(instructions, .LLDT, Register(dst)) }
|
||||
@@ -7499,9 +7525,9 @@ emit_xadd_r32_r32 :: #force_inline proc(instructions: ^[dynami
|
||||
emit_xadd_m32_r32 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem32, src: GPR32) { emit_mr(instructions, .XADD, dst.mem, 4, Register(src)) }
|
||||
emit_xadd_r64_r64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR64, src: GPR64) { emit_rr(instructions, .XADD, Register(dst), Register(src)) }
|
||||
emit_xadd_m64_r64 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Mem64, src: GPR64) { emit_mr(instructions, .XADD, dst.mem, 8, Register(src)) }
|
||||
inst_bound_r16 :: #force_inline proc "contextless" (dst: GPR16) -> Instruction { return inst_r(.BOUND, Register(dst)) }
|
||||
inst_bound_r16_m :: #force_inline proc "contextless" (dst: GPR16, src: Memory) -> Instruction { return inst_r_m(.BOUND, Register(dst), src, 0) }
|
||||
inst_bound_r32_m32 :: #force_inline proc "contextless" (dst: GPR32, src: Mem32) -> Instruction { return inst_r_m(.BOUND, Register(dst), src.mem, 4) }
|
||||
emit_bound_r16 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR16) { emit_r(instructions, .BOUND, Register(dst)) }
|
||||
emit_bound_r16_m :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR16, src: Memory) { emit_rm(instructions, .BOUND, Register(dst), src, 0) }
|
||||
emit_bound_r32_m32 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR32, src: Mem32) { emit_rm(instructions, .BOUND, Register(dst), src.mem, 4) }
|
||||
inst_enter_imm16_imm8 :: #force_inline proc "contextless" (imm: i16, imm2: i8) -> Instruction { return Instruction{ mnemonic = .ENTER, operand_count = 2, ops = {op_imm16(imm), op_imm8(imm2), {}, {}} } }
|
||||
emit_enter_imm16_imm8 :: #force_inline proc(instructions: ^[dynamic]Instruction, imm: i16, imm2: i8) { append(instructions, inst_enter_imm16_imm8(imm, imm2)) }
|
||||
@@ -7628,8 +7654,8 @@ inst_lzcnt :: proc{ inst_lzcnt_r16_r16, inst_lzcnt
|
||||
emit_lzcnt :: proc{ emit_lzcnt_r16_r16, emit_lzcnt_r16_m16, emit_lzcnt_r32_r32, emit_lzcnt_r32_m32, emit_lzcnt_r64_r64, emit_lzcnt_r64_m64 }
|
||||
inst_tzcnt :: proc{ inst_tzcnt_r16_r16, inst_tzcnt_r16_m16, inst_tzcnt_r32_r32, inst_tzcnt_r32_m32, inst_tzcnt_r64_r64, inst_tzcnt_r64_m64 }
|
||||
emit_tzcnt :: proc{ emit_tzcnt_r16_r16, emit_tzcnt_r16_m16, emit_tzcnt_r32_r32, emit_tzcnt_r32_m32, emit_tzcnt_r64_r64, emit_tzcnt_r64_m64 }
|
||||
inst_jmp :: proc{ inst_jmp_rel8, inst_jmp_rel32, inst_jmp_r64, inst_jmp_m64 }
|
||||
emit_jmp :: proc{ emit_jmp_rel8, emit_jmp_rel32, emit_jmp_r64, emit_jmp_m64 }
|
||||
inst_jmp :: proc{ inst_jmp_rel8, inst_jmp_rel32, inst_jmp_r64, inst_jmp_m64, inst_jmp_m }
|
||||
emit_jmp :: proc{ emit_jmp_rel8, emit_jmp_rel32, emit_jmp_r64, emit_jmp_m64, emit_jmp_m }
|
||||
inst_ja :: proc{ inst_ja_rel8, inst_ja_rel32 }
|
||||
emit_ja :: proc{ emit_ja_rel8, emit_ja_rel32 }
|
||||
inst_jae :: proc{ inst_jae_rel8, inst_jae_rel32 }
|
||||
@@ -7702,8 +7728,8 @@ inst_loope :: inst_loope_rel8
|
||||
emit_loope :: emit_loope_rel8
|
||||
inst_loopne :: inst_loopne_rel8
|
||||
emit_loopne :: emit_loopne_rel8
|
||||
inst_call :: proc{ inst_call_rel32, inst_call_r64, inst_call_m64 }
|
||||
emit_call :: proc{ emit_call_rel32, emit_call_r64, emit_call_m64 }
|
||||
inst_call :: proc{ inst_call_rel32, inst_call_r64, inst_call_m64, inst_call_m }
|
||||
emit_call :: proc{ emit_call_rel32, emit_call_r64, emit_call_m64, emit_call_m }
|
||||
inst_ret :: proc{ inst_ret_none, inst_ret_imm16 }
|
||||
emit_ret :: proc{ emit_ret_none, emit_ret_imm16 }
|
||||
inst_iret :: inst_iret_none
|
||||
@@ -9572,20 +9598,24 @@ inst_fxtract :: inst_fxtract_none
|
||||
emit_fxtract :: emit_fxtract_none
|
||||
inst_fxam :: inst_fxam_none
|
||||
emit_fxam :: emit_fxam_none
|
||||
inst_fld :: proc{ inst_fld_m32, inst_fld_m64, inst_fld_st }
|
||||
emit_fld :: proc{ emit_fld_m32, emit_fld_m64, emit_fld_st }
|
||||
inst_fld :: proc{ inst_fld_m32, inst_fld_m64, inst_fld_m80, inst_fld_st }
|
||||
emit_fld :: proc{ emit_fld_m32, emit_fld_m64, emit_fld_m80, emit_fld_st }
|
||||
inst_fild :: proc{ inst_fild_m16, inst_fild_m32, inst_fild_m64 }
|
||||
emit_fild :: proc{ emit_fild_m16, emit_fild_m32, emit_fild_m64 }
|
||||
inst_fbld :: inst_fbld_m80
|
||||
emit_fbld :: emit_fbld_m80
|
||||
inst_fst :: proc{ inst_fst_m32, inst_fst_m64, inst_fst_st }
|
||||
emit_fst :: proc{ emit_fst_m32, emit_fst_m64, emit_fst_st }
|
||||
inst_fstp :: proc{ inst_fstp_m32, inst_fstp_m64, inst_fstp_st }
|
||||
emit_fstp :: proc{ emit_fstp_m32, emit_fstp_m64, emit_fstp_st }
|
||||
inst_fstp :: proc{ inst_fstp_m32, inst_fstp_m64, inst_fstp_m80, inst_fstp_st }
|
||||
emit_fstp :: proc{ emit_fstp_m32, emit_fstp_m64, emit_fstp_m80, emit_fstp_st }
|
||||
inst_fist :: proc{ inst_fist_m16, inst_fist_m32 }
|
||||
emit_fist :: proc{ emit_fist_m16, emit_fist_m32 }
|
||||
inst_fistp :: proc{ inst_fistp_m16, inst_fistp_m32, inst_fistp_m64 }
|
||||
emit_fistp :: proc{ emit_fistp_m16, emit_fistp_m32, emit_fistp_m64 }
|
||||
inst_fisttp :: proc{ inst_fisttp_m16, inst_fisttp_m32, inst_fisttp_m64 }
|
||||
emit_fisttp :: proc{ emit_fisttp_m16, emit_fisttp_m32, emit_fisttp_m64 }
|
||||
inst_fbstp :: inst_fbstp_m80
|
||||
emit_fbstp :: emit_fbstp_m80
|
||||
inst_fxch :: proc{ inst_fxch_st, inst_fxch_none }
|
||||
emit_fxch :: proc{ emit_fxch_st, emit_fxch_none }
|
||||
inst_fcmovb :: inst_fcmovb_st
|
||||
@@ -9710,6 +9740,14 @@ inst_fxrstor :: inst_fxrstor_m512
|
||||
emit_fxrstor :: emit_fxrstor_m512
|
||||
inst_fxrstor64 :: inst_fxrstor64_m512
|
||||
emit_fxrstor64 :: emit_fxrstor64_m512
|
||||
inst_lgdt :: inst_lgdt_m
|
||||
emit_lgdt :: emit_lgdt_m
|
||||
inst_sgdt :: inst_sgdt_m
|
||||
emit_sgdt :: emit_sgdt_m
|
||||
inst_lidt :: inst_lidt_m
|
||||
emit_lidt :: emit_lidt_m
|
||||
inst_sidt :: inst_sidt_m
|
||||
emit_sidt :: emit_sidt_m
|
||||
inst_lldt :: proc{ inst_lldt_r16, inst_lldt_m16 }
|
||||
emit_lldt :: proc{ emit_lldt_r16, emit_lldt_m16 }
|
||||
inst_sldt :: proc{ inst_sldt_r16, inst_sldt_m16, inst_sldt_r32, inst_sldt_r64 }
|
||||
@@ -9862,8 +9900,8 @@ inst_cmpxchg16b :: inst_cmpxchg16b_m128
|
||||
emit_cmpxchg16b :: emit_cmpxchg16b_m128
|
||||
inst_xadd :: proc{ inst_xadd_r8_r8, inst_xadd_m8_r8, inst_xadd_r16_r16, inst_xadd_m16_r16, inst_xadd_r32_r32, inst_xadd_m32_r32, inst_xadd_r64_r64, inst_xadd_m64_r64 }
|
||||
emit_xadd :: proc{ emit_xadd_r8_r8, emit_xadd_m8_r8, emit_xadd_r16_r16, emit_xadd_m16_r16, emit_xadd_r32_r32, emit_xadd_m32_r32, emit_xadd_r64_r64, emit_xadd_m64_r64 }
|
||||
inst_bound :: proc{ inst_bound_r16, inst_bound_r32_m32 }
|
||||
emit_bound :: proc{ emit_bound_r16, emit_bound_r32_m32 }
|
||||
inst_bound :: proc{ inst_bound_r16_m, inst_bound_r32_m32 }
|
||||
emit_bound :: proc{ emit_bound_r16_m, emit_bound_r32_m32 }
|
||||
inst_enter :: inst_enter_imm16_imm8
|
||||
emit_enter :: emit_enter_imm16_imm8
|
||||
inst_leave :: inst_leave_none
|
||||
|
||||
@@ -244,6 +244,7 @@ Mem8 :: distinct struct { mem: Memory }
|
||||
Mem16 :: distinct struct { mem: Memory }
|
||||
Mem32 :: distinct struct { mem: Memory }
|
||||
Mem64 :: distinct struct { mem: Memory }
|
||||
Mem80 :: distinct struct { mem: Memory }
|
||||
Mem128 :: distinct struct { mem: Memory }
|
||||
Mem256 :: distinct struct { mem: Memory }
|
||||
Mem512 :: distinct struct { mem: Memory }
|
||||
@@ -269,6 +270,11 @@ mem64 :: #force_inline proc "contextless" (m: Memory) -> Mem64 {
|
||||
return Mem64{ mem = m }
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
mem80 :: #force_inline proc "contextless" (m: Memory) -> Mem80 {
|
||||
return Mem80{ mem = m }
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
mem128 :: #force_inline proc "contextless" (m: Memory) -> Mem128 {
|
||||
return Mem128{ mem = m }
|
||||
@@ -307,8 +313,8 @@ can_generate_builder :: proc(enc: x86.Encoding) -> bool {
|
||||
case .MOFFS8, .MOFFS16, .MOFFS32, .MOFFS64:
|
||||
// Skip moffs - special encoding
|
||||
continue
|
||||
case .PTR16_16, .PTR16_32, .PTR16_64, .M16_16, .M16_32, .M16_64:
|
||||
// Skip far pointers
|
||||
case .PTR16_16, .PTR16_32, .PTR16_64:
|
||||
// Skip far pointers (16:16 / 16:32 / 16:64 immediate ptr forms)
|
||||
continue
|
||||
case .IMM8SX:
|
||||
// Skip sign-extended immediates for now
|
||||
@@ -425,6 +431,10 @@ is_memory_only_operand :: proc(op: x86.Operand_Type) -> bool {
|
||||
#partial switch op {
|
||||
case .M, .M8, .M16, .M32, .M64, .M80, .M128, .M256, .M512:
|
||||
return true
|
||||
// Descriptor-table memory operands (LGDT/SGDT/LIDT/SIDT). The encoder treats
|
||||
// them as size-agnostic memory, so they map to a single Memory parameter.
|
||||
case .M16_16, .M16_32, .M16_64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -435,7 +445,7 @@ is_implicit_operand :: proc(op: x86.Operand_Type) -> bool {
|
||||
return true
|
||||
case .MOFFS8, .MOFFS16, .MOFFS32, .MOFFS64:
|
||||
return true
|
||||
case .PTR16_16, .PTR16_32, .PTR16_64, .M16_16, .M16_32, .M16_64:
|
||||
case .PTR16_16, .PTR16_32, .PTR16_64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -452,7 +462,10 @@ can_generate_operand :: proc(info: Operand_Info) -> bool {
|
||||
case .RM8, .RM16, .RM32, .RM64:
|
||||
return true
|
||||
// Memory only
|
||||
case .M, .M8, .M16, .M32, .M64, .M128, .M256, .M512:
|
||||
case .M, .M8, .M16, .M32, .M64, .M80, .M128, .M256, .M512:
|
||||
return true
|
||||
// Descriptor-table memory (LGDT/SGDT/LIDT/SIDT) -> single Memory parameter
|
||||
case .M16_16, .M16_32, .M16_64:
|
||||
return true
|
||||
// Immediates
|
||||
case .IMM8, .IMM16, .IMM32, .IMM64:
|
||||
@@ -549,9 +562,13 @@ operand_suffix :: proc(info: Operand_Info) -> string {
|
||||
case .M16: return "m16"
|
||||
case .M32: return "m32"
|
||||
case .M64: return "m64"
|
||||
case .M80: return "m80"
|
||||
case .M128: return "m128"
|
||||
case .M256: return "m256"
|
||||
case .M512: return "m512"
|
||||
// Descriptor-table memory: collapse 16:32 / 16:64 forms to a single
|
||||
// generic memory builder (matches the encoder's size-agnostic handling).
|
||||
case .M16_16, .M16_32, .M16_64: return "m"
|
||||
}
|
||||
} else {
|
||||
#partial switch op {
|
||||
@@ -620,9 +637,11 @@ operand_odin_type :: proc(info: Operand_Info) -> string {
|
||||
case .M16: return "Mem16"
|
||||
case .M32: return "Mem32"
|
||||
case .M64: return "Mem64"
|
||||
case .M80: return "Mem80"
|
||||
case .M128: return "Mem128"
|
||||
case .M256: return "Mem256"
|
||||
case .M512: return "Mem512"
|
||||
case .M16_16, .M16_32, .M16_64: return "Memory"
|
||||
}
|
||||
} else {
|
||||
#partial switch op {
|
||||
@@ -699,10 +718,10 @@ operand_size :: proc(info: Operand_Info) -> u8 {
|
||||
return 8
|
||||
case .K:
|
||||
return 8
|
||||
case .STI:
|
||||
case .STI, .M80:
|
||||
return 10
|
||||
case .M:
|
||||
return 0 // Size not known for generic memory
|
||||
case .M, .M16_16, .M16_32, .M16_64:
|
||||
return 0 // Size not known for generic / descriptor-table memory
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -714,10 +733,11 @@ generate_operand_expr :: proc(sb: ^strings.Builder, info: Operand_Info, param_na
|
||||
if info.is_memory {
|
||||
// Memory operands use op_mem with the inner memory and size
|
||||
size := operand_size(info)
|
||||
if op == .M {
|
||||
// Generic memory - size is 0
|
||||
fmt.sbprintf(sb, "op_mem(%s, 0)", param_name)
|
||||
} else {
|
||||
#partial switch op {
|
||||
case .M, .M16_16, .M16_32, .M16_64:
|
||||
// Generic / descriptor-table memory: typed as raw Memory, size unknown
|
||||
fmt.sbprintf(sb, "op_mem(%s, %d)", param_name, size)
|
||||
case:
|
||||
fmt.sbprintf(sb, "op_mem(%s.mem, %d)", param_name, size)
|
||||
}
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user