Commit Graph

952 Commits

Author SHA1 Message Date
ljkx f994b4f89f feat(relinker): lower MONITORX and MWAITX for --to-intel
Replace MONITORX with a NOP and MWAITX with PAUSE in place, padded with
NOPs to the original length, instead of failing the relink. MWAITX may
return before its timer or a store to the monitored line, and without an
armed monitor it does not wait, so callers already re-check their
condition; the replacement takes that path every time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:35:23 +02:00
boykopovar 63071deb03
Merge pull request #205 from oneandonlydean/fix/recompiler-fma-mix-op-sel
fix(recompiler): select v_fma_mix source precision by OP_SEL_HI and the half by OP_SEL
2026-10-01 11:32:45 +03:00
boykopovar db5ce0f83c
Merge pull request #204 from oneandonlydean/fix/agc-misaligned-buffers-in-shader-data
fix(agc): patch the memory offsets of misaligned guest buffers in the shader data buffer
2026-10-01 11:31:57 +03:00
boykopovar 33e11c7ea2
Merge pull request #203 from oneandonlydean/fix/libkernel-direct-memory-memfd
fix(libkernel): back direct memory with a memfd on Linux
2026-10-01 11:31:43 +03:00
boykopovar 4a73353eda
Merge pull request #202 from oneandonlydean/fix/agc-misaligned-draw-snapshots
fix(agc): snapshot misaligned draw inputs from the offset the shader reads
2026-10-01 11:31:30 +03:00
boykopovar 261272909c
Merge pull request #200 from ljkx/feat/sha256-to-intel
feat(relinker): lower SHA-256 instructions for --to-intel
2026-10-01 11:31:02 +03:00
boykopovar 4c1a92fe32
Merge pull request #199 from MichonGoddijn231849/fix/command-buffer-reserve
fix(agc): grow a command buffer whose free space is below its reserved words
2026-10-01 11:30:39 +03:00
boykopovar 5ab2fb00ee
Merge pull request #132 from oneandonlydean/feat/agc-cb-eliminate-passes
feat(agc): run CB fast-clear eliminate and DCC decompress passes within the DCC model
2026-10-01 11:29:22 +03:00
boykopovar 9623054a12
Merge pull request #181 from oneandonlydean/fix/agc-sample-2d-over-array
fix(agc): sample a 2D instruction over a 2D array texture through its first layer
2026-10-01 11:28:42 +03:00
boykopovar e4eaba6b2e
Merge pull request #198 from MichonGoddijn231849/fix/reserve-virtual-fixed
fix(libkernel): honour MAP_FIXED in sceKernelReserveVirtualRange and sceKernelMemoryPoolReserve
2026-10-01 11:28:18 +03:00
boykopovar cfb1f590bd
Merge pull request #197 from MichonGoddijn231849/fix/alias-view-granularity
fix(libc): map a shared-memory alias from the allocation granularity below its offset
2026-10-01 11:27:28 +03:00
boykopovar a7fdb497fe
Merge pull request #195 from MichonGoddijn231849/fix/guest-module-tls
fix(relinker): give Windows guest modules with TLS an import directory
2026-10-01 11:15:39 +03:00
Dean Galvin aa9a5c02a7 feat(agc): run CB fast-clear eliminate and DCC decompress passes within the DCC model
Draws with CB_COLOR_CONTROL MODE ELIMINATE_FAST_CLEAR (2) or DCC_DECOMPRESS
(6) were rejected as "only normal color rendering with copy ROP". On
hardware these full-target blits write the fast-clear value into the
fast-cleared pixels (or store the pixels uncompressed); the shader's output
never lands. In this driver's DCC model (surfaces stored uncompressed,
uniform fast-clear keys make every read return the clear value) that is:

- keys that are a clear code: the target's texels become that value and
  its keys read uncompressed. Reads see the same values before and after.
- uncompressed keys, or a target without DCC: nothing changes.
- mixed keys (per-block metadata), a mipmapped DCC target (its keys are not
  decoded) or a register clear of a texel over 64 bits: throw.

