sceAgcFuseShaderHalves copied both halves' code, the fused header and its
tables into the caller's memory and registered the program at that
address. PPSA02100 fuses different halves into the same memory a few
calls apart, while draws that use the earlier result are still queued.
The registry keeps one program per code address, so those draws were
compiled from the later program: its code read a user-data word the
bound layout does not have, and the device reported "BDA access failed"
for the mesh stage.
The PS5 5.02 module writes only the back half's register list into that
memory (sceAgcGetFusedShaderSize reports eight bytes per register,
alignment 4) and leaves the code where the halves are.
The fused code, header and tables now live in memory libSceAgc owns, one
program per distinct pair of halves, registered once. The caller's
memory receives the fused register list and the size query reports that
list. A second fusion of the same halves returns the same program; halves
whose contents changed get a new one.
The driver looked a header up by comparing its fields with the
registered header, including the register list's address. A header whose
register list is a copy elsewhere with the same contents now matches
too, which is what the title passes after a fusion.
Add the missing wide secure formatting exports required by PPSA15552
(Dead Cells), whose load failed with `undefined symbol: Im55VJ-Bekc`:
swprintf_s and its va_list counterpart follow C11 Annex K.3.9.1.4
(the complete output, including the terminator, must fit), while
vsnwprintf_s is the va_list counterpart of snwprintf_s (K.3.9.1.3,
truncation with full length). The export names produce NIDs
Im55VJ-Bekc, oDoV9tyHTbA and 8SKVXgeK1wY respectively. Document
the new functions in docs/dev/debt/libc.md.
When a relinked executable fails to load a staged module, the dependency
diagnostics routine maps the same path as an image resource to walk its
imports. If that mapping failed it printed only "FAIL: dependency
diagnostics Windows API failed". The log then did not say whether the
staged module itself (libs\libSceRegMgr.prx in #4720) was absent or was
not a Windows image, and a module reached through another module's
imports was not named at all.
The register routine now reads GetLastError when LoadLibraryExW fails.
ERROR_FILE_NOT_FOUND and ERROR_PATH_NOT_FOUND print "dependency module
not found", ERROR_BAD_EXE_FORMAT prints "dependency module is not a
Windows PE image"; both name the module path, its importer and the
chain from the executable, like the existing missing-module report.
Other errors keep the generic message. An API set contract that
LoadLibraryExA reports as ERROR_MOD_NOT_FOUND now gets the
missing-module report too.
Refs #4720
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A 2D-array image_load or image_store bound to a 3D T# addresses the slice as z. Add a 3D runtime mode for these accesses and drop loads and stores whose slice is past the volume depth.
The per-miss component comparison allocates and walks the whole key, and
it ran under APS5_PROFILE_DRAW as well, where it cost queue time on every
resource-content miss. It now runs only with the trace variable, which is
also the only one that prints its [keymiss] lines.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
WriteFileAtomically renames its handle over the published entry and
closes it afterwards. A std::ifstream reader opened in that window fails
with ERROR_SHARING_VIOLATION, and the cache reports the entry as gone.
Read through one CreateFileW handle that shares read, write and delete.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TryMakeTableImage planned a T# read from a bindless image table only when
its eight scalar reads had no user other than that one GetImageResource.
Descriptor-phi splitting gives each image operation its own copy of the
T# over the same reads, so a table entry that is both queried and sampled
was refused, and GetHandle then rejected the scalar reads.
Accept reads whose every user is a GetImageResource built from exactly
those eight reads; each copy is planned as the same table.
Crimson Desert (PPSA25286) stops on such a shader at start-up:
image_get_resinfo and image_sample_lz on one s_buffer_load_dwordx8 T#.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ComputeExtraBlockVaddr kept the ELF congruence p_vaddr == p_offset (mod 0x1000)
and bumped the result by one page only when it fell below the highest guest
load end as a byte address. When that end is not page aligned and the extra
block's file offset remainder reaches it, the extra RWX PT_LOAD landed in the
same 4 KiB page that holds the tail of the highest guest segment. The kernel
maps each PT_LOAD page by page with MAP_FIXED in program-header order, so the
extra block's file page replaced the guest's .data tail and zero-filled .bss
there.
Round the highest kept PT_LOAD end up to the page before adding the offset
remainder, which is the placement GuestModuleWriter::WriteLinux already uses
for the guest module. Only the overlapping case changes; an end that is page
aligned or a remainder below it yields the same address as before.
The extra PT_LOAD now also spans to the end of its own page in p_memsz, so the
page it takes over is fully covered, and its vaddr is derived in WriteLayout
instead of being taken from the request. The header block load carries the
same flags as before.
Read the optional aligned budget from param.json and apply it consistently to configured, available and mapped flexible memory. Add parser and allocation regression coverage.
Structurize the 20-block graph whose merge splitting adds one forwarder
per round until its round budget runs out, and a 12-block loop whose
2000-word latch is too large to clone, so merge splitting keeps adding
junctions. Both must give structured branches, every original block once
and the same executions as the input.
Give the loop an unsupported exit, which keeps the reach-flag fallback
out, and require Structurize to report the growth budget refusal instead
of the round budget refusal it reached before after 676 blocks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The structurizer's attempts could spend more than 10 s on a graph of 20
blocks before they accepted or refused it; in a title that stalls shader
preparation. Nearly all of that time went to recomputing dominators and
post-dominator fixpoints while merge splitting grew the graph to
hundreds or thousands of blocks.
Dominators, post-dominators and the fixpoints in mergeBesideReturns,
iterationSelectionMerge and joinWithinIteration now sweep the blocks in
reverse post-order (dominators) or post-order (the others) instead of in
id order. Splitting appends its new blocks at the end, so id order needed
a sweep for almost every new block. Each fixpoint starts from the full
set and its equations are monotone, so every sweep order reaches the
same greatest fixed point and the result does not change.
Shared merge splitting also refuses an attempt once the graph has more
than 16 times its block count at the start of splitting (at least 64
blocks). Without it, splits that route through a new junction each round
grew some graphs past 4,000 blocks before the round budget ran out. A
refused attempt goes to the next attempt or to the reach-flag fallback,
as it does after the round budget.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Structurize eight graphs the ladder refuses on main (a duplicate merge, a
region that wraps around to its dominator, the merge-splitting budget, a
conditional without a structured merge, an irreducible entry and three
loops that never exit) and the #2642 two-latch loop, which the ladder
accepts. Each must give structured branches, every original block exactly
once and the same executions as the input for every branch decision.
Register agc_structurizer_forced_fallback and
agc_shader_control_flow_forced_fallback, which run the in-tree graphs and
RDNA programs, through Recompile and SPIR-V validation, with
APS5_FORCE_STRUCTURIZER_FALLBACK=1. Under that switch the split-kind
checks become checks that no block is cloned and a route variable is
used, and the nested-selection check names blocks by their original
instructions instead of their position.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When every attempt of the structurizer ladder refuses a graph, the title
aborts. Rebuild such a graph from a hierarchical decomposition into
strongly connected components instead: each original block keeps one copy
and runs behind a guard on its own reach flag, and each component becomes
a loop with a single latch that repeats it while a back edge set its again
flag. Every edge sets the reach flag of its target, so exactly one flag
names the next original block and the blocks run in the input's order for
every branch decision. Irreducible and non-terminating graphs are handled
the same way, and no block is cloned.
The ladder runs first and its output is unchanged. The fallback only runs
when both attempt series refuse; if it cannot structure the graph, the
ladder's error is reported as before. Blocks that start the top-level
order unconditionally run unguarded, so the entry block's results reach
later blocks without a stale path. APS5_FORCE_STRUCTURIZER_FALLBACK=1
uses the construction for every supported graph, for tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The 9-word kernel #3816 matches stores one dword per thread, loaded with
s_buffer_load_dword through the descriptor in user[4..7]. Run as a
compute dispatch, its stores are pending writes of unknown value, so a
later read of DCC keys in that range waits for the GPU.
fillBuffer now takes it as a uniform buffer fill when all of these hold:
- the output descriptor's format is 32_UINT, 32_SINT or 32_FLOAT (20-22),
so buffer_store_format_x stores the loaded dword unchanged;
- the value descriptor covers the loaded dword: num_records times the
stride (num_records alone when the stride is 0) is at least 4 bytes,
the range the recompiler gives a scalar buffer load;
- exactly 8 user SGPRs and group id X enabled, so the group id is s8;
- a direct, non-partial dispatch, 64x1x1 threads, one group in Y and Z,
and a 16-byte aligned range of whole 16-byte units.
The value is read when the fill is recorded, after the labels queued
before it, as the pattern-fill kernel's source is. When the value is one
byte repeated and that byte is a DCC key code, the fill goes into the GPU
key-store memo whatever ClassifyFill reports: the memo answers a key
read only while this fill is still the newest pending write over the
keys, and a fill over a color target's keys is not a storage image's
keys fill.
Co-Authored-By: Claude Opus 5.5 <[email protected]>