authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-24 23:39:49+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-25 00:21:56+01:00
logfac41b3ec9237583f4d6bcb73c339464eb9a1edc
tree202820be01e843c5efd8c53a9413c969511cd188
parent8d471cd9018d99a3a39ee94212b2d42efe755ea1
signature Commit is signed but in an unrecognized format.

Elf2: minor performance improvements


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

src/link/Elf2.zig+33-21
...@@ -21,6 +21,15 @@ const Alignment = MappedFile.Alignment;...@@ -21,6 +21,15 @@ const Alignment = MappedFile.Alignment;
21base: link.File,21base: link.File,
22options: link.File.OpenOptions,22options: link.File.OpenOptions,
23mf: MappedFile,23mf: MappedFile,
24
25/// These fields could simply be read from the ehdr node when needed, but it turns out that they are
26/// accessed frequently enough that we see measurable performance gains from caching them here.
27ehdr_fields: struct {
28 class: std.elf.CLASS,
29 type: EhdrType,
30 machine: EhdrMachine,
31},
32
24ni: struct {33ni: struct {
25 elf: MappedFile.Node.Index,34 elf: MappedFile.Node.Index,
26 ehdr: MappedFile.Node.Index,35 ehdr: MappedFile.Node.Index,
...@@ -5115,6 +5124,11 @@ fn create(...@@ -5115,6 +5124,11 @@ fn create(
5115 },5124 },
5116 .options = options,5125 .options = options,
5117 .mf = try .init(file, comp.gpa, io),5126 .mf = try .init(file, comp.gpa, io),
5127 .ehdr_fields = .{
5128 .class = class,
5129 .type = @"type",
5130 .machine = machine,
5131 },
5118 .ni = .{5132 .ni = .{
5119 .elf = undefined,5133 .elf = undefined,
5120 .ehdr = undefined,5134 .ehdr = undefined,
...@@ -7007,7 +7021,9 @@ fn flushMovedNodeRelocs(...@@ -7007,7 +7021,9 @@ fn flushMovedNodeRelocs(
7007}7021}
70087022
7009fn identClass(elf: *const Elf) std.elf.CLASS {7023fn identClass(elf: *const Elf) std.elf.CLASS {
7010 return @fromBackingInt(elf.ni.elf.sliceConst(&elf.mf)[std.elf.EI.CLASS]);7024 // We used to get this value from `elf.ni.ehdr`, but it turns out this function is called often
7025 // enough to see measurable performance gains from memoizing its result on `Elf`.
7026 return elf.ehdr_fields.class;
7011}7027}
70127028
7013/// Like `std.elf.ET`, but only includes the ELF machine architectures we support, so that we can7029/// Like `std.elf.ET`, but only includes the ELF machine architectures we support, so that we can
...@@ -7039,24 +7055,14 @@ const EhdrType = enum(u16) {...@@ -7039,24 +7055,14 @@ const EhdrType = enum(u16) {
7039 }7055 }
7040};7056};
7041fn ehdrMachine(elf: *const Elf) EhdrMachine {7057fn ehdrMachine(elf: *const Elf) EhdrMachine {
7042 const ehdr_slice = elf.ni.ehdr.sliceConst(&elf.mf);7058 // We used to get this value from `elf.ni.ehdr`, but it turns out this function is called often
7043 switch (elf.identClass()) {7059 // enough to see measurable performance gains from memoizing its result on `Elf`.
7044 .NONE, _ => unreachable,7060 return elf.ehdr_fields.machine;
7045 inline else => |class| {
7046 const ehdr: *const class.ElfN().Ehdr = @ptrCast(@alignCast(ehdr_slice));
7047 return @bitCast(elf.targetLoad(&ehdr.machine));
7048 },
7049 }
7050}7061}
7051fn ehdrType(elf: *const Elf) EhdrType {7062fn ehdrType(elf: *const Elf) EhdrType {
7052 const ehdr_slice = elf.ni.ehdr.sliceConst(&elf.mf);7063 // We used to get this value from `elf.ni.ehdr`, but it turns out this function is called often
7053 switch (elf.identClass()) {7064 // enough to see measurable performance gains from memoizing its result on `Elf`.
7054 .NONE, _ => unreachable,7065 return elf.ehdr_fields.type;
7055 inline else => |class| {
7056 const ehdr: *const class.ElfN().Ehdr = @ptrCast(@alignCast(ehdr_slice));
7057 return @bitCast(elf.targetLoad(&ehdr.type));
7058 },
7059 }
7060}7066}
70617067
7062fn baseLoadableSegmentVAddr(elf: *const Elf) u64 {7068fn baseLoadableSegmentVAddr(elf: *const Elf) u64 {
...@@ -7279,11 +7285,17 @@ const SymPtr = union(std.elf.CLASS) {...@@ -7279,11 +7285,17 @@ const SymPtr = union(std.elf.CLASS) {
7279 @"32": *std.elf.Elf32.Sym,7285 @"32": *std.elf.Elf32.Sym,
7280 @"64": *std.elf.Elf64.Sym,7286 @"64": *std.elf.Elf64.Sym,
7281};7287};
7282fn symPtr(elf: *Elf, index: Symbol.Index) SymPtr {7288/// This function is marked as `inline` because performance measurements (specifically, using this
7289/// linker to link the Zig compiler against a static build of LLVM 23) show a measurable performance
7290/// improvement from doing so---it seems that this is one of the hottest functions in this linker!
7291inline fn symPtr(elf: *Elf, index: Symbol.Index) SymPtr {
7283 const raw_slice = Section.Index.symtab.node(elf).slice(&elf.mf);7292 const raw_slice = Section.Index.symtab.node(elf).slice(&elf.mf);
7284 switch (elf.shdrPtr(.symtab)) {7293 // Avoid calling `shdrPtr` here: this function is extremely hot, so having to locate the shdr
7285 inline else => |shdr, class| {7294 // actually noticably affects performance!
7286 const size = elf.targetLoad(&shdr.size);7295 switch (elf.identClass()) {
7296 .NONE, _ => unreachable,
7297 inline else => |class| {
7298 const size = elf.symtab.items.len * @sizeOf(class.ElfN().Sym);
7287 const slice: []class.ElfN().Sym = @ptrCast(@alignCast(raw_slice[0..@intCast(size)]));7299 const slice: []class.ElfN().Sym = @ptrCast(@alignCast(raw_slice[0..@intCast(size)]));
7288 return @unionInit(SymPtr, @tagName(class), &slice[@backingInt(index)]);7300 return @unionInit(SymPtr, @tagName(class), &slice[@backingInt(index)]);
7289 },7301 },