mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2024-11-23 18:24:13 +08:00
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:
parent
6309c9313b
commit
e7630ec278
279
docs/drivers/freedreno/isaspec.rst
Normal file
279
docs/drivers/freedreno/isaspec.rst
Normal 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)) && ({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 & IR3_INSTR_SS)</map>
|
||||
<map name="JP">!!(src->flags & IR3_INSTR_JP)</map>
|
||||
<map name="SY">!!(src->flags & IR3_INSTR_SY)</map>
|
||||
<map name="UL">!!(src->flags & IR3_INSTR_UL)</map>
|
||||
<map name="EQ">0</map> <!-- We don't use this (yet) -->
|
||||
<map name="SAT">!!(src->flags & 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.
|
1
src/freedreno/isa/README.rst
Symbolic link
1
src/freedreno/isa/README.rst
Symbolic link
@ -0,0 +1 @@
|
||||
../../../docs/drivers/freedreno/isaspec.rst
|
676
src/freedreno/isa/decode.c
Normal file
676
src/freedreno/isa/decode.c
Normal 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
154
src/freedreno/isa/decode.h
Normal 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
198
src/freedreno/isa/decode.py
Normal 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
315
src/freedreno/isa/encode.c
Normal 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
562
src/freedreno/isa/encode.py
Normal 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))
|
57
src/freedreno/isa/ir3-disasm.c
Normal file
57
src/freedreno/isa/ir3-disasm.c
Normal 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
88
src/freedreno/isa/isa.h
Normal 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
484
src/freedreno/isa/isa.py
Normal 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
|
Loading…
Reference in New Issue
Block a user