From 038680c2f6273889f0454a701308f5813a983267 Mon Sep 17 00:00:00 2001 From: Connor Abbott Date: Mon, 26 Jun 2023 17:20:34 +0200 Subject: [PATCH] isaspec: Add initial decoding support This reuses the entries in the block to go in the reverse direction and parse an instruction into a machine-readable structure. It currently assumes that 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 block to allow us to override that for more complex cases. Part-of: --- src/compiler/isaspec/decode.py | 106 +++++++++++++++++++++++- src/compiler/isaspec/isa.py | 11 +++ src/compiler/isaspec/isaspec.h | 2 + src/compiler/isaspec/isaspec_decode_decl.h | 16 ++++ src/compiler/isaspec/isaspec_decode_impl.c | 127 +++++++++++++++++++++++++++++ 5 files changed, 261 insertions(+), 1 deletion(-) diff --git a/src/compiler/isaspec/decode.py b/src/compiler/isaspec/decode.py index 6e1a9d0..7b348bf 100755 --- a/src/compiler/isaspec/decode.py +++ b/src/compiler/isaspec/decode.py @@ -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 entries for a leaf bitset, with the child + # bitset overriding the parent bitset's entries. Because we can't resolve + # which 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))) diff --git a/src/compiler/isaspec/isa.py b/src/compiler/isaspec/isa.py index 23e392b..5b8a05d 100644 --- a/src/compiler/isaspec/isa.py +++ b/src/compiler/isaspec/isa.py @@ -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 diff --git a/src/compiler/isaspec/isaspec.h b/src/compiler/isaspec/isaspec.h index a4e7748..3af0e73 100644 --- a/src/compiler/isaspec/isaspec.h +++ b/src/compiler/isaspec/isaspec.h @@ -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 diff --git a/src/compiler/isaspec/isaspec_decode_decl.h b/src/compiler/isaspec/isaspec_decode_decl.h index 97f16fc..db1402b 100644 --- a/src/compiler/isaspec/isaspec_decode_decl.h +++ b/src/compiler/isaspec/isaspec_decode_decl.h @@ -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[]; }; diff --git a/src/compiler/isaspec/isaspec_decode_impl.c b/src/compiler/isaspec/isaspec_decode_impl.c index 11db9f4..32fc07b 100644 --- a/src/compiler/isaspec/isaspec_decode_impl.c +++ b/src/compiler/isaspec/isaspec_decode_impl.c @@ -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; +} -- 2.7.4