diff --git a/app/Madeira/ConfigCatalog.generated.swift b/app/Madeira/ConfigCatalog.generated.swift index 3e88905389..a956830f2a 100644 --- a/app/Madeira/ConfigCatalog.generated.swift +++ b/app/Madeira/ConfigCatalog.generated.swift @@ -42,10 +42,11 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_FRONTEND_DEFAULT_NEW", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "Which interface Madeira starts with: the library (the default) or the developer interface. The choice is made in either interface, stored in UserDefaults and read once per run, so a change applies at the next start. Without a stored choi...", choices: [], sources: ["app/Madeira/Library.swift"]), ConfigOption(key: "env.MADEIRA_FRONTEND_KEYBOARD", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "MADEIRA_FRONTEND_KEYBOARD=0: the game view's own keyboard instead.", choices: [], sources: ["app/Madeira/Library.swift"]), ConfigOption(key: "env.MADEIRA_GESTURE_ZONES", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "A swipe zone turns a swipe into a short press of one input per direction (D-pad by default), optionally limited to horizontal or vertical swipes, and a tap into another. - A look zone moves the mouse by the finger's motion, like a trackp...", choices: [], sources: ["app/Madeira/GestureZones.swift"]), + ConfigOption(key: "env.MADEIRA_GL_EXTENSIONS_MAX", title: "", kind: .text, defaultValue: "", category: "App & front end", note: "byte limit of GL_EXTENSIONS in 32-bit OpenGL contexts (opengl32, patches/wine-opengl-winios.patch): unset = 3072, 0 = none; d3d9 = wined3d makes it 0 unless set", choices: [], sources: ["app/Madeira/WineProcessBridge.m"]), ConfigOption(key: "env.MADEIRA_GOG", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "Off with `env.MADEIRA_GOG = 0`.", choices: [], sources: ["app/Madeira/GOGLibrary.swift"]), ConfigOption(key: "env.MADEIRA_GOG_GALAXY", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "GOG Galaxy's game services (sign-in, achievements, stats, leaderboards) for GOG games, answered by Madeira on 127.0.0.1:9977 while one runs (docs/GOG.md, \"Galaxy\"); 0 turns them off, in madeira.cfg or in one game's own config", choices: [], sources: ["app/Madeira/SwiftGOG/GOGGalaxy.swift"]), - ConfigOption(key: "env.MADEIRA_ITCH", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "Off with `env.MADEIRA_ITCH = 0`.", choices: [], sources: ["app/Madeira/ItchLibrary.swift"]), ConfigOption(key: "env.MADEIRA_GRAPHICS_SETTINGS", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "0: no Settings › Graphics section; d3d9, d3d11, d3d12 and vulkan are back in All settings", choices: [], sources: ["app/Madeira/Library.swift"]), + ConfigOption(key: "env.MADEIRA_ITCH", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "Off with `env.MADEIRA_ITCH = 0`.", choices: [], sources: ["app/Madeira/ItchLibrary.swift"]), ConfigOption(key: "env.MADEIRA_JIT_SHORTCUT", title: "", kind: .bool, defaultValue: "0", category: "App & front end", note: "Settings › JIT › Madeira JIT shortcut: when LocalDevVPN's loopback does not answer, Enable JIT runs the shortcut. Off unless it is 1.", choices: [], sources: ["app/Madeira/JITNetwork.swift"]), ConfigOption(key: "env.MADEIRA_JIT_SIGNING_CHECK", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "Explains why JIT cannot be enabled on a copy signed without get-task-allow; 0 opens StikDebug regardless. A debugger can attach only to a process whose signature carries get-task-allow (a development signature). A copy signed with a dist...", choices: [], sources: ["app/Madeira/ContentView.swift"]), ConfigOption(key: "env.MADEIRA_LAUNCH_EARLY", title: "", kind: .bool, defaultValue: "1", category: "App & front end", note: "0: Play keeps the Game details page up until the session begins", choices: [], sources: ["app/Madeira/Library.swift"]), @@ -338,6 +339,7 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_DXGI_BUDGET_EVENTS", title: "Tell the game when its video memory budget changes", kind: .bool, defaultValue: "0", category: "Direct3D 9/10/11 (DXMT)", note: "Read by DXMT's dxgi with the budget-event change. The video memory budget DXGI reports shrinks as Madeira nears its memory limit (vram-trim-mb below it). 1: a game's budget-change event is registered for real, signalled at once and again whenever the budget moves by 32 MB, so a game that waits for it (Red Dead Redemption 2) reads the smaller budget and frees textures. Off (default): the registration is refused, as before. Set it in the game's own file; read at session start.", choices: [], sources: ["dxmt/src/dxgi/dxgi_adapter.cpp"]), ConfigOption(key: "env.MADEIRA_DXMT_SCHED_FIFO", title: "", kind: .text, defaultValue: "", category: "Direct3D 9/10/11 (DXMT)", note: "no thread handles exist here; refuse rather than lie", choices: [], sources: ["dxmt/src/util/util_madeira_compat.h"]), ConfigOption(key: "env.MADEIRA_FRAMEGEN", title: "", kind: .text, defaultValue: "", category: "Direct3D 9/10/11 (DXMT)", note: "3. runs MetalFX's frame interpolator (MTLFXFrameInterpolator, iOS 26) on the two frames, that motion field and a flat depth, 4. presents the generated frame at once and the real one half a frame later (presentDrawable:atTime:). The panel...", choices: [], sources: ["dxmt/src/winemetal/unix/winemetal_unix.c"]), + ConfigOption(key: "env.MADEIRA_FRAMEGEN_ONE_LAYER", title: "", kind: .text, defaultValue: "", category: "Direct3D 9/10/11 (DXMT)", note: "", choices: [], sources: ["dxmt/src/winemetal/unix/winemetal_unix.c"]), ConfigOption(key: "env.MADEIRA_IDX_REALIGN", title: "", kind: .text, defaultValue: "", category: "Direct3D 9/10/11 (DXMT)", note: "madeira-bcd: realigned 16-bit indices -- reserve argument-buffer room for a 16-bit draw whose index offset is not 4-byte aligned; 0 = not needed.", choices: [], sources: ["dxmt/src/d3d11/d3d11_context_impl.cpp"]), ConfigOption(key: "env.MADEIRA_MIP_CLAMP_AUTO", title: "", kind: .text, defaultValue: "", category: "Direct3D 9/10/11 (DXMT)", note: "", choices: [], sources: ["dxmt/src/d3d11/d3d11_texture_device.cpp"]), ConfigOption(key: "env.MADEIRA_PRESENT_LIMITER", title: "", kind: .text, defaultValue: "", category: "Direct3D 9/10/11 (DXMT)", note: "sleeps to an absolute deadline on the same timebase the scheduler uses, so it does not accumulate the per-iteration error a relative nanosleep ladder does and it does not quantise to a timer tick -- the failure mode the brief calls out, ...", choices: [], sources: ["dxmt/src/winemetal/unix/winemetal_unix.c"]), @@ -364,6 +366,7 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_FAULT_GUARD_RATE", title: "Fault-storm guards end only a real storm", kind: .bool, defaultValue: "0", category: "Exceptions & threads", note: "Default off. 1: a run of 2000 faults at one instruction ends the game only when it is dense (inside half a second) or presenting makes no progress, and the 5-repeat guard counts per thread within 5 ms; for a Unity title that throws managed exceptions through null-check traps while it keeps drawing. Read once per session.", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), ConfigOption(key: "env.MADEIRA_FAULT_LOOP_GUEST_PROGRESS", title: "A run of breakpoints at different addresses is not a stuck thread", kind: .bool, defaultValue: "0", category: "Exceptions & threads", note: "Default off. 1: the loop breaker (50 faults at one place end the thread) no longer counts int3 traps at different guest addresses as one stuck fault, though the emulator raises all of them from one host instruction. Logged as [fault-progress]. Read once per session.", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), ConfigOption(key: "env.MADEIRA_FAULT_OPERANDS", title: "", kind: .text, defaultValue: "", category: "Exceptions & threads", note: "", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), + ConfigOption(key: "env.MADEIRA_GUEST_CTX", title: "", kind: .text, defaultValue: "", category: "Exceptions & threads", note: "", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), ConfigOption(key: "env.MADEIRA_JIT_DUMP", title: "", kind: .text, defaultValue: "", category: "Exceptions & threads", note: "1: write Documents/fex-jit-dump.bin (the JIT pool, sparse) on the first mach UNHANDLED / SIGILL (ml1242); default 0.", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), ConfigOption(key: "env.MADEIRA_MACH_ALIGN_FAULT", title: "", kind: .text, defaultValue: "", category: "Exceptions & threads", note: "", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), ConfigOption(key: "env.MADEIRA_NO_HEAL", title: "", kind: .text, defaultValue: "", category: "Exceptions & threads", note: "Healing default-ON since 2026-07-07: the patcher now rewrites ONLY IAT/delay-IAT slots (parsed from each pool copy's PE headers), so the 2026-07-04 breakage class — arm64x metadata slots whose consumers need PE VAs for identity/range com...", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), @@ -392,7 +395,7 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_WOW_REAP_DEAD", title: "", kind: .text, defaultValue: "", category: "Exceptions & threads", note: "[wow-reap]: a thread of an EXITED process that is held suspended (the [zombie] rule's or ios_thread_registry_end_dead_range's fallback when thread_terminate is refused) never runs again, so it cannot touch the window: it does not hold it...", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c", "build/ntdll-unix/virtual_ios.c"]), ConfigOption(key: "env.MADEIRA_WX", title: "", kind: .text, defaultValue: "", category: "Exceptions & threads", note: "ml695: OFF BY DEFAULT. This optimization has known stale-translation and address-reuse hazards and has already produced deterministic crashes in two builds; silent corruption on a fresh install would be worse than a clean crash, and a ki...", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), ConfigOption(key: "env.MADEIRA_X18_SIGNED_LOAD", title: "", kind: .text, defaultValue: "", category: "Exceptions & threads", note: "ios_mach_exception_thread). It decoded the zero-extending unsigned-offset loads (LDR X, LDR W, LDRB, LDRH) but not the sign-extending ones, so the access was declined as \"UNRECOGNIZED\" and the thread died: `ldrsw x28, [x18, #0x180c]` (0x...", choices: [], sources: ["build/ntdll-unix/signal_arm64_ios.c"]), - ConfigOption(key: "env.MADEIRA_GL_QUERY_BUFFER_OFFSETS", title: "32-bit GL query results as buffer offsets", kind: .bool, defaultValue: "1", category: "Graphics", note: "On (default): a 32-bit program's glGetQueryObject*v result pointer is an offset into the bound GL_QUERY_BUFFER (wined3d reads occlusion results that way; converting it as a pointer left the game spinning at 1 fps, ml1340). 0: the old pointer conversion.", choices: [], sources: ["wine/dlls/opengl32/unix_wgl.c"]), + ConfigOption(key: "env.MADEIRA_GL_QUERY_BUFFER_OFFSETS", title: "32-bit GL query results as buffer offsets", kind: .bool, defaultValue: "1", category: "Graphics", note: "On (default): a 32-bit program's glGetQueryObject*v result pointer is an offset into the bound GL_QUERY_BUFFER (wined3d reads occlusion results that way; converting it as a pointer left the game spinning at 1 fps, ml1340). 0: the old pointer conversion.", choices: [], sources: []), ConfigOption(key: "gl-vulkan", title: "Vulkan driver for OpenGL (Zink)", kind: .choice, defaultValue: "", category: "Graphics", note: "The Vulkan driver Zink runs desktop OpenGL (and d3d9 = wined3d) on: MoltenVK (default) or KosmicKrisp (gl/libvulkan_kosmickrisp.dylib); any other value is a .dylib name in the gl folder or an absolute path. env.MADEIRA_GL_VULKAN is the fallback. Read when a program first creates an OpenGL context; the log says \"ml1344 zink Vulkan library: ...\".", choices: [("", "MoltenVK (default)"), ("moltenvk", "MoltenVK"), ("kosmickrisp", "KosmicKrisp")], sources: ["build/win32u-unix/opengl_ios.c"]), ConfigOption(key: "env.MADEIRA_DIAG", title: "", kind: .text, defaultValue: "", category: "Media", note: "MADEIRA_DIAG=1 lifts the cap; read once. Local to this file so the media unix side links against nothing but upstream's libntdll_unix.a.", choices: [], sources: ["build/ntdll-unix/winegstreamer_unixlib_ios.c"]), ConfigOption(key: "env.MADEIRA_WG_PARSER", title: "", kind: .text, defaultValue: "", category: "Media", note: "", choices: [], sources: ["build/ntdll-unix/winegstreamer_unixlib_ios.c"]), @@ -466,7 +469,9 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_PLACE_PROOF", title: "", kind: .text, defaultValue: "", category: "Memory & JIT pool", note: "0 walks the whole map on every failed large mapping; by default a failed walk is trusted for 2 s until a release.", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), ConfigOption(key: "env.MADEIRA_POOL_BIG_SLOT_BELOW", title: "", kind: .text, defaultValue: "", category: "Memory & JIT pool", note: "1 puts the existing image slot below the native hole; 0/default keeps it above.", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), ConfigOption(key: "env.MADEIRA_POOL_BIG_SLOT_MB", title: "", kind: .text, defaultValue: "", category: "Memory & JIT pool", note: "N MB adjacent to a split-pool hole for one image of 64 MB or more (Social Club's libcef.dll); off by default.", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), + ConfigOption(key: "env.MADEIRA_POOL_CODE_DEFER_FREE", title: "", kind: .text, defaultValue: "", category: "Memory & JIT pool", note: "0: a FEX code buffer released while a thread runs in it is refused and stays live, as before", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), ConfigOption(key: "env.MADEIRA_POOL_CODE_EXIT_RECLAIM", title: "Return an exited program's code buffers", kind: .bool, defaultValue: "1", category: "Memory & JIT pool", note: "On by default. When a program of the session exits (a launcher restarting itself, RockstarService.exe, a setup step), the emulator's code buffers it held go back to the JIT pool: reusable 3 seconds later, once no thread runs in them. Before, they stayed in use until Madeira closed, and a game started after many such exits got 16 MB code buffers and ran slowly. 0 keeps them in use, as before.", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), + ConfigOption(key: "env.MADEIRA_POOL_CODE_KEEP_BIG", title: "", kind: .text, defaultValue: "", category: "Memory & JIT pool", note: "0: a code buffer request takes the smallest free buffer that fits, however much larger, as before", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), ConfigOption(key: "env.MADEIRA_POOL_HEAD_RESERVE_MB", title: "", kind: .text, defaultValue: "", category: "Memory & JIT pool", note: "48 to 256 MB of image headroom in the FEX pool-tail budget; 48/default keeps the existing policy. Region C is unchanged.", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), ConfigOption(key: "env.MADEIRA_POOL_LOW_IMAGES", title: "Small images in spare code-buffer space", kind: .bool, defaultValue: "0", category: "Memory & JIT pool", note: "Default off. If the normal JIT image allocation fails, copies up to 16 MB may use unallocated region C, preserving 4 MB for code buffers. Requires pool-low; 64-bit processes only. Does not shrink live buffers or the pool-low-margin.", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), ConfigOption(key: "env.MADEIRA_POOL_QUARANTINE", title: "", kind: .text, defaultValue: "", category: "Memory & JIT pool", note: "OUT NON-EXEC off=0xa848000...). The next process started in that session (the windowed taskbar's in-session launcher: services.exe, then steam.exe) got its ntdll copy there and faulted NOEXEC at LdrInitializeThunk's first instruction, fo...", choices: [], sources: ["build/ntdll-unix/virtual_ios.c"]), @@ -591,9 +596,9 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_DOCK_STEAM_RUN", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "On by default: when a game in a Madeira Dock session asks Steam to start another game (steam.exe with steam://run/, as Black Ops II's campaign does for Zombies), steam.exe is not started and Madeira starts that game when it is next opened. 0 lets steam.exe start as before (the full Steam client, which updates itself and stops at a black window in a Dock session).", choices: [], sources: ["build/ntdll-unix/process_ios.c"]), ConfigOption(key: "env.MADEIRA_DOCK_VULKAN_STANDIN", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "0: a Vulkan loader an installer wrote stays in system32", choices: [], sources: ["app/Madeira/DockInstallers.swift"]), ConfigOption(key: "env.MADEIRA_DOTNET_FUSION", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "", choices: [], sources: ["app/Madeira/DockInstallers.swift"]), - ConfigOption(key: "env.MADEIRA_INSTALL_SKIP_DOTNET", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "0: .NET Framework setups run too", choices: [], sources: ["app/Madeira/DockInstallers.swift"]), ConfigOption(key: "env.MADEIRA_DOWNLOAD_NOTIFICATIONS", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "Local notifications when a Steam download finishes, fails or pauses while Madeira is in the background (permission asked at the first download). 0: none, and no permission request.", choices: [], sources: ["app/Madeira/SteamDownloadBackground.swift"]), ConfigOption(key: "env.MADEIRA_INSTALL_DEFAULT_KEY", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "The install scripts' programs of a game: its own folder (one level down) and the shared redistributables Steam keeps next to it, and how many scripts were read.", choices: [], sources: ["app/Madeira/DockInstallers.swift"]), + ConfigOption(key: "env.MADEIRA_INSTALL_SKIP_DOTNET", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "0: .NET Framework setups run too", choices: [], sources: ["app/Madeira/DockInstallers.swift"]), ConfigOption(key: "env.MADEIRA_LIBRARY_COLLAPSE", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "MADEIRA_LIBRARY_COLLAPSE=0: the section titles do not collapse.", choices: [], sources: ["app/Madeira/SteamGames.swift"]), ConfigOption(key: "env.MADEIRA_ONBOARDING", title: "First-run Steam setup", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "`env.MADEIRA_ONBOARDING = 0` (madeira.cfg or the environment) never opens setup and hides \"Run setup again\". On by default.", choices: [], sources: ["app/Madeira/Onboarding.swift"]), ConfigOption(key: "env.MADEIRA_STEAM_ALLOW_CRASHPAD", title: "Steam web helper: allow its crash reporter", kind: .bool, defaultValue: "0", category: "Steam & Dock", note: "ml2210: 1 (the Steam Big Picture list) lets steamwebhelper.exe start exactly one kind of child, --type=crashpad-handler, with its command line unchanged; without it the 32-bit web helper ends itself at start (crash server failed to launch). Every other --type= child stays refused. Off (default): all are refused.", choices: [], sources: ["build/ntdll-unix/process_ios.c"]), @@ -602,9 +607,11 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_STEAM_CLOUD_PLAY_CHECK", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "Saves that need the user's choice (count).", choices: [], sources: ["app/Madeira/SteamOwnedLibrary.swift"]), ConfigOption(key: "env.MADEIRA_STEAM_CLOUD_QUIT", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "A game cannot be closed from Madeira, so its saves otherwise reach the cloud only at the next app start. The session menu's \"Upload saves and close Madeira\" sends them now: the app takes the account back from the session's client, upload...", choices: [], sources: ["app/Madeira/SteamOwnedLibrary.swift"]), ConfigOption(key: "env.MADEIRA_STEAM_CLOUD_REMOTECACHE", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "`env.MADEIRA_STEAM_CLOUD_REMOTECACHE = 0`: Valve's client's remotecache.vdf is left as the client wrote it, as before SteamRemoteCache.", choices: [], sources: ["app/Madeira/SteamOwnedLibrary.swift"]), + ConfigOption(key: "env.MADEIRA_STEAM_DLC", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "Owned DLC is listed and downloaded with its game (SteamDLC); off with `env.MADEIRA_STEAM_DLC = 0`, when downloads take no DLC as before.", choices: [], sources: ["app/Madeira/SteamOwnedLibrary.swift"]), ConfigOption(key: "env.MADEIRA_STEAM_LIBRARY", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "Off with `env.MADEIRA_STEAM_LIBRARY = 0`, or without Madeira Dock (a downloaded game starts through it).", choices: [], sources: ["app/Madeira/SteamOwnedLibrary.swift"]), ConfigOption(key: "env.MADEIRA_STEAM_SIGNIN", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "`env.MADEIRA_STEAM_SIGNIN = 0` hides the sign-in button.", choices: [], sources: ["app/Madeira/SwiftSteam/SteamSignIn.swift"]), ConfigOption(key: "env.MADEIRA_STEAM_TRACE", title: "", kind: .bool, defaultValue: "0", category: "Steam & Dock", note: "Logging for Steam sign-in. `event` lines are the always-on, low-volume record ([steam-signin]). They never contain tokens, passwords, Steam Guard codes, account names, Steam IDs or message payloads. `trace` lines are protocol-level detai...", choices: [], sources: ["app/Madeira/SwiftSteam/Helpers/SteamLog.swift"]), + ConfigOption(key: "env.MADEIRA_STEAM_WORKSHOP", title: "", kind: .bool, defaultValue: "1", category: "Steam & Dock", note: "Off with `env.MADEIRA_STEAM_WORKSHOP = 0`: no Workshop page or area.", choices: [], sources: ["app/Madeira/SteamOwnedLibrary.swift"]), ConfigOption(key: "alert-spin-us", title: "", kind: .int, defaultValue: "0", category: "Synchronisation", note: "ml1122: madeira.cfg alert-spin-us (default 0)", choices: [], sources: ["wine/dlls/ntdll/unix/sync.c"]), ConfigOption(key: "eco", title: "Eco scheduling", kind: .bool, defaultValue: "0", category: "Synchronisation", note: "Runs guest threads at a lower iOS QoS class so the system favours efficiency and saves power; can cost speed. Class chosen by eco-qos.", choices: [], sources: ["wine/dlls/ntdll/unix/sync.c", "app/Madeira/Library.swift"]), ConfigOption(key: "eco-qos", title: "Eco QoS class", kind: .choice, defaultValue: "", category: "Synchronisation", note: "The QoS class guest threads run at while eco is on.", choices: [("", "Utility (default)"), ("background", "Background"), ("initiated", "User initiated")], sources: ["wine/dlls/ntdll/unix/sync.c"]), @@ -720,6 +727,7 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_CPU_COUNT", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "MADEIRA_CPU_COUNT, a per-launch setting the app exports for one program, wins over the global key; unset = the key's behaviour.", choices: [], sources: ["wine/dlls/ntdll/unix/system.c"]), ConfigOption(key: "env.MADEIRA_CS_X64", title: "", kind: .bool, defaultValue: "0", category: "Wine core (ntdll)", note: "x64 fast path for EnterCriticalSection/LeaveCriticalSection/TryEnterCriticalSection. Default off (experimental). 1: an uncontended or recursive critical section is taken and released inside the x64 JIT code instead of a round trip into n...", choices: [], sources: ["wine/dlls/ntdll/cs_x64_fastpath.c"]), ConfigOption(key: "env.MADEIRA_DEFER_SECTION_ABORT", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "MADEIRA_DEFER_SECTION_ABORT=0: a killed thread exits at once, even inside a section", choices: [], sources: ["build/ntdll-unix/server_ios.c"]), + ConfigOption(key: "env.MADEIRA_DIR_BY_PATH", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "entries skipped because their stat failed", choices: [], sources: ["wine/dlls/ntdll/unix/file.c"]), ConfigOption(key: "env.MADEIRA_DIRCACHE", title: "Remember directory listings for file lookups", kind: .bool, defaultValue: "1", category: "Wine core (ntdll)", note: "Kill switch, default 1: a file name that does not match in its exact case is looked up in an in-memory copy of its directory instead of a volume query and a directory read each time (twice when the name is absent). Source-engine games such as Team Fortress 2 probe thousands of absent paths while loading and during play. 0: as before. Read once per app run.", choices: [], sources: ["wine/dlls/ntdll/unix/file.c"]), ConfigOption(key: "env.MADEIRA_DIRCACHE_TTL_MS", title: "Directory listing trust window (ms)", kind: .int, defaultValue: "1000", category: "Wine core (ntdll)", note: "How long a remembered listing is used without checking the directory, while no file was created, renamed, linked or deleted through Wine. After that one stat of the directory decides whether it is read again. 0 checks it on every lookup. Read once per app run.", choices: [], sources: ["wine/dlls/ntdll/unix/file.c"]), ConfigOption(key: "env.MADEIRA_DOCK_SESSION", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "\"EnterEC wrote it\" does NOT name the origin — Core.cpp says so explicitly: EnterEC storing an x64 target in State.rip is its job. The upstream producer still needs finding via x9 at DispatchJump/RetToEntryThunk/ExitToX64. A game in a Mad...", choices: [], sources: ["build/ntdll-unix/process_ios.c", "app/Madeira/WineProcessBridge.m"]), @@ -772,7 +780,6 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_WOW64_PEB_SELF", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "Madeira wow64-peb-self: wow_peb is a session global that init_peb() sets for a 32-bit child and loader_ios.c temporarily clears while a 64-bit child runs unix_init_startup_info(). A 32-bit thread querying ProcessWow64Information during t...", choices: [], sources: ["build/ntdll-unix/process_ios.c"]), ConfigOption(key: "env.MADEIRA_WOW_MAIN_FAIL_EXIT", title: "", kind: .text, defaultValue: "", category: "Wine core (ntdll)", note: "This is the SESSION'S OWN program, not a child (a 32-bit program started on a device whose address space has no 4 GB window left). The pthread_exit() inside fatal_error() ends the boot thread (wine_process_thread) in the middle of __wine...", choices: [], sources: ["build/ntdll-unix/loader_ios.c"]), ConfigOption(key: "env.MADEIRA_DNS_TIMEOUT_MS", title: "", kind: .text, defaultValue: "", category: "Wine libraries", note: "", choices: [], sources: ["wine/dlls/ws2_32/unixlib.c"]), - ConfigOption(key: "env.MADEIRA_GL_EXTENSIONS_MAX", title: "", kind: .text, defaultValue: "", category: "Wine libraries", note: "Madeira: id Tech 2/3 era games print or copy GL_EXTENSIONS into a fixed stack buffer (Jedi Academy: Com_Printf's 4 KB), and Zink's list is ~9 KB, so the copy overwrote the return address. Mesa lists extensions oldest first for exactly th...", choices: [], sources: ["wine/dlls/opengl32/unix_wgl.c", "app/Madeira/WineProcessBridge.m"]), ConfigOption(key: "env.MADEIRA_MFC", title: "Microsoft MFC (mfc140.dll, mfc140u.dll)", kind: .bool, defaultValue: "1", category: "Wine libraries", note: "On by default. MFC comes from the same Microsoft VC++ package as the C++ runtime and is linked into the game's system32; games that import it (Horizon Zero Dawn) stop at once without it. Wine has no MFC of its own. 0 leaves it out. Read at session start.", choices: [], sources: ["app/Madeira/WineProcessBridge.m"]), ConfigOption(key: "env.MADEIRA_MONO_DEFAULTS", title: "", kind: .text, defaultValue: "", category: "Wine libraries", note: "Wine Mono, 32- and 64-bit: mscoree sets MONO_THREADS_SUSPEND=coop and adds keep-delegates to MONO_DEBUG before Mono loads, and puts the previous values back once Mono has read them (ml1282); values already set win. 0 sets nothing.", choices: [], sources: ["wine/dlls/mscoree/metahost.c"]), ConfigOption(key: "env.MADEIRA_NSI_DEVICE_CACHE", title: "", kind: .text, defaultValue: "", category: "Wine libraries", note: "On by default: without \\\\.\\Nsi, the failed open is remembered instead of being retried on every call. 0 retries every time.", choices: [], sources: ["wine/dlls/nsi/nsi.c"]), @@ -781,9 +788,11 @@ extension ConfigCatalog { ConfigOption(key: "env.MADEIRA_SETUPAPI_MEDIA_FIX", title: "", kind: .text, defaultValue: "", category: "Wine libraries", note: "Madeira ml1310: ask for the media again only when the answer named a new place to look. An unattended install's callback (the default one included) answers \"same place\" forever, and the loop never ended; report a copy error instead. Idea...", choices: [], sources: ["wine/dlls/setupapi/queue.c"]), ConfigOption(key: "env.MADEIRA_ZERO_MITIGATION_POLICY", title: "", kind: .text, defaultValue: "", category: "Wine libraries", note: "uninitialised PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY, and when EnableControlFlowGuard (bit 0) happens to be set it looks up SetProcessValidCallTargets in api-ms-win-core-memory-l1-1-3.dll, which this Wine does not export, and calls...", choices: [], sources: ["wine/dlls/kernelbase/process.c"]), ConfigOption(key: "env.MADEIRA_CLIP_NOTIFY_ALWAYS", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "1: notify the foreground thread on every ClipCursor, as before", choices: [], sources: ["build/wineserver/queue_ios.c"]), + ConfigOption(key: "env.MADEIRA_CTX_FP", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "MADEIRA_CTX_FP=0: a cross-thread GetThreadContext returns zeroed vector registers, as before. Read on every capture, madeira.cfg (and the game's own lines) first: this server starts before the app exports env.* lines (ml1236) and runs ag...", choices: [], sources: ["build/wineserver/mach_ios.c"]), ConfigOption(key: "env.MADEIRA_CTX_FRAME", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "a live mirror, which is why four of five stalled threads reported rip=0. * Selector mirrors is_inside_syscall() exactly: kernel_stack <= sp <= syscall_frame. NOT \"native pc is not EC\" -- FEX JIT code is also non-EC but its frame may be s...", choices: [], sources: ["build/wineserver/mach_ios.c"]), ConfigOption(key: "env.MADEIRA_DEAD_RELEASE", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "getenv once, not per release", choices: [], sources: ["wine/server/object.c"]), ConfigOption(key: "env.MADEIRA_ILONLY_WOW64", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "Kill switch, read once: 0 turns the check off.", choices: [], sources: ["build/wineserver/mapping_ios.c"]), + ConfigOption(key: "env.MADEIRA_LOCAL_PROCESS_WRITE", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "Keep local writes disabled by default: enabling them globally regressed Steam. Explicitly opt in only for pseudo-processes in this Unix task. The request handler has already checked PROCESS_VM_WRITE rights.", choices: [], sources: ["build/wineserver/mach_ios.c"]), ConfigOption(key: "env.MADEIRA_REAL_SUSPEND", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "Read on every call: the app sets it per session (a game's \"Pause threads fully\" switch, BCDLaunch.realSuspend), and this server runs again in the same process for the next session of the app run. Logged when it changes.", choices: [], sources: ["build/wineserver/mach_ios.c"]), ConfigOption(key: "env.MADEIRA_REGFLUSH_SAVE", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "MADEIRA_REGFLUSH_SAVE=0: RegFlushKey does nothing again; the registry is saved periodically and at exit (ml1310)", choices: [], sources: ["build/wineserver/request_ios.c"]), ConfigOption(key: "env.MADEIRA_REPLACE_READONLY_LINK", title: "", kind: .text, defaultValue: "", category: "Wine server", note: "On by default: an overwrite (supersede, overwrite, overwrite-if) through a link to a read-only file replaces the link with a new file. 0 fails it with access denied.", choices: [], sources: ["build/wineserver/fd_ios.c"]), diff --git a/build/ntdll-unix/virtual_ios.c b/build/ntdll-unix/virtual_ios.c index dacaa96bda..aebbecf3cf 100644 --- a/build/ntdll-unix/virtual_ios.c +++ b/build/ntdll-unix/virtual_ios.c @@ -3797,6 +3797,20 @@ static int ios_pool_preserve_code_carve( size_t available, size_t want, int low_ return low_fits && want <= 0x10000 && available >= 0x100000 && available / 4 >= want; } +/* Nor should any request take a free carve four or more times its size while the pool's + * budget still affords a fresh carve of the size asked: the carve stays with its taker for + * that process's life. Persona 5 Royal's crashpad_handler.exe took a 16 MB carve for its + * 16 KB dispatcher and a 128 MB one for a 16 MB code buffer, and the game itself was then + * capped at 16 MB. When the fresh carve fails anyway, the allocator retries with every + * carve allowed (reuse_all_carves). MADEIRA_POOL_CODE_KEEP_BIG=0 restores the old policy. */ +static int ios_pool_preserve_big_carve( size_t available, size_t want, size_t budget_cap ) +{ + const char *env = getenv( "MADEIRA_POOL_CODE_KEEP_BIG" ); /* 0: a code buffer request takes the smallest free buffer that fits, however much larger, as before */ + + if (env && !strcmp( env, "0" )) return 0; + return want && available / 4 >= want && want <= budget_cap; +} + /* ml557 (#74 REGRESSION): is any thread's PC currently INSIDE this tail carve? * * ml438 made a MEM_RELEASE of a live tail EC-buffer carve mark it free for reuse, @@ -33982,7 +33996,7 @@ NTSTATUS WINAPI NtAllocateVirtualMemoryEx( HANDLE process, PVOID *ret, SIZE_T *s int low_fits = low_ok && alloc_size <= IOS_POOL_LOW_CARVE_MAX && alloc_size <= ios_jit_low_size_global && ios_jit_low_reserved <= ios_jit_low_size_global - alloc_size; - int kept_large_carve = 0, reuse_all_carves = 0, retired_busy = 0; + int kept_large_carve = 0, kept_big_carve = 0, reuse_all_carves = 0, retired_busy = 0; size_t tail_cap = ~(size_t)0, budget_cap = ~(size_t)0; void *carve_peb = ios_jit_current_peb(); /* madeira-bcd: the carve's owner, see ios_tail_carve_ready */ /* ml459 (#75): cap a single EC code buffer at 16MB. FEX asks for 32MB @@ -34082,6 +34096,12 @@ NTSTATUS WINAPI NtAllocateVirtualMemoryEx( HANDLE process, PVOID *ret, SIZE_T *s kept_large_carve = 1; best = ~0u; } + else if (best != ~0u && !reuse_all_carves && + ios_pool_preserve_big_carve( ios_tail_carves[best].size, alloc_size, budget_cap )) + { + kept_big_carve = 1; + best = ~0u; + } /* madeira-bcd: a dead process's buffer, only when no thread runs in it; * a busy one starts its grace again and the next one is tried */ if (best != ~0u && ios_tail_carves[best].retired && @@ -34288,6 +34308,12 @@ NTSTATUS WINAPI NtAllocateVirtualMemoryEx( HANDLE process, PVOID *ret, SIZE_T *s * tail reservation — the old path leaked it on every refusal. */ ios_pool_tail_unreserve( &ios_jit_tail_reserved, reserve_offset + alloc_size, tail_added, ios_jit_hole_end > ios_jit_hole_off ); + /* a free carve was kept back for a bigger request: use it after all */ + if ((kept_large_carve || kept_big_carve) && !reuse_all_carves) + { + reuse_all_carves = 1; + goto retry_code_carve_reuse; + } ERR("NtAllocateVirtualMemoryEx iOS: JIT-pool tail exhausted for FEX EC_CODE %zu bytes\n", (size_t)*size_ptr); /* ml459 (#75): dump the carve table on the FIRST refusal — the @@ -35194,6 +35220,8 @@ NTSTATUS WINAPI NtFreeVirtualMemory( HANDLE process, PVOID *addr_ptr, SIZE_T *si size_t off = (size_t)((char *)base - (char *)ios_jit_rx_base_global); size_t carve_size = 0; void *caller_peb = ios_jit_current_peb(); + const char *defer_env = getenv( "MADEIRA_POOL_CODE_DEFER_FREE" ); /* 0: a FEX code buffer released while a thread runs in it is refused and stays live, as before */ + int defer_free = !(defer_env && !strcmp( defer_env, "0" )); pthread_mutex_lock( &ios_tail_carve_lock ); for (i = 0; i < ios_tail_carve_n; i++) { @@ -35210,16 +35238,30 @@ NTSTATUS WINAPI NtFreeVirtualMemory( HANDLE process, PVOID *addr_ptr, SIZE_T *si carve_size = 0; break; } - /* ml557: do NOT recycle a carve a thread is still executing in. */ + /* ml557: do NOT recycle a carve a thread is still executing in. Refusing the + * release leaked it for good, though: FEX releases a buffer once and never + * again, so "freeable on the next release" never came. Persona 5 Royal lost a + * 128 MB buffer that way and ran its last 4 minutes on 16 MB ones. Free it as + * retired instead, so reuse waits out the grace and its strict scan, exactly + * as for a dead process's buffer. MADEIRA_POOL_CODE_DEFER_FREE=0 refuses it. */ if (ios_tail_carve_occupied( base, ios_tail_carves[i].size )) { static unsigned long occ_n; + if (!defer_free) + { + if (++occ_n <= 16 || (occ_n % 256) == 0) + dprintf(2, "[jit-pool] tail FREE REFUSED rx=%p size=0x%lx — a thread's " + "PC is inside it (occupied=%lu) rev=ml557\n", + base, (unsigned long)ios_tail_carves[i].size, occ_n); + carve_size = 0; + break; + } if (++occ_n <= 16 || (occ_n % 256) == 0) - dprintf(2, "[jit-pool] tail FREE REFUSED rx=%p size=0x%lx — a thread's " - "PC is inside it (occupied=%lu) rev=ml557\n", + dprintf(2, "[jit-pool] tail FREE deferred rx=%p size=0x%lx — a thread's " + "PC is inside it (occupied=%lu): retired until it leaves rev=ml557\n", base, (unsigned long)ios_tail_carves[i].size, occ_n); - carve_size = 0; - break; + ios_tail_carves[i].retired = 1; + ios_tail_carves[i].peb = NULL; } ios_tail_carves[i].free = 1; ios_tail_carves[i].freed_at = time( NULL ); diff --git a/build/wineserver/mach_ios.c b/build/wineserver/mach_ios.c index 881c74de78..023a506d7a 100644 --- a/build/wineserver/mach_ios.c +++ b/build/wineserver/mach_ios.c @@ -443,6 +443,9 @@ int send_thread_signal( struct thread *thread, int sig ) #define IOS_SCF_SP 0x0f8 #define IOS_SCF_PC 0x100 #define IOS_SCF_CPSR 0x108 +#define IOS_SCF_FPCR 0x128 +#define IOS_SCF_FPSR 0x12c +#define IOS_SCF_V 0x130 /* NEON128 v[32] */ #define IOS_SCF_SIZE 0x330 #define IOS_SCF_RESTORE_FLAGS 0x10c /* syscall_frame.restore_flags */ #define IOS_CTX_ARM64_CONTROL 0x00400001u /* CONTEXT_ARM64 | CONTEXT_ARM64_CONTROL */ @@ -876,6 +879,17 @@ int ios_apply_resume_context( struct thread *thread, const struct context_data * * resuming in between. Snapshot-only callers leave it NULL. */ struct thread *ios_pending_persistent_hold; +/* MADEIRA_CTX_FP=0: a cross-thread GetThreadContext returns zeroed vector registers, as + * before. Read on every capture, madeira.cfg (and the game's own lines) first: this server + * starts before the app exports env.* lines (ml1236) and runs again for the next session. */ +static int ios_ctx_fp_enabled(void) +{ + char buf[16]; + const char *e = madeira_cfg_get( "env.MADEIRA_CTX_FP", buf, sizeof(buf) ) ? buf + : getenv( "MADEIRA_CTX_FP" ); + return !(e && e[0] == '0'); +} + int ios_fill_thread_context( struct thread *thread, struct context_data *native, struct context_data *wow ) @@ -909,6 +923,55 @@ int ios_fill_thread_context( struct thread *thread, have_native = 1; } + /* The vector registers too: without them every cross-thread floating-point context + * came back zeroed. FEX keeps guest XMM0-15 in V0-V15 under ARM64EC, the same mapping + * the ARM64EC ABI uses, so these are the guest's XMM registers. Persona 5 Royal moves + * code between threads this way: a thread loads it into XMM13, and the main thread + * reads its floating-point context into a CONTEXT in the image and jumps to the + * XMM13 slot. A thread blocked in a syscall is overridden below from the saved frame. + * MADEIRA_CTX_FP=0 restores the zeroed vector registers. */ + if (have_native && ios_ctx_fp_enabled()) + { + arm_neon_state64_t neon; + mach_msg_type_number_t ncount = ARM_NEON_STATE64_COUNT; + + if (!thread_get_state( port, ARM_NEON_STATE64, (thread_state_t)&neon, &ncount )) + { + unsigned int i; + for (i = 0; i < 32; i++) + { + native->fp.arm64_regs.q[i].low = (uint64_t)neon.__v[i]; + native->fp.arm64_regs.q[i].high = (uint64_t)(neon.__v[i] >> 64); + } + native->fp.arm64_regs.fpcr = neon.__fpcr; + native->fp.arm64_regs.fpsr = neon.__fpsr; + native->flags |= SERVER_CTX_FLOATING_POINT; + } + } + + /* Inside a syscall the live vector registers belong to Unix code, which may have + * clobbered them; the dispatcher saved the guest's at entry. Same validation as the + * ml716 frame substitution below, but not behind its opt-in: only FP is replaced. + * A Unix call saves only q8-q15 in that frame, so inside one, q0-q7 are the values of + * the thread's last syscall. */ + if (have_native && thread->teb && (native->flags & SERVER_CTX_FLOATING_POINT)) + { + uint64_t frame = 0, kstack = 0; + uint64_t sp = native->ctl.arm64_regs.sp; + if (ios_safe_read( (uint64_t)thread->teb + IOS_TEB_SYSCALL_FRAME_OFF, &frame, 8 ) && + ios_safe_read( (uint64_t)thread->teb + IOS_TEB_KERNEL_STACK_OFF, &kstack, 8 ) && + frame && kstack && !(frame & 7) && kstack < frame && sp >= kstack && sp <= frame) + { + unsigned char f[IOS_SCF_SIZE]; + if (ios_safe_read( frame, f, sizeof(f) )) + { + memcpy( native->fp.arm64_regs.q, f + IOS_SCF_V, sizeof(native->fp.arm64_regs.q) ); + native->fp.arm64_regs.fpcr = *(uint32_t *)(f + IOS_SCF_FPCR); + native->fp.arm64_regs.fpsr = *(uint32_t *)(f + IOS_SCF_FPSR); + } + } + } + /* ml716: A THREAD PARKED INSIDE A SYSCALL MUST REPORT ITS SYSCALL FRAME. * * Wine's own suspend path already does this: usr1_handler checks diff --git a/tests/host/check-ctx-fp.py b/tests/host/check-ctx-fp.py new file mode 100644 index 0000000000..f4784daca5 --- /dev/null +++ b/tests/host/check-ctx-fp.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Vector registers in a cross-thread context (build/wineserver/mach_ios.c); no Wine runs. + +The server's Mach capture read only ARM_THREAD_STATE64, so every cross-thread GetThreadContext +returned zeroed XMM registers. Persona 5 Royal reads another thread's floating-point context into +a CONTEXT inside its image and jumps to the XMM13 slot, and executed zeros. Checks that the syscall +frame offsets the capture reads match struct syscall_frame, that the syscall dispatcher saves every +vector register there, that the capture is gated by MADEIRA_CTX_FP and runs while the thread is +halted, and the switch itself: unset or 1 captures, 0 restores the zeroed registers, and the +madeira.cfg line wins over the environment. +""" +from pathlib import Path +import re, subprocess, tempfile + +root = Path(__file__).resolve().parents[2] +src = (root / "build/wineserver/mach_ios.c").read_text() +sig = (root / "build/ntdll-unix/signal_arm64_ios.c").read_text() + +def function(source, start): + a = source.index(start); b = source.index("{", a); depth = 1; c = b + 1 + while depth: + depth += (source[c] == "{") - (source[c] == "}"); c += 1 + return source[a:c] + +offs = dict(re.findall(r"#define (IOS_SCF_\w+)\s+(0x[0-9a-f]+)\b", src)) +frame = sig[sig.index("struct syscall_frame\n{"):] +frame = frame[:frame.index("};") + 2] +harness = r""" +#include +#include +typedef uint64_t ULONG64; typedef uint32_t ULONG; +typedef struct { uint64_t Low; int64_t High; } NEON128; +""" + frame + "\n" +for name, field in (("FP", "fp"), ("LR", "lr"), ("SP", "sp"), ("PC", "pc"), ("CPSR", "cpsr"), + ("FPCR", "fpcr"), ("FPSR", "fpsr"), ("V", "v")): + harness += f"_Static_assert(offsetof(struct syscall_frame, {field}) == {offs['IOS_SCF_' + name]}, \"{name}\");\n" +harness += f"_Static_assert(sizeof(struct syscall_frame) == {offs['IOS_SCF_SIZE']}, \"SIZE\");\n" +harness += "int main(void) { return 0; }\n" +with tempfile.TemporaryDirectory() as t: + c = Path(t) / "layout.c"; c.write_text(harness) + subprocess.run(["cc", "-std=gnu11", "-Wall", str(c), "-o", str(Path(t) / "layout")], check=True) +print("PASS: the IOS_SCF offsets match struct syscall_frame, including fpcr, fpsr and v[32]") + +dispatcher = sig[sig.index("__ASM_GLOBAL_FUNC( __wine_syscall_dispatcher,"):] +dispatcher = dispatcher[:dispatcher.index("__ASM_GLOBAL_FUNC(", 10)] +for q in range(0, 32, 2): + assert re.search(rf'"stp q{q},\s+q{q + 1},\s+\[x10, #0x{0x130 + q * 16:x}\]', dispatcher), q +assert '"str w9, [x10, #0x128]' in dispatcher and '"str w9, [x10, #0x12c]' in dispatcher +print("PASS: the syscall dispatcher saves q0-q31, fpcr and fpsr where the capture reads them") + +fill = function(src, "int ios_fill_thread_context(") +neon = fill.index("thread_get_state( port, ARM_NEON_STATE64") +assert fill.index("thread_suspend( port )") < fill.index("thread_get_state( port, ARM_THREAD_STATE64") < neon +assert neon < fill.index("f + IOS_SCF_V") < fill.rindex("thread_resume( port );") +assert "if (have_native && ios_ctx_fp_enabled())" in fill[:neon] +assert "(native->flags & SERVER_CTX_FLOATING_POINT)" in fill[neon:fill.index("f + IOS_SCF_V")] +assert "native->flags |= SERVER_CTX_FLOATING_POINT;" in fill +print("PASS: the vector capture is gated by MADEIRA_CTX_FP, runs while the thread is halted, and the " + "frame override only replaces a capture that happened") + +switch = function(src, "static int ios_ctx_fp_enabled(void)") +harness = r""" +#include +#include +#include +static const char *cfg_value; +static int madeira_cfg_get(const char *key, char *buf, size_t cap) +{ + if (strcmp(key, "env.MADEIRA_CTX_FP") || !cfg_value) return 0; + snprintf(buf, cap, "%s", cfg_value); + return 1; +} +""" + switch + r""" +#define CHECK(x) do { if (!(x)) { printf("failed line %d: %s\n", __LINE__, #x); return 1; } } while (0) +int main(void) +{ + unsetenv("MADEIRA_CTX_FP"); + CHECK(ios_ctx_fp_enabled()); + setenv("MADEIRA_CTX_FP", "1", 1); CHECK(ios_ctx_fp_enabled()); + setenv("MADEIRA_CTX_FP", "0", 1); CHECK(!ios_ctx_fp_enabled()); + cfg_value = "1"; CHECK(ios_ctx_fp_enabled()); + unsetenv("MADEIRA_CTX_FP"); cfg_value = "0"; CHECK(!ios_ctx_fp_enabled()); + return 0; +} +""" +with tempfile.TemporaryDirectory() as t: + c = Path(t) / "switch.c"; c.write_text(harness) + exe = Path(t) / "switch" + subprocess.run(["cc", "-std=c11", "-D_POSIX_C_SOURCE=200809L", "-Wall", "-Wextra", "-Werror", + str(c), "-o", str(exe)], check=True) + run = subprocess.run([str(exe)], capture_output=True, text=True) + print(run.stdout, end="") + run.check_returncode() +print("PASS: MADEIRA_CTX_FP unset or 1 captures, 0 restores zeroed registers, madeira.cfg wins over the environment") diff --git a/tests/host/check-pool-code-exit.py b/tests/host/check-pool-code-exit.py index 373d760fc3..e24b10f7cf 100644 --- a/tests/host/check-pool-code-exit.py +++ b/tests/host/check-pool-code-exit.py @@ -41,6 +41,7 @@ def function(signature): 'static int ios_pool_low_take(', 'static int ios_tail_carve_in_low(', 'static int ios_pool_preserve_code_carve(', + 'static int ios_pool_preserve_big_carve(', 'static int ios_tail_carve_ready(', 'static unsigned ios_tail_carves_retire_owner(', 'static int ios_tail_carve_foreign_release(', @@ -59,7 +60,7 @@ def function(signature): assert reclaim.count('ios_tail_carves_owner_died( peb );') == 1 assert reclaim.index('ios_tail_carves_owner_died( peb );') > reclaim.rindex('pthread_mutex_unlock( &ios_pool_lock );'), \ 'the carve lock is never taken under the pool lock' -assert source.count('.retired = 1;') == 1, 'only an exit retires a carve' +assert source.count('.retired = 1;') == 2, 'an exit, or a release while a thread still runs in the carve, retires it' assert source.count('ios_tail_carves[ios_tail_carve_n].retired = 0;') == 4, 'every new carve starts unretired' assert source.count('ios_tail_carves[ios_tail_carve_n].peb = carve_peb;') == 3, 'region C, ml1039 and tail carves' assert source.count('ios_tail_carves[ios_tail_carve_n].peb = NULL;') == 1, 'a skipped piece has no owner' @@ -111,6 +112,7 @@ def function(signature): static int allocate(size_t *size_ptr, void **ret) { ''' + ec + r''' + (void)kept_big_carve; /* read by the production tail path after this prefix */ return FALLBACK_TAIL; /* the production tail path starts after this prefix */ } static int release(void *base, unsigned type, void **addr_ptr, size_t *size_ptr) @@ -361,15 +363,26 @@ def function(signature): owner = 8; CHECK(release(rx, MEM_RELEASE, &addr, &size) == FALL_THROUGH && !ios_tail_carves[0].free); owner = 0; /* a thread without a TEB: as before */ + /* MADEIRA_POOL_CODE_DEFER_FREE=0: refused and left live, as before. */ occupied = 1; + setenv("MADEIRA_POOL_CODE_DEFER_FREE", "0", 1); CHECK(release(rx, MEM_RELEASE, &addr, &size) == FALL_THROUGH && !ios_tail_carves[0].free); + unsetenv("MADEIRA_POOL_CODE_DEFER_FREE"); + /* A release while a thread still runs in the buffer frees it as retired: no + * owner, and reuse waits out the grace and a strict scan, as for an exit. */ + CHECK(release(rx, MEM_RELEASE, &addr, &size) == STATUS_SUCCESS && addr == rx && size == 64 * MB); + CHECK(ios_tail_carves[0].free && ios_tail_carves[0].retired && !ios_tail_carves[0].peb); + CHECK(ios_tail_carves[0].freed_at == fake_now && !ios_tail_carve_ready( &ios_tail_carves[0], fake_now )); + CHECK(ios_tail_carve_ready( &ios_tail_carves[0], fake_now + 3 )); occupied = 0; + ios_tail_carves[0].free = ios_tail_carves[0].retired = 0; /* live again, owned by 9 */ + ios_tail_carves[0].peb = (void *)9; owner = 9; CHECK(release(rx, MEM_RELEASE, &addr, &size) == STATUS_SUCCESS && addr == rx && size == 64 * MB); CHECK(ios_tail_carves[0].free && !ios_tail_carves[0].retired); owner = 8; CHECK(release(rx, MEM_RELEASE, &addr, &size) == FALL_THROUGH); /* already free */ - puts("PASS: a release from another process is refused; the owner's (or a TEB-less thread's) works as before"); + puts("PASS: a release from another process is refused; an occupied one is retired until its thread leaves (refused with MADEIRA_POOL_CODE_DEFER_FREE=0); the owner's (or a TEB-less thread's) works as before"); } int main(void) diff --git a/tests/host/check-pool-startup-budget.py b/tests/host/check-pool-startup-budget.py index 64a4c48d58..889d10a966 100644 --- a/tests/host/check-pool-startup-budget.py +++ b/tests/host/check-pool-startup-budget.py @@ -42,6 +42,7 @@ def function(source, signature, closing='\n}'): 'static int ios_pool_low_take(', 'static int ios_tail_carve_in_low(', 'static int ios_pool_preserve_code_carve(', + 'static int ios_pool_preserve_big_carve(', 'static int ios_tail_carve_ready(', )) carve_start = native.index('#define IOS_TAIL_CARVE_MAX ') @@ -51,6 +52,7 @@ def function(source, signature, closing='\n}'): #include #include #include +#include #include #include #include @@ -89,6 +91,7 @@ def function(source, signature, closing='\n}'): static int allocate(size_t *size_ptr, void **ret) { ''' + ec.replace('ios_pool_low_take(', 'racing_low_take(') + r''' + (void)kept_big_carve; /* read by the production tail path after this prefix */ return FALLBACK_TAIL; /* The production tail path starts after this prefix. */ } static const size_t used = 272 * MB + 0x14000; @@ -145,7 +148,10 @@ def function(source, signature, closing='\n}'): reset(memory); ios_jit_low_reserved = ios_jit_low_size_global; request = 0x4000; - CHECK(allocate(&request, &ret) == STATUS_SUCCESS && request == 16 * MB); + CHECK(allocate(&request, &ret) == FALLBACK_TAIL && ios_tail_carves[0].free); + setenv("MADEIRA_POOL_CODE_KEEP_BIG", "0", 1); /* the old policy: the 16MB carve, whatever the ask */ + CHECK(allocate(&request, &ret) == STATUS_SUCCESS && request == 16 * MB && !ios_tail_carves[0].free); + unsetenv("MADEIRA_POOL_CODE_KEEP_BIG"); reset(memory); ios_tail_carves[1] = ios_tail_carves[0]; ios_tail_carves[1].off += 20 * MB; @@ -156,9 +162,13 @@ def function(source, signature, closing='\n}'): CHECK(ios_tail_carves[0].free && !ios_tail_carves[1].free && ios_jit_low_reserved == used); reset(memory); request = MB; + CHECK(allocate(&request, &ret) == STATUS_SUCCESS && request == MB && ios_tail_carves[0].free); + CHECK(ios_jit_low_reserved == used + MB); + reset(memory); + request = 4 * MB + 0x4000; CHECK(allocate(&request, &ret) == STATUS_SUCCESS && request == 16 * MB); - CHECK(ios_jit_low_reserved == used); - puts("PASS: full C, an exact-size retired carve and normal code-buffer requests retain the existing behavior"); + CHECK(!ios_tail_carves[0].free && ios_jit_low_reserved == used); + puts("PASS: a request under a quarter of the free 16MB carve leaves it free (fresh tail carve with C full, region C otherwise; MADEIRA_POOL_CODE_KEEP_BIG=0 takes it); an exact-size carve and a larger request still reuse"); /* A 440MB low run less the unchanged 128MB margin leaves 312MB. With * 16MB rounding C has only 304MB: the helper's 32MB request misses by