mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-20 00:52:33 +00:00
238 lines
9.0 KiB
Odin
238 lines
9.0 KiB
Odin
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)
|
|
_: [4]u8, // 4
|
|
}
|
|
#assert(size_of(Shifted_Reg) == 8)
|
|
|
|
Extended_Reg :: struct #packed {
|
|
reg: Register, // 2
|
|
extend: Extend, // 1
|
|
amount: u8, // 1 (0..4)
|
|
_: [4]u8, // 4
|
|
}
|
|
#assert(size_of(Extended_Reg) == 8)
|
|
|
|
// 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
|
|
},
|
|
kind: Operand_Kind, // 1
|
|
size: u8, // 1 -- carried width info; meaning varies
|
|
_: [2]u8, // 2
|
|
}
|
|
// NB: Memory is 12 bytes, larger than the i64 payload other arches use,
|
|
// so Operand here is 16+: the table-driven matcher is size-agnostic.
|
|
#assert(size_of(Operand) >= 16 && size_of(Operand) <= 24)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Constructors -- generic
|
|
// -----------------------------------------------------------------------------
|
|
|
|
op_reg :: #force_inline proc "contextless" (r: Register) -> Operand { return Operand{reg = r, kind = .REGISTER, size = 4} }
|
|
op_imm :: #force_inline proc "contextless" (v: i64, size: u8 = 4) -> Operand { return Operand{immediate = v, kind = .IMMEDIATE, size = size} }
|
|
op_label :: #force_inline proc "contextless" (label_id: u32, size: u8 = 4) -> Operand {
|
|
return Operand{relative = i64(label_id), kind = .RELATIVE, size = size}
|
|
}
|
|
op_rel_offset :: #force_inline proc "contextless" (off: i64) -> Operand {
|
|
return Operand{relative = off, kind = .RELATIVE, size = 4}
|
|
}
|
|
|
|
op_mem :: #force_inline proc "contextless" (m: Memory) -> Operand {
|
|
return Operand{mem = m, kind = .MEMORY, size = 4}
|
|
}
|
|
|
|
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}
|
|
}
|
|
|
|
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}
|
|
}
|
|
|
|
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.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
op_z_b :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_Z | u16(n & 0x1F)), kind = .REGISTER, size = 1}
|
|
}
|
|
op_z_h :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_Z | u16(n & 0x1F)), kind = .REGISTER, size = 2}
|
|
}
|
|
op_z_s :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_Z | u16(n & 0x1F)), kind = .REGISTER, size = 4}
|
|
}
|
|
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.)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
op_v_8b :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 8}
|
|
}
|
|
op_v_16b :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 16}
|
|
}
|
|
op_v_4h :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 24}
|
|
}
|
|
op_v_8h :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 32}
|
|
}
|
|
op_v_2s :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 40}
|
|
}
|
|
op_v_4s :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 48}
|
|
}
|
|
op_v_1d :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 56}
|
|
}
|
|
op_v_2d :: #force_inline proc "contextless" (n: u8) -> Operand {
|
|
return Operand{reg = Register(REG_V | u16(n & 0x1F)), kind = .REGISTER, size = 64}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Memory constructors (one per addressing mode)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
mem_offset :: #force_inline proc "contextless" (base: Register, disp: i32 = 0) -> Memory {
|
|
return Memory{base = base, index = NONE, disp = disp, mode = .OFFSET}
|
|
}
|
|
mem_pre :: #force_inline proc "contextless" (base: Register, disp: i32) -> Memory {
|
|
return Memory{base = base, index = NONE, disp = disp, mode = .PRE_INDEXED}
|
|
}
|
|
mem_post :: #force_inline proc "contextless" (base: Register, disp: i32) -> Memory {
|
|
return Memory{base = base, index = NONE, disp = disp, mode = .POST_INDEXED}
|
|
}
|
|
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}
|
|
}
|
|
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
|