The texels go into the target's resident image (its refresh already
cleared it to a code's value; the register code is cleared on the GPU with
FillClear) and reach memory through its write-back, or into guest memory
on the CPU when there is no resident image. A resident image made under
other DCC keys is remade under the target's keys by the storage cache
before the pass reads it. DecodeColorMetadataPass
requires the draw to be only that pass: copy ROP, no depth or stencil
work, no multisampling, a viewport and scissor covering each target.
Indirect passes throw. The CB_COLOR decode moves into DecodeColorBuffer,
which also reads CB_COLOR_CLEAR_WORD0/1.

The register clear code (DCC key 0x20) had no value before: its keys were
read as the stored texels. A target under register keys now reads as its
CB_COLOR_CLEAR_WORD texel: a resident target is cleared to it before a draw
renders into it (then its keys read uncompressed), and a non-resident
target's copy starts from it.

Every decision that materializes a clear reads the keys through
CurrentDccKeys, which first waits for the batches up to the newest recorded
writer of the key range. The title's fast-clear fill of the metadata can
still be pending on the GPU when the next draw is decoded; reading the
stale bytes there made a later draw clear the target over the results of
the earlier ones.

The remaining CB modes (resolve, decompress, FMASK decompress) keep the
rejection, which now names the mode.
2026-10-01 20:59:03 +13:00
Dean Galvin 74abc7acc3 fix(agc): sample a 2D instruction over a 2D array texture through its first layer
A shader sampling with MIMG DIM 2D over a T# of type 2D array was
refused: "guest texture dimension disagrees with the shader's declared
image shape (shape 1, dimension 2)". The access carries no slice
coordinate, so the hardware samples slice 0 of the resource view, the
BASE_ARRAY layer, as it does for the storage access of the previous
commit.

A 2D array Texture now also makes a 2D view of that layer
(Texture::FirstLayerView), whether it holds its own snapshot or views the
surface's storage image, and the sampled binding uses it for such an
access. Every other shape/dimension mismatch still throws.
2026-10-01 20:57:49 +13:00
boykopovar aa74ed81b9
Merge pull request #193 from arreina/pr/elf-reader-overflow
fix(relinker): keep ELF bounds checks from overflowing
2026-10-01 10:53:23 +03:00
boykopovar 89570be51b
Merge pull request #191 from asimplestray/feat/shader-vopc-cmpx
feat(shader): implement VOPC I16/U16 cmpx compares
2026-10-01 10:52:25 +03:00
boykopovar e0b923621b
Merge pull request #178 from oneandonlydean/fix/agc-label-waits-after-submission
fix(agc): satisfy label waits from stores made after their submission
2026-10-01 10:50:47 +03:00
boykopovar aca6566746
Merge pull request #166 from asimplestray/feat/libc-tail
feat(libc): implement RaiseEv, catchReturnFromMain and reallocalign
2026-10-01 10:50:27 +03:00
boykopovar 116351b242
Merge pull request #163 from MichonGoddijn231849/fix/dcc-key-fill-coverage
fix(agc): make DCC key fills clear what reads through the surface's keys
2026-10-01 10:50:04 +03:00
boykopovar 8764959f2c
Merge pull request #137 from oneandonlydean/fix/pixel-inputs-at-parameter-slots
fix(recompiler): declare pixel shader inputs at their parameter slots
2026-10-01 10:49:16 +03:00
boykopovar bc3528be44
Merge pull request #136 from oneandonlydean/feat/pixel-input-vgpr-layout
feat(agc): centroid pixel inputs and NoPerspective by I/J pair
2026-10-01 10:49:01 +03:00
boykopovar 4a78540e38
Merge pull request #134 from oneandonlydean/feat/recompiler-structurize-by-cloning
feat(recompiler): structurize externally entered selection regions by cloning or route variables
2026-10-01 10:48:35 +03:00
boykopovar 00a18dc10f
Merge pull request #131 from oneandonlydean/fix/agc-2d-over-array-storage
fix(agc): bind a 2D storage access to a 2D array surface as its first layer
2026-10-01 10:40:20 +03:00
Dean Galvin edb1358117 fix(agc): bind a 2D storage access to a 2D array surface as its first layer
A compute shader addressing an image with MIMG DIM 2D over a T# of type 2D
array was refused ("guest storage texture dimension disagrees with the
shader's declared image shape"). Such an access carries no slice
coordinate, so the hardware reads and writes slice 0 of the resource view,
which is the BASE_ARRAY layer of the surface.

The storage element is now bound through a 2D view of that layer of the
surface's shared storage image (StorageTexture::FirstLayerView, created on
demand per mip and released with the image). Every other shape/dimension
mismatch still throws, and both mismatch messages now name the shape and
the dimension.

