authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-12-06 10:05:23+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-12-06 10:41:42+00:00
log65922a2d4319ca2e9f3e67650d018e269d018a77
tree41aa53f75c76a514500ac1bdea96c267487ac41d
parent621e1d7b1e560901e2a5cbdca39f173f6592a004
signaturelock-open Commit is signed but in an unrecognized format.

std: make stack unwinding faster on macOS

https://github.com/ziglang/zig/issues/26027#issuecomment-3571227050 tracked some bad performance in `DebugAllocator` on macOS down to a function in dyld which `std.debug.SelfInfo` was calling into. It turns out `dladdr`'s symbol lookup logic is horrendously slow (looking at its source code, it appears to be doing a *linear scan* over all symbols in the image?!). However, we don't actually need the symbol, so we want to try and avoid this logic. Luckily, dyld has more precise APIs for what we need! Unluckily, Apple, in their infinite wisdom, decided they should be deprecated in favour of `dladdr`, despite the latter being several times slower (and by "several times", I have measured a 50x slowdown on repeated calls to `dladdr` compared to the other API). But luckily again, the deprecated APIs are still exposed. So, after a careful analysis of the situation (reading dyld code and cursing Apple engineers), I think it makes sense to just use these deprecated APIs for now. If they ever go away, we can write our own cache for this data to bypass Apple's awfully slow code, but I suspect these functions will stick around for the foreseeable future. Uh, and if `_dyld_get_image_header_containing_address` goes away, there's also `dyld_image_header_containing_address`, which is a seemingly identical function, exported by dyld just the same, but with a separate (functionally identical) implementation, and not documented in the public header file. Apple work in mysterious ways, I guess.

3 files changed, 33 insertions(+), 21 deletions(-)

lib/std/c.zig+2
...@@ -11324,6 +11324,8 @@ pub const _dyld_get_image_header = darwin._dyld_get_image_header;...@@ -11324,6 +11324,8 @@ pub const _dyld_get_image_header = darwin._dyld_get_image_header;
11324pub const _dyld_get_image_name = darwin._dyld_get_image_name;11324pub const _dyld_get_image_name = darwin._dyld_get_image_name;
11325pub const _dyld_get_image_vmaddr_slide = darwin._dyld_get_image_vmaddr_slide;11325pub const _dyld_get_image_vmaddr_slide = darwin._dyld_get_image_vmaddr_slide;
11326pub const _dyld_image_count = darwin._dyld_image_count;11326pub const _dyld_image_count = darwin._dyld_image_count;
11327pub const _dyld_get_image_header_containing_address = darwin._dyld_get_image_header_containing_address;
11328pub const dyld_image_path_containing_address = darwin.dyld_image_path_containing_address;
11327pub const _host_page_size = darwin._host_page_size;11329pub const _host_page_size = darwin._host_page_size;
11328pub const boolean_t = darwin.boolean_t;11330pub const boolean_t = darwin.boolean_t;
11329pub const clock_get_time = darwin.clock_get_time;11331pub const clock_get_time = darwin.clock_get_time;
lib/std/c/darwin.zig+2
...@@ -354,6 +354,8 @@ pub extern "c" fn _dyld_image_count() u32;...@@ -354,6 +354,8 @@ pub extern "c" fn _dyld_image_count() u32;
354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;
355pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;355pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;
356pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8;356pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8;
357pub extern "c" fn _dyld_get_image_header_containing_address(address: *const anyopaque) ?*mach_header;
358pub extern "c" fn dyld_image_path_containing_address(address: *const anyopaque) ?[*:0]const u8;
357pub extern "c" fn dladdr(addr: *const anyopaque, info: *dl_info) c_int;359pub extern "c" fn dladdr(addr: *const anyopaque, info: *dl_info) c_int;
358360
359pub const dl_info = extern struct {361pub const dl_info = extern struct {
lib/std/debug/SelfInfo/MachO.zig+29-21
...@@ -78,9 +78,14 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st...@@ -78,9 +78,14 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st
78 };78 };
79}79}
80pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {80pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
81 const module = try si.findModule(gpa, address);81 _ = si;
82 defer si.mutex.unlock();82 _ = gpa;
83 return module.name;83 // This function is marked as deprecated; however, it is significantly more
84 // performant than `dladdr` (since the latter also does a very slow symbol
85 // lookup), so let's use it since it's still available.
86 return std.mem.span(std.c.dyld_image_path_containing_address(
87 @ptrFromInt(address),
88 ) orelse return error.MissingDebugInfo);
84}89}
85pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, address: usize) Error!usize {90pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, address: usize) Error!usize {
86 const module = try si.findModule(gpa, address);91 const module = try si.findModule(gpa, address);
...@@ -426,28 +431,26 @@ fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usi...@@ -426,28 +431,26 @@ fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usi
426431
427/// Acquires the mutex on success.432/// Acquires the mutex on success.
428fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module {433fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module {
429 var info: std.c.dl_info = undefined;434 // This function is marked as deprecated; however, it is significantly more
430 if (std.c.dladdr(@ptrFromInt(address), &info) == 0) {435 // performant than `dladdr` (since the latter also does a very slow symbol
431 return error.MissingDebugInfo;436 // lookup), so let's use it since it's still available.
432 }437 const text_base = std.c._dyld_get_image_header_containing_address(
438 @ptrFromInt(address),
439 ) orelse return error.MissingDebugInfo;
433 si.mutex.lock();440 si.mutex.lock();
434 errdefer si.mutex.unlock();441 errdefer si.mutex.unlock();
435 const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(info.fbase), Module.Adapter{});442 const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(text_base), Module.Adapter{});
436 errdefer comptime unreachable;443 errdefer comptime unreachable;
437 if (!gop.found_existing) {444 if (!gop.found_existing) gop.key_ptr.* = .{
438 gop.key_ptr.* = .{445 .text_base = @intFromPtr(text_base),
439 .text_base = @intFromPtr(info.fbase),446 .unwind = null,
440 .name = std.mem.span(info.fname),447 .file = null,
441 .unwind = null,448 };
442 .file = null,
443 };
444 }
445 return gop.key_ptr;449 return gop.key_ptr;
446}450}
447451
448const Module = struct {452const Module = struct {
449 text_base: usize,453 text_base: usize,
450 name: []const u8,
451 unwind: ?(Error!Unwind),454 unwind: ?(Error!Unwind),
452 file: ?(Error!MachOFile),455 file: ?(Error!MachOFile),
453456
...@@ -544,10 +547,15 @@ const Module = struct {...@@ -544,10 +547,15 @@ const Module = struct {
544 }547 }
545548
546 fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile {549 fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile {
547 if (module.file == null) module.file = MachOFile.load(gpa, module.name, builtin.cpu.arch) catch |err| switch (err) {550 if (module.file == null) {
548 error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo,551 const path = std.mem.span(
549 error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e,552 std.c.dyld_image_path_containing_address(@ptrFromInt(module.text_base)).?,
550 };553 );
554 module.file = MachOFile.load(gpa, path, builtin.cpu.arch) catch |err| switch (err) {
555 error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo,
556 error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e,
557 };
558 }
551 return if (module.file.?) |*f| f else |err| err;559 return if (module.file.?) |*f| f else |err| err;
552 }560 }
553};561};