The registered-shader decode ran before the deferral decision, so a single
instruction the decoder does not implement ended the title at registration
instead of deferring the shader to its first use.
Give the decoder's unsupported-instruction failures their own type
(UnsupportedInstructionError): the five family-scoped SOP opcode defaults and
the MIMG opcode lookup (which now names the opcode) raise it, and the decode in
PrepareRegistered defers the shader with that reason when it hits one. Any
other exception still throws, so the corrupt-code contract is unchanged.
PPSA26344 hits this: unsupported MIMG opcode 229 (0xE5), issue #1941. The
failing word is reproducible offline from the title's own shader store
(cache_ps5/all_shaders.xpps, code word 260), which is how the fixture was
checked before it was written.
Scope: this defers the scalar-family and MIMG opcode gaps. The vector (VOP*)
and memory (SMEM/MUBUF/MTBUF/FLAT/DS) opcode lookups, the modifier-level
"not supported" throws and the malformed/truncated throws still abort.
Widening the deferral to those is a policy call for the maintainers.
Stacked on #1836 (the deferral machinery); re-stacked on its current head so
this diff no longer carries #1836's earlier broad catch. Once #1836 lands only
the decode change remains.
Written with AI assistance.
Decode the three VOP3 16-bit interpolation instructions (attribute, channel
and high-half select in the src0 field, the I/J VGPR in src1, P0 or the first
step's result in src2) and translate them with a new
GetInterpolationParameterF16 IR value. It reads the selected 16-bit half of
each vertex's parameter through VK_KHR_fragment_shader_barycentric, so P10 and
P20 are vertex differences rounded to f16; both steps are a multiply and an
add, as in the f32 forms.
An input in FP16_INTERP_MODE whose low half is defaulted is still declared at
its slot when its high half is read, and the high half takes its default from
USE_DEFAULT_ATTR1 and DEFAULT_VAL_ATTR1. Without barycentrics, on an input
without FP16_INTERP_MODE or on a pass-through input the instructions throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The decoder threw on the GFX10.1 DS src2 opcodes 0x80-0x8b, 0x8d,
0x92, 0x93 and 0x95. Each reads DS[B], where B is the address plus
four times the 15-bit signed offset in offset1[6:0]:offset0, and
applies the matching operation to DS[A]: add, sub, rsub, inc, dec,
signed and unsigned min and max, and, or, xor, f32 min, max and add
through the 32-bit LDS atomics, and a plain write for
ds_write_src2_b32. The form that takes the offset from the address
bits (offset1[7]), the 64-bit forms and GDS still throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decodes IMAGE_GATHER4H_PCK (MIMG opcode 98) and IMAGE_GATHER8H_PCK
(opcode 99), implements gather4h_pck through translation, the SPIR-V
backend and a GPU execution test, and documents gather8h_pck as a
desktop null-op.
Semantics were measured on gfx1031 hardware with hand-encoded MIMG
words (LLVM rejects the PCK gather mnemonics on every RDNA1/RDNA2
target). The measured rule for gather4h_pck: the four-texel horizontal
window is floor(u*width-0.5)+{-1,0,+1,+2} clamped to the width, the row
is floor(v*height) clamped, the raw bits of the window texels are
concatenated into a little-endian bitstream (texel k at bit offset
k*texelBits), and output register j - the j-th set DMASK bit in
ascending order - receives stream word j, zero past the end of the
stream. Exactly popcount(dmask) consecutive VGPRs are written from vdst
ascending; texels wider than 32 bits write zeros (hardware limit); rows
past the height read zeros; negative coordinates clamp to zero. The
window anchor matches the repo's existing PS5-validated HorizontalOffsets
rule, so this reuses the established convention.
The decoder accepts multi-bit DMASKs for these two opcodes only and
sizes their data span as popcount(dmask) (measured), unlike gather4h
which always writes four components. Translation carries the packed
flag through imageMemoryInfoFromInstruction; the SPIR-V emitter
reassembles the bitstream per lane with per-texel OpImageFetch and
byte assembly. D16 data throws until measured.
gather8h_pck never returns texel data on this hardware: across dmasks
0x1-0xFF including the upper nibble, RGBA8/RGBA16/RGBA32 formats, 1D/2D
and 3D descriptors, normalized and unnormalized sampling, sentinel-
prefilled destination registers confirm it only ever zero-fills
popcount(dmask) registers. It decodes but throws at translation.
The measured semantics and the gather8h_pck null-op are recorded in
docs/dev/TechnicalDebt.md.
Decodes SOP1 0x21 s_swappc_b64 (link pair = pc + 4) and resolves its call/return pairing through the existing s_setpc_b64 path: static call regions in GraphBuilder and the vertex-entry fetch-shader call inlined from the parsed fetch-shader plan. s_call_b64 (SOPK 0x16) and s_rfe_b64 (SOP1 0x22) throw at decode. Unmodeled cases throw; see docs/dev/TechnicalDebt.md.
perf(agc): baseline shader recompilation on registration
In main, baseline recompilation depends on the resource configuration and runs on the first use of each corresponding variant.
This branch prepares IR, the static interface, baseline SPIR-V, and available ABI variants during shader registration. The compiled artifact is separate from per-draw and per-dispatch data.
Before the first execution with a new resource configuration, the runtime reads descriptors, validates compatibility, prepares the binding plan, specializes existing SPIR-V by substituting constants and removing unused branches and computations, materializes invocation data, and creates the Vulkan pipeline. These results are cached.
Variants that depend on linked graphics stages are prepared when the corresponding ABI is resolved. Unregistered compute shaders undergo baseline preparation on their first dispatch, emit an error log, and cache the result.
Subsequent executions reuse prepared artifacts, specializations, and pipelines while updating invocation data and resources as needed.
s_sethalt with SIMM16[0] clear is a no-op; s_sethalt 1, s_sendmsghalt, a reached s_code_end, s_rfe_b64, the ds_gws_* instructions and ds_ordered_count throw with the reason at translation. Linear decoding stops at s_code_end like at s_endpgm, so code padded with s_code_end after a final backward branch decodes. Encodings from LLVM SOPInstructions.td and DSInstructions.td, checked against the gfx10 MC tests; semantics from the RDNA 2 ISA guide.
NVIDIA 610.62 cannot sample R8G8_SRGB, and lavapipe can sample neither
it nor R8_SRGB, so an 8_8 sRGB T# made an image the device does not
support: creating it crashes the NVIDIA driver and lavapipe loads zeros.
The device's format support is read once. An 8 or 8_8 sRGB texture the
device cannot sample is created and viewed as R8_UNORM or R8G8_UNORM,
and SpirvTarget::srgbDecodeFormats makes the materializer mark the
image, so image loads map every channel through a 256-entry table of
the sRGB EOTF rounded to nearest even at bf16 precision, which is what
RDNA2 (gfx1035) returns; both channels of 8_8 are sRGB, and DST_SEL 0
and 1 map to themselves. Samples and gathers of such an image throw,
and its bindless table entries are unmapped like converted formats.
APS5_SRGB_SHADER_DECODE=1 takes this path on every device. Shader disk
cache entries and keys carry the mark: cache format version 7.
DS opcodes 0x6e and 0x6f exchange two qwords at offset0 and offset1
scaled by 8 (st64: 512) and return both old values. Each address is
exchanged with its own 64-bit LDS exchange on the locked 64-bit path.
Equal offsets and a destination past v255 throw, as for the b32 forms;
64-bit GDS throws like the other 64-bit DS atomics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both swap data0 with the dword at addr + offset0 and data1 with the dword
at addr + offset1, and return the two previous values in vdst and vdst + 1.
The offsets are scaled by 4 bytes, or by 256 bytes for the st64 form.
Each location goes through the existing SharedAtomicSwap32.
An encoding whose two offsets are equal is rejected at decode time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decode MIMG opcode 0x80 on 2D MSAA and 2D MSAA array images with a
single-bit dmask and return four dwords: the selected component of the
fragment named in the address and the three that follow it. Each fragment
is read with its own image fetch, adding the fragment step to the packed
address dword so a16 layouts keep working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GLOBAL opcode 0x34 subtracts the data register from a global dword and
clamps the result to zero, returning the previous value. It only exists
in the global segment, so the flat form throws in the decoder, and the
form without glc throws like buffer_atomic_csub does. The clamped
subtract is now a shared helper used by both opcodes.
Decode FLAT opcode 0x17 in the global segment and store the data VGPR of
each active lane at the SGPR pair base plus the signed offset plus four
times the lane id, sharing the address path of global_load_dword_addtid.
Co-Authored-By: Claude <noreply@anthropic.com>
These make up the precise f64 division macro compilers emit for a / b.
Like the f32 ops, the rules were measured on an RDNA2 GPU (41152 rows,
model matches all of them):
- div_scale decides from the exponent fields only: 2^128 or 2^-128
scaling and VCC for differences >= 768 or <= -768, a denormal or huge
(>= 2045) denominator and a numerator exponent <= 53; a NaN source is
passed through unchanged and a zero input gives 0xfff8000000000000.
- div_fmas scales by 2^128 when the exponent of S2 is >= 1024, else
2^-128, before the single rounding, so it reuses the exact fma
emulation with the scale folded into its final rescale.
- div_fixup follows the ISA: NaN sources are quieted, a NaN quotient
with finite inputs becomes +-inf, exponent differences below -1075
give a signed zero.
Co-Authored-By: Claude <noreply@anthropic.com>
Decode and translate the remaining unary f64 transcendentals and
v_trig_preop_f64.
On RDNA2 hardware v_rcp_f64, v_rsq_f64 and v_sqrt_f64 are approximations
(relative error up to ~2^-24.3 over 8209 inputs; sqrt(1.0) is not even
exact), so they are emitted as exact double operations and the test
compares against the hardware value with a 2^-23 tolerance. Special
cases are exact and selected on bits: NaN inputs are quieted (RADV keeps
signaling NaN on FDiv), negative inputs give the default NaN
0xfff8000000000000, rsq(-0) = -inf, sqrt(-0) = -0.
v_trig_preop_f64 matches the hardware bit for bit on 24627 results plus
a sweep of the table tail: the 2/PI table is 1184 bits (bits below b17
read as zero), shift = 53 * S1[4:0] + max(exp - 1077, 0), +128 scale for
exp >= 1968, omod ignored. The 53-bit window is read from a private u32
table and scaled in two exact steps so only the final multiply rounds.
Stacked on the f64 infrastructure of #446.
Co-Authored-By: Claude <noreply@anthropic.com>
s_endpgm_saved and s_endpgm_ordered_ps_done end the wave like s_endpgm
(checked on an RDNA2 GPU: nothing after them runs), so they decode to
SEndpgm and the control flow graph treats them as terminators.
s_atc_probe, s_atc_probe_buffer and s_dcache_discard(_x2) have no visible
effect and translate to no-ops like the other scalar cache controls.
s_round_mode is ignored like s_setreg MODE writes; listed in TechnicalDebt.
Co-Authored-By: Claude <noreply@anthropic.com>
MIMG 0xe7 is the node test of image_bvh_intersect_ray with a 64-bit node
pointer: the pointer takes the first two address dwords (low, high), so
the instruction addresses 12 dwords, or 9 with A16.
The node index and address are computed from the whole pointer. A zero
base in the T# is a null BVH only for the 32-bit form: with 64-bit
pointers the T# may cover all of memory from base 0 and the pointer then
carries the node address, so only the T# size bounds the node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MUBUF opcode 0x34 subtracts the data register from a buffer dword and
clamps the result to zero, returning the previous value. The form without
glc throws: no GFX10 target is known to provide it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ds_add/sub/rsub/inc/dec_u64, ds_min/max_i64/u64, ds_and/or/xor/mskor_b64,
ds_cmpst_b64/f64, ds_min/max_f64, their _rtn forms and ds_wrxchg_rtn_b64
threw in the decoder.
LDS is a 32-bit workgroup array, so the two dwords are read and written
under a lock dword placed after the guest LDS, which is cleared with a
workgroup barrier at entry. The lock is acquired and released in the same
loop iteration, so lanes of one subgroup cannot deadlock on it. Stages
whose LDS is a function variable update it directly. 64-bit GDS atomics
throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SOPK 0x12 threw in the decoder. The recompiler emits round to nearest
even, which s_setreg_imm32_b32 already requires, so reads that stay
within MODE bits 0..3 return 0. Other hardware registers and fields
throw with the register and bits read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VOP3P 0x13 threw "VOP3P opcode is not implemented". It computes
src0.lo * src1.lo + src0.hi * src1.hi + src2 like v_dot2c_f32_f16, with
the VOP3P op_sel/op_sel_hi and neg_lo/neg_hi modifiers on the packed f16
sources, neg on the f32 accumulator and clamp. op_sel and neg_hi on the
accumulator throw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>