perf(recompiler): report which user-data words a resource capture only reads through

The SRT evaluator records every raw read with the user-data word its address is
based on, and marks a word used as a plain value. The result also names the
user-data word behind each shader-data dword.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Scott SchusterandClaude Opus 5.5 committed 2026-10-15 09:26:58 -05:00
1 parent 940eba9a26
commit 553690c0d3
8 files changed
+46 -3

No files matched your search

@@ -19,6 +19,7 @@ struct BindingAllocationResult : CompiledBindingLayout {
std::vector<PipelineSpecializationConstant> specialization;
std::vector<DescriptorBinding> bindings;
std::vector<std::byte> pushConstants;
std::vector<std::uint32_t> shaderDataUserWords;
};
class BindingAllocator {
@@ -38,6 +38,7 @@ struct ResourceCapture {
// The walk's read addresses when the plan has pure flat slots (the leaf of each pure slot,
// and every other read sorted and deduplicated); empty otherwise.
SrtReadTrace readTrace;
SrtUserDataUse userDataUse;
// APS5_PROFILE_DRAW: what CaptureResources spent resolving the source (the stage inputs, the
// key over the code, the plan) before the walk.
std::uint64_t sourceNanoseconds = 0;
@@ -2,6 +2,7 @@
#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_INCLUDE_OPTIMIZATION_SRTWALKER_HPP
#include "IntermediateRepresentation/IrProgram.hpp"
#include "UserDataUse.hpp"
#include <cstdint>
#include <span>
@@ -35,6 +36,7 @@ struct SrtRuntime {
SrtMemoryProbe accessible = nullptr;
SrtMemoryProbe pendingWrite = nullptr;
SrtMemoryBulkReader readWords = nullptr;
SrtUserDataUse* userDataUse = nullptr;
};
enum class RuntimeValueType {
@@ -525,6 +525,10 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con
const auto& layout = compiled.layout;
const bool needsShaderData = layout.UsesPushData() || plan.storageData;
auto shaderData = needsShaderData ? ShaderDataDwordsFor(layout, userDataBase, snapshot, partialThreads, plan.imageMetadata, plan.runtimeState) : std::vector<std::uint32_t>{};
allocation.shaderDataUserWords.clear();
if (needsShaderData) {
for (const auto reg : layout.userDataRegisters) allocation.shaderDataUserWords.push_back(reg - userDataBase);
}
std::vector<DescriptorBinding> bindings;
bindings.reserve(plan.bindings.size());
std::size_t writtenHere = 0;
@@ -194,7 +194,18 @@ bool BasicEvaluator<TNode>::EvaluateRawRead(const TNode& inst, std::uint64_t& re
std::uint64_t low = 0;
std::uint64_t high = 0;
std::uint64_t offset = 0;
if (!Arg(handle, 0, low) || !Arg(handle, 1, high) || !Arg(inst, 1, offset)) {
std::int32_t origin = -1;
const auto baseWord = [&] {
const TNode source = handle.Argument(0);
if (_runtime.userDataUse == nullptr || source.HasImmediate() || source.Opcode() != IrOpcode::GetUserData) return Arg(handle, 0, low);
const auto reg = RegIndex(static_cast<ScalarReg>(source.Argument(0).RegisterIndex()));
if (reg < _program.userDataBase || reg - _program.userDataBase >= _runtime.userData.size() || reg - _program.userDataBase >= SrtUserDataUse::TrackedWords) return Arg(handle, 0, low);
origin = static_cast<std::int32_t>(reg - _program.userDataBase);
low = _runtime.userData[reg - _program.userDataBase];
_runtime.userDataUse->asBase |= std::uint64_t {1} << origin;
return true;
};
if (!baseWord() || !Arg(handle, 1, high) || !Arg(inst, 1, offset)) {
return false;
}
const auto base = ((high << 32u) | static_cast<std::uint32_t>(low)) & AddressMask;
@@ -229,6 +240,7 @@ bool BasicEvaluator<TNode>::EvaluateRawRead(const TNode& inst, std::uint64_t& re
result = 0;
return true;
}
if (_runtime.userDataUse != nullptr) _runtime.userDataUse->reads.emplace_back(address, origin);
if (_inaccessible != nullptr && _runtime.accessible != nullptr && !_runtime.accessible(_runtime.userContext, address, sizeof(std::uint32_t)) && LoadedFromMemory(*handle.Value())) {
*_inaccessible = {inst.Value(), address};
return false;
@@ -407,6 +419,7 @@ bool BasicEvaluator<TNode>::EvaluateInst(const TNode& inst, std::uint64_t& resul
if (reg < _program.userDataBase || reg - _program.userDataBase >= _runtime.userData.size()) {
return false;
}
if (_runtime.userDataUse != nullptr) _runtime.userDataUse->asValue |= reg - _program.userDataBase < SrtUserDataUse::TrackedWords ? std::uint64_t {1} << (reg - _program.userDataBase) : ~std::uint64_t {0};
result = _runtime.userData[reg - _program.userDataBase];
return true;
}
+4 -2
View File
@@ -992,6 +992,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
std::copy_n(runtimeSelectors.begin(), layout.runtimeStateDwords, data->guestDescriptor.begin() + first);
}
}
result.shaderDataUserWords = std::move(bindings.shaderDataUserWords);
result.poisonedSrtReads = static_cast<std::uint32_t>(snapshot.srtPoison.size()) + snapshot.nullRootReads;
if (!tables.map.empty()) {
result.imageTablePoison.reserve(tables.poison.size());
@@ -1326,11 +1327,12 @@ namespace {
// traced into the capture (ResourceCapture::readTrace).
void materializeCapture(ResourceCapture& capture, const SrtRuntime& runtime) {
const auto& plan = *capture.plan;
SrtRuntime traced = runtime;
traced.userDataUse = &capture.userDataUse;
if (std::none_of(plan.pureFlatSlots.begin(), plan.pureFlatSlots.end(), [](std::uint8_t pure) { return pure != 0u; })) {
ResourceMaterializer{}.Materialize(plan, runtime, capture.snapshot);
ResourceMaterializer{}.Materialize(plan, traced, capture.snapshot);
return;
}
SrtRuntime traced = runtime;
traced.readTrace = &capture.readTrace;
ResourceMaterializer{}.Materialize(plan, traced, capture.snapshot);
auto& other = capture.readTrace.otherReads;
+1
View File
@@ -579,6 +579,7 @@ struct ShaderInvocation {
struct RecompileResult : CompiledShaderArtifact, ShaderInvocation {
bool cacheHit = false;
std::uint32_t workgroupMemoryDwords = 0;
std::vector<std::uint32_t> shaderDataUserWords;
[[nodiscard]] std::uint64_t PipelineVariantId() const { return specializationId != 0 ? specializationId : variantId; }
};
+19
View File
@@ -0,0 +1,19 @@
#ifndef CORE_SHADER_RECOMPILER_USERDATAUSE_HPP
#define CORE_SHADER_RECOMPILER_USERDATAUSE_HPP
#include <cstdint>
#include <utility>
#include <vector>
namespace ShaderRecompiler {
struct SrtUserDataUse {
static constexpr std::uint32_t TrackedWords = 64;
std::uint64_t asBase = 0;
std::uint64_t asValue = 0;
std::vector<std::pair<std::uint64_t, std::int32_t>> reads;
};
}
#endif