agx: add XML-based disassembler

autogenerate a disassembler from the XML. also add a simple pure Python
reference disassembler, since autogenerated C code can be annoying to work with
in various contexts (we might drop this down the line, TBD if it's useful to
port over the current non-Mesa consumers of dougallj/applegpu). both
disassemblers produce identical output, unit tested via Meson.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/35189>
This commit is contained in:
Alyssa Rosenzweig
2025-05-27 15:57:14 -04:00
committed by Marge Bot
parent 53a2ada9d7
commit 65a5ff67e9
18 changed files with 1185 additions and 4 deletions
+35 -1
View File
@@ -7,6 +7,7 @@
#include "agx_compile.h"
#include "asahi/clc/asahi_clc.h"
#include "asahi/isa/disasm.h"
#include "asahi/layout/layout.h"
#include "asahi/lib/agx_abi.h"
#include "compiler/nir/nir_builder.h"
@@ -3647,8 +3648,41 @@ agx_compile_function_nir(nir_shader *nir, nir_function_impl *impl,
}
}
ralloc_free(ctx);
/*
* Disassemble if the environment variable is set. Also disassemble to
* /dev/null in debug builds as a smoke test - a legally encoded shader
* should not hit any disassembler errors.
*/
bool dump_shaders = agx_should_dump(nir, AGX_DBG_SHADERS);
#ifndef NDEBUG
bool selftest = !dump_shaders;
#else
bool selftest = false;
#endif
if (dump_shaders || selftest) {
FILE *fp = selftest ? fopen("/dev/null", "w") : stdout;
bool errors =
agx2_disassemble(binary->data + offset, binary->size - offset, fp);
if (errors) {
/* In a self-test, disassemble again to get something the user can look
* at to figure out what went wrong.
*/
if (selftest) {
agx2_disassemble(binary->data + offset, binary->size - offset,
stderr);
}
unreachable("Disassembly error hit.");
}
if (selftest) {
fclose(fp);
}
}
ralloc_free(ctx);
return offset;
}
+1 -1
View File
@@ -6,13 +6,13 @@
#pragma once
#include "asahi/isa/agx_minifloat.h"
#include "compiler/nir/nir.h"
#include "util/half_float.h"
#include "util/u_dynarray.h"
#include "util/u_math.h"
#include "util/u_worklist.h"
#include "agx_compile.h"
#include "agx_minifloat.h"
#include "agx_opcodes.h"
#ifdef __cplusplus
-1
View File
@@ -6,7 +6,6 @@
#include "util/macros.h"
#include "agx_builder.h"
#include "agx_compiler.h"
#include "agx_minifloat.h"
#include "agx_opcodes.h"
/* AGX peephole optimizer responsible for instruction combining. It operates in
+1
View File
@@ -92,6 +92,7 @@ libasahi_compiler = static_library(
'asahi_compiler',
[libasahi_agx_files, agx_opcodes_c, agx_nir_algebraic_c],
dependencies: [idep_nir, idep_agx_opcodes_h, idep_agx_builder_h, idep_mesautil, idep_libagx],
link_with : [libagx2_disasm],
c_args : [no_override_init_args, '-Wno-c2x-extensions'],
gnu_symbol_visibility : 'hidden',
build_by_default : false,
View File
@@ -6,6 +6,8 @@
#pragma once
#include <math.h>
#include <stdbool.h>
#include <string.h>
#include "util/macros.h"
/* AGX includes an 8-bit floating-point format for small dyadic immediates,
+258
View File
@@ -0,0 +1,258 @@
/*
* Copyright 2025 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "asahi/isa/agx_minifloat.h"
#include "util/bitset.h"
#include "util/u_math.h"
#include "disasm.h"
struct disasm_ctx {
FILE *fp;
bool any_operands;
bool error;
};
static inline uint64_t
bits(BITSET_WORD *word, unsigned start, unsigned size, unsigned shift)
{
return ((uint64_t)__bitset_extract(word, start, size)) << shift;
}
typedef void (*disasm_instr)(struct disasm_ctx *ctx, BITSET_WORD *code);
struct disasm_spec {
const char *display;
disasm_instr disassemble;
unsigned length_bit;
unsigned length_short;
unsigned length_long;
BITSET_WORD mask[4];
BITSET_WORD exact[4];
BITSET_WORD known[4];
};
static void
introduce_operand(struct disasm_ctx *ctx)
{
if (ctx->any_operands) {
fprintf(ctx->fp, ", ");
} else {
fprintf(ctx->fp, " ");
}
ctx->any_operands = true;
}
enum operand_kind {
KIND_NONE = 0,
KIND_REG,
KIND_UNIFORM,
KIND_CF,
KIND_TS,
KIND_SS,
KIND_IMM,
KIND_FIMM,
};
struct operand_desc {
enum operand_kind kind;
int32_t value;
unsigned hint, count;
bool optional, size16, size32, size64, abs, neg, sx, cache, lu;
};
static void
_print_operand(struct disasm_ctx *ctx, struct operand_desc d)
{
if (d.kind == KIND_NONE) {
if (!d.optional) {
introduce_operand(ctx);
fprintf(ctx->fp, "_");
}
return;
}
unsigned size = d.size64 ? 64 : d.size32 ? 32 : 16;
bool cache = d.cache || d.hint == 2;
bool lu = d.lu || d.hint == 3;
unsigned count = MAX2(d.count, 1);
introduce_operand(ctx);
if (lu && cache) {
fprintf(ctx->fp, "XXX invalid cache+lu set\n");
ctx->error = true;
}
if (lu)
fprintf(ctx->fp, "^");
if (cache)
fprintf(ctx->fp, "$");
const char *prefixes[][2] = {
[KIND_REG] = {"r", "dr"}, [KIND_UNIFORM] = {"u", "du"},
[KIND_CF] = {"cf"}, [KIND_TS] = {"ts"},
[KIND_SS] = {"ss"},
};
if (d.kind == KIND_IMM) {
fprintf(ctx->fp, "%d", d.value);
} else if (d.kind == KIND_FIMM) {
float f = agx_minifloat_decode(d.value);
/* Match python's float printing */
if (f == (int)f)
fprintf(ctx->fp, "%g.0", agx_minifloat_decode(d.value));
else
fprintf(ctx->fp, "%g", agx_minifloat_decode(d.value));
} else if (d.kind == KIND_CF || d.kind == KIND_TS || d.kind == KIND_SS) {
fprintf(ctx->fp, "%s%u", prefixes[d.kind][0], d.value);
} else {
for (unsigned i = 0; i < count; ++i) {
if (i)
fprintf(ctx->fp, "_");
unsigned reg = d.value + (i * (size / 16));
unsigned whole = reg >> 1;
unsigned part = reg & 1;
const char *prefix = prefixes[d.kind][size == 64];
if (size == 16) {
fprintf(ctx->fp, "%s%u%c", prefix, whole, "lh"[part]);
} else {
if (part != 0) {
fprintf(ctx->fp, "# 32-bit must be expected, but got raw %u\n",
reg);
ctx->error = true;
}
fprintf(ctx->fp, "%s%u", prefix, whole);
}
}
}
if (d.abs)
fprintf(ctx->fp, ".abs");
if (d.neg)
fprintf(ctx->fp, ".neg");
if (d.sx)
fprintf(ctx->fp, ".sx");
}
static void
print_immediate(struct disasm_ctx *ctx, bool is_signed, uint32_t value)
{
introduce_operand(ctx);
fprintf(ctx->fp, is_signed ? "%d" : "%u", value);
}
static void
_print_enum(struct disasm_ctx *ctx, const char **arr, unsigned n,
unsigned value)
{
if (value >= n || arr[value] == NULL) {
introduce_operand(ctx);
fprintf(ctx->fp, "XXX: Unknown enum value %u", value);
ctx->error = true;
} else if (arr[value][0] != 0) {
introduce_operand(ctx);
fprintf(ctx->fp, "%s", arr[value]);
}
}
#define print_enum(ctx, arr, value) \
_print_enum(ctx, arr, ARRAY_SIZE(arr), value)
static void
print_modifier(struct disasm_ctx *ctx, const char *display, unsigned value)
{
if (value) {
introduce_operand(ctx);
fprintf(ctx->fp, "%s", display);
}
}
static signed
_disassemble_instr(BITSET_WORD *code, FILE *fp, struct disasm_spec *specs,
unsigned nr_specs, unsigned offset, bool verbose)
{
BITSET_WORD tmp[4];
memcpy(tmp, code, sizeof(tmp));
struct disasm_spec *spec = NULL;
unsigned n = 0;
bool match = false;
BITSET_WORD masked[4] = {0};
for (unsigned i = 0; i < nr_specs; ++i) {
spec = &specs[i];
n = spec->length_short;
if (BITSET_TEST(tmp, spec->length_bit)) {
n = spec->length_long;
}
match = true;
for (unsigned i = 0; i < ARRAY_SIZE(tmp); ++i) {
masked[i] = tmp[i];
uint32_t bytes_left = (n - (i * 4));
if (bytes_left < 4) {
masked[i] &= ((1 << (bytes_left * 8)) - 1);
}
if ((masked[i] & spec->mask[i]) != spec->exact[i]) {
match = false;
break;
}
}
if (match)
break;
}
struct disasm_ctx ctx = {.fp = fp};
if (match) {
BITSET_WORD unknown[4];
__bitset_andnot(unknown, masked, spec->known, ARRAY_SIZE(masked));
int i;
BITSET_FOREACH_SET(i, unknown, n * 8) {
fprintf(fp, "# XXX: Unknown bit set %u\n", i);
ctx.error = true;
}
} else {
n = 2;
}
if (verbose) {
fprintf(fp, "%4x: ", offset);
for (unsigned i = 0; i < n; ++i) {
fprintf(fp, "%02x", __bitset_extract(masked, i * 8, 8));
}
for (unsigned i = n; i < 11; ++i) {
fprintf(fp, " ");
}
fprintf(fp, " ");
}
if (!match) {
fprintf(fp, "<unknown instr>\n");
return -n;
}
fprintf(fp, "%s", spec->display);
if (spec->disassemble)
spec->disassemble(&ctx, masked);
fprintf(fp, "\n");
return ctx.error ? -n : n;
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright 2025 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include "util/bitset.h"
signed agx2_disassemble_instr(BITSET_WORD *code, FILE *fp, unsigned offset,
bool verbose);
static inline bool
agx2_disassemble(uint8_t *code, size_t max_len, FILE *fp)
{
int i = 0;
bool errors = false;
while (i < max_len) {
/* Break on trap */
if (code[i + 0] == 0x08 && code[i + 1] == 0x00)
break;
signed ret =
agx2_disassemble_instr((BITSET_WORD *)(code + i), fp, i, true);
if (ret < 0)
fprintf(fp, "XXX error here\n");
errors |= ret < 0;
i += abs(ret);
}
return errors;
}
+168
View File
@@ -0,0 +1,168 @@
# Copyright 2016 Intel Corporation
# Copyright 2016 Broadcom
# Copyright 2020 Collabora, Ltd.
# Copyright 2025 Valve Corporation
# SPDX-License-Identifier: MIT
from mako.template import Template
from mako import exceptions
from isa import isa
# XXX: deduplicate
def to_alphanum(name):
substitutions = {
' ': '_',
'/': '_',
'[': '',
']': '',
'(': '',
')': '',
'-': '_',
':': '',
'.': '',
',': '',
'=': '',
'>': '',
'#': '',
'&': '',
'*': '',
'"': '',
'+': '',
'\'': '',
}
for i, j in substitutions.items():
name = name.replace(i, j)
return name
def safe_name(name):
name = to_alphanum(name)
if not name[0].isalpha():
name = '_' + name
return name.lower()
def chunk(x):
chunks = [hex((x >> (32 * i)) & ((1 << 32) - 1)) for i in range(4)]
while len(chunks) > 1 and chunks[-1] == '0x0':
chunks.pop()
return f'{{{", ".join(chunks)}}}'
def instr_is_trivial(I):
return len(I.dests + I.sources + I.immediates + I.modifiers) == 0
def extract_bits(x, bitset="r"):
parts = []
n = 0
for r in x.bits.ranges:
parts.append(f'bits({bitset}, {r.start}, {r.size}, {n})')
n += r.size
return ' | '.join(parts)
template = """
#include "disasm-internal.h"
% for en in isa.enums:
UNUSED static const char *enum_${safe_name(isa.enums[en].kind)}[] = {
% for n, v in isa.enums[en].values.items():
[${n}] = "${v.display or ''}",
% endfor
};
% endfor
% for k, enc in isa.encodings.items():
static void
print_${k}(struct disasm_ctx *ctx, BITSET_WORD *orig, uint64_t v, bool source)
{
BITSET_WORD r[] = { v, v >> 32 };
% for i, case in enumerate(enc.cases):
${"} else " if i > 0 else ""}if ((v & ${hex(case.exact.mask)}ull) == ${hex(case.exact.value)}ull) {
% for mod in case.modifiers:
% if mod.name == 'hint':
if (${extract_bits(mod)} == 0) {
fprintf(ctx->fp, "# missing hint");
}
% endif
% endfor
_print_operand(ctx, (struct operand_desc){
% if case.kind is not None:
.kind = KIND_${case.kind.name.upper()},
% if case.kind.kind == 'signed':
.value = util_sign_extend(${extract_bits(case.kind)} << ${case.kind.shift}, ${case.kind.bits.size()}),
% else:
.value = ${extract_bits(case.kind)} << ${case.kind.shift},
% endif
% endif
% for mod in case.modifiers:
% if mod.name == 'mask':
.count = util_bitcount(${extract_bits(mod)}),
% elif mod.name == 'count':
.count = (${extract_bits(mod)}) ?: 4,
% elif mod.name == 'zx':
.sx = ${extract_bits(mod)} ^ 1,
% else:
.${mod.name} = ${extract_bits(mod)},
% endif
% endfor
% for prop, v in case.properties.items():
.${prop} = ${v},
% endfor
.optional = source,
});
% endfor
} else {
fprintf(ctx->fp, "# XXX: Invalid value 0x%"PRIx64" for ${k}", v);
ctx->error = true;
}
}
% endfor
% for k, instr in isa.instrs.items():
% if not instr_is_trivial(instr):
static void
print_${instr.name}(struct disasm_ctx *ctx, BITSET_WORD *r)
{
% for i, op in enumerate(instr.dests):
print_${op.kind}(ctx, r, ${extract_bits(op)}, false);
% endfor
% for i, op in enumerate(instr.sources):
print_${op.kind}(ctx, r, ${extract_bits(op)}, true);
% endfor
% for imm in instr.immediates:
print_immediate(ctx, ${'true' if imm.kind == 'signed' else 'false'}, ${extract_bits(imm)});
% endfor
% for mod in instr.modifiers:
% if mod.name != 'length':
% if mod.kind in isa.enums:
print_enum(ctx, enum_${mod.kind}, ${extract_bits(mod)});
% else:
print_modifier(ctx, "${mod.display}", ${extract_bits(mod)});
% endif
% endif
% endfor
}
% endif
% endfor
struct disasm_spec decodings[] = {
% for k, instr in isa.instrs.items():
{ "${instr.display if instr_is_trivial(instr) else f'{instr.display:<8}'}", ${'NULL' if instr_is_trivial(instr) else f'print_{instr.name}'}, ${instr.length_bit}, ${instr.length[0]}, ${instr.length[-1]}, ${chunk(instr.exact.mask)}, ${chunk(instr.exact.value)}, ${chunk(instr.mask())} },
% endfor
};
signed
agx2_disassemble_instr(BITSET_WORD *code, FILE *fp, unsigned offset, bool verbose)
{
return _disassemble_instr(code, fp, decodings, ARRAY_SIZE(decodings), offset, verbose);
}"""
try:
print(Template(template).render(isa = isa, safe_name = safe_name, chunk = chunk, instr_is_trivial = instr_is_trivial, extract_bits = extract_bits))
except:
print(exceptions.text_error_template().render())
+321
View File
@@ -0,0 +1,321 @@
# Copyright 2025 Valve Corporation
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from functools import reduce
import xml.etree.ElementTree as ET
import sys
import copy
import os
def ensure(cond, msg):
if not cond:
print(msg)
sys.exit(1)
class BitRange:
def __init__(self, start, end):
assert(start <= end)
self.start = start
self.end = end
if self.start >= 0:
self.size = end - start + 1
self.size_mask = (1 << self.size) - 1
self.mask = self.size_mask << start
def parse(spec: str):
if ':' in spec:
start, end = [int(x) for x in spec.split(':')]
return BitRange(start, end)
else:
return BitRange(int(spec), int(spec))
def specialize(self, length):
return BitRange(self.start % length, self.end % length)
def __repr__(self):
if self.start == self.end:
return f'{self.start}'
else:
return f'{self.start}:{self.end}'
class Bits:
def __init__(self, spec = None):
if spec is not None:
self.ranges = [BitRange.parse(r) for r in spec.split(' ')]
else:
self.ranges = []
def size(self) -> int:
return sum([x.size for x in self.ranges])
def mask(self) -> int:
masks = [r.mask for r in self.ranges]
union = 0
for mask in masks:
union |= mask
# No overlap
assert(sum(masks) == union)
return union
def extract(self, x):
accum = 0
n = 0
for r in self.ranges:
accum |= ((x >> r.start) & r.size_mask) << n
n += r.size
return accum
def specialize(self, length):
obj = copy.copy(self)
obj.ranges = [r.specialize(length) for r in obj.ranges]
return obj
def __repr__(self):
return ' '.join([str(r) for r in self.ranges])
def parse_bitstring(bits):
value = 0
# Reverse to deal with the convention
bits = bits[::-1]
for i, bit in enumerate(bits):
assert(bit in ['0', '1'])
if bit == '1':
value |= (1 << i)
return value
# Spread the bits of value out according to the 1s in mask.
# Similar to pdep in x86.
def deposit_bits(mask, value):
accum = 0
value_i = 0
assert(mask < (1 << 128))
for m in range(128):
if mask & (1 << m):
if (value & (1 << value_i)) != 0:
accum |= (1 << m)
value_i += 1
assert((accum & ~mask) == 0)
return accum
def substitute(text, substitutions):
if text in substitutions:
return substitutions[text]
else:
return text
class Exact:
def __init__(self, el = None, subst = None):
bits = 0
if el is not None:
self.mask = Bits(el.attrib['bit']).mask()
if el.tag != 'zero':
text = substitute(el.text, subst)
bits = parse_bitstring(text)
assert(len(text) == self.mask.bit_count())
else:
self.mask = 0
self.value = deposit_bits(self.mask, bits)
def union(self, other):
self.value |= other.value
self.mask |= other.mask
class EnumValue:
def __init__(self, el):
self.name = el.text
self.label = el.attrib.get('label')
self.default = bool(el.attrib.get('default', False))
self.display = None if self.default else self.name
class Enum:
def __init__(self, el):
self.name = el.attrib['name']
self.kind = el.attrib['kind']
self.values = {}
for child in el:
self.values[int(child.attrib['value'])] = EnumValue(child)
class Immediate:
def __init__(self, el, name):
self.name = name
self.kind = el.attrib.get('kind', 'int')
self.bits = Bits(el.attrib['bit'])
self.shift = int(el.attrib.get('shift', 0))
class Operand:
def __init__(self, el, is_dest):
self.kind = el.attrib['kind']
self.bits = Bits(el.attrib['bit'])
self.is_dest = is_dest
def specialize(self, length):
obj = copy.copy(self)
obj.bits = obj.bits.specialize(length)
return obj
class EncodingCase:
def __init__(self, el):
self.exact = Exact()
self.modifiers = []
self.kind = None
self.properties = {}
for ch in el:
if ch.tag in ["exact", "zero"]:
self.exact.union(Exact(ch, {}))
elif ch.tag == "immediate":
assert(self.kind is None)
self.kind = Immediate(ch, ch.attrib['name'])
elif ch.tag == "modifier":
self.modifiers.append(Modifier(ch, ch.attrib['name']))
elif ch.tag == "property":
self.properties[ch.attrib['name']] = ch.text or '1'
def union(self, other):
self.exact.union(other.exact)
self.modifiers = other.modifiers + self.modifiers
self.properties.update(other.properties)
assert(self.kind is None or other.kind is None)
self.kind = self.kind or other.kind
class Encoding:
def __init__(self, el):
base = EncodingCase(el)
cases = [EncodingCase(ch) for ch in el if ch.tag == 'case']
if len(cases) == 0:
self.cases = [base]
else:
self.cases = cases
for c in self.cases:
c.union(base)
class Modifier:
def __init__(self, el, name):
self.name = name
self.display = el.get('display', name)
self.kind = el.get('kind', 'int')
self.bits = Bits(el.attrib['bit'])
class Instruction:
def __init__(self, xml, name, display, substitutions, defs):
length = [int(x) for x in xml.attrib['length'].split('/')]
exact = Exact()
mods = []
dests = []
srcs = []
imms = []
for el in xml:
if el.tag in ["exact", "zero"]:
exact.union(Exact(el, substitutions))
elif el.tag == "immediate":
imms.append(Immediate(el, el.attrib['name']))
elif el.tag == "src":
srcs.append(Operand(el, False))
elif el.tag == "dest":
dests.append(Operand(el, True))
elif el.tag == "modifier":
mods.append(Modifier(el, el.attrib['name']))
elif el.tag in defs:
d = defs[el.tag]
if isinstance(d, Operand):
spec = d.specialize(length[-1] * 8)
if spec.is_dest:
dests.append(spec)
else:
srcs.append(spec)
elif isinstance(d, Modifier):
mods.append(d)
elif isinstance(d, Immediate):
imms.append(d)
else:
assert(0)
elif el.tag == 'ins':
assert(xml.tag == 'group')
else:
print(name)
assert(0)
# Infer length bit if needed
if len(length) > 1 and not any([x.name == 'length' for x in mods]):
mods.append(defs['length'])
self.length_spec = None
self.length_bit = 0
for mod in mods:
if mod.name == 'length':
self.length_spec = mod
self.length_bit = mod.bits.ranges[0].start
self.name = name
self.display = display or name
self.length = length
self.exact = exact
self.modifiers = mods
self.dests = dests
self.sources = srcs
self.immediates = imms
def mask(self):
mask = self.exact.mask
parts = self.modifiers + self.immediates + self.sources + self.dests
bits = [x.bits.mask() for x in parts]
return self.exact.mask | reduce(lambda x, y: x | y, bits, 0)
class ISA:
def __init__(self, el):
self.enums = {}
self.instrs = {}
self.encodings = {}
defs = {}
for child in el:
name = child.get('name')
if child.tag == "define":
if 'src' in child.attrib:
defs[child.attrib['src']] = Operand(child, False)
elif 'dest' in child.attrib:
defs[child.attrib['dest']] = Operand(child, True)
elif 'modifier' in child.attrib:
name = child.attrib['modifier']
defs[child.attrib['modifier']] = Modifier(child, name)
elif 'immediate' in child.attrib:
name = child.attrib['immediate']
defs[child.attrib['immediate']] = Immediate(child, name)
else:
assert(0)
elif child.tag == "encoding":
self.encodings[child.attrib['name']] = Encoding(child)
elif child.tag == "enum":
enum = Enum(child)
self.enums[enum.kind] = enum
elif child.tag == "ins":
ins = Instruction(child, name, child.get('display'), {}, defs)
ensure(ins.name not in self.instrs, f'Multiple instructions named {ins.name}')
self.instrs[ins.name] = ins
elif child.tag == "group":
ensure(any([c.tag == "ins" for c in child]), f'No instructions in group {name}')
for c in child:
if c.tag == "ins":
ins = Instruction(child, c.attrib['name'], c.get('display'), c.attrib, defs)
ensure(ins.name not in self.instrs, f'Multiple instructions named {ins.name}')
self.instrs[ins.name] = ins
xmlfile = os.path.join(os.path.dirname(__file__), 'AGX2.xml')
isa = ISA(ET.parse(xmlfile).getroot())
+41
View File
@@ -0,0 +1,41 @@
# Copyright (C) 2020-2021 Collabora
agx2_disasm_c = custom_target(
input : ['gen-disasm.py', 'AGX2.xml'],
output : 'agx2_disasm.c',
command : [prog_python, '@INPUT@'],
capture : true,
depend_files : files('isa.py'),
)
libagx2_disasm = static_library(
'agx2_disasm',
agx2_disasm_c,
include_directories : [inc_include, inc_src],
c_args : [no_override_init_args],
gnu_symbol_visibility : 'hidden',
build_by_default : true,
)
if with_tests
test(
'agx2_disasm',
executable(
'agx2_disasm_test',
files('test/test-disassembler.c'),
c_args : [c_msvc_compat_args, no_override_init_args],
gnu_symbol_visibility : 'hidden',
include_directories : [inc_include, inc_src],
link_with : [libagx2_disasm],
),
suite : ['asahi'],
args : files('test/cases.txt'),
)
test(
'agx2_python_disasm',
prog_python,
args : files('test-disasm.py', 'test/cases.txt'),
suite : ['asahi']
)
endif
+46
View File
@@ -0,0 +1,46 @@
# Copyright 2025 Valve Corporation
# SPDX-License-Identifier: MIT
import sys
import os
import textwrap
import binascii
from test.disasm import disasm
errors = False
new_cases = []
for case in open(sys.argv[1]).read().strip().split('\n'):
first = case.split(' ')[0]
ref = case[len(first):].strip()
# Extract bytes
lst = [int(x, 16) for x in textwrap.wrap(first, 2)]
# Make sure we don't depend on zeroes in upper bits
padded = lst + [0xCA, 0xFE, 0xBA, 0xBE] * 16
raw = sum([x << (8 * i) for i, x in enumerate(padded)])
error = False
length, asm = disasm(raw)
error |= (length != len(lst))
actual = f'{str(binascii.hexlify(bytes(lst)).decode()):<20} {asm}\n'
new_cases.append(actual)
error |= (actual[:-1] != case)
if error:
print(case)
print(actual)
errors |= error
# If there were errors, optionally update expectations.
if len(sys.argv) >= 3:
assert(sys.argv[2] in ['-u', '--update'])
open(sys.argv[1], 'w').write(''.join(new_cases))
if errors:
print('Fail.')
sys.exit(1)
else:
print('Pass.')
View File
+158
View File
@@ -0,0 +1,158 @@
# Copyright 2025 Valve Corporation
# SPDX-License-Identifier: MIT
from isa import isa
import math
import sys
import binascii
def match_instr(raw):
for key in isa.instrs:
instr = isa.instrs[key]
length = instr.length[instr.length_spec.bits.extract(raw) if instr.length_spec is not None else 0]
r = raw & ((1 << (length * 8)) - 1)
if (r & instr.exact.mask) == instr.exact.value:
return length, instr, r
return 2, None, raw
def map_value(v, kind, length):
if kind in isa.enums:
val = isa.enums[kind].values.get(v)
return f'unk{v}' if val is None else val.display
elif kind == 'signed':
return str(-((1 << length) - v) if (v & (1 << (length - 1))) else v)
else:
return str(v)
def map_mod(v, mod):
if mod.kind in isa.enums:
return map_value(v, mod.kind, 0)
else:
assert(mod.bits.size() == 1)
return mod.display if v != 0 else None
def decode_float_immediate(n):
sign = -1.0 if n & 0x80 else 1.0
e = (n & 0x70) >> 4
f = n & 0xF
if e == 0:
return sign * f / 64.0
else:
return sign * float(0x10 | f) * (2.0 ** (e - 7))
def operand(desc, length):
count = desc.get('count', 1)
if count == 0:
count = 4
if 'mask' in desc:
count = desc['mask'].bit_count()
if 'zx' in desc:
desc['sx'] = desc['zx'] ^ 1
size = 64 if desc.get('size64', 0) != 0 else 32 if desc.get('size32', 0) != 0 else 16
base = desc.get('base')
kind = desc.get('kind')
cache = desc.get('cache', False)
discard = desc.get('discard', False)
hint = desc.get('hint')
if hint is not None:
if hint == 3:
discard = True
elif hint == 2:
cache = True
else:
assert(hint == 1)
assert(not (cache and discard))
hint = '^' if discard else '$' if cache else ''
suffixes = ''.join(['.' + x for x in ['abs', 'neg', 'sx'] if desc.get(x, 0) != 0])
if kind == None:
return None
elif kind in ['cf', 'ts', 'ss']:
assert(count == 1)
return f'{kind}{base}' + suffixes
elif kind == 'fimm':
return str(decode_float_immediate(base)) + suffixes
elif kind in ['imm', 'signed']:
return map_value(base, kind, length) + suffixes
def m(offs):
assert(size == 16 or size == 32 or size == 64)
reg = base + (offs * (size // 16))
whole = reg >> 1
part = reg & 1
prefix = kind[0]
if size == 16:
part_str = 'h' if part != 0 else 'l'
return f'{prefix}{whole}{part_str}'
else:
assert(part == 0)
return f'{"d" if size == 64 else ""}{prefix}{whole}'
return hint + '_'.join([m(x) for x in range(count)]) + suffixes
def map_op(v, kind, raw, ins, length):
for c in isa.encodings[kind].cases:
if (v & c.exact.mask) == c.exact.value:
desc = {}
if c.kind is not None:
length = c.kind.bits.size()
desc['kind'] = c.kind.name
desc['base'] = c.kind.bits.extract(v) << c.kind.shift
if c.kind.kind == 'signed':
desc['kind'] = 'signed'
for mod in c.modifiers:
desc[mod.name] = mod.bits.extract(v)
for prop in c.properties:
desc[prop] = int(c.properties[prop])
return operand(desc, length)
def disasm(raw_in):
length, instr, raw = match_instr(raw_in)
if instr is None:
return length, "<unknown instr>"
imms = [map_value(imm.bits.extract(raw), imm.kind, imm.bits.size()) for imm in instr.immediates]
mods = [map_mod(mod.bits.extract(raw), mod) for mod in instr.modifiers if mod.name != 'length']
dests = [map_op(d.bits.extract(raw), d.kind, raw, instr, d.bits.size()) for d in instr.dests]
srcs = [map_op(s.bits.extract(raw), s.kind, raw, instr, s.bits.size()) for s in instr.sources]
dests = ['_' if x is None else str(x) for x in dests]
srcs = [str(x) for x in srcs if x is not None]
imms = [str(x) for x in imms if x is not None]
mods = [str(x) for x in mods if x is not None]
end = ', '.join(dests + srcs + imms + mods)
if end != "":
end = " " + end
asm = f'{instr.display:<8}{end}'.strip()
unexpected = raw & ~instr.mask()
unexpected = [i for i in range(length * 8) if unexpected & (1 << i)]
if len(unexpected) != 0:
print(f"Unexpected set bits: {unexpected}")
length = -length
return length, asm
if __name__ == '__main__':
prog = open(sys.argv[1], 'rb').read()
print("")
i = 0
while i < len(prog):
lst = prog[i:i + 16]
raw = sum([int(x) << (8 * i) for i, x in enumerate(lst)])
length, asm = disasm(raw)
lst = lst[0:length]
print(f'{hex(i)[2:]:>4}: {str(binascii.hexlify(lst).decode()):<20} {asm}')
if asm.strip() == 'trap':
break
i += length
print("")
+114
View File
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2021 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "asahi/isa/disasm.h"
#include "util/macros.h"
static inline uint8_t
parse_nibble(const char c)
{
return (c >= 'a') ? 10 + (c - 'a') : (c - '0');
}
/* Given a little endian 8 byte hexdump, parse out the 64-bit value */
static uint64_t
parse_hex(const char *in)
{
uint64_t v = 0;
for (unsigned i = 0; i < 8; ++i) {
uint8_t byte = (parse_nibble(in[0]) << 4) | parse_nibble(in[1]);
v |= ((uint64_t)byte) << (8 * i);
/* Skip the space after the byte */
in += 3;
}
return v;
}
int
main(int argc, const char **argv)
{
if (argc < 2) {
fprintf(stderr, "Expected case list\n");
return 1;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "Could not open the case list");
return 1;
}
char line[128];
unsigned nr_fail = 0, nr_pass = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
char *output = NULL;
size_t sz = 0;
size_t len = strlen(line);
/* Skip empty lines */
if (len <= 1)
continue;
/* Parse the hex */
uint8_t code[16];
const char *cursor = line;
const char *end = line + len;
uint32_t i = 0;
while ((cursor + 2) <= end && cursor[0] != ' ') {
assert(i < ARRAY_SIZE(code));
code[i] = (parse_nibble(cursor[0]) << 4) | parse_nibble(cursor[1]);
cursor += 2;
++i;
}
/* Skip spacing */
while (cursor < end && (*cursor) == ' ')
++cursor;
FILE *outputp = open_memstream(&output, &sz);
signed ret = agx2_disassemble_instr((void *)code, outputp, 0, false);
unsigned instr_len = abs(ret);
fclose(outputp);
/* Rest of the line is the reference assembly */
bool fail = strcmp(cursor, output);
fail |= (instr_len != i);
fail |= (ret < 0);
if (fail) {
/* Extra spaces after Got to align with Expected */
printf("Got %sExpected %s\n", output, cursor);
if (instr_len != i) {
printf("Got length %d, expected length %u\n", instr_len, i);
}
if (ret < 0) {
printf("Got an error.\n");
}
nr_fail++;
} else {
nr_pass++;
}
free(output);
}
printf("Passed %u/%u tests.\n", nr_pass, nr_pass + nr_fail);
fclose(fp);
return nr_fail ? 1 : 0;
}
+3 -1
View File
@@ -16,6 +16,7 @@
#include <sys/mman.h>
#include <agx_pack.h>
#include "asahi/isa/disasm.h"
#include "util/u_hexdump.h"
#include "decode.h"
@@ -24,7 +25,8 @@ struct libagxdecode_config lib_config;
static void
agx_disassemble(void *_code, size_t maxlen, FILE *fp)
{
/* stub */
bool errors = agx2_disassemble(_code, maxlen, fp);
assert(!errors);
}
FILE *agxdecode_dump_stream;
+2
View File
@@ -36,6 +36,7 @@ libasahi_decode = static_library(
dependencies : [dep_valgrind, idep_mesautil],
c_args : [no_override_init_args, '-Wno-c2x-extensions'],
gnu_symbol_visibility : 'hidden',
link_with : [libagx2_disasm],
build_by_default : false,
)
@@ -45,6 +46,7 @@ libasahi_decode_shared = shared_library(
dependencies : [dep_valgrind, idep_mesautil],
c_args : [no_override_init_args, '-Wno-c2x-extensions'],
build_by_default : with_tools.contains('asahi'),
link_with : [libagx2_disasm],
)
libagx_shaders = custom_target(
+2
View File
@@ -9,6 +9,8 @@ inc_asahi = include_directories([
'.', 'layout', 'lib', 'genxml', 'compiler'
])
subdir('isa')
if with_gallium_asahi or with_asahi_vk or with_tools.contains('asahi')
subdir('genxml')
subdir('libagx')