sceSystemServiceLoadExec found its own executable through /proc/self/exe
and replaced the process with fexecve after close_range, none of which
macOS has, so on macOS every call except "exit" threw. On macOS:
- the running executable's path comes from _NSGetExecutablePath, resolved
with std::filesystem::canonical, as libSceNpEntitlementAccess and
libSceFont already do;
- the target must be an x86-64 Mach-O executable (MH_MAGIC_64,
CPU_TYPE_X86_64, MH_EXECUTE) that the user may run;
- macOS has no fexecve, so the process is replaced by path with
posix_spawn and POSIX_SPAWN_SETEXEC; POSIX_SPAWN_CLOEXEC_DEFAULT closes
every descriptor but the standard streams, as close_range does on Linux;
- before the replacement the exit starts and the shutdown callbacks run
(LibcStartExit, then LibcRunShutdown), as on Linux, so guest threads
that call a stopped library wait instead of unwinding;
- descriptor 2 gets the host standard error back before the replacement,
as on Linux, and guest_system_load_exec_standard_error runs on macOS.
A launch without arguments keeps the Linux execv path, which macOS has.
guest_system_exit finds its own executable the same way on macOS.
Every path resolution reaches the app0.zar state, which was a
function-local static: it is destroyed during exit before the handlers
registered ahead of its first use run, so a path resolved in such a
handler locked a destroyed mutex. glibc lets that pass; macOS fails it
with EINVAL, and guest_path_lifetime failed there. The state is now
allocated once and never destroyed, as the working directory state
beside it already is.
The new samplerCoordinatesIgnored field adds 40 bytes to DescriptorBinding
under libstdc++ on Linux x86_64, where the size assert is compiled. The
encoder and decoder already carry the field.
AI-assisted: yes
An S# that forces unnormalized coordinates was refused when it samples a
T# the shader declares 3D, 2D-array, cube or multisampled, since Vulkan
allows unnormalized coordinates only with a single-level 1D or 2D view.
A null T# reads zeros at any coordinate, and a T# whose channels all
select constants is emitted as those constants without a sample, so when
the S# samples no other T# its coordinates cannot change a result. Such
an S# is now marked in its binding (samplerCoordinatesIgnored) and the
driver creates its sampler without FORCE_UNNORMALIZED. The flag is part
of the binding content key and of the shader disk cache, whose format
version is raised to 38. An S# that also samples a real T# while a null
T# of such a shape shares it is still refused. PPSA01317 pairs an
unnormalized clamped bilinear S# with a null 3D T# and a 3D T# whose
channels all select 0 for image_sample_lz in a compute shader during
boot.
AI-assisted: yes
Specialization parsed every instruction of the prepared module into
its own std::vector, so a module of n instructions made n allocations
when it was read and n frees when the specialization ended. Parsed
instructions now take their words from a WordArena the Specialization
owns, through an ArenaAllocator whose first chunk is the size of the
module's words, so parsing allocates about once and the arena frees
its chunks when the specialization ends. An allocator without an arena
(Make's results and other temporaries) uses std::allocator as before,
and assigning one to an instruction copies it into the arena. The
specialized module is unchanged.
This is a plain allocator rather than std::pmr because Apple's libc++
marks std::pmr unavailable before macOS 14.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SpirvModule's interned type and constant lookups probe a 512-slot cache
linearly from the key's slot. Once a module has declared more than 512
of them, every slot is taken, so a key that is not cached probed all
512 slots before falling back to declarationIds, and then overwrote
the last slot it probed. A lookup now probes at most eight slots and,
when none of them is free, replaces the key's own slot. The cache only
memoizes declarationIds, which allocates every id, so the module is
unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a device without VK_EXT_depth_range_unrestricted the target remaps every
vertex shader, so an in-range draw carries the remap's push block too. Draw
it from a direct and from a prepared build; both must validate and store the
right depth.
fopen and freopen in an append mode gave the C runtime a descriptor with
_O_APPEND, which seeks to the end of the file and then writes. Streams on
separate descriptors overwrote each other's data. FreeBSD opens these
streams with O_APPEND, so every flush lands at the end of the file.
Open append streams with FILE_APPEND_DATA but without FILE_WRITE_DATA,
so Windows writes each flush at the end of the file. ftruncate and
flock reopen the file to get the access such a handle lacks.
The selected-records limit tests only read through image tables, so
nothing showed that the estimate counts direct images and samplers too.
TableManyKeyCode can now also load T#s straight from the SRT and sample
them, with the same samplers as the table reads and optionally a third
sampler on the last one.
agc_shader_selected_records_direct_image_limit:
- 63 selected reads and 1 direct image, 64 images: the per-key split is
kept (63 selected tables and the direct image).
- 63 selected reads and 2 direct images, 65 images: over the limit only
because of the direct images. Heap A's reads merge into one unselected
table beside the 2 direct images (3 images, 3 pairs), and keys 2, 33
and 64 still bind their records.
agc_shader_selected_records_direct_pair_limit:
- 31 selected reads and 1 direct image, each with 2 samplers, 64 pairs:
the split is kept.
- The same with a third sampler on the direct image, 65 pairs: over the
pair limit only because of the direct sampler. The reads merge into
one table (2 images, 5 pairs), and keys 2, 17 and 32 still bind.
With ResourceTracker.cpp as on main 9caecfd2d5, the over-limit cases fail
with "image resource limit exceeded" and "sampled image/sampler pair
limit exceeded", and the cases within the limits pass.
The listener read event now reports one connection after the first connect, and on Linux two once a second client is waiting, before both are accepted.
A read event on a listening socket took its data from FIONREAD, which a listening socket refuses on Linux, so kevent and sceKernelAddReadEvent reported 0 connections while saying one was ready. FreeBSD 11.4 gives a listening socket filt_solisten, which reports so_qlen, the completed connections waiting for accept. Ready now checks SO_ACCEPTCONN and on Linux reads a TCP listener's accept backlog from TCP_INFO, where tcp_get_info puts it in tcpi_unacked; with no host call for the count (Windows, macOS, a listener that is not TCP) it reports 1, the lower bound the readiness already implies.
Since #6243, each read whose record offset the CPU evaluates gets its own
selected table column, so a shader that reads one heap at many such keys
plans one image per key. Past 64 keys that is refused with "image resource
limit exceeded", or with "sampled image/sampler pair limit exceeded" when
each read is sampled twice, where the shader planned within both limits
before.
After planning the table columns, the tracker now estimates the images
and sampled pairs that collecting the program would add, keyed as
AddImage and AddSampledPair key them, from the planned column sources
and the direct handles. If either is over its limit, it plans again with
the columns of one heap unselected, the heap with the most selected
columns first, until both fit or no heap is left. Those reads then share
one keyed column, as before #6243: the union of their key domains and the
largest key. A shader within both limits plans exactly as before, from
one planning pass.
The estimate counts images exactly. A direct sampler is told apart by its
handle's words and whether the sample adjusts, before the canonicalization
collecting it applies, so the pair count can only be too high and errs
toward merging.
A device that cannot sample R8_SRGB or R8G8_SRGB had its sRGB textures
decoded in the shader, which cannot serve filtered samples: the decode must
come before the filter. The texture is now an RGBA8 UNORM image viewed as
RGBA8 sRGB, filled by a one-to-one blit of the R8 or RG8 texels (a snapshot's
upload, or a storage image's texels refreshed when its Version changes), so
the sampler decodes and then filters, and every sample, gather and load of
these images is an ordinary sRGB one. The recompiler is given the decode mask
only for formats the device cannot blit, or when APS5_SRGB_SHADER_DECODE=1
forces the shader decode. Tests run on lavapipe only.
Every IR value kept two lists of its users: uses (the users) and
operandUses (the users with their operand index). AddArgument,
ReplaceArgument and ReplaceAllUsesWith updated both, and each value
allocated and freed both. Only operandUses is kept now. Uses() is a view
of the users in operandUses, and HasUses, UseCount and Invalidate read
it. sizeof(IrValue) drops from 152 to 128 bytes; the plan value encoder
writes no use lists, so only its size check changes.
The two lists held the same users in the same order, except after
ReplaceArgument on a user that took the value in more than one
operand: uses dropped the user's first entry and operandUses the
replaced operand's entry. Uses() now follows operandUses there too.
shader_ir_use_rewrite counted the allocations of both lists: its
allocation failure check now fails the one reserve ReplaceAllUsesWith
makes, and its growth check allows the allocations of one list, which
main exceeds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every IR value grew its argument vector one AddArgument at a time from
empty, so a value with three arguments allocated three times (capacity
1, 2 and then 4) and freed the first two. The constructor now reserves
IrOpcodeOperandCount(opcode) arguments, the count IrBuilder::Emit
already requires, so a value built with its opcode's arguments
allocates once. Opcodes without fixed arguments (constants, phis)
reserve nothing, as before. The IR and the SPIR-V are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The SPIR-V interface check only allowed a push constant block for a program
with push data of its own, so a remapped vertex shader without any was
rejected as having an invalid push constant interface.
minMaxF16Bits compares the halves as ordered integers (the low 16 bits
sign-extended, with the magnitude bits of a negative half inverted),
then replaced the result when both halves were zero: max took a & b and
min took a | b. The ordered integers already put -0 (-1) below +0 (0),
so the compare picks +0 for max and -0 for min, which is what the
both-zero select produced. Every caller passes halves with their upper
16 bits clear (readF16Bits masks them, the packed path extracts them),
and for those all 2^32 pairs of each give the same bits with and
without the select. Each f16 min, max, min3, max3 and med3 now emits
five instructions fewer per pair.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The flag that adds the vertex stage to the push constant stages was not part
of the encoded result, so a journalled remapped vertex shader replayed
without it. Encode and decode it with the invocation and cover it in the
result round trip and the push stage test.
APS5_CRASH_STACK_BYTES=<n> prints n bytes of stack words from rsp after the
stack return address candidates, then eight words at every distinct stack word
that points at readable, non-executable memory outside the thread's stack.
Off when the variable is unset, empty or 0.
Windows refuses to start the image with a 0x7fe00000 gap (CreateProcess
WinError 193, SizeOfImage 0x7fe3e000), with or without this change. The
far image still checks the PE layout and the macOS conversion; native
Windows execution now uses a fixture whose segment is 64 MiB away.
Reported by simonbronk in the review.