A register written under EXEC is usually consumed by an LDS or buffer
access or an export under the same EXEC. MaskedSelectEliminator kept
the select in that case, which leaves the NGG mesh shaders where they
were.
The emitter performs buffer and LDS loads and stores (8 to 128 bits,
GPU-selected V# included) and SetAttribute only in lanes whose active
operand is true: stores and exports sit in an if on it, loads return 0
outside it. Every other operand is read only in those lanes. So such an
access whose active operand implies the select's condition reads the
derived value only where it equals the original, and it now ends the
search like a masked select does. The active operand itself, atomics,
append/consume, scratch and address-space accesses and image accesses
still keep the select.
Every VGPR write under EXEC is translated to Select(exec, new, old), so
in SSA each register's previous value stays live until the next write,
even when the guest only used the register as a temporary. In NGG mesh
shaders at 255 registers that keeps dead values alive and the driver
spills them to local memory.
MaskedSelectEliminator (after SSA, folding and the readlane pass)
replaces r = Select(m, new, old) by new when no lane where m is false
can observe r:
- every value derived from r is either a lane-local pure op or a phi,
and their uses are checked the same way;
- or the derived value reaches the true operand of a select, or an
operand of a LogicalAnd, whose condition implies m: m itself, a
LogicalAnd containing m, or a thread bit rebuilt from a ballot of
such a condition (also through ANDs of the mask words);
- anything else (cross-lane ops, ballots, readfirstlane, DPP, swizzle,
permutes, stores, exports, atomics, branch references, implicit-LOD
pixel samples, the false operand of any select) keeps the select.
The select and every value it reaches must be in blocks outside loops,
so each of them runs at most once per invocation and every use sees the
same instance of m. Selects whose condition is the full wave
((0xffffffff >> (lane & 31)) & 1) are replaced in any block.
Exact: in lanes where m is true r equals new, so every derived value is
unchanged there; in lanes where m is false only derived values change,
and each path from them ends in a select or AND that discards those
lanes. The selects are removed latest first and dead code is removed
between rounds, so chains of writes to one register collapse.
Pixel shader color exports are compacted: export MRTi goes to the i-th
MRT slot with a non-zero CB_SHADER_MASK nibble and takes the i-th
SPI_SHADER_COL_FORMAT field. Draws still mapped export i to slot i and
read slot i's format field, so a shader with CB_SHADER_MASK 0xf000f and
SPI_SHADER_COL_FORMAT 0x44 (export 1 to slot 4) was refused with
"color export format 0".
Each color target now carries its export index next to its slot.
Attachment references, blend states, export mappings and the pipeline
and pass keys follow the export index; the color buffer, blend control
and target mask come from the slot. An unwritten export below the
highest written one stays an unused attachment.
Loads and stores with glc or dlc are coherent, as MUBUF already does; slc
is a cache hint and changes no result. Atomics keep glc as the return bit.
lds, segment 3 and scratch atomics still throw.
Co-Authored-By: Claude <[email protected]>
flat_store_* and global_store_* decoded and translated, but a store whose
segment is not scratch was refused when the SPIR-V was emitted ("writable
FLAT/GLOBAL addresses require GPU ownership tracking").
Such a store now goes through the BDA page table like a store through a
GPU-selected V#: the write lookup needs Write permission, which only a
writable registered range imported in place has, and the written page is
noted for the host to mark GPU-written. Byte and short stores, and dword
stores to an address that is not dword aligned, are stored byte by byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- v_cndmask_b32: neg/abs on DPP; neg/abs applied on the bits, as the hardware does
- v_mul_legacy_f32: neg/abs on DPP and SDWA, clamp/omod on SDWA and VOP3
- v_ldexp_f16: neg/abs on src0 for VOP3, DPP and SDWA; NaN result chosen on the bits
Co-Authored-By: Claude <[email protected]>
s_cbranch_execz/execnz/vccz/vccnz test the whole wave mask on hardware,
but the single-lane path branched on each lane's own bit, so inactive
lanes took the other side and diverged in scalar state. Branch on a
wave ballot instead, and execute the untested SOPP ops (cdbg branches,
s_clause, s_inst_prefetch, s_setprio, s_waitcnt_depctr, s_ttracedata)
against values measured on hardware.
Co-Authored-By: Claude <[email protected]>
Without fetch-inactive or bound_ctrl, a v_permlane16_b32/v_permlanex16_b32
lane whose source lane is inactive keeps its old value on hardware;
AnyPS5 wrote 0 there. bound_ctrl alone now writes 0.
Co-Authored-By: Claude <[email protected]>
The byte address of each dword of s_buffer_load (soffset + imm + 4*i)
was computed in 32 bits, so a sum past 2^32 wrapped around and read the
start of the buffer. On hardware the sum doesn't wrap: such dwords are
out of range and read 0, like any dword at or past num_records. Both the
bound V# path and the GPU-computed V# path now check the carry.
The new execution test runs s_buffer_load_dwordx4/x16 and
s_load_dwordx16 against values captured on hardware.
Co-Authored-By: Claude <[email protected]>
Preserve each bundled module directory under app0 and resolve Linux dependencies across directories. Add integration coverage for both output formats and Windows startup. Refs #556; duplicate-export rejection remains unchanged.