A deferred module that only another deferred module names (PSNCommon, needed by
PSNCore) started after the plugins that use it (SPFXUnityPlugin) and crashed
Soul Hackers 2 on Windows. The first title load of any deferred module now starts
every deferred module some module depends on, in dependency order, with that
load's start arguments, and logs the sweep. Boot is unchanged.
On Windows a module the executable needs or imports from stays deferred, but its
dependencies were deferred too and started only when the title loads it, so a title
that calls it first ran them uninitialised (Soul Hackers 2, #15370 rerun). They start
at startup again, as on main. The loader prints one line per deferred start.
On the x86_64 macOS relinker job (emulated on an arm runner) the test took 11 to 20 s against its
20 s limit. Over the main runs of 4a7770bf54 to 66387a10ee, 20.04, 20.07, 20.03 and 20.07 s timed
out and 19.99, 19.99 and 19.33 s passed, while the native arm64 job takes 1.3 to 5 s. The test is a
loop of relinker subprocess runs over sce False and True. It now takes "plain", "sce" or "all" (the
default, so the importing tests are unaffected), and CMake registers guest_none_relocations (plain)
and guest_none_relocations_sce, each with the same 20 s limit and half the work.
Since #12621 a Windows runtime module that another runtime module needs is deferred instead of started at boot, but loading a deferred module only started that module. A Unity plugin such as SPFXUnityPlugin that calls into a deferred module it needs (Il2CppUserAssemblies in Media/Modules) ran against code whose initializers had never run, and PPSA03743 crashed about 2.6 s in.
A deferred module's initialization table now lists the deferred modules it depends on (names, after the initializer array, behind a magic number so older output reads as having none). DynamicLoader starts those first, transitively and once each through the existing per-module state, with the start arguments of the load that began it, then starts the module itself. The module start result stays the module's own. Boot still starts no deferred module.
Linux is unchanged: its deferred modules still start only when loaded.
Reapplies the code, tests and USAGE.md change of 420b6a064e (#12621 by Ragbie), which commit 987ebe9855 (#15360) reverted because Soul Hackers 2 (PPSA03743) crashed on Windows: SPFXUnityPlugin calls into Il2CppUserAssemblies, which #12621 defers, and loading a deferred module did not start the deferred modules it needs. The next commit makes the loader start them, so the deferral and the start come back together.
Model a module whose initializer dispatches its own preinit callback and assert the exact once-only callback, preserved arguments and return value. Retain zero-address and no-preinit controls and cover deferred Linux initialization.
Regression coverage for #7636, introduced by #6112 and reverted by #7646. The new test fails with the actual historical bad relinker at 376c040 and passes on main 9090972.
AI-assisted: yes. Independently reviewed staged patch 6e437226c361065dad96478101fc899f65cde86caf51a39415369c01c1725168.
This reverts the code, tests and USAGE.md change of commit 420b6a064e
(#12621). The docs/dev/debt/relinker.md line is left for the follow-up
that brings the deferral back with dependency start, to keep a running
batch that edits the same file valid.
Soul Hackers 2 (PPSA03743) stops about 2.6 s into boot on Windows with an
access violation on UnityGfxDeviceWorker inside SPFXUnityPlugin.prx since
this change. klosixgaming-arch bisected main's merge commits to 420b6a064e
and confirmed that undoing its dependency filter makes the title boot again
(https://github.com/boykopovar/AnyPS5/pull/12621#issuecomment-6587462742).
SPFXUnityPlugin (Media/Plugins) calls into Il2CppUserAssemblies
(Media/Modules). With #12621 that dependency is deferred, and loading a
deferred module does not start the deferred modules it needs, so it is
never started. The revert restores the previous Windows behaviour, where a
module another module needs starts at startup.
Emit a standard DT_GNU_HASH table (0x6ffffef5) alongside the SysV DT_HASH table in LinuxElfPatcher::Patch(), and only insert defined symbols (st_shndx != SHN_UNDEF) into both hash tables.
When the executable exports defined symbols, sort the contiguous defined tail of .dynsym by GNU hash bucket before constructing both DT_HASH and DT_GNU_HASH so both tables record the final post-sort symbol indices.
AI-assisted: yes (Antigravity)