Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2b8d2a7e0 | |||
| 5dde1f641e | |||
| d6262135a8 |
@@ -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
|
||||
@@ -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 10–15 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.
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user