The recorder test device sets formatProperties, which storage images need
to pick their Vulkan format.
2026-10-01 10:23:36 +03:00
boykopovar 74f84dd048
Merge pull request #130 from oneandonlydean/feat/agc-texture-min-lod
feat(agc): apply a texture descriptor's MIN_LOD clamp through the image view
2026-10-01 10:17:19 +03:00
boykopovar 89afcdc070
Merge pull request #124 from oneandonlydean/feat/agc-fuse-shader-halves
fix(agc): fuse geometry shader halves into one self-contained program
2026-10-01 10:12:31 +03:00
Dean Galvin b556b87db3 fix(recompiler): select v_fma_mix source precision by OP_SEL_HI and the half by OP_SEL
v_fma_mix_f32 and v_fma_mixlo/hi_f16 read a source as f16 when its OP_SEL_HI bit is set, with
OP_SEL choosing the high half; otherwise the source is an f32. readMixF32 had the two bits swapped,
so a low-half f16 source (op_sel_hi set, op_sel clear) was read as the whole register's f32 bits.
PPSA21564's material shaders lerp packed f16 colours this way: red and blue (the low halves)
collapsed to near zero while green and alpha survived, giving the green cast on lit surfaces.

agc_driver_mix_precision_tests runs the mix encodings on the GPU against a model of the guest
instructions.
2026-10-01 16:21:48 +13:00
Dean Galvin 81d6f94ceb fix(agc): patch the memory offsets of misaligned guest buffers in the shader data buffer
A guest buffer view off minStorageBufferOffsetAlignment is bound from the
aligned offset below it, and the difference goes into the shader's memory
byte offsets (3dec6ced), but only when those live in the push constants:
a shader whose shader data is a buffer failed with "a guest buffer off the
storage buffer offset alignment in a shader whose shader data is a buffer
is not implemented".

The adjustment now goes into the shader data buffer's byte at
memoryOffsetDword * 4 + element, and a template's data refresh writes the
dispatch's words with the same patches applied.
2026-10-01 16:09:16 +13:00
Dean Galvin d544596283 fix(libkernel): back direct memory with a memfd on Linux
std::tmpfile() places the shared direct memory backing in /tmp, so a
title's direct memory counts against that filesystem's size (a tmpfs is
half of RAM by default): once /tmp fills, touching a backing page raises
SIGBUS. memfd_create gives the same shared memory pages without a
filesystem or its size limit.
2026-10-01 16:06:42 +13:00
Dean Galvin f5355d917a fix(agc): snapshot misaligned draw inputs from the offset the shader reads
A guest buffer view off minStorageBufferOffsetAlignment is bound from the
aligned offset below it, and the shader adds the difference to every
access (3dec6ced). PrepareDrawBindings (9f1c680b) copied such a read-only
in-place view into a snapshot that starts at the view itself, so the
shader read the snapshot that many bytes past the view.

