authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-17 12:34:16+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-19 18:30:33+01:00
log8f183c8c185f43db596b6aac1983f97403503592
tree8ba0adac8a2c240da058c8d9ca17412b11191c2f
parent64caa0e324d522915769dd5cc8a11374f919f01f
signature Commit is signed but in an unrecognized format.

MappedFile: slight tweak to node deletion

This strategy is a bit simpler and lets us reuse nodes more quickly, at the cost of a single bit (of which we had 17 spare anyway).

4 files changed, 56 insertions(+), 24 deletions(-)

src/link/Coff.zig+1-1
......@@ -6100,7 +6100,7 @@ pub fn idle(coff: *Coff) !bool {
61006100 break :task;
61016101 }
61026102 while (coff.mf.updates.pop()) |ni| : (coff.mf.update_prog_node.completeOne()) {
6103 if (ni.pendingDelete(&coff.mf)) continue;
6103 ni.cleanUpdated(&coff.mf);
61046104 const clean_moved = ni.cleanMoved(&coff.mf);
61056105 const clean_resized = ni.cleanResized(&coff.mf);
61066106 const clean_next_moved = ni.cleanNextMoved(&coff.mf);
src/link/Elf2.zig+1-1
......@@ -10857,7 +10857,7 @@ pub fn idle(elf: *Elf) link.Error!bool {
1085710857 break :task;
1085810858 }
1085910859 while (elf.mf.updates.pop()) |ni| : (elf.mf.update_prog_node.completeOne()) {
10860 if (ni.pendingDelete(&elf.mf)) continue;
10860 ni.cleanUpdated(&elf.mf);
1086110861 const clean_moved = ni.cleanMoved(&elf.mf);
1086210862 const clean_resized = ni.cleanResized(&elf.mf);
1086310863 const clean_next_moved = ni.cleanNextMoved(&elf.mf);
src/link/MachO2.zig+1
......@@ -3342,6 +3342,7 @@ pub fn idle(macho: *MachO) link.Error!bool {
33423342 }
33433343
33443344 while (macho.mf.updates.pop()) |ni| {
3345 ni.cleanUpdated(&macho.mf);
33453346 const clean_moved = ni.cleanMoved(&macho.mf);
33463347 const clean_resized = ni.cleanResized(&macho.mf);
33473348 const clean_next_moved = ni.cleanNextMoved(&macho.mf);
src/link/MappedFile.zig+53-22
......@@ -207,6 +207,7 @@ pub fn init(file: Io.File, gpa: Allocator, io: Io) (Allocator.Error || Io.Cancel
207207 .resized = false,
208208 .next_moved = false,
209209 .has_content = false,
210 .updated = false,
210211 },
211212 .location_payload = switch (root_location) {
212213 .small => |small| .{ .small = small },
......@@ -287,7 +288,14 @@ pub const Node = extern struct {
287288 next_moved: bool,
288289 /// Whether this node might contain initialized bytes.
289290 has_content: bool,
290 unused: u17 = 0,
291
292 /// Whether this node is currently in `updates`. Usually this is just equal to whether any
293 /// of `moved`, `resized`, or `next_moved` is set, but when a node is deleted and later
294 /// reused, it may still be in `updates`, so preserving this flag through the deletion
295 /// prevents us from adding it to `updates` a second time.
296 updated: bool,
297
298 unused: u16 = 0,
291299 };
292300
293301 pub const Location = union(enum(u1)) {
......@@ -494,6 +502,11 @@ pub const Node = extern struct {
494502 }
495503 }
496504
505 pub fn cleanUpdated(ni: Node.Index, mf: *MappedFile) void {
506 assert(ni.get(mf).flags.updated);
507 ni.get(mf).flags.updated = false;
508 }
509
497510 pub fn hasMoved(ni: Node.Index, mf: *const MappedFile) bool {
498511 var cur_ni = ni;
499512 while (cur_ni != .root) {
......@@ -518,9 +531,11 @@ pub const Node = extern struct {
518531 if (node.prev.unwrap()) |prev_ni| prev_ni.nextMovedAssumeCapacity(mf);
519532 if (ni.hasMoved(mf)) return;
520533 node.flags.moved = true;
521 if (node.flags.resized or node.flags.next_moved) return;
522 mf.updates.appendAssumeCapacity(ni);
523 mf.update_prog_node.increaseEstimatedTotalItems(1);
534 if (!node.flags.updated) {
535 node.flags.updated = true;
536 mf.updates.appendAssumeCapacity(ni);
537 mf.update_prog_node.increaseEstimatedTotalItems(1);
538 }
524539 }
525540
526541 pub fn hasResized(ni: Node.Index, mf: *const MappedFile) bool {
......@@ -539,9 +554,11 @@ pub const Node = extern struct {
539554 const node = ni.get(mf);
540555 if (node.flags.resized) return;
541556 node.flags.resized = true;
542 if (node.flags.moved or node.flags.next_moved) return;
543 mf.updates.appendAssumeCapacity(ni);
544 mf.update_prog_node.increaseEstimatedTotalItems(1);
557 if (!node.flags.updated) {
558 node.flags.updated = true;
559 mf.updates.appendAssumeCapacity(ni);
560 mf.update_prog_node.increaseEstimatedTotalItems(1);
561 }
545562 }
546563
547564 pub fn hasNextMoved(ni: Node.Index, mf: *const MappedFile) bool {
......@@ -560,9 +577,11 @@ pub const Node = extern struct {
560577 const node = ni.get(mf);
561578 if (!node.flags.enable_next_moved or node.flags.next_moved) return;
562579 node.flags.next_moved = true;
563 if (node.flags.moved or node.flags.resized) return;
564 mf.updates.appendAssumeCapacity(ni);
565 mf.update_prog_node.increaseEstimatedTotalItems(1);
580 if (!node.flags.updated) {
581 node.flags.updated = true;
582 mf.updates.appendAssumeCapacity(ni);
583 mf.update_prog_node.increaseEstimatedTotalItems(1);
584 }
566585 }
567586
568587 pub fn alignment(ni: Node.Index, mf: *const MappedFile) Alignment {
......@@ -647,19 +666,30 @@ pub const Node = extern struct {
647666 pub fn delete(ni: Node.Index, gpa: Allocator, mf: *MappedFile) Allocator.Error!void {
648667 const node = ni.get(mf);
649668 assert(node.first == .none and node.last == .none); // has children
650 mf.removeNodesFromChildList(gpa, ni, ni);
651 const updated = node.flags.moved or node.flags.resized or node.flags.next_moved;
652 node.* = undefined;
653 node.next = ni.toOptional();
654 if (!updated) assert(ni.pendingDelete(mf));
655 }
656
657 pub fn pendingDelete(ni: Node.Index, mf: *MappedFile) bool {
658 const node = ni.get(mf);
659 if (node.next != ni.toOptional()) return false;
660 node.next = mf.free_ni;
669 try mf.removeNodesFromChildList(gpa, ni, ni);
670 node.* = .{
671 .parent = undefined,
672 .prev = undefined,
673 .next = mf.free_ni,
674 .first = undefined,
675 .last = undefined,
676 .flags = .{
677 .position = .floating,
678 .alignment = .@"1",
679 .bubbles_moved = false,
680 .enable_next_moved = false,
681 .location_tag = .small,
682 .moved = false,
683 .resized = false,
684 .next_moved = false,
685 .has_content = false,
686 // It is important that we preserve this flag! When the node gets reused, this
687 // prevents it being redundantly added to `mf.updates` if it is already there.
688 .updated = node.flags.updated,
689 },
690 .location_payload = undefined,
691 };
661692 mf.free_ni = ni.toOptional();
662 return true;
663693 }
664694
665695 /// Ensures that the size of `ni` is at least `min_size`. Valid for any node.
......@@ -946,6 +976,7 @@ fn addNode(mf: *MappedFile, gpa: Allocator, opts: struct {
946976 .resized = false,
947977 .next_moved = false,
948978 .has_content = false,
979 .updated = false,
949980 },
950981 .location_payload = switch (location) {
951982 .small => |small| .{ .small = small },