Compare commits

...

3 Commits

Author SHA1 Message Date
Dusk-92 a2b8d2a7e0 ci: build and ABI-gate WeirdPerformance 2.4-B2 2026-09-07 18:11:22 +02:00
Dusk-92 5dde1f641e docs: add B2 reload-guard test protocol 2026-09-07 18:10:54 +02:00
Dusk-92 d6262135a8 test: add B2 new-Lua-state GC grace guard 2026-09-07 18:10:36 +02:00
3 changed files with 263 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
name: Build WeirdPerformance 2.4-B2 GC reload-guard test
on:
push:
branches:
- test/wp24b2-gc-reload-guard
paths:
- 'experiments/wp24b-gc/**'
- '.github/workflows/wp24b2-gc.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: '0.17.0-dev.1970+67f39b551'
- name: Build and ABI-validate x86 DLL
working-directory: experiments/wp24b-gc
run: |
set -euo pipefail
python3 b2_patch.py
grep -q 'NATIVE_GRACE_COLLECTIONS: u32 = 3' main.zig
grep -q '2.4-B2-gc-safe-sweep-reload-guard' main.zig
grep -q 'weirdperformance_gc24b2' build.zig
zig build --fetch
python3 - <<'PY'
from pathlib import Path
matches = list(Path("zig-pkg").glob("zhook-*/src/zhook.zig"))
if len(matches) != 1:
raise SystemExit(f"expected one pinned zhook source, found {len(matches)}")
p = matches[0]
text = p.read_text(encoding="utf-8")
old = "var patch: [MAX_STOLEN]u8 = .{0x90} ** MAX_STOLEN;"
new = "var patch: [MAX_STOLEN]u8 = @splat(0x90);"
if old not in text:
raise SystemExit("expected pinned zhook repeat initializer not found")
p.write_text(text.replace(old, new), encoding="utf-8")
PY
zig build -Doptimize=small
DLL=zig-out/bin/weirdperformance_gc24b2.dll
test -s "$DLL"
file "$DLL" | tee BINARY_INFO.txt
file "$DLL" | grep -Eq 'PE32.*Intel (80386|i386)'
strings "$DLL" | grep -q '2.4-B2-gc-safe-sweep-reload-guard'
if command -v llvm-objdump >/dev/null 2>&1; then
llvm-objdump -d --x86-asm-syntax=intel "$DLL" > ABI_DISASM.txt
else
objdump -d -M intel "$DLL" > ABI_DISASM.txt
fi
python3 - <<'PY'
import re
from pathlib import Path
asm = Path("ABI_DISASM.txt").read_text(encoding="utf-8", errors="replace").lower()
def require(pattern, label):
if not re.search(pattern, asm, re.S):
raise SystemExit(f"ABI validation failed: {label}")
# zhook must jump to callbacks compiled with L arriving in ECX.
m = re.search(r"mov\s+edx,\s*0x6f7340.{0,240}?push\s+0x([0-9a-f]+)", asm, re.S)
if not m:
raise SystemExit("ABI validation failed: collector callback address not found")
cb = m.group(1).lstrip("0") or "0"
pos = asm.find(cb + ":")
if pos < 0 or not re.search(r"mov\s+esi,\s*ecx", asm[pos:pos+700]):
raise SystemExit("ABI validation failed: collector callback does not consume L from ECX")
m = re.search(r"mov\s+edx,\s*0x6f6ef0.{0,240}?push\s+0x([0-9a-f]+)", asm, re.S)
if not m:
raise SystemExit("ABI validation failed: lua_close callback address not found")
cb = m.group(1).lstrip("0") or "0"
pos = asm.find(cb + ":")
if pos < 0 or not re.search(r"mov\s+esi,\s*ecx", asm[pos:pos+500]):
raise SystemExit("ABI validation failed: lua_close callback does not consume L from ECX")
# Native helper ABI observed in WoW 5875:
# ECX=L, EDX=&list, stack arg=all for 0x6F7210.
require(
r"lea\s+edx,\s*\[edi\s*\+\s*0x14\].{0,160}?"
r"mov\s+eax,\s*0x6f7210.{0,120}?"
r"mov\s+ecx,\s*esi.{0,120}?"
r"push\s+0x0.{0,80}?call\s+eax",
"lua_gc_remove_objects register/stack ABI",
)
require(
r"mov\s+eax,\s*0x6f72f0.{0,120}?"
r"mov\s+ecx,\s*esi.{0,100}?"
r"xor\s+edx,\s*edx.{0,80}?call\s+eax",
"lua_gc_sweep_all_lists fastcall ABI",
)
for addr, label in (
("6f73e0", "lua_gc_full_collection"),
("6f7370", "lua_gc_shrink_memory"),
("6f7080", "luaCallUserDataGC"),
):
require(
rf"mov\s+eax,\s*0x{addr}.{{0,120}}?mov\s+ecx,\s*esi.{{0,80}}?call\s+eax",
f"{label} ECX ABI",
)
print("ABI validation: PASS")
PY
sha256sum "$DLL" | tee SHA256SUMS.txt
sha256sum main.zig build.zig b2_patch.py > PATCHED_SOURCE_SHA256.txt
- uses: actions/upload-artifact@v4
with:
name: WeirdPerformance-2.4-B2-GC-Reload-Guard
path: |
experiments/wp24b-gc/zig-out/bin/weirdperformance_gc24b2.dll
experiments/wp24b-gc/SHA256SUMS.txt
experiments/wp24b-gc/PATCHED_SOURCE_SHA256.txt
experiments/wp24b-gc/BINARY_INFO.txt
experiments/wp24b-gc/ABI_DISASM.txt
experiments/wp24b-gc/README-B2.md
experiments/wp24b-gc/b2_patch.py
if-no-files-found: error
+53
View File
@@ -0,0 +1,53 @@
# WeirdPerformance 2.4-B2 — GC Safe Sweep Reload Guard
Strict A/B experiment for WoW 1.12.1 build 5875.
## Baseline
Keep the validated 2.4-A binary unchanged:
- `weirdperformance.dll`
- SHA-256: `d35168ae06c19087ef9b7c68918a054640396dfbfcef0664e18e9372284b5eef`
## B2 variant
Add:
- `weirdperformance_gc24b2.dll`
B2 is generated from the ABI-fixed B1 source with one isolated behavioral change:
- whenever a new Lua `global_State` is observed (initial world entry, `/reload`, logout/relog), the first **3 GC collections stay fully native**;
- after those 3 native collections, the exact B1 incremental rootgc safe-sweep behavior resumes.
Nothing else is changed: same 50,000-object chunk size, same native mark/userdata/string work, same birth-mark ownership checks, same `lua_close` reconnect safety, same `IS_IN_WORLD` guard, no allocator changes, no generational age bitmap, no write barriers, no profiling/RDTSC.
The B2 delta is applied reproducibly by `b2_patch.py`; the build fails if any expected B1 source anchor no longer matches.
## Why this test exists
A delayed crash can be caused by corruption that happened earlier, so the crashing stack does not have to contain the GC companion. B2 specifically reduces risk during Lua state recreation without changing the core GC experiment.
## Installation
Keep both DLLs next to WoW and list both in `dlls.txt`:
```text
weirdperformance.dll
weirdperformance_gc24b2.dll
```
Remove `weirdperformance_gc24b1.dll` while testing B2. Never load B1 and B2 together.
Removing `weirdperformance_gc24b2.dll` returns exactly to the validated 2.4-A baseline.
## First test
1. Launch and enter the world.
2. Play normally for 1015 minutes.
3. Do one `/reload`, then wait and play for several minutes.
4. If clean, do 10 `/reload` total.
5. Then test logout/relog and character changes.
6. Keep all other DLLs/addons/settings unchanged for the A/B comparison.
If an ERROR #132 occurs, keep both `Crash.txt` and `Crash.dmp` and note what happened shortly before the crash.
+79
View File
@@ -0,0 +1,79 @@
from pathlib import Path
root = Path(__file__).resolve().parent
main_path = root / "main.zig"
build_path = root / "build.zig"
main = main_path.read_text(encoding="utf-8")
build = build_path.read_text(encoding="utf-8")
replacements = [
(
"const BATCH_HEADROOM: u32 = 128 * 1024;\n",
"const BATCH_HEADROOM: u32 = 128 * 1024;\n"
"const NATIVE_GRACE_COLLECTIONS: u32 = 3;\n",
),
(
"var installed = false;\nvar in_gc = false;\nvar closing_lua = false;\n",
"var installed = false;\nvar in_gc = false;\nvar closing_lua = false;\n"
"var active_g: u32 = 0;\n"
"var native_grace_collections: u32 = 0;\n",
),
(
" // Never carry private list state into another Lua global_State.\n"
" if (sweeping and g != saved_g) {\n"
" resetSweepState();\n"
" collect_hook.callOriginal(.{L});\n"
" return;\n"
" }\n\n"
" // If another module changed the birth byte during our split cycle,\n",
" // Never carry private list state into another Lua global_State.\n"
" if (sweeping and g != saved_g) {\n"
" resetSweepState();\n"
" collect_hook.callOriginal(.{L});\n"
" return;\n"
" }\n\n"
" // B2: when a fresh Lua global_State appears (initial world entry,\n"
" // /reload, logout/relog), keep the first three collections fully\n"
" // native. This gives WoW ownership of the fragile state-rebuild\n"
" // window before incremental rootgc sweeping resumes.\n"
" if (active_g != g) {\n"
" active_g = g;\n"
" native_grace_collections = NATIVE_GRACE_COLLECTIONS;\n"
" }\n"
" if (native_grace_collections != 0) {\n"
" native_grace_collections -= 1;\n"
" collect_hook.callOriginal(.{L});\n"
" return;\n"
" }\n\n"
" // If another module changed the birth byte during our split cycle,\n",
),
(
" reconnectSweep();\n in_gc = false;\n\n lua_close_hook.callOriginal(.{L});\n",
" reconnectSweep();\n in_gc = false;\n"
" active_g = 0;\n"
" native_grace_collections = 0;\n\n"
" lua_close_hook.callOriginal(.{L});\n",
),
(
'const version: [*:0]const u8 = "2.4-B1-gc-safe-sweep-abi";\n',
'const version: [*:0]const u8 = "2.4-B2-gc-safe-sweep-reload-guard";\n',
),
]
for old, new in replacements:
count = main.count(old)
if count != 1:
raise SystemExit(f"main.zig patch anchor mismatch: expected 1, found {count}: {old[:80]!r}")
main = main.replace(old, new, 1)
old_name = '.name = "weirdperformance_gc24b1",'
new_name = '.name = "weirdperformance_gc24b2",'
if build.count(old_name) != 1:
raise SystemExit("build.zig output-name anchor mismatch")
build = build.replace(old_name, new_name, 1)
main_path.write_text(main, encoding="utf-8")
build_path.write_text(build, encoding="utf-8")
print("B2 patch applied: new-global_State native grace = 3 GC cycles")