aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNéo Aguilera-Laurens <85024393+neoluigi4123@users.noreply.github.com>2026-10-31 12:29:08 +0100
committerGitHub <noreply@github.com>2026-10-31 12:29:08 +0100
commit20262163d1cd62fa6de8237aa1f252b2927d553d (patch)
tree5104c895b757c313ee31f440e87dc4468af13929 /src
parentb7a816add8f1c169e1f4b926ad524c6f60cb9c73 (diff)
parent38230873a4a59ad9c05651e95d2d76e0cc575228 (diff)
Merge pull request #82 from vibecodekun/fix/special-cup-device-loss
Tested on Windows with the attached save, the Special Cup reveal no longer crashe
Diffstat (limited to 'src')
-rw-r--r--src/shader_recompiler/ir_opt/constant_propagation_pass.cpp102
1 files changed, 85 insertions, 17 deletions
diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
index 0c0d4fb1a2..9325259450 100644
--- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
+++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
@@ -953,7 +953,77 @@ void FoldDriverConstBuffer(Environment& env, IR::Block& block, IR::Inst& inst, u
}
}
+/// Values a predicate mask can take, as bits for -1 (1), 0 (2) and 1 (4); 0 when it can take any
+/// other value. Follows the selects, negations, +/-1 and phis guest compilers build masks from.
+u32 MaskValues(const IR::Value& value, u32 depth = 0) {
+ constexpr u32 minus_one{1}, zero{2}, one{4};
+ const IR::Value resolved{value.Resolve()};
+ if (resolved.IsImmediate()) {
+ if (resolved.Type() != IR::Type::U32) {
+ return 0;
+ }
+ switch (resolved.U32()) {
+ case 0xffffffffU:
+ return minus_one;
+ case 0:
+ return zero;
+ case 1:
+ return one;
+ default:
+ return 0;
+ }
+ }
+ if (depth >= 8) {
+ return 0;
+ }
+ const IR::Inst* const inst{resolved.InstRecursive()};
+ switch (inst->GetOpcode()) {
+ case IR::Opcode::SelectU32: {
+ const u32 true_values{MaskValues(inst->Arg(1), depth + 1)};
+ const u32 false_values{MaskValues(inst->Arg(2), depth + 1)};
+ return true_values != 0 && false_values != 0 ? true_values | false_values : 0;
+ }
+ case IR::Opcode::INeg32: {
+ const u32 values{MaskValues(inst->Arg(0), depth + 1)};
+ return (values & zero) | ((values & minus_one) << 2) | ((values & one) >> 2);
+ }
+ case IR::Opcode::IAdd32:
+ for (size_t index = 0; index < 2; ++index) {
+ const IR::Value step{inst->Arg(1 - index).Resolve()};
+ if (!step.IsImmediate()) {
+ continue;
+ }
+ const u32 values{MaskValues(inst->Arg(index), depth + 1)};
+ if (step.U32() == 1 && (values & one) == 0) {
+ return values << 1;
+ }
+ if (step.U32() == 0xffffffffU && (values & minus_one) == 0) {
+ return values >> 1;
+ }
+ return 0;
+ }
+ return 0;
+ case IR::Opcode::Phi: {
+ u32 values{};
+ for (size_t index = 0; index < inst->NumArgs(); ++index) {
+ const u32 arg_values{MaskValues(inst->Arg(index), depth + 1)};
+ if (arg_values == 0) {
+ return 0;
+ }
+ values |= arg_values;
+ }
+ return values;
+ }
+ default:
+ return 0;
+ }
+}
+
void FoldSMax32(IR::Block& block, IR::Inst& inst) {
+ const auto is_mask = [](const IR::Value& candidate) {
+ return candidate.IsImmediate() &&
+ (candidate.U32() == 0 || candidate.U32() == 1 || candidate.U32() == 0xffffffffU);
+ };
for (size_t index = 0; index < 2; ++index) {
const IR::Value value{inst.Arg(index).Resolve()};
const IR::Inst* const negation{inst.Arg(1 - index).TryInstRecursive()};
@@ -961,26 +1031,24 @@ void FoldSMax32(IR::Block& block, IR::Inst& inst) {
negation->Arg(0).Resolve() != value) {
continue;
}
+ IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
const IR::Inst* const select{value.TryInstRecursive()};
- if (!select || select->GetOpcode() != IR::Opcode::SelectU32) {
- continue;
+ if (select && select->GetOpcode() == IR::Opcode::SelectU32 &&
+ is_mask(select->Arg(1).Resolve()) && is_mask(select->Arg(2).Resolve())) {
+ // max(x, -x) is 0 or 1 for boolean and predicate-mask constants.
+ const IR::Value true_value{select->Arg(1).Resolve()};
+ const IR::Value false_value{select->Arg(2).Resolve()};
+ inst.ReplaceUsesWith(ir.Select(IR::U1{select->Arg(0)},
+ ir.Imm32(true_value.U32() != 0 ? 1U : 0U),
+ ir.Imm32(false_value.U32() != 0 ? 1U : 0U)));
+ return;
}
- const IR::Value true_value{select->Arg(1).Resolve()};
- const IR::Value false_value{select->Arg(2).Resolve()};
- const auto is_mask = [](const IR::Value& candidate) {
- return candidate.IsImmediate() &&
- (candidate.U32() == 0 || candidate.U32() == 1 ||
- candidate.U32() == 0xffffffffU);
- };
- if (!is_mask(true_value) || !is_mask(false_value)) {
- continue;
+ // Masks merged by predicated moves (-mask, 1 - mask) are still -1, 0 or 1, so max(x, -x)
+ // is x & 1. NVIDIA's compiler loses the device on the max form (Mario Kart World).
+ if (MaskValues(value) != 0) {
+ inst.ReplaceUsesWith(ir.BitwiseAnd(IR::U32{value}, ir.Imm32(1)));
+ return;
}
- // max(x, -x) is 0 or 1 for boolean and predicate-mask constants.
- IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
- inst.ReplaceUsesWith(ir.Select(IR::U1{select->Arg(0)},
- ir.Imm32(true_value.U32() != 0 ? 1U : 0U),
- ir.Imm32(false_value.U32() != 0 ? 1U : 0U)));
- return;
}
}
void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) {