The snapshot now covers the view from the binding's start.
2026-10-01 16:05:24 +13:00
asimplestray 889af1d542 feat(shader): implement VOPC I16/U16 cmpx compares 2026-09-30 23:55:29 +00:00
asimplestray 4816e105ad fix(tests): match default convention of ApplicationHeapRealign 2026-09-30 23:55:00 +00:00
asimplestray b42db4b6f3 feat(libc): implement RaiseEv, catchReturnFromMain and reallocalign 2026-09-30 23:55:00 +00:00
ljkx e66b0b34d7 feat(relinker): lower SHA-256 instructions for --to-intel
Lower SHA256RNDS2, SHA256MSG1 and SHA256MSG2 (register forms) out of line
with legacy SSE2 instructions only, so relinked code no longer needs
SHA-NI. Consecutive short SHA-256 and SSE4a sites share one stub. The
SSE4a stub builder moves to StubBodyBuilder for reuse; IsShaNi now also
recognises SHA256RNDS2 (0F 38 CB). Memory operand forms fail the relink.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:20:43 +02:00
Michon 548162a203 fix(agc): grow a command buffer whose free space is below its reserved words
A command buffer with fewer free dwords than reserved_dw (an empty one whose
title allocates its storage in the callback, as Marvel's Wolverine does)
threw "reserved space exceeds command buffer capacity" before its callback ran.
Its available space is now 0, so the allocation goes to the callback with the
reserved words added, as for any other full buffer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 00:02:31 +02:00
Michon 1d806a4688 fix(libkernel): honour MAP_FIXED in sceKernelReserveVirtualRange and sceKernelMemoryPoolReserve
Both ignored the flags and the requested address and reserved wherever the arena
had room, so a title that reserves a fixed range (Marvel's Wolverine passes
0x400010, MAP_FIXED | MAP_NO_COALESCE) and then uses the address it asked for
touched memory it never reserved. A fixed reservation now lands at the address,
after checking the range is free; other reservations are placed as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 00:00:22 +02:00
Michon e404e7f0aa fix(libc): map a shared-memory alias from the allocation granularity below its offset
MapViewOfFile3 takes an offset that is a multiple of the allocation granularity
(64 KiB), while shared guest pages are 16 KiB. An alias of a range starting at
a 16 KiB page that is not 64 KiB aligned was refused ("the section offset is not
a multiple of the allocation granularity"), so its host import failed. The view
now starts at the granularity boundary below the offset, the alias points past
the lead, and unmapping rounds back to the view's base.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 23:57:56 +02:00
Michon d29a1296cd fix(relinker): give Windows guest modules with TLS an import directory
The Windows loader sets up TLS for a dynamically loaded DLL only when it snaps
the DLL's imports. Guest modules had no import directory, so their TLS
directory was never processed: every guest module kept TLS index 0 and read
and wrote the executable's TLS block. A guest libc's thread-locals (gmtime's
buffer at +0x39c in Marvel's Wolverine) then ran past the executable's
0x150-byte block into the process heap, and corrupted a loader node that
crashed the next thread start in ntdll. Guest modules with TLS now import from
KERNEL32, as the executable does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 22:47:50 +02:00
Dean Galvin 258e4d526d fix(agc): fuse geometry shader halves into one self-contained program
sceAgcUnknownFuseShaderHalves (fd4700b9) fuses both kinds of halves at
the register level: the back half's registers with the front half's
program address in PGM_LO_ES (PGM_LO_LS for hull halves). For geometry
halves the driver then compiles that front half as the draw's whole
program (type 4, Stage::Mesh) and skips the back (Role::GeometryBack);
nothing links the two. The front ends in s_setpc_b64 s[6:7] and has no
s_endpgm, so the decode runs on into the s_code_end padding and the
program trailer. In PPSA21564 the geometry shader draws were skipped
with "unsupported SOPP opcode 31" (s_code_end) and "unsupported scalar
source operand code 115 at program counter 64".

Geometry halves are now fused into one Gs program: the front half's code
up to its final s_setpc_b64 s[6:7] (its length comes from the program
trailer), s_nop padding to the next 256 bytes, then the whole back half,
so the front falls through into the back. Its SH registers are the back
half's, with PGM_LO/HI_GS pointing at the copied back code, the ES
program address at the fused code, RSRC1 taking the larger VGPR and SGPR
counts of the two halves, RSRC2 taking the front half's user-SGPR count
(the SPI loads the front layout; the back reloads its own from memory
through s[0:1]), and the front's other SH registers except its checksum
appended. Context registers are merged the same way. Inputs come from
the front, outputs, specials and user data from the back; the scratch
size is the larger of the two.

Everything the fused Shader points to, including a copy of the header
itself, is written into the caller's memory after the code, and the
header is registered with the driver as a self-contained shader. Titles
pass memory without honouring the reported alignment, so the program is
placed at the first 256-byte boundary inside it, and the size query
returns the total plus 255 bytes of padding (alignment reported as log2
8). Halves with embedded constant buffers, a front half without a
trailer or final s_setpc_b64, a front half that needs RSRC2 state the
back half does not enable, and geometry halves without fused memory
throw.

Hull halves keep the register-level fusion, which the tessellation
path's separate local and hull programs fit. The half-type and stage
checks stay shared.

The driver now loads s[0:1] of a Gs program from its user-data address
registers whenever they are set, not only for GsFront programs, since
the back half of a fused program reloads its user data from there; a
program without those registers keeps zero there (from SamG-Coder's
8345fbc1).
2026-10-01 08:45:51 +13:00
Michon 4ce40333ea fix(agc): make DCC key fills clear what reads through the surface's keys
A buffer fill that starts at a surface's DCC keys and covers at least its key range (titles fill
the padding too) is a key fill. A uniform key fill leaves the image's texels and pending results
in place and records the fill's code: a sampled read through the surface's keys binds the clear
value, a color target through the keys starts from it at its next refresh, and reads that bypass
the keys still see the texels. Key scans return the fill's code until its write lands, and a
write-back of results drawn before the fill keeps the keys holding that code. The immediate clear
of the covered images is opt-in (APS5_KEYS_FILL_CLEAR=1): it fixes titles whose sampled reads
reach the pending image before a refresh, at the cost of reads that bypass the keys (Astro Bot's
title logo, reported by @oneandonlydean).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:40:15 +02:00
Dean Galvin a36ef52b33 fix(agc): decide a 64-bit label wait by the dwords its mask compares
A WAIT_REG_MEM_64 whose mask leaves the high dword out compares the low
dword alone, but the wait paths looked for all eight bytes: the
recorder's label table and the stores committed since the submission
want an entry for each dword, while a producer's RELEASE_MEM or
WRITE_DATA stores 32 bits, so neither ever satisfied such a wait, and the
held-at-submission check refused it when an earlier packet stored the
unrelated high dword.

Pm4::WaitAwaitedBytes gives the bytes that decide the compare (4 for
WAIT_REG_MEM and for a WAIT_REG_MEM_64 whose high mask dword is zero,
else 8), and the wait paths use it. The timeout report now prints a
64-bit wait's reference and mask (it printed the reference's two halves
as "ref" and "mask").
2026-10-01 08:39:53 +13:00
Dean Galvin 7df4ce4007 fix(agc): satisfy label waits from stores made after their submission
A title may reclaim a label (reset it to 0) as soon as the submission
that released it is done, trusting every queue waiting on it to have
passed its WAIT_REG_MEM within microseconds, as the CP polls from the
moment it reaches the wait. A worker that reaches a later wait of the
same submission long after (it records and compiles in between) found
the reset label and timed out.

A memory wait now also passes when
- a label store the driver committed (recorded on the GPU, as a
  completion action, or on the CPU) after the wait's submission gave the
  awaited bytes a value the wait accepts (the last four stores per dword,
  with their eventSerial stamps). Such a store belongs to this use of the
  label or a later one, since the title reset it before submitting.
- the awaited bytes held such a value when the game submitted, and no
  earlier packet of the queue (in the submission, or one still queued or
  running) stores to them.

A store made before the submission never satisfies a wait.
2026-10-01 08:39:53 +13:00
Dean Galvin 6a33a3d371 fix(recompiler): declare pixel shader inputs at their parameter slots
SPI_PS_INPUT_CNTL_n.OFFSET (bits 0-5) names the parameter slot pixel
input n reads, so several inputs may read one slot and an input may read
a slot the vertex stage never exports. With OFFSET bit 5 set and
FLAT_SHADE clear the input is the DEFAULT_VAL constant instead (bits 8-9:
0000, 0001, 1110, 1111). With OFFSET bit 5 and FLAT_SHADE both set the
slot in the low bits is passed through with its three vertices unchanged:
v_interp_mov P0, P10 and P20 read vertices 0, 1 and 2 rather than P0,
P1-P0 and P2-P0. The PS5 compiler writes such inputs as 0x422 and 0x423.

The old mapping masked OFFSET to five bits, so a defaulted input landed
on a real slot, and gave the second of two inputs reading one slot either
the same Location (two variables, "duplicate shader interface location")
or, when one was flat, a free Location the vertex stage never wrote
("graphics interfaces disagree at location N").

Now one input variable is declared per slot actually read, at Location =
slot, and inputs reading that slot share it. A slot read both flat and
interpolated (or through the perspective and linear pairs) is read per
vertex: flat readers take the provoking vertex, the others interpolate
with the barycentrics of their own pair. Pass-through inputs are read per
vertex at their slot without subtracting vertex 0, and reading one with
v_interp_p1/p2 is refused. Defaulted inputs read their DEFAULT_VAL
constant and declare nothing; inputs the program never reads are no
longer declared.
2026-10-01 08:38:47 +13:00
Dean Galvin cb66ec2004 feat(agc): centroid pixel inputs and NoPerspective by I/J pair
SPI_PS_INPUT_ENA/ADDR with PERSP_CENTROID (bit 2) or LINEAR_CENTROID
(bit 6) was rejected; PPSA21564 binds 0x326 (perspective center and
centroid, linear center, POS_X/Y).

The PS input VGPR layout now follows the hardware rule: SPI_PS_INPUT_ADDR
places every input it names in SPI order (two VGPRs per I/J pair, three
for the pull model, one otherwise), loaded or not, and ENA & ADDR selects
the loaded ones. ShaderPixelStageInfo carries inputAddr (replacing
perspectiveCenterVgpr) plus perspectiveCentroid and linearCentroid; the
recompiler places each loaded input with PixelInputVgpr. This also fixes
the system inputs, which were always written from v0 (psSystemInputBase
was never set), so POS_X overwrote the center I in any draw with
barycentrics enabled.

Draws are rasterized single-sampled, where the centroid is the pixel
center: with fragment barycentrics the centroid pairs carry the center
barycentrics (BaryCoordKHR for perspective; the linear center and
centroid pairs now get BaryCoordNoPerspKHR).

NoPerspective was decided for every parameter by one flag (LINEAR_CENTER
loaded). v_interp_p2 now records whether it reads the J of a linear or a
perspective pair, and a parameter is NoPerspective when it is read
through a linear pair; parameters read through neither keep the old
rule. A parameter read through both kinds of pair throws. Only pixel
stages consult this.

The request serializer moves to version 5 (older requests read back with
their loaded inputs as the layout); the cache key and the driver's draw
key compare and hash the new fields. The unsupported-input error names
ENA and ADDR.
2026-10-01 08:37:45 +13:00
Dean Galvin b57ab5a0e9 feat(recompiler): structurize externally entered selection regions by cloning or route variables
A selection whose region (the blocks reachable from its arms before its
merge) is also entered from outside the header cannot be expressed as a
SPIR-V selection construct, and the structurizer gave up with "semantic
block cloning is disabled". Pixel shaders hit this through a shared
discard tail (s_mov_b64 exec, 0 / exp null / s_endpgm) that several
selections branch to, compute shaders through short-circuit conditions
that jump into a shared body, and ray-traced compute passes through an
s_cbranch_execz that skips past a join into the middle of a selection.

Such a region is made single-entry in one of two ways, chosen per region
by its estimated translated size:

- Cloning (node splitting): the blocks the outside edges reach inside the
  region are copied and the outside edges redirected to the copies. When
  the header's merge is a shared linear return tail, giving the header a
  private copy of that tail is tried first. Each block now carries an
  estimate of the SPIR-V words it translates to. The per-instruction
  weights come from a fit of the recompiler's per-block output (before
  spirv-opt, phis excluded) over 8,300 blocks of captured shaders: VALU
  60, buffer and flat 120, SMEM 180, image 220, LDS 400. SALU (8) and
  exports (20) fit near zero and keep a small weight. Lane ops (120) and
  image_bvh_intersect_ray (2000) were too rare there to fit and are
  weighted for the wave and traversal expansions they need. A region is cloned only when it is at most 1024 estimated
  words and at most an eighth of the program (at least 256), and while
  all clones stay within a quarter of the program.
