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:
committed by
Marge Bot
parent
53a2ada9d7
commit
65a5ff67e9
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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.')
|
||||
@@ -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("")
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user