diff --git a/src/bench/main.zig b/src/bench/main.zig index ac75906..81334a2 100644 --- a/src/bench/main.zig +++ b/src/bench/main.zig @@ -35,19 +35,19 @@ extern fn si_normalizeVec3(u32, u32) callconv(cc_tc) void; extern fn si_mulMat3x4(u32, u32, u32) callconv(cc_fc) u32; extern fn si_rotateMatByQuat(u32, u32) callconv(cc_tc) u32; extern fn si_createRotMat3x4(u32, u32, u32, u32) callconv(cc_fc) u32; -extern fn si_distanceToPlane() callconv(.naked) void; // naked: ECX=point, EDX=plane, stack=dir, returns ST(0), RET 4 +extern fn si_distanceToPlane(u32, u32, u32) callconv(cc_fc) f64; extern fn si_classifyPointFrustum(u32, u32, u32) callconv(cc_tc) u32; extern fn si_checkBoxLineIntersect(u32, u32, u32) callconv(cc_fc) u32; extern fn si_testOBBFrustum(u32, u32, u32, u32) callconv(cc_tc) u32; extern fn si_testSphereFrustum(u32, u32) callconv(cc_tc) u32; extern fn si_quatSlerp(u32, u32, u32, u32) callconv(cc_fc) u32; -extern fn si_isPointInsideBounds() callconv(.naked) void; +extern fn si_isPointInsideBounds(u32, u32) callconv(cc_fc) u32; extern fn si_calculateSinCos(u32, u32, u32) callconv(cc_sc) void; extern fn si_createZRotMat3x3(u32, u32) callconv(cc_tc) u32; extern fn si_transposeMat4x4(u32, u32) callconv(cc_tc) u32; extern fn si_mulMat3x4InPlace(u32, u32) callconv(cc_tc) u32; extern fn si_normalizeVec3InPlace(u32) callconv(cc_fc) void; -extern fn si_vec3Dot() callconv(.naked) void; // naked: ECX=a, EDX=b, returns ST(0) +extern fn si_vec3Dot(u32, u32) callconv(cc_fc) f64; extern fn si_translateBoundingVol(u32, u32) callconv(cc_tc) void; extern fn si_addVec3ToAccumulator(u32, u32, u32) callconv(cc_tc) void; extern fn si_addToColorAccumulator(u32, u32) callconv(cc_tc) void; @@ -481,90 +481,28 @@ pub fn main() void { print("\n{s}\n", .{"--- SILICON SSE functions ---"}); // si_isPointInsideBounds (1.7M/7.5s) -- fastcall(vecA_ECX, vecB_EDX) -> u32 - // Patch-in-place test: overwrite bytes at 0x699330 with SSE version { const va2 = tv3(); const vb2 = Vec3{ 1.0, -3.0, 0.5 }; // all <= va - const FnType = fn (u32, u32) callconv(cc_fc) u32; - const target: [*]u8 = @ptrFromInt(0x699330); - const si_ptr: [*]const u8 = @ptrFromInt(@intFromPtr(&si_isPointInsideBounds)); - - // Find patch size (scan for RET after the first RET — 2 exit paths) - var patch_size: usize = 0; - var rets: u32 = 0; - while (patch_size < 46) : (patch_size += 1) { - if (si_ptr[patch_size] == 0xC3) { - rets += 1; - if (rets == 2) { patch_size += 1; break; } - } - } - - var orig_bytes: [46]u8 = undefined; - @memcpy(&orig_bytes, target[0..46]); - - // Parity: original - const f: *const FnType = @ptrCast(@alignCast(@as(*const anyopaque, @ptrFromInt(0x699330)))); - const ov = f(a(&va2), a(&vb2)); - - // Patch + test - @memcpy(target[0..patch_size], si_ptr[0..patch_size]); - const sv = f(a(&va2), a(&vb2)); + const of = origFn(fn (u32, u32) callconv(cc_fc) u32, 0x699330); + const ov = of(a(&va2), a(&vb2)); + const sv = si_isPointInsideBounds(a(&va2), a(&vb2)); const ok = ov == sv; - - // Bench original - @memcpy(target[0..46], &orig_bytes); - var t_best: u64 = std.math.maxInt(u64); - for (0..5) |_| { - var sum: u32 = 0; - const t0 = rdtsc(); - for (0..ITERS) |_| { sum +%= f(a(&va2), a(&vb2)); } - const elapsed = rdtsc() - t0; - if (elapsed < t_best) t_best = elapsed; - std.mem.doNotOptimizeAway(sum); - } - - // Bench patched - @memcpy(target[0..patch_size], si_ptr[0..patch_size]); - var s_best: u64 = std.math.maxInt(u64); - for (0..5) |_| { - var sum: u32 = 0; - const s0 = rdtsc(); - for (0..ITERS) |_| { sum +%= f(a(&va2), a(&vb2)); } - const elapsed = rdtsc() - s0; - if (elapsed < s_best) s_best = elapsed; - std.mem.doNotOptimizeAway(sum); - } - - @memcpy(target[0..46], &orig_bytes); - report("isPointInsideBounds", t_best, s_best, ok); + var t: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = of(a(&va2), a(&vb2)); } const _te = rdtsc() - _t0; if (_te < t) t = _te; } + var s: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = si_isPointInsideBounds(a(&va2), a(&vb2)); } const _te = rdtsc() - _t0; if (_te < s) s = _te; } + report("isPointInsideBounds", t, s, ok); } - // si_vec3Dot (31K/7.5s) -- fastcall(vecA_ECX, vecB_EDX) -> ST(0) - // Both original and SSE are naked/fastcall, call via asm + // si_vec3Dot (31K/7.5s) -- fastcall(vecA_ECX, vecB_EDX) -> f64 { const va2 = tv3(); const vb2 = tv3b(); - const callDot = struct { - fn call(func: u32, va_ptr: u32, vb_ptr: u32) f64 { - var result: f64 = undefined; - var eax_trash: u32 = undefined; - asm volatile ( - \\call *%[func] - \\fstpl (%[out]) - : [eax_out] "={eax}" (eax_trash), - : [func] "r" (func), - [out] "r" (&result), - [_ecx] "{ecx}" (va_ptr), - [_edx] "{edx}" (vb_ptr), - ); - return result; - } - }.call; - const ov = callDot(0x602630, a(&va2), a(&vb2)); - const sv = callDot(@intFromPtr(&si_vec3Dot), a(&va2), a(&vb2)); + const of = origFn(fn (u32, u32) callconv(cc_fc) f64, 0x602630); + const ov = of(a(&va2), a(&vb2)); + const sv = si_vec3Dot(a(&va2), a(&vb2)); const ok = @abs(ov - sv) < 1e-4; - var t: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = callDot(0x602630, a(&va2), a(&vb2)); } const _te = rdtsc() - _t0; if (_te < t) t = _te; } - var s: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = callDot(@intFromPtr(&si_vec3Dot), a(&va2), a(&vb2)); } const _te = rdtsc() - _t0; if (_te < s) s = _te; } + var t: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = of(a(&va2), a(&vb2)); } const _te = rdtsc() - _t0; if (_te < t) t = _te; } + var s: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = si_vec3Dot(a(&va2), a(&vb2)); } const _te = rdtsc() - _t0; if (_te < s) s = _te; } report("si_vec3Dot", t, s, ok); } @@ -587,14 +525,12 @@ pub fn main() void { const pt = tv3(); const plane = [4]f32{ 0.0, 1.0, 0.0, -5.0 }; // y=5 plane const dir = Vec3{ 0.0, -1.0, 0.0 }; // pointing down - const FnType = fn (u32, u32, u32) callconv(cc_fc) f64; - const of: *const FnType = origFn(FnType, 0x6329E0); - const sf: *const FnType = @ptrCast(&si_distanceToPlane); + const of = origFn(fn (u32, u32, u32) callconv(cc_fc) f64, 0x6329E0); const ov = of(a(&pt), a(&plane), a(&dir)); - const sv = sf(a(&pt), a(&plane), a(&dir)); + const sv = si_distanceToPlane(a(&pt), a(&plane), a(&dir)); const ok = @abs(ov - sv) < 1e-2; var t: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = of(a(&pt), a(&plane), a(&dir)); } const _te = rdtsc() - _t0; if (_te < t) t = _te; } - var s: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = sf(a(&pt), a(&plane), a(&dir)); } const _te = rdtsc() - _t0; if (_te < s) s = _te; } + var s: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { _ = si_distanceToPlane(a(&pt), a(&plane), a(&dir)); } const _te = rdtsc() - _t0; if (_te < s) s = _te; } report("distanceToPlane", t, s, ok); } diff --git a/src/silicon/silicon.zig b/src/silicon/silicon.zig index 33793f4..a4ffd4e 100644 --- a/src/silicon/silicon.zig +++ b/src/silicon/silicon.zig @@ -2114,7 +2114,86 @@ pub fn installHooks() void { g_is_hook_owner = result.is_owner; if (!g_is_hook_owner) return; log_state = logging.Logger.open(module_name, .both); - log_state.print("silicon: module loaded (probe hooks deferred to lateInit)\n"); + + // Binary-patch SSE replacements immediately at DLL load + const patched = installPatches(); + log_state.fmt("silicon: {d} JMP patches installed\n", .{patched}); +} + +// ============================================================================= +// Binary patching: write JMP rel32 at each game function to our SSE replacement. +// No trampoline, no CC translation — our functions use the game's native CC. +// ============================================================================= + +const sse = struct { + // silicon_sse.zig exports (linked via object file) + extern fn si_normalizeVec3(u32, u32) callconv(TC) void; + extern fn si_mulMat3x4(u32, u32, u32) callconv(FC) u32; + extern fn si_rotateMatByQuat(u32, u32) callconv(TC) u32; + extern fn si_createRotMat3x4(u32, u32, u32, u32) callconv(FC) u32; + extern fn si_distanceToPlane() callconv(.naked) void; + extern fn si_classifyPointFrustum(u32, u32, u32) callconv(TC) u32; + extern fn si_checkBoxLineIntersect(u32, u32, u32) callconv(FC) u32; + extern fn si_testOBBFrustum(u32, u32, u32, u32) callconv(TC) u32; + extern fn si_testSphereFrustum(u32, u32) callconv(TC) u32; + extern fn si_quatSlerp(u32, u32, u32, u32) callconv(FC) u32; + extern fn si_isPointInsideBounds() callconv(.naked) void; + extern fn si_calculateSinCos(u32, u32, u32) callconv(SC) void; + extern fn si_createZRotMat3x3(u32, u32) callconv(TC) u32; + extern fn si_transposeMat4x4(u32, u32) callconv(TC) u32; + extern fn si_mulMat3x4InPlace(u32, u32) callconv(TC) u32; + extern fn si_normalizeVec3InPlace(u32) callconv(FC) void; + extern fn si_addVec3ToAccumulator(u32, u32, u32) callconv(TC) void; + extern fn si_addToColorAccumulator(u32, u32) callconv(TC) void; + extern fn si_packParticleColor(u32, u32, u32, u32) callconv(FC) void; + extern fn si_setParticleAlpha(u32, u32) callconv(FC) void; + extern fn si_ftol() callconv(.naked) void; + extern fn si_vec3Dot() callconv(.naked) void; + extern fn si_translateBoundingVol(u32, u32) callconv(TC) void; +}; + +const PatchEntry = struct { target: u32, replacement: u32, name: [*:0]const u8 }; + +fn getPatchTable() []const PatchEntry { + const table = [_]PatchEntry{ + .{ .target = 0x4549C0, .replacement = @intFromPtr(&sse.si_normalizeVec3), .name = "normalizeVec3" }, + .{ .target = 0x7BAE60, .replacement = @intFromPtr(&sse.si_mulMat3x4), .name = "mulMat3x4" }, + .{ .target = 0x7BDDB0, .replacement = @intFromPtr(&sse.si_rotateMatByQuat), .name = "rotateMatByQuat" }, + .{ .target = 0x7BB860, .replacement = @intFromPtr(&sse.si_createRotMat3x4), .name = "createRotMat3x4" }, + .{ .target = 0x6329E0, .replacement = @intFromPtr(&sse.si_distanceToPlane), .name = "distanceToPlane" }, + .{ .target = 0x686C20, .replacement = @intFromPtr(&sse.si_classifyPointFrustum), .name = "classifyPointFrustum" }, + .{ .target = 0x6DC5A0, .replacement = @intFromPtr(&sse.si_checkBoxLineIntersect), .name = "checkBoxLineIntersect" }, + .{ .target = 0x6869C0, .replacement = @intFromPtr(&sse.si_testOBBFrustum), .name = "testOBBFrustum" }, + .{ .target = 0x686B80, .replacement = @intFromPtr(&sse.si_testSphereFrustum), .name = "testSphereFrustum" }, + .{ .target = 0x7C0570, .replacement = @intFromPtr(&sse.si_quatSlerp), .name = "quatSlerp" }, + .{ .target = 0x699330, .replacement = @intFromPtr(&sse.si_isPointInsideBounds), .name = "isPointInsideBounds" }, + .{ .target = 0x749280, .replacement = @intFromPtr(&sse.si_calculateSinCos), .name = "calculateSinCos" }, + .{ .target = 0x7BE5B0, .replacement = @intFromPtr(&sse.si_createZRotMat3x3), .name = "createZRotMat3x3" }, + .{ .target = 0x7BCEF0, .replacement = @intFromPtr(&sse.si_transposeMat4x4), .name = "transposeMat4x4" }, + .{ .target = 0x7BB420, .replacement = @intFromPtr(&sse.si_mulMat3x4InPlace), .name = "mulMat3x4InPlace" }, + .{ .target = 0x6720F0, .replacement = @intFromPtr(&sse.si_normalizeVec3InPlace), .name = "normalizeVec3InPlace" }, + .{ .target = 0x71BC70, .replacement = @intFromPtr(&sse.si_addVec3ToAccumulator), .name = "addVec3ToAccumulator" }, + .{ .target = 0x71BF60, .replacement = @intFromPtr(&sse.si_addToColorAccumulator), .name = "addToColorAccumulator" }, + .{ .target = 0x7B7A80, .replacement = @intFromPtr(&sse.si_packParticleColor), .name = "packParticleColor" }, + .{ .target = 0x7B7B10, .replacement = @intFromPtr(&sse.si_setParticleAlpha), .name = "setParticleAlpha" }, + .{ .target = 0x40A2B0, .replacement = @intFromPtr(&sse.si_ftol), .name = "__ftol" }, + .{ .target = 0x602630, .replacement = @intFromPtr(&sse.si_vec3Dot), .name = "vec3Dot" }, + .{ .target = 0x686820, .replacement = @intFromPtr(&sse.si_translateBoundingVol), .name = "translateBoundingVol" }, + }; + return &table; +} + +fn installPatches() u32 { + var count: u32 = 0; + for (getPatchTable()) |entry| { + // Write JMP rel32: E9 XX XX XX XX + const rel = @as(i32, @bitCast(entry.replacement -% entry.target -% 5)); + var patch = [5]u8{ 0xE9, 0, 0, 0, 0 }; + @as(*align(1) i32, @ptrCast(patch[1..5])).* = rel; + hook.writeProtected(entry.target, &patch); + count += 1; + } + return count; } /// Called from engineInitDetour after engine is fully initialized. @@ -2123,11 +2202,9 @@ pub fn lateInit() void { var installed: u32 = 0; - // Debug: only install ftol to isolate visual issues - const FTOL_ONLY = true; - - // Cat 1: Scalar math — SSE replacements - if (!FTOL_ONLY) { + // Detour hooks for profiling probes (functions without SSE replacements) + const INSTALL_PROBES = false; // disable probes when patching + if (INSTALL_PROBES) { if (h00.attach(0x4549C0, &sseNormalizeVec3) == .ok) installed += 1; // 137K/7.5s if (h01.attach(0x41AE40, probeDetour(SC1d, &h01, &cnt[1])) == .ok) installed += 1; if (h02.attach(0x41AE50, probeDetour(SC1d, &h02, &cnt[2])) == .ok) installed += 1; diff --git a/src/silicon/silicon_sse.zig b/src/silicon/silicon_sse.zig index cce85e1..b998c3f 100644 --- a/src/silicon/silicon_sse.zig +++ b/src/silicon/silicon_sse.zig @@ -135,37 +135,15 @@ export fn si_createRotMat3x4(out: u32, axis_ptr: u32, angle_bits: u32, is_normal } // --- 0x6329E0: distanceToPlane (525K/7.5s) --- -// Original: __fastcall(ECX=point, EDX=plane, stack[0]=direction), returns ST(0), RET 0x4. -// (dot(point,normal)+d) / dot(direction,normal). 70 bytes, 14cy. -// Naked FMA version: 2 dot products via vfmadd + vdivss, transfer to ST(0). -export fn si_distanceToPlane() callconv(.naked) void { - // ECX=point, EDX=plane, [ESP+4]=direction. Return ST(0), RET 0x4. - asm volatile ( - // dot1 = p[0]*pl[0] + p[1]*pl[1] + p[2]*pl[2] + pl[3] - \\vmovss (%%ecx), %%xmm0 - \\vmulss (%%edx), %%xmm0, %%xmm0 - \\vmovss 4(%%ecx), %%xmm1 - \\vfmadd231ss 4(%%edx), %%xmm1, %%xmm0 - \\vmovss 8(%%ecx), %%xmm1 - \\vfmadd231ss 8(%%edx), %%xmm1, %%xmm0 - \\vaddss 12(%%edx), %%xmm0, %%xmm0 - // dot2 = dir[0]*pl[0] + dir[1]*pl[1] + dir[2]*pl[2] - \\mov 4(%%esp), %%eax - \\vmovss (%%eax), %%xmm2 - \\vmulss (%%edx), %%xmm2, %%xmm2 - \\vmovss 4(%%eax), %%xmm1 - \\vfmadd231ss 4(%%edx), %%xmm1, %%xmm2 - \\vmovss 8(%%eax), %%xmm1 - \\vfmadd231ss 8(%%edx), %%xmm1, %%xmm2 - // result = dot1 / dot2 (skip epsilon check for speed — game tolerates it) - \\vdivss %%xmm2, %%xmm0, %%xmm0 - // Transfer to ST(0) - \\sub $4, %%esp - \\vmovss %%xmm0, (%%esp) - \\flds (%%esp) - \\add $4, %%esp - \\ret $4 - ); +// __fastcall(ECX=point, EDX=plane, stack=direction), returns f64 via ST(0), RET 0x4. +export fn si_distanceToPlane(point: u32, plane: u32, direction: u32) callconv(FC) f64 { + const p: [*]const f32 = @ptrFromInt(point); + const pl: [*]const f32 = @ptrFromInt(plane); + const dir: [*]const f32 = @ptrFromInt(direction); + const dot1 = @mulAdd(f32, p[2], pl[2], @mulAdd(f32, p[1], pl[1], @mulAdd(f32, p[0], pl[0], pl[3]))); + const dot2 = @mulAdd(f32, dir[2], pl[2], @mulAdd(f32, dir[1], pl[1], dir[0] * pl[0])); + if (@abs(dot2) < 1.0e-20) return 0.0; + return @as(f64, dot1) / @as(f64, dot2); } @@ -315,27 +293,12 @@ export fn si_quatSlerp(out: u32, a_ptr: u32, t_bits: u32, b_ptr: u32) callconv(F } // --- 0x699330: isPointInsideBounds (1.7M/7.5s) --- -// Original: __fastcall(ECX=a, EDX=b), RET. 46 bytes, 6cy. -// Naked SSE: comiss replaces x87 FCOMP+FNSTSW+TEST (3 insns -> 1 insn per compare). -// Binary patch candidate: fits in 46 bytes. -export fn si_isPointInsideBounds() callconv(.naked) void { - // ECX = a, EDX = b. Return EAX = 1 if b[0..2] <= a[0..2], else 0. - asm volatile ( - \\vmovss (%%edx), %%xmm0 - \\vucomiss (%%ecx), %%xmm0 - \\ja 1f - \\vmovss 4(%%edx), %%xmm0 - \\vucomiss 4(%%ecx), %%xmm0 - \\ja 1f - \\vmovss 8(%%edx), %%xmm0 - \\vucomiss 8(%%ecx), %%xmm0 - \\ja 1f - \\mov $1, %%eax - \\ret - \\1: - \\xor %%eax, %%eax - \\ret - ); +// __fastcall(ECX=a, EDX=b), returns u32. +export fn si_isPointInsideBounds(a: u32, b: u32) callconv(FC) u32 { + const va: [*]const f32 = @ptrFromInt(a); + const vb: [*]const f32 = @ptrFromInt(b); + if (vb[0] <= va[0] and vb[1] <= va[1] and vb[2] <= va[2]) return 1; + return 0; } // --- 0x749280: calculateSinCos --- @@ -487,27 +450,12 @@ export fn si_ftol() callconv(.naked) void { ); } -// --- 0x602630: vec3Dot (31K/7.5s, 0.05ms total) --- -// Original: __fastcall(ECX=a, EDX=b), returns ST(0). 21 bytes, 5cy. -// Achieved 0.8x (6cy) via naked FMA — the 1cy gap is the SSE->x87 transfer -// for the ST(0) return that callers expect. DPPS (SSE4.1) is 7-11cy, no better. -// x87 is inherently optimal here: 21 bytes, no domain crossing, pipelined. -// Not worth further optimization at 31K calls — 0.05ms total frame cost. -export fn si_vec3Dot() callconv(.naked) void { - // ECX = a ptr, EDX = b ptr (fastcall), return in ST(0) - asm volatile ( - \\vmovss (%%ecx), %%xmm0 - \\vmulss (%%edx), %%xmm0, %%xmm0 - \\vmovss 4(%%ecx), %%xmm1 - \\vfmadd231ss 4(%%edx), %%xmm1, %%xmm0 - \\vmovss 8(%%ecx), %%xmm1 - \\vfmadd231ss 8(%%edx), %%xmm1, %%xmm0 - \\sub $4, %%esp - \\vmovss %%xmm0, (%%esp) - \\flds (%%esp) - \\add $4, %%esp - \\ret - ); +// --- 0x602630: vec3Dot (31K/7.5s) --- +// __fastcall(ECX=a, EDX=b), returns f64 via ST(0). +export fn si_vec3Dot(a: u32, b: u32) callconv(FC) f64 { + const va: [*]const f32 = @ptrFromInt(a); + const vb: [*]const f32 = @ptrFromInt(b); + return @floatCast(@mulAdd(f32, va[2], vb[2], @mulAdd(f32, va[1], vb[1], va[0] * vb[0]))); } // --- 0x686820: translateBoundingVol ---