From dc874e465473e8bbc738e1684b9508b4ccb5907b Mon Sep 17 00:00:00 2001 From: Connor Abbott Date: Thu, 22 Jun 2023 14:13:25 +0200 Subject: [PATCH] isaspec: Add support for function and entrypoint labels Functions (i.e. labels reached from call instructions) should be printed differently from normal labels. In addition we also need to add support for entrypoints with user-defined names in order to show packet names in afuc. Part-of: --- docs/drivers/freedreno/isaspec.rst | 24 ++++++ src/compiler/isaspec/.editorconfig | 4 + src/compiler/isaspec/decode.py | 3 + src/compiler/isaspec/isa.py | 2 + src/compiler/isaspec/isaspec.h | 11 +++ src/compiler/isaspec/isaspec_decode_decl.h | 1 + src/compiler/isaspec/isaspec_decode_impl.c | 96 ++++++++++++++++++++-- 7 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 src/compiler/isaspec/.editorconfig diff --git a/docs/drivers/freedreno/isaspec.rst b/docs/drivers/freedreno/isaspec.rst index e512d2f657b..34e01e7fb3a 100644 --- a/docs/drivers/freedreno/isaspec.rst +++ b/docs/drivers/freedreno/isaspec.rst @@ -276,6 +276,30 @@ This would produce a disassembly like ``jump #l42`` if the destination is 42 instructions after the start of the disassembly. The destination would be preceded by a line with just ``l42:``. +``branch`` and ``absbranch`` fields can additionally have a ``call="true"`` +attribute. For now, this just changes the disassembly. In particular the label +prefix is changed to ``fxn`` and an extra empty line before the destination is +added to visually seperate the disassembly into functions. So, for example, a +call instruction defined like this: + +.. code-block:: xml + + + + call #{OFFSET} + + 110010 + + + +will disassemble to ``call #fxn42``. + +Finally, users with special knowledge about where execution may start can define +"entrypoints" when disassembling which are printed like function call +destinations, with an extra empty line, but with an arbitrary user-defined +name. Names that are ``fxn`` or ``l`` followed by a number are discouraged +because they may clash with automatically-generated names. + Encoding -------- diff --git a/src/compiler/isaspec/.editorconfig b/src/compiler/isaspec/.editorconfig new file mode 100644 index 00000000000..321b4353ca2 --- /dev/null +++ b/src/compiler/isaspec/.editorconfig @@ -0,0 +1,4 @@ +[*.{c,h,cpp,hpp,cc,hh,y,yy}] +indent_style = tab +indent_size = 8 +max_line_length = 78 diff --git a/src/compiler/isaspec/decode.py b/src/compiler/isaspec/decode.py index 9e05fa00701..6e1a9d05739 100755 --- a/src/compiler/isaspec/decode.py +++ b/src/compiler/isaspec/decode.py @@ -147,6 +147,9 @@ static const struct isa_case ${case.get_c_name()}_gen_${bitset.gen_min} = { % endif % if field.get_c_typename() == 'TYPE_ASSERT': .val.bitset = { ${', '.join(isa.split_bits(field.val, 32))} }, +% endif +% if field.get_c_typename() == 'TYPE_BRANCH' or field.get_c_typename() == 'TYPE_ABSBRANCH': + .call = ${str(field.call).lower()}, % endif }, % endfor diff --git a/src/compiler/isaspec/isa.py b/src/compiler/isaspec/isa.py index 3e70c342985..501cb6d0be8 100644 --- a/src/compiler/isaspec/isa.py +++ b/src/compiler/isaspec/isa.py @@ -114,6 +114,7 @@ class BitSetField(object): self.params.append([name, aas]) self.expr = None self.display = None + self.call = 'call' in xml.attrib and xml.attrib['call'] == 'true' if 'display' in xml.attrib: self.display = xml.attrib['display'].strip() @@ -176,6 +177,7 @@ class BitSetDerivedField(BitSetField): self.display = None if 'display' in xml.attrib: self.display = xml.attrib['display'].strip() + self.call = 'call' in xml.attrib and xml.attrib['call'] == 'true' class BitSetCase(object): """Class that encapsulates a single bitset case diff --git a/src/compiler/isaspec/isaspec.h b/src/compiler/isaspec/isaspec.h index 2867a0e7a37..fdfc28117d3 100644 --- a/src/compiler/isaspec/isaspec.h +++ b/src/compiler/isaspec/isaspec.h @@ -46,6 +46,11 @@ struct isa_decode_hook { void (*cb)(void *data, struct isa_decode_value *val); }; +struct isa_entrypoint { + const char *name; + uint32_t offset; +}; + struct isa_decode_options { uint32_t gpu_id; @@ -87,6 +92,12 @@ struct isa_decode_options { * callback for undefined instructions */ void (*no_match_cb)(FILE *out, const BITSET_WORD *bitset, size_t size); + + /** + * List of known entrypoints to treat like call targets + */ + unsigned entrypoint_count; + const struct isa_entrypoint *entrypoints; }; void isa_decode(void *bin, int sz, FILE *out, const struct isa_decode_options *options); diff --git a/src/compiler/isaspec/isaspec_decode_decl.h b/src/compiler/isaspec/isaspec_decode_decl.h index 8ae61a79e4f..93367f17edf 100644 --- a/src/compiler/isaspec/isaspec_decode_decl.h +++ b/src/compiler/isaspec/isaspec_decode_decl.h @@ -104,6 +104,7 @@ struct isa_field { bitmask_t val; /* if type==ASSERT */ const struct isa_enum *enums; /* if type==ENUM */ const char *display; /* if type==BOOL */ + bool call; /* if type==(BRANCH|ABSBRANCH) */ }; /** diff --git a/src/compiler/isaspec/isaspec_decode_impl.c b/src/compiler/isaspec/isaspec_decode_impl.c index b5e8d54b8a2..3b2c59a4111 100644 --- a/src/compiler/isaspec/isaspec_decode_impl.c +++ b/src/compiler/isaspec/isaspec_decode_impl.c @@ -141,6 +141,16 @@ struct decode_state { */ BITSET_WORD *branch_targets; + /** + * Bitset of instructions that are call targets. + */ + BITSET_WORD *call_targets; + + /** + * Bitset of instructions that are entrypoints. + */ + BITSET_WORD *entrypoints; + /** * We allow a limited amount of expression evaluation recursion, but * not recursive evaluation of any given expression, to prevent infinite @@ -156,6 +166,12 @@ struct decode_state { */ struct decode_scope *scope; + /* Next entrypoint to be decoded. */ + struct isa_entrypoint *next_entrypoint; + + /* Sentinel value after the last entrypoint in the array. */ + struct isa_entrypoint *end_entrypoint; + /** * A small fixed upper limit on # of decode errors to capture per- * instruction seems reasonable. @@ -619,8 +635,13 @@ display_field(struct decode_scope *scope, const char *field_name) offset = val; } if (offset < scope->state->num_instr) { - print(scope->state, "l%d", offset); - BITSET_SET(scope->state->branch_targets, offset); + if (field->call) { + print(scope->state, "fxn%d", offset); + BITSET_SET(scope->state->call_targets, offset); + } else { + print(scope->state, "l%d", offset); + BITSET_SET(scope->state->branch_targets, offset); + } break; } } @@ -732,13 +753,48 @@ decode(struct decode_state *state, void *bin, int sz) break; } - if (state->options->branch_labels && - BITSET_TEST(state->branch_targets, state->n)) { - if (state->options->instr_cb) { - state->options->instr_cb(state->options->cbdata, - state->n, instr.bitset); + if (state->options->branch_labels) { + bool entrypoint = state->next_entrypoint != + state->end_entrypoint && + state->next_entrypoint->offset == state->n; + + /* Print an extra empty line before functions and + * entrypoints to more clearly separate them. + */ + if ((BITSET_TEST(state->call_targets, state->n) || entrypoint) && + state->n != 0) { + if (state->options->instr_cb) { + state->options->instr_cb(state->options->cbdata, + state->n, instr.bitset); + } + print(state, "\n"); + } + + while (state->next_entrypoint != state->end_entrypoint && + state->next_entrypoint->offset == state->n) { + if (state->options->instr_cb) { + state->options->instr_cb(state->options->cbdata, + state->n, instr.bitset); + } + print(state, "%s:\n", state->next_entrypoint->name); + state->next_entrypoint++; + } + + if (BITSET_TEST(state->call_targets, state->n)) { + if (state->options->instr_cb) { + state->options->instr_cb(state->options->cbdata, + state->n, instr.bitset); + } + print(state, "fxn%d:\n", state->n); + } + + if (BITSET_TEST(state->branch_targets, state->n)) { + if (state->options->instr_cb) { + state->options->instr_cb(state->options->cbdata, + state->n, instr.bitset); + } + print(state, "l%d:\n", state->n); } - print(state, "l%d:\n", state->n); } if (state->options->instr_cb) { @@ -774,6 +830,13 @@ decode(struct decode_state *state, void *bin, int sz) } } +static int +cmp_entrypoints(const void *_a, const void *_b) +{ + const struct isa_entrypoint *a = _a, *b = _b; + return (int)a->offset - (int)b->offset; +} + void isa_decode(void *bin, int sz, FILE *out, const struct isa_decode_options *options) { @@ -793,6 +856,8 @@ isa_decode(void *bin, int sz, FILE *out, const struct isa_decode_options *option if (state->options->branch_labels) { state->branch_targets = rzalloc_size(state, sizeof(BITSET_WORD) * BITSET_WORDS(state->num_instr)); + state->call_targets = rzalloc_size(state, + sizeof(BITSET_WORD) * BITSET_WORDS(state->num_instr)); /* Do a pre-pass to find all the branch targets: */ state->out = fopen("/dev/null", "w"); @@ -802,6 +867,21 @@ isa_decode(void *bin, int sz, FILE *out, const struct isa_decode_options *option if (options) { state->options = options; } + + /* Sort the entrypoints by offset and initialize entrypoint + * state. + */ + if (options->entrypoint_count) { + struct isa_entrypoint *entrypoints = + ralloc_array(state, struct isa_entrypoint, + options->entrypoint_count); + memcpy(entrypoints, options->entrypoints, + options->entrypoint_count * sizeof(*entrypoints)); + qsort(entrypoints, options->entrypoint_count, + sizeof(*entrypoints), cmp_entrypoints); + state->next_entrypoint = entrypoints; + state->end_entrypoint = entrypoints + options->entrypoint_count; + } } state->out = out;