pan/va: Generalize opcode/opcode2

Rather than opcode/opcode2 hardcoded, treat the opcode as a list of
one or more subcodes.

This implies modifying the disassembler to hold an arbitrary depth dict
of dicts and recursively build the switch statements used to look up
each level.

Reviewed-by: Christoph Pillmayer <christoph.pillmayer@arm.com>
Acked-by: Lorenzo Rossi <lorenzo.rossi@collabora.com>
Acked-by: Eric R. Smith <eric.smith@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40199>
This commit is contained in:
Lars-Ivar Hesselberg Simonsen 2026-03-11 11:12:14 +01:00 committed by Marge Bot
parent 11f243205c
commit 614d07c986
5 changed files with 1017 additions and 405 deletions

File diff suppressed because it is too large Load diff

View file

@ -315,9 +315,8 @@ def parse_asm(line):
operands = operands[len(ins.immediates):]
# Encode the operation itself
encoded |= (ins.opcode.value << ins.opcode.start)
if ins.opcode2:
encoded |= (ins.opcode2.value << ins.opcode2.start)
for subcode in ins.opcode:
encoded |= (subcode.value << subcode.start)
# Encode FAU page
if fau.page:

View file

@ -194,39 +194,42 @@ va_print_dest(FILE *fp, uint8_t dest, bool can_mask)
% endfor
</%def>
<%def name="recurse_subcodes(op_bucket)">
%if op_bucket.instr:
${print_instr(op_bucket.instr)}
%else:
opcode = (instr >> ${op_bucket.start}) & ${hex(op_bucket.mask)};
switch (opcode) {
%for op in op_bucket.children:
case ${hex(op)}:
{
${recurse_subcodes(op_bucket.children[op])}
break;
}
%endfor
}
%endif
</%def>
void
va_disasm_instr(FILE *fp, uint64_t instr)
{
unsigned primary_opc = (instr >> 48) & MASK(9);
unsigned opcode;
unsigned fau_page = (instr >> 57) & MASK(2);
unsigned secondary_opc = 0;
switch (primary_opc) {
% for bucket in OPCODES:
<%
ops = OPCODES[bucket]
ambiguous = (len(ops) > 1)
%>
% if len(ops) > 0:
case ${hex(bucket)}:
% if ambiguous:
secondary_opc = (instr >> ${ops[0].opcode2.start}) & ${hex(ops[0].opcode2.mask)};
% endif
% for op in ops:
% if ambiguous:
${recurse_subcodes(OPCODES)}
}
if (secondary_opc == ${op.opcode2.value}) {
% endif
${print_instr(op)}
% if ambiguous:
}
% endif
% endfor
break;
% endif
% endfor
}
static bool is_branch(uint64_t instr)
{
<% (exact, mask) = OPCODES.get_exact_mask("BRANCHZ") %>
if ((instr & ${hex(mask)}) == ${hex(exact)})
return true;
<% (exact, mask) = OPCODES.get_exact_mask("BRANCHZI") %>
if ((instr & ${hex(mask)}) == ${hex(exact)})
return true;
return false;
}
void
@ -259,13 +262,8 @@ disassemble_valhall(FILE *fp, const void *code, size_t size, bool verbose)
va_disasm_instr(fp, instr);
fprintf(fp, "\\n");
/* Detect branches */
uint64_t opcode = (instr >> 48) & MASK(9);
bool branchz = (opcode == 0x1F);
bool branchzi = (opcode == 0x2F);
/* Separate blocks visually by inserting whitespace after branches */
if (branchz || branchzi)
if (is_branch(instr))
fprintf(fp, "\\n");
}
@ -273,30 +271,47 @@ disassemble_valhall(FILE *fp, const void *code, size_t size, bool verbose)
}
"""
# Bucket by opcode for hierarchical disassembly
OPCODE_BUCKETS = {}
class OpBucket:
def __init__(self):
self.start = None
self.mask = None
self.instr = None
self.children = {}
def insert(self, subcodes, ins):
if len(subcodes) == 0:
self.instr = ins
else:
sc = subcodes[0]
assert(self.start is None or self.start == sc.start)
assert(self.mask is None or self.mask == sc.mask)
self.start = sc.start
self.mask = sc.mask
if sc.value not in self.children:
self.children[sc.value] = OpBucket()
self.children[sc.value].insert(subcodes[1:], ins)
def get_exact_mask(self, op_name, exact = 0, mask = 0):
if self.instr:
if self.instr.name == op_name:
return (exact, mask)
else:
return ()
else:
for op in self.children:
exact_mask = self.children[op].get_exact_mask(op_name,
exact | (op << self.start),
mask | (self.mask << self.start))
if exact_mask:
return exact_mask
return ()
# Build opcode hierarchy:
OPCODES = OpBucket()
for ins in instructions:
opc = ins.opcode.value
OPCODE_BUCKETS[opc] = OPCODE_BUCKETS.get(opc, []) + [ins]
# Check that each bucket may be disambiguated
for op in OPCODE_BUCKETS:
bucket = OPCODE_BUCKETS[op]
# Nothing to disambiguate
if len(bucket) < 2:
continue
SECONDARY = {}
for ins in bucket:
# Number of sources determines opcode2 placement, must be consistent
assert(len(ins.srcs) == len(bucket[0].srcs))
# Must not repeat, else we're ambiguous
assert(ins.opcode2.value not in SECONDARY)
SECONDARY[ins.opcode2.value] = ins
OPCODES.insert(ins.opcode, ins)
try:
print(Template(template).render(OPCODES = OPCODE_BUCKETS, IMMEDIATES = immediates, ENUMS = enums, typesize = typesize, safe_name = safe_name))
print(Template(template).render(OPCODES = OPCODES, IMMEDIATES = immediates, ENUMS = enums, typesize = typesize, safe_name = safe_name))
except:
print(exceptions.text_error_template().render())

View file

@ -147,9 +147,9 @@ valhall_opcodes[BI_NUM_OPCODES] = {
# Exact value to be ORed in to every opcode
def exact_op(op):
exact_op = (op.opcode.value << op.opcode.start)
if op.opcode2:
exact_op |= (op.opcode2.value << op.opcode2.start)
exact_op = 0
for subcode in op.opcode:
exact_op |= (subcode.value << subcode.start)
return exact_op
try:

View file

@ -164,12 +164,11 @@ class Opcode:
self.mask = mask
class Instruction:
def __init__(self, name, opcode, opcode2, srcs = [], dests = [], immediates = [], modifiers = [], staging = None, unit = None):
def __init__(self, name, opcode, srcs = [], dests = [], immediates = [], modifiers = [], staging = None, unit = None):
self.name = name
self.srcs = srcs
self.dests = dests
self.opcode = opcode
self.opcode2 = opcode2
self.immediates = immediates
self.modifiers = modifiers
self.staging = staging
@ -180,7 +179,6 @@ class Instruction:
self.message = unit not in ["FMA", "CVT", "SFU"]
assert(len(dests) == 0 or not staging)
assert(not opcode2 or (opcode2.value & opcode2.mask) == opcode2.value)
def __str__(self):
return self.name
@ -226,20 +224,25 @@ def build_modifier(el):
return Modifier(name, start, size, implied)
def build_opcode(el, name):
op_arr = []
opcode = el.find(name)
if opcode is None:
return None
value = int(opcode.get('val'), base=0)
start = int(opcode.get('start'))
mask = int(opcode.get('mask'), base=0)
return Opcode(value, start, mask)
for subcode in opcode:
value = int(subcode.get('val'), base=0)
start = int(subcode.get('start'))
mask = int(subcode.get('mask'), base=0)
assert((value & mask) == value)
op_arr.append(Opcode(value, start, mask))
return op_arr
# Build a single instruction from XML and group based overrides
def build_instr(el, overrides = {}):
# Get overridables
name = overrides.get('name') or el.attrib.get('name')
opcode = overrides.get('opcode') or build_opcode(el, 'opcode')
opcode2 = overrides.get('opcode2') or build_opcode(el, 'opcode2')
unit = overrides.get('unit') or el.attrib.get('unit')
# Get explicit sources/dests
@ -279,7 +282,7 @@ def build_instr(el, overrides = {}):
elif mod.tag =='va_mod':
modifiers.append(build_modifier(mod))
instr = Instruction(name, opcode, opcode2, srcs = sources, dests = dests, immediates = imms, modifiers = modifiers, staging = staging, unit = unit)
instr = Instruction(name, opcode, srcs = sources, dests = dests, immediates = imms, modifiers = modifiers, staging = staging, unit = unit)
instructions.append(instr)
@ -290,7 +293,6 @@ def build_group(el):
build_instr(el, overrides = {
'name': ins.attrib['name'],
'opcode': build_opcode(ins, 'opcode'),
'opcode2': build_opcode(ins, 'opcode2'),
'unit': ins.attrib.get('unit'),
})