diff --git a/core/libs/tests/PixelAncillary.cpp b/core/libs/tests/PixelAncillary.cpp index 2d6f10dad..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,14 +80,96 @@ static void Refused(IrOpcode opcode, std::uint32_t offset, std::uint32_t count) } Require(false); } +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, opcode, offset, count, throughSelect); + Lower(program); + 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); + 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 a05a6c7a8..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,30 +255,29 @@ bool lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil } return *fields[index]; }; - constexpr std::array, 2> ranges {{{8u, 4u}, {16u, 13u}}}; - bool lowered = false; - for (const IrUse& use : uses) { - IrValue& user = *use.user; - if ((user.Opcode() != IrOpcode::BitFieldUExtract && user.Opcode() != IrOpcode::BitFieldSExtract) || use.operand != 0u) { - continue; + 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; + }; + 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); - if (!isImmediate(offset, IrType::U32) || !isImmediate(count, IrType::U32) || count.ImmediateU32() == 0u) { - 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; + const auto index = *packedField(offset.ImmediateU32(), count.ImmediateU32()); + user.ReplaceArgument(0, &field(index)); + user.ReplaceArgument(1, &builder.Constant(offset.ImmediateU32() - packedFields[index].first)); } - return lowered; } } // namespace @@ -245,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); @@ -652,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/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) { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a2c6e161b..87c952a6a 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