Commit Graph

2 Commits

Author SHA1 Message Date
Dean Galvin 60c655211d perf(recompiler): let exec-guarded memory and exports end a masked select
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.
2026-10-05 18:12:23 +13:00
Dean Galvin dd2a70bc13 perf(recompiler): drop exec selects whose inactive lanes nobody reads
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.
2026-10-05 18:12:23 +13:00