freedreno/hw: Add isaspec mechanism for documenting/defining an ISA

Signed-off-by: Rob Clark <robdclark@chromium.org>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/7997>
This commit is contained in:
Rob Clark 2020-12-13 16:18:43 -08:00 committed by Marge Bot
parent 6309c9313b
commit e7630ec278
10 changed files with 2814 additions and 0 deletions

View File

@ -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
<bitset name="#instruction" size="64">
<doc>
Encoding of an ir3 instruction. All instructions are 64b.
</doc>
</bitset>
In the first level of instruction encoding hierarchy, the high three bits
group things into instruction "categories":
.. code-block:: xml
<bitset name="#instruction-cat2" extends="#instruction">
<field name="DST" low="32" high="39" type="#reg-gpr"/>
<field name="REPEAT" low="40" high="41" type="#rptN"/>
<field name="SAT" pos="42" type="bool" display="(sat)"/>
<field name="SS" pos="44" type="bool" display="(ss)"/>
<field name="UL" pos="45" type="bool" display="(ul)"/>
<field name="DST_CONV" pos="46" type="bool">
<doc>
Destination register is opposite precision as source, ie.
if {FULL} is true then destination is half precision, and
visa versa.
</doc>
</field>
<derived name="DST_HALF" expr="#dest-half" type="bool" display="h"/>
<field name="EI" pos="47" type="bool" display="(ei)"/>
<field name="FULL" pos="52" type="bool">
<doc>Full precision source registers</doc>
</field>
<field name="JP" pos="59" type="bool" display="(jp)"/>
<field name="SY" pos="60" type="bool" display="(sy)"/>
<pattern low="61" high="63">010</pattern> <!-- cat2 -->
<!--
NOTE, both SRC1_R and SRC2_R are defined at this level because
SRC2_R is still a valid bit for (nopN) (REPEAT==0) for cat2
instructions with only a single src
-->
<field name="SRC1_R" pos="43" type="bool" display="(r)"/>
<field name="SRC2_R" pos="51" type="bool" display="(r)"/>
<derived name="ZERO" expr="#zero" type="bool" display=""/>
</bitset>
The ``<pattern>`` 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
<bitset name="#instruction-cat2-1src" extends="#instruction-cat2">
<override expr="#cat2-cat3-nop-encoding">
<display>
{SY}{SS}{JP}{SAT}(nop{NOP}) {UL}{NAME} {EI}{DST_HALF}{DST}, {SRC1}
</display>
<derived name="NOP" expr="#cat2-cat3-nop-value" type="uint"/>
<field name="SRC1" low="0" high="15" type="#multisrc">
<param name="ZERO" as="SRC_R"/>
<param name="FULL"/>
</field>
</override>
<display>
{SY}{SS}{JP}{SAT}{REPEAT}{UL}{NAME} {EI}{DST_HALF}{DST}, {SRC1}
</display>
<pattern low="16" high="31">xxxxxxxxxxxxxxxx</pattern>
<pattern low="48" high="50">xxx</pattern> <!-- COND -->
<field name="SRC1" low="0" high="15" type="#multisrc">
<param name="SRC1_R" as="SRC_R"/>
<param name="FULL"/>
</field>
</bitset>
<bitset name="absneg.f" extends="#instruction-cat2-1src">
<pattern low="53" high="58">000110</pattern>
</bitset>
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 ``<param>``
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
<bitset name="#multisrc" size="16">
<doc>
Encoding for instruction source which can be GPR/CONST/IMMED
or relative GPR/CONST.
</doc>
</bitset>
...
<bitset name="#multisrc-gpr" extends="#multisrc">
<display>
{ABSNEG}{SRC_R}{HALF}{SRC}
</display>
<derived name="HALF" expr="#multisrc-half" type="bool" display="h"/>
<field name="SRC" low="0" high="7" type="#reg-gpr"/>
<pattern low="8" high="13">000000</pattern>
<field name="ABSNEG" low="14" high="15" type="#absneg"/>
</bitset>
At some level in the bitset inheritance hiearchy, there is expected to be a
``<display>`` 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 ``<override>`` 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 ``<override>`` 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, ``<display>``, ``<field>``, and ``<derived>``
elements take precedence over what is defined in the toplevel of the
bitset (ie. the default case).
Expressions
-----------
Both ``<override>`` and ``<derived>`` fields make use of ``<expr>`` 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
<expr name="#cat2-cat3-nop-encoding">
(({SRC1_R} != 0) || ({SRC2_R} != 0)) &amp;&amp; ({REPEAT} == 0)
</expr>
In the case of ``<override>`` elements, the override applies if the expression
evaluates to non-zero. In the case of ``<derived>`` fields, the expression
evaluates to the value of the derived field.
Encoding
--------
To facilitate instruction encoding, ``<encode>`` 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
<bitset name="#instruction" size="64">
<doc>
Encoding of an ir3 instruction. All instructions are 64b.
</doc>
<gen min="300"/>
<encode type="struct ir3_instruction *" case-prefix="OPC_">
<!--
Define mapping from encode src to individual fields,
which are common across all instruction categories
at the root instruction level
Not all of these apply to all instructions, but we
can define mappings here for anything that is used
in more than one instruction category. For things
that are specific to a single instruction category,
mappings should be defined at that level instead.
-->
<map name="DST">src->regs[0]</map>
<map name="SRC1">src->regs[1]</map>
<map name="SRC2">src->regs[2]</map>
<map name="SRC3">src->regs[3]</map>
<map name="REPEAT">src->repeat</map>
<map name="SS">!!(src->flags &amp; IR3_INSTR_SS)</map>
<map name="JP">!!(src->flags &amp; IR3_INSTR_JP)</map>
<map name="SY">!!(src->flags &amp; IR3_INSTR_SY)</map>
<map name="UL">!!(src->flags &amp; IR3_INSTR_UL)</map>
<map name="EQ">0</map> <!-- We don't use this (yet) -->
<map name="SAT">!!(src->flags &amp; IR3_INSTR_SAT)</map>
</encode>
</bitset>
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 ``<map>`` elements teach the encoder how to map from the encode
source to fields in the encoded instruction.

View File

@ -0,0 +1 @@
../../../docs/drivers/freedreno/isaspec.rst

676
src/freedreno/isa/decode.c Normal file
View File

@ -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 <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <override/>'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);
}

154
src/freedreno/isa/decode.h Normal file
View File

@ -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 <stdbool.h>
#include <stdint.h>
/*
* 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_ */

198
src/freedreno/isa/decode.py Normal file
View File

@ -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))

315
src/freedreno/isa/encode.c Normal file
View File

@ -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;
}

562
src/freedreno/isa/encode.py Normal file
View File

@ -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 <override/> 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 <override/>
# }
# 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 <stdbool.h>
#include <stdint.h>
<%
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};
}
</%def>
## 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>
<%def name="case_post(root, expr)">
%if expr is not None:
} else
%else:
}
%endif
</%def>
<%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>
<%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;
</%def>
/*
* 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))

View File

@ -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 <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#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;
}

88
src/freedreno/isa/isa.h Normal file
View File

@ -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 <stdbool.h>
#include <stdint.h>
#include <stdio.h>
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_ */

484
src/freedreno/isa/isa.py Normal file
View File

@ -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 <assert/>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 <display/> 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 <expr> 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
# <var/> references that would be unresolved at evaluation time