isaspec: Add initial decoding support

This reuses the <map> entries in the <encode> block to go in the reverse
direction and parse an instruction into a machine-readable structure. It
currently assumes that <map> entries are simple l-values like
"src->src[0]" or "src->flag", which is enough for afuc, but the plan for
the future is to use the <decode> block to allow us to override that for
more complex cases.

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/23949>
This commit is contained in:
Connor Abbott 2023-06-26 17:20:34 +02:00 committed by Marge Bot
parent 2faf344f03
commit 038680c2f6
5 changed files with 261 additions and 1 deletions

View file

@ -27,6 +27,59 @@ import argparse
import os
import sys
class FieldDecode(object):
def __init__(self, name, map_expr):
self.name = name;
self.map_expr = map_expr
def get_c_name(self):
return self.name.lower().replace('-', '_')
# State and helpers used by the template:
class State(object):
def __init__(self, isa):
self.isa = isa
def case_name(self, bitset, name):
return bitset.encode.case_prefix + name.upper().replace('.', '_').replace('-', '_').replace('#', '')
# Return a list of all <map> entries for a leaf bitset, with the child
# bitset overriding the parent bitset's entries. Because we can't resolve
# which <map>s are used until we resolve which overload is used, we
# generate code for encoding all of these and then at runtime select which
# one to call based on the display.
def decode_fields(self, bitset):
if bitset.get_root().decode is None:
return
seen_fields = set()
if bitset.encode is not None:
for name, expr in bitset.encode.maps.items():
seen_fields.add(name)
yield FieldDecode(name, expr)
if bitset.extends is not None:
for field in self.decode_fields(self.isa.bitsets[bitset.extends]):
if field.name not in seen_fields:
yield field
# A limited resolver for field type which doesn't properly account for
# overrides. In particular, if a field is defined differently in multiple
# different cases, this just blindly picks the last one.
#
# TODO to do this properly, I don't think there is an alternative than
# to emit code which evaluates the case.expr
def resolve_simple_field(self, bitset, name):
field = None
for case in bitset.cases:
if name in case.fields:
field = case.fields[name]
if field is not None:
return field
if bitset.extends is not None:
return self.resolve_simple_field(bitset.isa.bitsets[bitset.extends], name)
return None
template = """\
/* Copyright (C) 2020 Google, Inc.
*
@ -84,6 +137,22 @@ ${expr.get_c_name()}(struct decode_scope *scope)
}
%endfor
/* forward-declarations of bitset decode functions */
%for name, bitset in isa.all_bitsets():
% for df in s.decode_fields(bitset):
static void decode_${bitset.get_c_name()}_gen_${bitset.gen_min}_${df.get_c_name()}(void *out, struct decode_scope *scope, uint64_t val);
% endfor
static const struct isa_field_decode decode_${bitset.get_c_name()}_gen_${bitset.gen_min}_fields[] = {
% for df in s.decode_fields(bitset):
{
.name = "${df.name}",
.decode = decode_${bitset.get_c_name()}_gen_${bitset.gen_min}_${df.get_c_name()},
},
% endfor
};
static void decode_${bitset.get_c_name()}_gen_${bitset.gen_min}(void *out, struct decode_scope *scope);
%endfor
/*
* Forward-declarations (so we don't have to figure out which order to
* emit various tables when they have pointers to each other)
@ -169,6 +238,9 @@ static const struct isa_bitset bitset_${bitset.get_c_name()}_gen_${bitset.gen_mi
.match.bitset = { ${', '.join(isa.split_bits(pattern.match, 32))} },
.dontcare.bitset = { ${', '.join(isa.split_bits(pattern.dontcare, 32))} },
.mask.bitset = { ${', '.join(isa.split_bits(pattern.mask, 32))} },
.decode = decode_${bitset.get_c_name()}_gen_${bitset.gen_min},
.num_decode_fields = ARRAY_SIZE(decode_${bitset.get_c_name()}_gen_${bitset.gen_min}_fields),
.decode_fields = decode_${bitset.get_c_name()}_gen_${bitset.gen_min}_fields,
.num_cases = ${len(bitset.cases)},
.cases = {
% for case in bitset.cases:
@ -197,6 +269,37 @@ const struct isa_bitset *${root.get_c_name()}[] = {
#include "isaspec_decode_impl.c"
%for name, bitset in isa.all_bitsets():
% for df in s.decode_fields(bitset):
<% field = s.resolve_simple_field(bitset, df.name) %>
static void decode_${bitset.get_c_name()}_gen_${bitset.gen_min}_${df.get_c_name()}(void *out, struct decode_scope *scope, uint64_t val)
{
% if bitset.get_root().decode is not None and field is not None:
${bitset.get_root().encode.type} src = *(${bitset.get_root().encode.type} *)out;
% if field.get_c_typename() == 'TYPE_BITSET':
isa_decode_bitset(&${df.map_expr}, ${isa.roots[field.type].get_c_name()}, scope, uint64_t_to_bitmask(val));
% elif field.get_c_typename() in ['TYPE_BRANCH', 'TYPE_INT', 'TYPE_OFFSET']:
${df.map_expr} = util_sign_extend(val, ${field.get_size()});
% else:
${df.map_expr} = val;
% endif
*(${bitset.get_root().encode.type} *)out = src;
% endif
}
% endfor
static void decode_${bitset.get_c_name()}_gen_${bitset.gen_min}(void *out, struct decode_scope *scope)
{
% if bitset.get_root().decode is not None:
UNUSED ${bitset.get_root().encode.type} src;
% if bitset.get_root().encode.type.endswith('*') and name in isa.leafs and bitset.get_root().encode.case_prefix is not None:
src = ${bitset.get_root().get_c_name()}_create(${s.case_name(bitset.get_root(), bitset.name)});
*(${bitset.get_root().encode.type} *)out = src;
% endif
% endif
}
%endfor
"""
header = """\
@ -286,11 +389,12 @@ def main():
args = parser.parse_args()
isa = ISA(args.xml)
s = State(isa)
try:
with open(args.out_c, 'w') as f:
out_h_basename = os.path.basename(args.out_h)
f.write(Template(template).render(isa=isa, header=out_h_basename))
f.write(Template(template).render(isa=isa, s=s, header=out_h_basename))
with open(args.out_h, 'w') as f:
f.write(Template(header).render(isa=isa, guard=guard(args.out_h)))

View file

@ -242,6 +242,14 @@ class BitSetEncode(object):
if 'force' in map.attrib and map.attrib['force'] == 'true':
self.forced[name] = 'true'
class BitSetDecode(object):
"""Additional data that may be associated with a root bitset node
to provide additional information needed to generate helpers
to decode the bitset.
"""
def __init__(self, xml):
pass
class BitSet(object):
"""Class that encapsulates a single bitset rule
"""
@ -266,6 +274,9 @@ class BitSet(object):
self.encode = None
if xml.find('encode') is not None:
self.encode = BitSetEncode(xml.find('encode'))
self.decode = None
if xml.find('decode') is not None:
self.decode = BitSetDecode(xml.find('encode'))
self.gen_min = 0
self.gen_max = (1 << 32) - 1

View file

@ -123,6 +123,8 @@ struct isa_decode_options {
void isa_disasm(void *bin, int sz, FILE *out, const struct isa_decode_options *options);
bool isa_decode(void *out, void *bin, const struct isa_decode_options *options);
#ifdef __cplusplus
}
#endif

View file

@ -56,6 +56,12 @@ typedef uint64_t (*isa_expr_t)(struct decode_scope *scope);
*/
uint64_t isa_decode_field(struct decode_scope *scope, const char *field_name);
void isa_decode_bitset(void *out, const struct isa_bitset **bitsets, struct decode_scope *scope, bitmask_t val);
/**
* Used by generated decode functions
*/
/**
* For bitset fields, there are some cases where we want to "remap" field
* names, essentially allowing one to parameterize a nested bitset when
@ -69,6 +75,8 @@ struct isa_field_params {
} params[];
};
struct decode_scope;
/**
* Description of a single field within a bitset case.
*/
@ -132,6 +140,11 @@ struct isa_case {
struct isa_field fields[];
};
struct isa_field_decode {
const char *name;
void (*decode)(void *out, struct decode_scope *scope, uint64_t val);
};
/**
* An individual bitset, the leaves of a bitset inheritance hiearchy will
* have the match and mask to match a single instruction (or arbitrary
@ -147,6 +160,9 @@ struct isa_bitset {
bitmask_t match;
bitmask_t dontcare;
bitmask_t mask;
void (*decode)(void *out, struct decode_scope *scope);
unsigned num_decode_fields;
const struct isa_field_decode *decode_fields;
unsigned num_cases;
const struct isa_case *cases[];
};

View file

@ -840,6 +840,117 @@ disasm(struct decode_state *state, void *bin, int sz)
}
}
static void
decode_bitset_cb(void *out, struct decode_scope *scope, const struct isa_bitset *b)
{
while (b) {
b->decode(out, scope);
b = b->parent;
}
}
static void
decode_field(void *out, struct decode_scope *scope, const char *field_name)
{
const struct isa_bitset *bitset = scope->bitset;
size_t field_name_len = strlen(field_name);
/* alignment handling */
const char *align = strstr(field_name, ":align=");
if (align) {
field_name_len = align - field_name;
}
if (field_name == align)
return;
if (!strncmp("NAME", field_name, field_name_len))
return;
bitmask_t v;
const struct isa_field *field = resolve_field(scope, field_name, field_name_len, &v);
if (!field) {
decode_error(scope->state, "no field '%.*s'", (int)field_name_len, field_name);
return;
}
uint64_t val = bitmask_to_uint64_t(v);
for (unsigned i = 0; i < bitset->num_decode_fields; i++) {
if (!strncmp(bitset->decode_fields[i].name, field_name, field_name_len)) {
bitset->decode_fields[i].decode(out, scope, val);
return;
}
}
}
static void
decode_bitset(void *out, struct decode_scope *scope)
{
const struct isa_bitset *bitset = scope->bitset;
decode_bitset_cb(out, scope, bitset);
const char *display = find_display(scope, bitset);
if (!display) {
decode_error(scope->state, "%s: no display template", bitset->name);
return;
}
const char *p = display;
while (*p != '\0') {
if (*p == '{') {
const char *e = ++p;
while (*e != '}') {
e++;
}
char *field_name = strndup(p, e-p);
decode_field(out, scope, field_name);
free(field_name);
p = e;
}
p++;
}
}
void
isa_decode_bitset(void *out, const struct isa_bitset **bitsets, struct decode_scope *scope, bitmask_t val)
{
struct decode_state *state = scope->state;
const struct isa_bitset *b = find_bitset(state, bitsets, val);
if (!b)
return;
struct decode_scope *new_scope = push_scope(state, b, val);
decode_bitset(out, new_scope);
pop_scope(new_scope);
}
static bool
decode(void *out, struct decode_state *state, void *bin)
{
bitmask_t instr = { 0 };
next_instruction(&instr, bin);
const struct isa_bitset *b = find_bitset(state, __instruction, instr);
if (!b)
return false;
struct decode_scope *scope = push_scope(state, b, instr);
decode_bitset(out, scope);
pop_scope(scope);
return true;
}
static int
cmp_entrypoints(const void *_a, const void *_b)
{
@ -900,3 +1011,19 @@ isa_disasm(void *bin, int sz, FILE *out, const struct isa_decode_options *option
ralloc_free(state);
}
bool
isa_decode(void *out, void *bin, const struct isa_decode_options *options)
{
struct decode_state *state = rzalloc_size(NULL, sizeof(*state));
state->options = options;
bool result = decode(out, state, bin);
if (flush_errors(state)) {
return false;
}
ralloc_free(state);
return result;
}