Shader: implement V_BFE_I32 and correct VOP3 0x36A mapping (#714)

* Implement VOP3 0x149 V_BFE_I32 and 0x36A V_CVT_PKRTZ_F16_F32

PPSA10112 dropped three shaders per run on two unimplemented VOP3
opcodes. The translator fails a whole shader on an opcode it does not
know, so each one costs a dropped draw rather than wrong pixels.

0x149 sits between 0x148 V_BFE_U32 and 0x14A V_BFI_B32, and 0x36A
between 0x369 V_CVT_PKNORM_U16_F32 and 0x36D V_ADD3_U32; the
surrounding table entries already match the canonical map densely on
both sides of each gap. V_CVT_PKRTZ_F16_F32 needed no emitter - it was
already implemented for the VOP2 form at 0x2F and its VOP3 alias at
0x12F, and only the VOP3-only opcode number was missing.

V_BFE_I32 mirrors its unsigned sibling, masking offset and width to 5
bits, and differs only in extracting through a signed type so the field
sign-extends. GCN defines width 0 as returning 0 where SPIR-V leaves a
zero Count unspecified; VBfeU32 has the same gap, so this matches it
deliberately rather than diverging - fix both together if it matters.

Also widen ReportGuestPointerSplit, which filtered to an incoming value
of exactly 1. The crash being hunted leaves 0x0000007000000000, whose
low dword is 0, so the one detector built to catch this bug class could
never have reported it.

Verified: unsupported-opcode errors 3-4 per run -> 0, over a 130s run
that survives to the same stage. Metal translator still lacks VBfeI32;
Vulkan/MoltenVK is the macOS path so it is a divergence, not a blocker.

* Correct VOP3 0x36A: V_CVT_PK_U16_U32, not V_CVT_PKRTZ_F16_F32

a2d186e mapped 0x36A to V_CVT_PKRTZ_F16_F32 on the strength of a gap in
our own opcode table. That was wrong, and wrong in the worst available
way: an emitter for V_CVT_PKRTZ_F16_F32 already existed, so instead of
failing loudly like an unknown opcode, the mapping would have emitted
float-pack semantics for an integer-pack instruction and produced
silently incorrect results.

LLVM is unambiguous:
    defm V_CVT_PK_U16_U32    : VOP3Only_Real_gfx10<0x36a>;
    defm V_CVT_PKRTZ_F16_F32 : VOP2_Real_gfx6_gfx7_gfx10<0x02f>;
so on Gen5 the float pack is VOP2 0x2F with VOP3 alias 0x12F - both of
which our table already had - and there is no VOP3-only encoding of it
to add. 0x36A sits with the other integer/normalised pack conversions
at 0x368/0x369/0x36B.

silent-hill-minimal-rebased had this right all along.

The V_BFE_I32 half of a2d186e stands: LLVM confirms 0x149, and that
branch maps it identically.

Lesson, since I had just warned someone else about exactly this: a gap
in a table is evidence about numbering, not about identity. Inference
from neighbouring entries is fine for narrowing candidates and worth
nothing as a conclusion - especially when a plausible emitter already
exists to swallow the mistake quietly.
This commit is contained in:
kuba
2026-07-31 11:09:50 +02:00
committed by GitHub
parent 539baa66e7
commit e5e02c0908
2 changed files with 18 additions and 0 deletions
@@ -903,6 +903,22 @@ public static partial class Gen5SpirvTranslator
width);
break;
}
case "VBfeI32":
{
// Same extract as VBfeU32 but sign-extended from the top bit
// of the extracted field, so the result type must be signed
// and bitcast back for storage.
var width = BitwiseAnd(GetRawSource(instruction, 2), UInt(31));
result = Bitcast(
_uintType,
_module.AddInstruction(
SpirvOp.BitFieldSExtract,
_intType,
Bitcast(_intType, GetRawSource(instruction, 0)),
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)),
width));
break;
}
case "VBfiB32":
{
var mask = GetRawSource(instruction, 0);
@@ -1157,6 +1157,7 @@ public static class Gen5ShaderTranslator
0x15D => "VSadU32",
0x15E => "VCvtPkU8F32",
0x148 => "VBfeU32",
0x149 => "VBfeI32",
0x169 => "VMulLoU32",
0x16A => "VMulHiU32",
0x16B => "VMulLoI32",
@@ -1170,6 +1171,7 @@ public static class Gen5ShaderTranslator
0x366 => "VMbcntHiU32B32",
0x368 => "VCvtPknormI16F32",
0x369 => "VCvtPknormU16F32",
0x36A => "VCvtPkU16U32",
0x373 => "VMadU32U16",
0x346 => "VLshlAddU32",
0x347 => "VAddLshlU32",