mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-19 16:42:33 +00:00
MOV_V (ORR alias: source feeds both Vn and Vm via a new VN_VM_DUP encoding), MVN_V (NOT alias, plain 2-register), DUP_V (element form Vd.T,Vn.Ts[i] and general form Vd.T,Wn/Xn), INS (element-to-element and from-GPR), EXT_V (imm4 byte index). Adds a VEC_INDEX operand type plus NEON_IDX5/NEON_IDX4/NEON_EXT_IDX encodings: the element-size marker rides in the entry bits, the lane index drives the bits above it, and the decoder recovers the element size from imm5's marker. Element size now rides in op.size (B=1/H=2/S=4/D=8) via op_v_elem_b/h/s/d so the matcher can disambiguate DUP/INS element forms; the builder generator maps V_ELEM_* to those constructors. specgen derives the mask by varying registers and each index field to its max -- the GPR-source forms vary Vd and Rn independently (Rn 31 = wzr/xzr) so the low bit of each field toggles. All 19 representative forms byte-exact vs llvm-mc and decode-clean; 461 tests green. (TBL/TBX register-list forms deferred.)
284 lines
10 KiB
Odin
284 lines
10 KiB
Odin
// rexcode · Brendan Punsky (dotbmp@github), original author
|
|
|
|
package rexcode_arm64
|
|
|
|
// =============================================================================
|
|
// AArch64 OPERANDS
|
|
// =============================================================================
|
|
//
|
|
// AArch64 has a rich addressing repertoire:
|
|
//
|
|
// [Xn] OFFSET with imm=0
|
|
// [Xn, #imm] OFFSET (signed 9 or unsigned scaled 12)
|
|
// [Xn, #imm]! PRE_INDEXED (writeback before)
|
|
// [Xn], #imm POST_INDEXED (writeback after)
|
|
// [Xn, Xm{, LSL #s}] REG_OFFSET (shift = log2(size) when present)
|
|
// [Xn, Wm, SXTW|UXTW|SXTX #s] EXT_REG_OFFSET
|
|
// label LITERAL (PC-relative for LDR literal)
|
|
//
|
|
// `Shift_Type` and `Extend` enumerate the shifter/extender flavours that
|
|
// data-processing register and memory operand encodings need.
|
|
|
|
Operand_Kind :: enum u8 {
|
|
NONE,
|
|
REGISTER,
|
|
IMMEDIATE,
|
|
MEMORY,
|
|
RELATIVE,
|
|
SHIFTED_REG, // X reg + shift type + shift amount
|
|
EXTENDED_REG, // X/W reg + extend + amount
|
|
COND, // 4-bit condition code (EQ/NE/.../AL/NV)
|
|
}
|
|
|
|
Shift_Type :: enum u8 {
|
|
LSL = 0,
|
|
LSR = 1,
|
|
ASR = 2,
|
|
ROR = 3,
|
|
}
|
|
|
|
Extend :: enum u8 {
|
|
UXTB = 0,
|
|
UXTH = 1,
|
|
UXTW = 2,
|
|
UXTX = 3,
|
|
SXTB = 4,
|
|
SXTH = 5,
|
|
SXTW = 6,
|
|
SXTX = 7,
|
|
}
|
|
|
|
Address_Mode :: enum u8 {
|
|
OFFSET, // [Xn, #imm] (imm may be 0)
|
|
PRE_INDEXED, // [Xn, #imm]!
|
|
POST_INDEXED, // [Xn], #imm
|
|
REG_OFFSET, // [Xn, Xm{, LSL #s}]
|
|
EXT_REG_OFFSET, // [Xn, Wm, SXTW|UXTW|SXTX #s]
|
|
LITERAL, // PC-rel target (LDR literal)
|
|
}
|
|
|
|
// 16-byte memory operand: base + optional index + signed disp + addressing
|
|
// metadata. Index is `NONE` for non-register-offset modes.
|
|
Memory :: struct #packed {
|
|
base: Register, // 2
|
|
index: Register, // 2 (NONE for OFFSET/PRE/POST/LITERAL)
|
|
disp: i32, // 4 (signed; pre/post can be -256..255 unscaled,
|
|
// OFFSET supports 0..32760 scaled via imm12*size)
|
|
extend: Extend, // 1 (for EXT_REG_OFFSET; UXTX otherwise)
|
|
shift: u8, // 1 (0..4 for register-offset / extended; or
|
|
// shift amount for shifted-register operands
|
|
// when reused there)
|
|
mode: Address_Mode, // 1
|
|
_: u8, // 1
|
|
}
|
|
#assert(size_of(Memory) == 12)
|
|
|
|
Shifted_Reg :: struct #packed {
|
|
reg: Register, // 2
|
|
type: Shift_Type, // 1
|
|
amount: u8, // 1 (0..63 for 64-bit; 0..31 for 32-bit)
|
|
}
|
|
#assert(size_of(Shifted_Reg) == 4)
|
|
|
|
Extended_Reg :: struct #packed {
|
|
reg: Register, // 2
|
|
extend: Extend, // 1
|
|
amount: u8, // 1 (0..4)
|
|
}
|
|
#assert(size_of(Extended_Reg) == 4)
|
|
|
|
// 16-byte tagged operand. The union holds whichever payload matches `kind`.
|
|
Operand :: struct #packed {
|
|
using _: struct #raw_union {
|
|
reg: Register, // 2
|
|
mem: Memory, // 12
|
|
immediate: i64, // 8
|
|
relative: i64, // 8
|
|
shifted: Shifted_Reg, // 8
|
|
extended: Extended_Reg, // 8
|
|
cond: u8, // 1
|
|
}, // 16 total because of alignment
|
|
kind: Operand_Kind, // 1
|
|
size: u8, // 1 -- carried width info; meaning varies
|
|
}
|
|
#assert(size_of(Operand) == 18)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Constructors -- generic
|
|
// -----------------------------------------------------------------------------
|
|
|
|
@(require_results)
|
|
op_reg :: #force_inline proc "contextless" (r: Register) -> Operand {
|
|
return Operand{reg = r, kind = .REGISTER, size = 4}
|
|
}
|
|
@(require_results)
|
|
op_imm :: #force_inline proc "contextless" (v: i64, size: u8 = 4) -> Operand {
|
|
return Operand{immediate = v, kind = .IMMEDIATE, size = size}
|
|
}
|
|
@(require_results)
|
|
op_label :: #force_inline proc "contextless" (label_id: u32, size: u8 = 4) -> Operand {
|
|
return Operand{relative = i64(label_id), kind = .RELATIVE, size = size}
|
|
}
|
|
@(require_results)
|
|
op_rel_offset :: #force_inline proc "contextless" (off: i64) -> Operand {
|
|
return Operand{relative = off, kind = .RELATIVE, size = 4}
|
|
}
|
|
|
|
@(require_results)
|
|
op_mem :: #force_inline proc "contextless" (m: Memory) -> Operand {
|
|
return Operand{mem = m, kind = .MEMORY, size = 4}
|
|
}
|
|
|
|
@(require_results)
|
|
op_shifted :: #force_inline proc "contextless" (r: Register, type: Shift_Type, amount: u8) -> Operand {
|
|
return Operand{shifted = Shifted_Reg{reg = r, type = type, amount = amount}, kind = .SHIFTED_REG, size = 4}
|
|
}
|
|
|
|
@(require_results)
|
|
op_extended :: #force_inline proc "contextless" (r: Register, ext: Extend, amount: u8) -> Operand {
|
|
return Operand{extended = Extended_Reg{reg = r, extend = ext, amount = amount}, kind = .EXTENDED_REG, size = 4}
|
|
}
|
|
|
|
@(require_results)
|
|
op_cond :: #force_inline proc "contextless" (c: Cond) -> Operand {
|
|
return Operand{cond = u8(c), kind = .COND, size = 1}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// SVE Z-register builders -- encode the element arrangement in op.size
|
|
// (B=1, H=2, S=4, D=8). Matcher uses op.size to disambiguate the right
|
|
// table form when multiple element sizes share a base mnemonic.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
@(require_results)
|
|
op_z_b :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_Z | u16(n & 0x1F)), kind = .REGISTER, size = 1}
|
|
}
|
|
@(require_results)
|
|
op_z_h :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_Z | u16(n & 0x1F)), kind = .REGISTER, size = 2}
|
|
}
|
|
@(require_results)
|
|
op_z_s :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_Z | u16(n & 0x1F)), kind = .REGISTER, size = 4}
|
|
}
|
|
@(require_results)
|
|
op_z_d :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_Z | u16(n & 0x1F)), kind = .REGISTER, size = 8}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// NEON V-register arrangement builders -- op.size encodes lanes*elem-bytes:
|
|
// .8B = 8 .16B = 16
|
|
// .4H = 24 .8H = 32
|
|
// .2S = 40 .4S = 48
|
|
// .1D = 56 .2D = 64
|
|
// (Encoded so that no two arrangements collide and so the value is easy
|
|
// to inspect.)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
@(require_results)
|
|
op_v_8b :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 8}
|
|
}
|
|
@(require_results)
|
|
op_v_16b :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 16}
|
|
}
|
|
@(require_results)
|
|
op_v_4h :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 24}
|
|
}
|
|
@(require_results)
|
|
op_v_8h :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 32}
|
|
}
|
|
@(require_results)
|
|
op_v_2s :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 40}
|
|
}
|
|
@(require_results)
|
|
op_v_4s :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 48}
|
|
}
|
|
@(require_results)
|
|
op_v_1d :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 56}
|
|
}
|
|
@(require_results)
|
|
op_v_2d :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 64}
|
|
}
|
|
|
|
// Element-indexed V views (V0.B[i]/.H[i]/.S[i]/.D[i]). The element size rides
|
|
// in op.size (1/2/4/8) so the matcher can disambiguate DUP/INS forms; the lane
|
|
// index is a separate immediate operand.
|
|
@(require_results)
|
|
op_v_elem_b :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 1}
|
|
}
|
|
@(require_results)
|
|
op_v_elem_h :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 2}
|
|
}
|
|
@(require_results)
|
|
op_v_elem_s :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 4}
|
|
}
|
|
@(require_results)
|
|
op_v_elem_d :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 8}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Memory constructors (one per addressing mode)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
@(require_results)
|
|
mem_offset :: #force_inline proc "contextless" (base: Register, disp: i32 = 0) -> Memory {
|
|
return Memory{base = base, index = NONE, disp = disp, mode = .OFFSET}
|
|
}
|
|
@(require_results)
|
|
mem_pre :: #force_inline proc "contextless" (base: Register, disp: i32) -> Memory {
|
|
return Memory{base = base, index = NONE, disp = disp, mode = .PRE_INDEXED}
|
|
}
|
|
@(require_results)
|
|
mem_post :: #force_inline proc "contextless" (base: Register, disp: i32) -> Memory {
|
|
return Memory{base = base, index = NONE, disp = disp, mode = .POST_INDEXED}
|
|
}
|
|
@(require_results)
|
|
mem_reg :: #force_inline proc "contextless" (base, index: Register, shift_amount: u8 = 0) -> Memory {
|
|
return Memory{base = base, index = index, mode = .REG_OFFSET, shift = shift_amount, extend = .UXTX}
|
|
}
|
|
@(require_results)
|
|
mem_ext :: #force_inline proc "contextless" (base, index: Register, ext: Extend, shift_amount: u8 = 0) -> Memory {
|
|
return Memory{base = base, index = index, mode = .EXT_REG_OFFSET, extend = ext, shift = shift_amount}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Condition codes
|
|
// -----------------------------------------------------------------------------
|
|
|
|
Cond :: enum u8 {
|
|
EQ = 0x0,
|
|
NE = 0x1,
|
|
CS = 0x2, // unsigned higher or same (alias HS)
|
|
CC = 0x3, // unsigned lower (alias LO)
|
|
MI = 0x4,
|
|
PL = 0x5,
|
|
VS = 0x6,
|
|
VC = 0x7,
|
|
HI = 0x8,
|
|
LS = 0x9,
|
|
GE = 0xA,
|
|
LT = 0xB,
|
|
GT = 0xC,
|
|
LE = 0xD,
|
|
AL = 0xE,
|
|
NV = 0xF,
|
|
}
|
|
|
|
// Architectural aliases for the two carry-style conditions.
|
|
COND_HS :: Cond.CS
|
|
COND_LO :: Cond.CC
|