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)
This commit is contained in:
MarcelineVQ
2026-04-11 13:21:51 -07:00
parent 039cec1806
commit 27b9254ab8
4 changed files with 9 additions and 273 deletions
+7 -6
View File
@@ -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`
+2 -2
View File
@@ -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 {
-67
View File
@@ -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)
-198
View File
@@ -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;
}