From 27b9254ab80a70b80c876c187918da89ec1ea852 Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Sat, 11 Apr 2026 13:21:51 -0700 Subject: [PATCH] weirdperformance: consolidate luastr and luavm as sub-modules Drop the top-level luastr and luavm module flags and wire both through weirdperformance, matching the existing luaalloc/luagc sub-module pattern. Also removes luavm's A/B rdtsc instrumentation now that the newlstr hash pre-check is production-only. - build.zig: remove luastr/luavm from module_list - DLL_README.md: add Lua Runtime bullet under Performance, swap em/en-dashes for ASCII - src/luavm/: delete (files moved into src/weirdperformance/ which was already the tracked location in HEAD) --- DLL_README.md | 13 +-- build.zig | 4 +- src/luavm/GC_WRITE_BARRIER.md | 67 ------------ src/luavm/luavm.zig | 198 ---------------------------------- 4 files changed, 9 insertions(+), 273 deletions(-) delete mode 100644 src/luavm/GC_WRITE_BARRIER.md delete mode 100644 src/luavm/luavm.zig diff --git a/DLL_README.md b/DLL_README.md index 597db56..0439320 100644 --- a/DLL_README.md +++ b/DLL_README.md @@ -234,7 +234,7 @@ This value is saved to the `cursorScale` CVar in tenths: `/script SetCVar("curso Lua API for addon developers: -- `SetCursorScale(n)` -- set scale factor (1.0–4.0), takes effect on next cursor change +- `SetCursorScale(n)` -- set scale factor (1.0-4.0), takes effect on next cursor change - `GetCursorScale()` -- returns current scale factor **DLL:** `bigcursor.dll` @@ -245,12 +245,13 @@ Lua API for addon developers: Engine-level optimizations that reduce CPU time on math, rendering helpers, file lookups, and data decompression. -- **SIMD Math** — replaces 20+ internal math functions with SSE/AVX equivalents covering skeletal animation, particle rendering, frustum culling, collision detection, text glyph caching, and float-to-integer conversion -- **Data Decompression** — swaps the game's 2004-era zlib with a modern library (2.2x faster). Loading screen times reduced by at least 13% -- **MPQ File Cache** — caches archive file lookups so repeat file opens skip the archive chain walk. Saving 50-160ms every 15 seconds during heavy gameplay -- **Timer Calibration** — recalibrates the OS performance counter for accurate animation timing. Ported from [VanillaFixes](https://github.com/hannesmann/vanillafixes) +- **SIMD Math** -- replaces 20+ internal math functions with SSE/AVX equivalents covering skeletal animation, particle rendering, frustum culling, collision detection, text glyph caching, and float-to-integer conversion +- **Data Decompression** -- swaps the game's 2004-era zlib with a modern library (2.2x faster). Loading screen times reduced by at least 13% +- **MPQ File Cache** -- caches archive file lookups so repeat file opens skip the archive chain walk. Saving 50-160ms every 15 seconds during heavy gameplay +- **Timer Calibration** -- recalibrates the OS performance counter for accurate animation timing. Ported from [VanillaFixes](https://github.com/hannesmann/vanillafixes) +- **Lua Runtime** -- custom slab allocator (O(1) free/realloc), incremental/generational GC (turns the ~5s stop-the-world freeze into ~9ms chunks), faster string interning (~40% on `luaS_newlstr`), and a literal prefilter on `string.find`/`gfind`/`gsub` that kills the O(n²) backtracking addon combat log parsers inflict on every chat message -Most noticeable in cities, raids, and during zone transitions. +Most noticeable in cities, raids, during zone transitions, and in addon-heavy setups. **DLL:** `weirdperformance.dll` diff --git a/build.zig b/build.zig index 9d3d0ea..0c58dc8 100644 --- a/build.zig +++ b/build.zig @@ -35,9 +35,9 @@ const module_list = [_]ModuleDesc{ .{ .name = "addonperf", .version = "1.0", .desc = "Enable addon memory/CPU profiling API", .default = false }, .{ .name = "ssemaths", .version = "1.0", .desc = "Enable UnitXP x87 math polyfill replacements (SSE)", .default = false }, .{ .name = "silicon", .version = "1.0", .desc = "Enable SSE2 math replacements (ported from libSiliconPatch)", .default = false }, - .{ .name = "weirdperformance", .version = "1.1.1", .desc = "Enable production performance optimizations (SSE, inflate, filecache, timer)", .default = true }, - .{ .name = "luavm", .version = "0.1", .desc = "Enable Lua VM hotspot optimizations (hash, string intern, opcode SSE)", .default = false }, + .{ .name = "weirdperformance", .version = "1.1.1", .desc = "Enable production performance optimizations (SSE, inflate, filecache, timer, luastr, luavm)", .default = true }, .{ .name = "superweirdo", .version = "0.1", .desc = "Enable GO loot sparkle on interactable objects", .default = false }, + .{ .name = "luagc", .version = "0.1", .desc = "Enable incremental Lua GC (replaces stop-the-world mark+sweep)", .default = false }, }; pub fn build(b: *std.Build) void { diff --git a/src/luavm/GC_WRITE_BARRIER.md b/src/luavm/GC_WRITE_BARRIER.md deleted file mode 100644 index 8e51a14..0000000 --- a/src/luavm/GC_WRITE_BARRIER.md +++ /dev/null @@ -1,67 +0,0 @@ -# Lua 5.0 GC Write Barrier in lua_vm_execute - -## Location - -`lua_vm_execute` (0x6F8720), the main VM interpreter loop. -The barrier appears after every TValue copy (~20 sites in the function). - -## Globals - -- `0xCEEAC0` -- GC barrier value (current white marker / gc object pointer) -- `0xCEEAC4` -- GC barrier flag (non-zero = barrier is active) - -## Pattern (from disassembly) - -After copying a 16-byte TValue (type_tag, gc_ptr, value_lo, value_hi): - -```asm -mov eax, [dst + 4] ; eax = copied gc_ptr field -test eax, eax -jz skip ; NULL gc_ptr -> no barrier needed -cmp dword ptr [0xCEEAC4], 0 -jz skip ; barrier disabled -> skip -mov [0xCEEAC0], eax ; mark: write gc_ptr into barrier global -skip: -``` - -## What it does - -The gc_ptr field at TValue+0x04 holds a pointer to a GC-managed object -(string, table, closure, userdata) or NULL for non-collectable types -(number, boolean, nil, lightuserdata). - -When a TValue is copied (MOVE, GETGLOBAL, GETTABLE, LOADK, etc.), the -barrier checks: -1. Is the copied value a GC object? (gc_ptr != NULL) -2. Is the write barrier active? (flag at 0xCEEAC4 != 0) -3. If both true, write the gc_ptr to the barrier global at 0xCEEAC0 - -This is Lua 5.0's incremental GC write barrier. It tracks which GC objects -have been moved/copied so the collector knows which objects are reachable -from newly-written locations. The barrier global accumulates the "last written" -gc object -- the actual GC uses this to avoid rescanning the full root set. - -## Opcodes that trigger the barrier - -Every opcode that writes a TValue to a register or table slot: -- MOVE (op 0) -- LOADK (op 1) -- loads constant, has gc_ptr for string constants -- GETUPVAL (op 4) -- GETGLOBAL (op 5) -- GETTABLE (op 6) -- SETGLOBAL (op 7) -- barrier on the table side -- SETTABLE (op 9) -- NEWTABLE (op 10) -- SELF (op 11) -- CONCAT (op 21) -- CLOSURE (op 30) -- FORLOOP (op 23) -- number only, but barrier still present - -## Relevance to luagc module - -Our luagc module (in weirdperformance) hooks `lua_gc_step` (0x6FAE00). -Understanding the barrier globals is useful for: -- Knowing when/how often the barrier fires during heavy addon activity -- Potentially batching barrier writes if we ever replace the GC step -- The flag at 0xCEEAC4 could be used to temporarily disable the barrier - during bulk operations (dangerous -- must re-enable before GC runs) diff --git a/src/luavm/luavm.zig b/src/luavm/luavm.zig deleted file mode 100644 index 5514e2f..0000000 --- a/src/luavm/luavm.zig +++ /dev/null @@ -1,198 +0,0 @@ -//! luavm -- Lua VM hotspot optimizations. -//! -//! luaS_newlstr (0x6F9D00, 1.35% CPU): hash pre-check before memcmp. -//! A/B: alternating calls, per-call rdtsc, periodic dump via OnWorldUpdate. - -const hook = @import("zhook"); -const logging = @import("../logging.zig"); -const mod_mutex = @import("../mutex.zig"); - -pub const module_name: [*:0]const u8 = "luavm"; - -var g_is_hook_owner: bool = false; -var log: logging.Logger = .{}; - -pub fn isActive() bool { - return g_is_hook_owner; -} - -// ============================================================================= -// A/B instrumentation -// ============================================================================= - -inline fn rdtsc() u64 { - var lo: u32 = undefined; - var hi: u32 = undefined; - asm volatile ("rdtsc" - : [lo] "={eax}" (lo), - [hi] "={edx}" (hi), - ); - return @as(u64, hi) << 32 | lo; -} - -const AB_DUMP_INTERVAL: u64 = 10000; - -const ABStats = struct { - cycles: u64 = 0, - calls: u64 = 0, -}; - -var custom_ab: ABStats = .{}; -var baseline_ab: ABStats = .{}; - -// ============================================================================= -// luaS_newlstr (0x6F9D00) -// __fastcall(ECX=lua_State*, EDX=str_ptr, stack=len) -> TString* -// RET 0x4 -// ============================================================================= - -const NewLStrFn = fn (u32, u32, u32) callconv(hook.cc.fastcall) u32; -var newlstr_hook: hook.Detour(NewLStrFn) = .{}; - -var call_ctr: u32 = 0; - -fn luaCreateStringObject(state: u32, str_ptr: u32, len: u32, hash_val: u32) u32 { - return hook.call( - fn (u32, u32, u32, u32) callconv(hook.cc.fastcall) u32, - 0x6F9D90, - .{ state, str_ptr, len, hash_val }, - ); -} - -fn newlstrDetour(state: u32, str_ptr: u32, len: u32) callconv(hook.cc.fastcall) u32 { - asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true }); - - const use_custom = (call_ctr & 1) == 0; - call_ctr +%= 1; - const t0 = rdtsc(); - - const result = if (use_custom) - newlstrImpl(state, str_ptr, len) - else - newlstr_hook.callOriginal(.{ state, str_ptr, len }); - - const elapsed = rdtsc() - t0; - if (use_custom) { - custom_ab.cycles +|= elapsed; - custom_ab.calls +|= 1; - } else { - baseline_ab.cycles +|= elapsed; - baseline_ab.calls +|= 1; - } - return result; -} - -fn newlstrImpl(state: u32, str_ptr: u32, len: u32) u32 { - const str: [*]const u8 = @ptrFromInt(str_ptr); - var h: u32 = len; - const step: u32 = (len >> 5) + 1; - var l1: u32 = len; - while (l1 >= step) { - const c: u32 = str[l1 - 1]; - h = h ^ (c +% (h << 5) +% (h >> 2)); - l1 -= step; - } - - const global_state: u32 = hook.readMem(u32, state + 0x10); - const strt_hash: u32 = hook.readMem(u32, global_state + 0x04); - const strt_size: u32 = hook.readMem(u32, global_state + 0x0C); - - const bucket: u32 = h & (strt_size - 1); - var ts: u32 = hook.readMem(u32, strt_hash + bucket * 4); - - while (ts != 0) { - const ts_len: u32 = hook.readMem(u32, ts + 0x0C); - if (ts_len == len) { - const ts_hash: u32 = hook.readMem(u32, ts + 0x08); - if (ts_hash == h) { - if (len == 0 or strEqual(str_ptr, ts + 0x10, len)) { - return ts; - } - } - } - ts = hook.readMem(u32, ts); - } - - return luaCreateStringObject(state, str_ptr, len, h); -} - -fn strEqual(a_ptr: u32, b_ptr: u32, len: u32) bool { - const a: [*]const u8 = @ptrFromInt(a_ptr); - const b: [*]const u8 = @ptrFromInt(b_ptr); - - var i: u32 = 0; - while (i + 4 <= len) : (i += 4) { - const va = @as(*align(1) const u32, @ptrCast(a + i)).*; - const vb = @as(*align(1) const u32, @ptrCast(b + i)).*; - if (va != vb) return false; - } - while (i < len) : (i += 1) { - if (a[i] != b[i]) return false; - } - return true; -} - -// ============================================================================= -// OnWorldUpdate (0x482EA0) -- periodic stats dump -// ============================================================================= - -const WorldUpdateFn = fn (u32) callconv(hook.cc.fastcall) void; -var world_update_hook: hook.Detour(WorldUpdateFn) = .{}; - -var frame_count: u64 = 0; - -fn worldUpdateDetour(fc: u32) callconv(hook.cc.fastcall) void { - frame_count +%= 1; - - if (frame_count % AB_DUMP_INTERVAL == 0 and frame_count > 0) { - dumpStats(); - } - - world_update_hook.callOriginal(.{fc}); -} - -fn dumpStats() void { - const ca = custom_ab.calls; - const ba = baseline_ab.calls; - const c_avg: u64 = if (ca > 0) custom_ab.cycles / ca else 0; - const b_avg: u64 = if (ba > 0) baseline_ab.cycles / ba else 0; - - log.fmt("[luavm] {d}f newlstr: c={d} b={d} cyc/call ({d}k calls)\n", .{ - frame_count, c_avg, b_avg, ca / 1000, - }); - - custom_ab = .{}; - baseline_ab = .{}; -} - -// ============================================================================= -// Install / Remove -// ============================================================================= - -pub fn installHooks() void { - const result = mod_mutex.acquire(module_name); - g_is_hook_owner = result.is_owner; - if (!g_is_hook_owner) return; - - log = logging.Logger.open(module_name, .both); - - if (newlstr_hook.attach(0x6F9D00, &newlstrDetour) == .ok) { - log.print(" newlstr: A/B hash pre-check vs original\n"); - } - - if (world_update_hook.attach(0x482EA0, &worldUpdateDetour) == .ok) { - log.print(" OnWorldUpdate: periodic stats dump\n"); - } - - log.print("luavm: active\n"); -} - -pub fn removeHooks() void { - if (g_is_hook_owner) { - dumpStats(); - world_update_hook.detach(); - newlstr_hook.detach(); - log.close(); - } - g_is_hook_owner = false; -}