mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2026-05-05 20:28:04 +02:00
ir3: allow asm roundtrip testing of compiled shader variants
The `asmroundtrip` IR3_SHADER_DEBUG option enables roundtrip testing of ir3 asm facilities by generating disassembly for each compiled shader variant, parsing that disassembly back into ir3 and assembling back into binary, with the expectation that the initial binary and the post-roundtrip binary are identical. This should give some guarantee that any shader that ir3 can produce can also be constructed through assembly and fed back into ir3. When enabled, each shader variant has a parallel roundtrip variant created. At the moment this variant is discarded after validation, but it could replace the initial variant in the future to also test behavior of such roundtrip-generated binary and accompanying metadata. Signed-off-by: Zan Dobersek <zdobersek@igalia.com> Reviewed-by: Danylo Piliaiev <dpiliaiev@igalia.com> Reviewed-by: Job Noorman <jnoorman@igalia.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/34076>
This commit is contained in:
parent
0acf46b973
commit
b8cc891e6e
4 changed files with 202 additions and 9 deletions
|
|
@ -36,6 +36,7 @@ static const struct debug_named_value shader_debug_options[] = {
|
|||
{"expandrpt", IR3_DBG_EXPANDRPT, "Expand rptN instructions"},
|
||||
{"noaliastex", IR3_DBG_NOALIASTEX, "Don't use alias.tex"},
|
||||
{"noaliasrt", IR3_DBG_NOALIASRT, "Don't use alias.rt"},
|
||||
{"asmroundtrip", IR3_DBG_ASM_ROUNDTRIP, "Disassemble, reassemble and compare every shader"},
|
||||
#if MESA_DEBUG
|
||||
/* MESA_DEBUG-only options: */
|
||||
{"schedmsgs", IR3_DBG_SCHEDMSGS, "Enable scheduler debug messages"},
|
||||
|
|
|
|||
|
|
@ -369,12 +369,13 @@ enum ir3_shader_debug {
|
|||
IR3_DBG_NOEARLYPREAMBLE = BITFIELD_BIT(17),
|
||||
IR3_DBG_NODESCPREFETCH = BITFIELD_BIT(18),
|
||||
IR3_DBG_EXPANDRPT = BITFIELD_BIT(19),
|
||||
IR3_DBG_ASM_ROUNDTRIP = BITFIELD_BIT(20),
|
||||
|
||||
/* MESA_DEBUG-only options: */
|
||||
IR3_DBG_SCHEDMSGS = BITFIELD_BIT(20),
|
||||
IR3_DBG_RAMSGS = BITFIELD_BIT(21),
|
||||
IR3_DBG_NOALIASTEX = BITFIELD_BIT(22),
|
||||
IR3_DBG_NOALIASRT = BITFIELD_BIT(23),
|
||||
IR3_DBG_SCHEDMSGS = BITFIELD_BIT(21),
|
||||
IR3_DBG_RAMSGS = BITFIELD_BIT(22),
|
||||
IR3_DBG_NOALIASTEX = BITFIELD_BIT(23),
|
||||
IR3_DBG_NOALIASRT = BITFIELD_BIT(24),
|
||||
};
|
||||
|
||||
extern enum ir3_shader_debug ir3_shader_debug;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ struct ir3 * ir3_parse(struct ir3_shader_variant *v,
|
|||
#define IR3_REG_ABS IR3_REG_FABS
|
||||
#define IR3_REG_NEGATE IR3_REG_FNEG
|
||||
|
||||
static pthread_mutex_t ir3_parse_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static struct ir3_kernel_info *info;
|
||||
static struct ir3_shader_variant *variant;
|
||||
/* NOTE the assembler doesn't really use the ir3_block construction
|
||||
|
|
@ -337,6 +339,8 @@ static void yyerror(const char *error)
|
|||
struct ir3 * ir3_parse(struct ir3_shader_variant *v,
|
||||
struct ir3_kernel_info *k, FILE *f)
|
||||
{
|
||||
pthread_mutex_lock(&ir3_parse_mtx);
|
||||
|
||||
ir3_yyset_lineno(1);
|
||||
ir3_yyset_input(f);
|
||||
#ifdef YYDEBUG
|
||||
|
|
@ -354,7 +358,10 @@ struct ir3 * ir3_parse(struct ir3_shader_variant *v,
|
|||
}
|
||||
ralloc_free(labels);
|
||||
ralloc_free(ir3_parser_dead_ctx);
|
||||
return variant->ir;
|
||||
|
||||
struct ir3 *ir = variant->ir;
|
||||
pthread_mutex_unlock(&ir3_parse_mtx);
|
||||
return ir;
|
||||
}
|
||||
%}
|
||||
|
||||
|
|
|
|||
|
|
@ -222,6 +222,174 @@ try_override_shader_variant(struct ir3_shader_variant *v,
|
|||
return true;
|
||||
}
|
||||
|
||||
struct disasm_context {
|
||||
FILE *stream;
|
||||
uint8_t *mismatch_array;
|
||||
};
|
||||
|
||||
static void
|
||||
disasm_pre_instr_cb(void *cbdata, unsigned n, void *instr)
|
||||
{
|
||||
struct disasm_context *context = (struct disasm_context *)cbdata;
|
||||
bool mismatch = context->mismatch_array && context->mismatch_array[n];
|
||||
uint32_t *dwords = (uint32_t *)instr;
|
||||
fprintf(context->stream, " %s [%03d] [%08x_%08x] ",
|
||||
mismatch ? "!!" : " ", n, dwords[1], dwords[0]);
|
||||
}
|
||||
|
||||
static void
|
||||
disasm_no_match_cb(FILE *stream, const BITSET_WORD *dwords, size_t size)
|
||||
{
|
||||
fprintf(stream, " XX [000] raw 0x%X%X\n", dwords[0], dwords[1]);
|
||||
}
|
||||
|
||||
static char *
|
||||
disasm_collect(struct ir3_shader_variant *v, uint8_t *mismatch_array,
|
||||
uint32_t *binary_data, uint32_t binary_size)
|
||||
{
|
||||
char *stream_data = NULL;
|
||||
size_t stream_size = 0;
|
||||
FILE *stream = open_memstream(&stream_data, &stream_size);
|
||||
|
||||
struct disasm_context context = {
|
||||
.stream = stream,
|
||||
.mismatch_array = mismatch_array,
|
||||
};
|
||||
|
||||
struct isa_decode_options decode_options = {
|
||||
.gpu_id = v->ir->compiler->gen * 100,
|
||||
.show_errors = true,
|
||||
.branch_labels = true,
|
||||
.cbdata = &context,
|
||||
.pre_instr_cb = disasm_pre_instr_cb,
|
||||
.no_match_cb = disasm_no_match_cb,
|
||||
};
|
||||
|
||||
ir3_isa_disasm(binary_data, binary_size, stream, &decode_options);
|
||||
|
||||
fclose(stream);
|
||||
return stream_data;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
variant_unpadded_binary_size(struct ir3_shader_variant *v)
|
||||
{
|
||||
/* This helper returns the size (in dwords) of variant's binary after
|
||||
* the padding nops at the end are ignored.
|
||||
*/
|
||||
uint16_t size = v->info.sizedwords;
|
||||
|
||||
for (uint16_t i = 0; i < v->info.sizedwords; i += 2) {
|
||||
uint32_t *dword = &v->bin[v->info.sizedwords - 2 - i];
|
||||
if (!!dword[0] || !!dword[1])
|
||||
break;
|
||||
|
||||
size -= 2;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void
|
||||
validate_print_disasm(struct ir3_shader_variant *v, uint8_t *mismatch_array)
|
||||
{
|
||||
char *disasm = disasm_collect(v, mismatch_array,
|
||||
v->bin, variant_unpadded_binary_size(v) * 4);
|
||||
mesa_loge("\n%s", disasm);
|
||||
free(disasm);
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_roundtrip_variant_binary(struct ir3_shader_variant *rt_v, struct ir3_shader_variant *v)
|
||||
{
|
||||
/* Ignoring padding nops in both variants, compare the binary data.
|
||||
* If there's a mismatch, print both disassemblies with highlighted
|
||||
* points of difference.
|
||||
*/
|
||||
uint16_t v_sizedwords = variant_unpadded_binary_size(v);
|
||||
uint16_t rt_v_sizedwords = variant_unpadded_binary_size(rt_v);
|
||||
if (v_sizedwords == rt_v_sizedwords &&
|
||||
!memcmp(v->bin, rt_v->bin, v_sizedwords * 4))
|
||||
return true;
|
||||
|
||||
mesa_loge("validate_roundtrip_variant_binary: mismatch between initial and reassembled binary\n");
|
||||
|
||||
uint32_t max_sizedwords = MAX2(v_sizedwords, rt_v_sizedwords);
|
||||
uint8_t *mismatch_array = calloc(max_sizedwords / 2, sizeof(uint8_t));
|
||||
|
||||
for (uint32_t i = 0; i < max_sizedwords; i += 2) {
|
||||
if (i >= v_sizedwords || i >= rt_v_sizedwords) {
|
||||
mismatch_array[i / 2] = 0xff;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t *v_dword = &v->bin[i];
|
||||
uint32_t *rt_v_dword = &rt_v->bin[i];
|
||||
if (v_dword[0] != rt_v_dword[0] || v_dword[1] != rt_v_dword[1])
|
||||
mismatch_array[i / 2] = 0xff;
|
||||
}
|
||||
|
||||
mesa_loge(" disassembly of initial binary:");
|
||||
validate_print_disasm(v, mismatch_array);
|
||||
|
||||
mesa_loge(" disassembly of reassembled binary:");
|
||||
validate_print_disasm(rt_v, mismatch_array);
|
||||
|
||||
free(mismatch_array);
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct ir3_shader_variant *
|
||||
alloc_variant(struct ir3_shader *shader, const struct ir3_shader_key *key,
|
||||
struct ir3_shader_variant *nonbinning, void *mem_ctx);
|
||||
|
||||
static struct ir3_shader_variant *
|
||||
create_roundtrip_variant(struct ir3_shader *shader, struct ir3_shader_variant *v)
|
||||
{
|
||||
struct ir3_shader_variant *rt_v = alloc_variant(shader, &v->key, NULL, NULL);
|
||||
if (!rt_v)
|
||||
return NULL;
|
||||
|
||||
/* Dump variant's disassembly into a memory stream, then read and
|
||||
* parse from that stream to assemble the roundtrip variant.
|
||||
*/
|
||||
char *disasm_data = NULL;
|
||||
size_t disasm_size = 0;
|
||||
FILE *disasm_stream = open_memstream(&disasm_data, &disasm_size);
|
||||
ir3_shader_disasm(v, v->bin, disasm_stream);
|
||||
fflush(disasm_stream);
|
||||
|
||||
struct ir3_kernel_info info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.numwg = INVALID_REG;
|
||||
|
||||
fseek(disasm_stream, 0, SEEK_SET);
|
||||
rt_v->ir = ir3_parse(rt_v, &info, disasm_stream);
|
||||
|
||||
fclose(disasm_stream);
|
||||
free(disasm_data);
|
||||
|
||||
if (!rt_v->ir) {
|
||||
mesa_loge("create_roundtrip_variant: failed to parse initial disassembly");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rt_v->bin = ir3_shader_assemble(rt_v);
|
||||
if (!rt_v->bin) {
|
||||
mesa_loge("create_roundtrip_variant: failed to assemble parsed initial disassembly");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!validate_roundtrip_variant_binary(rt_v, v))
|
||||
goto fail;
|
||||
|
||||
return rt_v;
|
||||
|
||||
fail:
|
||||
ralloc_free(rt_v);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
assemble_variant(struct ir3_shader_variant *v, bool internal)
|
||||
{
|
||||
|
|
@ -275,10 +443,6 @@ assemble_variant(struct ir3_shader_variant *v, bool internal)
|
|||
free(stream_data);
|
||||
}
|
||||
}
|
||||
|
||||
/* no need to keep the ir around beyond this point: */
|
||||
ir3_destroy(v->ir);
|
||||
v->ir = NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
@ -298,6 +462,26 @@ compile_variant(struct ir3_shader *shader, struct ir3_shader_variant *v)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (ir3_shader_debug & IR3_DBG_ASM_ROUNDTRIP) {
|
||||
struct ir3_shader_variant *rt_v = create_roundtrip_variant(shader, v);
|
||||
if (!rt_v)
|
||||
return false;
|
||||
|
||||
/* TODO: the roundtrip variant could replace the initial variant
|
||||
* to also test the gathered variant information that's then emitted
|
||||
* into shader state. Some known problems:
|
||||
* - parsing from assembly will lack constant data that's filled
|
||||
* in ir3_nir_lower_load_constant during compilation
|
||||
* - parsing from assembly will also lack metadata (e.g. writemasks)
|
||||
* that's used to determine register footprint
|
||||
*/
|
||||
ralloc_free(rt_v);
|
||||
}
|
||||
|
||||
/* no need to keep the ir around beyond this point: */
|
||||
ir3_destroy(v->ir);
|
||||
v->ir = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue