Files
Odin/core/rexcode/arm32/operands.odin
2026-06-15 14:24:05 +01:00

154 lines
5.6 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_arm32
// =============================================================================
// AArch32 OPERANDS
// =============================================================================
//
// Kind-tagged operand, same shape as other arches. Variations specific to
// AArch32:
//
// * Memory operands carry a much richer payload than RISC-V: base GPR +
// {imm | reg}-offset + shift {LSL/LSR/ASR/ROR/RRX} + pre/post indexing
// + sign. We pack these into a single Memory struct.
//
// * REGISTER operands always store a single Register byte; lane and shape
// hints (e.g. D[idx]) ride in the `lane`/`shift_type` fields.
//
// * IMMEDIATE i64 is wide enough for sign-extended branch displacements,
// 16-bit MOVW/MOVT immediates, modified-immediate raw values, and the
// packed CDE/coproc imm fields.
//
// * RELATIVE = label id (pre-resolution) or signed byte offset (post).
//
// * REG_LIST is a 16-bit GPR bitmask packed into the immediate slot.
Operand_Kind :: enum u8 {
NONE,
REGISTER,
IMMEDIATE,
MEMORY,
RELATIVE,
REG_LIST, // LDM/STM/PUSH/POP bitmask (low 16 bits = R0..R15)
}
// ---- Shift / addressing-mode helpers ---------------------------------------
Shift_Type :: enum u8 {
LSL = 0,
LSR = 1,
ASR = 2,
ROR = 3,
RRX = 4, // pseudo: encoded as ROR #0
NONE = 5,
// Register-shifted-register markers: the shift count comes from the Rs
// register stored in shift_amt (0..15), not from an immediate. Encoder
// packs bits 11..8 = Rs, 6..5 = (type - LSL_REG) low 2 bits, bit 4 = 1.
LSL_REG = 6,
LSR_REG = 7,
ASR_REG = 8,
ROR_REG = 9,
}
Index_Mode :: enum u8 {
OFFSET = 0, // [Rn, #imm] -- no writeback
PRE_INDEX = 1, // [Rn, #imm]! -- writeback after addr calc
POST_INDEX = 2, // [Rn], #imm -- writeback, base = Rn pre-update
}
Memory :: struct #packed {
base: Register, // GPR base register
index: Register, // GPR or .NONE for imm-only forms
shift_type: Shift_Type,
shift_amt: u8, // 0..31 immediate shift
mode: Index_Mode,
sign: i8, // +1 or -1 (U bit)
disp: i32, // immediate displacement (sign-extended)
}
#assert(size_of(Memory) == 12)
@(require_results)
mem_imm :: #force_inline proc "contextless" (base: Register, disp: i32) -> Memory {
return Memory{base = base, disp = disp, sign = 1, mode = .OFFSET}
}
@(require_results)
mem_imm_pre :: #force_inline proc "contextless" (base: Register, disp: i32) -> Memory {
return Memory{base = base, disp = disp, sign = 1, mode = .PRE_INDEX}
}
@(require_results)
mem_imm_post :: #force_inline proc "contextless" (base: Register, disp: i32) -> Memory {
return Memory{base = base, disp = disp, sign = 1, mode = .POST_INDEX}
}
@(require_results)
mem_reg :: #force_inline proc "contextless" (base, index: Register, sign: i8 = 1) -> Memory {
return Memory{base = base, index = index, sign = sign, mode = .OFFSET}
}
@(require_results)
mem_reg_shift :: #force_inline proc "contextless" (
base, index: Register, st: Shift_Type, amt: u8, sign: i8 = 1,
) -> Memory {
return Memory{base = base, index = index, shift_type = st, shift_amt = amt, sign = sign, mode = .OFFSET}
}
// ---- Operand structure -----------------------------------------------------
Operand :: struct #packed {
using _: struct #raw_union {
reg: Register,
mem: Memory,
immediate: i64,
relative: i64, // label id (pre) or signed byte offset (post)
},
kind: Operand_Kind,
size: u8,
shift_type: Shift_Type, // for GPR_SHIFTED operands (otherwise NONE)
shift_amt: u8, // immediate shift amount 0..31 (or Rs index for RSR)
lane: u8, // SIMD lane index for DPR_ELEM / QPR_ELEM
cond: u8, // condition code 0..15 (default = AL = 14)
}
#assert(size_of(Operand) == 22)
// 16-byte raw_union (Memory is largest) + 6 bytes of trailing fields = 22 bytes
// (packed; no alignment padding).
// ---- Operand builders ------------------------------------------------------
@(require_results)
op_reg :: #force_inline proc "contextless" (r: Register) -> Operand {
return Operand{reg = r, kind = .REGISTER, size = 4, cond = 14}
}
@(require_results)
op_reg_shifted :: #force_inline proc "contextless" (
r: Register, st: Shift_Type, amt: u8,
) -> Operand {
return Operand{reg = r, kind = .REGISTER, size = 4, shift_type = st, shift_amt = amt, cond = 14}
}
@(require_results)
op_imm :: #force_inline proc "contextless" (v: i64, size: u8 = 4) -> Operand {
return Operand{immediate = v, kind = .IMMEDIATE, size = size, cond = 14}
}
@(require_results)
op_mem :: #force_inline proc "contextless" (m: Memory) -> Operand {
return Operand{mem = m, kind = .MEMORY, size = 4, cond = 14}
}
@(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, cond = 14}
}
@(require_results)
op_rel_offset :: #force_inline proc "contextless" (off: i64) -> Operand {
return Operand{relative = off, kind = .RELATIVE, size = 4, cond = 14}
}
@(require_results)
op_reg_list :: #force_inline proc "contextless" (mask: u16) -> Operand {
return Operand{immediate = i64(mask), kind = .REG_LIST, size = 2, cond = 14}
}
@(require_results)
op_dpr_lane :: #force_inline proc "contextless" (d: Register, idx: u8) -> Operand {
return Operand{reg = d, kind = .REGISTER, size = 4, lane = idx, cond = 14}
}
@(require_results)
op_qpr_lane :: #force_inline proc "contextless" (q: Register, idx: u8) -> Operand {
return Operand{reg = q, kind = .REGISTER, size = 4, lane = idx, cond = 14}
}