mirror of
https://github.com/odin-lang/Odin.git
synced 2026-09-18 09:44:43 +00:00
A ZA tile printed as the bare number it is encoded as -- `addha #0, p0/m, p0/m, z0.s` -- and a tile slice printed as one too, where the syntax is `za0h.b[w12, 0]`: a tile, taken along its rows or columns, addressed by one of W12..W15 plus an offset. Neither was anything an assembler would take. Tiles are a register class now (ZA0..ZA15, viewed at an element size), so they print through the same path as every other register. A slice is its own operand kind holding the four things it is made of, rather than one packed immediate that only the encoder understood. Getting that right needed the field layout, and the layout is not what the encoding table implied: the tile number and the offset share the low nibble, and how it splits follows the element size -- a byte tile has no tile bits at all and four of offset, while a quadword tile is all tile and none. Reading a fixed four bits as the tile made every byte slice come back as tile 4. LD1Q/ST1Q scale their index by 16, which the mnemonic's last letter does not spell the way B/H/W/D do; they were left unscaled when the other 38 forms were fixed. That also wanted a .q element shape, which nothing had needed before. SVE/SME2 decode entries against llvm-mc: 559 byte-exact and 0 mismatched, from 211 and 78 at the start of the session. What is left is mostly one structural limit: SME's outer products take five operands (`umopa za0.s, p0/m, p1/m, z0.b, z1.b`) and Instruction holds four. Widening it would fit -- five Operands is 55 bytes of the 64 -- but it reaches through Encoding, the table, both codecs and the builders, so it wants its own change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UmHLRF11EoWwNWCJ7JGaA
695 lines
22 KiB
Odin
695 lines
22 KiB
Odin
// rexcode · Brendan Punsky (dotbmp@github), original author
|
|
|
|
package rexcode_arm64
|
|
|
|
import "core:strings"
|
|
import "core:reflect"
|
|
import "core:os"
|
|
import "core:io"
|
|
import "core:rexcode/isa"
|
|
|
|
// =============================================================================
|
|
// AArch64 PRINTER
|
|
// =============================================================================
|
|
//
|
|
// Canonical Arm assembly syntax:
|
|
//
|
|
// add x0, x1, x2 (R-type)
|
|
// add x0, x1, #16 (imm)
|
|
// add x0, x1, x2, lsl #3 (shifted register)
|
|
// add x0, x1, w2, sxtw #2 (extended register)
|
|
// ldr x0, [x1, #8] (offset)
|
|
// ldr x0, [x1, #-8]! (pre-index)
|
|
// ldr x0, [x1], #8 (post-index)
|
|
// ldr x0, [x1, x2, lsl #3] (register offset)
|
|
// ldr x0, [x1, w2, sxtw #2] (extended-register offset)
|
|
// b .L0 (relative)
|
|
// b.eq .L0 (B.cond with condition suffix)
|
|
// cbz x0, .L0
|
|
// tbz x0, #5, .L0
|
|
// fadd d0, d1, d2 (FP scalar)
|
|
// fmov w0, s0 (cross-class FMOV)
|
|
//
|
|
// FP mnemonics: the enum names already include the dot via the underscore-
|
|
// to-dot rule (FADD_S -> fadd.s). For the canonical assembly form we want
|
|
// no dot inside .S/.D (it's just `fadd s0, s0, s0`) -- the operand types
|
|
// disambiguate. So the printer special-cases the FP mnemonics.
|
|
|
|
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
|
|
|
|
@(rodata, private="file")
|
|
COND_NAMES := [16]string{
|
|
"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
|
|
"hi", "ls", "ge", "lt", "gt", "le", "al", "nv",
|
|
}
|
|
|
|
@(rodata, private="file")
|
|
SHIFT_NAMES := [4]string{ "lsl", "lsr", "asr", "ror" }
|
|
|
|
@(rodata, private="file")
|
|
EXTEND_NAMES := [8]string{
|
|
"uxtb", "uxth", "uxtw", "uxtx",
|
|
"sxtb", "sxth", "sxtw", "sxtx",
|
|
}
|
|
|
|
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_byte(sb, ':')
|
|
strings.write_string(sb, opts.separator)
|
|
}
|
|
|
|
strings.write_string(sb, opts.indent)
|
|
if opts.show_offsets {
|
|
isa.print_hex(sb, u64(offset), opts)
|
|
strings.write_string(sb, ": ")
|
|
}
|
|
|
|
write_full_mnemonic(sb, inst, opts.uppercase)
|
|
|
|
|
|
// MOVZ/MOVN/MOVK store the shift as an hw index (0..3 = LSL #0/16/32/48),
|
|
// which assemblers write as `lsl #16` and omit entirely when it is zero.
|
|
mov_wide := inst.mnemonic == .MOVZ || inst.mnemonic == .MOVN || inst.mnemonic == .MOVK
|
|
end_slot := int(inst.operand_count)
|
|
if mov_wide && end_slot == 3 && inst.ops[2].kind == .IMMEDIATE && inst.ops[2].immediate == 0 {
|
|
end_slot = 2
|
|
}
|
|
|
|
if end_slot > 0 {
|
|
strings.write_byte(sb, ' ')
|
|
for slot in 0..<end_slot {
|
|
op := &inst.ops[slot]
|
|
// A lane index belongs to the register before it, so it is
|
|
// written `[3]` with no separator rather than as an operand.
|
|
lane_index := op.kind == .IMMEDIATE && op.size == LANE_INDEX
|
|
if slot > 0 && !lane_index {
|
|
strings.write_byte(sb, ',')
|
|
if opts.space_after_comma { strings.write_byte(sb, ' ') }
|
|
}
|
|
// A register the syntax writes as a list keeps its braces, and
|
|
// names every register in the run.
|
|
list := op.kind == .REGISTER && op.list_count > 0
|
|
switch {
|
|
case lane_index:
|
|
strings.write_byte(sb, '[')
|
|
write_decimal_u32(sb, u32(op.immediate))
|
|
strings.write_byte(sb, ']')
|
|
case mov_wide && slot == 2:
|
|
strings.write_string(sb, opts.uppercase ? "LSL #" : "lsl #")
|
|
write_decimal_u32(sb, u32(op.immediate) * 16)
|
|
case list:
|
|
strings.write_byte(sb, '{')
|
|
if opts.space_after_comma { strings.write_byte(sb, ' ') }
|
|
for n in 0 ..< op.list_count {
|
|
if n > 0 {
|
|
strings.write_byte(sb, ',')
|
|
if opts.space_after_comma { strings.write_byte(sb, ' ') }
|
|
}
|
|
// The run is consecutive and wraps at v31.
|
|
member := op^
|
|
member.reg = Register(reg_class(op.reg) | u16((reg_hw(op.reg) + n) & 0x1F))
|
|
write_operand(sb, &member, &display, opts)
|
|
}
|
|
if opts.space_after_comma { strings.write_byte(sb, ' ') }
|
|
strings.write_byte(sb, '}')
|
|
case:
|
|
write_operand(sb, op, &display, opts)
|
|
}
|
|
}
|
|
// CMLE/CMLT/FCMLE/FCMLT only ever compare against zero, and the
|
|
// zero is part of the syntax rather than an encoded operand -- an
|
|
// assembler will not take the instruction without it.
|
|
if inst.mnemonic == .CMLE || inst.mnemonic == .CMLT {
|
|
strings.write_string(sb, opts.space_after_comma ? ", #0" : ",#0")
|
|
} else if inst.mnemonic == .FCMLE || inst.mnemonic == .FCMLT {
|
|
strings.write_string(sb, opts.space_after_comma ? ", #0.0" : ",#0.0")
|
|
}
|
|
}
|
|
strings.write_string(sb, opts.separator)
|
|
}
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
// =============================================================================
|
|
// Internal writers
|
|
// =============================================================================
|
|
|
|
// Every mnemonic now prints straight from its name -- the conditional
|
|
// branches carry their condition in the name (B_LE -> `b.le`), so there is
|
|
// no operand to fold in.
|
|
@(private="file")
|
|
write_full_mnemonic :: proc(sb: ^strings.Builder, inst: ^Instruction, uppercase: bool) {
|
|
write_mnemonic(sb, inst.mnemonic, uppercase)
|
|
}
|
|
|
|
@(private="file")
|
|
write_mnemonic :: proc(sb: ^strings.Builder, m: Mnemonic, uppercase: bool) {
|
|
name, ok := reflect.enum_name_from_value(m)
|
|
if !ok { strings.write_string(sb, "<?>"); return }
|
|
|
|
// Enum names are the assembler mnemonics, so this is a straight
|
|
// transliteration -- with one exception. The system instructions below
|
|
// are written by assemblers as a mnemonic plus an op-name token
|
|
// (`dc zva`, `tlbi vae1`, `bti j`), which we store as one enum member,
|
|
// so for those the first underscore prints as a space. Every other
|
|
// underscore is kept: AMX_LDX is an undocumented Apple coprocessor op
|
|
// with no assembler spelling at all, and printing it `amx ldx` would
|
|
// imply a two-token syntax that does not exist.
|
|
split, sep := -1, byte(' ')
|
|
for prefix in ([]string{"DC_", "IC_", "AT_", "TLBI_", "BTI_", "PSB_", "TSB_"}) {
|
|
if len(name) > len(prefix) && name[:len(prefix)] == prefix {
|
|
split = len(prefix) - 1
|
|
break
|
|
}
|
|
}
|
|
// Conditional branches spell the separator as a dot: B_LE -> `b.le`.
|
|
// BC_ is checked first, since it also starts with B.
|
|
if split < 0 {
|
|
for prefix in ([]string{"BC_", "B_"}) {
|
|
if len(name) > len(prefix) && name[:len(prefix)] == prefix {
|
|
split, sep = len(prefix) - 1, '.'
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
for i in 0..<len(name) {
|
|
c := name[i]
|
|
if i == split {
|
|
strings.write_byte(sb, sep)
|
|
} else if !uppercase && c >= 'A' && c <= 'Z' {
|
|
strings.write_byte(sb, c + 32)
|
|
} else {
|
|
strings.write_byte(sb, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
// NEON arrangement (`.4s`), element view (`.d`) or SVE element width (`.s`)
|
|
// suffix. Vector operands carry the shape in op.size using the codes
|
|
// op_v_*/op_z_* produce and the decoder restores (see operands.odin);
|
|
// arrangements are multiples of 8, element views are odd, and the neutral 4
|
|
// that every scalar class uses prints nothing.
|
|
//
|
|
// A lane index arrives as its own immediate operand carrying the LANE_INDEX
|
|
// marker; the operand loop glues it to the register it indexes (`v0.s[2]`)
|
|
// instead of writing it as a separate `#2`.
|
|
// A system register by name (`cntvct_el0`), falling back to the raw field
|
|
// when it is not one we know.
|
|
@(private="file")
|
|
write_sysreg :: proc(sb: ^strings.Builder, sr: System_Register, uppercase: bool) {
|
|
name, ok := sysreg_name(sr)
|
|
if !ok {
|
|
strings.write_byte(sb, '#')
|
|
write_signed_decimal(sb, i64(sr))
|
|
return
|
|
}
|
|
for i in 0 ..< len(name) {
|
|
c := name[i]
|
|
if uppercase && c >= 'a' && c <= 'z' {
|
|
strings.write_byte(sb, c - 'a' + 'A')
|
|
} else {
|
|
strings.write_byte(sb, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
@(private="file")
|
|
write_vector_shape :: proc(sb: ^strings.Builder, r: Register, size: u8, uppercase: bool) {
|
|
shape := ""
|
|
sep := byte('.')
|
|
switch reg_class(r) {
|
|
case REG_V:
|
|
switch size {
|
|
case 8: shape = "8b"
|
|
case 16: shape = "16b"
|
|
case 24: shape = "4h"
|
|
case 32: shape = "8h"
|
|
case 40: shape = "2s"
|
|
case 48: shape = "4s"
|
|
case 56: shape = "1d"
|
|
case 64: shape = "2d"
|
|
case 72: shape = "1q"
|
|
case 1: shape = "b"
|
|
case 3: shape = "h"
|
|
case 5: shape = "s"
|
|
case 7: shape = "d"
|
|
}
|
|
case REG_Z, REG_ZA:
|
|
switch size {
|
|
case 1: shape = "b"
|
|
case 2: shape = "h"
|
|
case 4: shape = "s"
|
|
case 8: shape = "d"
|
|
case 16: shape = "q"
|
|
}
|
|
// A predicate's suffix is its governing qualifier, and it hangs off a
|
|
// slash rather than a dot.
|
|
case REG_P, REG_PN:
|
|
switch size {
|
|
case PQUAL_ZERO: sep = '/'; shape = "z"
|
|
case PQUAL_MERGE: sep = '/'; shape = "m"
|
|
case PSHAPE_B: shape = "b"
|
|
case PSHAPE_H: shape = "h"
|
|
case PSHAPE_S: shape = "s"
|
|
case PSHAPE_D: shape = "d"
|
|
}
|
|
}
|
|
if shape == "" {
|
|
return
|
|
}
|
|
strings.write_byte(sb, sep)
|
|
for i in 0..<len(shape) {
|
|
c := shape[i]
|
|
if uppercase && c >= 'a' && c <= 'z' {
|
|
strings.write_byte(sb, c - 32)
|
|
} else {
|
|
strings.write_byte(sb, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
@(private="file")
|
|
write_register :: proc(sb: ^strings.Builder, r: Register, uppercase: bool) {
|
|
if r == NONE { strings.write_string(sb, "<none>"); return }
|
|
cls := reg_class(r)
|
|
hw := reg_hw(r)
|
|
|
|
// SP and ZR have named forms; the rest are letter+number.
|
|
switch cls {
|
|
case REG_XSP:
|
|
strings.write_string(sb, uppercase ? "SP" : "sp")
|
|
return
|
|
case REG_WSP:
|
|
strings.write_string(sb, uppercase ? "WSP" : "wsp")
|
|
return
|
|
case REG_X:
|
|
if hw == 31 {
|
|
strings.write_string(sb, uppercase ? "XZR" : "xzr")
|
|
return
|
|
}
|
|
strings.write_byte(sb, uppercase ? 'X' : 'x')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_W:
|
|
if hw == 31 {
|
|
strings.write_string(sb, uppercase ? "WZR" : "wzr")
|
|
return
|
|
}
|
|
strings.write_byte(sb, uppercase ? 'W' : 'w')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_B:
|
|
strings.write_byte(sb, uppercase ? 'B' : 'b')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_H:
|
|
strings.write_byte(sb, uppercase ? 'H' : 'h')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_S:
|
|
strings.write_byte(sb, uppercase ? 'S' : 's')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_D:
|
|
strings.write_byte(sb, uppercase ? 'D' : 'd')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_Q:
|
|
strings.write_byte(sb, uppercase ? 'Q' : 'q')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_V:
|
|
strings.write_byte(sb, uppercase ? 'V' : 'v')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_Z:
|
|
strings.write_byte(sb, uppercase ? 'Z' : 'z')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_P:
|
|
strings.write_byte(sb, uppercase ? 'P' : 'p')
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_PN:
|
|
strings.write_string(sb, uppercase ? "PN" : "pn")
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_ZT:
|
|
strings.write_string(sb, uppercase ? "ZT" : "zt")
|
|
write_decimal_u32(sb, u32(hw))
|
|
case REG_ZA:
|
|
strings.write_string(sb, uppercase ? "ZA" : "za")
|
|
write_decimal_u32(sb, u32(hw))
|
|
}
|
|
}
|
|
|
|
@(private="file")
|
|
write_operand :: proc(
|
|
sb: ^strings.Builder,
|
|
op: ^Operand,
|
|
display: ^isa.Label_Display,
|
|
opts: ^Print_Options,
|
|
) {
|
|
switch op.kind {
|
|
case .NONE:
|
|
|
|
case .REGISTER:
|
|
write_register(sb, op.reg, opts.uppercase)
|
|
write_vector_shape(sb, op.reg, op.size, opts.uppercase)
|
|
|
|
case .IMMEDIATE:
|
|
strings.write_byte(sb, '#')
|
|
write_signed_decimal(sb, op.immediate)
|
|
|
|
case .SYSTEM_REGISTER:
|
|
write_sysreg(sb, op.sysreg, opts.uppercase)
|
|
|
|
case .ZA_SLICE:
|
|
// `za0h.b[w12, 0]`
|
|
strings.write_string(sb, opts.uppercase ? "ZA" : "za")
|
|
write_decimal_u32(sb, u32(op.za.tile))
|
|
strings.write_byte(sb, op.za.vertical ? (opts.uppercase ? 'V' : 'v') : (opts.uppercase ? 'H' : 'h'))
|
|
write_vector_shape(sb, Register(REG_Z), op.za.elem, opts.uppercase)
|
|
strings.write_byte(sb, '[')
|
|
strings.write_string(sb, opts.uppercase ? "W" : "w")
|
|
write_decimal_u32(sb, 12 + u32(op.za.ws))
|
|
strings.write_string(sb, opts.space_after_comma ? ", " : ",")
|
|
write_decimal_u32(sb, u32(op.za.offset))
|
|
strings.write_byte(sb, ']')
|
|
|
|
case .COND:
|
|
c := op.cond & 0xF
|
|
s := COND_NAMES[c]
|
|
if opts.uppercase {
|
|
for i in 0..<len(s) {
|
|
ch := s[i]
|
|
if ch >= 'a' && ch <= 'z' { strings.write_byte(sb, ch - 32) } else { strings.write_byte(sb, ch) }
|
|
}
|
|
} else {
|
|
strings.write_string(sb, s)
|
|
}
|
|
|
|
case .SHIFTED_REG:
|
|
write_register(sb, op.shifted.reg, opts.uppercase)
|
|
if op.shifted.amount != 0 || op.shifted.type != .LSL {
|
|
if opts.space_after_comma {
|
|
strings.write_string(sb, ", ")
|
|
} else {
|
|
strings.write_byte(sb, ',')
|
|
}
|
|
strings.write_string(sb, SHIFT_NAMES[u8(op.shifted.type) & 0x3])
|
|
strings.write_string(sb, " #")
|
|
write_decimal_u32(sb, u32(op.shifted.amount))
|
|
}
|
|
|
|
case .EXTENDED_REG:
|
|
write_register(sb, op.extended.reg, opts.uppercase)
|
|
if opts.space_after_comma {
|
|
strings.write_string(sb, ", ")
|
|
} else {
|
|
strings.write_byte(sb, ',')
|
|
}
|
|
strings.write_string(sb, EXTEND_NAMES[u8(op.extended.extend) & 0x7])
|
|
if op.extended.amount != 0 {
|
|
strings.write_string(sb, " #")
|
|
write_decimal_u32(sb, u32(op.extended.amount))
|
|
}
|
|
|
|
case .MEMORY:
|
|
write_memory(sb, op.mem, op.size, opts)
|
|
|
|
case .RELATIVE:
|
|
target := u32(op.relative)
|
|
if isa.label_display_at(display, target) {
|
|
isa.label_display_write(display, sb, target, opts.label_prefix)
|
|
} else {
|
|
isa.print_hex(sb, u64(target), opts)
|
|
}
|
|
}
|
|
}
|
|
|
|
@(private="file")
|
|
// `index_shape` names the element size of a vector index (SVE gather), which
|
|
// Memory has no room left to carry; it is 0 for every ordinary addressing mode.
|
|
write_memory :: proc(sb: ^strings.Builder, m: Memory, index_shape: u8, opts: ^Print_Options) {
|
|
strings.write_byte(sb, '[')
|
|
write_register(sb, m.base, opts.uppercase)
|
|
|
|
switch m.mode {
|
|
case .OFFSET:
|
|
if m.disp != 0 {
|
|
if opts.space_after_comma {
|
|
strings.write_string(sb, ", #")
|
|
} else {
|
|
strings.write_string(sb, ",#")
|
|
}
|
|
write_signed_decimal(sb, i64(m.disp))
|
|
}
|
|
strings.write_byte(sb, ']')
|
|
|
|
case .PRE_INDEXED:
|
|
if opts.space_after_comma {
|
|
strings.write_string(sb, ", #")
|
|
} else {
|
|
strings.write_string(sb, ",#")
|
|
}
|
|
write_signed_decimal(sb, i64(m.disp))
|
|
strings.write_string(sb, "]!")
|
|
|
|
case .POST_INDEXED:
|
|
strings.write_string(sb, "], #")
|
|
write_signed_decimal(sb, i64(m.disp))
|
|
|
|
case .REG_OFFSET:
|
|
strings.write_string(sb, ", ")
|
|
write_register(sb, m.index, opts.uppercase)
|
|
if reg_class(m.index) == REG_Z {
|
|
write_vector_shape(sb, m.index, index_shape, opts.uppercase)
|
|
}
|
|
if m.shift != 0 {
|
|
strings.write_string(sb, ", lsl #")
|
|
write_decimal_u32(sb, u32(m.shift))
|
|
}
|
|
strings.write_byte(sb, ']')
|
|
|
|
case .EXT_REG_OFFSET:
|
|
strings.write_string(sb, ", ")
|
|
write_register(sb, m.index, opts.uppercase)
|
|
if reg_class(m.index) == REG_Z {
|
|
write_vector_shape(sb, m.index, index_shape, opts.uppercase)
|
|
}
|
|
strings.write_string(sb, ", ")
|
|
strings.write_string(sb, EXTEND_NAMES[u8(m.extend) & 0x7])
|
|
if m.shift != 0 {
|
|
strings.write_string(sb, " #")
|
|
write_decimal_u32(sb, u32(m.shift))
|
|
}
|
|
strings.write_byte(sb, ']')
|
|
|
|
case .LITERAL:
|
|
strings.write_byte(sb, ']') // shouldn't normally appear
|
|
}
|
|
}
|
|
|
|
@(private="file")
|
|
write_decimal_u32 :: proc(sb: ^strings.Builder, v: u32) {
|
|
if v == 0 { strings.write_byte(sb, '0'); return }
|
|
buf: [10]u8
|
|
i := 0
|
|
n := v
|
|
for n > 0 { buf[i] = '0' + u8(n % 10); n /= 10; i += 1 }
|
|
for j := i - 1; j >= 0; j -= 1 { strings.write_byte(sb, buf[j]) }
|
|
}
|
|
|
|
@(private="file")
|
|
write_signed_decimal :: proc(sb: ^strings.Builder, v: i64) {
|
|
if v < 0 {
|
|
strings.write_byte(sb, '-')
|
|
n := u64(-(v + 1)) + 1
|
|
write_decimal_u64(sb, n)
|
|
} else {
|
|
write_decimal_u64(sb, u64(v))
|
|
}
|
|
}
|
|
|
|
@(private="file")
|
|
write_decimal_u64 :: proc(sb: ^strings.Builder, v: u64) {
|
|
if v == 0 { strings.write_byte(sb, '0'); return }
|
|
buf: [20]u8
|
|
i := 0
|
|
n := v
|
|
for n > 0 { buf[i] = '0' + u8(n % 10); n /= 10; i += 1 }
|
|
for j := i - 1; j >= 0; j -= 1 { strings.write_byte(sb, buf[j]) }
|
|
}
|