A program that uses relative VGPR addressing placed every register from the
lowest relatively addressed one upward in the window array, so even direct
accesses became array loads and stores. A descriptor spilled with v_writelane
and restored with v_readlane then lost its definition, and resource analysis
refused GetSamplerResource with ReadLane(LoadVectorWindow(Immediate)) (GTA V,
PPSA04264, shader 0x25a889400).
When the program has no indexed writes (v_movreld, v_movrelsd, v_swaprel),
direct accesses stay SSA registers and each direct write is mirrored into the
window for the relative reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
No pre-rasterization stage writes Layer: vertex layer exports are
ignored, so the first layer is used and the render target index field
of the packed ancillary word is 0. The lowering read the Layer builtin
for it instead. AMD's Windows driver drops the color writes of a
fragment shader that consumes an unwritten Layer, which made
agc_driver_relative_ancillary fail on AMD hardware (#4800); lavapipe
returns 0 there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main reads target.vertexSubgroups at request version 18 since #4470, so
reading the pixel primitive class at 18 as well read a version 18
request written by main one byte short. The class is now written and
read at version 19. agc_shader_memory loads a version 18 request as
main writes it, and it fails with "truncated data" when the class is
read at 18.
The composed word no longer reads the Layer builtin for bits 16-28.
Draws render to a single layer and guest layer exports are ignored, so
no stage writes Layer, and AMD's Windows driver drops the writes of a
fragment shader that reads it unwritten (#4800). The field is the
constant 0 and pixel_ancillary checks that the composed program has no
Layer read. The debt entry names the bits that have no source and are
assumed zero (2-7, 12-15, 29-31), and says that a change which makes
draws export a layer must restore the read.
A packed ancillary word that still reaches an export, or any other read
the masked-select pass cannot drop, no longer stops the recompile. After
that pass every remaining use gets the composed word: the primitive class
at bits 0-1, SampleId at bits 8-11 and Layer at bits 16-28, with every
other bit zero. Those lanes do export the ancillary VGPR on the console,
so the word has to be right, not just present.
The primitive class (0 point, 1 line, 2 triangle, 3 rectangle) comes from
the draw. RasterPrimitiveOf maps rect lists to 3, line lists on the vertex
path to 1 and everything else the driver accepts to 2, because its
geometry and tessellation paths only output triangles. It reaches the
shader as pipeline specialization constant 8233, the way dual-source
blending does: a request field that is folded into the specialized
module and is part of the result memo, the draw key and the serialized
request (version 17).
Only shaders that read the class pay for it. ShaderInfo records the read
(disk cache format 28), and only those shaders put the class in their
module key, so a shader that reads just the sample or layer field keeps
one module for every primitive class. The draw sets the class only when
the pixel shader loads the ancillary VGPR, so other draws keep the draw
cache entries they have today.
The VRS rates (bits 2-5) and the eye id (bit 7) are 0 because the driver
supports neither VRS nor multiview. SampleId is 0 because the driver
rejects MSAA and per-sample shading, and Layer is 0 while it ignores
vertex layer exports. TechnicalDebt.md records the layout sources and
these assumptions.
Tests:
- pixel_ancillary builds an export to target 0 whose two channels select
between a packed value and a phi carrying the word. It checks the word
for each primitive class, sample and layer, checks that the three
shapes the masked-select pass keeps now compile with the composed word,
and that a program whose select the pass drops reads no class.
- agc_driver_pixel_ancillary_word exports the word from a draw and reads
back 2, 3 and 1 for triangle, rectangle and line. A shader that reads
only the sample field shares one module across the classes, and the
whole-word shader gets one per class. It also checks RasterPrimitiveOf.
- agc_shader_memory covers the version 17 round trip, and
agc_shader_disk_cache the new flag.
Both ancillary tests fail with the old refusal when the composition is
disabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A branch condition that reads through a null user-data pointer is now
treated as not evaluable instead of being caught, so every successor
stays active.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Several image instructions of different dimensions on the same T# address share
one descriptor source, so a source stays active as soon as one of them is taken
and the others are still checked against the T#. Record the images a block uses
as well, deactivate the images used only behind a branch the capture does not
take, and bind them as null descriptors.
PPSA15246 (Teardown) then passes the "incompatible with the static runtime
image interface" check of the three image_store variants that are not taken.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A compute kernel that picks a buffer or an image path from an SRT word reads
the same address as a V# in one path and as a T# in another. The resource
tracker now records which block uses each descriptor source and builds the
resource control flow from the structured block terminators, so a source whose
only uses sit behind a conditional branch with a clean, evaluable condition is
left inactive (bound as zero words, not validated) when the capture takes the
other path. A condition that reads inaccessible memory is not evaluable instead
of throwing.
PPSA15246 (Teardown) stopped on "runtime image descriptor is invalid" for the
untaken path of such a kernel.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
image_sample_c on an 8-bit UNORM T# returns 0 for every compare
function on gfx1035, as on 8-bit sRGB (measured on the hardware oracle
in the review of #3438), but the emulated comparison still compared the
red channel. 8, 8_8 and 8_8_8_8 UNORM now return 0 like 8-bit sRGB.
Every texel comparison of those formats is 0, so a bilinear cube
without DISABLE_CUBE_WRAP and an anisotropic S# cannot change the
result; both are accepted for them, as the cube already was for 8-bit
sRGB. Other formats keep both refusals.
The test rows that check real comparisons move to 16_16 UNORM.
referencedIds() took every operand word of every instruction as a
possible id, so a literal that happened to equal the id of a dead folded
constant or an unreachable block kept it: the OpTypeInt width 32 kept a
dead constant %32, LocalSize literals kept dead labels.
Visit only the <id> operands of each instruction. Literal operands are
skipped for the instructions that carry them: type widths, counts and
image parameters, constant values, storage classes, memory access and
image operand masks (their trailing ids still count), composite and
shuffle indices, OpArrayLength members, extended instruction numbers,
entry point names, execution modes, merge and function controls, branch
weights, OpSwitch case values (two words for a 64-bit selector) and
group operations. An instruction without a listed layout still counts
every word, so an unknown instruction can only keep debris, never drop
something in use.
The SpirvModule case now also folds a switch whose dead constant has id
32 and whose dead blocks' ids appear as LocalSize literals; it fails
without this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EmitBdaWrite, used for buffer stores through GPU-selected descriptors, recorded an
Unaligned fault and dropped every 32-bit store whose address is not 4-byte aligned.
EmitBdaStore and the BDA reads already take the byte path for unaligned addresses, and
raw buffer accesses use the exact bytes, as RDNA does in unaligned mode. The unaligned
case now stores the four bytes one by one like the 8- and 16-bit stores of the same
function.
Sword of the Sea hit this about once per frame from one compute store, so four bytes
were lost and a fault was reported each time.
The BDA store execution table now also runs through EmitBdaWrite; its unaligned dword
cases failed before this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lavapipe and MoltenVK leave the vertex stage out of subgroupSupportedStages,
so the driver's shader validation refuses every vertex program that declares
GroupNonUniform ("subgroup capability 61 is unsupported for shader stage 1").
Most do, since the entry EXEC is a ballot.
The device target now records whether the vertex stage has subgroups. When
it does not, vertex and local programs get a host subgroup width of 1: s3
reports one vertex and one primitive, the lane id is 0, a ballot is the
invocation's own bit, a shuffle returns the invocation's value, an elect is
true and a half-wave reduction read is the key itself. Reads of other lanes
keep the existing narrow-host rules and throw where they did. Devices with
vertex subgroups emit the same SPIR-V as before.
SpecializeSpirv hoists every instruction it folds into a module-level
constant and keeps that constant after its last use has folded away
too. It also leaves each block that a folded branch cuts off as
OpLabel; OpUnreachable. A formatted buffer helper's 77-case format
switch leaves about 1,800 such blocks.
Emit only the folded constants that something still refers to, and
remove OpLabel; OpUnreachable blocks that no instruction names. Merge
and continue targets of live constructs are named, so they stay.
The specialized modules lose words, not behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaskedSelectEliminator walked forward from each select to see whether the
lanes its mask excludes are read. Since fda4aa90 the walks pass explicit-LOD
gathers, size queries and guarded image writes, so in a large pixel shader
most of them cover the rest of the program, and the cost grows with the
square of its size (#3504).
The pass now computes, for every value, the set of masks under which it is
unread, from its uses backwards: a use guarded by a condition contributes the
masks that condition implies, a lane-local use adds what its result is unread
under, and any other use contributes nothing. A select is dropped when its
mask is in its own set. Loops are solved as strongly connected components.
The rules for guards, lane-local uses, loops, whole quad masks, selected
execs, rebuilt mask words and guarded first-lane reads are the ones the walk
applied. A first-lane read passes the masks its read mask implies when its
own result is unread outside that read mask, which is solved once per read in
the same way. The walk's step limit, its 64-level implication depth and its
first-lane nesting depth are gone, so a proof no longer depends on the order
of the questions.
A selection outside loops whose one arm only returns and whose other arm
branches to a join it does not dominate gets an empty block on that edge
as its merge (splitOneReturnJoin), but only when every other predecessor
of the join dominates the selection or is dominated by it. When the
parent selection's other arm is a region that ends in the same join, the
selection has no structured merge and the shader fails to recompile.
When every structurization attempt fails, Structurize now runs them again
and accepts that split for any join the selection does not dominate. The
split only inserts an empty block on the edge from the selection to the
join. Graphs that structurize on main keep their result.
A synthetic CFG and a synthetic program cover the shape; both fail on main
with "conditional block N has no structured merge".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fnrk5Dazjb2tLooVSpdRQq