From 3fec03c1595106098ad912d927b368bc52ca5c66 Mon Sep 17 00:00:00 2001 From: Yangwen Huang Date: Mon, 11 May 2026 17:00:55 +0900 Subject: [PATCH] [stinkytofu] Fix verifier false positive for SGPR source on VOP2/VOPC instructions The register type checker only consulted the base encoding's operand fields. VOP2/VOP2_COMMUTATIVE formats define src1 as vgpr, but the assembler promotes to VOP3 encoding when an SGPR appears, where src1 accepts both VGPR and SGPR. The verifier now falls back to promotedFields before reporting a type mismatch. Adds FileCheck test verifier_vop2_sgpr_src.stir covering v_mul_u32_u24, v_cmp_eq_u32, v_cmp_gt_u32, and v_cmp_ge_u32 with SGPR in src1. --- .../src/analysis/asm/AsmVerifierPass.cpp | 22 +++++++-- .../filecheck/verifier_vop2_sgpr_src.stir | 46 +++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 shared/stinkytofu/tests/filecheck/verifier_vop2_sgpr_src.stir diff --git a/shared/stinkytofu/src/analysis/asm/AsmVerifierPass.cpp b/shared/stinkytofu/src/analysis/asm/AsmVerifierPass.cpp index d39b786e2b44..c8b411c8da2f 100644 --- a/shared/stinkytofu/src/analysis/asm/AsmVerifierPass.cpp +++ b/shared/stinkytofu/src/analysis/asm/AsmVerifierPass.cpp @@ -173,11 +173,23 @@ static std::string checkRegisterWidths(const StinkyInstruction* inst, // receive a VGPR, not a SGPR (and vice versa). RegType expectedType = fieldTypeToRegType(field.fieldType); if (!isExpectedTypeMatch(field.fieldType, expectedType, reg.reg.type)) { - errors << "Instruction '"; - inst->dump(errors); - errors << "' operand " << (isDest ? "dest[" : "src[") << operandIndex << "] " - << "has register type '" << regTypeToString(reg.reg.type) << "', expected '" - << regTypeToString(expectedType) << "'\n"; + // Check promoted encoding: e.g. VOP2 src1 is vgpr-only, but VOP3 + // promotion widens it to src (accepts SGPR). The assembler will + // promote automatically, so accept the promoted field type too. + unsigned fieldIdx = static_cast(&field - hwDesc->operandFields.data()); + bool promotedOk = false; + if (fieldIdx < hwDesc->promotedFields.size()) { + auto promotedFT = hwDesc->promotedFields[fieldIdx].fieldType; + auto promotedET = fieldTypeToRegType(promotedFT); + promotedOk = isExpectedTypeMatch(promotedFT, promotedET, reg.reg.type); + } + if (!promotedOk) { + errors << "Instruction '"; + inst->dump(errors); + errors << "' operand " << (isDest ? "dest[" : "src[") << operandIndex << "] " + << "has register type '" << regTypeToString(reg.reg.type) << "', expected '" + << regTypeToString(expectedType) << "'\n"; + } } } diff --git a/shared/stinkytofu/tests/filecheck/verifier_vop2_sgpr_src.stir b/shared/stinkytofu/tests/filecheck/verifier_vop2_sgpr_src.stir new file mode 100644 index 000000000000..95df9165e05b --- /dev/null +++ b/shared/stinkytofu/tests/filecheck/verifier_vop2_sgpr_src.stir @@ -0,0 +1,46 @@ +# RUN: %stinkytofu-opt --arch gfx1250 %s --StinkyIRVerifierPass --print-output 2>&1 +# +# Verify that VOP2/VOPC instructions accept SGPR source operands without +# triggering register-type errors. The base VOP2 encoding restricts src1 +# to vgpr, but VOP3 promotion widens it to accept SGPRs. The verifier +# must check promotedFields before rejecting. + +#--- v_mul_u32_u24 with SGPR src1 +# CHECK-LABEL: @vop2_sgpr_src1_mul +# CHECK-NOT: [StinkyIRVerifier] +# CHECK: v_mul_u32_u24 + +st.func @vop2_sgpr_src1_mul() { +^entry: + v1 = "st.v_mul_u32_u24"(v0, s16) { issueCycles = 1, latencyCycles = 5 } +} + +#--- v_cmp_eq_u32 with SGPR src1 +# CHECK-LABEL: @vopc_sgpr_src1_cmp_eq +# CHECK-NOT: [StinkyIRVerifier] +# CHECK: v_cmp_eq_u32 + +st.func @vopc_sgpr_src1_cmp_eq() { +^entry: + vcc_lo0 = "st.v_cmp_eq_u32"(v1, s16) { issueCycles = 1, latencyCycles = 5 } +} + +#--- v_cmp_gt_u32 with SGPR src1 +# CHECK-LABEL: @vopc_sgpr_src1_cmp_gt +# CHECK-NOT: [StinkyIRVerifier] +# CHECK: v_cmp_gt_u32 + +st.func @vopc_sgpr_src1_cmp_gt() { +^entry: + vcc_lo0 = "st.v_cmp_gt_u32"(v1, s15) { issueCycles = 1, latencyCycles = 5 } +} + +#--- v_cmp_ge_u32 with SGPR src1 +# CHECK-LABEL: @vopc_sgpr_src1_cmp_ge +# CHECK-NOT: [StinkyIRVerifier] +# CHECK: v_cmp_ge_u32 + +st.func @vopc_sgpr_src1_cmp_ge() { +^entry: + vcc_lo0 = "st.v_cmp_ge_u32"(v2, s18) { issueCycles = 1, latencyCycles = 5 } +}