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.
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.
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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
feat: export the AMPR command buffer waits and writes, sceAgcFuseShaderHalves, sceAgcSetSubmitMode, sceNpSessionSignalingCreateContext and sceNetEpollAbort
A .param file of a save directory or of save data memory that exists but cannot
be read, or whose size is not sizeof(SaveDataParam), was silently ignored or
truncated. It now throws, naming the file and, for a size mismatch, both sizes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ReadHeader reads fields up to offset 0x40 but only rejected files
shorter than 20 bytes, so a truncated ELF failed with
"FileByteOffset out of bounds" instead of "File too small for ELF header".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
sceContentExportInit2 accepted any non-null parameter block. It now throws
when the malloc or free function is missing or a reserved field is not
zero. A test covers these refusals and the init and term order.