mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-04 13:18:31 +00:00
Internal label ids are allocation-order handles: the encoder's creation
order, or the decoder's branch-DISCOVERY order (a loop's latch names the
header before an earlier forward target). Printing labels by raw id leaked
that accident into listings — label numbers appeared out of order down the
page — and the printers' `label_names: ^map[u32]string` keyed the caller's
names by those synthesized ids, which a decode consumer cannot know without
re-deriving them (the practical result: naming "label 0" could caption a
random interior branch target).
Naming is now derived at the presentation seam, shared by every ISA
(`isa.Label_Display` in isa/print.odin):
- display numbers are assigned in ASCENDING ADDRESS order, so a listing
reads L0, L1, L2 … top to bottom regardless of id allocation;
- caller names are keyed by BYTE OFFSET (`isa.Label_Names`, with a
`distinct` Label_Offset key so an id-keyed map from the old contract
fails to compile instead of silently mis-naming);
- a named offset is guaranteed a label row even when no Label_Definition
points at it — `names[0] = "factorial"` heads a function's listing.
All ten ISA printers (x86, mips, rsp, arm32, arm64, riscv, ppc, ppc_vle,
mos6502, mos65816) drop their per-printer offset_to_label maps and
write_label helpers for the shared display; each arch re-exports
Label_Offset/Label_Names beside Label_Definition. En route this fixes an
arm32/ppc/ppc_vle bug where passing ANY names map suppressed the default
L<n> label rows for unnamed labels. Decode-side id assignment is untouched:
the reloc round-trip contract (encoder ids surviving decode) and the
sparse-id padding it relies on stay exactly as they were.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Riok9vMpkLmo78wsVKJHhz
400 lines
13 KiB
Odin
400 lines
13 KiB
Odin
// rexcode · Brendan Punsky (dotbmp@github), original author
|
|
|
|
package rexcode_ppc
|
|
|
|
import "core:strings"
|
|
import "core:fmt"
|
|
import "core:io"
|
|
import "core:os"
|
|
import "core:reflect"
|
|
import "core:rexcode/isa"
|
|
|
|
// =============================================================================
|
|
// PowerPC PRINTER
|
|
// =============================================================================
|
|
//
|
|
// Canonical assembler syntax (as accepted by binutils/LLVM):
|
|
//
|
|
// add rD, rA, rB arithmetic
|
|
// addi rD, rA, simm D-form immediate
|
|
// lwz rD, D(rA) D-form load (mem operand printed as "off(base)")
|
|
// lwzx rD, rA, rB X-form indexed
|
|
// stwx rS, rA, rB X-form store
|
|
// beq crN, label cond branch with CR field
|
|
// bl label unconditional+link
|
|
// addi. rD, rA, simm Rc=1 variant prints with trailing "."
|
|
// addo rD, rA, rB OE=1 variant prints with trailing "o"
|
|
//
|
|
// Mnemonic emitted lowercase by default; flags map to "." (Rc) and "o" (OE)
|
|
// suffixes consistent with the Mnemonic enum naming (e.g. ADD_DOT -> "add.").
|
|
|
|
Token :: isa.Token
|
|
Token_Kind :: isa.Token_Kind
|
|
Print_Options :: isa.Print_Options
|
|
Print_Result :: isa.Print_Result
|
|
DEFAULT_PRINT_OPTIONS :: isa.DEFAULT_PRINT_OPTIONS
|
|
|
|
mnemonic_to_string :: proc(m: Mnemonic, lowercase: bool = true, allocator := context.temp_allocator) -> string {
|
|
sb := strings.builder_make(allocator)
|
|
write_mnemonic(&sb, m, !lowercase)
|
|
return strings.to_string(sb)
|
|
}
|
|
|
|
register_name :: proc(r: Register, lowercase: bool = true, allocator := context.temp_allocator) -> string {
|
|
sb := strings.builder_make(allocator)
|
|
write_register(&sb, r, !lowercase)
|
|
return strings.to_string(sb)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Core sbprint
|
|
// =============================================================================
|
|
|
|
sbprint :: proc(
|
|
sb: ^strings.Builder,
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) {
|
|
opts := options
|
|
if opts == nil {
|
|
@(static) defaults := DEFAULT_PRINT_OPTIONS
|
|
opts = &defaults
|
|
}
|
|
|
|
// Display-side label naming: numbers in ADDRESS order (independent of the internal ids'
|
|
// allocation order), caller names keyed by byte offset (isa.Label_Display).
|
|
display: isa.Label_Display
|
|
isa.label_display_init(&display, label_defs, label_names)
|
|
defer isa.label_display_destroy(&display)
|
|
|
|
for i in 0..<len(instructions) {
|
|
inst := &instructions[i]
|
|
offset := u32(i) * 4
|
|
if i < len(inst_info) {
|
|
offset = inst_info[i].offset
|
|
}
|
|
|
|
// A displayable label at this offset — a definition, or a caller-named offset
|
|
if isa.label_display_at(&display, offset) {
|
|
isa.label_display_write(&display, sb, offset, opts.label_prefix)
|
|
strings.write_string(sb, ":\n")
|
|
}
|
|
|
|
if opts.show_offsets {
|
|
fmt.sbprintf(sb, "%08x: ", offset)
|
|
}
|
|
|
|
if inst.mnemonic == .INVALID {
|
|
strings.write_string(sb, " .long 0x????????\n")
|
|
continue
|
|
}
|
|
|
|
strings.write_string(sb, " ")
|
|
write_mnemonic(sb, inst.mnemonic, opts.uppercase)
|
|
|
|
if inst.operand_count > 0 {
|
|
strings.write_string(sb, " ")
|
|
for k in 0..<inst.operand_count {
|
|
if k > 0 { strings.write_string(sb, ", ") }
|
|
write_operand(sb, &inst.ops[k], offset, &display, opts)
|
|
}
|
|
}
|
|
strings.write_string(sb, "\n")
|
|
}
|
|
}
|
|
|
|
sbprintln :: proc(
|
|
sb: ^strings.Builder,
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) {
|
|
sbprint(sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
strings.write_byte(sb, '\n')
|
|
}
|
|
|
|
// =============================================================================
|
|
// Sink wrappers
|
|
// =============================================================================
|
|
|
|
print :: proc(
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
os.write_string(os.stdout, strings.to_string(sb))
|
|
}
|
|
|
|
println :: proc(
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
os.write_string(os.stdout, strings.to_string(sb))
|
|
}
|
|
|
|
aprint :: proc(
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
allocator := context.allocator,
|
|
) -> string {
|
|
sb := strings.builder_make(allocator)
|
|
sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
return strings.to_string(sb)
|
|
}
|
|
|
|
aprintln :: proc(
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
allocator := context.allocator,
|
|
) -> string {
|
|
sb := strings.builder_make(allocator)
|
|
sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
return strings.to_string(sb)
|
|
}
|
|
|
|
tprint :: proc(
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) -> string {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
return strings.to_string(sb)
|
|
}
|
|
|
|
tprintln :: proc(
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) -> string {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
return strings.to_string(sb)
|
|
}
|
|
|
|
bprint :: proc(
|
|
buf: []u8,
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) -> string {
|
|
sb := strings.builder_from_bytes(buf)
|
|
sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
return strings.to_string(sb)
|
|
}
|
|
|
|
bprintln :: proc(
|
|
buf: []u8,
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) -> string {
|
|
sb := strings.builder_from_bytes(buf)
|
|
sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
return strings.to_string(sb)
|
|
}
|
|
|
|
fprint :: proc(
|
|
fd: ^os.File,
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
os.write_string(fd, strings.to_string(sb))
|
|
}
|
|
|
|
fprintln :: proc(
|
|
fd: ^os.File,
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
os.write_string(fd, strings.to_string(sb))
|
|
}
|
|
|
|
wprint :: proc(
|
|
w: io.Writer,
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
io.write_string(w, strings.to_string(sb))
|
|
}
|
|
|
|
wprintln :: proc(
|
|
w: io.Writer,
|
|
instructions: []Instruction,
|
|
inst_info: []Instruction_Info,
|
|
label_defs: []Label_Definition,
|
|
tokens: ^[dynamic]Token = nil,
|
|
options: ^Print_Options = nil,
|
|
label_names: ^isa.Label_Names = nil,
|
|
) {
|
|
sb := strings.builder_make(context.temp_allocator)
|
|
sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names)
|
|
io.write_string(w, strings.to_string(sb))
|
|
}
|
|
|
|
// =============================================================================
|
|
// Mnemonic / register writers
|
|
// =============================================================================
|
|
|
|
write_mnemonic :: proc(sb: ^strings.Builder, m: Mnemonic, uppercase: bool) {
|
|
name := reflect.enum_string(m)
|
|
// The Mnemonic enum uses _DOT / _O / _O_DOT suffixes for Rc / OE / both;
|
|
// translate to canonical "." / "o" / "o." suffixes.
|
|
suffix := ""
|
|
if strings.has_suffix(name, "_O_DOT") {
|
|
name = name[:len(name)-6]
|
|
suffix = "o."
|
|
} else if strings.has_suffix(name, "_DOT") {
|
|
name = name[:len(name)-4]
|
|
suffix = "."
|
|
} else if strings.has_suffix(name, "_O") {
|
|
name = name[:len(name)-2]
|
|
suffix = "o"
|
|
}
|
|
if uppercase {
|
|
strings.write_string(sb, strings.to_upper(name, context.temp_allocator))
|
|
strings.write_string(sb, strings.to_upper(suffix, context.temp_allocator))
|
|
} else {
|
|
strings.write_string(sb, strings.to_lower(name, context.temp_allocator))
|
|
strings.write_string(sb, suffix)
|
|
}
|
|
}
|
|
|
|
write_register :: proc(sb: ^strings.Builder, r: Register, uppercase: bool) {
|
|
hw := reg_hw(r)
|
|
switch reg_class(r) {
|
|
case REG_GPR: fmt.sbprintf(sb, uppercase ? "R%d" : "r%d", hw)
|
|
case REG_FPR: fmt.sbprintf(sb, uppercase ? "F%d" : "f%d", hw)
|
|
case REG_VR: fmt.sbprintf(sb, uppercase ? "V%d" : "v%d", hw)
|
|
case REG_VR128: fmt.sbprintf(sb, uppercase ? "VR%d" : "vr%d", u16(r) & 0x7F)
|
|
case REG_VSR: fmt.sbprintf(sb, uppercase ? "VS%d" : "vs%d", u16(r) & 0x3F)
|
|
case REG_CR: fmt.sbprintf(sb, uppercase ? "CR%d" : "cr%d", hw & 0x7)
|
|
case REG_SPR:
|
|
switch u16(r) & 0x3FF {
|
|
case 1: strings.write_string(sb, uppercase ? "XER" : "xer")
|
|
case 8: strings.write_string(sb, uppercase ? "LR" : "lr")
|
|
case 9: strings.write_string(sb, uppercase ? "CTR" : "ctr")
|
|
case 17: strings.write_string(sb, uppercase ? "DSCR" : "dscr")
|
|
case 18: strings.write_string(sb, uppercase ? "DSISR" : "dsisr")
|
|
case 19: strings.write_string(sb, uppercase ? "DAR" : "dar")
|
|
case 22: strings.write_string(sb, uppercase ? "DEC" : "dec")
|
|
case 26: strings.write_string(sb, uppercase ? "SRR0" : "srr0")
|
|
case 27: strings.write_string(sb, uppercase ? "SRR1" : "srr1")
|
|
case 256: strings.write_string(sb, uppercase ? "VRSAVE" : "vrsave")
|
|
case 268: strings.write_string(sb, uppercase ? "TBL" : "tbl")
|
|
case 269: strings.write_string(sb, uppercase ? "TBU" : "tbu")
|
|
case: fmt.sbprintf(sb, "%d", u16(r) & 0x3FF)
|
|
}
|
|
case:
|
|
fmt.sbprintf(sb, "?%04x", u16(r))
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Operand writer
|
|
// =============================================================================
|
|
|
|
write_operand :: proc(
|
|
sb: ^strings.Builder,
|
|
op: ^Operand,
|
|
inst_offset: u32,
|
|
display: ^isa.Label_Display,
|
|
opts: ^Print_Options,
|
|
) {
|
|
#partial switch op.kind {
|
|
case .NONE:
|
|
// skip
|
|
|
|
case .REGISTER:
|
|
write_register(sb, op.reg, opts.uppercase)
|
|
|
|
case .IMMEDIATE:
|
|
fmt.sbprintf(sb, "%d", op.immediate)
|
|
|
|
case .MEMORY:
|
|
if op.mem.index == NONE {
|
|
// D-form: "disp(rA)"
|
|
fmt.sbprintf(sb, "%d(", op.mem.disp)
|
|
write_register(sb, op.mem.base, opts.uppercase)
|
|
strings.write_string(sb, ")")
|
|
} else {
|
|
// X-form indexed: "rA, rB"
|
|
write_register(sb, op.mem.base, opts.uppercase)
|
|
strings.write_string(sb, ", ")
|
|
write_register(sb, op.mem.index, opts.uppercase)
|
|
}
|
|
|
|
case .RELATIVE:
|
|
// op.relative holds a signed byte offset relative to this instruction
|
|
// (post-decode) or an absolute label target if a label_defs roundtrip
|
|
// has resolved it. Prefer label form when we can match the target.
|
|
target := u32(i32(inst_offset) + i32(op.relative))
|
|
if isa.label_display_at(display, target) {
|
|
isa.label_display_write(display, sb, target, opts.label_prefix)
|
|
} else {
|
|
// Print as PC-relative offset
|
|
if op.relative >= 0 {
|
|
fmt.sbprintf(sb, ".+%d", op.relative)
|
|
} else {
|
|
fmt.sbprintf(sb, ".-%d", -op.relative)
|
|
}
|
|
}
|
|
}
|
|
}
|