diff --git a/docs/drivers/freedreno/isaspec.rst b/docs/drivers/freedreno/isaspec.rst new file mode 100644 index 00000000000..df67c71effb --- /dev/null +++ b/docs/drivers/freedreno/isaspec.rst @@ -0,0 +1,279 @@ +ISASPEC - XML Based ISA Specification +===================================== + +isaspec provides a mechanism to describe an instruction set in xml, and +generate a disassembler and assembler (eventually). The intention is +to describe the instruction set more formally than hand-coded assembler +and disassembler, and better decouple the shader compiler from the +underlying instruction encoding to simplify dealing with instruction +encoding differences between generations of GPU. + +Benefits of a formal ISA description, compared to hand-coded assemblers +and disassemblers, include easier detection of new bit combintions that +were not seen before in previous generations due to more rigerous +description of bits that are expect to be '0' or '1' or 'x' (dontcare) +and verification that different encodings don't have conflicting bits +(ie. that the specification cannot result in more than one valid +interpretation of any bit pattern). + +The isaspec tool and xml schema are intended to be generic (not specific +to ir3), although there are currently a couple limitations due to short- +cuts taken to get things up and running (which are mostly not inherent to +the xml schema, and should not be too difficult to remove from the py and +decode/disasm utility): + +* Maximum "bitset" size is 64b +* Fixed instruction size + +Often times, especially when new functionality is added in later gens +while retaining (or at least mostly retaining) backwards compatibility +with encodings used in earlier generations, the actual encoding can be +rather messy to describe. To support this, isaspec provides many flexible +mechanism, such as conditional overrides and derived fields. This not +only allows for describing an irregular instruction encoding, but also +allows matching an existing disasm syntax (which might not have been +design around the idea of disassembly based on a formal ISA description). + +Bitsets +------- + +The fundamental concept of matching a bit-pattern to an instruction +decoding/encoding is the concept of a hierarchial tree of bitsets. +This is intended to match how the hw decodes instructions, where certain +bits describe the instruction (and sub-encoding, and so on), and other +bits describe various operands to the instruction. + +Bitsets can also be used recursively as the type of a field described +in another bitset. + +The leaves of the tree of instruction bitsets represent every possible +instruction. Deciding which instruction a bitpattern is amounts to: + +.. code-block:: c + + m = (val & bitsets[n]->mask) & ~bitsets[n]->dontcare; + + if (m == bitsets[n]->match) { + ... we've found the instruction description ... + } + +For example, the starting point to decode an ir3 instruction is a 64b +bitset: + +.. code-block:: xml + + + + Encoding of an ir3 instruction. All instructions are 64b. + + + +In the first level of instruction encoding hierarchy, the high three bits +group things into instruction "categories": + +.. code-block:: xml + + + + + + + + + + Destination register is opposite precision as source, ie. + if {FULL} is true then destination is half precision, and + visa versa. + + + + + + Full precision source registers + + + + 010 + + + + + + +The ```` elements are the part(s) that determine which leaf-node +bitset matches against a given bit pattern. The leaf node's match/mask/ +dontcare bitmasks are a combination of those defined at the leaf node and +recursively each parent bitclass. + +For example, cat2 instructions (ALU instructions with up to two src +registers) can have either one or two source registers: + +.. code-block:: xml + + + + + {SY}{SS}{JP}{SAT}(nop{NOP}) {UL}{NAME} {EI}{DST_HALF}{DST}, {SRC1} + + + + + + + + + {SY}{SS}{JP}{SAT}{REPEAT}{UL}{NAME} {EI}{DST_HALF}{DST}, {SRC1} + + xxxxxxxxxxxxxxxx + xxx + + + + + + + + 000110 + + +In this example, ``absneg.f`` is a concrete cat2 instruction (leaf node of +the bitset inheritance tree) which has a single src register. At the +``#instruction-cat2-1src`` level, bits that are used for the 2nd src arg +and condition code (for cat2 instructions which use a condition code) are +defined as 'x' (dontcare), which matches our understanding of the hardware +(but also lets the disassembler flag cases where '1' bits show up in places +we don't expect, which may signal a new instruction (sub)encoding). + +You'll notice that ``SRC1`` refers back to a different bitset hierarchy +that describes various different src register encoding (used for cat2 and +cat4 instructions), ie. GPR vs CONST vs relative GPR/CONST. For fields +which have bitset types, parameters can be "passed" in via ```` +elements, which can be referred to by the display template string, and/or +expressions. For example, this helps to deal with cases where other fields +outside of that bitset control the encoding/decoding, such as in the +``#multisrc`` example: + +.. code-block:: xml + + + + Encoding for instruction source which can be GPR/CONST/IMMED + or relative GPR/CONST. + + + + ... + + + + {ABSNEG}{SRC_R}{HALF}{SRC} + + + + 000000 + + + +At some level in the bitset inheritance hiearchy, there is expected to be a +```` element specifying a template string used during bitset +decoding. The display template consists of references to fields (which may +be derived fields) specified as ``{FIELDNAME}`` and other characters +which are just echoed through to the resulting decoded bitset. + +The ```` element will be described in the next section, but it +provides for both different decoded instruction syntax/mnemonics (when +simply providing a different display template string) as well as instruction +encoding where different ranges of bits have a different meaning based on +some other bitfield (or combination of bitfields). In this example it is +used to cover the cases where ``SRCn_R`` has a different meaning and a +different disassembly syntax depending on whether ``REPEAT`` equals zero. + +Overrides +--------- + +In many cases, a bitset is not convenient for describing the expected +disasm syntax, and/or interpretation of some range of bits differs based +on some other field or combination of fields. These *could* be modeled +as different derived bitsets, at the expense of a combinatorical explosion +of the size of the bitset inheritance tree. For example, *every* cat2 +(and cat3) instruction has both a ``(nopN)`` interpretation in addtion to +the ``(rptN`)`` interpretation. + +An ```` in a bitset allows to redefine the display string, and/or +field definitions from the default case. If the override's expr(ession) +evaluates to non-zero, ````, ````, and ```` +elements take precedence over what is defined in the toplevel of the +bitset (ie. the default case). + +Expressions +----------- + +Both ```` and ```` fields make use of ```` elements, +either defined inline, or defined and named at the top level and referred to +by name in multiple other places. An expression is a simple 'C' expression +which can reference fields (including other derived fields) with the same +``{FIELDNAME}`` syntax as display template strings. For example: + +.. code-block:: xml + + + (({SRC1_R} != 0) || ({SRC2_R} != 0)) && ({REPEAT} == 0) + + +In the case of ```` elements, the override applies if the expression +evaluates to non-zero. In the case of ```` fields, the expression +evaluates to the value of the derived field. + +Encoding +-------- + +To facilitate instruction encoding, ```` elements can be provided +to teach the generated instruction packing code how to map from data structures +representing the IR to fields. For example: + +.. code-block:: xml + + + + Encoding of an ir3 instruction. All instructions are 64b. + + + + + src->regs[0] + src->regs[1] + src->regs[2] + src->regs[3] + src->repeat + !!(src->flags & IR3_INSTR_SS) + !!(src->flags & IR3_INSTR_JP) + !!(src->flags & IR3_INSTR_SY) + !!(src->flags & IR3_INSTR_UL) + 0 + !!(src->flags & IR3_INSTR_SAT) + + + +The ``type`` attribute specifies that the input to encoding an instruction +is a ``struct ir3_instruction *``. In the case of bitset hierarchies with +multiple possible leaf nodes, a ``case-prefix`` attribute should be supplied +along with a function that maps the bitset encode source to an enum value +with the specified prefix prepended to uppercase'd leaf node name. Ie. in +this case, "add.f" becomes ``OPC_ADD_F``. + +Individual ```` elements teach the encoder how to map from the encode +source to fields in the encoded instruction. \ No newline at end of file diff --git a/src/freedreno/isa/README.rst b/src/freedreno/isa/README.rst new file mode 120000 index 00000000000..27379790c02 --- /dev/null +++ b/src/freedreno/isa/README.rst @@ -0,0 +1 @@ +../../../docs/drivers/freedreno/isaspec.rst \ No newline at end of file diff --git a/src/freedreno/isa/decode.c b/src/freedreno/isa/decode.c new file mode 100644 index 00000000000..92d2664ee09 --- /dev/null +++ b/src/freedreno/isa/decode.c @@ -0,0 +1,676 @@ +/* + * Copyright © 2020 Google, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "util/bitset.h" +#include "util/compiler.h" +#include "util/half_float.h" +#include "util/ralloc.h" +#include "util/u_debug.h" +#include "util/u_math.h" + +#include "decode.h" +#include "isa.h" + +/** + * The set of leaf node bitsets in the bitset hiearchy which defines all + * the possible instructions. + * + * TODO maybe we want to pass this in as parameter so this same decoder + * can work with multiple different instruction sets. + */ +extern const struct isa_bitset *__instruction[]; + +struct decode_state; + +/** + * Decode scope. When parsing a field that is itself a bitset, we push a + * new scope to the stack. A nested bitset is allowed to resolve fields + * from an enclosing scope (needed, for example, to decode src register + * bitsets, where half/fullness is determined by fields outset if bitset + * in the instruction containing the bitset. + * + * But the field being resolved could be a derived field, or different + * depending on an override at a higher level of the stack, requiring + * expression evaluation which could in turn reference variables which + * triggers a recursive field lookup. But those lookups should not start + * from the top of the stack, but instead the current stack level. This + * prevents a field from accidentally resolving to different values + * depending on the starting point of the lookup. (Not only causing + * confusion, but this is behavior we don't want to depend on if we + * wanted to optimize things by caching field lookup results.) + */ +struct decode_scope { + /** + * Enclosing scope + */ + struct decode_scope *parent; + + /** + * Current bitset value being decoded + */ + uint64_t val; + + /** + * Current bitset. + */ + const struct isa_bitset *bitset; + + /** + * Field name remapping. + */ + const struct isa_field_params *params; + + /** + * Pointer back to decode state, for convenience. + */ + struct decode_state *state; +}; + +/** + * Current decode state + */ +struct decode_state { + const struct isa_decode_options *options; + FILE *out; + + /** + * Current instruction being decoded: + */ + unsigned n; + + /** + * Number of instructions being decoded + */ + unsigned num_instr; + + /** + * Bitset of instructions that are branch targets (if options->branch_labels + * is enabled) + */ + BITSET_WORD *branch_targets; + + /** + * We allow a limited amount of expression evaluation recursion, but + * not recursive evaluation of any given expression, to prevent infinite + * recursion. + */ + int expr_sp; + isa_expr_t expr_stack[8]; + + /** + * Current topmost/innermost level of scope used for decoding fields, + * including derived fields which may in turn rely on decoding other + * fields, potentially from a lower/out level in the stack. + */ + struct decode_scope *scope; + + /** + * A small fixed upper limit on # of decode errors to capture per- + * instruction seems reasonable. + */ + unsigned num_errors; + char *errors[4]; +}; + +static void display(struct decode_scope *scope); +static void decode_error(struct decode_state *state, const char *fmt, ...) _util_printf_format(2,3); + +static void +decode_error(struct decode_state *state, const char *fmt, ...) +{ + if (!state->options->show_errors) { + return; + } + + if (state->num_errors == ARRAY_SIZE(state->errors)) { + /* too many errors, bail */ + return; + } + + va_list ap; + va_start(ap, fmt); + vasprintf(&state->errors[state->num_errors++], fmt, ap); + va_end(ap); +} + +static unsigned +flush_errors(struct decode_state *state) +{ + unsigned num_errors = state->num_errors; + if (num_errors > 0) + fprintf(state->out, "\t; "); + for (unsigned i = 0; i < num_errors; i++) { + fprintf(state->out, "%s%s", (i > 0) ? ", " : "", state->errors[i]); + free(state->errors[i]); + } + state->num_errors = 0; + return num_errors; +} + + +static bool +push_expr(struct decode_state *state, isa_expr_t expr) +{ + for (int i = state->expr_sp - 1; i > 0; i--) { + if (state->expr_stack[i] == expr) { + return false; + } + } + state->expr_stack[state->expr_sp++] = expr; + return true; +} + +static void +pop_expr(struct decode_state *state) +{ + assert(state->expr_sp > 0); + state->expr_sp--; +} + +static struct decode_scope * +push_scope(struct decode_state *state, const struct isa_bitset *bitset, uint64_t val) +{ + struct decode_scope *scope = rzalloc_size(state, sizeof(*scope)); + + scope->val = val; + scope->bitset = bitset; + scope->parent = state->scope; + scope->state = state; + + state->scope = scope; + + return scope; +} + +static void +pop_scope(struct decode_scope *scope) +{ + assert(scope->state->scope == scope); /* must be top of stack */ + + scope->state->scope = scope->parent; + ralloc_free(scope); +} + +/** + * Evaluate an expression, returning it's resulting value + */ +static uint64_t +evaluate_expr(struct decode_scope *scope, isa_expr_t expr) +{ + if (!push_expr(scope->state, expr)) + return 0; + + uint64_t ret = expr(scope); + + pop_expr(scope->state); + + return ret; +} + +/** + * Find the bitset in NULL terminated bitset hiearchy root table which + * matches against 'val' + */ +static const struct isa_bitset * +find_bitset(struct decode_state *state, const struct isa_bitset **bitsets, + uint64_t val) +{ + const struct isa_bitset *match = NULL; + for (int n = 0; bitsets[n]; n++) { + if (state->options->gpu_id > bitsets[n]->gen.max) + continue; + if (state->options->gpu_id < bitsets[n]->gen.min) + continue; + + uint64_t m = (val & bitsets[n]->mask) & ~bitsets[n]->dontcare; + + if (m != bitsets[n]->match) { + continue; + } + + /* We should only have exactly one match + * + * TODO more complete/formal way to validate that any given + * bit pattern will only have a single match? + */ + if (match) { + decode_error(state, "bitset conflict: %s vs %s", match->name, + bitsets[n]->name); + return NULL; + } + + match = bitsets[n]; + } + + if (match && (match->dontcare & val)) { + decode_error(state, "dontcare bits in %s: %"PRIx64, + match->name, (match->dontcare & val)); + } + + return match; +} + +static const struct isa_field * +find_field(struct decode_scope *scope, const struct isa_bitset *bitset, + const char *name) +{ + for (unsigned i = 0; i < bitset->num_cases; i++) { + const struct isa_case *c = bitset->cases[i]; + + if (c->expr) { + struct decode_state *state = scope->state; + + /* When resolving a field for evaluating an expression, + * temporarily assume the expression evaluates to true. + * This allows 's to speculatively refer to + * fields defined within the override: + */ + isa_expr_t cur_expr = NULL; + if (state->expr_sp > 0) + cur_expr = state->expr_stack[state->expr_sp - 1]; + if ((cur_expr != c->expr) && !evaluate_expr(scope, c->expr)) + continue; + } + + for (unsigned i = 0; i < c->num_fields; i++) { + if (!strcmp(name, c->fields[i].name)) { + return &c->fields[i]; + } + } + } + + if (bitset->parent) { + const struct isa_field *f = find_field(scope, bitset->parent, name); + if (f) { + return f; + } + } + + return NULL; +} + +static uint64_t +extract_field(struct decode_scope *scope, const struct isa_field *field) +{ + uint64_t val = scope->val; + val = (val >> field->low) & ((1ul << (1 + field->high - field->low)) - 1); + return val; +} + +/** + * Find the display template for a given bitset, recursively searching + * parents in the bitset hierarchy. + */ +static const char * +find_display(struct decode_scope *scope, const struct isa_bitset *bitset) +{ + for (unsigned i = 0; i < bitset->num_cases; i++) { + const struct isa_case *c = bitset->cases[i]; + if (c->expr && !evaluate_expr(scope, c->expr)) + continue; + /* since this is the chosen case, it seems like a good place + * to check asserted bits: + */ + for (unsigned j = 0; j < c->num_fields; j++) { + if (c->fields[j].type == TYPE_ASSERT) { + const struct isa_field *f = &c->fields[j]; + uint64_t val = extract_field(scope, f); + if (val != f->val) { + decode_error(scope->state, "WARNING: unexpected " + "bits[%u:%u] in %s: 0x%"PRIx64" vs 0x%"PRIx64, + f->low, f->high, bitset->name, + val, f->val); + } + } + } + if (!c->display) + continue; + return c->display; + } + + /** + * If we didn't find something check up the bitset hierarchy. + */ + if (bitset->parent) { + return find_display(scope, bitset->parent); + } + + return NULL; +} + +/** + * Decode a field that is itself another bitset type + */ +static void +display_bitset_field(struct decode_scope *scope, const struct isa_field *field, uint64_t val) +{ + const struct isa_bitset *b = find_bitset(scope->state, field->bitsets, val); + if (!b) { + decode_error(scope->state, "no match: FIELD: '%s.%s': 0x%"PRIx64, + scope->bitset->name, field->name, val); + return; + } + + struct decode_scope *nested_scope = + push_scope(scope->state, b, val); + nested_scope->params = field->params; + display(nested_scope); + pop_scope(nested_scope); +} + +static void +display_enum_field(struct decode_scope *scope, const struct isa_field *field, uint64_t val) +{ + FILE *out = scope->state->out; + + const struct isa_enum *e = field->enums; + for (unsigned i = 0; i < e->num_values; i++) { + if (e->values[i].val == val) { + fprintf(out, "%s", e->values[i].display); + return; + } + } + + fprintf(out, "%u", (unsigned)val); +} + +static const struct isa_field * +resolve_field(struct decode_scope *scope, const char *field_name, uint64_t *valp) +{ + if (!scope) { + /* We've reached the bottom of the stack! */ + return NULL; + } + + const struct isa_field *field = + find_field(scope, scope->bitset, field_name); + + if (!field && scope->params) { + for (unsigned i = 0; i < scope->params->num_params; i++) { + if (!strcmp(field_name, scope->params->params[i].as)) { + const char *param_name = scope->params->params[i].name; + return resolve_field(scope->parent, param_name, valp); + } + } + } + + if (!field) { + return NULL; + } + + /* extract out raw field value: */ + if (field->expr) { + *valp = evaluate_expr(scope, field->expr); + } else { + *valp = extract_field(scope, field); + } + + return field; +} + +/* This is also used from generated expr functions */ +uint64_t +isa_decode_field(struct decode_scope *scope, const char *field_name) +{ + uint64_t val; + const struct isa_field *field = resolve_field(scope, field_name, &val); + if (!field) { + decode_error(scope->state, "no field '%s'", field_name); + return 0; + } + + return val; +} + +static int64_t +sign_extend(uint64_t val, unsigned width) +{ + assert(width > 0); + if (val & (UINT64_C(1) << (width - 1))) { + return -(int64_t)((UINT64_C(1) << width) - val); + } else { + return val; + } +} + +static void +display_field(struct decode_scope *scope, const char *field_name) +{ + const struct isa_decode_options *options = scope->state->options; + + /* Special case 'NAME' maps to instruction/bitset name: */ + if (!strcmp("NAME", field_name)) { + if (options->field_cb) { + options->field_cb(options->cbdata, field_name, &(struct isa_decode_value){ + .str = scope->bitset->name, + }); + } + + fprintf(scope->state->out, "%s", scope->bitset->name); + + return; + } + + uint64_t val; + const struct isa_field *field = resolve_field(scope, field_name, &val); + if (!field) { + decode_error(scope->state, "no field '%s'", field_name); + return; + } + + if (options->field_cb) { + options->field_cb(options->cbdata, field_name, &(struct isa_decode_value){ + .num = val, + }); + } + + unsigned width = 1 + field->high - field->low; + FILE *out = scope->state->out; + + switch (field->type) { + /* Basic types: */ + case TYPE_BRANCH: + if (scope->state->options->branch_labels) { + int offset = sign_extend(val, width) + scope->state->n; + if (offset < scope->state->num_instr) { + fprintf(out, "l%d", offset); + BITSET_SET(scope->state->branch_targets, offset); + break; + } + } + FALLTHROUGH; + case TYPE_INT: + fprintf(out, "%"PRId64, sign_extend(val, width)); + break; + case TYPE_UINT: + fprintf(out, "%"PRIu64, val); + break; + case TYPE_HEX: + // TODO format # of digits based on field width? + fprintf(out, "%"PRIx64, val); + break; + case TYPE_OFFSET: + if (val != 0) { + fprintf(out, "%+"PRId64, sign_extend(val, width)); + } + break; + case TYPE_FLOAT: + if (width == 16) { + fprintf(out, "%f", _mesa_half_to_float(val)); + } else { + assert(width == 32); + fprintf(out, "%f", uif(val)); + } + break; + case TYPE_BOOL: + if (field->display) { + if (val) { + fprintf(out, "%s", field->display); + } + } else { + fprintf(out, "%u", (unsigned)val); + } + break; + case TYPE_ENUM: + display_enum_field(scope, field, val); + break; + + case TYPE_ASSERT: + /* assert fields are not for display */ + assert(0); + break; + + /* For fields that are decoded with another bitset hierarchy: */ + case TYPE_BITSET: + display_bitset_field(scope, field, val); + break; + default: + decode_error(scope->state, "Bad field type: %d (%s)", + field->type, field->name); + } +} + +static void +display(struct decode_scope *scope) +{ + const struct isa_bitset *bitset = 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); + display_field(scope, field_name); + free(field_name); + + p = e; + } else { + fputc(*p, scope->state->out); + } + p++; + } +} + +static void +decode(struct decode_state *state, void *bin, int sz) +{ + uint64_t *instrs = bin; + unsigned errors = 0; /* number of consecutive unmatched instructions */ + + for (state->n = 0; state->n < state->num_instr; state->n++) { + uint64_t instr = instrs[state->n]; + + if (state->options->max_errors && (errors > state->options->max_errors)) { + 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); + } + fprintf(state->out, "l%d:\n", state->n); + } + + if (state->options->instr_cb) { + state->options->instr_cb(state->options->cbdata, state->n, instr); + } + + const struct isa_bitset *b = find_bitset(state, __instruction, instr); + if (!b) { + fprintf(state->out, "no match: %016"PRIx64"\n", instr); + errors++; + continue; + } + + struct decode_scope *scope = push_scope(state, b, instr); + + display(scope); + if (flush_errors(state)) { + errors++; + } else { + errors = 0; + } + fprintf(state->out, "\n"); + + pop_scope(scope); + + if (state->options->stop) { + break; + } + } +} + +void +isa_decode(void *bin, int sz, FILE *out, const struct isa_decode_options *options) +{ + static const struct isa_decode_options default_options = {}; + struct decode_state *state; + + if (!options) + options = &default_options; + + state = rzalloc_size(NULL, sizeof(*state)); + state->options = options; + state->num_instr = sz / 8; + + if (state->options->branch_labels) { + state->branch_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"); + state->options = &default_options; /* skip hooks for prepass */ + decode(state, bin, sz); + fclose(state->out); + if (options) { + state->options = options; + } + } + + state->out = out; + + decode(state, bin, sz); + + ralloc_free(state); +} diff --git a/src/freedreno/isa/decode.h b/src/freedreno/isa/decode.h new file mode 100644 index 00000000000..f9c8fcb5694 --- /dev/null +++ b/src/freedreno/isa/decode.h @@ -0,0 +1,154 @@ +/* + * Copyright © 2020 Google, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _DECODE_H_ +#define _DECODE_H_ + +#include +#include + +/* + * Defines the tables which are generated from xml for disassembly + */ + +struct decode_scope; + +/* TODO we could maybe make this a uint8_t array, with some helpers, to + * support arbitrary sized patterns.. or add AND/OR/SHIFT support to + * util/bitset.h? + */ +typedef uint64_t bitmask_t; + +struct isa_bitset; + +/** + * Table of enum values + */ +struct isa_enum { + unsigned num_values; + struct { + unsigned val; + const char *display; + } values[]; +}; + +/** + * An expression used to for conditional overrides, derived fields, etc + */ +typedef uint64_t (*isa_expr_t)(struct decode_scope *scope); + +/** + * Used by generated expr functions + */ +uint64_t isa_decode_field(struct decode_scope *scope, const char *field_name); + +/** + * For bitset fields, there are some cases where we want to "remap" field + * names, essentially allowing one to parameterize a nested bitset when + * it resolves fields in an enclosing bitset. + */ +struct isa_field_params { + unsigned num_params; + struct { + const char *name; + const char *as; + } params[]; +}; + +/** + * Description of a single field within a bitset case. + */ +struct isa_field { + const char *name; + isa_expr_t expr; /* for virtual "derived" fields */ + unsigned low; + unsigned high; + enum { + /* Basic types: */ + TYPE_BRANCH, /* branch target, like INT but optional labeling*/ + TYPE_INT, + TYPE_UINT, + TYPE_HEX, + TYPE_OFFSET, /* Like INT but formated with +/- or ommited if ==0 */ + TYPE_FLOAT, + TYPE_BOOL, + TYPE_ENUM, + + /* To assert a certain value in a given range of bits.. not + * used for pattern matching, but allows an override to specify + * that a certain bitpattern in some "unused" bits is expected + */ + TYPE_ASSERT, + + /* For fields that are decoded with another bitset hierarchy: */ + TYPE_BITSET, + } type; + union { + const struct isa_bitset **bitsets; /* if type==BITSET */ + uint64_t val; /* if type==ASSERT */ + const struct isa_enum *enums; /* if type==ENUM */ + const char *display; /* if type==BOOL */ + }; + + /** + * type==BITSET fields can also optionally provide remapping for + * field names + */ + const struct isa_field_params *params; +}; + +/** + * A bitset consists of N "cases", with the last one (with case->expr==NULL) + * being the default. + * + * When resolving a field, display template string, etc, all the cases with + * an expression that evaluates to non-zero are consider, falling back to + * the last (default) case. + */ +struct isa_case { + isa_expr_t expr; + const char *display; + unsigned num_fields; + struct isa_field fields[]; +}; + +/** + * An individual bitset, the leaves of a bitset inheritance hiearchy will + * have the match and mask to match a single instruction (or arbitrary + * bit-pattern) against. + */ +struct isa_bitset { + const struct isa_bitset *parent; + const char *name; + struct { + unsigned min; + unsigned max; + } gen; + bitmask_t match; + bitmask_t dontcare; + bitmask_t mask; + unsigned num_cases; + const struct isa_case *cases[]; +}; + +#endif /* _DECODE_H_ */ diff --git a/src/freedreno/isa/decode.py b/src/freedreno/isa/decode.py new file mode 100644 index 00000000000..e4a2b949f58 --- /dev/null +++ b/src/freedreno/isa/decode.py @@ -0,0 +1,198 @@ +# +# Copyright © 2020 Google, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +from mako.template import Template +from isa import ISA +import sys + +template = """\ +/* Copyright (C) 2020 Google, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "decode.h" + +/* + * enum tables, these don't have any link back to other tables so just + * dump them up front before the bitset tables + */ + +%for name, enum in isa.enums.items(): +static const struct isa_enum ${enum.get_c_name()} = { + .num_values = ${len(enum.values)}, + .values = { +% for val, display in enum.values.items(): + { .val = ${val}, .display = "${display}" }, +% endfor + }, +}; +%endfor + +/* + * generated expression functions, can be linked from bitset tables, so + * also dump them up front + */ + +%for name, expr in isa.expressions.items(): +static uint64_t +${expr.get_c_name()}(struct decode_scope *scope) +{ +% for fieldname in expr.fieldnames: + int64_t ${fieldname} = isa_decode_field(scope, "${fieldname}"); +% endfor + return ${expr.expr}; +} +%endfor + +/* + * Forward-declarations (so we don't have to figure out which order to + * emit various tables when they have pointers to each other) + */ + +%for name, bitset in isa.bitsets.items(): +static const struct isa_bitset bitset_${bitset.get_c_name()}; +%endfor + +%for root_name, root in isa.roots.items(): +const struct isa_bitset *${root.get_c_name()}[]; +%endfor + +/* + * bitset tables: + */ + +%for name, bitset in isa.bitsets.items(): +% for case in bitset.cases: +% for field_name, field in case.fields.items(): +% if field.get_c_typename() == 'TYPE_BITSET': +% if len(field.params) > 0: +static const struct isa_field_params ${case.get_c_name()}_${field.get_c_name()} = { + .num_params = ${len(field.params)}, + .params = { +% for param in field.params: + { .name= "${param[0]}", .as = "${param[1]}" }, +% endfor + + }, +}; +% endif +% endif +% endfor +static const struct isa_case ${case.get_c_name()} = { +% if case.expr is not None: + .expr = &${isa.expressions[case.expr].get_c_name()}, +% endif +% if case.display is not None: + .display = "${case.display}", +% endif + .num_fields = ${len(case.fields)}, + .fields = { +% for field_name, field in case.fields.items(): + { .name = "${field_name}", .low = ${field.low}, .high = ${field.high}, +% if field.expr is not None: + .expr = &${isa.expressions[field.expr].get_c_name()}, +% endif +% if field.display is not None: + .display = "${field.display}", +% endif + .type = ${field.get_c_typename()}, +% if field.get_c_typename() == 'TYPE_BITSET': + .bitsets = ${isa.roots[field.type].get_c_name()}, +% if len(field.params) > 0: + .params = &${case.get_c_name()}_${field.get_c_name()}, +% endif +% endif +% if field.get_c_typename() == 'TYPE_ENUM': + .enums = &${isa.enums[field.type].get_c_name()}, +% endif +% if field.get_c_typename() == 'TYPE_ASSERT': + .val = ${field.val}, +% endif + }, +% endfor + }, +}; +% endfor +static const struct isa_bitset bitset_${bitset.get_c_name()} = { +<% pattern = bitset.get_pattern() %> +% if bitset.extends is not None: + .parent = &bitset_${isa.bitsets[bitset.extends].get_c_name()}, +% endif + .name = "${name}", + .gen = { + .min = ${bitset.gen_min}, + .max = ${bitset.gen_max}, + }, + .match = ${hex(pattern.match)}, + .dontcare = ${hex(pattern.dontcare)}, + .mask = ${hex(pattern.mask)}, + .num_cases = ${len(bitset.cases)}, + .cases = { +% for case in bitset.cases: + &${case.get_c_name()}, +% endfor + }, +}; +%endfor + +/* + * bitset hierarchy root tables (where decoding starts from): + */ + +%for root_name, root in isa.roots.items(): +const struct isa_bitset *${root.get_c_name()}[] = { +% for leaf_name, leaf in isa.leafs.items(): +% if leaf.get_root() == root: + &bitset_${leaf.get_c_name()}, +% endif +% endfor + (void *)0 +}; +%endfor + +""" + +xml = sys.argv[1] +dst = sys.argv[2] + +isa = ISA(xml) + +with open(dst, 'wb') as f: + f.write(Template(template, output_encoding='utf-8').render(isa=isa)) diff --git a/src/freedreno/isa/encode.c b/src/freedreno/isa/encode.c new file mode 100644 index 00000000000..5d809a330b5 --- /dev/null +++ b/src/freedreno/isa/encode.c @@ -0,0 +1,315 @@ +/* + * Copyright © 2020 Google, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "util/log.h" + +#include "ir3/ir3.h" +#include "ir3/ir3_shader.h" +#include "ir3/instr-a3xx.h" // TODO move opc's and other useful things to ir3-instr.h or so + +#include "isa.h" + +struct bitset_params; + +struct encode_state { + struct ir3_compiler *compiler; + + /** + * The instruction which is currently being encoded + */ + struct ir3_instruction *instr; +}; + +/* + * Helpers defining how to map from ir3_instruction/ir3_register/etc to fields + * to be encoded: + */ + +static inline bool +extract_SRC1_R(struct ir3_instruction *instr) +{ + if (instr->nop) { + assert(!instr->repeat); + return instr->nop & 0x1; + } + return !!(instr->regs[1]->flags & IR3_REG_R); +} + +static inline bool +extract_SRC2_R(struct ir3_instruction *instr) +{ + if (instr->nop) { + assert(!instr->repeat); + return (instr->nop >> 1) & 0x1; + } + /* src2 does not appear in all cat2, but SRC2_R does (for nop encoding) */ + if (instr->regs_count > 2) + return !!(instr->regs[2]->flags & IR3_REG_R); + return 0; +} + +static inline opc_t +__instruction_case(struct encode_state *s, struct ir3_instruction *instr) +{ + /* + * Temporary hack.. the new world doesn't map opcodes directly to hw + * encoding, so there are some cases where we need to fixup the opc + * to match what the encoder expects. Eventually this will go away + * once we completely transition away from the packed-struct encoding/ + * decoding and split up things which are logically different + * instructions + */ + if (instr->opc == OPC_B) { + switch (instr->cat0.brtype) { + case BRANCH_PLAIN: + return OPC_BR; + case BRANCH_OR: + return OPC_BRAO; + case BRANCH_AND: + return OPC_BRAA; + case BRANCH_CONST: + return OPC_BRAC; + case BRANCH_ANY: + return OPC_BANY; + case BRANCH_ALL: + return OPC_BALL; + case BRANCH_X: + return OPC_BRAX; + } + } else if (instr->opc == OPC_MOV) { + struct ir3_register *src = instr->regs[1]; + if (src->flags & IR3_REG_IMMED) { + return OPC_MOV_IMMED; + } if (src->flags & IR3_REG_RELATIV) { + if (src->flags & IR3_REG_CONST) { + return OPC_MOV_RELCONST; + } else { + return OPC_MOV_RELGPR; + } + } else if (src->flags & IR3_REG_CONST) { + return OPC_MOV_CONST; + } else { + return OPC_MOV_GPR; + } + } else if ((instr->block->shader->compiler->gpu_id > 600) && + is_atomic(instr->opc) && (instr->flags & IR3_INSTR_G)) { + return instr->opc - OPC_ATOMIC_ADD + OPC_ATOMIC_B_ADD; + } else if (s->compiler->gpu_id >= 600) { + if (instr->opc == OPC_RESINFO) { + return OPC_RESINFO_B; + } else if (instr->opc == OPC_LDIB) { + return OPC_LDIB_B; + } else if (instr->opc == OPC_STIB) { + return OPC_STIB_B; + } + } + return instr->opc; +} + +static inline unsigned +extract_ABSNEG(struct ir3_register *reg) +{ + // TODO generate enums for this: + if (reg->flags & (IR3_REG_FNEG | IR3_REG_SNEG | IR3_REG_BNOT)) { + if (reg->flags & (IR3_REG_FABS | IR3_REG_SABS)) { + return 3; // ABSNEG + } else { + return 1; // NEG + } + } else if (reg->flags & (IR3_REG_FABS | IR3_REG_SABS)) { + return 2; // ABS + } else { + return 0; + } +} + +/** + * This is a bit messy, to deal with the fact that the optional "s2en" + * src is the first src, shifting everything else up by one. + * + * TODO revisit this once legacy 'packed struct' encoding is gone + */ +static inline struct ir3_register * +extract_cat5_SRC(struct ir3_instruction *instr, unsigned n) +{ + if (instr->flags & IR3_INSTR_S2EN) { + n++; + } + if (n < instr->regs_count) + return instr->regs[n]; + return NULL; +} + +static inline bool +extract_cat5_FULL(struct ir3_instruction *instr) +{ + struct ir3_register *reg = extract_cat5_SRC(instr, 1); + /* some cat5 have zero src regs, in which case 'FULL' is false */ + if (!reg) + return false; + return !(reg->flags & IR3_REG_HALF); +} + +static inline cat5_desc_mode_t +extract_cat5_DESC_MODE(struct ir3_instruction *instr) +{ + assert(instr->flags & (IR3_INSTR_S2EN | IR3_INSTR_B)); + if (instr->flags & IR3_INSTR_S2EN) { + if (instr->flags & IR3_INSTR_B) { + if (instr->flags & IR3_INSTR_A1EN) { + return CAT5_BINDLESS_A1_UNIFORM; + } else { + return CAT5_BINDLESS_UNIFORM; + } + } else { + /* TODO: This should probably be CAT5_UNIFORM, at least on a6xx, + * as this is what the blob does and it is presumably faster, but + * first we should confirm it is actually nonuniform and figure + * out when the whole descriptor mode mechanism was introduced. + */ + return CAT5_NONUNIFORM; + } + assert(!(instr->cat5.samp | instr->cat5.tex)); + } else if (instr->flags & IR3_INSTR_B) { + if (instr->flags & IR3_INSTR_A1EN) { + return CAT5_BINDLESS_A1_IMM; + } else { + return CAT5_BINDLESS_IMM; + } + } + return 0; +} + +static inline unsigned +extract_cat6_DESC_MODE(struct ir3_instruction *instr) +{ + struct ir3_register *ssbo = instr->regs[1]; + if (ssbo->flags & IR3_REG_IMMED) { + return 0; // todo enum + } else if (instr->flags & IR3_INSTR_NONUNIF) { + return 2; // todo enum + } else { + return 1; // todo enum + } +} + +/** + * This is a bit messy, for legacy (pre-bindless) atomic instructions, + * the .g (global) variety have SSBO as first src and everything else + * shifted up by one. + * + * TODO revisit this once legacy 'packed struct' encoding is gone + */ +static inline struct ir3_register * +extract_cat6_SRC(struct ir3_instruction *instr, unsigned n) +{ + if (instr->flags & IR3_INSTR_G) { + n++; + } + assert(n < instr->regs_count); + return instr->regs[n]; +} + +typedef enum { + REG_MULITSRC_IMMED, + REG_MULTISRC_IMMED_FLUT_FULL, + REG_MULTISRC_IMMED_FLUT_HALF, + REG_MULTISRC_GPR, + REG_MULTISRC_CONST, + REG_MULTISRC_RELATIVE_GPR, + REG_MULTISRC_RELATIVE_CONST, +} reg_multisrc_t; + +static inline reg_multisrc_t +__multisrc_case(struct encode_state *s, struct ir3_register *reg) +{ + if (reg->flags & IR3_REG_IMMED) { + assert(opc_cat(s->instr->opc) == 2); + if (ir3_cat2_int(s->instr->opc)) { + return REG_MULITSRC_IMMED; + } else if (reg->flags & IR3_REG_HALF) { + return REG_MULTISRC_IMMED_FLUT_HALF; + } else { + return REG_MULTISRC_IMMED_FLUT_FULL; + } + } else if (reg->flags & IR3_REG_RELATIV) { + if (reg->flags & IR3_REG_CONST) { + return REG_MULTISRC_RELATIVE_CONST; + } else { + return REG_MULTISRC_RELATIVE_GPR; + } + } else if (reg->flags & IR3_REG_CONST) { + return REG_MULTISRC_CONST; + } else { + return REG_MULTISRC_GPR; + } +} + +typedef enum { + REG_CAT3_SRC_GPR, + REG_CAT3_SRC_CONST, + REG_CAT3_SRC_RELATIVE_GPR, + REG_CAT3_SRC_RELATIVE_CONST, +} reg_cat3_src_t; + +static inline reg_cat3_src_t +__cat3_src_case(struct encode_state *s, struct ir3_register *reg) +{ + if (reg->flags & IR3_REG_RELATIV) { + if (reg->flags & IR3_REG_CONST) { + return REG_CAT3_SRC_RELATIVE_CONST; + } else { + return REG_CAT3_SRC_RELATIVE_GPR; + } + } else if (reg->flags & IR3_REG_CONST) { + return REG_CAT3_SRC_CONST; + } else { + return REG_CAT3_SRC_GPR; + } +} + +#include "encode.h" + + +void * +isa_assemble(struct ir3_shader_variant *v) +{ + uint64_t *ptr, *instrs; + const struct ir3_info *info = &v->info; + struct ir3 *shader = v->ir; + + ptr = instrs = rzalloc_size(v, info->size); + + foreach_block (block, &shader->block_list) { + foreach_instr (instr, &block->instr_list) { + struct encode_state s = { + .compiler = shader->compiler, + .instr = instr, + }; + + *(instrs++) = encode__instruction(&s, NULL, instr); + } + } + + return ptr; +} diff --git a/src/freedreno/isa/encode.py b/src/freedreno/isa/encode.py new file mode 100644 index 00000000000..576fef626ac --- /dev/null +++ b/src/freedreno/isa/encode.py @@ -0,0 +1,562 @@ +# +# Copyright © 2020 Google, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +from mako.template import Template +from isa import ISA, BitSetDerivedField, BitSetAssertField +import sys +import re + +# Encoding is driven by the display template that would be used +# to decode any given instruction, essentially working backwards +# from the decode case. (Or put another way, the decoded bitset +# should contain enough information to re-encode it again.) +# +# In the xml, we can have multiple override cases per bitset, +# which can override display template and/or fields. Iterating +# all this from within the template is messy, so use helpers +# outside of the template for this. +# +# The hierarchy of iterators for encoding is: +# +# // First level - Case() (s.bitset_cases() iterator) +# if (caseA.expression()) { // maps to in xml +# // Second level - DisplayField() (case.display_fields() iterator) +# ... encode field A ... +# ... encode field B ... +# +# // Third level - each display field can be resolved in potentially +# // resolved by multiple different overrides, you can end up with +# // an if/else ladder for an individual display field +# if (field_c_case1.expression()) { +# ... encode field C ... +# } else if (field_c_case2.expression() { +# ... encode field C ... +# } else { +# } +# +# } else if (caseB.expression())( +# } else { // maps to the default case in bitset, ie. outside +# } + + +# Represents a concrete field, ie. a field can be overriden +# by an override, so the exact choice to encode a given field +# in a bitset may be conditional +class FieldCase(object): + def __init__(self, field, case): + self.field = field + self.expr = None + if case.expr is not None: + self.expr = isa.expressions[case.expr] + +class AssertField(object): + def __init__(self, field, case): + self.field = field + self.expr = None + if case.expr is not None: + self.expr = isa.expressions[case.expr] + +# Represents a field to be encoded: +class DisplayField(object): + def __init__(self, bitset, case, name): + self.bitset = bitset # leaf bitset + self.case = case + self.name = name + + def fields(self, bitset=None): + if bitset is None: + bitset = self.bitset + # resolving the various cases for encoding a given + # field is similar to resolving the display template + # string + for case in bitset.cases: + if case.expr is not None: + expr = bitset.isa.expressions[case.expr] + self.case.append_expr_fields(expr) + if self.name in case.fields: + field = case.fields[self.name] + # For bitset fields, the bitset type could reference + # fields in this (the containing) bitset, in addition + # to the ones which are directly used to encode the + # field itself. + if field.get_c_typename() == 'TYPE_BITSET': + for param in field.params: + self.case.append_field(param[0]) + # For derived fields, we want to consider any other + # fields that are referenced by the expr + if isinstance(field, BitSetDerivedField): + expr = bitset.isa.expressions[field.expr] + self.case.append_expr_fields(expr) + elif not isinstance(field, BitSetAssertField): + yield FieldCase(field, case) + # if we've found an unconditional case specifying + # the named field, we are done + if case.expr is None: + return + if bitset.extends is not None: + yield from self.fields(isa.bitsets[bitset.extends]) + +# Represents an if/else case in bitset encoding which has a display +# template string: +class Case(object): + def __init__(self, bitset, case): + self.bitset = bitset # leaf bitset + self.case = case + self.expr = None + if case.expr is not None: + self.expr = isa.expressions[case.expr] + self.fieldnames = re.findall(r"{([a-zA-Z0-9_]+)}", case.display) + self.append_forced(bitset) + + # Handle fields which don't appear in display template but have + # force="true" + def append_forced(self, bitset): + if bitset.encode is not None: + for name, val in bitset.encode.forced.items(): + self.append_field(name) + if bitset.extends is not None: + self.append_forced(isa.bitsets[bitset.extends]) + + # In the process of resolving a field, we might discover additional + # fields that need resolving: + # + # a) a derived field which maps to one or more other concrete fields + # b) a bitset field, which may be "parameterized".. for example a + # #multisrc field which refers back to SRC1_R/SRC2_R outside of + # the range of bits covered by the #multisrc field itself + def append_field(self, fieldname): + if fieldname not in self.fieldnames: + self.fieldnames.append(fieldname) + + def append_expr_fields(self, expr): + for fieldname in expr.fieldnames: + self.append_field(fieldname) + + def display_fields(self): + for fieldname in self.fieldnames: + yield DisplayField(self.bitset, self, fieldname) + + def assert_cases(self, bitset=None): + if bitset is None: + bitset = self.bitset + for case in bitset.cases: + for name, field in case.fields.items(): + if field.get_c_typename() == 'TYPE_ASSERT': + yield AssertField(field, case) + if bitset.extends is not None: + yield from self.assert_cases(isa.bitsets[bitset.extends]) + +# State and helpers used by the template: +class State(object): + def __init__(self, isa): + self.isa = isa + self.warned_missing_extractors = [] + + def bitset_cases(self, bitset, leaf_bitset=None): + if leaf_bitset is None: + leaf_bitset = bitset; + for case in bitset.cases: + if case.display is None: + # if this is the last case (ie. case.expr is None) + # then we need to go up the inheritance chain: + if case.expr is None and bitset.extends is not None: + parent_bitset = isa.bitsets[bitset.extends] + yield from self.bitset_cases(parent_bitset, leaf_bitset) + continue; + yield Case(leaf_bitset, case) + + # Find unique bitset remap/parameter names, to generate a struct + # used to pass "parameters" to bitset fields: + def unique_param_names(self): + unique_names = [] + for root in self.encode_roots(): + for leaf in self.encode_leafs(root): + for case in s.bitset_cases(leaf): + for df in case.display_fields(): + for f in df.fields(): + if f.field.get_c_typename() == 'TYPE_BITSET': + for param in f.field.params: + target_name = param[1] + if target_name not in unique_names: + yield target_name + unique_names.append(target_name) + + def case_name(self, bitset, name): + return bitset.encode.case_prefix + name.upper().replace('.', '_').replace('-', '_').replace('#', '') + + def encode_roots(self): + for name, root in self.isa.roots.items(): + if root.encode is None: + continue + yield root + + def encode_leafs(self, root): + for name, leaf in self.isa.leafs.items(): + if leaf.get_root() != root: + continue + yield leaf + + # expressions used in a bitset (case or field or recursively parent bitsets) + def bitset_used_exprs(self, bitset): + for case in bitset.cases: + if case.expr: + yield self.isa.expressions[case.expr] + for name, field in case.fields.items(): + if isinstance(field, BitSetDerivedField): + yield self.isa.expressions[field.expr] + if bitset.extends is not None: + yield from self.bitset_used_exprs(self.isa.bitsets[bitset.extends]) + + def extractor_impl(self, bitset, name): + if bitset.encode is not None: + if name in bitset.encode.maps: + return bitset.encode.maps[name] + if bitset.extends is not None: + return self.extractor_impl(self.isa.bitsets[bitset.extends], name) + return None + + # Default fallback when no mapping is defined, simply to avoid + # having to deal with encoding at the same time as r/e new + # instruction decoding.. but we can at least print warnings: + def extractor_fallback(self, bitset, name): + extr_name = bitset.name + '.' + name + if extr_name not in self.warned_missing_extractors: + print('WARNING: no encode mapping for {}.{}'.format(bitset.name, name)) + self.warned_missing_extractors.append(extr_name) + return '0 /* XXX */' + + def extractor(self, bitset, name): + extr = self.extractor_impl(bitset, name) + if extr is not None: + return extr + return self.extractor_fallback(bitset, name) + + # In the special case of needing to access a field with bitset type + # for an expr, we need to encode the field so we end up with an + # integer, and not some pointer to a thing that will be encoded to + # an integer + def expr_extractor(self, bitset, name, p): + extr = self.extractor_impl(bitset, name) + field = self.resolve_simple_field(bitset, name) + if isinstance(field, BitSetDerivedField): + expr = self.isa.expressions[field.expr] + return self.expr_name(bitset.get_root(), expr) + '(s, p, src)' + if extr is None: + if name in self.unique_param_names(): + extr = 'p->' + name + else: + extr = self.extractor_fallback(bitset, name) + if field and field.get_c_typename() == 'TYPE_BITSET': + extr = 'encode' + isa.roots[field.type].get_c_name() + '(s, ' + p + ', ' + extr + ')' + return extr + + # 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(isa.bitsets[bitset.extends], name) + return None + + def encode_type(self, bitset): + if bitset.encode is not None: + if bitset.encode.type is not None: + return bitset.encode.type + if bitset.extends is not None: + return self.encode_type(isa.bitsets[bitset.extends]) + return None + + def expr_name(self, root, expr): + return root.get_c_name() + '_' + expr.get_c_name() + + def has_jmp(self, instructions): + # I'm sure there is some clever more pythony way to do this: + for instr in instructions: + if instr[0] == 'JMP': + return True + return False + +template = """\ +/* Copyright (C) 2020 Google, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include + +<% +isa = s.isa +%> + +/** + * Opaque type from the PoV of generated code, but allows state to be passed + * thru to the hand written helpers used by the generated code. + */ +struct encode_state; + +struct bitset_params; + +static uint64_t +pack_field(unsigned low, unsigned high, uint64_t val) +{ + val &= ((1ul << (1 + high - low)) - 1); + return val << low; +} + +/* + * Forward-declarations (so we don't have to figure out which order to + * emit various encoders when they have reference each other) + */ + +%for root in s.encode_roots(): +static uint64_t encode${root.get_c_name()}(struct encode_state *s, struct bitset_params *p, ${root.encode.type} src); +%endfor + +## TODO before the expr evaluators, we should generate extract_FOO() for +## derived fields.. which probably also need to be in the context of the +## respective root so they take the correct src arg?? + +/* + * Expression evaluators: + */ + +struct bitset_params { +%for name in s.unique_param_names(): + int64_t ${name}; +%endfor +}; + +#define push(v) do { \ + assert(sp < ARRAY_SIZE(stack)); \ + stack[sp] = (v); \ + sp++; \ + } while (0) +#define peek() ({ \ + assert(sp < ARRAY_SIZE(stack)); \ + stack[sp - 1]; \ + }) +#define pop() ({ \ + assert(sp > 0); \ + --sp; \ + stack[sp]; \ + }) + +<%def name="render_expr(leaf, expr)"> +static inline int64_t +${s.expr_name(leaf.get_root(), expr)}(struct encode_state *s, struct bitset_params *p, ${leaf.get_root().encode.type} src) +{ +% for fieldname in expr.fieldnames: + int64_t ${fieldname}; +% endfor +% for fieldname in expr.fieldnames: +<% field = s.resolve_simple_field(leaf, fieldname) %> +% if field is not None and field.get_c_typename() == 'TYPE_BITSET': + { ${encode_params(leaf, field)} + ${fieldname} = ${s.expr_extractor(leaf, fieldname, '&bp')}; + } +% else: + ${fieldname} = ${s.expr_extractor(leaf, fieldname, 'p')}; +% endif +% endfor + return ${expr.expr}; +} + + +## note, we can't just iterate all the expressions, but we need to find +## the context in which they are used to know the correct src type + +%for root in s.encode_roots(): +<% + rendered_exprs = [] +%> +% for leaf in s.encode_leafs(root): +% for expr in s.bitset_used_exprs(leaf): +<% + if expr in rendered_exprs: + continue + rendered_exprs.append(expr) +%> + ${render_expr(leaf, expr)} +% endfor +% endfor +%endfor + +#undef pop +#undef peek +#undef push + + +<%def name="case_pre(root, expr)"> +%if expr is not None: + if (${s.expr_name(root, expr)}(s, p, src)) { +%else: + { +%endif + + +<%def name="case_post(root, expr)"> +%if expr is not None: + } else +%else: + } +%endif + + +<%def name="encode_params(leaf, field)"> + struct bitset_params bp = { +%for param in field.params: + .${param[1]} = ${s.expr_extractor(leaf, param[0], 'p')}, /* ${param[0]} */ +%endfor + }; + + +<%def name="encode_bitset(root, leaf)"> + uint64_t fld, val = ${hex(leaf.get_pattern().match)}; + (void)fld; +<% visited_exprs = [] %> +%for case in s.bitset_cases(leaf): +<% + if case.expr is not None: + visited_exprs.append(case.expr) +%> + ${case_pre(root, case.expr)} +% for df in case.display_fields(): +% for f in df.fields(): +<% + # simplify the control flow a bit to give the compiler a bit + # less to clean up + expr = f.expr + if expr == case.expr: + # Don't need to evaluate the same condition twice: + expr = None + elif expr in visited_exprs: + # We are in an 'else'/'else-if' leg that we wouldn't + # go down due to passing an earlier if() + continue +%> + ${case_pre(root, expr)} +% if f.field.get_c_typename() == 'TYPE_BITSET': + { ${encode_params(leaf, f.field)} + fld = encode${isa.roots[f.field.type].get_c_name()}(s, &bp, ${s.extractor(leaf, f.field.name)}); } +% else: + fld = ${s.extractor(leaf, f.field.name)}; +% endif + val |= pack_field(${f.field.low}, ${f.field.high}, fld); /* ${f.field.name} */ + ${case_post(root, expr)} +% endfor +% endfor + +% for f in case.assert_cases(): +<% + # simplify the control flow a bit to give the compiler a bit + # less to clean up + expr = f.expr + if expr == case.expr: + # Don't need to evaluate the same condition twice: + expr = None + elif expr in visited_exprs: + # We are in an 'else'/'else-if' leg that we wouldn't + # go down due to passing an earlier if() + continue +%> + ${case_pre(root, expr)} + val |= pack_field(${f.field.low}, ${f.field.high}, ${f.field.val}); + ${case_post(root, None)} +% endfor + {} /* in case no unconditional field to close out last '} else' */ + ${case_post(root, case.expr)} +%endfor + return val; + + +/* + * The actual encoder definitions + */ + +%for root in s.encode_roots(): + +static uint64_t +encode${root.get_c_name()}(struct encode_state *s, struct bitset_params *p, ${root.encode.type} src) +{ +% if root.encode.case_prefix is not None: + switch (${root.get_c_name()}_case(s, src)) { +% for leaf in s.encode_leafs(root): + case ${s.case_name(root, leaf.name)}: { + ${encode_bitset(root, leaf)} + } +% endfor + default: + /* Note that we need the default case, because there are + * instructions which we never expect to be encoded, (ie. + * meta/macro instructions) as they are removed/replace + * in earlier stages of the compiler. + */ + break; + } + mesa_loge("Unhandled ${root.name} encode case: 0x%x\\n", ${root.get_c_name()}_case(s, src)); + return 0; +% else: # single case bitset, no switch +% for leaf in s.encode_leafs(root): + ${encode_bitset(root, leaf)} +% endfor +% endif +} + +%endfor + +""" + +xml = sys.argv[1] +dst = sys.argv[2] + +isa = ISA(xml) +s = State(isa) + +with open(dst, 'wb') as f: + f.write(Template(template, output_encoding='utf-8').render(s=s)) diff --git a/src/freedreno/isa/ir3-disasm.c b/src/freedreno/isa/ir3-disasm.c new file mode 100644 index 00000000000..ee1c0c16602 --- /dev/null +++ b/src/freedreno/isa/ir3-disasm.c @@ -0,0 +1,57 @@ +/* + * Copyright © 2020 Google, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "util/os_file.h" + +#include "isa.h" + + +static void +disasm_instr_cb(void *d, unsigned n, uint64_t instr) +{ + uint32_t *dwords = (uint32_t *)&instr; + printf("%3d[%08x_%08x] ", n, dwords[1], dwords[0]); +} + +int +main(int argc, char **argv) +{ + size_t sz; + void *raw = os_read_file(argv[1], &sz); + + isa_decode(raw, sz, stdout, &(struct isa_decode_options) { + .show_errors = true, + .branch_labels = true, + .instr_cb = disasm_instr_cb, + }); + + return 0; +} diff --git a/src/freedreno/isa/isa.h b/src/freedreno/isa/isa.h new file mode 100644 index 00000000000..84f12081b49 --- /dev/null +++ b/src/freedreno/isa/isa.h @@ -0,0 +1,88 @@ +/* + * Copyright © 2020 Google, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _ISA_H_ +#define _ISA_H_ + +#include +#include + +#include + +struct isa_decode_value { + /** for {NAME} */ + const char *str; + /** for all other fields */ + uint64_t num; +}; + +struct isa_decode_hook { + const char *fieldname; + void (*cb)(void *data, struct isa_decode_value *val); +}; + +struct isa_decode_options { + uint32_t gpu_id; + + /** show errors detected in decoding, like unexpected dontcare bits */ + bool show_errors; + + /** + * If non-zero, maximum # of instructions that are unmatched before + * bailing, ie. to trigger stopping if we start trying to decode + * random garbage. + */ + unsigned max_errors; + + /** Generate branch target labels */ + bool branch_labels; + + /** + * Flag which can be set, for ex, but decode hook to trigger end of + * decoding + */ + bool stop; + + /** + * Data passed back to decode hooks + */ + void *cbdata; + + /** + * Callback for field decode + */ + void (*field_cb)(void *data, const char *field_name, struct isa_decode_value *val); + + /** + * Callback prior to instruction decode + */ + void (*instr_cb)(void *data, unsigned n, uint64_t instr); +}; + +void isa_decode(void *bin, int sz, FILE *out, const struct isa_decode_options *options); + + +struct ir3_shader_variant; +void * isa_assemble(struct ir3_shader_variant *v); + +#endif /* _ISA_H_ */ diff --git a/src/freedreno/isa/isa.py b/src/freedreno/isa/isa.py new file mode 100644 index 00000000000..995823ab1cf --- /dev/null +++ b/src/freedreno/isa/isa.py @@ -0,0 +1,484 @@ +# +# Copyright © 2020 Google, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +from xml.etree import ElementTree +import os +import re + +def dbg(str): + if False: + print(str) + +class BitSetPattern(object): + """Class that encapsulated the pattern matching, ie. + the match/dontcare/mask bitmasks. The following + rules should hold + + (match ^ dontcare) == 0 + (match || dontcare) == mask + + For a leaf node, the mask should be (1 << size) - 1 + (ie. all bits set) + """ + def __init__(self, bitset): + self.match = bitset.match + self.dontcare = bitset.dontcare + self.mask = bitset.mask + self.field_mask = bitset.field_mask; + + def merge(self, pattern): + p = BitSetPattern(pattern) + p.match = p.match | self.match + p.dontcare = p.dontcare | self.dontcare + p.mask = p.mask | self.mask + p.field_mask = p.field_mask | self.field_mask + return p + + def defined_bits(self): + return self.match | self.dontcare | self.mask | self.field_mask + +def get_bitrange(field): + if 'pos' in field.attrib: + assert('low' not in field.attrib) + assert('high' not in field.attrib) + low = int(field.attrib['pos']) + high = low + else: + low = int(field.attrib['low']) + high = int(field.attrib['high']) + assert low <= high + return low, high + +def extract_pattern(xml, name, is_defined_bits=None): + low, high = get_bitrange(xml) + mask = ((1 << (1 + high - low)) - 1) << low + + patstr = xml.text.strip() + + assert (len(patstr) == (1 + high - low)), "Invalid {} length in {}: {}..{}".format(xml.tag, name, low, high) + if is_defined_bits is not None: + assert not is_defined_bits(mask), "Redefined bits in {} {}: {}..{}".format(xml.tag, name, low, high); + + match = 0; + dontcare = 0 + + for n in range(0, len(patstr)): + match = match << 1 + dontcare = dontcare << 1 + if patstr[n] == '1': + match |= 1 + elif patstr[n] == 'x': + dontcare |= 1 + elif patstr[n] != '0': + assert 0, "Invalid {} character in {}: {}".format(xml.tag, name, patstr[n]) + + dbg("{}: {}.{} => {:016x} / {:016x} / {:016x}".format(xml.tag, name, patstr, match << low, dontcare << low, mask)) + + return match << low, dontcare << low, mask + +def get_c_name(name): + return name.lower().replace('#', '__').replace('-', '_').replace('.', '_') + +class BitSetField(object): + """Class that encapsulates a field defined in a bitset + """ + def __init__(self, isa, xml): + self.isa = isa + self.low, self.high = get_bitrange(xml) + self.name = xml.attrib['name'] + self.type = xml.attrib['type'] + self.params = [] + for param in xml.findall('param'): + aas = name = param.attrib['name'] + if 'as' in param.attrib: + aas = param.attrib['as'] + self.params.append([name, aas]) + self.expr = None + self.display = None + if 'display' in xml.attrib: + self.display = xml.attrib['display'].strip() + + def get_c_name(self): + return get_c_name(self.name) + + def get_c_typename(self): + if self.type in self.isa.enums: + return 'TYPE_ENUM' + if self.type in self.isa.bitsets: + return 'TYPE_BITSET' + return 'TYPE_' + self.type.upper() + + def mask(self): + return ((1 << self.get_size()) - 1) << self.low + + def get_size(self): + return 1 + self.high - self.low + +class BitSetAssertField(BitSetField): + """Similar to BitSetField, but for s, which can be + used to specify that a certain bitpattern is expected in + place of (for example) unused bitfields + """ + def __init__(self, case, xml): + self.isa = case.bitset.isa + self.low, self.high = get_bitrange(xml) + self.name = case.bitset.name + '#assert' + str(len(case.fields)) + self.type = 'uint' + self.expr = None + self.display = None + + match, dontcare, mask = extract_pattern(xml, case.bitset.name) + self.val = match >> self.low + + assert dontcare == 0, "'x' (dontcare) is not valid in an assert" + + def get_c_typename(self): + return 'TYPE_ASSERT' + +class BitSetDerivedField(BitSetField): + """Similar to BitSetField, but for derived fields + """ + def __init__(self, isa, xml): + self.isa = isa + self.low = 0 + self.high = 0 + # NOTE: a width should be provided for 'int' derived fields, ie. + # where sign extension is needed. We just repurpose the 'high' + # field for that to make '1 + high - low' work out + if 'width' in xml.attrib: + self.high = xml.attrib['width'] + ' - 1' + self.name = xml.attrib['name'] + self.type = xml.attrib['type'] + if 'expr' in xml.attrib: + self.expr = xml.attrib['expr'] + else: + e = isa.parse_one_expression(xml, self.name) + self.expr = e.name + self.display = None + if 'display' in xml.attrib: + self.display = xml.attrib['display'].strip() + +class BitSetCase(object): + """Class that encapsulates a single bitset case + """ + def __init__(self, bitset, xml, update_field_mask, expr=None): + self.bitset = bitset + if expr is not None: + self.name = bitset.name + '#case' + str(len(bitset.cases)) + else: + self.name = bitset.name + "#default" + self.expr = expr + self.fields = {} + + for derived in xml.findall('derived'): + f = BitSetDerivedField(bitset.isa, derived) + self.fields[f.name] = f + + for assrt in xml.findall('assert'): + f = BitSetAssertField(self, assrt) + update_field_mask(self, f) + self.fields[f.name] = f + + for field in xml.findall('field'): + dbg("{}.{}".format(self.name, field.attrib['name'])) + f = BitSetField(bitset.isa, field) + update_field_mask(self, f) + self.fields[f.name] = f + + self.display = None + for d in xml.findall('display'): + # Allow for empty display string: + if d.text is not None: + self.display = d.text.strip() + else: + self.display = '' + dbg("found display: '{}'".format(self.display)) + + def get_c_name(self): + return get_c_name(self.name) + +class BitSetEncode(object): + """Additional data that may be associated with a root bitset node + to provide additional information needed to generate helpers + to encode the bitset, such as source data type and "opcode" + case prefix (ie. how to choose/enumerate which leaf node bitset + to use to encode the source data + """ + def __init__(self, xml): + self.type = None + if 'type' in xml.attrib: + self.type = xml.attrib['type'] + self.case_prefix = None + if 'case-prefix' in xml.attrib: + self.case_prefix = xml.attrib['case-prefix'] + # The encode element may also contain mappings from encode src + # to individual field names: + self.maps = {} + self.forced = {} + for map in xml.findall('map'): + name = map.attrib['name'] + self.maps[name] = map.text.strip() + if 'force' in map.attrib and map.attrib['force'] == 'true': + self.forced[name] = 'true' + +class BitSet(object): + """Class that encapsulates a single bitset rule + """ + def __init__(self, isa, xml): + self.isa = isa + self.xml = xml + self.name = xml.attrib['name'] + + if 'size' in xml.attrib: + assert('extends' not in xml.attrib) + self.size = int(xml.attrib['size']) + self.extends = None + else: + self.size = None + self.extends = xml.attrib['extends'] + + self.encode = None + if xml.find('encode') is not None: + self.encode = BitSetEncode(xml.find('encode')) + + self.gen_min = 0 + self.gen_max = ~0 + + for gen in xml.findall('gen'): + if 'min' in gen.attrib: + self.gen_min = gen.attrib['min'] + if 'max' in gen.attrib: + self.gen_max = gen.attrib['max'] + + # Collect up the match/dontcare/mask bitmasks for + # this bitset case: + self.match = 0 + self.dontcare = 0 + self.mask = 0 + self.field_mask = 0 + + self.cases = [] + + # Helper to check for redefined bits: + def is_defined_bits(m): + return ((self.field_mask | self.mask | self.dontcare | self.match) & m) != 0 + + def update_default_bitmask_field(bs, field): + m = field.mask() + dbg("field: {}.{} => {:016x}".format(self.name, field.name, m)) + # For default case, we don't expect any bits to be doubly defined: + assert not is_defined_bits(m), "Redefined bits in field {}.{}: {}..{}".format( + self.name, field.name, field.low, field.high); + self.field_mask |= m + + def update_override_bitmask_field(bs, field): + m = field.mask() + dbg("field: {}.{} => {:016x}".format(self.name, field.name, m)) + assert self.field_mask ^ ~m + + dflt = BitSetCase(self, xml, update_default_bitmask_field) + + for override in xml.findall('override'): + if 'expr' in override.attrib: + expr = override.attrib['expr'] + else: + e = isa.parse_one_expression(override, self.name) + expr = e.name + c = BitSetCase(self, override, update_override_bitmask_field, expr) + self.cases.append(c) + + # Default case is expected to be the last one: + self.cases.append(dflt) + + for pattern in xml.findall('pattern'): + match, dontcare, mask = extract_pattern(pattern, self.name, is_defined_bits) + + self.match |= match + self.dontcare |= dontcare + self.mask |= mask + + def get_pattern(self): + if self.extends is not None: + parent = self.isa.bitsets[self.extends] + ppat = parent.get_pattern() + pat = BitSetPattern(self) + + assert ((ppat.defined_bits() & pat.defined_bits()) == 0), "bitset conflict in {}: {:x}".format(self.name, (ppat.defined_bits() & pat.defined_bits())) + + return pat.merge(ppat) + + return BitSetPattern(self) + + def get_size(self): + if self.extends is not None: + parent = self.isa.bitsets[self.extends] + return parent.get_size() + return self.size + + def get_c_name(self): + return get_c_name(self.name) + + def get_root(self): + if self.extends is not None: + return self.isa.bitsets[self.extends].get_root() + return self + +class BitSetEnum(object): + """Class that encapsulates an enum declaration + """ + def __init__(self, isa, xml): + self.isa = isa + self.name = xml.attrib['name'] + # Table mapping value to name + # TODO currently just mapping to 'display' name, but if we + # need more attributes then maybe need BitSetEnumValue? + self.values = {} + for value in xml.findall('value'): + self.values[value.attrib['val']] = value.attrib['display'] + + def get_c_name(self): + return 'enum_' + get_c_name(self.name) + +class BitSetExpression(object): + """Class that encapsulates an declaration + """ + def __init__(self, isa, xml): + self.isa = isa + if 'name' in xml.attrib: + self.name = xml.attrib['name'] + else: + self.name = 'anon_' + str(isa.anon_expression_count) + isa.anon_expression_count = isa.anon_expression_count + 1 + expr = xml.text.strip() + self.fieldnames = list(set(re.findall(r"{([a-zA-Z0-9_]+)}", expr))) + self.expr = re.sub(r"{([a-zA-Z0-9_]+)}", r"\1", expr) + dbg("'{}' -> '{}'".format(expr, self.expr)) + + def get_c_name(self): + return 'expr_' + get_c_name(self.name) + +class ISA(object): + """Class that encapsulates all the parsed bitset rules + """ + def __init__(self, xmlpath): + self.base_path = os.path.dirname(xmlpath) + + # Counter used to name inline (anonymous) expressions: + self.anon_expression_count = 0 + + # Table of (globally defined) expressions: + self.expressions = {} + + # Table of enums: + self.enums = {} + + # Table of toplevel bitset hierarchies: + self.roots = {} + + # Table of leaf nodes of bitset hierarchies: + self.leafs = {} + + # Table of all bitsets: + self.bitsets = {} + + root = ElementTree.parse(xmlpath).getroot() + self.parse_file(root) + self.validate_isa() + + def parse_expressions(self, root): + e = None + for expr in root.findall('expr'): + e = BitSetExpression(self, expr) + self.expressions[e.name] = e + return e + + def parse_one_expression(self, root, name): + assert len(root.findall('expr')) == 1, "expected a single expression in: {}".format(name) + return self.parse_expressions(root) + + def parse_file(self, root): + # Handle imports up-front: + for imprt in root.findall('import'): + p = os.path.join(self.base_path, imprt.attrib['file']) + self.parse_file(ElementTree.parse(p)) + + # Extract expressions: + self.parse_expressions(root) + + # Extract enums: + for enum in root.findall('enum'): + e = BitSetEnum(self, enum) + self.enums[e.name] = e + + # Extract bitsets: + for bitset in root.findall('bitset'): + b = BitSet(self, bitset) + if b.size is not None: + dbg("toplevel: " + b.name) + self.roots[b.name] = b + else: + dbg("derived: " + b.name) + self.bitsets[b.name] = b + self.leafs[b.name] = b + + # Remove non-leaf nodes from the leafs table: + for name, bitset in self.bitsets.items(): + if bitset.extends is not None: + if bitset.extends in self.leafs: + del self.leafs[bitset.extends] + + def validate_isa(self): + # Validate that all bitset fields have valid types, and in + # the case of bitset type, the sizes match: + builtin_types = ['branch', 'int', 'uint', 'hex', 'offset', 'float', 'bool', 'enum'] + for bitset_name, bitset in self.bitsets.items(): + if bitset.extends is not None: + assert bitset.extends in self.bitsets, "{} extends invalid type: {}".format( + bitset_name, bitset.extends) + for case in bitset.cases: + for field_name, field in case.fields.items(): + if field.type == 'float': + assert field.get_size() == 32 or field.get_size() == 16 + if field.type in builtin_types: + continue + if field.type in self.enums: + continue + assert field.type in self.bitsets, "{}.{}: invalid type: {}".format( + bitset_name, field_name, field.type) + bs = self.bitsets[field.type] + assert field.get_size() == bs.get_size(), "{}.{}: invalid size: {} vs {}".format( + bitset_name, field_name, field.get_size(), bs.get_size()) + + # Validate that all the leaf node bitsets have no remaining + # undefined bits + for name, bitset in self.leafs.items(): + pat = bitset.get_pattern() + sz = bitset.get_size() + assert ((pat.mask | pat.field_mask) == (1 << sz) - 1), "leaf bitset {} has undefined bits: {:x}".format( + bitset.name, ~(pat.mask | pat.field_mask) & ((1 << sz) - 1)) + + # TODO somehow validating that only one bitset in a hierarchy + # matches any given bit pattern would be useful. + + # TODO we should probably be able to look at the contexts where + # an expression is evaluated and verify that it doesn't have any + # references that would be unresolved at evaluation time \ No newline at end of file