Files
Adrià 415fd7136b Merge pull request #8991 from Tamalero/fix/shader-image-table-split-tsharp-halves
fix(recompiler): match image-table T# halves offset before and after readfirstlane
2026-10-21 21:53:27 +02:00

149 KiB
Raw Permalink Blame History

shader technical debt

Build

  • winpthreads initializes a statically initialized rwlock on its first lock and fails a concurrent first lock with EINVAL, which std::shared_mutex and std::shared_timed_mutex ignore, so that lock does not exclude. The host std::shared_mutex objects in Recompiler.cpp are locked once when created. A new host std::shared_mutex used on Windows needs the same.

  • The bundled SPIRV-Tools build replaces the redundancy-elimination source with a build-directory copy. A depth-first traversal keeps one value map and removes each subtree's added values on return, avoiding growing map copies and preventing sibling values from leaking into one another. This extends the processed-map release fix in SPIRV-Tools #6962. Remove the patch when the pinned dependency has equivalent copy-free traversal; #6962 alone still copies maps for sibling branches. System-dependency builds need an equivalent fix in their installed package. Optimization passes and validation remain unchanged; wider shader and game performance measurement is needed.

Unknown function info

  • VCC-backed MIMG samplers: the s104/s105/vcc_lo/vcc_hi tuple follows LLVM's SGPR register classes and gfx10 sampler operand, using the existing VCC word reads. Recompiled wave32/wave64 reads are checked by bilinear and white-border cases; raw RDNA execution of this tuple on gfx1013 or gfx1036 is unmeasured.

  • MSAA-address image loads on 2D textures - 32-bit image_load with DIM 2D_MSAA reads a non-multisampled 2D T# as the same DIM 2D load, ignoring the sample index. Measured on RX 6650 XT (gfx1032), wave32 and wave64, for R32_UINT, R32_FLOAT, RGBA8_UNORM and RGBA8_SRGB, sample indices 0/1/2/7/31/0xffffffff and in-range/out-of-range coordinates; IEEE 0, DX10 clamp 1, denorm32 0, denorm16 3, round32/16 0, fp16 overflow 0. Pinned in ImageMsaaAddressOnPlane.cpp. Not measured on gfx1013. Stores, atomics, mip-bearing or A16 addresses, packed accesses, converted formats, depth-bit views, array-MSAA addresses and image-table accesses are not enabled by this compatibility mode.

  • MUBUF/MTBUF descriptors ending in VCC: the four dword descriptor at s104 is s104, s105, vcc_lo and vcc_hi, as LLVM's VCCTuple/SReg_128 and buffer resource operands describe. Existing translation maps scalar codes106/107 to the raw VCC words. Recompiled loads/stores, both words, bounds, formats and wave32/wave64 execute on NVIDIA and AMD Vulkan; raw gfx10/gfx1013 hardware is not measured. Higher descriptor bases remain refused; the normal SGPR bound is unchanged.

  • Packed pixel ancillary word (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin in a pixel shader the guest iterates per sample (PS_ITER_SAMPLE on a multisampled draw), an equality inferred, not measured. In any other pixel shader it is 0, the iterated sample number of a pixel the hardware does not iterate (also inferred), so reading it does not make the host shade per sample. The render target index is the Layer builtin when the draw exports it (a layered draw, PA_CL_VS_OUT_CNTL bit 18; the pipeline specialization constant LayerExported) and 0 otherwise, the first layer Vulkan renders when no pre-rasterization stage writes Layer. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800); with the constant at 0 the load is folded away. Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on (ac_nir_lower_intrinsics_to_args.c). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant (RasterPrimitiveOf) in the shaders that read it, plus the sample field, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding (LowerGlCompatibility.cpp) and without a GFX-level check (getPrimType). Nobody has checked it on gfx1013. In the whole word only bits 0-1 and 8-11 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId only in iterated shaders. The whole word has the same 0 at bits 16-28. A change that makes draws export a layer must restore the Layer read in both places

  • Front-face input with FRONT_FACE_ALL_BITS (shader recompiler) - with SPI_BARYC_CNTL.FRONT_FACE_ALL_BITS set, a back face reads 0 and a front face 0xffffffff. Mesa 22.0 RADV sets the bit (radv_pipeline.c) and ACO tests the input with v_cmp_lg_u32 0 (aco_instruction_selection.cpp), so only back = 0 and front ≠ 0 are sourced; PPSA05684's two-sided cloth shaders test it with v_cmp_lt_u32 0. The exact front value is not measured. Without the bit the input is ±1.0, as current Mesa reads it (fgt 0, fsign; ac_nir_lower_intrinsics_to_args.c)

  • Color comparison descriptor forms: for formats AnyPS5 can sample as floating-point from GFX10 codes 1–77, image_sample_c* returns 0 for every comparison function except codes 7 (16_UNORM) and 22 (32_FLOAT), which use the native comparison path. The split was measured with point sampling on RDNA2 (gfx1031 and gfx1036) and with point and bilinear sampling on RDNA3 (gfx1100); the PS5's gfx1013 and 2D-array/cube comparisons across these formats remain unverified. Codes 128–130 (8-bit sRGB) also return 0, measured on gfx1035 over references -0.5..1.5 and texel codes 0..255. Scaled, integer and formats requiring converted sampling remain unsupported, although the tested hardware formats also return 0 for them. Anisotropic comparison of the remaining formats has not been measured.

  • s_setkill (core/shader/recompiler) - decoded as s_endpgm for any immediate: measured on RDNA2 hardware (gfx1035), a store before it is visible and nothing after it runs (#771); whether the KILL status bit it sets is observable differently from s_endpgm is unverified

  • s_sethalt (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: s_sethalt 0 followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws

  • s_subvector_loop_begin and s_subvector_loop_end (core/shader/recompiler) - follow AMD's RDNA 2 instruction set reference and a gfx1036 (wave64) measurement, pinned in SubvectorLoop.cpp; S0 in the end instruction's elif(S0 == 0) is read as its SGPR operand. A wave32 shader and a destination other than an SGPR throw because neither is measured. The begin instruction tests for an empty mask as (exec_lo | exec_hi) == 0: AMD's Windows driver (32.0.21045.5002 and 32.0.21042.62) compiled exec_lo == 0 && exec_hi == 0 as a test of the whole 64-bit mask when guest loads use selects, as they do with robustBufferAccess2 (#7376, #7623). Measured on PC RDNA2 (Radeon 610M), not on a PS5. The specialization pass keeps a constant-condition branch whose untaken target is a loop header, since folding it removes the loop's back edge, which SPIR-V requires exactly once per loop header (SPIRV-Tools rejects the module and the NVIDIA driver 615.78.08 crashes compiling it); a loop whose back-edge block becomes unreachable in another way is not handled

  • Vertex entry merged wave info (recompiler) - s3 of every vertex-path shader is set to waveSize | waveSize << 8 (vertex and primitive count of a full wave, wave index 0), or to 32 | 32 << 8 for a wave64 program on a 32-lane host subgroup, which runs it at one lane per invocation and holds no lanes 32-63. On a device whose subgroupSupportedStages lacks the vertex stage (lavapipe, MoltenVK), vertex and local programs run as one-lane waves: s3 is 1 | 1 << 8, the lane id is 0, a ballot is the invocation's own bit, a shuffle or broadcast returns the invocation's value, an elect is true and a subgroup reduction is the value itself, so no GroupNonUniform capability is declared. Layout taken from the prologue Mesa's ACO emits for NGG vertex shaders (s_bfe_u64 exec from s3), not measured on hardware. The driver gives every graphics program user data from s8, so the condition does not tell NGG from legacy vertex shaders: they just never read s3. A draw whose last wave is partial still reports a full wave, and so does a host wave with idle invocations (an RTX 5070 ran every vertex warp of a non-indexed triangle list with 30 of its 32 invocations).

  • s_barrier in vertex-path programs (recompiler) - emits no instruction when the program has no LDS or GDS access and no memory write or atomic, so no wave of the subgroup can observe another, or when the barrier runs before every memory access of the program (it is on the entry's straight-line path with no load, store, atomic, image or LDS access ahead of it), so it orders nothing; the GS_ALLOC_REQ such a barrier orders on NGG passthrough programs is already dropped on the vertex path. The Vertex execution model has no workgroup barrier. Other programs with LDS, GDS or memory writes throw. Not measured on hardware

  • Vertex clip and cull distances (libSceAgcDriver) - PA_CL_VS_OUT_CNTL enable bit s of CLIP_DIST_ENA/CULL_DIST_ENA is taken to select component s of the pos1/pos2 exports, with the Vulkan array index counted by the enabled bits below it, and VS_OUT_CCDIST0_VEC_ENA as bit 22; this follows the recompiler's existing position-export mapping, not hardware or documentation. The guest clip distances share one gl_ClipDistance array with the all-zero clip position distance, which takes the index after them. A vertex program whose own code has no pos1..pos3 export is compiled and prepared without these bits (PositionExportLayout), so registration and draws agree on its artifact; an export in a subroutine it calls is not seen by that scan

  • 32_AR color exports (shader recompiler) - with SPI_SHADER_COL_FORMAT 3 the red value is export channel 0 and the alpha is export channel 1, the GFX10 layout of Mesa's emit_ps_color_export, ACO and radeonsi in Mesa 22.0 (aco_instruction_selection.cpp, si_shader_llvm_ps.c) and AMD's LLPC; before GFX10 the alpha was channel 3. Channels 2 and 3, which none of these compilers enables, are ignored; green and blue are 0 and the alpha of an export without channel 1 is 1.0, the defaults of the other formats, which no source gives. Not measured on hardware, neither on gfx1013 nor on a PC RDNA 2 GPU; pinned in Pixel32ArExport.cpp

Functional

  • Pixel interpolators past SPI_PS_IN_CONTROL.NUM_INTERP (shader recompiler) - when the IR reads an attribute at or past the guest interpolator count, the missing SPI_PS_INPUT_CNTL slots are filled as DEFAULT (OFFSET 0x20, constant 0/1) before info collection, and SPIR-V emission of GetInterpolationParameter for a missing binding returns the same defaults instead of throwing, matching Mesa's treatment of a PS input with no VS output. Component and mode bounds still throw. A retail title prepares a pixel shader with NUM_INTERP 0 while it still issues v_interp on attribute 0 (undefined attribute after the collector fill alone). Console behaviour for that pairing is not measured.

  • Windows cache housekeeping: eviction of old versions and temporary files is skipped for \\?\ and \\.\ cache roots because the supported WinLibs directory iterator can enumerate the process working directory for long prefixed paths. The active version's last-used stamp update still runs; it fails for long \\.\ aliases with the supported toolchain. Ordinary Windows directory enumeration remains subject to MAX_PATH.

  • f64 compares flush denormal inputs to signed zero when bit 0 of the FLOAT_MODE f16/f64 denormal field is clear (modes 0 and 2); modes 1 and 3 and an unknown mode keep them. This follows the RDNA2 ISA section 6.4 and LLVM's sign-preserving AMDGPU denormal modes. The gfx1036 audit in #981 reports mode-sensitive VopcCompareF64 results. Class compares still inspect the original bits. The full four-mode matrix has not been newly measured on AMD hardware or the PS5's gfx1013.

  • Unlinked calls - the s_swappc_b64 a table call's compare chain falls back to, when its target matches no linked copy, records the UnlinkedCall fault with the target and the program counter and stops the invocation, or continues after the call in programs whose faults do not stop invocations (see bdaStopsInvocations). The driver lists those calls in the request (linkedCallFallbacks), and it reports the fault like a BDA fault, naming the target: a dispatch that checks its faults when it completes throws, one whose check runs in a deferred write-back logs it and continues. The hardware would jump to the target, which is not modelled. Any other s_swappc_b64 whose target is not resolved still throws when the shader is compiled.

  • Cached source IR releases lazily allocated SSA construction tables after resource preparation and reclaims values unreachable from instructions, metadata and resource plans when retaining a pending source program. Immediate shader preparation skips reclamation because compilation consumes the program. Reclamation must account for every new IR pointer owner; a remaining external use throws. Live IDs are retained. Source and variant entries remain cached without an eviction bound. A pending source program is released after its first variant is obtained; memory beyond the measured title checkpoint and Linux performance remain unmeasured.

  • Vertex user-data tail jumps link a linear raw vertex prolog's terminal s_setpc_b64 through an unchanged user-data pair to its continuation. The instruction semantics follow AMD RDNA2 ISA section 12.3; the prolog convention follows Mesa aco_select_vs_prolog.cpp at 0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8. Console hardware has not been measured. Branching prologs, computed low pointers and continuations reading s_getpc_b64 remain unsupported

  • Raw fragment user-data tails also link a terminal s_setpc_b64 when the low half is unchanged user data and the last high-half writer is an unconditional immediate s_mov_b32. Other computed high halves remain unsupported. This follows AMD RDNA2 ISA section 12.3 and Mesa aco_select_nir.cpp (create_fs_jump_to_epilog), aco_isel_helpers.cpp (convert_pointer_to_64_bit) and aco_lower_to_hw_instr.cpp (p_jump_to_epilog) at 0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8; console hardware has not been measured

  • The resource tracker plans a descriptor source used only behind a conditional branch as inactive when the branch condition reads only clean (specialization) memory and so is evaluable at capture time; an inactive source is bound as zero words and not validated. Other conditions (EXEC, VCC, SCC set by vector or non-clean data, loop headers) leave every source active, as before, and a branch on a value that changes between dispatches is a specialization key like any other clean slot. PPSA15246 has a copy kernel that selects a buffer or an image path from an SRT word and reads the same address as a V# or a T#; before, the untaken path's descriptor was validated and threw. Whether the console would also accept an invalid descriptor in a path it does not take is assumed, not measured

  • Image table entries of 8_SRGB and 8_8_SRGB textures are decoded by image_load like a direct T#; a sample, gather or LOD query of one faults (sRGB decode) on a device that cannot sample R8_SRGB or R8G8_SRGB.

  • Image tables (T#s and S#s that s_buffer_load reads at key * stride + addend from a V#) are snapshotted when the dispatch or draw is recorded; a later CPU write to a record is not seen.

  • An image table entry that cannot be bound exactly reads zeros, and an active lane that selects it raises a BDA fault (reason 9) naming the record's address, words and reason; the dispatch's write-back reports the first one.

  • An image table snapshot holds at most 65536 keys per table, 65536 distinct images and 256 S# entries per shader; a key past a longer table's snapshot, or an entry over the budget, faults when selected. A selected column (a T# the SRT walker evaluates) holds its one record wherever it lies.

  • Image tables read by s_load through a 64-bit pointer hold the keys the key's known bits allow, at most 65536; a key past them, or an entry at a wrapping, unaligned or unmapped address, faults when selected.

  • An image table whose key is neither a V#-relative scalar read nor a value the SRT walker evaluates (for example a shifted or masked runtime-offset V# read, a per-lane value made uniform by v_readfirstlane, or a loop-carried phi) binds every record of the snapshot domain instead of the selected ones. So does a table read at (key << n) & mask with such a key: a key domain would select records by the unmasked key, so that key is not narrowed. A key the walker evaluates selects its one record instead, unless the shader may write the memory the key is read from at run time (the same buffer, or any memory when the read or a write goes through a pointer or a GPU-only descriptor); then the column binds the whole domain too. A write to the key's memory through another V# over the same bytes is not detected. A shader whose selected records would pass 64 images or 64 sampled image/sampler pairs binds, for the heaps with the most such reads, the union of their keys in one table instead.

  • Image table pointer indexing propagates 32-bit value bounds through ReadFirstLane and SelectU32 so waterfall key masks keep their bit domain. The SRT walker validates runtime select conditions whose active lane mask implies the condition through LogicalAnd conjunctions, assuming unselected inactive lanes need not be uniform across the full wave. The same assumption lets the two s_loads of one T# record match when one offset is added before v_readfirstlane and the other after (ReadFirstLane(Select(mask, (key << 5) + 16, old), mask) against ReadFirstLane(key, mask) << 5): with no active lane the hardware reads lane 0 of the inactive arm, and the table still binds the whole record.

  • An image table T# of an MSAA or FMASK image, or of a color format under a depth compare, faults when selected: tables have no multisampled descriptors and no emulated color compare.

  • Direct FMASK image loads read uncompressed Fmask32_S8_F8 through a single-mip 2D or 2D-array R32_UINT snapshot with Z_64KB_X addressing. Destination selectors may name X, zero or one. Other FMASK formats, CMASK compression, packed accesses and other layouts throw. The layout follows PAL's FMASK-to-surface conversion and 32-bit view selectors. Native RDNA/PS5 image-load results and title rendering are unmeasured; compressed color sampling remains unsupported.

  • image_store, image atomics, PCK2/PCK4 and MIMG BY2/BY4 through an image table T# fault (unsupported operation) when an active lane executes them; the store is dropped and the load reads zeros. A T# whose words the SRT walker evaluates is bound directly instead, for every operation of the shader on it (DirectImageHandles), so only T#s keyed per lane or by a value the walker cannot evaluate reach a table with these operations; such a direct T# is read when the work is recorded, so a key its own dispatch writes is read before that write.

  • An image table S# with a min or max reduction or FORCE_UNNORMALIZED faults when selected, as does an integer, converted or depth-bits table T# paired with a direct min or max S# that filters between texels.

  • Uint and Sint image table entries and direct T#s under a table S# sample through a point-filtered copy of the S#: Vulkan leaves linear filtering of integer formats undefined; the console is assumed to point-sample.

  • Comparison sampling of a color texture through an image table S# throws at the first dispatch or draw that binds it.

  • An image table key with no key domain whose key * stride wraps past 2^32 to an offset between records faults only when the build already expects a fault; otherwise the fault buffer is not read and it reads zeros.

  • MaskedSelectEliminator can discard inactive source lanes through ReadFirstLane only when its read mask implies the write mask and its result is unobserved outside the read mask. A loop-local read mask requires result uses in the same block without phi transport. The empty-EXEC lane-zero fallback follows AMD's Vega ISA, section 12.8, V_READFIRSTLANE_B32, page 127 and the existing SPIR-V emitter. The optimization is covered by synthetic IR tests but has not been compared with console hardware; a proof that needs a read mask of a kind that is already being solved is refused.

  • DenormalFlushEliminator drops the result flush of a v_mul_f32 or v_mul_legacy_f32 product behind its nearest-even tininess check (see f32 denormals) when both factors are denormal-free: a product the check keeps is then zero, infinite, NaN or at least 2^-126 in magnitude, provided the driver rounds it as it rounds the check's multiply.

  • Shader modules built at registration (shader recompiler): on devices with graphics pipeline libraries a vertex or pixel module reads buffer V# words, image modes, swizzles and mip ranges, depth-compare state, export mappings and vertex component selectors at run time instead of specializing on them per draw, so its libraries can be built when the shader is registered. A module that would exceed SpirvTarget::registrationWordLimit (200000 SPIR-V words) is emitted specialized as before: on RADV (Radeon 660M, Mesa 26.2.4) a sample of 60 pixel shaders of a retail title compiled 1.9 to 2.2 times slower in registration form below 141k words and 3.8 to 5.7 times slower above 280k words (none in between), so the limit sits in that gap; it was not measured on other drivers. Texel-offset folding, the format word of a buffer accessed with the descriptor's format (buffer_load_format_*; as runtime values every format's decoder is compiled, which made CI's lavapipe JIT ten times slower) and the vertex attribute numeric class stay specialized, so shaders with the first two, and pixel shaders whose image needs a multisampled descriptor, get no registration result; these, and every draw whose fixed-function state differs from the prebuild's, still build their libraries at the draw. Compute and the other stages stay specialized: in registration form the agc_driver_dword_pattern_fill compute shader hung CI's lavapipe (Mesa 25.2.8) when built with ANYPS5_ENABLE_SPIRV_TOOLS=ON, not on RADV, and the cause was not found.

  • SPIRV-Tools optimization skips CCP and Simplification for modules with storage-only 8- or 16-bit types because the pinned folder produces illegal narrow constants. This also disables unrelated constant folding; the restricted full pipeline mirrors the pinned performance recipe.

  • SRT signed comparison evaluates SLessThan32 as signed 32-bit less-than, matching the existing SPIR-V backend and SPIR-V OpSLessThan. Signed boundary descriptor expressions are tested on the host; native RDNA2/PS5 execution and title behavior remain unverified.

  • MIMG tfe and lwe never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for lwe on sample/gather, when the LOD is below the T# MIN_LOD_WARN; texture memory is always resident here, the LOD warning (texture-streaming feedback, like the T# MIN_LOD_WARN itself) is never reported, and lwe on image_get_lod throws.

  • Packed horizontal gathers (image_gather4h_pck, MIMG opcode 98): the four window texels are read raw and concatenated into a little-endian bitstream, with DMASK bit i writing stream word i to the next destination register; measured on RDNA2 (gfx1031) at every edge: the anchor is floor(x-0.5) with elements at -1, 0, +1 and +2 clamped horizontally into the width, the row is floor(y), negative rows clamp to row 0, rows at or past the height return zeros, sampler addressing modes do not affect the window, and formats wider than 32 bits per texel write zeros in every register. The emitter fetches each window texel at LOD 0 and packs its bits like image_load_pck, so the sampler's filter, addressing and LOD state are not used; formats whose bits are not recoverable from the view and D16 data throw. The anchor and row multiply the normalized coordinate by the image size without contraction. The stream rule was measured for R8_UNORM, RG8_UNORM and RGBA8; only RGBA8 has an execution test. image_gather8h_pck (opcode 99) never returns texel data on this hardware - across dmasks 0x1 to 0xFF including the upper nibble, RGBA8, RGBA16 and RGBA32 formats, 1D, 2D and 3D descriptors and normalized and unnormalized sampling it only zero-fills popcount(dmask) registers (sentinel-prefilled destinations) - so it decodes but throws at translation; its eight-texel semantics remain unmeasured.

  • MIMG BY2/BY4 loads (image_load[_mip]_by2/by4, opcodes 0x42, 0x43, 0x4a and 0x4b) were measured on RDNA2 (gfx1035) with the hardware oracle for 2D images with identity swizzle. They read N texels (BY2: 2, BY4: 4) from x & ~(N - 1) on row y, texel by texel and component by component within each texel, with each texel converted like image_load. Every register is zero when the mip level is past the last level, x + N - 1 is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when the N texels fit in one dword: R8 and R16 (DMASK 0x3) or RG8 (0xf) for BY2, and R8 (0xf) for BY4. With 32-bit texels (R32, RG32, RG16, RGBA8) the hardware returns zeros. Other formats, masks, dimensions, swizzles, D16/A16 and indirect descriptors throw. The stores (0x52, 0x53, 0x5a and 0x5b) decode but throw at translation. Measured on hardware, they write from the unaligned x up to the end of its dword, which is not implemented.

  • 2D stores to 1D storage images: an image_store with DIM 2D through a 1D T# writes texel x and ignores y, as measured on RDNA2 (gfx1035) for y from 0 to 4 and 0xffffffff, single- and four-component formats and a 1x1 8_8_8_8_UNORM T#: the T# type decides the addressing, DIM only how many address VGPRs are read. Storage loads, atomics and image_store_mip through such a pair still throw

  • MIMG PCK2/PCK4 loads (image_load[_mip]_pck2/pck4, opcodes 0x70, 0x71, 0x73 and 0x74) were measured on RDNA2 (gfx1035) with the hardware oracle for 2D images with identity swizzle. With data mask 0x1, they write one dword: the raw bits of the N texels (PCK2: 2, PCK4: 4) from x & ~(N - 1) on row y, concatenated (element k at bit k * texel bits) and zero-extended. The dword is zero when the mip level is past the last level, x + N - 1 is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when N texels fit in 32 bits: R8, R16 or RG8 for PCK2, R8 for PCK4. Wider texels (R32, RG16, RGBA8) and masks other than 0x1 write zeros on hardware. Those masks throw at decode, and formats outside UNORM/UINT/SINT R8, R16 and RG8 throw. #1314's earlier measurement saw only zeros because it used masks 0x3 and 0xf. The stores (0x76, 0x77, 0x79 and 0x7a) decode but throw at translation. They are not measured, and opcode 0x76 on 32_32_32_32_FLOAT hung the GPU (#1314).

  • 3D image accesses bound to a 2D or 2D-array T# follow CouchRiv's hardware oracle measurement on RDNA2 (gfx1036, #2023) and adriadam10's (gfx1035, #2140), not the PS5's gfx1013. On a 2D T#, image_sample_lz ignores r and reads the (u,v) texel, image_load and image_load_mip read the (x,y) texel when z is 0 and return zero otherwise, and image_get_resinfo returns (width, height, 1, levels). On a 2D-array T#, these accesses behave like dim 2D-array: r or z is the layer, and image_get_resinfo returns the layer count as its third component. Every other access of such an image, including samples with an LOD, bias, derivatives, offset or comparison, gathers, image_get_lod and stores, keeps throwing because it is not measured.

  • 2D-array samples through a 3D T# (image_sample, image_sample_l and image_sample_lz with DIM 2D_ARRAY) sample the volume as DIM 3D does: the third address VGPR is the normalized r coordinate, addressed by the sampler's Z mode (only wrap was measured), not a layer index, and a plain sample in a pixel shader takes its LOD from the derivatives of all three coordinates. Measured on RDNA2 (RX 6700 XT, gfx1031, driver 32.0.21045.5002, Windows 11) with the hardware oracle template run through the driver's comgr and HIP runtime, not on the PS5's gfx1013: on 16x16x4 R32_FLOAT 3D T#s with one and three levels, the three instructions with DIM 2D_ARRAY returned the same value as with DIM 3D in all 256 lanes of 66 cases (point, bilinear and z-linear filters, wrap addressing, r from -0.125 to 3, quad steps of 1 to 4 texels in u and v and of 0 to 4 slices in r, LOD 1). Other samples of such a pair (bias, LOD clamp, derivatives, offsets, comparison, 16-bit addresses), gathers, image_get_lod and a sample mixed with a load of the same image keep throwing because they are not measured. The host sampler ignores Z_FILTER, as for every 3D sample, so r is filtered with the XY filter.

  • 2D accesses through a cube T# (#4139) read the T#'s BASE_ARRAY face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the hardware oracle template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (DEPTH 5 and 11, BASE_ARRAY 0 and 6): image_sample_lz, plain image_sample (one level, and six levels in 2x2 compute quads) and image_sample_l (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and DISABLE_CUBE_WRAP 0 and 1, and the VGPR after the two coordinates is not read; image_load with DIM 2D reads texel (x, y) of the BASE_ARRAY face. So the face coordinates are cube coordinates in [1, 2]: the texel is floor((u - 1) * width). They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and DISABLE_CUBE_WRAP 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with BASE_ARRAY 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and DISABLE_CUBE_WRAP setting measured, clamps a coordinate of 2 or more to the last texel when DISABLE_CUBE_WRAP is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only image_sample_lz, plain image_sample, image_sample_l, mip-less image_load, and mip-less image_store to an image the program only stores to take this mode. Such a store writes texel (x, y) of the BASE_ARRAY face, measured on RDNA2 (gfx1036, by simonbbby, not the PS5's gfx1013) with the hardware oracle on 8x8 mip-less cube arrays (DEPTH 11) of 32_UINT, 32_FLOAT and 16_16_16_16_FLOAT (the last also by simonbbby, with exactly representable half values) with BASE_ARRAY 0 and 6, wave32 and wave64: the 64 stored texels land on that face and every other byte of the 64 KiB backing is unchanged. Other formats, and Crimson Desert's own 256x256 tiled descriptor, are not measured. Other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, atomics, stores to an image the program also reads, stores of formats written through a conversion such as 32_SINT, mip loads and stores) still throw.

  • Unnormalized S#s are accepted only when direct and used only by explicit-LOD image_sample without comparison, derivatives or _a, on a single-level, single-layer 1D or 2D view of mip 0, even when the image is also sampled through a normalized S#.

  • Unnormalized texel offsets are added to the f32 coordinates: exact on RDNA2 (gfx1035), which applies the offset before the clamp (measured by adriadam10).

  • Cube image gradients (shader recompiler) - image_sample_d and image_sample_cd on a cube pass their gradients to the host sample without the -1.0 that moves the face coordinates from [1, 2] to [0, 1], as Mesa's AMD lowering builds them (src/amd/common/nir/ac_nir_lower_tex_coords.c, prepare_cube_coords: face-space derivatives with no offset, coordinates offset by 1.5). Measured on RDNA2 (gfx1035) with the hardware oracle: on a 16x16 32_FLOAT cube with five levels, point mip filter, image_sample_d reads the level of round(log2(16 * max(|ddx|, |ddy|))) for gradients of 2^(level ± 0.3) texels on each of the four components, both signs, mixed components, every face and two coordinates (1032 rows). image_sample_cd takes one level per quad of lanes, from the gradients of its first lane; the host sample computes it per lane.

  • Image atomics on 32_SINT and 32_FLOAT surfaces act on the raw dword through an R32_UINT storage view. On gfx1100 (RDNA3, hardware oracle, wave32 and wave64), all 13 integer image atomics, swap to dec, return the old dword and store the raw-dword result identically on 32_UINT, 32_SINT and 32_FLOAT T#s, over 121 old/data pairs per op, among them 0x80000000, 0xffffffff, 1.0, -1.0 and NaN: add adds the bits and umin is unsigned on every format. A 16_UINT control differs, so the format is read. gfx1031 (RDNA2) gives the same rows, and its fmin, fmax and fcmpswap, which gfx1100 lacks, also match on the three formats (measured by thuanquang). The console's gfx1013 is unmeasured. Other formats throw.

  • s_memtime on RADV with a GFX10.3 to GFX11.7 GPU (RDNA 2, RDNA 3, RDNA 3.5) reads the Vulkan device clock instead of the subgroup clock, which ACO reads from the 20-bit SHADER_CYCLES register there and which wraps every 2^20 cycles. The device clock is a monotonic 64-bit counter, but it counts the 100 MHz reference clock (the same one as s_memrealtime), not shader cycles, so s_memtime deltas are about 20 times smaller than on hardware. The families are matched by the name RADV reports (NarrowSubgroupClock); a family added to Mesa later reads the subgroup clock until it is listed. AMD's proprietary Windows driver also exposes a 20-bit subgroup clock on device 0x1114 (Radeon 860M, measured in #1615), device 0x744c (Radeon RX 7900 XT, measured in #1632), and device 0x747e (Radeon RX 7800 XT, measured in #1632); other AMD proprietary devices remain unverified.

  • s_memtime and s_memrealtime on llvmpipe read only the low 32 bits of the host's nanosecond clock at either scope: Mesa's emit_clock builds the high dword with a left shift by 32, so it is always 0 and both counters wrap every 2^32 ns (about 4.3 s). The shader clock test compares them modulo 2^32 there

  • ds_wrxchg2_rtn_b32, ds_wrxchg2st64_rtn_b32, ds_wrxchg2_rtn_b64 and ds_wrxchg2st64_rtn_b64 are translated as two separate 32-bit or 64-bit atomic swaps per invocation. RDNA2 hardware (measured on gfx1035) performs every lane's first exchange before any lane's second one, so when lanes of the same wave or of different waves overlap addresses, the result can differ from hardware. offset0 == offset1 throws, although on hardware it is two exchanges in order.

  • ds_condxchg32_rtn_b64 uses two 32-bit atomic conditional exchanges. RDNA2 (gfx1035) measurements confirmed aligned return values, conditional bit clearing and 16-bit offsets; out-of-range accesses return zero without modifying LDS. Misaligned accesses fault the wave on gfx1035; the recompiler does not model this fault. Radeon 780M (gfx1103, RDNA3) measurements confirmed aligned return values, conditional bit clearing, EXEC masking and operand overlap in wave32 and wave64; contended cases passed per-dword conservation checks. Contended lane ordering and gfx1013 remain unmeasured; pair-wide indivisibility is not assumed, and concurrent 64-bit LDS atomics use a separate lock. GDS throws. VGPR pairs extending beyond v255 throw; allocated-VGPR EXEC nullification is not emulated.

  • 64-bit image atomics (image_atomic_* with a two-dword data mask) need VK_EXT_shader_image_atomic_int64 and act on 32_32 UINT, SINT and FLOAT surfaces through an R64_UINT storage view. 64-bit image_atomic_inc, image_atomic_dec and the float atomics throw, as does any other access in the same shader to an image it updates with 64-bit atomics.

  • 8_SRGB and 8_8_SRGB loads on a device that cannot sample R8_SRGB or R8G8_SRGB return the sRGB EOTF rounded to nearest even at bf16 precision, as adriadam10 measured on RDNA2 (gfx1035) in #866; the PS5's gfx1013 is not measured. This is the path on a device that cannot blit R8 or RG8 to RGBA8, and with APS5_SRGB_SHADER_DECODE=1; otherwise a load of a sampled texture reads the RGBA8 sRGB copy and gets the device's own decode.

  • Samples and gathers of 8_SRGB and 8_8_SRGB textures read an RGBA8 sRGB copy of the texture (libSceAgcDriver, Texture.cpp) on a device that cannot sample R8_SRGB or R8G8_SRGB but can blit R8 or RG8 to RGBA8, so they use that device's own decode. They still throw on a device that cannot blit, and with APS5_SRGB_SHADER_DECODE=1. APS5_SRGB_SHADER_DECODE=copy also takes the copy on a device that samples these formats natively, so the copy can be tested on any device that can blit R8 or RG8 to RGBA8; on a device that cannot blit, it behaves like APS5_SRGB_SHADER_DECODE=1. A multisampled or depth view of these formats gets no copy.

  • Bindless image table entries of 8_SRGB and 8_8_SRGB textures read zeros, like converted formats, on a device that cannot sample R8_SRGB or R8G8_SRGB.

  • Shader MODE register access starts from the rounding fields of GuestContext::floatMode and follows immediate rounding writes (s_round_mode, s_setreg_imm32_b32 on MODE bits 0-3) through the control flow graph; s_getreg_b32 of those bits returns the tracked value. Only v_cvt_f32_i32, v_cvt_f32_u32, v_ldexp_f32, the f16 conversions and v_ldexp_f16 (with f16 denormals kept) and the instructions that already read FLOAT_MODE follow a rounding mode set at run time; any other float instruction that runs while a directed rounding mode differs from the initial one throws at that instruction. Rounding bits written from an SGPR (s_setreg_b32) or that differ between the paths reaching an instruction throw where they are read or used. A shader without FLOAT_MODE metadata throws on any rounding change. Non-rounding register reads still throw; s_setreg_b32 writes to other hardware registers are still ignored. Tests follow the public RDNA2/LLVM contract, not console measurements.

  • Flat scalar-read reachability keeps both successors active for non-clean predicates and loop headers, so an inaccessible direct root on such a path still refuses capture. Skipping a read under a clean, evaluable scalar branch follows RDNA2 conditional PC semantics and synthetic fixtures; console execution and title behavior are unverified.

  • A flat SRT read through a null or unmapped pointer loaded from memory raises a BDA fault only if the program executes it, then stops or reads zero like a BDA read.

  • Repeated s_loads of one flat SRT dword through a null or unmapped pointer report one of their pcs, and an s_load whose inaccessible dwords are all unused, or only address repeated loads, reports no fault of its own.

  • A value read through a null or unmapped pointer still throws at record time if a uniform fill or table base needs it, or if the pointer comes from user data and is not null, even if the console skips the read.

  • A V# or flat SRT word read through a null pointer taken from user data is zero and counts as a poisoned SRT read, so a dispatch that reads it is not cached. The console faults if the program executes the read; PPSA28416 branches around it with s_cbranch_execz. The fault is not modeled.

  • A V#, T# or S# loaded through a null or unmapped pointer read from memory is zero when the work is recorded, and the capture throws if no flat SRT slot records that read.

  • 2D accesses through a 1D T# read the texel the 1D addressing selects and ignore the second coordinate, as simonbbby measured on RDNA2 (gfx1036) in #2237 for image_sample_lz and mip-less image_load with point filtering and wrap, clamp and border addressing; image_sample_l was measured the same way on gfx1036 against image_sample_l with DIM 1D through the same T#, for u inside, on and past the edges, v of 0, 0.5, 1, -0.25 and 3, LOD -1 to 6 on a 5-level R32_FLOAT texture, point, linear and linear-mip filtering, wrap, clamp and border addressing and HEIGHT or DEPTH 3: all 4725 rows matched. The PS5's gfx1013 is not measured. A plain image_sample (implicit LOD) through such a pair takes its LOD from the derivatives of the first coordinate alone, measured on RDNA2 (gfx1036) with the hardware oracle on the quads of a compute kernel (wave32 and wave64, IEEE off, DX10 clamp on, denormals preserved, round to nearest even): on a 16-texel, 5-mip 32_UINT T# with point filters it picks the same mip and texel as DIM 1D for first-coordinate steps of 0, 1, 2, 4 and 8 texels per lane along x or along y, whatever the second coordinate does (normalised steps up to 16 per lane), while the same rows on a 16x16 2D T# move the mip with the second coordinate. Outside the pixel stage a plain image_sample is emitted with LOD 0 for every image type, so this pair reads mip 0 there, where the kernel measurement shows the quad's derivatives being used. image_sample_c_lz and plain image_sample_c through such a pair compare against the texel the DIM 1D comparison with the same reference and first coordinate reads, measured on RDNA1 (gfx1010, RX 5700 XT) with the hardware oracle template run through the Windows driver's comgr and HIP runtime (wave32 and wave64, IEEE off, DX10 clamp on, denormals preserved, round to nearest even): 8-texel 32_FLOAT and 16_UNORM T#s, also with HEIGHT 3, point and bilinear filters, wrap, clamp and opaque-white border addressing, less and gequal functions, second coordinates 0, 0.5, 1, 1.5, -0.25 and 3; on a 64-texel, 7-mip 32_FLOAT T# image_sample_c picks its LOD from the first coordinate's steps alone (1, 4 and 16 texels per lane give mips 0, 2 and 4; second-coordinate steps up to 64 texels leave it unchanged). Only comparisons the runtime does natively (32_FLOAT, 16_UNORM) reach the 1D view; comparisons the runtime emulates for other color formats still throw through such a pair. 2D-array image_sample_l and image_sample_lz through a 1D T# read as DIM 1D too, ignoring v and the slice: measured on gfx1036 with the hardware oracle (template assembled by the AMD Windows driver's amd_comgr_2.dll and launched through its amdhip64_6.dll) against the same sample with DIM 1D on a 16-texel 8_8_8_8 UNORM T# with 1 or 5 levels, HEIGHT or DEPTH 3, for u inside, on and past the edges, v of 0, 0.5, 1, -0.25 and 3, slices 0, 1, 2.7, -1 and 5, LOD -1 to 6, and the five filter and address cases above: all 48000 rows matched. A 2D-array image_load through a 1D T# is not the same: on gfx1036 it returns the DIM 1D texel only at slice 0, and zeros at slices 1, 2, 5 and -1 even with DEPTH 3, so it, the plain image_sample and the other 2D-array accesses still throw. Other 2D accesses through a 1D T# (storage, depth, packed, BY loads, mips other than explicit LOD, explicit LOD with comparison, bias, offsets, derivatives) still throw.

  • 3D image accesses bound to a 1D T# follow a hardware oracle measurement on RDNA1 (RX 5700 XT, gfx1010; wave32 and wave64, IEEE off, DX10 clamp on, denormals preserved, round to nearest even), not the PS5's gfx1013, on 8x1 R8G8B8A8_UNORM linear 1D T#s with HEIGHT and DEPTH 0 or 3 and point filters. image_sample_lz reads the texel u selects for v and r in {0, 0.5, 1, 1.5, -0.25, 3}, with wrap, clamp and border addressing, and v and r never produce the border; u wraps, clamps and borders as with DIM 1D. Mip-less image_load reads the x texel for any y in {0, 1, 2, 3, -1} when z is 0, returns zero for z in {1, 2, 3, -1}, whatever DEPTH says, and returns zero for x past the width. A plain image_sample (implicit LOD) on the quads of a compute kernel takes its texel and LOD from u alone: on a 64-texel, 7-mip 1D T# steps of 1, 4 and 16 texels per lane pick mips 0, 2 and 4, as DIM 1D image_sample_l does, and steps in v or r alone stay at mip 0. Outside the pixel stage a plain image_sample is emitted with LOD 0, as for 2D accesses through a 1D T#. Every other 3D access of such an image (image_load with mips, storage, explicit LOD, bias, derivatives, offsets, comparison, gathers, queries) keeps throwing. SONIC SUPERSTARS (PPSA06888) samples a 1x1 1D T# this way with plain image_sample and image_load.

  • Emulated comparison of a 2D-array access through a 2D T# samples the 2D image and ignores the slice, as the native modes of a 2D-array access through a 2D T# already do. This follows the T# type deciding the addressing; it is not measured on hardware for comparison samples

  • Flat aperture bases (core/shader/recompiler) - the layout follows the gfx9+ one in the AMD KFD driver's kfd_flat_memory.c (MAKE_LDS_APP_BASE_V9, MAKE_LDS_APP_LIMIT): each aperture is a 4 GB window whose base is the high dword, so a flat address is routed by its whole high dword, as measured on RDNA2 (gfx1035: src_shared_base 0x00010000_00000000, src_shared_limit 0x00010000_ffffffff). The PS5's base values, high dwords 0x80000000 (shared, SharedApertureHigh) and 0x70000000 (private, PrivateApertureHigh), are inferred from PPSA04263's traversal addresses, not measured on a console or read from SH_MEM_BASES. The scalar aperture sources read the same two values

  • Flat accesses outside the aperture routing (core/shader/recompiler) - only compute stages route flat accesses, so graphics-stage flat accesses to the shared or private aperture still go to BDA. In compute, a 64-bit flat atomic whose address is in the shared aperture takes the workgroup lock (ldsLock) for its two-dword update, and every such atomic requests that lock, so a shader with one pays for it even on global addresses (the path is exercised by agc_driver_global_atomics_float). Inferred, not measured on LDS or scratch: an aperture access that is not inside one dword is done byte by byte, an unaligned aperture atomic records the BDA unaligned fault, and LDS or scratch accesses past their end are dropped or read zero.

  • On a host subgroup narrower than 32 lanes, threadBit reads an EXEC written as a value (s_mov_b32/s_mov_b64) at bit (subgroup invocation id) of EXEC_LO, so lanes at or above the subgroup size read a lower lane's bit.

  • EXEC read as a value (s_mov_b64 sN, exec, the s_*_saveexec copy, ALU sources) is limited to host lanes. Pixel reads retain helper bits in quads with an active fragment, preserving whole-quad masks across copies. Quad expansion follows the RDNA 2 ISA, section 12.3, S_WQM_B32/B64, page 111; save/restore followed by implicit-LOD sampling is checked on an RX 6700 XT, not on a console. The console also holds lanes the host lacks, e.g. 32-63 of a wave64 on 32 host lanes.

  • v_readlane_b32 and fetch-inactive v_permlane16_b32/v_permlanex16_b32 read undefined data from a lane that the host subgroup does not hold or whose host invocation is inactive; the console reads the lane's VGPR.

  • Without fetch-inactive, v_permlane16_b32 and v_permlanex16_b32 take a source lane that the host subgroup does not hold as inactive and keep the old value (0 with bound_ctrl); the console reads that lane's VGPR.

  • On a host subgroup narrower than the wave, v_readlane_b32 of a constant lane outside it builds only as lane 63 of a half-wave scan (DPP row_shr 1/2/4/8, v_permlanex16_b32, EXEC -1) or, in a program whose entry EXEC is every lane (vertex, local and tessellation stages), as 0 when the value is 0 in lanes 32-63; other such reads throw. A value is 0 there when it is built from 0 through selects under a lane index (lane id or v_mbcnt of -1) below at most 32, v_cmp_ne_u32 against 0, OR, DPP and permlanes; the counts in s3 give such a bound on a 32-lane host. On a 32-lane host a half-wave scan may also run under s_mov_b64 exec, -1, use bound_ctrl:1 steps for an OR, add or unsigned max, and take keys that are 0 in lanes 32-63; its lane 31 is then the subgroup reduction of the host's invocations (a shuffle of the scan on a device without subgroup arithmetic): idle invocations add the identity, where the console would add the keys of the lanes s3 claims.

  • NGG mesh entry uses the guest wave size (32 or 64) for wave indices, per-wave vertex and primitive counts, and the workgroup wave count in s3. The four-bit wave fields follow Mesa's get_wave_id_in_tg, get_tgsize and get_thread_id_in_tg; partial waves and groups exceeding 15 waves throw. Indexed and non-indexed wave32 mesh rendering with 32, 64 and 128 threads was checked by pixel readback on Windows with an NVIDIA RTX 4070 Laptop GPU. The guest register values have not been compared with console hardware.

  • Mesh shader grid size (shader recompiler) - every mesh shader loads NumWorkgroups.x and declares no vertices when it is 0, which no dispatch produces. The read is a workaround for RADV (Mesa 26.2.4, seen on an RX 9070 XT, GFX12): binding a mesh pipeline whose shader reads neither NumWorkgroups nor DrawIndex keeps the user SGPR count and draw-id flag of the vertex pipeline bound before it in the same command buffer (radv_bind_pre_rast_shader updates them only when the shader has the AC_UD_VS_BASE_VERTEX_START_INSTANCE slot), so radv_emit_userdata_mesh opens a 3-register SET_SH_REG and writes one value, the CP takes the following DISPATCH_MESH_DIRECT as register data and the GPU hangs (gfx ring timeout, MODE1 reset). Kena: Bridge of Spirits (PPSA01802) hits it on its layered volume draws. Reading NumWorkgroups gives every mesh shader the grid-size slot, so the count and the values match; drop the read once a fixed Mesa is the minimum

  • Mesh layer output (shader recompiler) - a mesh shader writes each primitive's layer through a one-member Block struct array whose member is decorated BuiltIn Layer and PerPrimitiveEXT, as glslang declares gl_Layer inside gl_MeshPerPrimitiveEXT, not through a standalone PerPrimitiveEXT Layer array. Both forms pass spirv-val, but AMD's Windows driver (32.0.21045.5002, RX 6700 XT) crashes in amdvlk64.dll creating a pipeline whose mesh shader writes the standalone form, whether the layer is signed or unsigned and whatever the target, while the block form draws, also with a pixel shader that reads the layer.

  • Shader recompilation currently occurs right before it was transferred to Vulkan with caching, but should be moved to the relinker stage. For this purpose, shader/recompiler was written completely independently from libs/prx.

  • v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16 read the 16-bit halves of each vertex's parameter through VK_KHR_fragment_shader_barycentric, as fixed-function interpolation cannot split a 32-bit attribute. Without the extension the pixel shader takes the barycentric emulation, whose generated geometry stage passes each vertex's parameter to it as a flat input, so the halves arrive bit for bit; a device without geometry shaders takes the tessellation stages of the same emulation instead, which no 16-bit interpolation test has run through yet. P10 and P20 are the vertex differences rounded to f16 to nearest even, which is not confirmed on hardware. v_interp_p1ll_f16 and v_interp_p1lv_f16 round the exact P10 * I + P0 once to f32 and v_interp_p2_f16 rounds the exact P20 * J + S2 once to f16, as measured on RDNA2 (#697); the exact sums are computed in f64 or, on a device without the Float64 capability, in 64-bit integers: the exact product is added with a sticky bit and rounded once. They follow the shader's float mode, measured on RDNA2 (gfx1035) in all eight combinations of IEEE_MODE and kept/flushed f32 and f16 denormals over 768 special and 1,024 random rows. With IEEE=1 a NaN operand is returned quieted; with IEEE=0 it is returned unquieted, except that v_interp_p2_f16's f16 result is always quiet. Flushed f32 denormals flush I/J, v_interp_p2_f16's f32 S2 and the f32 result; flushed f16 denormals flush P0, P10/P20, v_interp_p1lv_f16's f16 S2 and the f16 result. Denormal modes 1 and 2 (flush only inputs or only outputs) and rounding other than to nearest even are not measured and throw. An unknown mode follows IEEE=0 with denormals kept. Lavapipe has no barycentrics, so the draw test is skipped in CI; agc_driver_interpolation_modes checks the integer arithmetic on every device, and the f64 arithmetic on devices with Float64. It checks both against the hardware rows in every mode, and against a CPU reference on random rows in every mode and f16 rounding.

  • Without VK_KHR_fragment_shader_barycentric, a pixel shader whose I/J VGPRs only feed v_interp_p1_f32/v_interp_p2_f32 of their own pair reads the attribute Vulkan interpolated. Any other read of them or of a raw vertex parameter takes the weights and the three vertex values from a generated geometry stage, which needs geometryShader and a vertex shader before it. On a device with tessellationShader but no geometryShader (MoltenVK), a triangle list without primitive restart takes them from generated tessellation stages instead: each triangle is one three-point patch at tessellation level 1, the evaluation stage writes the corner gl_TessCoord points at as the weights, and the vertex clip distances are forwarded: the control stage reads them as the separate ClipDistance array the vertex shader writes, so the vertex-to-control interfaces match, and passes them on inside its per-vertex block, which the evaluation stage reads and writes. MoltenVK 1.4.2 (SPIRV-Cross) cannot translate a separate ClipDistance array in a tessellation evaluation stage (no member named 'gl_ClipDistance' in 'main0_out'), so the two generated stages keep it in their blocks. A flat parameter takes the provoking vertex's value there, the first or with PROVOKING_VTX_LAST the last, and every corner of the patch writes it, so the host's own provoking vertex does not matter and VK_EXT_provoking_vertex is not needed; a flat per-vertex parameter with the last provoking vertex still throws. The patch keeps the triangle's facing with VertexOrderCw, checked with face culling on MoltenVK 1.4.2 (Apple M2) only. Tessellation, mesh and rect-list pipelines, primitives other than triangles, triangle strips and fans without geometryShader, more than 32 fragment input locations and interfaces beyond the device's geometry or tessellation limits throw.

  • v_fma_f64 and v_div_fmas_f64 with a subnormal operand or result round once, as IEEE 754 fusedMultiplyAdd does with f64 denormals kept, measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1, v_div_fmas_f64 with VCC clear and set. The f16/f64 denormal field of FLOAT_MODE (bits 7:6) is not read: when it flushes, the hardware flushes the operands and the result of v_fma_f64 (tininess before rounding) and only the result of v_div_fmas_f64, while these keep them. Shaders with f64 instructions declare DenormPreserve for 64-bit floats only when shaderDenormPreserveFloat64 is true and Vulkan reports denormBehaviorIndependence as ALL; other independence modes are not handled by the current backend because they require coupled execution modes. See VkShaderFloatControlsIndependence. Elsewhere (including lavapipe), the driver may flush f64 subnormals, and agc_driver_f64_fma_subnormals is skipped.

  • v_floor_f64, v_ceil_f64, v_fract_f64, v_frexp_mant_f64 and v_frexp_exp_i32_f64 follow the FLOAT_MODE f16/f64 denormal field: a denormal input is flushed to a zero of its sign when bit 0 is clear (0 and 2), and v_fract_f64 flushes a denormal result, the fraction of a positive denormal input, to +0 when bit 1 is clear (0 and 1). v_trunc_f64 and v_rndne_f64 give the same results either way and are unchanged. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with denorm16 0..3 over 23 f64 values (zeros, denormals, minimum normals, ±0.5, ±1, values next to 1, the largest finite value, infinities and quiet and signaling NaNs) and 16 of them with neg and abs modifiers. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured.

  • v_add_f64 and v_mul_f64 denormals follow the FLOAT_MODE f16/f64 denormal field; the PS5's gfx1013 is not measured. Inputs flush to a zero of their sign when bit 0 is clear (0 and 2). When bit 1 is clear (0 and 1), a sum below 2^-1022, or a product whose exact value rounded to 53 significant bits with an unbounded exponent is below 2^-1022, flushes to a zero of its sign, so 0x2000000000000001 * 0x1ffffffffffffffe (exactly 2^-1022 - 2^-1126) rounds to 2^-1022 and is kept. Measured on RDNA2 (gfx1031, RX 6700 XT, AMD Windows driver 32.0.21045.5002, HIP) at IEEE=0 and 1, denorm16 0..3, over 576 pairs of 24 f64 values (zeros, denormals, minimum normals, values next to 1 and to 2^-1022, products just below 2^-1022, the largest finite value, infinities), with neg, abs and clamp. v_fma_f64, v_div_fmas_f64 and shaders without a known float mode keep the previous lowering.

  • SINT storage images of 8- and 16-bit components saturate each stored 32-bit value to the component range, as adriadam10 measured for image stores on RDNA2 (gfx1035) in #944 (448 rows match); the PS5's gfx1013 is not measured. A D16 store sign-extends each 16-bit half before that saturation, as measured on RDNA2 (gfx1035) for image_store d16 to all eight SINT formats with DMASK 0xf, 0x7, 0x5, 0x3, 0x2, 0xa and 0x1 (1792 rows match).

  • StoreTexel writes channel 1 of a two-channel format from the data slot whose DST_SEL is Y, for SINT and UINT alike. With DST_SEL X,Y,Z,W, gfx1035 writes it from the W slot, as adriadam10 measured in #944.

  • v_ldexp_f16 follows the FLOAT_MODE f16 denormal field: bit 0 preserves the half input and bit 1 the half result. Otherwise a denormal input is flushed to its signed zero, and a result whose exact scaled value is below the minimum normal (2^-14) is flushed to its signed zero before rounding, so v_ldexp_f16 0x07ff, -1 gives +0 instead of 0x0400. Measured on RDNA2 (gfx1036) with round to nearest, IEEE 0 and 1 and denorm16 0..3, over every half magnitude 0x0000..0x0fff of both signs scaled by 2^-1 and 2^0 (16384 rows per mode): no mismatch. NaN and infinity handling and destination high-half preservation are unchanged. Shaders without a known float mode keep the previous lowering.

  • v_div_fixup_f64 flushes its src1 and src2 to a zero of the same sign when bit 0 of the FLOAT_MODE f16/f64 denormal field is clear (0 and 2) and keeps a denormal src0 and result in every mode, as v_div_fixup_f32 does with the f32 field. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) over 1728 triples of 12 f64 values (zeros, denormals, the minimum normal, ±1, the largest finite value, infinity and quiet and signaling NaNs) at IEEE=0 and IEEE=1 with denorm16 0..3. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured.

  • v_mullit_f32 follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted only with IEEE=1, as measured at IEEE=0 too; a positive denormal src2 counts as positive); with f32 denormals flushed it follows the f32 denormals entry below, and denormal modes 1 and 2 throw.

  • v_mad_legacy_f32 and v_mac_legacy_f32 (VOP3 0x140, VOP2 0x06) round the product to f32 before the add, give a +0 product for a zero factor, and flush their f32 denormal sources, their product and their result to a zero of the same sign in every f32 denormal mode, an exception to the FLOAT_MODE model in f32DenormalFlushFor. Measured on gfx1010 (RX 5700 XT) with tools/hw-oracle/template.s, assembled by the AMD Windows driver's amd_comgr_2.dll and launched through its amdhip64_6.dll instead of oracle.c and ROCr, over the 32 rows of the five FmaLegacy.cpp and the four MacLegacy.cpp encodings: identical in wave32 and wave64 at IEEE 0 and 1 with f32 denormal modes 0 to 3, except that the two signaling NaN rows are quieted only with IEEE=1 (#1281, #1290). A sum of two zeros takes its sign on the bits (+0 unless both are -0), as lavapipe returned -0 for +0 + -0. The PS5's gfx1013 is not measured; LLVM decodes the encoding as v_mad_legacy_f32 there, as on gfx1010. RDNA2 differs: on gfx1031 (RX 6700 XT, measured by pandather in #1281) the encoding is v_fma_legacy_f32 and fused, with the same zero rule and denormal-source flush, so an RDNA2 measurement is not evidence for the console.

  • v_mad_f32, v_mac_f32, v_madak_f32 and v_madmk_f32 (gfx10.1 only) leave f32 denormals and NaN bits to the driver, unlike the console. Measured on gfx1010 (RX 5700 XT, #5782): the product is rounded before the add; f32 sources, product and result are flushed to a zero of the same sign in every f32 denormal mode; the product is flushed when tiny after rounding to 24 bits with an unbounded exponent; NaN results follow the fma rule, an invalid product beating a NaN src2; in round mode 2 a sum of zeros of opposite signs is -0. Translating this cost 15× main on a v_mac_f32 chain on the RX 5700 XT and 23× on an RTX 3070, and a never-taken branch around it 6×, so it waits for the SPIR-V float controls of #981.

  • v_fma_mixlo_f16 and v_fma_mixhi_f16 round the exact product-sum once to f16 with the arithmetic of v_interp_p2_f16, in f64 or, without the Float64 capability, in 64-bit integers. On RDNA2 (gfx1036) they follow the FLOAT_MODE f16 round and denormal fields, not the f32 round field: -1.5 * 2^-24 + 2^-126 from f32 sources (0xbfc00000, 0x33800000, 0x00800000) gives 0x8001 with f16 denormals kept and round to nearest even, 0x8000 with f16 denormals flushed and 0x8002 with f16 round toward -infinity. With f16 denormals kept, every f16 rounding mode is translated: the exact product-sum is rounded once in the mode's direction. All four modes match RDNA2 (gfx1036) on the 2,233 rows of the #2631 sweep, including FLOAT_MODE 0xcc (toward zero, used by ps5-opengl) and the exact cancellations toward -infinity; the PS5's gfx1013 is not measured. A result beyond 65504 becomes infinity or 65504 as IEEE 754 gives for the mode. Flushed f16 denormals still throw, and packed f16 throws with any f16 rounding but nearest even. FP16_OVFL set is not measured.

  • Signaling NaN inputs of the f32 and f16 unary instructions (v_sqrt/rcp/rsq/log/exp/sin/cos/fract/floor/ceil/trunc/rndne/rcp_iflag/frexp_mant), v_ldexp_f32/f16, v_frexp_mant_f16, v_div_fixup_f32/f16 and v_mullit_f32 are returned quieted only with IEEE=1 and unchanged with IEEE=0, measured on RDNA2 (gfx1035) in both modes, and so are those of the f64 forms (v_sqrt/rcp/rsq/fract/floor/ceil/trunc/rndne/frexp_mant/ldexp/div_fixup_f64) and v_cvt_f64_f32, measured on RDNA2 (gfx1036) in both modes; shaders without a known float mode follow IEEE=0. v_pack_b32_f16 quiets each signaling NaN half by the same rule, measured on RDNA2 (gfx1036) in both modes. A signaling NaN result of v_cubesc/cubetc/cubema_f32 is quieted by the same rule and keeps the sign the instruction gives it, measured on RDNA2 (gfx1036) in both modes. v_add/sub/subrev/mul/fma/fmac/fmamk/fmaak/mul_legacy_f32 and v_fma_mix_f32 with f32 sources choose their NaN result on the bits, measured over 1000 operand triples in both modes: the first NaN source in operand order after its modifiers (the subtrahend negated, as the hardware adds its negation), quieted only with IEEE=1, or the default NaN 0xffc00000 for an invalid operation; in the fma forms an invalid product (inf * 0) gives the default NaN even when src2 is a NaN, and in the legacy forms a zero factor makes the product 0 even when the other factor is a NaN (a NaN src2 still wins). v_mad_legacy_f32 and v_mac_legacy_f32 follow the same rule with their separately rounded product, as measured on gfx1010 (#1281, #1290): a NaN source quieted only with IEEE=1, inf - inf giving 0xffc00000, the zero rule hiding a NaN factor and a NaN src2 winning. The f16 and f64 arithmetic, v_fma_mix_f32 with f16 sources and the separately rounded mad/mac forms (not measurable on RDNA2) follow the same rule on hardware but their NaN bits are left to the driver, except that an invalid operation of v_fma_f16, v_fmac_f16, v_fmamk_f16, v_fmaak_f16 and v_fma_mix_f32 with f16 sources (an inf * 0 product, also with a NaN src2, or a NaN from sources that are not NaN) gives the default NaN 0xfe00 or 0xffc00000, which NVIDIA does not return on its own. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with f16 and f32 denormals kept and flushed, over six inf * 0 products with eight NaN src2 values in every f16 fma form (#7074); the AMD Windows driver 32.0.21042.62 on an RDNA2 iGPU (Ryzen 5 7600) instead returns src2 quieted through Vulkan in the f16 fma forms. v_cvt_f16_f32, v_cvt_f32_f64, v_dot2_f32_f16, v_fma_mixlo_f16 and v_fma_mixhi_f16 always quiet and v_div_scale_f32 never does: the f32 signaling NaN 0x7f812345 as src0 or src2 gives 0x7e09 from mixlo and mixhi at IEEE=0 and IEEE=1 on RDNA2 (gfx1036), where v_fma_mix_f32 gives 0x7f812345 at IEEE=0 and 0x7fc12345 at IEEE=1. The payload of an f16 NaN source of mixlo and mixhi is left to the driver's f16 to f32 conversion, which NVIDIA replaces.

  • Scalar f16 add, sub, subrev and mul denormals follow the FLOAT_MODE f16 denormal field at nearest-even: bit 0 preserves inputs and bit 1 preserves the result; otherwise a denormal is flushed to its signed zero. Measured on RDNA2 (gfx1036) with IEEE=0/1 and FP16_OVFL=0/1, over 32 operand pairs in all four denormal modes, including signed zeros, denormals, minimum normals, infinities and NaNs. Multiply detects tininess after rounding to 11 significant bits with an unbounded exponent, before gradual half packing: 0x3bff * 0x0400 flushes, while 0x3c01 * 0x03ff rounds to 0x0400 and is kept. v_fma_f16, v_fmac_f16, v_fmamk_f16 and v_fmaak_f16 follow the same field at nearest-even: inputs are flushed when bit 0 is clear, and the result is flushed when bit 1 is clear if the exact product-sum rounded to 11 significant bits with an unbounded exponent is below 2^-14, so 0x0400 * 0x0400 + 0x8400 rounds to 0x8400 and is kept. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with denorm16 0..3 over 5075 operand triples of v_fma_f16 and v_fmac_f16 (4000 from 20 halves around the denormal range, 75 whose product-sum lies just below 2^-14 and 1000 random), and over the first 4075 of them for v_fmamk_f16 and v_fmaak_f16, FP16_OVFL=1, the clamp, neg and abs modifiers and op_sel. Directed rounding and v_fma_mixlo/mixhi_f16 keep their previous denormal lowering. Shaders without a known float mode keep the previous lowering. Scalar v_sub_f16/v_subrev_f16 share float16Binary with add/mul and were measured on gfx1036 over the same operand set as add (1156 non-NaN-input rows per mode); NaN result bits still depend on the driver as recorded above.

  • f16 unary, frexp and conversion denormals follow the FLOAT_MODE f16 denormal field: v_ceil_f16, v_floor_f16, v_fract_f16, v_rcp_f16, v_rsq_f16, v_sqrt_f16, v_log_f16, v_exp_f16, v_sin_f16, v_frexp_mant_f16, v_frexp_exp_i16_f16, v_cvt_norm_i16_f16, v_cvt_norm_u16_f16 and v_cvt_f32_f16 flush a denormal half input to its signed zero when bit 0 is clear (0 and 2), and the f16-result ones flush a denormal half result to its signed zero when bit 1 is clear (0 and 1), after the host rounds it to half. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with denorm16 0..3 over every non-NaN f16 input (63,490 rows per mode and instruction): the only mismatches are the 1-ULP driver differences of v_exp_f16 (2 inputs) and v_sin_f16 (14), which are the same with denormals kept. NaN inputs are not compared. v_ldexp_f16 shares the input rule. v_cvt_pknorm_i16_f16 and v_cvt_pknorm_u16_f16 flush both half inputs by the same bit, measured on RDNA2 (gfx1030) over every f16 input (each half also negated in the other source) at IEEE=0 and IEEE=1 with denorm16 0..3 and at f16 rounding 1..3: every row matches. Shaders without a known float mode keep the previous lowering.

  • v_div_fixup_f16 flushes its src1 and src2 to a zero of the same sign when bit 0 of the FLOAT_MODE f16 denormal field is clear (0 and 2) and keeps a denormal src0 and result in every mode, as v_div_fixup_f32 does with the f32 field. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) over 6859 triples of 19 halves (zeros, denormals, minimum normals, ±0.5, ±1, the largest finite value, infinities and quiet and signaling NaNs) at IEEE=0 and IEEE=1 with denorm16 0..3 and FP16_OVFL 0 and 1, also with neg and abs source modifiers and with op_sel high halves. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured.

  • Packed f16 NaN results (v_pk_add/mul/fma/min/max_f16) follow the hardware measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1 with f16 round-to-nearest-even and f16 denormals kept: with IEEE=1 a signaling NaN input is quieted and min/max return the other operand only for a quiet NaN; with IEEE=0 NaN inputs pass through unquieted and min/max ignore signaling. In v_pk_fma_f16 an invalid product (inf * 0) gives the default NaN even when src2 is a NaN. A shader without a known float mode is translated as IEEE=0, the mode titles use; a mode with another f16 rounding throws. In the f16 denormal modes that flush, each denormal input lane is flushed to a zero of its sign when bit 0 of the field is clear (0 and 2), so inf * 0x0001 is an invalid product there, and when bit 1 is clear (0 and 1) a v_pk_add_f16 result below 2^-14, or a v_pk_mul_f16, v_pk_fma_f16 or v_pk_fmac_f16 result whose exact value rounded to 11 significant bits with an unbounded exponent is below 2^-14, is flushed to a zero of its sign; v_pk_min/max_f16 flush only their inputs. The native f16x2 add and multiply is not used while results are flushed. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with denorm16 0..3, over 19,192 to 162,204 lanes per instruction with op_sel, op_sel_hi, neg_lo, neg_hi and clamp variants and products and sums at the 2^-14 boundary.

  • f32 denormals follow the shader's FLOAT_MODE f32 denormal field (bits 5:4) when it flushes them (0, and shaders without a known float mode, as in the 0xc0 most titles use), measured on RDNA2 (gfx1035) over 1444 rows per instruction at IEEE=0 and IEEE=1. v_add/sub/subrev/mul/mul_legacy/mullit/fma/fmac/fmamk/fmaak/min/max/min3/max3/med3/fract/floor/ceil/frexp_mant/sin/cos_f32 flush their f32 inputs and their result to a zero of the same sign (v_mullit_f32 measured on RDNA2 (gfx1036) over 6907 rows per mode at IEEE=0 and IEEE=1, so a positive denormal src2 counts as zero); the f32 compares, v_cvt_i32/u32/rpi_i32/flr_i32_f32, v_frexp_exp_i32_f32, v_cvt_f64_f32, v_cvt_f16_f32, v_cvt_pkrtz_f16_f32, v_cvt_pknorm_i16/u16_f32 and v_cvt_pk_u8_f32 flush their f32 inputs. v_div_scale_f32 flushes all three inputs to signed zeros, measured on RDNA2 (gfx1036) over 2744 operand triples per mode at IEEE=0 and IEEE=1 with f32 denormals flushed and kept; its scale-down behaviour and host denormal preservation remain incomplete. v_div_fixup_f32 flushes its src1 and src2 and keeps a denormal src0 and result, measured on RDNA2 (gfx1036) over 6859 rows per mode at IEEE=0 and IEEE=1. Tininess is detected after rounding, measured on RDNA2 (gfx1036) for v_mul_f32 and v_mullit_f32 at IEEE=0 and IEEE=1: a product is flushed when its value rounded to 24 bits with an unbounded exponent is below 2^-126, so 0x3f7ffffe * 0x00800001 (2^-126 * (1 - 2^-46)) gives 0x00800000 and 0x3f7fffff * 0x00800000 (2^-126 * (1 - 2^-24)) gives zero. The check rounds in the f32 round mode of FLOAT_MODE (bits 1:0), measured in all four modes, while the products themselves are still rounded to nearest even. This applies to v_mul_f32, v_mul_legacy_f32, v_mullit_f32, and to v_fma_f32, v_fmac_f32, v_fmamk_f32, v_fmaak_f32 and v_fma_mix_f32 when the addend is zero; with a nonzero addend the fused result is kept, so an exact result just below 2^-126 that rounds to it is not flushed. In the three directed modes the check is computed exactly from the significands. To nearest even (0, the mode of shaders without a known float mode) one factor is multiplied by 2^64, which is exact below 2^64, and the product with the other factor is compared with 2^-62. That multiply is a plain OpFMul like the product, and no f32 rounding mode is requested in the SPIR-V, so the check relies on the driver rounding f32 to nearest even by default, as the products do, and a product that rounds up to 2^-126 is the emitted product. v_fma_mix_f32 follows this rule with mixed f16/f32 sources too, measured on RDNA2 (gfx1036) with f16 in either multiplicand slot, low and high half selection, and an f32 or f16 zero addend: f32 0x00801002 times f16 0x3bff gives 0x00800000 to nearest at IEEE=0 and IEEE=1, while the neighbours 0x00801001 and 0x00801003 give zero and 0x00800001. The tiny-product boundary was also measured for both signs in all four f32 round modes at IEEE=0; Its f16 sources are flushed to a zero of their sign when bit 0 of the f16/f64 denormal field is clear (0 and 2), whatever the f32 field, measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with f32 denormal modes 0 and 3 and f16 denormal modes 0 to 3, over 2744 triples of 14 halves with all three sources f16 and 1960 rows with an f32 multiplicand. v_fma_mixlo_f16 and v_fma_mixhi_f16 flush their f32 sources too and keep a product below 2^-126, measured on RDNA2 (gfx1036). v_mad_legacy_f32 and v_mac_legacy_f32 flush their inputs, their product and their result in every mode, see its entry; their product is still checked for tininess on its exact value, so one below 2^-126 gives zero even when it rounds to 2^-126, as the console's separately rounded encoding is not measured at this boundary. Denormal modes 1 and 2 throw for the other instructions. Not covered, and not measurable on RDNA2 (no such opcodes) or not measured: v_mac_f32, v_mad_f32, v_madak_f32 and v_madmk_f32 (rounded separately), v_dot2_f32_f16 and v_div_fmas_f32 (on hardware v_div_fmas_f32 flushes only its result), f32 interpolation and ds_add_f32 keep the previous lowering; ds_min/max/cmpst_f32 flush both operands by the field, see the LDS float atomics entry. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no DenormPreserve execution mode is requested; v_ldexp_f32 and the f32 compares are the exceptions. v_cmp_X_f32 and v_cmpx_X_f32 compare the operands as sign-magnitude integers with the NaN cases handled apart, so a denormal is not read as zero, measured on RDNA2 (gfx1036) over 256 operand pairs per opcode at IEEE=0 and IEEE=1 with f32 denormals kept and flushed. v_ldexp_f32 preserves subnormal inputs and rounds a subnormal result in the f32 round mode of FLOAT_MODE, measured on RDNA2 (gfx1036) over 2163 rows in each of the four round modes at IEEE=0 and IEEE=1. An overflow rounds like any other result (infinity, or the largest finite value when the mode rounds that sign toward zero). With f32 denormals flushed it flushes a subnormal input and result, measured over the same rows in each round mode at IEEE=0 only. A device whose driver splits Fma into a multiply and an add flushes a product below 2^-126 before adding a non-zero addend in v_fma_f32 and v_fmac_f32: 0x1fffffff * 0x1fffffff + 0x00800000 gives 0x00800000 instead of the fused 0x00ffffff on RADV RENOIR (Mesa 26.2.4), whose compiled shader has v_mad_f32 and no v_fma_f32. F32DenormalFlush accepts that sum on such a device.

  • v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. Measured on RDNA2 (gfx1036) over every f16 input and 32,832 f32 inputs: RADV returns the hardware's bits for every input of rcp, rsq, sqrt and log and all but one of exp (f16 0x11c5, 1 ULP); NVIDIA (615.71.09) stays within 2 ULP except v_log_f32 near 1.0 (next entry). v_sin and v_cos take the driver's sine or cosine of the radians of the exact cycle, except within 2^-12 of a turn from a zero of the function, where the result is the linear term of the exact distance to the zero: the hardware's constant 6.2831826 (0x40c90fd5) at the integers, which the hardware applies exactly to inputs below 2^-12 of a turn, and the f32 2π at the half and quarter turns, where the hardware is within 7 ULP of it. Outside those bands, a driver result more than 4 ULP from a Taylor series in turns of the exact cycle is replaced by the series, which is within 2 ULP of the true sine and cosine; the hardware is up to 9 ULP from them (gfx1035, 243,092 f32 inputs), so results stay within 13 ULP of the hardware on any driver. RADV returns the hardware's bits for every f16 input and for 32,422 sine and 32,486 cosine results of the 32,832 f32 inputs; the rest are 1 ULP off, except up to 14 ULP on either side of non-zero integers and up to 15 ULP beyond ±256 cycles, where the hardware's own reduction differs from the exact fraction, and up to 6 ULP inside the half- and quarter-turn bands. NVIDIA's own sine is within 1 ULP on 93% of the f32 inputs and up to 1,166 ULP off just outside the bands, its cosine within 163 ULP; the series replaces those results. A denormal f32 input with f32 denormals kept gives the hardware the input times 6.2831826; both hosts flush it.

  • v_ldexp_f32 in denormal modes 1 and 2 follows the FLOAT_MODE f32 denormal field: mode 1 keeps a subnormal input and flushes a result below 2^-126 to a zero of its sign in every f32 round mode, also one that rounds to 2^-126 at subnormal precision (0x3fffffff scaled by 2^-127); mode 2 flushes a subnormal input to a zero of its sign and rounds a subnormal result in the f32 round mode of FLOAT_MODE, as mode 3 does. Measured on RDNA2 (gfx1030, Radeon RX 6800, AMD Windows driver 32.0.21045.5002 through HIP) over 10,260 rows (54 inputs of both signs: zeros, subnormals, normals near 2^-126 and 1.0, the largest finite value, infinities and NaNs, each scaled by 190 exponents from -2^31 to 2^31-1) at IEEE=0 and IEEE=1 with each f32 denormal mode 0 to 3 and f32 round mode 0 to 3, DX10_CLAMP=1, f16 denormals kept, f16 round to nearest even and FP16_OVFL=0; with the clamp modifier the result is clamped as in the other modes. The PS5's gfx1013 is not measured.

  • v_log_f32 and v_log_f16 keep the driver's log2 unless, for inputs within 1/8 of 1.0, it is more than 4 ULP from an eight-term series in x - 1, which then replaces it. v_log_f16 goes through the same f32 lowering and is rounded to half. The series is evaluated for every v_log and chosen with OpSelect rather than a branch, so its seven fused multiply-adds and two multiplies are an unconditional cost. Measured on RDNA2 over all 3,145,728 f32 inputs within 1/8 of 1.0 (gfx1035, IEEE 0, f32 denormals flushed), the hardware is within 1 ULP of the correctly rounded log2 and the series within 2 ULP of it and within 3 ULP of the hardware (2 except at 0x3f752e47, 0x3f7baab0 and 0x3f7bc4fb), so a kept driver value is within 7 ULP of the hardware and a driver that returns the hardware's bits never triggers the gate; RADV (Mesa 26.2.4) returns the hardware's bits on the 9,280 such inputs measured on gfx1036; NVIDIA (615.71.09) was up to 23,947,836 ULP off at 1 - 2^-24 and is within 6 ULP inside the band after the change; outside it its own log2 stays up to 9 ULP off (measured up to 1.375). Over every f16 input, the change moves one v_log_f16 result on NVIDIA (0x3bed, 1 ULP, onto the hardware's bits) and none on RADV.

  • Ordered not-equal, less-than and greater-than compares (shader recompiler) - FPOrdNotEqual32 is emitted as OpFUnordNotEqual, and FPOrdLessThan32 and FPOrdGreaterThan32 as their ordered forms, each with both operands checked for NaN on their bits; the results are the same for every input. The AMD Windows driver 32.0.13058.8001 (Radeon 860M, RDNA 3.5) returns 1 for a NaN operand when any float form of not-equal feeds a ballot on a 32-wide subgroup (#825), and so does a less-than or greater-than against an infinity the driver sees as a constant (#3536), presumably rewritten into a not-equal by its compiler

  • 16-bit integer multiplies (v_mul_lo_u16, v_pk_mul_lo_u16) (shader recompiler) - a factor taken from the low half of its source is read as the low 24 bits of the register, not the low 16; the low 16 bits of the product are the same for every input. The AMD Windows driver 32.0.21045.5002 (RX 6700 XT, RDNA 2) stores the full 32-bit product when two values it knows fit in 16 bits are multiplied, the product is reduced to 16 bits and nothing else is merged into the stored value: v_mul_lo_u16 into a register whose high half is zero, also with factors masked to 16 bits by the program, and v_pk_mul_lo_u16 whose high lanes are zero wrote 0xfffe0001 for 0xffff * 0xffff where the GPU writes 0x00000001. Reducing the product with v_and_b32, v_bfe_u32 or a shift pair loses the same bits; factors read as 24-bit fields do not. The constant folder keeps a field read that a multiply uses, even when the program already masked the factor to 16 bits, since folding it away brings the full product back (#8271). Measured against the same instructions run through the hardware oracle on that GPU; other drivers are not checked

  • f32 compares in denormal modes 1 and 2 (v_cmp_X_f32 and v_cmpx_X_f32 with X = eq, ge, gt, le, lg, lt, neq, nge, ngt, nle, nlg, nlt) flush their f32 denormal inputs by bit 0 of the FLOAT_MODE f32 denormal field: mode 1 keeps them as mode 3 does, and mode 2 flushes them as mode 0 does. Measured on RDNA2 (gfx1030, Radeon RX 6800, AMD Windows driver 32.0.21045.5002 through HIP) over 484 operand pairs of zeros, denormals, minimum normals, ones, the largest finite value, infinities and quiet and signaling NaNs of both signs, in VOPC form, at IEEE=0 and IEEE=1 with each f32 denormal mode 0 to 3, f32 round to nearest even, DX10_CLAMP=1, f16 denormals kept, f16 round to nearest even and FP16_OVFL=0. The PS5's gfx1013 is not measured.

  • f16 compares (v_cmp_X_f16 and v_cmpx_X_f16 with X = eq, ge, gt, le, lg, lt, neq, nge, ngt, nle, nlg, nlt) flush their f16 denormal inputs to a zero of the same sign by bit 0 of the FLOAT_MODE f16 denormal field: modes 0 and 2 flush them, 1 and 3 keep them. Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) over 256 operand pairs of zeros, denormals, minimum normals, ones, the largest finite value, infinities and quiet and signaling NaNs of both signs, in VOPC form, at IEEE=0 and IEEE=1 with each f16 denormal mode 0 to 3, f16 round to nearest even, DX10_CLAMP=1, f32 denormals kept and FP16_OVFL=0. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured.

  • DB_SHADER_CONTROL.PRIMITIVE_ORDERED_PIXEL_SHADER (shader recompiler) - the host pixel interlock spans the whole shader, not only up to s_sendmsg ORDERED_PS_DONE.

  • POPS collision SGPRs (shader recompiler) - the collision wave ID and intrawave collision mask read 0, so a title skips its POPS wait and runs its per-collision loop once.

  • Image loads (shader recompiler) - an image_load reads a sampled binding, also of an image the shader stores to, so in a primitive-ordered pixel shader it misses an earlier overlapping primitive's image_store in the same draw.

  • Tessellation rings of local, hull and domain programs become Vulkan tessellation interfaces and are never written to guest memory; a title that reads the off-chip or factor ring back sees no data.

  • Hull input control points (shader recompiler) - a hull LDS read at a control-point index computed from data reads gl_in at that index clamped to the patch's last input control point, and a domain off-chip read at a computed index reads gl_in clamped to the patch's last output control point. On the console an index past the patch reads the LDS slot or off-chip record of a neighbouring patch or of no patch; the host has no such memory, and Vulkan leaves an out-of-range gl_in index undefined. The lowering refuses a computed offset that is not a whole number of control points, but the index itself is only known when the program runs.

  • Inner tessellation factors of integer, fractional-even or fractional-odd triangle patches that are NaN tessellate as 1 (fractional-even then raises them to 2, as Vulkan does), as in the D3D11 reference tessellator; the console is not measured.

  • Hull programs start from the hull launch registers (user data, ring bases, v0, v1); on the console they start with what the local program leaves, and that it leaves them unchanged is not checked.

  • Quad tessellation factors are taken as outer[0..3] then inner[0..1], 24 bytes per patch, as Mesa stores them; unmeasured on the console.

  • VGT_HOS_MIN/MAX_TESS_LEVEL clamp positive tessellation factors (inner ones to at least 1); outer factors that are zero, negative or NaN still cull the patch and such inner factors become max(HOS_MIN, 1). The console's clamp and cull order is not measured.

  • Patch constants that hull programs write and domain programs read are placed by their off-chip address modulo the record stride, as the two compile apart; a domain read of a field the hull never writes, or of only a neighbouring patch's record, is not detected.

  • Integer partitioning (VGT_TF_PARAM.PARTITIONING 0) is drawn with Vulkan's equal spacing, which radeonsi programs as PART_INTEGER, with factors bounded at 64 instead of fractional-odd's 63; how the console rounds a clamped factor up to a whole number of segments is not measured. Power-of-two (1) partitioning throws.

  • Fractional-even partitioning (VGT_TF_PARAM.PARTITIONING 3) is drawn with Vulkan's fractional-even spacing, which radeonsi programs as PART_FRAC_EVEN, with factors bounded at 64. Vulkan raises every factor that is not culled to at least 2 after VGT_HOS_MIN/MAX_TESS_LEVEL, so a VGT_HOS_MAX_TESS_LEVEL below 2 still tessellates at 2; the D3D11 range for this mode is also [2, 64], but the console's order of the HOS clamp and that minimum is not measured.

  • The relative patch index that hull and domain programs use as data is (PrimitiveID mod PRIM_GRP_SIZE) mod NUM_PATCHES, with the draw-relative instance times the patches per instance added when GE_CNTL.BREAK_WAVE_AT_EOI is clear and the instances share hull threadgroups; a domain that uses it in such a draw, and a hull without shaderDrawParameters, throw. Unmeasured on hardware; PPSA26344 programs 64 primitives for 63 patches.

  • The output modifier (mul:2, mul:4, div:2) follows the shader's float mode as measured on RDNA2 (gfx1035): the hardware applies it only with IEEE mode off and output denormals of the result's precision flushed (bit 1 of the FLOAT_MODE denormal field clear), and ignores it otherwise. On f32 results it is applied as the hardware does with f32 denormals flushed; a shader without a known float mode is translated with IEEE mode off and f32 denormals flushed, the mode titles use. On f16 and f64 results it is ignored, and it throws in the modes where the hardware applies it. v_cubeid_f32, v_cubesc_f32, v_cubetc_f32 and v_cubema_f32, measured on RDNA2 (gfx1036) with DX10_CLAMP=1, apply it whenever IEEE mode is off, in all four f32 denormal modes (v_mul_f32_e64 in the same measurement follows the rule above), with the same bit-level result as the f32 path, and ignore it with IEEE mode on; clamp is applied after it. Only round to nearest even is modelled: in the other round modes (measured with IEEE mode off) gfx1036 rounds an overflow as the mode says (to the largest finite value when rounding toward zero), in v_cubema_f32's own result and in the mul:2 and mul:4 scaling, where the translation gives infinity, and when rounding toward negative infinity a zero v_cubema_f32 result with a NaN input is -0, where the translation gives +0.

  • The clamp modifier follows DX10_CLAMP, measured on RDNA2 (gfx1035) for f32, f16, packed f16 and f64 results at IEEE=0 and IEEE=1: with DX10_CLAMP=1 (and in shaders without a known float mode) a NaN result clamps to +0, including the quieted NaN that v_pk_min_f16 and v_pk_max_f16 return for a signaling NaN operand with IEEE=1 (measured on RDNA2 gfx1036 at both IEEE_MODE and both DX10_CLAMP settings); with DX10_CLAMP=0 a NaN result is written unchanged and every other value clamps to [0, 1] as before. f64 results follow the same rule on hardware, but a clamped f64 result with DX10_CLAMP=0 throws until it has an execution test. With DX10_CLAMP=0 the NaN's bits are those the translation already produces for the instruction without clamp, so where those are left to the driver (f32 and scalar f16 arithmetic) the payload is the driver's, not the hardware's input propagation. v_dot2_f32_f16 and the integer clamps are unchanged.

  • FP16_OVFL follows the shader's float mode as measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f16 denormals kept and flushed: with FP16_OVFL=1 an f16 result that rounds to ±infinity is written as ±0x7bff unless a source operand is infinite, so v_rcp_f16, v_rsq_f16 and v_log_f16 of ±0 and v_exp_f16 of 16 or more are clamped while an infinite input passes through, and v_div_fixup_f16 keeps the infinity it writes for a zero denominator or an infinite numerator and clamps any other infinite quotient, a NaN one included. v_cvt_pkrtz_f16_f32 rounds toward zero and never overflows. The sources are checked as the f32 values the translation computes with; the rule is applied to v_fma_mix*_f16 with f32 sources and to v_interp_p2_f16 without a measurement of infinite f32 sources or parameters. Round modes other than nearest even are not modelled, so the overflow threshold is the nearest-even one (65520).

  • Directed f16 rounding for v_cvt_f16_f32, v_cvt_f16_i16, v_cvt_f16_u16 and v_ldexp_f16 follows FLOAT_MODE bits 3:2 in every f16 denormal mode, measured on RDNA2 (gfx1036 with f16 denormals preserved, gfx1030 in all sixteen combinations of f16 rounding and denormal mode). When bit 7 flushes f16 results, v_cvt_f16_f32, v_cvt_f16_i16 and v_cvt_f16_u16 write a zero of the result's sign for a value that, rounded in that mode with an unbounded exponent, is below 2^-14, so a value just below 2^-14 that rounds up to it stays 0x0400; v_ldexp_f16 keeps its rule above. The clamp modifier is applied before the rounding. On gfx1030 every row matches: 8,996 f32 inputs around the half denormal, normal and overflow boundaries with and without clamp, every 16-bit integer, and 2,470 v_ldexp_f16 operands.

  • v_cvt_pkrtz_f16_f32 and v_pack_b32_f16 denormals follow the FLOAT_MODE f16 denormal field: v_cvt_pkrtz_f16_f32 flushes each denormal half result to its signed zero when bit 1 is clear (0 and 1), and v_pack_b32_f16 flushes each denormal half input to its signed zero when bit 0 is clear (0 and 2). Measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) at IEEE=0 and IEEE=1 with denorm16 0..3 and f32 denormals kept, over 256 pairs of 16 f32 values around the f16 denormal range for v_cvt_pkrtz_f16_f32 and 256 pairs of 16 halves (zeros, denormals, minimum normals, ±1, the largest finite value, infinities and quiet and signaling NaNs) for v_pack_b32_f16. Shaders without a known float mode keep the previous lowering. The PS5's gfx1013 is not measured.

  • Comparison sampling of a color texture (image_sample_c/image_sample_c_lz on a texture that is not R32 float or R16 unorm) returns 0 for every comparison function on formats AnyPS5 can sample as floating-point measured on GFX10 (codes 1–77 except native comparison codes 7 and 22) and on the corresponding GFX11 formats measured on gfx1100. The current Vulkan path still lowers these formats through the shader; the compare result is constant zero. Scaled, integer and formats requiring converted sampling remain refused. Only 2D, 2D-array and cube views are accepted; image tables, min/max reduction, unnormalized coordinates and unsupported addressing remain refused. Point and bilinear results match on gfx1100. The PS5's gfx1013, array/cube behavior for the measured formats, and anisotropic comparisons remain unverified. Bilinear weights are full float, not the hardware's fixed-point subtexel weights

  • Negative color-texture comparison wrap indices (libSceAgcDriver) use explicit OpSRem normalization to implement modulo; the negative-offset point-wrap regression fails with direct OpSMod and passes with normalization on Mesa 22.3.6 llvmpipe (LLVM 15.0.6), but negative-index wrap has not been measured on PS5 hardware.

  • Converted unorm images (10_11_11_UNORM, read and written through an R32_UINT view) are sampled in the shader, the way color comparisons are emulated: image_sample of a 2D or 2D array view fetches the one or four texels at LOD 0, decodes them and blends them, for point or bilinear filtering with equal minification and magnification filters and wrap or clamp-to-edge addressing. A shader gets these sample modes only once a converted descriptor reaches one of its sampled images: that capture throws ConvertedSampleRequired, the driver records the program and prepares it again with GuestContext::convertedSamples, so such a shader is prepared twice per run and its first capture is repeated. The record lives with the registered program and is not kept across runs. Bilinear weights are rounded to the hardware's 8-bit grid (roundEven(fraction * 256) / 256, ties to even), as measured with image_sample_lz on RDNA2 (gfx1035) for 10_11_11 UNORM texels by adriadam10 in #5958; point sampling, decoding, swizzles and wrap and clamp-to-edge addressing matched the same measurement. A sample that is not _lz needs an image with one mip level; gathers, LOD queries, clamp-to-border and mirrored addressing, other filters, min/max reduction, unnormalized coordinates, images selected at run time, 16-bit data and a DST_SEL of the missing W channel throw.

  • Converted unorm image loads divide by 2^n - 1 with the buffer format path's OpFDiv, which Vulkan allows to be up to 2.5 ULP off, so some drivers are not bit-exact.

  • 64-bit LDS atomics (ds_*_u64, ds_*_b64, ds_*_f64) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. On RDNA2 (gfx1036) the LDS serialises a 32-bit atomic on a slot's high dword and a 64-bit atomic on that slot: 128 lanes of ds_add_u32 and 128 lanes of ds_add_u64 on one slot sum exactly. Only that pair was measured. Every 32-bit LDS atomic of a program that contains a 64-bit LDS atomic therefore takes the same lock, including a 32-bit flat_atomic_* whose address lands in the LDS aperture (a 64-bit one takes it already); the flat forms were not measured against a 64-bit atomic either (the other 32-bit LDS atomics, ds_min/max, ds_cmpst, the float forms, ds_inc/dec, ds_mskor and ds_wrxchg, are extrapolated from ds_add_u32 and were not measured against a 64-bit atomic); a program without a 64-bit LDS atomic keeps its native 32-bit atomics. ds_condxchg32_rtn_b64 is two ds_mskor halves, each under the lock but not atomic as a pair, so a 64-bit atomic between the two halves is not excluded. Sub-dword writes (ds_write_b8, ds_write_b16 and their _d16_hi forms) and plain ds_read_*, ds_write_*, ds_append_u32 and ds_consume_u32 are not taken under the lock, so a plain access racing a 64-bit atomic on the same dwords can see or overwrite one half of it; whether the hardware orders them was not measured. The lock serialises the workgroup's LDS atomics of such a program; the throughput cost was not measured. VK_KHR_workgroup_memory_explicit_layout with shaderSharedInt64Atomics would make both widths native and is not used. A 64-bit atomic at an address that is 4-byte but not 8-byte aligned faults the wave on gfx1036, as on gfx1035. When the LDS lives in device memory (compute LDS beyond the device's shared-memory limit) and the device has shaderBufferInt64Atomics, no lock is used: a 64-bit atomic is a compare-exchange loop on a 64-bit view of the storage buffer and a 32-bit atomic is a native buffer atomic, a misaligned 64-bit atomic records the Unaligned shader fault with the LDS byte address instead of updating, so a mixed-width pair on one slot relies on the device ordering both widths on one buffer address; only ds_add_u32 against ds_add_u64 was measured, on gfx1036 (AMD Windows driver 32.0.21042.62) and an RTX 4070 SUPER. The lock is not used there because AMD's Windows driver compiles an acquire or release on a storage buffer, at workgroup scope or wider, to s_barrier, and those barriers inside the lock's divergent loop pair with the program's own s_barriers. The fence after each LDS atomic compiles to s_barrier on that driver as well, so a wave that skips an LDS atomic that other waves of its workgroup execute misses a barrier they reach; its effect was not measured. On that path a 64-bit atomic at an address that is 4-byte but not 8-byte aligned is skipped and returns 0.

  • LDS float atomics (ds_min/max/cmpst_f32, ds_min/max/cmpst_f64 and their _rtn forms) follow the hardware measured on RDNA2 (gfx1036) over 14 x 14 operand sets (zeros, ones, infinities, denormals, quiet and signalling NaNs of both signs and with payloads) in IEEE 0 and 1 with every value 0-3 of the type's denormal field: both operands are flushed to a signed zero when bit 0 of the field is clear (0 and 2; 1 and 3 keep them; the f32 field for the f32 forms, the f16/f64 field for the f64 forms; a shader without a known float mode flushes f32 and keeps f64), then a signalling-NaN data operand is stored quieted, else a signalling-NaN old value is stored quieted, else a quiet-NaN old value is replaced by the data, else a quiet-NaN data operand loses, else the ordered compare with -0 below +0 decides, and the flushed value is what is written, also when the old value wins. cmpst compares the flushed bits (both zeros equal, NaN never equal) and leaves memory unchanged when they differ. The returned value is always the old value as it was in memory. IEEE_MODE has no effect. The same bit rule serves the buffer float atomics (AtomicFloatBits). Not measured: a denormal or signalling-NaN data1 of cmpst (stored as given). ds_add_f32 and ds_add_rtn_f32 follow RDNA1 (gfx1010, RX 5700 XT), measured with the hardware oracle template run through the Windows driver's comgr and HIP runtime over 1574 operand pairs (signed zeros, denormals, the smallest normals, infinities, quiet and signalling NaNs with payloads, the largest normals, random pairs near the denormal range and pairs that nearly cancel) in all 16 combinations of the f32 denormal field and the f32 round mode, and at IEEE 1 and DX10 clamp 0: both operands are flushed to a signed zero when bit 0 of the field is clear and the result when bit 1 is clear, the sum is rounded to nearest even whatever the round mode, a NaN data operand is stored quieted, else a NaN old value is stored quieted, +inf + -inf stores 0xffc00000, and IEEE_MODE and DX10_CLAMP have no effect. The sum is the host's OpFAdd between those flushes, so where the field keeps f32 denormals the result also depends on the host preserving them, which the module does not request. thuanquang measured the same rule on RDNA2 (gfx1031, RX 6700 XT) in #5492, with the same operand classes and 18 modes in wave32 and wave64; the PS5's gfx1013 is not measured.

  • LDS in pixel, vertex-path and fetch programs (shader recompiler) - is an array private to each host invocation, in Function storage, as only compute and mesh programs get workgroup memory; on the console one LDS region is shared by the wave (RDNA 2 ISA sections 3.6.6 and 10.4), so a lane does not see another lane's LDS writes. Sub-dword writes (ds_write_b8, ds_write_b16 and their _d16_hi forms), the 32-bit LDS atomics, ds_append and ds_consume load, update and store the invocation's own dword without an atomic, as Vulkan allows atomics only on Uniform, Workgroup, Image, StorageBuffer, PhysicalStorageBuffer and TaskPayloadWorkgroupEXT pointers (VUID-StandaloneSpirv-None-04686). ds_append and ds_consume update only the first active lane's copy and return its old value to every lane, so a second append after the first active lane changes, or a read of the counter by another lane, differs from the hardware. Not measured on hardware.

  • V# bases off a DWORD boundary (shader recompiler) - raw accesses use the exact bytes and s_buffer_load reads (base & ~3) + (offset & ~3), as measured on gfx1035 (Linux, alignment mode UNALIGNED; the console's mode is unknown). Formatted accesses off their element's alignment throw. Atomics use the byte address base + offset, base's low two bits included: measured on RDNA2 (gfx1031, Radeon RX 6700 XT, AMD Windows driver 32.0.21045.5002 through HIP) with buffer_atomic_add, buffer_atomic_swap and buffer_atomic_cmpswap and bases 0 to 3 bytes past a DWORD boundary with offsets 0 to 7, an address on a DWORD boundary updates that DWORD and returns its old value, and any other address stops the dispatch, which the recompiler reports as an unaligned access fault.

  • Unaligned 32-bit buffer atomics (shader recompiler) act on the dword that holds their address, as section 8.1.7 of the RDNA2 ISA describes for alignment mode DWORD ("the two LSBs of the byte-address are ignored, thus forcing Dword alignment"). The mode comes from SH_MEM_CONFIG.alignment_mode, and the console's mode is unknown. MXGP 26 (PPSA30839) sets bits with buffer_atomic_or at odd byte offsets, so the console is not in a strict mode, where section 3.11 reports a memory violation for a misaligned atomic. In mode UNALIGNED, which Linux KFD sets, a misaligned atomic stops the dispatch (measured on gfx1031 and gfx1036), so the console is not in that mode either. A 64-bit buffer atomic off an 8-byte boundary still records an unaligned BDA fault: mode DWORD would align it only to 4 bytes, which a host 64-bit atomic cannot address. When the V# base itself is off a DWORD boundary and the containing dword lies before the first aligned dword the buffer view binds to, that atomic is out of bounds and returns 0 without writing. Covered by BufferAtomicUnaligned.cpp.

  • 32-bit bitfield masks (S_BFM_B32, V_BFM_B32) use the truncating 32-bit shift specified by the RDNA 1.0 ISA, sections 12.1 (opcode 36) and 12.12 (opcode 867). Width and offset use their low five bits. This matches gfx1035 (Linux) for every width/offset pair, with and without upper operand bits set, and both leave SCC unchanged; not measured on the console.

  • Vector buffer offsets that are not a multiple of 4 (shader recompiler) - dword accesses count whole dwords from the V# base, where the hardware reads the exact bytes at base + offset.

  • Raw buffer_load_dwordx2 is one 64-bit access on the hardware (with or without glc), so it never sees half of a buffer_atomic_swap_x2. Only a glc load that is 8-byte aligned and in bounds, in a program that has the 64-bit storage view (it uses 64-bit buffer atomics), is read with one 64-bit atomic load; every other dwordx2 load is two dword loads and can tear against a concurrent 64-bit write: loads without glc, and loads in a program without 64-bit buffer atomics while another shader writes the pair with swap_x2.

  • Buffer loads without GLC or DLC (shader recompiler) are not coherent in a program that has GLC or DLC buffer accesses and no buffer_gl0_inv, buffer_gl1_inv, s_gl1_inv or s_dcache_inv: only its GLC and DLC loads, stores and atomics use the Coherent buffer view, so a plain load can return a value cached before another work-group's write. This follows the LLVM AMDGPU memory model for GFX10, in which a load-acquire is a GLC DLC load followed by buffer_gl1_inv and buffer_gl0_inv; it is not measured on the console or on RDNA hardware.

  • DS src2 operations (ds_*_src2_*, GFX10.1 only) read DS[B], with B = A + 4 * sext(offset1[6:0]:offset0), and then update DS[A] with the matching 32-bit atomic, so the read of B is not atomic with the update of A. The form that takes the offset from the address (offset1[7] set), the 64-bit forms and GDS throw: whether the LDS address keeps A[31:17] in that form, and whether the 64-bit forms scale the offset by 4 or 8, is not documented. ds_min_src2_f32, ds_max_src2_f32 and ds_add_src2_f32 throw: on RDNA2 (gfx1035, the oracle's hardware, which has no src2 opcodes) ds_min_f32, ds_max_f32 and ds_add_f32 ignore IEEE_MODE but depend on whether f32 denormals are flushed (28, 28 and 12 of 196 special rows differ), and that is not modelled for LDS float atomics yet (#981).

  • ds_ordered_count (GDS only) reads its operand fields as LLVM encodes them for llvm.amdgcn.ds.ordered.add (SIISelLowering.cpp): offset0 is the counter's byte offset (index times 4) and the whole GDS address, offset1 bit 0 wave release, bit 1 wave done, bits 2-3 the shader type, bit 4 swap instead of add and bits 6-7 the dword count less one. M0 holds the ordered wave ID, not a GDS address as for ds_append (radeonsi passes TG_SIZE bits 6-17 as the intrinsic's pointer in si_compute_prim_discard.c and gs_tg_info bits 0-11 in gfx10_shader_ngg.c, with "The GDS address is always 0 with ordered append"); it only orders the waves, which is not modeled, so it is ignored. Each wave adds the value of its first active lane to the counter with one device-scope atomic and every active lane gets the counter's previous value (the ORDERED_APPEND_ENBL dispatch modifier is accepted for it). The hardware also serializes the waves of a dispatch in launch order, which is not modeled: the waves add in any order, so counters a title reads back as ordered offsets give the same totals but may hand out ranges in another order. Which lane's value is added is not documented; the first active lane is assumed and not measured on hardware. A swap (offset1 bit 4) stores the first active lane's value in place of the counter with one device-scope atomic exchange and returns the counter's previous value to every active lane; with the waves unordered, which wave's value the counter keeps and which previous value each wave gets need not follow the launch order the hardware gives. Counts of more than one dword and LDS forms throw. Covered by GdsAppendConsume.cpp.

  • s_swappc_b64 writes the link pair with the same model as s_getpc_b64: the guest return address pc + 4 as a byte offset over GetShaderBase. Any other use of the link pair (arithmetic, hash, store) throws, apart from the overwrites, SOPC compares and shared links that static scalar calls allow; constant-offset folding over the link is not modeled. The special encoding s_swappc_b64 with SDST 125 still decodes as s_setpc_b64, which matches the hardware: measured on RDNA2 (gfx1036) in wave32 and wave64, it branches to the source pair and leaves a marker SGPR pair untouched, and with an SGPR destination the link is PC + 4.

  • 2D biased sampling through a one-slice, single-level 3D T# supplies a zero third coordinate and retains the bias operation, with normalized point minification and magnification and no linear mip filtering. Measured on RDNA2 (AMD integrated gfx1036, driver 32.0.21036.18, Windows 11) by running an OpenCL-loaded native kernel with image_sample_b DIM 2D against 16x16x1 R32_UINT and RGBA8_UNORM 3D images: biases -16, -0.5, 0, 0.5 and 16 read the (u,v) texel in slice zero, including wrapped coordinates, point min/max reduction and the title's sampler with Z border addressing. Wave32, IEEE off, DX10 clamp on, denormals preserved, round to nearest even, FP16 overflow off; wave64 and the PS5's gfx1013 are not measured. Pixel-stage, direct, non-depth, unpacked samples whose only use is the plain 32-bit bias form are accepted; extra slices, any nonzero mip range, linear or anisotropic filtering and unnormalized coordinates throw. Vulkan readback is checked on NVIDIA RTX 5070 and AMD integrated graphics for positive and negative bias and point weighted-average/min/max reduction.

  • FORCE_DEGAMMA on RGBA8_UINT materializes a direct image sampled only by plain biased pixel accesses as RGBA8_SRGB when every paired sampler forces degamma. Measured on AMD integrated RDNA2 gfx1036 (driver 32.0.21036.18, Windows 11, wave32, IEEE off, DX10 clamp on, denormals preserved, round to nearest even, FP16 overflow off): a native image_sample_b DIM 2D on a 16x16x1 RGBA8_UINT 3D T# returns integer channels without the bit, but the same floating-point decoded values as RGBA8_UNORM with it, including point min/max reduction and the title's exact sampler. The metadata and image view must both become floating-point sRGB; ignoring the sampler bit or retaining a UINT heap is incorrect. The accepted case requires one slice, mip zero, normalized point sampling and uniform FORCE_DEGAMMA across its samplers. Other image accesses, mixed sampler states, indirect descriptors and other integer formats remain refused. Wave64, other filtering modes and the PS5's gfx1013 are not measured.

  • The s_swappc_b64 fetch-shader call at vertex/local/tessellation/mesh entry assumes the called fetch-shader blob ends in s_setpc_b64 to the link pair (the GNM fetch-shader shape shadPS4's EmitFetch copies); the blob is not part of the shader program, so its return closes the pairing by assumption and any in-program reader of the link pair throws. Attribute fetches are inlined from the request's parsed fetch-shader plan through the same GetAttribute path as the embedded fetch analyzer, honoring the buffer resource dst_sel; an attribute writing more than four registers, or an entry call without a parsed plan, throws. The vertex/instance offset accumulator inside the fetch blob is not replayed for external blobs.

  • The scalar aperture sources src_shared_base, src_shared_limit, src_private_base, src_private_limit (core/shader/recompiler, scalar source codes 235 to 238) read as the 64-bit apertures the flat routing uses: a base is the aperture's high dword (SharedApertureHigh, PrivateApertureHigh) over a low dword of 0, a limit is the same high dword over 0xffffffff. A 64-bit read (s_mov_b64, a 64-bit VALU source, a wave64 lane mask) gets the whole value and a 32-bit read gets the low dword. This follows LLVM's SIRegisterInfo.td (aperture registers are 64-bit, their low 32 bits are always 0 for a base and -1 for a limit) and getSegmentAperture (AMDGPULegalizerInfo.cpp, SIISelLowering.cpp: a 64-bit read whose high half is the aperture), the KFD's MAKE_LDS_APP_LIMIT (limit = base | 0xffffffff) and the gfx1030 and gfx1035 readouts on #1190 and #3803. Not measured on a console: the PS5's high dwords are the inferred values of the flat aperture bases entry, and the wave is taken to be in 64-bit address mode (PTR32 clear)

  • EmitSetAttribute (shader recompiler) - a primitive with a vertex whose clip position is (0,0,0,0) is clipped away through an extra gl_ClipDistance element on vertex and tessellation evaluation shaders, matching what a Radeon 610M (RDNA2, RADV) and llvmpipe rasterise; NVIDIA 615.78.08 without it draws the vertex at the viewport centre. Measured for triangles (strip and list) and for a point at (0,0,0,0), which the Radeon 610M does not draw, NVIDIA draws at the viewport centre and llvmpipe at the top-left corner; lines with such a vertex are clipped by the same element and were not measured. Mesh, geometry and the generated barycentric geometry stage do not write it, the generated rect-list and barycentric tessellation stages forward the vertex shader's array, and a vertex or tessellation evaluation shader whose guest clip and cull distances already take all eight does not write it either, so NVIDIA 615.78.08 draws such a vertex at the viewport centre; a device without shaderClipDistance cannot compile vertex shaders that write it (the compile throws). The PS5 rasteriser was not measured.

  • PERSP_SAMPLE and LINEAR_SAMPLE (recompiler) are the barycentrics at the iterated sample, which in a pixel shader the guest does not iterate is taken to be sample 0, inferred from the ancillary's iterated sample number and not measured. They are interpolated with InterpolateAtOffset, which GLSL_EXT_fragment_shader_barycentric names for barycentrics away from the default position, at the sample's offset from the pixel centre, read per pixel of the 2x2 quad from PA_SC_AA_SAMPLE_LOCS (the standard pattern unless the draw sets its own): sample 0's in a shader the guest does not iterate, and in an iterated shader the offset of the host's SampleId from a table of the guest's locations, because Vulkan leaves the position of plain barycentrics under sample shading implementation-defined (lavapipe uses the pixel centre). That needs the device's sampleRateShading; without it, and when every location is the pixel centre (a single-sample draw), they keep the host's default position, the pixel centre once per pixel and an implementation-defined position under sample shading. PERSP_CENTER and LINEAR_CENTER are interpolated at offset 0 in an iterated shader, so they stay at the pixel centre as on the console; the centroid pairs keep the host's default position. With barycentric emulation the offsets apply to the emulated barycentrics. Devices that interpolate attributes on the host instead (no usable fragment-shader barycentrics, as on MoltenVK) keep the default position for all four pairs.

  • Pixel positions (recompiler) - POS_X_FLOAT and POS_Y_FLOAT follow SPI_BARYC_CNTL.POS_FLOAT_LOCATION (bits 17:16) and POS_FLOAT_ULC (bit 20), whose positions come from PAL's GFX9+ register header and whose meanings from LLPC: the pixel centre (0), the iterated sample (2), or the upper-left corner (ULC). Under host sample shading the centre is floor(FragCoord) + 0.5, and a non-iterated shader's sample is the centre plus sample 0's offset. The centroid (1) keeps FragCoord, the pixel centre or under sample shading the sample, not the centroid of the covered samples. A missing SPI_BARYC_CNTL reads as the pixel centre and the reserved location 3 throws. POS_Z_FLOAT and POS_W_FLOAT keep FragCoord's depth and 1/w at the host's position. PERSP_CENTER_CNTL, PERSP_CENTROID_CNTL, LINEAR_CENTER_CNTL and LINEAR_CENTROID_CNTL (bits 0, 4, 8 and 12) are not read: PAL and LLPC never set them.

  • LINE_STIPPLE_TEX (recompiler) reads as 0: line stipple is not emulated. RADV enables the input only as the required pixel input of a shader that reads no interpolants, because it initializes the fewest VGPRs (Mesa radv_compute_spi_ps_input), and never reads it

  • Static scalar calls (s_call_b64 and s_swappc_b64) require an ordinary even-aligned SGPR link pair (s0:s1 through s104:s105) and a later s_setpc_b64 return reading that exact pair. Outside every region of its calls the pair is free to use wherever no return address can reach: a dataflow over the decoded instructions follows each dword of the pair from the calls that write it until a scalar load, SALU or VALU result is known to overwrite it, and an instruction there that may read a dword still holding a return address throws; SOPC compares are exempt, as they only set SCC. A VALU mask covers one SGPR in wave32 and a pair in wave64. When the graph is built without the wave size, the dataflow runs for both: a program that escapes under both throws there, and one that escapes under only one throws when it is translated at that size.

  • Static scalar call link pairs each serve one call, or several calls whose regions are disjoint, each call closed by the first return reading the pair after its target. Regions must nest or be disjoint; recursion, other shared links, escaping link values (including overlapping tuples and resource descriptors), invalid targets, interleaved regions, cross-region branches or fall-through, relative SGPR reads and dynamic s_setpc_b64 in a program with calls throw. The analysis checks all decoded instructions, including unreachable code. Arbitrary indirect calls and general call frames are not implemented.

  • Relative vector addressing (v_movrels_b32, v_movreld_b32, v_movrelsd_b32, v_movrelsd_2_b32, v_swaprel_b32) keeps every vector register from the lowest base register the program addresses relatively up to the last register the program names in zero-initialised function-scope storage per invocation (LoadVectorWindow/StoreVectorWindow, one element per register), so an indexed access is one load or store instead of a compare-and-select chain over the window. IndexedWindowSlots bounds each relative index through the IR (bit-field extracts, masks, shifts, adds, selects, phis, ReadFirstLane and the window stores that reach a load); the storage is an array of the slots below the largest bound that is under half the window, and one function variable per slot after it. A constant index uses the array element or the slot's variable; a relative access whose index has no such bound, or lands past the array at run time, indexes the array below its size and otherwise branches through an OpSwitch over the slot variables, so the bound only decides speed. When no index has a bound under half the window, the whole window is the array, as with APS5_NO_WINDOW_NARROWING=1 (a recompiler switch in the shader cache key). Measured on an NVIDIA GeForce RTX 3060 (driver 615.78.08, Linux) with a compute program whose 167-register window an ALU loop reads and writes in full and whose relative accesses take m0 = s8 & 3: a 2048-group dispatch took 70.2 ms in wave32 and 123.3 ms in wave64 with the whole window in the array (as before narrowing) and 1.9-2.1 ms and 7.8 ms narrowed; with a second relative access whose index has no bound and lands past the array at run time (index 50 or 166), 113 ms and 5.8-6.0 ms in wave32. In the PPSA08427 (Killing Floor 3) hub it made no measurable difference to presents or to the GPU time of the eight most expensive programs. Whether the host compiler keeps the array and the slot variables in registers or spills them to scratch is not measured. A register in the window the program never wrote reads 0, as an unwritten SSA register does; the console's value is undefined. M0 is unsigned: an index at or past the window reads v0 and drops its write, and v_swaprel_b32 writes neither side when either is out of range, as measured on RDNA2 (gfx1035, agc_driver_relative_indexing). The window ends at the last register the program names rather than at the shader's VGPR allocation, which the recompiler does not receive, so a relative write past the named registers but inside the allocation is dropped where the hardware keeps it for a later relative read. Killzone: Liberation's PSP vertex program (PPSA11142, 3176 ISA words, 104 relative accesses over 154 registers) went from 649,515 to 392,333 SPIR-V words with this. Pixel programs whose relative span contains the packed ancillary input instead keep that span in SSA and implement indexed operations with compare/select chains. This keeps ancillary definitions visible to field lowering and masked-write proofs; storing the unmodelled whole word in a private array would require composing unknown bits. A fold after masked-select elimination lowers fields whose consumers become visible only after redundant copies are removed. A whole-word use that the masked-select pass keeps gets the composed word described under the packed pixel ancillary word. Both representations include implicit entry inputs in the inferred register limit and retain unsigned M0, inactive-write and DPP update rules; wave32/wave64 native Vulkan readbacks cover relative reads, writes, split-offset swaps, inactive writes, DPP masks and out-of-range accesses, but console comparison and the performance cost of selecting over this span are unverified.

  • s_call_b64 saves PC + 4 over GetShaderBase and branches to PC + 4 + signext(SIMM16) * 4, following AMD RDNA2 ISA sections 3.6.3, 12.2 and 12.3. Forward callees after s_endpgm are decoded. Measured on RDNA2 (gfx1036) in wave32 and wave64 with SIMM16 = 3: the link pair receives PC + 4, the branch goes to PC + 4 + signext(SIMM16) * 4, and s_setpc_b64 of the link returns to the instruction after the call. Execution on the PS5 (gfx1013) and title compatibility for these bounded scalar calls remain unverified; s_rfe_b64 still throws in translation.

  • BDA anchor halfword reads retain volatile byte loads even for noncoherent memory. On RX 6650 XT / RADV Mesa 26.2.4, the valid SPIR-V for two adjacent nonvolatile byte loads returned 0x80a0 for bytes 0xa0, 0xa1, where lookup-based reads returned 0xa1a0. Adding the volatile operand makes the same GPU exactness fixture pass, including mixed direct/fallback lanes. The driver transformation responsible has not been isolated; removing this restriction needs equivalent readback coverage on the affected driver. Single-byte reads keep their original access mask.

  • BDA span reads (shader recompiler) - read_bda_span is one selection: one arm calls load_bda_span, a DontInline function that holds the in-place dword loads and calls nothing, and the other arm is the per-dword byte fallback. Two other shapes of the same reads each hang a group of NVIDIA drivers, where a dispatch never finishes and the device is lost. With the loads emitted in the arm itself, a dispatch whose lanes took both arms hung on an RTX 3060 and an RTX 4060 Laptop GPU (Windows, driver 591.86) and an RTX 5060 (Linux, driver 590.48.01) (#3291). With the loads and the fallback as two selections, the first under the span's condition and the second under its negation, it hung on an RTX 4070 Laptop GPU (Windows, drivers 595.97 and 596.36), an RTX 5080 (Windows, driver 610.62) and an RTX 4090 (Linux, driver 610.57.04) (#4975). The current shape is measured on the RTX 4090 with driver 610.57.04 only, where it does not hang with or without SPIRV-Tools; a shape with the loads in their own DontInline function was reported passing on driver 591.86 in #3291. The cause inside the driver is not known.

  • SDWA source selects on v_cvt_f32_ubyte1, v_cvt_f32_ubyte2 and v_cvt_f32_ubyte3 (shader recompiler) - byte N is taken from source 0 after SRC0_SEL and SRC0_SEXT select and extend it, since SDWA supplies source 0 (RDNA 2 ISA guide 6.8 and 13.3.7) and the opcode reads S0.u[8N+7:8N] (12.8). Not measured on hardware.

  • Flag structurization (shader recompiler) - a graph that every structurizer attempt refuses, irreducible and non-terminating graphs included, is rebuilt with one reach flag per block and one again flag per loop (structurizeWithFlags): each block keeps one copy and runs behind a guard on its flag, and each strongly connected component becomes a loop whose single latch repeats it while a back edge set its flag. The output has about 8 times the input's blocks and one route variable per block, and ends in an empty Return that never runs. Only blocks that start the top-level order unconditionally run unguarded, so resource planning also sees the static paths that skip a guarded block and can plan extra descriptor candidates for them. The attempts still run first. Shared merge splitting refuses an attempt once the graph has grown past 16 times its block count at the start of splitting (at least 64 blocks); succeeding attempts grew at most 10.6 times on random graphs of up to 128 blocks, and an attempt that would succeed only beyond the budget leaves the graph to a later attempt or to this construction. Splitting that adds one block per round stays within that budget until its 4 rounds per block run out, so the attempts can still take more than 10 s (9 of 2,000 random graphs of up to 128 blocks, 63 s at most). APS5_FORCE_STRUCTURIZER_FALLBACK=1 builds every graph this way, for tests

  • Scalar buffer descriptors use only the base address, stride and record count for resources accessed exclusively by scalar loads; their fourth word is normalized to zero, following AMD RDNA2 ISA section 7.2.2. Vector and mixed accesses retain descriptor validation. Native Vulkan readbacks cover ignored fourth-word fields on Windows/NVIDIA; their console behavior has not been measured.

  • Spanned wave64 compute (shader recompiler) - APS5_SPANNED_WAVE=1, off by default, runs a single-wave wave64 compute program at one lane per invocation on a 32-lane host subgroup and exchanges ballots, shuffles and reductions between the two host subgroups through workgroup memory and barriers. The driver turns it on only where compute subgroups can be pinned at 32 full lanes (subgroupSizeControl and computeFullSubgroups), passes it as SpirvTarget::spannedWaves (part of the cache key and the serialized request), and creates the pipelines of spanned programs with REQUIRE_FULL_SUBGROUPS, since the lane number SubgroupId * 32 + SubgroupLocalInvocationId needs two full subgroups. Those barriers are correct only where every invocation of the workgroup reaches them, which holds for the guest's wave-uniform control flow. The emulated 64-bit LDS atomics (LockedLdsUpdate) run only on the lanes the exec mask keeps, so their lock loop ballots within its own host subgroup, as its elect already does. Any other cross-lane exchange emitted inside per-lane control flow would hang a spanned wave in the same way, and the emitter does not reject one: there is no uniformity check. The only check so far is manual: disassemble the module (spirv-dis), list the selection and loop constructs that enclose each OpControlBarrier, and read the condition of each enclosing selection. In Ghost of Yōtei's program 0x80003af600 (PPSA26344), 108 of 111 barriers sat only in loops on wave-uniform conditions and 3 inside per-invocation conditionals, all in the lock loop. Other programs and titles have not been scanned. An earlier, cheaper ballot (one barrier instead of three) also lost the device for a reason that is not understood, which may be another barrier placement problem. Measured on an RTX 4090 (Windows): an offline replay of that program and two Yōtei runs that previously lost the device (NVIDIA TDR) now run past it, and LdsMaskWaterfall.cpp runs both modes and covers a half-inactive wave. Stray (PPSA02101) on Intel ANV and a Demon's Souls (PPSA01342) request replay showed no fault, before this fix

  • Guest memory load guards (shader recompiler) - a guest load whose lane is inactive or whose address is out of bounds is still performed and its result replaced by 0 with OpSelect, instead of a selection per guard, when the load can be pointed at a location nothing writes: scratch reads its own element 0; storage buffers (direct buffers, GDS) read element 0xfffffffe, which robustBufferAccess2 turns into a zero load with no memory access, so the extra read cannot race with a write the guarded program would not have raced with (Vulkan memory model). Without robustBufferAccess2 (VulkanDevice.cpp enables it when the device has it) those loads keep their selections; LDS loads, stores, atomics, formatted and physical-address (BDA) accesses always do. LDS keeps the branch because NVIDIA's compiler predicates a branched LDS load at no cost, while the select form costs about 2.5 instructions per dword and splits a ds_read_b128 into four loads (RTX 4060 measurements by @karanvora2599 in #4048); Intel's static counts favour the select only for wide reads, and run time was not measured on either. The aligned high word of a misaligned-capable buffer read re-reads the low word's own element. A selection guard costs four blocks per lane half and the host compiler's time grows with the block count: PPSA11142's merged PSP vertex program (3176 ISA words, wave64) emitted 16,983 blocks and 1,710 conditional branches, 5,907 and 868 with the selects. agc_guarded_memory_loads traces the emitted loads in both device modes.

  • Merged hull programs (registered binary type 3) run whole in the hull stage, one invocation per output control point. On the console hull lane i keeps local lane i's registers, which with fewer input than output control points stop matching the patch's control points from i = inputs on; here invocation i holds control point min(i, inputs - 1) of its own patch. A wave-mask branch (s_cbranch_exec[n]z, s_cbranch_vcc[n]z) tests the invocation's own lane, so it can skip a block, scalar instructions included, that the console runs for the wave; v_readfirstlane_b32 returns the invocation's own value, not the first active lane's, and another lane's register throws. The register layout (s3, v0-v5, as for the separate halves) is not measured on hardware. s[0:1] hold the hull user-data address (SH 0x102/0x103) when a draw sets it and zero otherwise, so a program reading it unset faults at the null address.

  • Wave LDS ordering on AMD's proprietary driver (shader recompiler) - there (SubgroupBarrier.hpp) one guest wave's LDS accesses are ordered by a subgroup-scope OpMemoryBarrier with no control barrier, relying on a 32-lane subgroup being one hardware wave whose lanes run in lockstep and issue LDS instructions in order, which Vulkan does not guarantee; for LDS kept in device memory the same is assumed of global loads and stores, unmeasured. Driver 32.0.21042.62 (Radeon 0x13c0, RDNA 2) compiled the subgroup-scope OpControlBarrier to s_barrier, which waves in a branch paired with other waves' barriers (PS5LM Plus). Measured on that driver only (LdsTreeReduction.cpp); a Radeon 860M on 32.0.13058.8001 reportedly did not show the fault.