mirror of https://gitlab.freedesktop.org/mesa/mesa
504 lines
18 KiB
C
504 lines
18 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
|
|
#include "util/ralloc.h"
|
|
|
|
#include "ir3.h"
|
|
#include "ir3_compiler.h"
|
|
|
|
struct ir3_validate_ctx {
|
|
struct ir3 *ir;
|
|
|
|
/* Current block being validated: */
|
|
struct ir3_block *current_block;
|
|
|
|
/* Current instruction being validated: */
|
|
struct ir3_instruction *current_instr;
|
|
|
|
/* Set of instructions found so far, used to validate that we
|
|
* don't have SSA uses that occure before def's
|
|
*/
|
|
struct set *defs;
|
|
};
|
|
|
|
static void
|
|
validate_error(struct ir3_validate_ctx *ctx, const char *condstr)
|
|
{
|
|
fprintf(stderr, "validation fail: %s\n", condstr);
|
|
if (ctx->current_instr) {
|
|
fprintf(stderr, " -> for instruction: ");
|
|
ir3_print_instr(ctx->current_instr);
|
|
} else {
|
|
fprintf(stderr, " -> for block%u\n", block_id(ctx->current_block));
|
|
}
|
|
abort();
|
|
}
|
|
|
|
#define validate_assert(ctx, cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
validate_error(ctx, #cond); \
|
|
} \
|
|
} while (0)
|
|
|
|
static unsigned
|
|
reg_class_flags(struct ir3_register *reg)
|
|
{
|
|
return reg->flags & (IR3_REG_HALF | IR3_REG_SHARED | IR3_REG_PREDICATE);
|
|
}
|
|
|
|
static void
|
|
validate_reg(struct ir3_validate_ctx *ctx, struct ir3_register *reg)
|
|
{
|
|
if ((reg->flags & IR3_REG_SHARED) && reg->num != INVALID_REG) {
|
|
validate_assert(ctx, reg->num >= SHARED_REG_START);
|
|
validate_assert(ctx, reg->num - SHARED_REG_START < SHARED_REG_SIZE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
validate_src(struct ir3_validate_ctx *ctx, struct ir3_instruction *instr,
|
|
struct ir3_register *reg)
|
|
{
|
|
if (reg->flags & IR3_REG_IMMED)
|
|
validate_assert(ctx, ir3_valid_immediate(instr, reg->iim_val));
|
|
|
|
if (!(reg->flags & IR3_REG_SSA) || !reg->def)
|
|
return;
|
|
|
|
if (reg->flags & IR3_REG_PREDICATE)
|
|
validate_assert(ctx, !(reg->flags & (IR3_REG_SHARED | IR3_REG_HALF)));
|
|
|
|
struct ir3_register *src = reg->def;
|
|
|
|
validate_assert(ctx, _mesa_set_search(ctx->defs, src->instr));
|
|
validate_assert(ctx, src->wrmask == reg->wrmask);
|
|
validate_assert(ctx, reg_class_flags(src) == reg_class_flags(reg));
|
|
|
|
if (src->flags & IR3_REG_CONST)
|
|
validate_assert(ctx, !(src->flags & IR3_REG_SHARED));
|
|
|
|
if (reg->tied) {
|
|
validate_assert(ctx, reg->tied->tied == reg);
|
|
validate_assert(ctx, reg_class_flags(reg) == reg_class_flags(reg->tied));
|
|
validate_assert(ctx, !(reg->flags & (IR3_REG_CONST | IR3_REG_IMMED)));
|
|
bool found = false;
|
|
foreach_dst (dst, instr) {
|
|
if (dst == reg->tied) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
validate_assert(ctx,
|
|
found && "tied register not in the same instruction");
|
|
}
|
|
|
|
validate_reg(ctx, reg);
|
|
}
|
|
|
|
/* phi sources are logically read at the end of the predecessor basic block,
|
|
* and we have to validate them then in order to correctly validate that the
|
|
* use comes after the definition for loop phis.
|
|
*/
|
|
static void
|
|
validate_phi_src(struct ir3_validate_ctx *ctx, struct ir3_block *block,
|
|
struct ir3_block *pred)
|
|
{
|
|
unsigned pred_idx = ir3_block_get_pred_index(block, pred);
|
|
|
|
foreach_instr (phi, &block->instr_list) {
|
|
if (phi->opc != OPC_META_PHI)
|
|
break;
|
|
|
|
ctx->current_instr = phi;
|
|
validate_assert(ctx, phi->srcs_count == block->predecessors_count);
|
|
validate_src(ctx, phi, phi->srcs[pred_idx]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
validate_phi(struct ir3_validate_ctx *ctx, struct ir3_instruction *phi)
|
|
{
|
|
_mesa_set_add(ctx->defs, phi);
|
|
validate_assert(ctx, phi->dsts_count == 1);
|
|
validate_assert(ctx, is_dest_gpr(phi->dsts[0]));
|
|
}
|
|
|
|
static void
|
|
validate_dst(struct ir3_validate_ctx *ctx, struct ir3_instruction *instr,
|
|
struct ir3_register *reg)
|
|
{
|
|
if (reg->tied) {
|
|
validate_assert(ctx, reg->tied->tied == reg);
|
|
validate_assert(ctx, reg_class_flags(reg->tied) == reg_class_flags(reg));
|
|
validate_assert(ctx, reg->tied->wrmask == reg->wrmask);
|
|
if (reg->flags & IR3_REG_ARRAY) {
|
|
validate_assert(ctx, reg->tied->array.base == reg->array.base);
|
|
validate_assert(ctx, reg->tied->size == reg->size);
|
|
}
|
|
bool found = false;
|
|
foreach_src (src, instr) {
|
|
if (src == reg->tied) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
validate_assert(ctx,
|
|
found && "tied register not in the same instruction");
|
|
}
|
|
|
|
if (reg->flags & IR3_REG_SSA)
|
|
validate_assert(ctx, reg->instr == instr);
|
|
|
|
if (reg->flags & IR3_REG_RELATIV)
|
|
validate_assert(ctx, instr->address);
|
|
|
|
validate_reg(ctx, reg);
|
|
}
|
|
|
|
#define validate_reg_size(ctx, reg, type) \
|
|
validate_assert( \
|
|
ctx, (type_size(type) <= 16) == !!((reg)->flags & IR3_REG_HALF))
|
|
|
|
static void
|
|
validate_instr(struct ir3_validate_ctx *ctx, struct ir3_instruction *instr)
|
|
{
|
|
struct ir3_register *last_reg = NULL;
|
|
|
|
foreach_src_n (reg, n, instr) {
|
|
if (reg->flags & IR3_REG_RELATIV)
|
|
validate_assert(ctx, instr->address);
|
|
|
|
validate_src(ctx, instr, reg);
|
|
|
|
/* Validate that all src's are either half of full.
|
|
*
|
|
* Note: tex instructions w/ .s2en are a bit special in that the
|
|
* tex/samp src reg is half-reg for non-bindless and full for
|
|
* bindless, irrespective of the precision of other srcs. The
|
|
* tex/samp src is the first src reg when .s2en is set
|
|
*/
|
|
if (reg->tied) {
|
|
/* must have the same size as the destination, handled in
|
|
* validate_reg().
|
|
*/
|
|
} else if (reg == instr->address) {
|
|
validate_assert(ctx, reg->flags & IR3_REG_HALF);
|
|
} else if ((instr->flags & IR3_INSTR_S2EN) && (n < 2)) {
|
|
if (n == 0) {
|
|
if (instr->flags & IR3_INSTR_B)
|
|
validate_assert(ctx, !(reg->flags & IR3_REG_HALF));
|
|
else
|
|
validate_assert(ctx, reg->flags & IR3_REG_HALF);
|
|
}
|
|
} else if (opc_cat(instr->opc) == 1 || opc_cat(instr->opc) == 6) {
|
|
/* handled below */
|
|
} else if (opc_cat(instr->opc) == 0) {
|
|
/* end/chmask/etc are allowed to have different size sources */
|
|
} else if (instr->opc == OPC_META_PARALLEL_COPY) {
|
|
/* pcopy sources have to match with their destination but can have
|
|
* different sizes from each other.
|
|
*/
|
|
} else if (instr->opc == OPC_ANY_MACRO || instr->opc == OPC_ALL_MACRO ||
|
|
instr->opc == OPC_READ_FIRST_MACRO ||
|
|
instr->opc == OPC_READ_COND_MACRO) {
|
|
/* nothing yet */
|
|
} else if (n > 0) {
|
|
validate_assert(ctx, (last_reg->flags & IR3_REG_HALF) ==
|
|
(reg->flags & IR3_REG_HALF));
|
|
}
|
|
|
|
if (is_scalar_alu(instr, ctx->ir->compiler) && reg != instr->address)
|
|
validate_assert(ctx, reg->flags & (IR3_REG_SHARED | IR3_REG_IMMED |
|
|
IR3_REG_CONST));
|
|
|
|
last_reg = reg;
|
|
}
|
|
|
|
for (unsigned i = 0; i < instr->dsts_count; i++) {
|
|
struct ir3_register *reg = instr->dsts[i];
|
|
|
|
validate_dst(ctx, instr, reg);
|
|
}
|
|
|
|
_mesa_set_add(ctx->defs, instr);
|
|
|
|
if ((opc_cat(instr->opc) == 2 || opc_cat(instr->opc) == 3 ||
|
|
opc_cat(instr->opc) == 4)) {
|
|
validate_assert(ctx, !(instr->dsts[0]->flags & IR3_REG_SHARED) ||
|
|
ctx->ir->compiler->has_scalar_alu);
|
|
}
|
|
|
|
/* Check that src/dst types match the register types, and for
|
|
* instructions that have different opcodes depending on type,
|
|
* that the opcodes are correct.
|
|
*/
|
|
switch (opc_cat(instr->opc)) {
|
|
case 1: /* move instructions */
|
|
if (instr->opc == OPC_MOVMSK || instr->opc == OPC_BALLOT_MACRO) {
|
|
validate_assert(ctx, instr->dsts_count == 1);
|
|
validate_assert(ctx, instr->dsts[0]->flags & IR3_REG_SHARED);
|
|
validate_assert(ctx, !(instr->dsts[0]->flags & IR3_REG_HALF));
|
|
validate_assert(
|
|
ctx, util_is_power_of_two_or_zero(instr->dsts[0]->wrmask + 1));
|
|
} else if (instr->opc == OPC_ANY_MACRO || instr->opc == OPC_ALL_MACRO ||
|
|
instr->opc == OPC_READ_FIRST_MACRO ||
|
|
instr->opc == OPC_READ_COND_MACRO) {
|
|
/* nothing yet */
|
|
} else if (instr->opc == OPC_ELECT_MACRO || instr->opc == OPC_SHPS_MACRO) {
|
|
validate_assert(ctx, instr->dsts_count == 1);
|
|
validate_assert(ctx, !(instr->dsts[0]->flags & IR3_REG_SHARED));
|
|
} else if (instr->opc == OPC_SCAN_MACRO) {
|
|
validate_assert(ctx, instr->dsts_count == 3);
|
|
validate_assert(ctx, instr->srcs_count == 2);
|
|
validate_assert(ctx, reg_class_flags(instr->dsts[0]) ==
|
|
reg_class_flags(instr->srcs[0]));
|
|
validate_assert(ctx, reg_class_flags(instr->dsts[1]) ==
|
|
reg_class_flags(instr->srcs[0]));
|
|
validate_assert(ctx, reg_class_flags(instr->dsts[2]) == IR3_REG_SHARED);
|
|
} else if (instr->opc == OPC_SCAN_CLUSTERS_MACRO) {
|
|
validate_assert(ctx, instr->dsts_count >= 2 && instr->dsts_count < 5);
|
|
validate_assert(ctx, instr->srcs_count >= 2 && instr->srcs_count < 4);
|
|
validate_assert(ctx,
|
|
reg_class_flags(instr->dsts[0]) == IR3_REG_SHARED);
|
|
validate_assert(ctx, reg_class_flags(instr->dsts[1]) ==
|
|
reg_class_flags(instr->srcs[1]));
|
|
|
|
/* exclusive scan */
|
|
if (instr->srcs_count == 3) {
|
|
validate_assert(ctx, instr->dsts_count >= 3);
|
|
validate_assert(ctx, reg_class_flags(instr->srcs[2]) ==
|
|
reg_class_flags(instr->srcs[1]));
|
|
validate_assert(ctx, reg_class_flags(instr->dsts[2]) ==
|
|
reg_class_flags(instr->srcs[1]));
|
|
}
|
|
|
|
/* scratch register */
|
|
validate_assert(ctx,
|
|
reg_class_flags(instr->dsts[instr->dsts_count - 1]) ==
|
|
reg_class_flags(instr->srcs[1]));
|
|
} else {
|
|
foreach_dst (dst, instr)
|
|
validate_reg_size(ctx, dst, instr->cat1.dst_type);
|
|
foreach_src (src, instr) {
|
|
if (!src->tied && src != instr->address)
|
|
validate_reg_size(ctx, src, instr->cat1.src_type);
|
|
}
|
|
|
|
switch (instr->opc) {
|
|
case OPC_SWZ:
|
|
validate_assert(ctx, instr->srcs_count == 2);
|
|
validate_assert(ctx, instr->dsts_count == 2);
|
|
break;
|
|
case OPC_GAT:
|
|
validate_assert(ctx, instr->srcs_count == 4);
|
|
validate_assert(ctx, instr->dsts_count == 1);
|
|
break;
|
|
case OPC_SCT:
|
|
validate_assert(ctx, instr->srcs_count == 1);
|
|
validate_assert(ctx, instr->dsts_count == 4);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (instr->opc != OPC_MOV)
|
|
validate_assert(ctx, !instr->address);
|
|
|
|
break;
|
|
case 3:
|
|
/* Validate that cat3 opc matches the src type. We've already checked
|
|
* that all the src regs are same type
|
|
*/
|
|
if (instr->srcs[0]->flags & IR3_REG_HALF) {
|
|
validate_assert(ctx, instr->opc == cat3_half_opc(instr->opc));
|
|
} else {
|
|
validate_assert(ctx, instr->opc == cat3_full_opc(instr->opc));
|
|
}
|
|
break;
|
|
case 4:
|
|
/* Validate that cat4 opc matches the dst type: */
|
|
if (instr->dsts[0]->flags & IR3_REG_HALF) {
|
|
validate_assert(ctx, instr->opc == cat4_half_opc(instr->opc));
|
|
} else {
|
|
validate_assert(ctx, instr->opc == cat4_full_opc(instr->opc));
|
|
}
|
|
break;
|
|
case 5:
|
|
validate_reg_size(ctx, instr->dsts[0], instr->cat5.type);
|
|
break;
|
|
case 6:
|
|
switch (instr->opc) {
|
|
case OPC_RESINFO:
|
|
case OPC_RESFMT:
|
|
validate_reg_size(ctx, instr->dsts[0], instr->cat6.type);
|
|
validate_reg_size(ctx, instr->srcs[0], instr->cat6.type);
|
|
break;
|
|
case OPC_L2G:
|
|
case OPC_G2L:
|
|
validate_assert(ctx, !(instr->dsts[0]->flags & IR3_REG_HALF));
|
|
validate_assert(ctx, !(instr->srcs[0]->flags & IR3_REG_HALF));
|
|
break;
|
|
case OPC_STG:
|
|
validate_assert(ctx, !(instr->srcs[0]->flags & IR3_REG_HALF));
|
|
validate_assert(ctx, !(instr->srcs[1]->flags & IR3_REG_HALF));
|
|
validate_reg_size(ctx, instr->srcs[2], instr->cat6.type);
|
|
validate_assert(ctx, !(instr->srcs[3]->flags & IR3_REG_HALF));
|
|
break;
|
|
case OPC_STG_A:
|
|
validate_assert(ctx, !(instr->srcs[0]->flags & IR3_REG_HALF));
|
|
validate_assert(ctx, !(instr->srcs[2]->flags & IR3_REG_HALF));
|
|
validate_assert(ctx, !(instr->srcs[3]->flags & IR3_REG_HALF));
|
|
validate_reg_size(ctx, instr->srcs[4], instr->cat6.type);
|
|
validate_assert(ctx, !(instr->srcs[5]->flags & IR3_REG_HALF));
|
|
break;
|
|
case OPC_STL:
|
|
case OPC_STP:
|
|
case OPC_STLW:
|
|
case OPC_SPILL_MACRO:
|
|
validate_assert(ctx, !(instr->srcs[0]->flags & IR3_REG_HALF));
|
|
validate_reg_size(ctx, instr->srcs[1], instr->cat6.type);
|
|
validate_assert(ctx, !(instr->srcs[2]->flags & IR3_REG_HALF));
|
|
break;
|
|
case OPC_STIB:
|
|
validate_assert(ctx, !(instr->srcs[0]->flags & IR3_REG_HALF));
|
|
validate_assert(ctx, !(instr->srcs[1]->flags & IR3_REG_HALF));
|
|
validate_reg_size(ctx, instr->srcs[2], instr->cat6.type);
|
|
break;
|
|
case OPC_GETFIBERID:
|
|
case OPC_GETSPID:
|
|
case OPC_GETWID:
|
|
validate_reg_size(ctx, instr->dsts[0], instr->cat6.type);
|
|
break;
|
|
case OPC_STC:
|
|
case OPC_STSC:
|
|
validate_reg_size(ctx, instr->srcs[0], instr->cat6.type);
|
|
validate_assert(ctx, !(instr->srcs[1]->flags & IR3_REG_HALF));
|
|
break;
|
|
case OPC_PUSH_CONSTS_LOAD_MACRO:
|
|
break;
|
|
case OPC_LDC:
|
|
validate_assert(ctx, !(instr->srcs[0]->flags & IR3_REG_HALF));
|
|
validate_assert(ctx, !(instr->srcs[1]->flags & IR3_REG_HALF));
|
|
validate_assert(ctx, !!(instr->dsts[0]->flags & IR3_REG_SHARED) ==
|
|
!!(instr->flags & IR3_INSTR_U));
|
|
break;
|
|
case OPC_LDC_K:
|
|
validate_assert(ctx, !(instr->srcs[0]->flags & IR3_REG_HALF));
|
|
validate_assert(ctx, !(instr->srcs[1]->flags & IR3_REG_HALF));
|
|
break;
|
|
default:
|
|
validate_reg_size(ctx, instr->dsts[0], instr->cat6.type);
|
|
validate_assert(ctx, !(instr->srcs[0]->flags & IR3_REG_HALF));
|
|
if (instr->srcs_count > 1)
|
|
validate_assert(ctx, !(instr->srcs[1]->flags & IR3_REG_HALF));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (instr->opc == OPC_META_PARALLEL_COPY) {
|
|
foreach_src_n (src, n, instr) {
|
|
validate_assert(ctx, (src->flags & IR3_REG_HALF) ==
|
|
(instr->dsts[n]->flags & IR3_REG_HALF));
|
|
if (instr->dsts[n]->flags & IR3_REG_SHARED) {
|
|
validate_assert(ctx, src->flags & (IR3_REG_SHARED | IR3_REG_CONST |
|
|
IR3_REG_IMMED));
|
|
} else {
|
|
validate_assert(ctx, !(src->flags & IR3_REG_SHARED));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
is_physical_successor(struct ir3_block *block, struct ir3_block *succ)
|
|
{
|
|
for (unsigned i = 0; i < block->physical_successors_count; i++)
|
|
if (block->physical_successors[i] == succ)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void
|
|
ir3_validate(struct ir3 *ir)
|
|
{
|
|
#ifdef NDEBUG
|
|
#define VALIDATE 0
|
|
#else
|
|
#define VALIDATE 1
|
|
#endif
|
|
|
|
if (!VALIDATE)
|
|
return;
|
|
|
|
struct ir3_validate_ctx *ctx = ralloc_size(NULL, sizeof(*ctx));
|
|
|
|
ctx->ir = ir;
|
|
ctx->defs = _mesa_pointer_set_create(ctx);
|
|
|
|
foreach_block (block, &ir->block_list) {
|
|
ctx->current_block = block;
|
|
ctx->current_instr = NULL;
|
|
|
|
/* We require that the first block does not have any predecessors,
|
|
* which allows us to assume that phi nodes and meta:input's do not
|
|
* appear in the same basic block.
|
|
*/
|
|
validate_assert(
|
|
ctx, block != ir3_start_block(ir) || block->predecessors_count == 0);
|
|
|
|
struct ir3_instruction *prev = NULL;
|
|
foreach_instr (instr, &block->instr_list) {
|
|
validate_assert(ctx, instr->block == block);
|
|
ctx->current_instr = instr;
|
|
if (instr->opc == OPC_META_PHI) {
|
|
/* phis must be the first in the block */
|
|
validate_assert(ctx, prev == NULL || prev->opc == OPC_META_PHI);
|
|
validate_phi(ctx, instr);
|
|
} else {
|
|
validate_instr(ctx, instr);
|
|
}
|
|
prev = instr;
|
|
}
|
|
|
|
for (unsigned i = 0; i < 2; i++) {
|
|
if (block->successors[i]) {
|
|
validate_phi_src(ctx, block->successors[i], block);
|
|
|
|
ctx->current_instr = NULL;
|
|
|
|
/* Each logical successor should also be a physical successor: */
|
|
if (block->physical_successors_count > 0)
|
|
validate_assert(ctx, is_physical_successor(block, block->successors[i]));
|
|
}
|
|
}
|
|
|
|
validate_assert(ctx, block->successors[0] || !block->successors[1]);
|
|
}
|
|
|
|
ralloc_free(ctx);
|
|
}
|