Repository navigation
feat(agc): capture submissions and replay their decode and wait releases offline - #4969
Conversation
|
Windows test (MinGW GCC 15.2, Release, Windows 10, RTX 4080 SUPER). Built on main
Not tested: no game run. The batch was built once, not per PR; nothing failed, so no bisect was needed. AI-assisted (Claude Code). |
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
|
115c0cb to
2956c0a
Compare
64f083a to
b0b40aa
Compare
…ses offline
A hang with threads in Driver::waitMemory takes a ten-minute boot to
reproduce, and the existing switches show only the first submissions of
one queue (APS5_DUMP_QUEUE) or a rejected one (APS5_DUMP_REJECTED).
Capture. APS5_DUMP_SUBMITS=<dir> writes every submission the driver
accepts. <dir>/index.txt starts with "aps5-submissions 1" and "ordinal
queue type serial time_us dwords kind file held conditional", then one
line per submission: acceptance ordinal, queue id, graphics or compute,
serial, microseconds on the APS5_TRACE_GPU clock, dword count, kind,
file name, the dword offsets of the waits held at submit
(Submission::heldAtSubmit) and the COND_EXEC and predicated indirect
buffer ranges (Submission::conditionalEnds) as start:end, "-" for none.
<dir>/submission_<ordinal>.bin holds the copied dwords, indirect
buffers inlined. REWIND tails are listed as rewind with the serial of
their submission, suspend points as suspend without a file.
SubmissionCapture::Prepare copies the commands before the driver lock;
Take, under it after reserveOutputs, sets only the ordinal, time and
held waits and does not allocate; the file and then its index line are
written after the lock. A failed write reports the failure to the driver
(Driver::ReportFailure) and throws, so every later driver call throws
too. Unset, Submit tests a static pointer. An index written before the
held and conditional columns ("ordinal queue type serial time_us dwords
kind file") is read with both unknown.
Analysis. agc_pm4_replay <dir> [--full] decodes each submission and
throws on a packet the driver rejects (a corrupt capture). It does not
model cross-queue timing: a wait is released only when the capture
alone proves that the driver releases it (WaitMemory.cpp,
LabelHistory.cpp), and unknown, with the reason, when its outcome
depends on when another queue's stores run. A memory wait is released
when its compare always passes, when it was held at submit, when the
last store to the awaited bytes before it on its queue covers them with
an accepted value, is not conditional and no other queue stores to
those bytes anywhere in the capture, or when a RELEASE_MEM or
WRITE_DATA label earlier in the waiting submission that the label
history keeps (KeepsLabelStore, not conditional) stores an accepted
value and fewer than LabelStoreHistory other stores to the awaited
bytes can run between it and the wait: those between them on the
waiting queue and every store of another queue to those bytes, since
the history keeps the last stores per dword over all queues and finds
only a store that ran after the waiting submission entered the driver.
A REWIND is released when its tail is in the capture; one valid at
submit without a captured tail is unknown, because the driver reads the
valid bit again when the queue reaches it. A wait is unreleased when it was not held at submit and no
store that can run before it may write an accepted value (a store of
part of the awaited bytes or of a run-time value may; a store later on
its queue runs after it): the GPU does not release it, a CPU or shader
store could. A REWIND without a tail that was not valid at submit is unreleased. Every other wait is
unknown: "depends on cross-queue timing (stores on queue X at #...)"
when another queue stores to the awaited bytes, otherwise because the
last store before it on its queue is conditional, stores a run-time
value or part of the bytes, or overwrites an accepted store the capture
does not prove still in the label history. Without the held column a
wait that would be unreleased is unknown; without the conditional
column every packet after a COND_EXEC or predicated INDIRECT_BUFFER is
conditional to the end of its submission. The outcomes depend on the
order of each queue's stores and on which stores other queues make, not
on the submit order across queues. The report counts the outcomes,
routes and cross-queue unknowns, says when the index lacks the columns,
lists up to 32 unreleased and then unknown waits newest first with the
packets around them and their stores, then the last submissions of
each queue; exit code 1 when a wait is unreleased, 2 on errors.
Pm4 gains DecodeStores, DecodeWait, DecodeConditionalWrite,
DecodeConstantRamDump, the COPY_DATA and DMA_DATA field helpers, the
sample dump layout and RewindValid; Execute, ResolveStore, Validate,
DecodeMemoryCopy, the driver's sample dump, noteHeldAtSubmit and
executeRewindTail use them. LabelStoreHistory and KeepsLabelStore move
to LabelStores.hpp, used by noteLabelStore and the analysis.
Test. agc_driver_pm4_replay submits command buffers built with the
libSceAgc builders through the driver with the switch set and checks the
index, the held waits, the COND_EXEC range, the dwords, the decode and
the outcome of eight waits, including WRITE_DATA 1, WRITE_DATA 0,
WAIT == 1 in one submission, which the driver releases through its
label history. Hand-made captures check each release route, a store of
another queue to the awaited bytes that entered before or after
(unknown), the label history limit with later stores on the waiting
queue or on another queue, a fill the history does not keep, a store
in an earlier submission overwritten later, later stores, rejected
values and a missing rewind tail (unreleased), the six captures of the
reviews (none released but the wait held at submit), five groups of
waits in eight submission orders with identical outcomes, an index
without the held and conditional columns and corrupt captures. A
directory in place of the next submission file makes the write fail,
and the submit, the next submit and shutdown throw.
agc_driver_pm4_replay_tool runs the tool through
tests/Pm4ReplayTool.cmake and checks exit codes 1 and 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b0b40aa to
0286a76
Compare
|
Checked on the PR merged onto current |
What
A hang with threads in
Driver::waitMemorytakes a ten-minute boot to reproduce, and the existing switches show only the first submissions of one queue (APS5_DUMP_QUEUE) or a rejected one (APS5_DUMP_REJECTED). This adds a capture of every submission and an offline tool that sorts the captured waits into three groups: those the capture proves released, those no captured store can release, and those it cannot decide.Capture.
APS5_DUMP_SUBMITS=<dir>writes every submission the driver accepts.<dir>/index.txtstarts withaps5-submissions 1and the column lineordinal queue type serial time_us dwords kind file held conditional, then one line per submission.heldlists the dword offsets of the waits whose value passed their compare at submit (Submission::heldAtSubmit, which the driver then does not wait for).conditionallists the COND_EXEC and predicated indirect buffer ranges asstart:end(fromSubmission::conditionalEnds).submission_<ordinal>.binholds the copied dwords with indirect buffers inlined. REWIND tails and suspend points are listed too.SubmissionCapture::Preparecopies the commands before the driver lock. Under the lock,Takesets only the ordinal, the time and the held waits, afterreserveOutputs, and does not allocate (it isnoexcept). The file and then its index line are written after the lock is released. A failed write reports the error to the driver (Driver::ReportFailure) and throws: the call that made the submission throws after the submission was queued, and every later driver call throws the same error. When the switch is unset,Submitonly tests a static pointer.An index written by an earlier build, whose column line is
ordinal queue type serial time_us dwords kind file, is still read. Its held waits and conditional ranges are unknown (CapturedSubmission::heldKnown,conditionalKnown), and the report says so.Analysis.
agc_pm4_replay <dir> [--full]decodes each submission and throws if the driver would reject a packet, since that means the capture is corrupt.The tool does not model cross-queue timing. A store of another queue can run before, between or after the stores of the waiting queue, whatever order the submissions entered the driver in, so the tool never uses submit order across queues.
Each WAIT_REG_MEM, WAIT_REG_MEM_64 and REWIND gets one of three outcomes.
KeepsLabelStore: 4-byte aligned, at most 64 bytes; not conditional). Fewer thanLabelStoreHistory(4) other stores to the awaited bytes may run between the label and the wait.storedSince(LabelHistory.cpp:21) finds a store only if its stamp is above the waiting submission'sreceived. The capture proves that only for a store in the same submission.noteLabelStore(LabelHistory.cpp:9-19) keeps one history per dword, shared by all queues. So the count includes the stores between the label and the wait on the waiting queue, plus every store of another queue to those bytes, whatever its submit order.unreleased by the GPU; a CPU store could release it.unknown: depends on cross-queue timing (stores on queue 0x20 at #377, #427, #477; ...)when another queue stores to the awaited bytes and no route above holds. For each queue, the list gives the 4 stores nearest the waiting submission in submit order and how many more there are.The outcomes depend only on the order of each queue's own stores and on which stores the other queues make. They never depend on the submit order across queues.
This revision removes the fixed-point machinery of the previous one (two sets of waits grown and alternated, store run states). Each wait is now matched once, against the stores to its bytes.
The report counts waits per outcome and route, and the unknown waits that depend on cross-queue timing. It says when the index lacks the held and conditional columns. It lists up to 32 unreleased and then unknown waits, newest first, each with the packets around it and the stores to the awaited bytes. It ends with the last two submissions of each queue. The exit code is 1 when a wait is unreleased and 2 on errors.
Shared decode. The store and wait fields come from new
Pm4helpers:DecodeStores,DecodeWait,DecodeConditionalWrite,DecodeConstantRamDump,CopyDataSource/CopyDataImmediate/CopyDataBytes/CopyDataToRegister,DmaBytesand the DMA select constants,SampleDump/SampleDumpAddressand the sample-dump layout constants, andRewindValid.Pm4::Execute,ResolveStore,Validate,DecodeMemoryCopy,PacketMemory/PacketExecution(sample dump),VulkanDevice::DumpSamplesOnGpu,noteHeldAtSubmitandexecuteRewindTailnow use them in place of their own copies.LabelStoreHistoryandKeepsLabelStoremoved toDriver/Synchronization/LabelStores.hpp, andnoteLabelStoreusesKeepsLabelStore. There is no EVENT_WRITE_EOP decoding:Validaterejects opcode 0x47.Tests.
agc_driver_pm4_replaybuilds command buffers with the libSceAgc builders and submits them through the driver with the switch set. It checks:It then checks hand-made captures:
every capture from the reviews, with its exact dwords:
deadlock,stale,stale-held,evict3,evict4andevict-own4. No wait may be released, except the compute wait ofstale-held, which is held at submit. Every other wait must beunknown: depends on cross-queue timing (stores on queue ..., and the report must exit with 0.each provable route:
the limits of those routes:
unreleased: a store later on the waiting queue, a value of another queue the compare rejects, and a REWIND without a tail, reported newest first.
permutations: five groups of waits, each on its own queues and addresses, written in eight submission orders: in sequence, reversed, round robin both ways and four pseudo-random interleavings. The groups are:
stalepattern;All 13 waits must get the same verdict, route and cross-queue flag in every order.
an index without the held and conditional columns.
corrupt captures: EVENT_WRITE_EOP, a held offset that is no wait, and a range that starts at no COND_EXEC.
Last, a directory in place of the next submission file makes the capture write fail. The submit throws
cannot write, the next submit throws the same error, the index gets no line for it, and shutdown throws the error too.agc_driver_pm4_replay_tool(tests/Pm4ReplayTool.cmake) runs the tool on the driver capture. It checks:--full;Tested
Windows Server 2025, RTX 3060, GCC 15.2 (winlibs), build-dev configuration, rebased on
mainat ea2bbef.agc_driver_pm4_replayandagc_driver_pm4_replay_toolpass.agc_driver_pm4_replayfail:the reviewer capture evict-own4 reports the wait at #0 +12 as 0 route 3;a store of 1 in an earlier submission, overwritten by a store of 0, does not leave the wait unknown.deadlock,stale,evict3,evict4,evict-own4:released 0,unknown 2 (cross-queue timing 2),unreleased 0.stale-held:released 1 (... held at submit 1 ...),unknown 1 (cross-queue timing 1).ctest -R "^agc" -j 4, with every test executable built: 462 tests, 461 pass andagc_driver_stencil_ref_exportskips. The tests listed as failing onmainon this machine (agc_driver_flat_loads*,agc_driver_global_loads*,agc_driver_flat_apertures_2,_5,agc_driver_pixel_interlock) pass on this base.diff -r). An earlier build wrote both without theheldandconditionalcolumns, so no wait can be unreleased: a wait that would be is unknown instead. I did not convert them, because writing-would claim that no wait was held.capture2: 488 index lines (469 submissions with commands, 19 suspend points), 293,334 dwords, 60,433 packets, 6,978 stores.capture3: 513 index lines (493 submissions with commands, 20 suspend points), 308,366 dwords, 63,535 packets, 7,337 stores.capture2Windows build fails when BUILD_TESTING=ON (guest_json_tests and fiber_tests) #75+0x005egraphicsWAIT_REG_MEM [0x242dff7e0] == 0x1was released by current value and is now released by the label history:WRITE_DATA 1at Windows build fails when BUILD_TESTING=ON (guest_json_tests and fiber_tests) #75+0x0054, with 3 compute stores to the address in the whole capture. Because compute stores to that address, the current value no longer proves the release.capture2waits on the same label are unknown by cross-queue timing:+0x0141and+0x038eof fix(tools): count the debug branches and s_ttracedata_imm as decoded shader instructions #380, feat(relinker): lower SHA-1 instructions for --to-intel #430 and fix(relinker): keep the host libc.prx loadable when a title bundles its own #480. In each, the last graphics store before the wait (Windows build fails when BUILD_TESTING=ON (guest_json_tests and fiber_tests) #75) stores 1, and so do the 3 compute stores (feat(agc): 4 KiB standard color targets and color views of one array slice #377, fix(libc): count wide string precision in output characters #427, ci: run lavapipe at the AVX level on every Linux runner #477). The waits would pass whichever store ran last, but proving that needs a route over the stores of other queues, which this revision deliberately does not have.capture20.29, 0.31 and 0.25 s;capture30.28, 0.34 and 0.34 s. These are runtimes of the new tool only. No speed change of the emulator is claimed.python -I tools/check_test_registration.py build-devand the conventions check pass.A final review found that a REWIND valid at submit was reported released although the driver reads its valid bit again when the queue reaches it; such a REWIND without a captured tail is now unknown, covered by a new case in
agc_driver_pm4_replay(a REWIND that was valid at submit without its tail), which fails with the previous rule.agc_driver_pm4_replayandagc_driver_pm4_replay_toolpass on the final commit.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code