- Routing through a junction, for larger regions: every edge into the
  entered part (from outside, and from the header's own arms) and every
  edge from the arms to the merge is sent through a new empty junction
  block. Each rerouted edge first passes empty blocks that assign
  Boolean route variables (the IR's goto variables); the junction tests
  them in order to reach the edge's original target. The entered part
  then follows the junction and is emitted once. Branch conditions are
  still evaluated only at the end of their original blocks: a route
  variable records the edge a block already took, so a later SCC, VCC or
  EXEC write cannot change the route, and in the two-lane wave64 layout
  the source branch is combined across both halves as before while the
  route variable holds the same constant in both. Routing is refused
  when a rerouted edge is a back edge or leaves an indirect branch, when
  the region wraps around to a dominating block, or when it would change
  the natural loops or make the graph irreducible; cloning bounded by the
  program's estimated size is the fallback, and otherwise it throws with
  the header, the entered block and both reasons.

Selections whose arms only meet again after private return tails had no
post-dominator merge. When the existing rules find none, the merge is
now looked up with edges into private return tails ignored, or taken as
the other arm when one arm only returns; the candidate must still exit
the construct only through the merge. applyBlockOrder also remaps
indirect branch targets.

agc_shader_control_flow_tests (under ctest) covers hand-assembled
gfx1030 programs: the cloned shapes above, and routed ones (an
s_cbranch_execz into an expensive region with a loop, a region inside a
loop, nested entered regions, a shared body reached after SCC is
overwritten, early returns beside an entered region, an arm that only
returns). It checks that every selection construct exits only through
its merge, follows route variables to check that the structured graph
runs the same instruction ranges under the same branch conditions as
the original, bounds the cloned instructions, recompiles each program
for 64- and 32-lane subgroups, and keeps the routed execz program within
10% of the SPIR-V of the same program without the entering branch.
Given .req files it checks their graphs instead. agc_shader_replay --cfg
prints a request's graph, now with each block's estimate, before and
after structurization.
2026-10-01 08:36:11 +13:00
Dean Galvin 2be78b6965 feat(agc): apply a texture descriptor's MIN_LOD clamp through the image view
T# word1 MIN_LOD (unsigned 4.8, in levels of the whole surface) was
rejected. Sampled views now chain VkImageViewMinLodCreateInfoEXT
(VK_EXT_image_view_min_lod, enabled when the device offers the minLod
feature). Its minLod counts levels of the whole image exactly as MIN_LOD
counts levels of the whole surface, because the image holds every mip and
the view starts at BASE_LEVEL.

- A clamp at or below BASE_LEVEL cannot change which level a sample reads
  and is left off; one past the view's last level is bounded by it (the
  view selects that level anyway, and Vulkan requires the bound).
- A clamp that binds on a device without the feature throws.
- The texture cache already keys by the eight descriptor words, so views
  with different clamps are not shared.
- Storage views address one level: a clamp at or below that level is
  accepted, one above it still throws.
2026-10-01 08:30:05 +13:00
boykopovar a67b644ff6
Merge pull request #192 from MichonGoddijn231849/perf/validation-failure-memo
perf(agc): remember shader validation failures and reject repeats without throwing
2026-09-30 22:12:58 +03:00
Alberto Ruiz 46e63f2ec1 fix(relinker): keep ELF bounds checks from overflowing
ElfReader checked ranges as offset + size > buffer size. An offset near
UINT64_MAX wrapped the sum, passed the check and read outside the buffer,
which AddressSanitizer reports as a heap-buffer-overflow. The relinker
then failed later with unrelated messages built from the stray bytes.
The checks now compare without adding to the offset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
2026-09-30 21:06:43 +02:00
boykopovar c71be475fc
Merge pull request #187 from MichonGoddijn231849/feat/demons-souls-exports
feat: export the AMPR command buffer waits and writes, sceAgcFuseShaderHalves, sceAgcSetSubmitMode, sceNpSessionSignalingCreateContext and sceNetEpollAbort
2026-09-30 22:03:54 +03:00
boykopovar f59c867a5b
Merge pull request #183 from arreina/fix/elf-header-size
fix(relinker): require a full ELF64 header before reading it
2026-09-30 22:03:27 +03:00