const std = @import("std"); const core = @import("core.zig"); const BumpArena = core.BumpArena; const builtin = @import("builtin"); pub fn hashStackList(stack: []const usize) u32 { var c: u32 = 0; for (stack) |l| { c = combineHash32(c, hashPtr(l)); } return c; } pub inline fn combineHash32(lhs: u32, rhs: u32) u32 { return lhs ^ (rhs +% 0x9e3779b9 +% (lhs << 6) +% (lhs >> 2)); } pub inline fn hashPtr(value: usize) u32 { var k = value +% 1; if (@sizeOf(@TypeOf(value)) == 8) { k = (~k) +% (k << 18); k = k ^ std.math.rotr(usize, k, 31); k = @mulWithOverflow(k, 21)[0]; k = k ^ std.math.rotr(usize, k, 11); k = k +% (k << 6); k = k ^ std.math.rotr(usize, k, 22); return @truncate(k); } else {} } test "1 million stacks." { // see how fast i can walk 1 million stacks var timer = std.time.Timer.start() catch unreachable; const startTime = timer.read(); for (0..100000) |_| { core.stacks.pushCallStack(); } const endTime = timer.read(); std.debug.print("timeElapsed 100k callstacks {d}s", .{@as(f64, @floatFromInt(endTime - startTime)) / 1000000000}); } test "test hash64s" { const pointers: []const usize = &.{ 0x0, 0x1, 0xffffffff1eed1234, 0xffffffff_ffffffff, 0xffffffff_ffffffff - 1, }; var combined: u32 = 0; for (pointers) |l| { const h = hashPtr(l); std.debug.print("l = {x}, hash = {x}\n", .{ l, h }); combined = combineHash32(combined, h); } std.debug.print("combined hash = {x} ({x})\n", .{ combined, hashStackList(pointers) }); } pub const StackCompactor = struct { allocator: std.mem.Allocator, stackArena: BumpArena, stackMap: std.AutoHashMapUnmanaged(u32, *CallStack) = .{}, const CallStack = struct { pointers: []usize, debugStr: []?[]u8, }; pub fn create(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, .stackArena = try BumpArena.init(allocator), }; return self; } pub fn addNewCallstack(self: *@This(), stack: []const usize) !u32 { const hash = hashStackList(stack); const bumpAllocator = self.stackArena.allocator(); if (!self.stackMap.contains(hash)) { const ownedStack: *CallStack = try bumpAllocator.create(CallStack); ownedStack.pointers = try bumpAllocator.dupe(usize, stack); ownedStack.debugStr = try bumpAllocator.alloc(?[]u8, stack.len); const debug_info = std.debug.getSelfDebugInfo() catch { try self.stackMap.put(self.allocator, hash, ownedStack); return hash; }; for (stack, 0..) |address, i| { ownedStack.debugStr[i] = null; const module = debug_info.getModuleForAddress(address) catch continue; const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch continue; if (symbol_info.source_location) |line_info| { ownedStack.debugStr[i] = try std.fmt.allocPrintZ( bumpAllocator, "{d}> @0x{x} symbol_name: {s} {s} > {s}: {d}", .{ i, address, symbol_info.name, symbol_info.compile_unit_name, line_info.file_name, line_info.line }, ); } else { ownedStack.debugStr[i] = try std.fmt.allocPrintZ( bumpAllocator, "{d}> @0x{x} symbol_name: {s} {s} > no file info", .{ i, address, symbol_info.name, symbol_info.compile_unit_name }, ); } } try self.stackMap.put(self.allocator, hash, ownedStack); } return hash; } pub fn deinit(self: @This()) void { self.stackList.deinit(self.allocator); self.allocator.destroy(self); } pub fn getCallStack(self: *@This()) u32 { if (builtin.os.tag != .windows) { return 0; } var context: std.debug.ThreadContext = undefined; const has_context = std.debug.getContext(&context); if (!has_context) { return 0; } var addr_buf: [1024]usize = undefined; const n = std.debug.walkStackWindows(addr_buf[0..], &context); return self.addNewCallstack(addr_buf[0..n]) catch unreachable; } }; var stackCompactor: *core.StackCompactor = undefined; pub fn initStackCompactor() void { stackCompactor = core.StackCompactor.create(std.heap.c_allocator) catch unreachable; } pub inline fn pushCallStack() void { var context: std.debug.ThreadContext = undefined; const has_context = std.debug.getContext(&context); if (!has_context) { return; } var addr_buf: [1024]usize = undefined; const n = std.debug.walkStackWindows(addr_buf[0..], &context); stackCompactor.addNewCallstack(addr_buf[0..n]) catch unreachable; } pub fn getStackCompactor() *StackCompactor { return stackCompactor; } pub fn walkAndPrintStack() void { var context: std.debug.ThreadContext = undefined; const has_context = std.debug.getContext(&context); if (!has_context) { return; } var addr_buf: [1024]usize = undefined; const n = std.debug.walkStackWindows(addr_buf[0..], &context); const debug_info = std.debug.getSelfDebugInfo() catch { return; }; var i: u32 = 0; while (i < n) : (i += 1) { const address = addr_buf[i]; const module = debug_info.getModuleForAddress(address) catch continue; const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch continue; std.debug.print("@0x{x} symbol_name: {s} {s} > ", .{ address, symbol_info.symbol_name, symbol_info.compile_unit_name }); if (symbol_info.line_info) |line_info| { std.debug.print(" {s}:{d}\n", .{ line_info.file_name, line_info.line }); } else { std.debug.print(" no file info\n", .{}); } } }