Files
Odin/core/rexcode/isa/mos6502/operands.odin
Brendan Punsky 95df04fbe1 rexcode: re-house ISA packages under core:rexcode/isa/<arch>
Move all ten ISA packages (x86, arm32, arm64, mips, riscv, ppc, ppc_vle,
rsp, mos6502, mos65816) from core/rexcode/<arch> to core/rexcode/isa/<arch>,
so the import pattern is now `import "core:rexcode/isa/x86"`. The shared
core stays at core:rexcode/isa.

Mechanical: relative `import "../isa"` / "../../isa" -> absolute
"core:rexcode/isa" (the only path that survives the move; the "../" and
"../.." self/generated imports move with their packages). build.lua now
builds paths as <root>/isa/<name>; stale `cd <arch>` hints in the verify
tools and the doc.odin paths updated.

WASM stays at core/rexcode/wasm for now -- it is an IR, not an ISA, and
will move under the forthcoming core:rexcode/ir once that layer lands.

All 10 arches gen/builders/check/test green; import core:rexcode/isa/x86
verified working; wasm still compiles.
2026-06-18 19:03:27 -04:00

140 lines
6.9 KiB
Odin

// rexcode · Brendan Punsky (dotbmp@github), original author
package rexcode_mos6502
// =============================================================================
// MOS 6502 OPERANDS
// =============================================================================
//
// The 6502 has more *addressing modes* than it has registers. The
// addressing mode is encoded into the operand as part of the Memory
// type so the matcher can dispatch to the correct opcode form for a
// given mnemonic. Layout:
//
// LDA #$12 IMMEDIATE imm = 0x12
// LDA $12 MEMORY {mode = ZP, address = 0x0012}
// LDA $12,X MEMORY {mode = ZP_X, address = 0x0012}
// LDA $1234 MEMORY {mode = ABS, address = 0x1234}
// LDA $1234,X MEMORY {mode = ABS_X, address = 0x1234}
// LDA $1234,Y MEMORY {mode = ABS_Y, address = 0x1234}
// LDA ($12,X) MEMORY {mode = IND_X, address = 0x0012}
// LDA ($12),Y MEMORY {mode = IND_Y, address = 0x0012}
// LDA ($12) MEMORY {mode = IND_ZP, address = 0x0012} -- 65C02
// JMP ($1234) MEMORY {mode = IND, address = 0x1234}
// JMP ($1234,X) MEMORY {mode = IND_ABS_X, address = 0x1234} -- 65C02
// BEQ label RELATIVE relative = label_id
// ROL A REGISTER reg = A
Operand_Kind :: enum u8 {
NONE,
REGISTER, // mostly used for `A` in `ROL A` (implicit; rarely needed)
IMMEDIATE,
MEMORY,
RELATIVE, // PC-relative target (label or raw byte offset)
}
Address_Mode :: enum u8 {
ZP, // $nn
ZP_X, // $nn,X
ZP_Y, // $nn,Y
ABS, // $nnnn
ABS_X, // $nnnn,X
ABS_Y, // $nnnn,Y
IND, // ($nnnn) -- JMP only
IND_X, // ($nn,X)
IND_Y, // ($nn),Y
IND_ZP, // ($nn) -- 65C02
IND_ABS_X, // ($nnnn,X) -- 65C02 JMP only
}
Memory :: struct #packed {
address: u16,
mode: Address_Mode,
_: u8,
}
#assert(size_of(Memory) == 4)
// -----------------------------------------------------------------------------
// Memory constructors (one per addressing mode)
// -----------------------------------------------------------------------------
@(require_results) mem_zp :: #force_inline proc "contextless" (addr: u8) -> Memory { return Memory{address = u16(addr), mode = .ZP } }
@(require_results) mem_zp_x :: #force_inline proc "contextless" (addr: u8) -> Memory { return Memory{address = u16(addr), mode = .ZP_X } }
@(require_results) mem_zp_y :: #force_inline proc "contextless" (addr: u8) -> Memory { return Memory{address = u16(addr), mode = .ZP_Y } }
@(require_results) mem_abs :: #force_inline proc "contextless" (addr: u16) -> Memory { return Memory{address = addr, mode = .ABS } }
@(require_results) mem_abs_x :: #force_inline proc "contextless" (addr: u16) -> Memory { return Memory{address = addr, mode = .ABS_X } }
@(require_results) mem_abs_y :: #force_inline proc "contextless" (addr: u16) -> Memory { return Memory{address = addr, mode = .ABS_Y } }
@(require_results) mem_ind :: #force_inline proc "contextless" (addr: u16) -> Memory { return Memory{address = addr, mode = .IND } }
@(require_results) mem_ind_x :: #force_inline proc "contextless" (addr: u8) -> Memory { return Memory{address = u16(addr), mode = .IND_X } }
@(require_results) mem_ind_y :: #force_inline proc "contextless" (addr: u8) -> Memory { return Memory{address = u16(addr), mode = .IND_Y } }
@(require_results) mem_ind_zp :: #force_inline proc "contextless" (addr: u8) -> Memory { return Memory{address = u16(addr), mode = .IND_ZP } }
@(require_results) mem_ind_abs_x :: #force_inline proc "contextless" (addr: u16) -> Memory { return Memory{address = addr, mode = .IND_ABS_X } }
// -----------------------------------------------------------------------------
// Operand: kind-tagged union, 16 bytes
// -----------------------------------------------------------------------------
Operand :: struct #packed {
using _: struct #raw_union {
reg: Register, // 2 bytes
mem: Memory, // 4 bytes
immediate: i64, // 8 bytes
relative: i64, // 8 bytes (label id pre-resolution; byte offset post)
},
kind: Operand_Kind, // 1
size: u8, // 1
}
#assert(size_of(Operand) == 10)
// Generic constructors -------------------------------------------------------
@(require_results)
op_reg :: #force_inline proc "contextless" (r: Register) -> Operand {
return Operand{reg = r, kind = .REGISTER, size = 1}
}
@(require_results)
op_imm8 :: #force_inline proc "contextless" (v: i64) -> Operand {
return Operand{immediate = v, kind = .IMMEDIATE, size = 1}
}
@(require_results)
op_imm16 :: #force_inline proc "contextless" (v: i64) -> Operand {
return Operand{immediate = v, kind = .IMMEDIATE, size = 2}
}
@(require_results)
op_mem :: #force_inline proc "contextless" (m: Memory) -> Operand {
size: u8 = 2
switch m.mode {
case .ZP, .ZP_X, .ZP_Y, .IND_X, .IND_Y, .IND_ZP:
size = 1
case .ABS, .ABS_X, .ABS_Y, .IND, .IND_ABS_X:
size = 2
}
return Operand{mem = m, kind = .MEMORY, size = size}
}
@(require_results)
op_label :: #force_inline proc "contextless" (label_id: u32, size: u8 = 1) -> Operand {
return Operand{relative = i64(label_id), kind = .RELATIVE, size = size}
}
@(require_results)
op_rel_offset :: #force_inline proc "contextless" (offset: i64) -> Operand {
return Operand{relative = offset, kind = .RELATIVE, size = 1}
}
// Address-mode-named operand helpers (call site reads like asm).
@(require_results) op_zp :: #force_inline proc "contextless" (a: u8) -> Operand { return op_mem(mem_zp(a)) }
@(require_results) op_zp_x :: #force_inline proc "contextless" (a: u8) -> Operand { return op_mem(mem_zp_x(a)) }
@(require_results) op_zp_y :: #force_inline proc "contextless" (a: u8) -> Operand { return op_mem(mem_zp_y(a)) }
@(require_results) op_abs :: #force_inline proc "contextless" (a: u16) -> Operand { return op_mem(mem_abs(a)) }
@(require_results) op_abs_x :: #force_inline proc "contextless" (a: u16) -> Operand { return op_mem(mem_abs_x(a)) }
@(require_results) op_abs_y :: #force_inline proc "contextless" (a: u16) -> Operand { return op_mem(mem_abs_y(a)) }
@(require_results) op_ind :: #force_inline proc "contextless" (a: u16) -> Operand { return op_mem(mem_ind(a)) }
@(require_results) op_ind_x :: #force_inline proc "contextless" (a: u8) -> Operand { return op_mem(mem_ind_x(a)) }
@(require_results) op_ind_y :: #force_inline proc "contextless" (a: u8) -> Operand { return op_mem(mem_ind_y(a)) }
@(require_results) op_ind_zp :: #force_inline proc "contextless" (a: u8) -> Operand { return op_mem(mem_ind_zp(a)) }
@(require_results) op_ind_abs_x :: #force_inline proc "contextless" (a: u16) -> Operand { return op_mem(mem_ind_abs_x(a)) }