Just force reads on 8/16bits

This commit is contained in:
CamilleLaVey
2026-09-29 17:46:53 -04:00
parent 2d6f28c50e
commit 213d9fb4a7
4 changed files with 144 additions and 35 deletions
@@ -962,7 +962,7 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
OpBranchConditional(cond, then_label, else_label);
AddLabel(then_label);
const Id word{OpShiftRightLogical(U32[1], offset, Const(2U))};
const Id result{callback(ssbos[index].U32, word, offset, value)};
const Id result{callback(ssbos[index], word, offset, value)};
if (returns_value) {
OpReturnValue(result);
} else {
@@ -978,11 +978,22 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
OpFunctionEnd();
global_memory_funcs.emplace(opcode, func);
}};
const auto vector_pointer{[&](const StorageDefinitions& ssbo, Id word, u32 count) {
if (count == 2) {
return OpAccessChain(storage_types.U32x2.element, ssbo.U32x2, zero,
OpShiftRightLogical(U32[1], word, Const(1U)));
}
return OpAccessChain(storage_types.U32x4.element, ssbo.U32x4, zero,
OpShiftRightLogical(U32[1], word, Const(2U)));
}};
const auto load{[&](Id type, u32 count) {
return [&, type, count](Id ssbo, Id word, Id, Id) {
return [&, type, count](const StorageDefinitions& ssbo, Id word, Id, Id) {
if (count > 1 && profile.support_descriptor_aliasing) {
return OpLoad(type, vector_pointer(ssbo, word, count));
}
std::array<Id, 4> words{};
for (u32 element = 0; element < count; ++element) {
words[element] = OpLoad(U32[1], word_pointer(ssbo, word, element));
words[element] = OpLoad(U32[1], word_pointer(ssbo.U32, word, element));
}
if (count == 1) {
return words[0];
@@ -991,21 +1002,25 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
};
}};
const auto store{[&](u32 count) {
return [&, count](Id ssbo, Id word, Id, Id value) {
return [&, count](const StorageDefinitions& ssbo, Id word, Id, Id value) {
if (count > 1 && profile.support_descriptor_aliasing) {
OpStore(vector_pointer(ssbo, word, count), value);
return Id{};
}
if (count == 1) {
OpStore(word_pointer(ssbo, word, 0), value);
OpStore(word_pointer(ssbo.U32, word, 0), value);
return Id{};
}
for (u32 element = 0; element < count; ++element) {
OpStore(word_pointer(ssbo, word, element),
OpStore(word_pointer(ssbo.U32, word, element),
OpCompositeExtract(U32[1], value, element));
}
return Id{};
};
}};
const auto extract{[&](bool is_signed, u32 count) {
return [&, is_signed, count](Id ssbo, Id word, Id offset, Id) {
const Id loaded{OpLoad(U32[1], word_pointer(ssbo, word, 0))};
return [&, is_signed, count](const StorageDefinitions& ssbo, Id word, Id offset, Id) {
const Id loaded{OpLoad(U32[1], word_pointer(ssbo.U32, word, 0))};
if (is_signed) {
return OpBitFieldSExtract(U32[1], loaded, bits(offset, count), Const(count));
}
@@ -1013,28 +1028,28 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
};
}};
const auto insert{[&](u32 count) {
return [&, count](Id ssbo, Id word, Id offset, Id value) {
AtomicBitFieldInsert(word_pointer(ssbo, word, 0), value, bits(offset, count),
return [&, count](const StorageDefinitions& ssbo, Id word, Id offset, Id value) {
AtomicBitFieldInsert(word_pointer(ssbo.U32, word, 0), value, bits(offset, count),
Const(count));
return Id{};
};
}};
const auto atomic{[&](Id (Sirit::Module::*func)(Id, Id, Id, Id, Id)) {
return [&, func](Id ssbo, Id word, Id, Id value) {
return (this->*func)(U32[1], word_pointer(ssbo, word, 0), scope, zero, value);
return [&, func](const StorageDefinitions& ssbo, Id word, Id, Id value) {
return (this->*func)(U32[1], word_pointer(ssbo.U32, word, 0), scope, zero, value);
};
}};
const auto cas{[&](Id type, Id helper) {
return [&, type, helper](Id ssbo, Id word, Id, Id value) {
return OpFunctionCall(type, helper, word, value, ssbo);
return [&, type, helper](const StorageDefinitions& ssbo, Id word, Id, Id value) {
return OpFunctionCall(type, helper, word, value, ssbo.U32);
};
}};
const auto packed{[&](bool is_half, Id helper) {
return [&, is_half, helper](Id ssbo, Id word, Id, Id value) {
return [&, is_half, helper](const StorageDefinitions& ssbo, Id word, Id, Id value) {
if (is_half) {
return OpBitcast(U32[1], OpFunctionCall(F16[2], helper, word, value, ssbo));
return OpBitcast(U32[1], OpFunctionCall(F16[2], helper, word, value, ssbo.U32));
}
return OpPackHalf2x16(U32[1], OpFunctionCall(F32[2], helper, word, value, ssbo));
return OpPackHalf2x16(U32[1], OpFunctionCall(F32[2], helper, word, value, ssbo.U32));
};
}};
for (const IR::Block* const block : program.post_order_blocks) {
@@ -632,6 +632,8 @@ void VisitUsages(Info& info, IR::Inst& inst) {
case IR::Opcode::StorageAtomicExchange32:
info.used_storage_buffer_types |= IR::Type::U32;
break;
case IR::Opcode::LoadGlobal64:
case IR::Opcode::WriteGlobal64:
case IR::Opcode::LoadStorage64:
case IR::Opcode::WriteStorage64:
case IR::Opcode::StorageAtomicIAdd32x2:
@@ -645,6 +647,8 @@ void VisitUsages(Info& info, IR::Inst& inst) {
case IR::Opcode::StorageAtomicExchange32x2:
info.used_storage_buffer_types |= IR::Type::U32x2;
break;
case IR::Opcode::LoadGlobal128:
case IR::Opcode::WriteGlobal128:
case IR::Opcode::LoadStorage128:
case IR::Opcode::WriteStorage128:
info.used_storage_buffer_types |= IR::Type::U32x4;
@@ -414,6 +414,14 @@ void FoldSelect(IR::Inst& inst) {
}
}
void FoldAtomicWrap(IR::Inst& inst, IR::Opcode opcode, u32 addend) {
const IR::Value limit{inst.Arg(1)};
if (limit.IsImmediate() && limit.U32() == 0xFFFFFFFFU) {
inst.ReplaceOpcode(opcode);
inst.SetArg(1, IR::Value{addend});
}
}
void FoldFPAdd32(IR::Inst& inst) {
if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a + b; })) {
return;
@@ -1104,6 +1112,14 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) {
IR::Opcode::CompositeInsertF16x4);
case IR::Opcode::FSwizzleAdd:
return FoldFSwizzleAdd(block, inst);
case IR::Opcode::GlobalAtomicInc32:
return FoldAtomicWrap(inst, IR::Opcode::GlobalAtomicIAdd32, 1U);
case IR::Opcode::GlobalAtomicDec32:
return FoldAtomicWrap(inst, IR::Opcode::GlobalAtomicIAdd32, 0xFFFFFFFFU);
case IR::Opcode::SharedAtomicInc32:
return FoldAtomicWrap(inst, IR::Opcode::SharedAtomicIAdd32, 1U);
case IR::Opcode::SharedAtomicDec32:
return FoldAtomicWrap(inst, IR::Opcode::SharedAtomicIAdd32, 0xFFFFFFFFU);
case IR::Opcode::GetCbufF32:
case IR::Opcode::GetCbufU32:
if (env.HasHLEMacroState()) {
@@ -4,14 +4,15 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
#include <optional>
#include <unordered_set>
#include <boost/container/flat_set.hpp>
#include <boost/container/small_vector.hpp>
#include "common/alignment.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/breadth_first_search.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/host_translate_info.h"
@@ -49,6 +50,7 @@ using StorageBufferSet =
using StorageInstVector = small_vector<StorageInst, 24>;
using StorageWritesSet =
flat_set<StorageBufferAddr, std::less<StorageBufferAddr>, small_vector<StorageBufferAddr, 16>>;
using LocalStores = std::multimap<u32, IR::Value>;
struct StorageInfo {
StorageBufferSet set;
@@ -333,7 +335,7 @@ std::optional<LowAddrInfo> TrackLowAddress(IR::Inst* inst) {
}
/// Tries to track the storage buffer address used by a global memory instruction
std::optional<StorageBufferAddr> Track(const IR::Value& value, const Bias* bias) {
StorageBufferSet Track(const IR::Value& value, const Bias* bias, const LocalStores& local_stores) {
const auto pred{[bias](const IR::Inst* inst) -> std::optional<StorageBufferAddr> {
if (inst->GetOpcode() != IR::Opcode::GetCbufU32 &&
inst->GetOpcode() != IR::Opcode::GetCbufU32x2) {
@@ -366,11 +368,83 @@ std::optional<StorageBufferAddr> Track(const IR::Value& value, const Bias* bias)
}
return storage_buffer;
}};
return BreadthFirstSearch(value, pred);
StorageBufferSet result;
std::unordered_set<const IR::Inst*> visited;
small_vector<const IR::Inst*, 32> pending;
const auto push{[&](const IR::Value& arg) {
if (!arg.IsImmediate() && visited.insert(arg.InstRecursive()).second) {
pending.push_back(arg.InstRecursive());
}
}};
push(value);
while (!pending.empty()) {
const IR::Inst* const inst{pending.back()};
pending.pop_back();
if (const std::optional<StorageBufferAddr> storage_buffer{pred(inst)}) {
result.insert(*storage_buffer);
continue;
}
switch (inst->GetOpcode()) {
case IR::Opcode::LoadLocal:
if (inst->Arg(0).IsImmediate()) {
const auto [begin, end]{local_stores.equal_range(inst->Arg(0).U32())};
for (auto it = begin; it != end; ++it) {
push(it->second);
}
}
continue;
case IR::Opcode::SelectU32:
case IR::Opcode::SelectU64:
push(inst->Arg(1));
push(inst->Arg(2));
continue;
case IR::Opcode::GetCbufU8:
case IR::Opcode::GetCbufS8:
case IR::Opcode::GetCbufU16:
case IR::Opcode::GetCbufS16:
case IR::Opcode::GetCbufU32:
case IR::Opcode::GetCbufF32:
case IR::Opcode::GetCbufU32x2:
case IR::Opcode::LoadSharedU8:
case IR::Opcode::LoadSharedS8:
case IR::Opcode::LoadSharedU16:
case IR::Opcode::LoadSharedS16:
case IR::Opcode::LoadSharedU32:
case IR::Opcode::LoadSharedU64:
case IR::Opcode::LoadSharedU128:
continue;
default:
break;
}
if (IsGlobalMemory(*inst) || inst->MayHaveSideEffects()) {
continue;
}
for (size_t arg = 0; arg < inst->NumArgs(); ++arg) {
push(inst->Arg(arg));
}
}
return result;
}
LocalStores GatherLocalStores(const IR::Program& program) {
LocalStores stores;
for (IR::Block* const block : program.post_order_blocks) {
for (const IR::Inst& inst : block->Instructions()) {
if (inst.GetOpcode() != IR::Opcode::WriteLocal) {
continue;
}
if (!inst.Arg(0).IsImmediate()) {
return {};
}
stores.emplace(inst.Arg(0).U32(), inst.Arg(1));
}
}
return stores;
}
/// Collects the storage buffer used by a global memory instruction and the instruction itself
void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info) {
void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info,
const LocalStores& local_stores) {
// NVN puts storage buffers in a specific range, we have to bias towards these addresses to
// avoid getting false positives
static constexpr Bias nvn_bias{
@@ -387,25 +461,24 @@ void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info)
}
// First try to find storage buffers in the NVN address
const IR::U32 low_addr{low_addr_info->value};
std::optional<StorageBufferAddr> storage_buffer{Track(low_addr, &nvn_bias)};
if (!storage_buffer) {
StorageBufferSet candidates{Track(low_addr, &nvn_bias, local_stores)};
if (candidates.empty()) {
// If it fails, track without a bias
storage_buffer = Track(low_addr, nullptr);
if (!storage_buffer) {
// If that also fails, use NVN fallbacks
LOG_WARNING(Shader, "Storage buffer failed to track, using global memory fallbacks");
return;
}
LOG_WARNING(Shader, "Storage buffer tracked without bias, index {} offset {}",
storage_buffer->index, storage_buffer->offset);
candidates = Track(low_addr, nullptr, local_stores);
}
if (candidates.size() != 1) {
// If that also fails, use NVN fallbacks
LOG_WARNING(Shader, "Storage buffer failed to track, using global memory fallbacks");
return;
}
const StorageBufferAddr storage_buffer{*candidates.begin()};
// Collect storage buffer and the instruction
if (IsGlobalMemoryWrite(inst)) {
info.writes.insert(*storage_buffer);
info.writes.insert(storage_buffer);
}
info.set.insert(*storage_buffer);
info.set.insert(storage_buffer);
info.to_replace.push_back(StorageInst{
.storage_buffer{*storage_buffer},
.storage_buffer{storage_buffer},
.inst = &inst,
.block = &block,
});
@@ -525,12 +598,13 @@ void Replace(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index,
void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info) {
StorageInfo info;
const LocalStores local_stores{GatherLocalStores(program)};
for (IR::Block* const block : program.post_order_blocks) {
for (IR::Inst& inst : block->Instructions()) {
if (!IsGlobalMemory(inst)) {
continue;
}
CollectStorageBuffers(*block, inst, info);
CollectStorageBuffers(*block, inst, info, local_stores);
}
}
for (const StorageBufferAddr& storage_buffer : info.set) {