149 lines
5.7 KiB
YAML
149 lines
5.7 KiB
YAML
name: Build WeirdPerformance 2.4-B2 in-place GC test
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- test/wp24b2-gc-inplace-sweep
|
|
paths:
|
|
- 'experiments/wp24b-gc/**'
|
|
- '.github/workflows/wp24b2-inplace-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
|
|
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-inplace-safe-sweep'
|
|
|
|
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}")
|
|
|
|
def callback_snippet(target, window):
|
|
m = re.search(rf"mov\s+edx,\s*0x{target}.{{0,240}}?push\s+0x([0-9a-f]+)", asm, re.S)
|
|
if not m:
|
|
raise SystemExit(f"ABI validation failed: callback for 0x{target} not found")
|
|
cb = m.group(1).lstrip("0") or "0"
|
|
pos = asm.find(cb + ":")
|
|
if pos < 0:
|
|
raise SystemExit(f"ABI validation failed: callback body {cb} not found")
|
|
return asm[pos:pos+window]
|
|
|
|
# Collector callback: same proven B1 code shape, L copied from ECX.
|
|
collector = callback_snippet("6f7340", 900)
|
|
if not re.search(r"mov\s+esi,\s*ecx", collector):
|
|
raise SystemExit("ABI validation failed: collector callback does not consume L from ECX")
|
|
|
|
# B2 lua_close has no helper call before callOriginal: Zig can legally
|
|
# leave the incoming fastcall ECX untouched and call the trampoline
|
|
# directly. Accept either explicit preservation or verified no write
|
|
# to ECX before the first call instruction.
|
|
close = callback_snippet("6f6ef0", 800)
|
|
first_call = re.search(r"\bcall\b", close)
|
|
if not first_call:
|
|
raise SystemExit("ABI validation failed: lua_close trampoline call missing")
|
|
before_call = close[:first_call.start()]
|
|
ecx_saved = re.search(
|
|
r"(?:mov\s+(?:esi|edi|ebx|ebp|eax|edx),\s*ecx|push\s+ecx|mov\s+(?:dword ptr\s*)?\[[^\]]+\],\s*ecx)",
|
|
before_call,
|
|
)
|
|
ecx_written = re.search(
|
|
r"\b(?:mov|lea|xor|and|or|add|sub|imul|inc|dec|pop)\s+ecx\b",
|
|
before_call,
|
|
)
|
|
if not ecx_saved and ecx_written:
|
|
print("lua_close callback snippet:")
|
|
print(close)
|
|
raise SystemExit("ABI validation failed: lua_close clobbers incoming ECX before trampoline")
|
|
|
|
# Native helper ABI observed directly in WoW 5875:
|
|
# lua_gc_remove_objects: ECX=L, EDX=&list, stack arg all=0.
|
|
require(
|
|
r"lea\s+edx,\s*\[edi\s*\+\s*0x14\].{0,200}?"
|
|
r"mov\s+eax,\s*0x6f7210.{0,160}?"
|
|
r"mov\s+ecx,\s*esi.{0,160}?"
|
|
r"push\s+0x0.{0,100}?call\s+eax",
|
|
"rootudata lua_gc_remove_objects ABI",
|
|
)
|
|
|
|
# At least one dynamic-list remove call must load L into ECX.
|
|
require(
|
|
r"mov\s+eax,\s*0x6f7210.{0,240}?mov\s+ecx,\s*e[a-z]{2}.{0,200}?call\s+eax",
|
|
"chunk lua_gc_remove_objects fastcall callsite",
|
|
)
|
|
|
|
require(
|
|
r"mov\s+eax,\s*0x6f72f0.{0,160}?"
|
|
r"mov\s+ecx,\s*esi.{0,120}?"
|
|
r"xor\s+edx,\s*edx.{0,100}?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,180}}?mov\s+ecx,\s*e[a-z]{{2}}.{{0,120}}?call\s+eax",
|
|
f"{label} ECX ABI",
|
|
)
|
|
|
|
print("ABI validation: PASS")
|
|
PY
|
|
|
|
sha256sum "$DLL" | tee SHA256SUMS.txt
|
|
- uses: actions/upload-artifact@v4
|
|
if: always()
|
|
with:
|
|
name: WeirdPerformance-2.4-B2-GC-InPlace-Safe-Sweep
|
|
path: |
|
|
experiments/wp24b-gc/zig-out/bin/weirdperformance_gc24b2.dll
|
|
experiments/wp24b-gc/SHA256SUMS.txt
|
|
experiments/wp24b-gc/BINARY_INFO.txt
|
|
experiments/wp24b-gc/ABI_DISASM.txt
|
|
if-no-files-found: warn
|