Merge pull request #3400 from AchrafReyani/carry/pipodi-3349

fix(recompiler): evaluate an out-of-range constant buffer read as zero
This commit is contained in:
Adrià 2026-10-11 12:08:15 +02:00 committed by GitHub
commit 6cee0bb02a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 60 additions and 1 deletions

View File

@ -464,6 +464,63 @@ void verifySignedSrtComparison() {
}
}
void verifyOutOfRangeConstantReads() {
std::vector<std::uint32_t> code{0xf40c0200u, 0xfa000000u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf4000400u, 0xfa000050u, 0xf4080a00u, 0xfa000080u, 0xbf8cc07fu, 0xf42c0814u, 0xfa000000u, 0xbf8cc07fu, 0xbf068010u, 0xbf850008u};
for (std::uint32_t dword = 0; dword < 8u; dword++) {
code.push_back(0xbe800300u | ((8u + dword) << 16u) | (32u + dword));
}
code.insert(code.end(), {0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf810000u});
using namespace ShaderRecompiler;
constexpr std::uint32_t Format8888UNorm = 56;
constexpr std::uint32_t Type2D = 9;
struct alignas(256) Texture { std::array<std::uint8_t, 256> bytes{}; };
static Texture textures[2];
const auto imageDescriptor = [&](const Texture& texture) {
const auto base = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(texture.bytes.data()));
return std::array<std::uint32_t, 8>{static_cast<std::uint32_t>(base >> 8u), static_cast<std::uint32_t>((base >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (3u << 30u), 3u << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u};
};
std::array<std::uint32_t, 4> output{};
const auto outputAddress = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(output.data()));
std::array<std::uint32_t, 36> srt{};
const auto first = imageDescriptor(textures[0]);
const auto second = imageDescriptor(textures[1]);
std::copy(first.begin(), first.end(), srt.begin());
const std::array<std::uint32_t, 4> outputV{static_cast<std::uint32_t>(outputAddress), static_cast<std::uint32_t>((outputAddress >> 32u) & 0xffffu), 16u, 0xfacu};
std::copy(outputV.begin(), outputV.end(), srt.begin() + 16);
srt[20] = 1u;
std::copy(second.begin(), second.end(), srt.begin() + 24);
const auto srtAddress = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(srt.data()));
const std::array<std::uint32_t, 2> userData{static_cast<std::uint32_t>(srtAddress), static_cast<std::uint32_t>(srtAddress >> 32u)};
const std::array<std::uint32_t, 4> capabilities{29u, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess};
const std::array<std::string_view, 2> extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"};
RecompileRequest request{};
request.shader = {ShaderStage::Compute, 0x23000u, code, 0, {}};
request.context.waveSize = 32u;
request.context.userDataBaseRegister = 0;
request.context.userData = userData;
request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
request.target.vulkanVersion = 0x00401000u;
request.target.spirvVersion = 0x00010300u;
request.target.subgroupSize = 32;
request.target.supportedCapabilities = capabilities;
request.target.supportedExtensions = extensions;
request.target.bdaAbiVersion = BdaAbi::Version;
request.target.fragmentShaderBarycentricEnabled = false;
request.layout.pushConstantSizeBytes = 128;
const auto plan = GetResourcePlan(request);
require(plan->info.images.size() == 2u && plan->info.samplers.size() == 1u, "out-of-range s_buffer_load: the two T#s were not planned as one image each");
AgcDriver::ShaderMemory memory({});
const auto capture = memory.Capture(request);
request.context.memory = memory.Regions();
static_cast<void>(Recompile(request, *capture));
const auto holds = [&](const std::array<std::uint32_t, 8>& words) {
return std::ranges::any_of(capture->snapshot.images, [&](const DescriptorValue& value) {
return std::equal(words.begin(), words.end(), value.dwords.begin());
});
};
require(holds(first) && holds(std::array<std::uint32_t, 8>{}), "out-of-range s_buffer_load: a T# read through a null V# was not evaluated as zero");
}
// The pure flat slots of a hand-built plan (Detail::ComputePureFlatSlots): a slot is pure unless
// a descriptor dword, a condition, a uniform value or another slot's address cone reaches it.
void verifyPureFlatSlots() {
@ -2711,6 +2768,7 @@ int main(int argc, char** argv) {
verifyEvaluatedValues();
verifyFrontendPair();
verifySignedSrtComparison();
verifyOutOfRangeConstantReads();
verifySignedMinimum();
verifyPureFlatSlots();
verifyBindlessTable();

View File

@ -143,7 +143,8 @@ bool Evaluator::EvaluateRawRead(IrValue& inst, std::uint64_t& result) {
const auto stride = (static_cast<std::uint32_t>(high) >> 16u) & 0x3fffu;
const auto size = stride == 0u ? static_cast<std::uint64_t>(static_cast<std::uint32_t>(records)) : static_cast<std::uint64_t>(stride) * static_cast<std::uint32_t>(records);
if (aligned > size || size - aligned < sizeof(std::uint32_t)) {
return false;
result = 0;
return true;
}
address = ((base & ~std::uint64_t {3}) + byteOffset) & ~std::uint64_t {3};
} else {