From 9905f5ddd7302fc994caa680ee24029b9d8cc600 Mon Sep 17 00:00:00 2001 From: Michon <231849@buas.nl> Date: Sun, 4 Oct 2026 18:27:40 +0200 Subject: [PATCH 1/2] feat(recompiler): pixel ancillary word used whole A use of the packed ancillary VGPR other than a constant field extract (the word passed through a select before its sample number is extracted) reads the word rebuilt from the sample number (bit 8) and the render target index (bit 16). Extracts of other bits straight from the word stay refused, and the refusal now names the uses. Co-Authored-By: Claude Opus 5.5 (1M context) --- core/libs/tests/PixelAncillary.cpp | 15 ++++++++++++- .../Optimization/src/ConstantFolder.cpp | 21 ++++++++++++++++++- .../Optimization/src/ShaderInfoCollector.cpp | 14 +++++++++++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/core/libs/tests/PixelAncillary.cpp b/core/libs/tests/PixelAncillary.cpp index 2d6f10dad..9ed4c2a44 100644 --- a/core/libs/tests/PixelAncillary.cpp +++ b/core/libs/tests/PixelAncillary.cpp @@ -51,12 +51,25 @@ static void Refused(IrOpcode opcode, std::uint32_t offset, std::uint32_t count) } Require(false); } +static void Whole() { + IrProgram program; + auto& user = Build(program, IrOpcode::BitwiseOr32, 1u, 0u); + Lower(program); + const IrValue* word = user.Argument(0)->Resolve(); + Require(word->Opcode() == IrOpcode::BitwiseOr32); + const IrValue* sample = word->Argument(0)->Resolve(); + const IrValue* layer = word->Argument(1)->Resolve(); + Require(sample->Opcode() == IrOpcode::ShiftLeftLogical32 && sample->Argument(1)->Resolve()->ImmediateU32() == 8u); + Require(static_cast(sample->Argument(0)->Resolve()->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::SampleId); + Require(layer->Opcode() == IrOpcode::ShiftLeftLogical32 && layer->Argument(1)->Resolve()->ImmediateU32() == 16u); + Require(static_cast(layer->Argument(0)->Resolve()->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::Layer); +} int main() { Extract(IrOpcode::BitFieldUExtract, 8u, 4u, StageInputKind::SampleId, 0u); Extract(IrOpcode::BitFieldUExtract, 9u, 2u, StageInputKind::SampleId, 1u); Extract(IrOpcode::BitFieldUExtract, 16u, 13u, StageInputKind::Layer, 0u); Extract(IrOpcode::BitFieldSExtract, 20u, 9u, StageInputKind::Layer, 4u); - Refused(IrOpcode::BitwiseOr32, 1u, 0u); + Whole(); Refused(IrOpcode::BitFieldUExtract, 2u, 4u); Refused(IrOpcode::BitFieldUExtract, 10u, 4u); Refused(IrOpcode::BitFieldUExtract, 13u, 2u); diff --git a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp index a05a6c7a8..ae6d8963e 100644 --- a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp +++ b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp @@ -215,14 +215,17 @@ bool lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil }; constexpr std::array, 2> ranges {{{8u, 4u}, {16u, 13u}}}; bool lowered = false; + std::vector whole; for (const IrUse& use : uses) { IrValue& user = *use.user; if ((user.Opcode() != IrOpcode::BitFieldUExtract && user.Opcode() != IrOpcode::BitFieldSExtract) || use.operand != 0u) { + whole.push_back(use); continue; } auto& offset = resolveArg(user, 1); auto& count = resolveArg(user, 2); if (!isImmediate(offset, IrType::U32) || !isImmediate(count, IrType::U32) || count.ImmediateU32() == 0u) { + whole.push_back(use); continue; } const auto range = std::ranges::find_if(ranges, [&](const auto& candidate) { @@ -236,7 +239,23 @@ bool lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil user.ReplaceArgument(1, &builder.Constant(offset.ImmediateU32() - range->first)); lowered = true; } - return lowered; + if (whole.empty()) { + return lowered; + } + const auto emit = [&](IrOpcode opcode, IrValue& lhs, IrValue& rhs) -> IrValue& { + IrValue& created = program.CreateValue(opcode, IrType::U32); + created.AddArgument(&lhs); + created.AddArgument(&rhs); + ancillary.Parent()->InsertInstructionBefore(&ancillary, &created); + return created; + }; + IrValue& sample = emit(IrOpcode::ShiftLeftLogical32, field(0u), builder.Constant(8u)); + IrValue& layer = emit(IrOpcode::ShiftLeftLogical32, field(1u), builder.Constant(16u)); + IrValue& packed = emit(IrOpcode::BitwiseOr32, sample, layer); + for (const IrUse& use : whole) { + use.user->ReplaceArgument(use.operand, &packed); + } + return true; } } // namespace diff --git a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp index 93f66f1c3..672f01ef4 100644 --- a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp +++ b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp @@ -129,8 +129,18 @@ void ValidateValueReferences(const IrProgram& program, ShaderStageInputInfo inpu const auto kind = static_cast(kindValue->ImmediateU32()); const auto component = componentValue->ImmediateU32(); switch (kind) { - case StageInputKind::PackedAncillary: - return Fail("packed pixel ancillary input has an unsupported live use"); + case StageInputKind::PackedAncillary: { + std::string uses; + for (const IrValue* user : inst->Uses()) { + uses += uses.empty() ? " (used by " : ", "; + uses += IrOpcodeName(user->Opcode()); + for (std::size_t i = 1; i < user->ArgumentCount(); ++i) { + const IrValue* argument = user->Argument(i)->Resolve(); + uses += argument->HasImmediate() && argument->Type() == IrType::U32 ? " " + std::to_string(argument->ImmediateU32()) : " ?"; + } + } + return Fail("packed pixel ancillary input has an unsupported live use" + uses + (uses.empty() ? "" : ")")); + } case StageInputKind::Layer: case StageInputKind::SampleId: if (program.Resources().stage != IrShaderStage::Pixel || component != 0u) { From d8b1ad71d860b91465a453d268ce1941eabdedc7 Mon Sep 17 00:00:00 2001 From: Michon <231849@buas.nl> Date: Thu, 8 Oct 2026 17:47:08 +0200 Subject: [PATCH 2/2] fix(recompiler): lower the ancillary word only where every reader extracts a modelled field Co-Authored-By: Claude Opus 5.5 (1M context) --- core/libs/tests/PixelAncillary.cpp | 136 +++++++++++++++--- .../Optimization/src/ConstantFolder.cpp | 123 ++++++++++------ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 196 insertions(+), 64 deletions(-) diff --git a/core/libs/tests/PixelAncillary.cpp b/core/libs/tests/PixelAncillary.cpp index 9ed4c2a44..8815d9225 100644 --- a/core/libs/tests/PixelAncillary.cpp +++ b/core/libs/tests/PixelAncillary.cpp @@ -2,25 +2,54 @@ #include "Optimization/ConstantFolder.hpp" #include "Optimization/DeadCodeEliminator.hpp" #include "Optimization/ShaderInfoCollector.hpp" +#include #include #include #include using namespace ShaderRecompiler; static void Require(bool value) { if (!value) throw std::runtime_error("packed pixel ancillary regression"); } -static IrValue& Build(IrProgram& program, IrOpcode opcode, std::uint32_t offset, std::uint32_t count) { + +constexpr std::uint32_t UnmodelledBits = 0xE000F0FFu; +constexpr std::uint32_t SelectOther = 0x5A3C9E17u; + +struct Row { + std::uint32_t sample; + std::uint32_t layer; + bool frontFacing; +}; +constexpr std::array Rows {{{0u, 0u, false}, {0u, 0u, true}, {15u, 0x1FFFu, false}, {15u, 0x1FFFu, true}, {0u, 0x1FFFu, true}, {15u, 0u, false}}}; + +static IrBlock& Begin(IrProgram& program) { program.Resources().stage = IrShaderStage::Pixel; program.Resources().resourceTrackingComplete = true; auto& block = program.CreateBlock(); program.SetEntryBlock(block); program.BlockOrder().push_back(&block); - IrBuilder builder(program); - builder.SetInsertionPoint(block); - auto& ancillary = builder.Emit(IrOpcode::GetBuiltin, IrType::U32, {&builder.Constant(static_cast(StageInputKind::PackedAncillary)), &builder.Constant(0u)}); - auto& user = opcode == IrOpcode::BitwiseOr32 ? builder.Emit(opcode, IrType::U32, {&ancillary, &builder.Constant(offset)}) - : builder.Emit(opcode, IrType::U32, {&ancillary, &builder.Constant(offset), &builder.Constant(count)}); + return block; +} +static IrValue& AncillaryBuiltin(IrBuilder& builder) { + return builder.Emit(IrOpcode::GetBuiltin, IrType::U32, {&builder.Constant(static_cast(StageInputKind::PackedAncillary)), &builder.Constant(0u)}); +} +static IrValue& FrontFacingBuiltin(IrBuilder& builder) { + return builder.Emit(IrOpcode::GetBuiltin, IrType::U1, {&builder.Constant(static_cast(StageInputKind::FrontFacing)), &builder.Constant(0u)}); +} +static void End(IrBuilder& builder, IrValue& user) { static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&user})); static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); +} +static IrValue& Build(IrProgram& program, IrOpcode opcode, std::uint32_t offset, std::uint32_t count, bool throughSelect = false) { + auto& block = Begin(program); + IrBuilder builder(program); + builder.SetInsertionPoint(block); + IrValue& ancillary = AncillaryBuiltin(builder); + IrValue* source = &ancillary; + if (throughSelect) { + source = &builder.Emit(IrOpcode::SelectU32, IrType::U32, {&FrontFacingBuiltin(builder), &ancillary, &builder.Constant(SelectOther)}); + } + IrValue& user = opcode == IrOpcode::BitwiseOr32 ? builder.Emit(opcode, IrType::U32, {source, &builder.Constant(offset)}) + : builder.Emit(opcode, IrType::U32, {source, &builder.Constant(offset), &builder.Constant(count)}); + End(builder, user); return user; } static void Lower(IrProgram& program) { @@ -40,9 +69,9 @@ static void Extract(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, Require(user.Argument(1)->Resolve()->ImmediateU32() == fieldOffset); Require(user.Argument(2)->Resolve()->ImmediateU32() == count); } -static void Refused(IrOpcode opcode, std::uint32_t offset, std::uint32_t count) { +static void Refused(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, bool throughSelect = false) { IrProgram program; - static_cast(Build(program, opcode, offset, count)); + static_cast(Build(program, opcode, offset, count, throughSelect)); try { Lower(program); } catch (const std::runtime_error& error) { @@ -51,27 +80,96 @@ static void Refused(IrOpcode opcode, std::uint32_t offset, std::uint32_t count) } Require(false); } -static void Whole() { +static std::uint32_t BitField(std::uint32_t word, std::uint32_t offset, std::uint32_t count, bool isSigned) { + const std::uint32_t mask = count == 32u ? ~0u : (1u << count) - 1u; + std::uint32_t bits = (word >> offset) & mask; + if (isSigned && ((bits >> (count - 1u)) & 1u) != 0u) { + bits |= ~mask; + } + return bits; +} +static std::uint32_t Evaluate(const IrValue* value, const Row& row) { + value = value->Resolve(); + if (value->HasImmediate()) { + return value->ImmediateU32(); + } + switch (value->Opcode()) { + case IrOpcode::GetBuiltin: + switch (static_cast(Evaluate(value->Argument(0), row))) { + case StageInputKind::SampleId: return row.sample; + case StageInputKind::Layer: return row.layer; + case StageInputKind::FrontFacing: return row.frontFacing ? 1u : 0u; + default: break; + } + break; + case IrOpcode::ShiftLeftLogical32: return Evaluate(value->Argument(0), row) << Evaluate(value->Argument(1), row); + case IrOpcode::BitwiseOr32: return Evaluate(value->Argument(0), row) | Evaluate(value->Argument(1), row); + case IrOpcode::BitFieldUExtract: + case IrOpcode::BitFieldSExtract: + return BitField(Evaluate(value->Argument(0), row), Evaluate(value->Argument(1), row), Evaluate(value->Argument(2), row), value->Opcode() == IrOpcode::BitFieldSExtract); + case IrOpcode::SelectU32: return Evaluate(value->Argument(0), row) != 0u ? Evaluate(value->Argument(1), row) : Evaluate(value->Argument(2), row); + default: break; + } + throw std::runtime_error("packed pixel ancillary evaluation reached an unsupported value"); +} +static std::uint32_t PackedWord(const Row& row) { + return UnmodelledBits | (row.sample << 8u) | (row.layer << 16u); +} +static void Values(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, bool throughSelect) { IrProgram program; - auto& user = Build(program, IrOpcode::BitwiseOr32, 1u, 0u); + auto& user = Build(program, opcode, offset, count, throughSelect); Lower(program); - const IrValue* word = user.Argument(0)->Resolve(); - Require(word->Opcode() == IrOpcode::BitwiseOr32); - const IrValue* sample = word->Argument(0)->Resolve(); - const IrValue* layer = word->Argument(1)->Resolve(); - Require(sample->Opcode() == IrOpcode::ShiftLeftLogical32 && sample->Argument(1)->Resolve()->ImmediateU32() == 8u); - Require(static_cast(sample->Argument(0)->Resolve()->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::SampleId); - Require(layer->Opcode() == IrOpcode::ShiftLeftLogical32 && layer->Argument(1)->Resolve()->ImmediateU32() == 16u); - Require(static_cast(layer->Argument(0)->Resolve()->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::Layer); + for (const Row& row : Rows) { + const std::uint32_t word = throughSelect && !row.frontFacing ? SelectOther : PackedWord(row); + Require(Evaluate(&user, row) == BitField(word, offset, count, opcode == IrOpcode::BitFieldSExtract)); + } +} +static void LateFold() { + IrProgram program; + auto& block = Begin(program); + IrBuilder builder(program); + builder.SetInsertionPoint(block); + IrValue& ancillary = AncillaryBuiltin(builder); + IrValue& offset = builder.Emit(IrOpcode::BitwiseAnd32, IrType::U32, {&builder.Constant(16u), &builder.Constant(31u)}); + IrValue& count = builder.Emit(IrOpcode::UMin32, IrType::U32, {&builder.Constant(11u), &builder.Constant(13u)}); + IrValue& user = builder.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&ancillary, &offset, &count}); + End(builder, user); + Lower(program); + const IrValue* field = user.Argument(0)->Resolve(); + Require(field->Opcode() == IrOpcode::GetBuiltin); + Require(static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::Layer); + Require(user.Argument(1)->Resolve()->ImmediateU32() == 0u); + Require(user.Argument(2)->Resolve()->ImmediateU32() == 11u); + for (const Row& row : Rows) { + Require(Evaluate(&user, row) == BitField(PackedWord(row), 16u, 11u, false)); + } } int main() { Extract(IrOpcode::BitFieldUExtract, 8u, 4u, StageInputKind::SampleId, 0u); Extract(IrOpcode::BitFieldUExtract, 9u, 2u, StageInputKind::SampleId, 1u); Extract(IrOpcode::BitFieldUExtract, 16u, 13u, StageInputKind::Layer, 0u); Extract(IrOpcode::BitFieldSExtract, 20u, 9u, StageInputKind::Layer, 4u); - Whole(); + Values(IrOpcode::BitFieldUExtract, 8u, 4u, false); + Values(IrOpcode::BitFieldSExtract, 8u, 4u, false); + Values(IrOpcode::BitFieldUExtract, 9u, 2u, false); + Values(IrOpcode::BitFieldUExtract, 16u, 13u, false); + Values(IrOpcode::BitFieldSExtract, 20u, 9u, false); + Values(IrOpcode::BitFieldUExtract, 28u, 1u, false); + LateFold(); + Values(IrOpcode::BitFieldUExtract, 8u, 4u, true); + Values(IrOpcode::BitFieldUExtract, 16u, 13u, true); + Values(IrOpcode::BitFieldSExtract, 20u, 9u, true); + Refused(IrOpcode::BitwiseOr32, 1u, 0u); + Refused(IrOpcode::BitwiseOr32, 1u, 0u, true); Refused(IrOpcode::BitFieldUExtract, 2u, 4u); Refused(IrOpcode::BitFieldUExtract, 10u, 4u); Refused(IrOpcode::BitFieldUExtract, 13u, 2u); Refused(IrOpcode::BitFieldUExtract, 16u, 14u); + Refused(IrOpcode::BitFieldUExtract, 0u, 2u); + Refused(IrOpcode::BitFieldUExtract, 7u, 1u); + Refused(IrOpcode::BitFieldUExtract, 12u, 4u); + Refused(IrOpcode::BitFieldUExtract, 29u, 1u); + Refused(IrOpcode::BitFieldUExtract, 8u, 0u); + Refused(IrOpcode::BitFieldUExtract, 0u, 2u, true); + Refused(IrOpcode::BitFieldSExtract, 12u, 4u, true); } diff --git a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp index ae6d8963e..ea498bb8e 100644 --- a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp +++ b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -184,23 +185,64 @@ bool foldCompositeExtract(IrBuilder& builder, IrValue& inst, IrOpcode construct, return false; } -void collectUses(IrValue& value, std::vector& uses) { - for (const IrUse& use : value.OperandUses()) { - if (use.user->Opcode() == IrOpcode::Identity) { - collectUses(*use.user, uses); +bool forwardsWord(const IrUse& use) { + return use.user->Opcode() == IrOpcode::Identity || (use.user->Opcode() == IrOpcode::SelectU32 && use.operand != 0u); +} + +void collectReaders(IrValue& value, std::vector& readers) { + const std::vector uses = value.OperandUses(); + for (const IrUse& use : uses) { + if (forwardsWord(use)) { + collectReaders(*use.user, readers); } else { - uses.push_back(use); + readers.push_back(use); } } } -bool lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancillary) { +constexpr std::array, 2> packedFields {{{8u, 4u}, {16u, 13u}}}; + +std::optional packedField(std::uint32_t offset, std::uint32_t count) { + const auto field = std::ranges::find_if(packedFields, [&](const auto& candidate) { + return offset >= candidate.first && count <= candidate.second && offset - candidate.first <= candidate.second - count; + }); + if (field == packedFields.end()) { + return std::nullopt; + } + return static_cast(field - packedFields.begin()); +} + +void lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancillary) { auto& kind = resolveArg(ancillary, 0); if (!isImmediate(kind, IrType::U32) || static_cast(kind.ImmediateU32()) != StageInputKind::PackedAncillary) { - return false; + return; + } + const std::vector uses = ancillary.OperandUses(); + std::vector direct; + std::vector forwarded; + for (const IrUse& use : uses) { + if (forwardsWord(use)) { + forwarded.push_back(use); + } else { + direct.push_back(use); + } + } + std::vector readers = direct; + for (const IrUse& use : forwarded) { + collectReaders(*use.user, readers); + } + for (const IrUse& use : readers) { + const IrValue& user = *use.user; + if ((user.Opcode() != IrOpcode::BitFieldUExtract && user.Opcode() != IrOpcode::BitFieldSExtract) || use.operand != 0u) { + return; + } + auto& offset = resolveArg(user, 1); + auto& count = resolveArg(user, 2); + if (!isImmediate(offset, IrType::U32) || !isImmediate(count, IrType::U32) || count.ImmediateU32() == 0u || + !packedField(offset.ImmediateU32(), count.ImmediateU32())) { + return; + } } - std::vector uses; - collectUses(ancillary, uses); std::array fields {}; const auto field = [&](std::size_t index) -> IrValue& { if (fields[index] == nullptr) { @@ -213,35 +255,6 @@ bool lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil } return *fields[index]; }; - constexpr std::array, 2> ranges {{{8u, 4u}, {16u, 13u}}}; - bool lowered = false; - std::vector whole; - for (const IrUse& use : uses) { - IrValue& user = *use.user; - if ((user.Opcode() != IrOpcode::BitFieldUExtract && user.Opcode() != IrOpcode::BitFieldSExtract) || use.operand != 0u) { - whole.push_back(use); - continue; - } - auto& offset = resolveArg(user, 1); - auto& count = resolveArg(user, 2); - if (!isImmediate(offset, IrType::U32) || !isImmediate(count, IrType::U32) || count.ImmediateU32() == 0u) { - whole.push_back(use); - continue; - } - const auto range = std::ranges::find_if(ranges, [&](const auto& candidate) { - return offset.ImmediateU32() >= candidate.first && count.ImmediateU32() <= candidate.second && - offset.ImmediateU32() - candidate.first <= candidate.second - count.ImmediateU32(); - }); - if (range == ranges.end()) { - continue; - } - user.ReplaceArgument(0, &field(static_cast(range - ranges.begin()))); - user.ReplaceArgument(1, &builder.Constant(offset.ImmediateU32() - range->first)); - lowered = true; - } - if (whole.empty()) { - return lowered; - } const auto emit = [&](IrOpcode opcode, IrValue& lhs, IrValue& rhs) -> IrValue& { IrValue& created = program.CreateValue(opcode, IrType::U32); created.AddArgument(&lhs); @@ -249,13 +262,22 @@ bool lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil ancillary.Parent()->InsertInstructionBefore(&ancillary, &created); return created; }; - IrValue& sample = emit(IrOpcode::ShiftLeftLogical32, field(0u), builder.Constant(8u)); - IrValue& layer = emit(IrOpcode::ShiftLeftLogical32, field(1u), builder.Constant(16u)); - IrValue& packed = emit(IrOpcode::BitwiseOr32, sample, layer); - for (const IrUse& use : whole) { - use.user->ReplaceArgument(use.operand, &packed); + if (!forwarded.empty()) { + IrValue& sample = emit(IrOpcode::ShiftLeftLogical32, field(0u), builder.Constant(8u)); + IrValue& layer = emit(IrOpcode::ShiftLeftLogical32, field(1u), builder.Constant(16u)); + IrValue& packed = emit(IrOpcode::BitwiseOr32, sample, layer); + for (const IrUse& use : forwarded) { + use.user->ReplaceArgument(use.operand, &packed); + } + } + for (const IrUse& use : direct) { + IrValue& user = *use.user; + auto& offset = resolveArg(user, 1); + auto& count = resolveArg(user, 2); + const auto index = *packedField(offset.ImmediateU32(), count.ImmediateU32()); + user.ReplaceArgument(0, &field(index)); + user.ReplaceArgument(1, &builder.Constant(offset.ImmediateU32() - packedFields[index].first)); } - return true; } } // namespace @@ -264,7 +286,6 @@ bool ConstantFolder::tryFoldValue(IrProgram& program, IrValue& value) const { IrBuilder builder(program); switch (value.Opcode()) { case IrOpcode::Phi: return foldPhi(value); - case IrOpcode::GetBuiltin: return lowerPackedAncillary(program, builder, value); case IrOpcode::SelectU1: case IrOpcode::SelectF32: case IrOpcode::SelectU32: return foldSelect(value); @@ -671,6 +692,18 @@ void ConstantFolder::Fold(IrProgram& program, std::span blocks) const auto success = tryFoldValue(program, *inst); } } + std::vector words; + for (IrBlock* block : blocks) { + for (IrValue* inst : block->Instructions()) { + if (inst->Opcode() == IrOpcode::GetBuiltin) { + words.push_back(inst); + } + } + } + IrBuilder builder(program); + for (IrValue* word : words) { + lowerPackedAncillary(program, builder, *word); + } } void ConstantFolder::Fold(IrProgram& program) const { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index bfd74ff5f..b5cab86ac 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -41,6 +41,7 @@ Throughout the project, every function at every stage either **does exactly what ### Unknown function info - [getuid, geteuid, getgid, getegid and issetugid](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) report the root identity (0) and a process that never changed credentials (`issetugid` 0), as a jailbroken process has; the host user and group are not exposed to the guest. The values a title sees on a console are unverified +- [Packed pixel ancillary word](../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295, and their equality with the SampleId and Layer builtins is inferred, not measured. A value passed through a select or move is rebuilt from those two fields with bits 0-7, 12-15 and 29-31 zero. That is exact only because every read of the word must be an extract inside one modelled field; any other read throws. The other bits are unmodelled and their RDNA 2 layout is unknown: the RDNA 3.5 ISA is reported to give prim type at bits 0-1, VRS rates at bits 2-5 and eye id at bit 7, which was not checked against RDNA 2 - [sceNpEntitlementAccessGetEntitlementKey](../../core/libs/prx/libSceNpEntitlementAccess/Export.cpp) - the three-argument signature, 16-byte key and errors `0x817D0002`/`0x817D0007` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libNet.cpp); PPSA01521 passes a service label, entitlement label and output pointer. Only the no-key path is implemented: local entitlement labels carry no keys, and the output is untouched on failure. Console error precedence and key provisioning are unverified - [sceNpEntitlementAccess{Request,Poll}{Unified,Service}EntitlementInfoList](../../core/libs/prx/libSceNpEntitlementAccess/Export.cpp) - signatures unknown; no reference implements them, so they are declared without parameters. Like the consume requests in the same library, they report the signed-out NP state with `SCE_NP_ERROR_SIGNED_OUT` (`0x80550006`) and write nothing. PPSA15657 calls the unified request during start-up and continues past the error. Whether the console fails at the request or at the poll when signed out is unverified - [sceRudpGetStatus](../../core/libs/prx/libSceRudp/Export.cpp) - PPSA01521 passes an output pointer and byte count `0xF8`; the status layout and size rules are unknown, so the buffer is zeroed like `sceRudpGetContextStatus` (state 0 = idle, no connections) and the size is not validated. It returns `RUDP_ERROR_NOT_INITIALIZED` before initialization and 0 otherwise, as `sceRudpGetContextStatus` does. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/rudp/rudp.cpp) declares it without arguments and returns success without writing status