Commit e7630ec2 authored by Rob Clark's avatar Rob Clark 💬 Committed by Marge Bot

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

Signed-off-by: Rob Clark's avatarRob Clark <robdclark@chromium.org>
Part-of: <!7997>
parent 6309c931
ISASPEC - XML Based ISA Specification
=====================================
isaspec provides a mechanism to describe an instruction set in xml, and
generate a disassembler and assembler (eventually). The intention is
to describe the instruction set more formally than hand-coded assembler
and disassembler, and better decouple the shader compiler from the
underlying instruction encoding to simplify dealing with instruction
encoding differences between generations of GPU.
Benefits of a formal ISA description, compared to hand-coded assemblers
and disassemblers, include easier detection of new bit combintions that
were not seen before in previous generations due to more rigerous
description of bits that are expect to be '0' or '1' or 'x' (dontcare)
and verification that different encodings don't have conflicting bits
(ie. that the specification cannot result in more than one valid
interpretation of any bit pattern).
The isaspec tool and xml schema are intended to be generic (not specific
to ir3), although there are currently a couple limitations due to short-
cuts taken to get things up and running (which are mostly not inherent to
the xml schema, and should not be too difficult to remove from the py and
decode/disasm utility):
* Maximum "bitset" size is 64b
* Fixed instruction size
Often times, especially when new functionality is added in later gens
while retaining (or at least mostly retaining) backwards compatibility
with encodings used in earlier generations, the actual encoding can be
rather messy to describe. To support this, isaspec provides many flexible
mechanism, such as conditional overrides and derived fields. This not
only allows for describing an irregular instruction encoding, but also
allows matching an existing disasm syntax (which might not have been
design around the idea of disassembly based on a formal ISA description).
Bitsets
-------
The fundamental concept of matching a bit-pattern to an instruction
decoding/encoding is the concept of a hierarchial tree of bitsets.
This is intended to match how the hw decodes instructions, where certain
bits describe the instruction (and sub-encoding, and so on), and other
bits describe various operands to the instruction.
Bitsets can also be used recursively as the type of a field described
in another bitset.
The leaves of the tree of instruction bitsets represent every possible
instruction. Deciding which instruction a bitpattern is amounts to:
.. code-block:: c
m = (val & bitsets[n]->mask) & ~bitsets[n]->dontcare;
if (m == bitsets[n]->match) {
... we've found the instruction description ...
}
For example, the starting point to decode an ir3 instruction is a 64b
bitset:
.. code-block:: xml
<bitset name="#instruction" size="64">
<doc>
Encoding of an ir3 instruction. All instructions are 64b.
</doc>
</bitset>
In the first level of instruction encoding hierarchy, the high three bits
group things into instruction "categories":
.. code-block:: xml
<bitset name="#instruction-cat2" extends="#instruction">
<field name="DST" low="32" high="39" type="#reg-gpr"/>
<field name="REPEAT" low="40" high="41" type="#rptN"/>
<field name="SAT" pos="42" type="bool" display="(sat)"/>
<field name="SS" pos="44" type="bool" display="(ss)"/>
<field name="UL" pos="45" type="bool" display="(ul)"/>
<field name="DST_CONV" pos="46" type="bool">
<doc>
Destination register is opposite precision as source, ie.
if {FULL} is true then destination is half precision, and
visa versa.
</doc>
</field>
<derived name="DST_HALF" expr="#dest-half" type="bool" display="h"/>
<field name="EI" pos="47" type="bool" display="(ei)"/>
<field name="FULL" pos="52" type="bool">
<doc>Full precision source registers</doc>
</field>
<field name="JP" pos="59" type="bool" display="(jp)"/>
<field name="SY" pos="60" type="bool" display="(sy)"/>
<pattern low="61" high="63">010</pattern> <!-- cat2 -->
<!--
NOTE, both SRC1_R and SRC2_R are defined at this level because
SRC2_R is still a valid bit for (nopN) (REPEAT==0) for cat2
instructions with only a single src
-->
<field name="SRC1_R" pos="43" type="bool" display="(r)"/>
<field name="SRC2_R" pos="51" type="bool" display="(r)"/>
<derived name="ZERO" expr="#zero" type="bool" display=""/>
</bitset>
The ``<pattern>`` elements are the part(s) that determine which leaf-node
bitset matches against a given bit pattern. The leaf node's match/mask/
dontcare bitmasks are a combination of those defined at the leaf node and
recursively each parent bitclass.
For example, cat2 instructions (ALU instructions with up to two src
registers) can have either one or two source registers:
.. code-block:: xml
<bitset name="#instruction-cat2-1src" extends="#instruction-cat2">
<override expr="#cat2-cat3-nop-encoding">
<display>
{SY}{SS}{JP}{SAT}(nop{NOP}) {UL}{NAME} {EI}{DST_HALF}{DST}, {SRC1}
</display>
<derived name="NOP" expr="#cat2-cat3-nop-value" type="uint"/>
<field name="SRC1" low="0" high="15" type="#multisrc">
<param name="ZERO" as="SRC_R"/>
<param name="FULL"/>
</field>
</override>
<display>
{SY}{SS}{JP}{SAT}{REPEAT}{UL}{NAME} {EI}{DST_HALF}{DST}, {SRC1}
</display>
<pattern low="16" high="31">xxxxxxxxxxxxxxxx</pattern>
<pattern low="48" high="50">xxx</pattern> <!-- COND -->
<field name="SRC1" low="0" high="15" type="#multisrc">
<param name="SRC1_R" as="SRC_R"/>
<param name="FULL"/>
</field>
</bitset>
<bitset name="absneg.f" extends="#instruction-cat2-1src">
<pattern low="53" high="58">000110</pattern>
</bitset>
In this example, ``absneg.f`` is a concrete cat2 instruction (leaf node of
the bitset inheritance tree) which has a single src register. At the
``#instruction-cat2-1src`` level, bits that are used for the 2nd src arg
and condition code (for cat2 instructions which use a condition code) are
defined as 'x' (dontcare), which matches our understanding of the hardware
(but also lets the disassembler flag cases where '1' bits show up in places
we don't expect, which may signal a new instruction (sub)encoding).
You'll notice that ``SRC1`` refers back to a different bitset hierarchy
that describes various different src register encoding (used for cat2 and
cat4 instructions), ie. GPR vs CONST vs relative GPR/CONST. For fields
which have bitset types, parameters can be "passed" in via ``<param>``
elements, which can be referred to by the display template string, and/or
expressions. For example, this helps to deal with cases where other fields
outside of that bitset control the encoding/decoding, such as in the
``#multisrc`` example:
.. code-block:: xml
<bitset name="#multisrc" size="16">
<doc>
Encoding for instruction source which can be GPR/CONST/IMMED
or relative GPR/CONST.
</doc>
</bitset>
...
<bitset name="#multisrc-gpr" extends="#multisrc">
<display>
{ABSNEG}{SRC_R}{HALF}{SRC}
</display>
<derived name="HALF" expr="#multisrc-half" type="bool" display="h"/>
<field name="SRC" low="0" high="7" type="#reg-gpr"/>
<pattern low="8" high="13">000000</pattern>
<field name="ABSNEG" low="14" high="15" type="#absneg"/>
</bitset>
At some level in the bitset inheritance hiearchy, there is expected to be a
``<display>`` element specifying a template string used during bitset
decoding. The display template consists of references to fields (which may
be derived fields) specified as ``{FIELDNAME}`` and other characters
which are just echoed through to the resulting decoded bitset.
The ``<override>`` element will be described in the next section, but it
provides for both different decoded instruction syntax/mnemonics (when
simply providing a different display template string) as well as instruction
encoding where different ranges of bits have a different meaning based on
some other bitfield (or combination of bitfields). In this example it is
used to cover the cases where ``SRCn_R`` has a different meaning and a
different disassembly syntax depending on whether ``REPEAT`` equals zero.
Overrides
---------
In many cases, a bitset is not convenient for describing the expected
disasm syntax, and/or interpretation of some range of bits differs based
on some other field or combination of fields. These *could* be modeled
as different derived bitsets, at the expense of a combinatorical explosion
of the size of the bitset inheritance tree. For example, *every* cat2
(and cat3) instruction has both a ``(nopN)`` interpretation in addtion to
the ``(rptN`)`` interpretation.
An ``<override>`` in a bitset allows to redefine the display string, and/or
field definitions from the default case. If the override's expr(ession)
evaluates to non-zero, ``<display>``, ``<field>``, and ``<derived>``
elements take precedence over what is defined in the toplevel of the
bitset (ie. the default case).
Expressions
-----------
Both ``<override>`` and ``<derived>`` fields make use of ``<expr>`` elements,
either defined inline, or defined and named at the top level and referred to
by name in multiple other places. An expression is a simple 'C' expression
which can reference fields (including other derived fields) with the same
``{FIELDNAME}`` syntax as display template strings. For example:
.. code-block:: xml
<expr name="#cat2-cat3-nop-encoding">
(({SRC1_R} != 0) || ({SRC2_R} != 0)) &amp;&amp; ({REPEAT} == 0)
</expr>
In the case of ``<override>`` elements, the override applies if the expression
evaluates to non-zero. In the case of ``<derived>`` fields, the expression
evaluates to the value of the derived field.
Encoding
--------
To facilitate instruction encoding, ``<encode>`` elements can be provided
to teach the generated instruction packing code how to map from data structures
representing the IR to fields. For example:
.. code-block:: xml
<bitset name="#instruction" size="64">
<doc>
Encoding of an ir3 instruction. All instructions are 64b.
</doc>
<gen min="300"/>
<encode type="struct ir3_instruction *" case-prefix="OPC_">
<!--
Define mapping from encode src to individual fields,
which are common across all instruction categories
at the root instruction level
Not all of these apply to all instructions, but we
can define mappings here for anything that is used
in more than one instruction category. For things
that are specific to a single instruction category,
mappings should be defined at that level instead.
-->
<map name="DST">src->regs[0]</map>
<map name="SRC1">src->regs[1]</map>
<map name="SRC2">src->regs[2]</map>
<map name="SRC3">src->regs[3]</map>
<map name="REPEAT">src->repeat</map>
<map name="SS">!!(src->flags &amp; IR3_INSTR_SS)</map>
<map name="JP">!!(src->flags &amp; IR3_INSTR_JP)</map>
<map name="SY">!!(src->flags &amp; IR3_INSTR_SY)</map>
<map name="UL">!!(src->flags &amp; IR3_INSTR_UL)</map>
<map name="EQ">0</map> <!-- We don't use this (yet) -->
<map name="SAT">!!(src->flags &amp; IR3_INSTR_SAT)</map>
</encode>
</bitset>
The ``type`` attribute specifies that the input to encoding an instruction
is a ``struct ir3_instruction *``. In the case of bitset hierarchies with
multiple possible leaf nodes, a ``case-prefix`` attribute should be supplied
along with a function that maps the bitset encode source to an enum value
with the specified prefix prepended to uppercase'd leaf node name. Ie. in
this case, "add.f" becomes ``OPC_ADD_F``.
Individual ``<map>`` elements teach the encoder how to map from the encode
source to fields in the encoded instruction.
\ No newline at end of file
../../../docs/drivers/freedreno/isaspec.rst
\ No newline at end of file
/*
* 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,