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.
This commit is contained in:
Dean Galvin 2026-10-05 14:10:02 +13:00
parent dd2a70bc13
commit 60c655211d
2 changed files with 72 additions and 0 deletions

View File

@ -336,5 +336,43 @@ int main() {
return sample(IrShaderStage::Pixel, RdnaImageSampleFlagLevelZero) && sample(IrShaderStage::Mesh, 0u);
});
const auto guarded = [](IrOpcode opcode, bool sameExec, bool asActive) {
Builder b;
auto& wide = b.mask(48u);
auto& exec = b.logicalAnd(wide, b.mask(16u));
auto& old = b.lane();
auto& written = b.select(exec, b.add(old, 1u), old);
IrValue& active = asActive ? b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)}) : sameExec ? exec : wide;
switch (opcode) {
case IrOpcode::WriteSharedU32:
b.emit(opcode, IrType::Void, {&old, &written, &active});
break;
case IrOpcode::LoadBufferU32:
b.keep(b.select(exec, b.emit(opcode, IrType::U32, {&b.emit(IrOpcode::GetBufferResource, IrType::BufferResource, {}), &written, &b.constant(0u), &b.constant(0u), &active}), old));
break;
default:
b.emit(opcode, IrType::Void, {&b.emit(IrOpcode::CompositeConstructU32x4, IrType::U32x4, {&written, &old, &old, &old}), &active});
break;
}
b.eliminate();
return removed(written);
};
passed &= run("a store, load or export whose exec implies the write's exec drops the select", [&] {
return guarded(IrOpcode::WriteSharedU32, true, false) && guarded(IrOpcode::LoadBufferU32, true, false) && guarded(IrOpcode::SetAttribute, true, false);
});
passed &= run("a store, load or export under a wider exec keeps the select", [&] {
return !guarded(IrOpcode::WriteSharedU32, false, false) && !guarded(IrOpcode::LoadBufferU32, false, false) && !guarded(IrOpcode::SetAttribute, false, false);
});
passed &= run("a value used as a store's exec keeps the select", [&] {
return !guarded(IrOpcode::WriteSharedU32, true, true);
});
passed &= run("an atomic keeps the select", [&] {
return !guarded(IrOpcode::SharedAtomicIAdd32, true, false);
});
return passed ? 0 : 1;
}

View File

@ -175,6 +175,39 @@ bool isLaneLocal(IrOpcode opcode) {
}
}
bool readsOnlyWhereActive(IrOpcode opcode) {
switch (opcode) {
case IrOpcode::LoadBufferU8:
case IrOpcode::LoadBufferU16:
case IrOpcode::LoadBufferU32:
case IrOpcode::LoadBufferU32x2:
case IrOpcode::LoadBufferU32x3:
case IrOpcode::LoadBufferU32x4:
case IrOpcode::StoreBufferU8:
case IrOpcode::StoreBufferU16:
case IrOpcode::StoreBufferU32:
case IrOpcode::StoreBufferU32x2:
case IrOpcode::StoreBufferU32x3:
case IrOpcode::StoreBufferU32x4:
case IrOpcode::LoadSharedU8:
case IrOpcode::LoadSharedU16:
case IrOpcode::LoadSharedU32:
case IrOpcode::LoadSharedU32x2:
case IrOpcode::LoadSharedU32x3:
case IrOpcode::LoadSharedU32x4:
case IrOpcode::WriteSharedU8:
case IrOpcode::WriteSharedU16:
case IrOpcode::WriteSharedU32:
case IrOpcode::WriteSharedU32x2:
case IrOpcode::WriteSharedU32x3:
case IrOpcode::WriteSharedU32x4:
case IrOpcode::SetAttribute:
return true;
default:
return false;
}
}
bool isExplicitLodSample(const IrProgram& program, const IrValue& value) {
if (value.Opcode() != IrOpcode::ImageSampleRaw) return false;
if (program.Resources().stage != IrShaderStage::Pixel) return true;
@ -316,6 +349,7 @@ bool unobservedWhereMasked(const IrProgram& program, const std::unordered_set<co
if (user->Parent() == nullptr || !once.contains(user->Parent())) return false;
if (isSelect(user->Opcode()) && use.operand == 1u && implies(user->Argument(0), mask, waveSize)) continue;
if (user->Opcode() == IrOpcode::LogicalAnd && use.operand < 2u && implies(user->Argument(1u - use.operand), mask, waveSize)) continue;
if (readsOnlyWhereActive(user->Opcode()) && use.operand + 1u < user->ArgumentCount() && implies(user->Argument(user->ArgumentCount() - 1u), mask, waveSize)) continue;
if (!user->IsPhi() && !isLaneLocal(user->Opcode()) && !isExplicitLodSample(program, *user)) return false;
if (visited.insert(user).second) {
if (visited.size() > VisitLimit) return false;