When queue 0 was busy, DecodeOrderedVertexStageInfo reread every
recorded attribute word and V# after the decode to catch a store that
finished while it ran, a second read of the vertex tables for every
draw issued behind queued work. Queue 0 now counts retired items.
Before the decode the helper notes that count and the write ranges
queued then; afterwards it drains and decodes again only when a read
overlaps a write still queued, or when the count moved and a read
overlaps a range that was queued at the start (as CnRJay proposed).
Writes still held by the draw finisher count as queued, as in Overlaps.
Decode-caused drains are counted on the [draw] pipelined line.
agc_driver_vertex_table_read_order also checks that a table a finished
item wrote, and a store elsewhere retiring mid-decode, drain nothing.
AI-assisted: yes
Review of #9572 found three gaps:
- PeModule.name() checked that an RVA was in some code section, then
bisected one list of every symbol, so an address in a later code
section before that section's first symbol took the last symbol of an
earlier section. Each code section now keeps its own sorted list.
- Address files keep both counters ('module offset self inclusive'):
the inclusive count is kept and subtracted by --since, and the summary
lists functions by inclusive samples as well, summed over each
function's addresses (an upper bound when a function is on one stack
more than once). The total comes from the '# N samples' line or from
the 'thread <id> samples <n>' headings of APS5_SAMPLE_THREADS, which
the loader previously misread as a stack line; --thread keeps one
thread.
- The address writers of both samplers wrote no '# module' records, so
those files skipped the size and timestamp check. Both now write them
(through the helper the collapsed-stack writer uses), and the tool
leaves a module unsymbolized when the samples have no record for it,
unless --unverified is given.
Five new cases cover a second code section, inclusive totals, --since
on an address file, thread headings with --thread, and the missing
record with and without --unverified.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Windows samplers (APS5_SAMPLE_WORKER, APS5_SAMPLE_COMMITTER and
APS5_SAMPLE_THREADS) write every frame as module+0xRVA, and BUILD.md left
naming them to the reader ("for example with nm on the .prx"). Doing that
by hand means finding each module's image base, choosing between COFF
symbols and the export table, and keeping the files cumulative totals in
mind.
tools/symbolize_samples.py reads a collapsed-stack file or the address
file, opens each module it finds in --libs as a PE32+ image, and names
every frame module!function:
- From the COFF symbol table that the MinGW-built .prx and .dll keep:
external and static symbols in code sections, at the section's RVA
plus the symbol value. Addresses outside a code section, or before the
first symbol, stay module+0xRVA.
- From the export table, prefixed with "~", for a module without
symbols, such as the runtime DLLs.
- A module whose SizeOfImage or TimeDateStamp differs from the sampler's
"# module" line is left unsymbolized, with a warning: its addresses
would name the wrong functions.
--since subtracts an earlier copy of the same file, which keeps only the
samples taken between the two copies (the files hold totals since
start-up). --out writes the named stacks for speedscope or flamegraph.pl.
--demangle uses c++filt when it is on PATH. It needs no binutils
otherwise.
test_symbolize_samples (registered with CTest) builds small PE32+ images
with COFF symbols and with an export table, and covers short, string-table
and static names, the ignored section and data symbols, addresses outside
code, the export fallback, the size and timestamp check, --since with
--out, the address-file format and rejected lines.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A device loss on NVIDIA reports only VK_ERROR_DEVICE_LOST, and
VK_EXT_device_fault often gives no address. Nsight Aftermath can write a
crash dump that names the faulting shader and the kind of fault, but
only when the device was created with VK_NV_device_diagnostics_config.
APS5_NV_DIAGNOSTICS (presence) now creates the device with that
extension (shader debug info, resource tracking, automatic checkpoints,
shader error reporting) and with VK_NV_device_diagnostic_checkpoints
when the device supports them. Unset, or on a device without the
extension, the device is created as before; the latter prints a notice.
ChooseDeviceDiagnostics holds the choice so the test can check it.
AI-assisted: yes
Three allocations ran on every stage materialization:
- DescriptorBindingBuilder::Populate pushed one shaderDataUserWords
entry per layout.userDataRegisters element without reserving, so the
vector regrew through 1, 2, 4, ... Reserve the exact count first.
- GuestSamplersDescriptor appended each sampler's dwords without a
reserve. Samplers are four dwords (the table path forces 4, and
Populate indexes the result as element * 4), matching the
resources.size() * 4 reserve GuestBuffersDescriptor already uses.
- ResolveImageTablesShared made a fresh shared ResolvedImageTables for
every snapshot without image tables. With no tables,
ResolveImageTables either throws (the shader expects tables) or
returns a default-constructed result, so return one shared, immutable
empty instance instead, still calling ResolveImageTables first so the
mismatch keeps throwing. Its only caller reads the result through a
const reference and never compares the pointer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Driver::draw reserved programs.size() + 2 RecompileResults for every
draw, but `results` only receives elements on three paths: the
rect-list tessellation pair (2), barycentric emulation (1 geometry
shader or 2 tessellation shaders) and CPU-path indirect draws (one copy
per compiled program). An ordinary direct draw never touches it, so the
reservation was a sizeable heap allocation thrown away each draw.
Move the reserve to just before the first push on each of those paths.
Every site keeps the old programs.size() + 2 bound, which covers the
largest combination (rect-list or barycentric emulation, then the
indirect copies; rect-list and barycentric emulation exclude each
other). The first reserve on a path therefore sets the same capacity
as before, later ones are no-ops, and the &results[i] pointers kept in
`stages` stay valid.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since the draw finisher (#6946), Driver::draw copies decode->programs
into the draw tail for every draw: one outer vector, one userData
vector per program and two shared_ptr reference bumps per program.
Before it, the copy was made only when the draw writes user words
(mesh index buffer words, indirect record words or relocated user
patches), and every other draw read decode->programs in place.
Keep the copy in DrawTail::writablePrograms and make DrawTail::programs
a pointer to it or to decode->programs, as before. Every write goes
through writablePrograms, and the draws that write are exactly the
mesh, indirect and patched ones, so a const view of decode->programs
can't be written.
The queued commit closures now move writablePrograms. When it is empty
the captured decode keeps decode->programs alive, and decode is only
replaced (by the relocated copy) on draws with user patches, which
take the copy.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
getpeername on a socketpair end returned the unnamed AF_UNIX address
even after the other end was closed. In FreeBSD 11, closing an end runs
uipc_detach, whose unp_disconnect and unp_drop clear SS_ISCONNECTED on
both stream and datagram ends, and kern_getpeername then fails with
ENOTCONN. Each end now keeps a weak reference to its peer's socket,
which expires when the peer's last descriptor, duplicates included, is
closed. shutdown leaves the ends connected, as soshutdown does.
FreeBSD 11.4's lookup() (sys/kern/vfs_lookup.c) fails with ENOTDIR when
a path with a trailing slash ends at something that is not a directory.
On Windows a mount alias dropped the slash, and the host calls do not
report ENOTDIR for "file\", so unlink, rename and open acted on the file
or failed with EINVAL. Guest path resolution now rejects an existing
non-directory named with a trailing slash with ENOTDIR on every host, and
a mount alias keeps the slash on Windows as it does on Linux and macOS.
Function starts were direct call targets plus 16-byte-aligned code after
int3 padding. A function reached only through a pointer and not preceded
by int3 then ended up inside the previous region, and its pop rbp; ret
exit could get the epilogue lea without a matching prologue.
The bounds now come from the executable's .eh_frame FDEs (the existing
EhFrameReader), each function is decoded from its FDE start to its FDE
end, and code outside every FDE is not framed. Conversion stops with an
error when the executable has no FDE. Sites added in one pass also count
as taken for the next function.
The driver refused every ROP3 code but copy, so a draw that asks for a
logic operation stopped the process. The code is decoded into the draw
state, keyed in the pipeline cache and the pipeline journal, and applied
through the Vulkan logic operation of the color blend state.
The field accepts the 28 codes of the source and destination form and of
the brush and destination form, where the brush is the shader color; both
keep their function in bits 0, 1, 6 and 7. A code that combines source and
brush, blending together with a logic operation, a device without the
logicOp feature, and a color target that is not a normalized or integer
format throw.
The pipeline journal format moves to 7.
AI-assisted: yes. Written with AI assistance and reviewed by me.
The debt entry no longer says that a leaf with an rsp operand that
cannot move in place is skipped. A new entry says that such an operand
moves to a stub, when the leaf is still skipped, and that a game patch
over a framed site or a moved displacement is refused. The USAGE row
says the same in short.
Game patches are applied before framing, and the overlap check that
refuses a patch over another writer's bytes did not see the framer's
writes. A patch in a framed leaf's prologue, exit or operand stub site
was copied into the stub and replaced by a jump, and a patch over an
rsp operand's displacement was overwritten when the displacement moved.
The framer's sites and moved displacements now join the writers that
CheckOverlaps compares the patched ranges with, reported as "red-zone
frame site" and "red-zone moved displacement".
A DIM 2D image_load or image_sample of a 2D MSAA T# returns sample 0 on
RDNA2 (RX 6700 XT, measured through HIP; bilinear blends sample-0 texels).
The recompiler now reads such a direct sampled access as a 2D image, and
the driver binds a one-sample copy of the target's sample 0, refreshed by
a fragment pass before each binding. Peace Walker (PPSA18216) stops on
this read at the end of its opening movie.
Co-Authored-By: Claude <[email protected]>
A leaf whose rsp operand could not keep its encoding after the shift
was skipped: an 8-bit displacement that overflows, an operand at (%rsp)
without a displacement, or an indexed (%rsp,%idx,s) operand. Such an
instruction now moves to a stub, re-encoded with the moved displacement
in 8 bits when it fits and in 32 bits otherwise, followed by a jump
back.
The site covers the instruction and the sequential instructions after
it until it is 5 bytes long. It is refused when a branch lands inside
it, when it would have to cover a branch, ret or rip-relative
instruction (these are not relocated), or when it overlaps the
prologue, an exit, another operand site or an existing stub. Legacy,
REX and VEX forms are re-encoded; EVEX operands, whose 8-bit
displacement is scaled, are still refused. A leaf with no room for a
site is skipped as "rsp operand that cannot move and has no room for a
stub". Operands inside a site that could move in place are carried by
the stub instead, so the site's bytes stay as they were.
The report counts the operand stubs and the operands they carry.
The framer's tests check its sites, stub bytes and moved displacements,
but nothing yet showed that framed code survives a write below rsp.
red_zone_framer_execution frames synthetic leaves with
FrameRedZoneFunctions in a writable and executable buffer, writes the
moved displacements and the stubs as Conversion.cpp and the trampoline
builders do, and calls each leaf with the trap flag set. A SIGTRAP
handler fills [rsp-0x80, rsp) of the interrupted context at every
instruction, as Windows exception dispatch may.
Five leaves each loop over slots they spilled, so a clobbered slot
changes the result: #5640's frameless shape, a leaf reaching 0x28 below
rsp, a leaf that also reads its saved rbx at +0x10 (moved in place), a
leaf that takes the address of a slot below rsp (shifted by the whole red
zone), and the inflate_fast-shaped leaf that allocates with sub after six
pushes. Each must return the right value unframed without the clobber
and a wrong one with it, and the right value framed with it.
The test is built and registered on Linux x86-64 only; elsewhere its main
returns 77.