authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-09-15 16:57:10+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-09-16 12:38:47+02:00
log983d6dcd9ea75e05abd8ce2bd247bbad3960acd7
treec5767d15a29bcc1e3704ca37e3dc0ddf54eca376
parent506f24cac2f5226210f9ce505d5b93c47b7b8c87

macho: implement object relinking in stage2

* In watch mode, when changing the C source, we will trigger complete relinking of objects, dylibs and archives (atoms coming from the incremental updates stay put however). This means, we need to undo metadata populated when linking in objects, archives and dylibs. * Remove unused splitting section into atoms bit. This optimisation will probably be best rewritten from scratch once self-hosted matures so parking the idea for now. Also, for easier management of atoms spawned from the Object file, keep the atoms subgraph as part of the Object file struct. * Remove obsolete ref to static initializers in object struct. * Implement handling of global symbol collision in updateDeclExports.

3 files changed, 368 insertions(+), 327 deletions(-)

src/link/MachO.zig+150-34
...@@ -231,7 +231,7 @@ const SymbolWithLoc = struct {...@@ -231,7 +231,7 @@ const SymbolWithLoc = struct {
231 },231 },
232 where_index: u32,232 where_index: u32,
233 local_sym_index: u32 = 0,233 local_sym_index: u32 = 0,
234 file: u16 = 0,234 file: ?u16 = null, // null means Zig module
235};235};
236236
237pub const GotIndirectionKey = struct {237pub const GotIndirectionKey = struct {
...@@ -543,9 +543,6 @@ pub fn flush(self: *MachO, comp: *Compilation) !void {...@@ -543,9 +543,6 @@ pub fn flush(self: *MachO, comp: *Compilation) !void {
543 .mode = link.determineMode(self.base.options),543 .mode = link.determineMode(self.base.options),
544 });544 });
545 try self.populateMissingMetadata();545 try self.populateMissingMetadata();
546
547 // TODO mimicking insertion of null symbol from incremental linker.
548 // This will need to moved.
549 try self.locals.append(self.base.allocator, .{546 try self.locals.append(self.base.allocator, .{
550 .n_strx = 0,547 .n_strx = 0,
551 .n_type = macho.N_UNDF,548 .n_type = macho.N_UNDF,
...@@ -557,13 +554,56 @@ pub fn flush(self: *MachO, comp: *Compilation) !void {...@@ -557,13 +554,56 @@ pub fn flush(self: *MachO, comp: *Compilation) !void {
557 }554 }
558555
559 if (needs_full_relink) {556 if (needs_full_relink) {
557 for (self.objects.items) |*object| {
558 object.free(self.base.allocator, self);
559 object.deinit(self.base.allocator);
560 }
560 self.objects.clearRetainingCapacity();561 self.objects.clearRetainingCapacity();
562
563 for (self.archives.items) |*archive| {
564 archive.deinit(self.base.allocator);
565 }
561 self.archives.clearRetainingCapacity();566 self.archives.clearRetainingCapacity();
567
568 for (self.dylibs.items) |*dylib| {
569 dylib.deinit(self.base.allocator);
570 }
562 self.dylibs.clearRetainingCapacity();571 self.dylibs.clearRetainingCapacity();
563 self.dylibs_map.clearRetainingCapacity();572 self.dylibs_map.clearRetainingCapacity();
564 self.referenced_dylibs.clearRetainingCapacity();573 self.referenced_dylibs.clearRetainingCapacity();
565574
566 // TODO figure out how to clear atoms from objects, etc.575 {
576 var to_remove = std.ArrayList(u32).init(self.base.allocator);
577 defer to_remove.deinit();
578 var it = self.symbol_resolver.iterator();
579 while (it.next()) |entry| {
580 const key = entry.key_ptr.*;
581 const value = entry.value_ptr.*;
582 if (value.file != null) {
583 try to_remove.append(key);
584 }
585 }
586
587 for (to_remove.items) |key| {
588 if (self.symbol_resolver.fetchRemove(key)) |entry| {
589 const resolv = entry.value;
590 switch (resolv.where) {
591 .global => {
592 self.globals_free_list.append(self.base.allocator, resolv.where_index) catch {};
593 const sym = &self.globals.items[resolv.where_index];
594 sym.n_strx = 0;
595 sym.n_type = 0;
596 sym.n_value = 0;
597 },
598 .undef => {
599 const sym = &self.undefs.items[resolv.where_index];
600 sym.n_strx = 0;
601 sym.n_desc = 0;
602 },
603 }
604 }
605 }
606 }
567607
568 // Positional arguments to the linker such as object files and static archives.608 // Positional arguments to the linker such as object files and static archives.
569 var positionals = std.ArrayList([]const u8).init(arena);609 var positionals = std.ArrayList([]const u8).init(arena);
...@@ -802,13 +842,35 @@ pub fn flush(self: *MachO, comp: *Compilation) !void {...@@ -802,13 +842,35 @@ pub fn flush(self: *MachO, comp: *Compilation) !void {
802 try self.createDsoHandleAtom();842 try self.createDsoHandleAtom();
803 try self.addCodeSignatureLC();843 try self.addCodeSignatureLC();
804844
845 // log.warn("locals:", .{});
846 // for (self.locals.items) |sym, id| {
847 // log.warn(" {d}: {s}: {}", .{ id, self.getString(sym.n_strx), sym });
848 // }
849 // log.warn("globals:", .{});
850 // for (self.globals.items) |sym, id| {
851 // log.warn(" {d}: {s}: {}", .{ id, self.getString(sym.n_strx), sym });
852 // }
853 // log.warn("undefs:", .{});
854 // for (self.undefs.items) |sym, id| {
855 // log.warn(" {d}: {s}: {}", .{ id, self.getString(sym.n_strx), sym });
856 // }
857 // {
858 // log.warn("resolver:", .{});
859 // var it = self.symbol_resolver.iterator();
860 // while (it.next()) |entry| {
861 // log.warn(" {s} => {}", .{ self.getString(entry.key_ptr.*), entry.value_ptr.* });
862 // }
863 // }
864
805 for (self.unresolved.keys()) |index| {865 for (self.unresolved.keys()) |index| {
806 const sym = self.undefs.items[index];866 const sym = self.undefs.items[index];
807 const sym_name = self.getString(sym.n_strx);867 const sym_name = self.getString(sym.n_strx);
808 const resolv = self.symbol_resolver.get(sym.n_strx) orelse unreachable;868 const resolv = self.symbol_resolver.get(sym.n_strx) orelse unreachable;
809869
810 log.err("undefined reference to symbol '{s}'", .{sym_name});870 log.err("undefined reference to symbol '{s}'", .{sym_name});
811 log.err(" first referenced in '{s}'", .{self.objects.items[resolv.file].name});871 if (resolv.file) |file| {
872 log.err(" first referenced in '{s}'", .{self.objects.items[file].name});
873 }
812 }874 }
813 if (self.unresolved.count() > 0) {875 if (self.unresolved.count() > 0) {
814 return error.UndefinedSymbolReference;876 return error.UndefinedSymbolReference;
...@@ -2349,7 +2411,9 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {...@@ -2349,7 +2411,9 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
2349 !(symbolIsWeakDef(global.*) or symbolIsPext(global.*)))2411 !(symbolIsWeakDef(global.*) or symbolIsPext(global.*)))
2350 {2412 {
2351 log.err("symbol '{s}' defined multiple times", .{sym_name});2413 log.err("symbol '{s}' defined multiple times", .{sym_name});
2352 log.err(" first definition in '{s}'", .{self.objects.items[resolv.file].name});2414 if (resolv.file) |file| {
2415 log.err(" first definition in '{s}'", .{self.objects.items[file].name});
2416 }
2353 log.err(" next definition in '{s}'", .{object.name});2417 log.err(" next definition in '{s}'", .{object.name});
2354 return error.MultipleSymbolDefinitions;2418 return error.MultipleSymbolDefinitions;
2355 } else if (symbolIsWeakDef(sym) or symbolIsPext(sym)) continue; // Current symbol is weak, so skip it.2419 } else if (symbolIsWeakDef(sym) or symbolIsPext(sym)) continue; // Current symbol is weak, so skip it.
...@@ -2632,10 +2696,10 @@ fn parseObjectsIntoAtoms(self: *MachO) !void {...@@ -2632,10 +2696,10 @@ fn parseObjectsIntoAtoms(self: *MachO) !void {
2632 const tracy = trace(@src());2696 const tracy = trace(@src());
2633 defer tracy.end();2697 defer tracy.end();
26342698
2635 var parsed_atoms = Object.ParsedAtoms.init(self.base.allocator);2699 var parsed_atoms = std.AutoArrayHashMap(MatchingSection, *Atom).init(self.base.allocator);
2636 defer parsed_atoms.deinit();2700 defer parsed_atoms.deinit();
26372701
2638 var first_atoms = Object.ParsedAtoms.init(self.base.allocator);2702 var first_atoms = std.AutoArrayHashMap(MatchingSection, *Atom).init(self.base.allocator);
2639 defer first_atoms.deinit();2703 defer first_atoms.deinit();
26402704
2641 var section_metadata = std.AutoHashMap(MatchingSection, struct {2705 var section_metadata = std.AutoHashMap(MatchingSection, struct {
...@@ -2644,13 +2708,12 @@ fn parseObjectsIntoAtoms(self: *MachO) !void {...@@ -2644,13 +2708,12 @@ fn parseObjectsIntoAtoms(self: *MachO) !void {
2644 }).init(self.base.allocator);2708 }).init(self.base.allocator);
2645 defer section_metadata.deinit();2709 defer section_metadata.deinit();
26462710
2647 for (self.objects.items) |*object, object_id| {2711 for (self.objects.items) |*object| {
2648 if (object.analyzed) continue;2712 if (object.analyzed) continue;
26492713
2650 var atoms_in_objects = try object.parseIntoAtoms(self.base.allocator, @intCast(u16, object_id), self);2714 try object.parseIntoAtoms(self.base.allocator, self);
2651 defer atoms_in_objects.deinit();
26522715
2653 var it = atoms_in_objects.iterator();2716 var it = object.end_atoms.iterator();
2654 while (it.next()) |entry| {2717 while (it.next()) |entry| {
2655 const match = entry.key_ptr.*;2718 const match = entry.key_ptr.*;
2656 const last_atom = entry.value_ptr.*;2719 const last_atom = entry.value_ptr.*;
...@@ -3292,8 +3355,6 @@ pub fn updateDeclExports(...@@ -3292,8 +3355,6 @@ pub fn updateDeclExports(
3292 decl: *Module.Decl,3355 decl: *Module.Decl,
3293 exports: []const *Module.Export,3356 exports: []const *Module.Export,
3294) !void {3357) !void {
3295 // TODO If we are exporting with global linkage, check for already defined globals and flag
3296 // symbol duplicate/collision!
3297 if (build_options.skip_non_native and builtin.object_format != .macho) {3358 if (build_options.skip_non_native and builtin.object_format != .macho) {
3298 @panic("Attempted to compile for object format that was disabled by build configuration");3359 @panic("Attempted to compile for object format that was disabled by build configuration");
3299 }3360 }
...@@ -3303,7 +3364,7 @@ pub fn updateDeclExports(...@@ -3303,7 +3364,7 @@ pub fn updateDeclExports(
3303 const tracy = trace(@src());3364 const tracy = trace(@src());
3304 defer tracy.end();3365 defer tracy.end();
33053366
3306 try self.globals.ensureCapacity(self.base.allocator, self.globals.items.len + exports.len);3367 try self.globals.ensureUnusedCapacity(self.base.allocator, exports.len);
3307 if (decl.link.macho.local_sym_index == 0) return;3368 if (decl.link.macho.local_sym_index == 0) return;
3308 const decl_sym = &self.locals.items[decl.link.macho.local_sym_index];3369 const decl_sym = &self.locals.items[decl.link.macho.local_sym_index];
33093370
...@@ -3313,15 +3374,76 @@ pub fn updateDeclExports(...@@ -3313,15 +3374,76 @@ pub fn updateDeclExports(
33133374
3314 if (exp.options.section) |section_name| {3375 if (exp.options.section) |section_name| {
3315 if (!mem.eql(u8, section_name, "__text")) {3376 if (!mem.eql(u8, section_name, "__text")) {
3316 try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.count() + 1);3377 try module.failed_exports.putNoClobber(
3317 module.failed_exports.putAssumeCapacityNoClobber(3378 module.gpa,
3318 exp,3379 exp,
3319 try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}),3380 try Module.ErrorMsg.create(
3381 self.base.allocator,
3382 decl.srcLoc(),
3383 "Unimplemented: ExportOptions.section",
3384 .{},
3385 ),
3320 );3386 );
3321 continue;3387 continue;
3322 }3388 }
3323 }3389 }
33243390
3391 if (exp.options.linkage == .LinkOnce) {
3392 try module.failed_exports.putNoClobber(
3393 module.gpa,
3394 exp,
3395 try Module.ErrorMsg.create(
3396 self.base.allocator,
3397 decl.srcLoc(),
3398 "Unimplemented: GlobalLinkage.LinkOnce",
3399 .{},
3400 ),
3401 );
3402 continue;
3403 }
3404
3405 const is_weak = exp.options.linkage == .Internal or exp.options.linkage == .Weak;
3406 const n_strx = try self.makeString(exp_name);
3407 if (self.symbol_resolver.getPtr(n_strx)) |resolv| {
3408 switch (resolv.where) {
3409 .global => {
3410 if (resolv.local_sym_index == decl.link.macho.local_sym_index) continue;
3411
3412 const sym = &self.globals.items[resolv.where_index];
3413
3414 if (symbolIsTentative(sym.*)) {
3415 _ = self.tentatives.fetchSwapRemove(resolv.where_index);
3416 } else if (!is_weak and !(symbolIsWeakDef(sym.*) or symbolIsPext(sym.*))) {
3417 _ = try module.failed_exports.put(
3418 module.gpa,
3419 exp,
3420 try Module.ErrorMsg.create(
3421 self.base.allocator,
3422 decl.srcLoc(),
3423 \\LinkError: symbol '{s}' defined multiple times
3424 \\ first definition in '{s}'
3425 ,
3426 .{ exp_name, self.objects.items[resolv.file.?].name },
3427 ),
3428 );
3429 continue;
3430 } else if (is_weak) continue; // Current symbol is weak, so skip it.
3431
3432 // Otherwise, update the resolver and the global symbol.
3433 sym.n_type = macho.N_SECT | macho.N_EXT;
3434 resolv.local_sym_index = decl.link.macho.local_sym_index;
3435 resolv.file = null;
3436 exp.link.macho.sym_index = resolv.where_index;
3437
3438 continue;
3439 },
3440 .undef => {
3441 _ = self.unresolved.fetchSwapRemove(resolv.where_index);
3442 _ = self.symbol_resolver.remove(n_strx);
3443 },
3444 }
3445 }
3446
3325 var n_type: u8 = macho.N_SECT | macho.N_EXT;3447 var n_type: u8 = macho.N_SECT | macho.N_EXT;
3326 var n_desc: u16 = 0;3448 var n_desc: u16 = 0;
33273449
...@@ -3339,14 +3461,7 @@ pub fn updateDeclExports(...@@ -3339,14 +3461,7 @@ pub fn updateDeclExports(
3339 // Symbol's n_type is like for a symbol with strong linkage.3461 // Symbol's n_type is like for a symbol with strong linkage.
3340 n_desc |= macho.N_WEAK_DEF;3462 n_desc |= macho.N_WEAK_DEF;
3341 },3463 },
3342 .LinkOnce => {3464 else => unreachable,
3343 try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.count() + 1);
3344 module.failed_exports.putAssumeCapacityNoClobber(
3345 exp,
3346 try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}),
3347 );
3348 continue;
3349 },
3350 }3465 }
33513466
3352 const global_sym_index = if (exp.link.macho.sym_index) |i| i else blk: {3467 const global_sym_index = if (exp.link.macho.sym_index) |i| i else blk: {
...@@ -3356,8 +3471,6 @@ pub fn updateDeclExports(...@@ -3356,8 +3471,6 @@ pub fn updateDeclExports(
3356 };3471 };
3357 break :blk i;3472 break :blk i;
3358 };3473 };
3359
3360 const n_strx = try self.makeString(exp_name);
3361 const sym = &self.globals.items[global_sym_index];3474 const sym = &self.globals.items[global_sym_index];
3362 sym.* = .{3475 sym.* = .{
3363 .n_strx = try self.makeString(exp_name),3476 .n_strx = try self.makeString(exp_name),
...@@ -3368,12 +3481,11 @@ pub fn updateDeclExports(...@@ -3368,12 +3481,11 @@ pub fn updateDeclExports(
3368 };3481 };
3369 exp.link.macho.sym_index = global_sym_index;3482 exp.link.macho.sym_index = global_sym_index;
33703483
3371 const resolv = try self.symbol_resolver.getOrPut(self.base.allocator, n_strx);3484 try self.symbol_resolver.putNoClobber(self.base.allocator, n_strx, .{
3372 resolv.value_ptr.* = .{
3373 .where = .global,3485 .where = .global,
3374 .where_index = global_sym_index,3486 .where_index = global_sym_index,
3375 .local_sym_index = decl.link.macho.local_sym_index,3487 .local_sym_index = decl.link.macho.local_sym_index,
3376 };3488 });
3377 }3489 }
3378}3490}
33793491
...@@ -3381,8 +3493,11 @@ pub fn deleteExport(self: *MachO, exp: Export) void {...@@ -3381,8 +3493,11 @@ pub fn deleteExport(self: *MachO, exp: Export) void {
3381 const sym_index = exp.sym_index orelse return;3493 const sym_index = exp.sym_index orelse return;
3382 self.globals_free_list.append(self.base.allocator, sym_index) catch {};3494 self.globals_free_list.append(self.base.allocator, sym_index) catch {};
3383 const global = &self.globals.items[sym_index];3495 const global = &self.globals.items[sym_index];
3384 global.n_type = 0;3496 log.debug("deleting export '{s}': {}", .{ self.getString(global.n_strx), global });
3385 assert(self.symbol_resolver.remove(global.n_strx));3497 assert(self.symbol_resolver.remove(global.n_strx));
3498 global.n_type = 0;
3499 global.n_strx = 0;
3500 global.n_value = 0;
3386}3501}
33873502
3388pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {3503pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {
...@@ -4403,6 +4518,7 @@ fn writeDyldInfoData(self: *MachO) !void {...@@ -4403,6 +4518,7 @@ fn writeDyldInfoData(self: *MachO) !void {
4403 const base_address = text_segment.inner.vmaddr;4518 const base_address = text_segment.inner.vmaddr;
44044519
4405 for (self.globals.items) |sym| {4520 for (self.globals.items) |sym| {
4521 if (sym.n_type == 0) continue;
4406 const sym_name = self.getString(sym.n_strx);4522 const sym_name = self.getString(sym.n_strx);
4407 log.debug(" (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value });4523 log.debug(" (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value });
44084524
...@@ -4655,7 +4771,7 @@ fn writeSymbolTable(self: *MachO) !void {...@@ -4655,7 +4771,7 @@ fn writeSymbolTable(self: *MachO) !void {
4655 .n_value = object.mtime orelse 0,4771 .n_value = object.mtime orelse 0,
4656 });4772 });
46574773
4658 for (object.atoms.items) |atom| {4774 for (object.contained_atoms.items) |atom| {
4659 if (atom.stab) |stab| {4775 if (atom.stab) |stab| {
4660 const nlists = try stab.asNlists(atom.local_sym_index, self);4776 const nlists = try stab.asNlists(atom.local_sym_index, self);
4661 defer self.base.allocator.free(nlists);4777 defer self.base.allocator.free(nlists);
src/link/MachO/Atom.zig+58-44
...@@ -645,7 +645,6 @@ const RelocContext = struct {...@@ -645,7 +645,6 @@ const RelocContext = struct {
645 allocator: *Allocator,645 allocator: *Allocator,
646 object: *Object,646 object: *Object,
647 macho_file: *MachO,647 macho_file: *MachO,
648 parsed_atoms: *Object.ParsedAtoms,
649};648};
650649
651fn initRelocFromObject(rel: macho.relocation_info, context: RelocContext) !Relocation {650fn initRelocFromObject(rel: macho.relocation_info, context: RelocContext) !Relocation {
...@@ -877,12 +876,16 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC...@@ -877,12 +876,16 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC
877 .sect = context.macho_file.got_section_index.?,876 .sect = context.macho_file.got_section_index.?,
878 };877 };
879878
880 if (context.parsed_atoms.getPtr(match)) |last| {879 if (!context.object.start_atoms.contains(match)) {
880 try context.object.start_atoms.putNoClobber(context.allocator, match, atom);
881 }
882
883 if (context.object.end_atoms.getPtr(match)) |last| {
881 last.*.next = atom;884 last.*.next = atom;
882 atom.prev = last.*;885 atom.prev = last.*;
883 last.* = atom;886 last.* = atom;
884 } else {887 } else {
885 try context.parsed_atoms.putNoClobber(match, atom);888 try context.object.end_atoms.putNoClobber(context.allocator, match, atom);
886 }889 }
887 } else if (parsed_rel.payload == .unsigned) {890 } else if (parsed_rel.payload == .unsigned) {
888 switch (parsed_rel.where) {891 switch (parsed_rel.where) {
...@@ -939,52 +942,63 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC...@@ -939,52 +942,63 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC
939 if (parsed_rel.where != .undef) break :blk;942 if (parsed_rel.where != .undef) break :blk;
940 if (context.macho_file.stubs_map.contains(parsed_rel.where_index)) break :blk;943 if (context.macho_file.stubs_map.contains(parsed_rel.where_index)) break :blk;
941944
942 const stub_helper_atom = try context.macho_file.createStubHelperAtom();
943 const laptr_atom = try context.macho_file.createLazyPointerAtom(
944 stub_helper_atom.local_sym_index,
945 parsed_rel.where_index,
946 );
947 const stub_atom = try context.macho_file.createStubAtom(laptr_atom.local_sym_index);
948 try context.macho_file.stubs_map.putNoClobber(context.allocator, parsed_rel.where_index, stub_atom);
949 // TODO clean this up!945 // TODO clean this up!
950 if (context.parsed_atoms.getPtr(.{946 const stub_helper_atom = atom: {
951 .seg = context.macho_file.text_segment_cmd_index.?,947 const atom = try context.macho_file.createStubHelperAtom();
952 .sect = context.macho_file.stub_helper_section_index.?,948 const match = MachO.MatchingSection{
953 })) |last| {
954 last.*.next = stub_helper_atom;
955 stub_helper_atom.prev = last.*;
956 last.* = stub_helper_atom;
957 } else {
958 try context.parsed_atoms.putNoClobber(.{
959 .seg = context.macho_file.text_segment_cmd_index.?,949 .seg = context.macho_file.text_segment_cmd_index.?,
960 .sect = context.macho_file.stub_helper_section_index.?,950 .sect = context.macho_file.stub_helper_section_index.?,
961 }, stub_helper_atom);951 };
962 }952 if (!context.object.start_atoms.contains(match)) {
963 if (context.parsed_atoms.getPtr(.{953 try context.object.start_atoms.putNoClobber(context.allocator, match, atom);
964 .seg = context.macho_file.text_segment_cmd_index.?,954 }
965 .sect = context.macho_file.stubs_section_index.?,955 if (context.object.end_atoms.getPtr(match)) |last| {
966 })) |last| {956 last.*.next = atom;
967 last.*.next = stub_atom;957 atom.prev = last.*;
968 stub_atom.prev = last.*;958 last.* = atom;
969 last.* = stub_atom;959 } else {
970 } else {960 try context.object.end_atoms.putNoClobber(context.allocator, match, atom);
971 try context.parsed_atoms.putNoClobber(.{961 }
972 .seg = context.macho_file.text_segment_cmd_index.?,962 break :atom atom;
973 .sect = context.macho_file.stubs_section_index.?,963 };
974 }, stub_atom);964 const laptr_atom = atom: {
975 }965 const atom = try context.macho_file.createLazyPointerAtom(
976 if (context.parsed_atoms.getPtr(.{966 stub_helper_atom.local_sym_index,
977 .seg = context.macho_file.data_segment_cmd_index.?,967 parsed_rel.where_index,
978 .sect = context.macho_file.la_symbol_ptr_section_index.?,968 );
979 })) |last| {969 const match = MachO.MatchingSection{
980 last.*.next = laptr_atom;
981 laptr_atom.prev = last.*;
982 last.* = laptr_atom;
983 } else {
984 try context.parsed_atoms.putNoClobber(.{
985 .seg = context.macho_file.data_segment_cmd_index.?,970 .seg = context.macho_file.data_segment_cmd_index.?,
986 .sect = context.macho_file.la_symbol_ptr_section_index.?,971 .sect = context.macho_file.la_symbol_ptr_section_index.?,
987 }, laptr_atom);972 };
973 if (!context.object.start_atoms.contains(match)) {
974 try context.object.start_atoms.putNoClobber(context.allocator, match, atom);
975 }
976 if (context.object.end_atoms.getPtr(match)) |last| {
977 last.*.next = atom;
978 atom.prev = last.*;
979 last.* = atom;
980 } else {
981 try context.object.end_atoms.putNoClobber(context.allocator, match, atom);
982 }
983 break :atom atom;
984 };
985 {
986 const atom = try context.macho_file.createStubAtom(laptr_atom.local_sym_index);
987 const match = MachO.MatchingSection{
988 .seg = context.macho_file.text_segment_cmd_index.?,
989 .sect = context.macho_file.stubs_section_index.?,
990 };
991 if (!context.object.start_atoms.contains(match)) {
992 try context.object.start_atoms.putNoClobber(context.allocator, match, atom);
993 }
994 if (context.object.end_atoms.getPtr(match)) |last| {
995 last.*.next = atom;
996 atom.prev = last.*;
997 last.* = atom;
998 } else {
999 try context.object.end_atoms.putNoClobber(context.allocator, match, atom);
1000 }
1001 try context.macho_file.stubs_map.putNoClobber(context.allocator, parsed_rel.where_index, atom);
988 }1002 }
989 }1003 }
990 }1004 }
src/link/MachO/Object.zig+160-249
...@@ -31,14 +31,12 @@ header: ?macho.mach_header_64 = null,...@@ -31,14 +31,12 @@ header: ?macho.mach_header_64 = null,
31load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},31load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
3232
33segment_cmd_index: ?u16 = null,33segment_cmd_index: ?u16 = null,
34text_section_index: ?u16 = null,
34symtab_cmd_index: ?u16 = null,35symtab_cmd_index: ?u16 = null,
35dysymtab_cmd_index: ?u16 = null,36dysymtab_cmd_index: ?u16 = null,
36build_version_cmd_index: ?u16 = null,37build_version_cmd_index: ?u16 = null,
37data_in_code_cmd_index: ?u16 = null,38data_in_code_cmd_index: ?u16 = null,
3839
39text_section_index: ?u16 = null,
40mod_init_func_section_index: ?u16 = null,
41
42// __DWARF segment sections40// __DWARF segment sections
43dwarf_debug_info_index: ?u16 = null,41dwarf_debug_info_index: ?u16 = null,
44dwarf_debug_abbrev_index: ?u16 = null,42dwarf_debug_abbrev_index: ?u16 = null,
...@@ -56,7 +54,9 @@ tu_name: ?[]const u8 = null,...@@ -56,7 +54,9 @@ tu_name: ?[]const u8 = null,
56tu_comp_dir: ?[]const u8 = null,54tu_comp_dir: ?[]const u8 = null,
57mtime: ?u64 = null,55mtime: ?u64 = null,
5856
59atoms: std.ArrayListUnmanaged(*Atom) = .{},57contained_atoms: std.ArrayListUnmanaged(*Atom) = .{},
58start_atoms: std.AutoHashMapUnmanaged(MachO.MatchingSection, *Atom) = .{},
59end_atoms: std.AutoHashMapUnmanaged(MachO.MatchingSection, *Atom) = .{},
60sections_as_symbols: std.AutoHashMapUnmanaged(u16, u32) = .{},60sections_as_symbols: std.AutoHashMapUnmanaged(u16, u32) = .{},
6161
62// TODO symbol mapping and its inverse can probably be simple arrays62// TODO symbol mapping and its inverse can probably be simple arrays
...@@ -138,12 +138,15 @@ pub fn deinit(self: *Object, allocator: *Allocator) void {...@@ -138,12 +138,15 @@ pub fn deinit(self: *Object, allocator: *Allocator) void {
138 self.data_in_code_entries.deinit(allocator);138 self.data_in_code_entries.deinit(allocator);
139 self.symtab.deinit(allocator);139 self.symtab.deinit(allocator);
140 self.strtab.deinit(allocator);140 self.strtab.deinit(allocator);
141 self.atoms.deinit(allocator);
142 self.sections_as_symbols.deinit(allocator);141 self.sections_as_symbols.deinit(allocator);
143 self.symbol_mapping.deinit(allocator);142 self.symbol_mapping.deinit(allocator);
144 self.reverse_symbol_mapping.deinit(allocator);143 self.reverse_symbol_mapping.deinit(allocator);
145 allocator.free(self.name);144 allocator.free(self.name);
146145
146 self.contained_atoms.deinit(allocator);
147 self.start_atoms.deinit(allocator);
148 self.end_atoms.deinit(allocator);
149
147 if (self.debug_info) |*db| {150 if (self.debug_info) |*db| {
148 db.deinit(allocator);151 db.deinit(allocator);
149 }152 }
...@@ -157,6 +160,67 @@ pub fn deinit(self: *Object, allocator: *Allocator) void {...@@ -157,6 +160,67 @@ pub fn deinit(self: *Object, allocator: *Allocator) void {
157 }160 }
158}161}
159162
163pub fn free(self: *Object, allocator: *Allocator, macho_file: *MachO) void {
164 log.debug("freeObject {*}", .{self});
165
166 var it = self.end_atoms.iterator();
167 while (it.next()) |entry| {
168 const match = entry.key_ptr.*;
169 const first_atom = self.start_atoms.get(match).?;
170 const last_atom = entry.value_ptr.*;
171 var atom = first_atom;
172
173 while (true) {
174 if (atom.local_sym_index != 0) {
175 macho_file.locals_free_list.append(allocator, atom.local_sym_index) catch {};
176 const local = &macho_file.locals.items[atom.local_sym_index];
177 local.n_type = 0;
178 atom.local_sym_index = 0;
179 }
180 if (atom == last_atom) {
181 break;
182 }
183 if (atom.next) |next| {
184 atom = next;
185 } else break;
186 }
187 }
188
189 self.freeAtoms(macho_file);
190}
191
192fn freeAtoms(self: *Object, macho_file: *MachO) void {
193 var it = self.end_atoms.iterator();
194 while (it.next()) |entry| {
195 const match = entry.key_ptr.*;
196 var first_atom: *Atom = self.start_atoms.get(match).?;
197 var last_atom: *Atom = entry.value_ptr.*;
198
199 if (macho_file.atoms.getPtr(match)) |atom_ptr| {
200 if (atom_ptr.* == last_atom) {
201 if (first_atom.prev) |prev| {
202 // TODO shrink the section size here
203 atom_ptr.* = prev;
204 } else {
205 _ = macho_file.atoms.fetchRemove(match);
206 }
207 }
208 }
209
210 if (first_atom.prev) |prev| {
211 prev.next = last_atom.next;
212 } else {
213 first_atom.prev = null;
214 }
215
216 if (last_atom.next) |next| {
217 next.prev = last_atom.prev;
218 } else {
219 last_atom.next = null;
220 }
221 }
222}
223
160pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void {224pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void {
161 const reader = self.file.reader();225 const reader = self.file.reader();
162 if (self.file_offset) |offset| {226 if (self.file_offset) |offset| {
...@@ -226,10 +290,6 @@ pub fn readLoadCommands(self: *Object, allocator: *Allocator, reader: anytype) !...@@ -226,10 +290,6 @@ pub fn readLoadCommands(self: *Object, allocator: *Allocator, reader: anytype) !
226 if (mem.eql(u8, sectname, "__text")) {290 if (mem.eql(u8, sectname, "__text")) {
227 self.text_section_index = index;291 self.text_section_index = index;
228 }292 }
229 } else if (mem.eql(u8, segname, "__DATA")) {
230 if (mem.eql(u8, sectname, "__mod_init_func")) {
231 self.mod_init_func_section_index = index;
232 }
233 }293 }
234294
235 sect.offset += offset;295 sect.offset += offset;
...@@ -320,7 +380,6 @@ const Context = struct {...@@ -320,7 +380,6 @@ const Context = struct {
320 object: *Object,380 object: *Object,
321 macho_file: *MachO,381 macho_file: *MachO,
322 match: MachO.MatchingSection,382 match: MachO.MatchingSection,
323 parsed_atoms: *ParsedAtoms,
324};383};
325384
326const AtomParser = struct {385const AtomParser = struct {
...@@ -437,7 +496,6 @@ const AtomParser = struct {...@@ -437,7 +496,6 @@ const AtomParser = struct {
437 .allocator = context.allocator,496 .allocator = context.allocator,
438 .object = context.object,497 .object = context.object,
439 .macho_file = context.macho_file,498 .macho_file = context.macho_file,
440 .parsed_atoms = context.parsed_atoms,
441 });499 });
442500
443 if (context.macho_file.has_dices) {501 if (context.macho_file.has_dices) {
...@@ -463,18 +521,10 @@ const AtomParser = struct {...@@ -463,18 +521,10 @@ const AtomParser = struct {
463 }521 }
464};522};
465523
466pub const ParsedAtoms = std.AutoHashMap(MachO.MatchingSection, *Atom);524pub fn parseIntoAtoms(self: *Object, allocator: *Allocator, macho_file: *MachO) !void {
467
468pub fn parseIntoAtoms(
469 self: *Object,
470 allocator: *Allocator,
471 object_id: u16,
472 macho_file: *MachO,
473) !ParsedAtoms {
474 const tracy = trace(@src());525 const tracy = trace(@src());
475 defer tracy.end();526 defer tracy.end();
476527
477 var parsed_atoms = ParsedAtoms.init(allocator);
478 const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;528 const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
479529
480 log.debug("analysing {s}", .{self.name});530 log.debug("analysing {s}", .{self.name});
...@@ -540,16 +590,6 @@ pub fn parseIntoAtoms(...@@ -540,16 +590,6 @@ pub fn parseIntoAtoms(
540 // Symbols within this section only.590 // Symbols within this section only.
541 const filtered_nlists = NlistWithIndex.filterInSection(sorted_nlists, sect);591 const filtered_nlists = NlistWithIndex.filterInSection(sorted_nlists, sect);
542592
543 // TODO rewrite and re-enable dead-code stripping optimisation. I think it might make sense
544 // to do this in a standalone pass after we parse the sections as atoms.
545 // In release mode, if the object file was generated with dead code stripping optimisations,
546 // note it now and parse sections as atoms.
547 // const is_splittable = blk: {
548 // if (macho_file.base.options.optimize_mode == .Debug) break :blk false;
549 // break :blk self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
550 // };
551 const is_splittable = false;
552
553 macho_file.has_dices = macho_file.has_dices or blk: {593 macho_file.has_dices = macho_file.has_dices or blk: {
554 if (self.text_section_index) |index| {594 if (self.text_section_index) |index| {
555 if (index != id) break :blk false;595 if (index != id) break :blk false;
...@@ -560,237 +600,108 @@ pub fn parseIntoAtoms(...@@ -560,237 +600,108 @@ pub fn parseIntoAtoms(
560 };600 };
561 macho_file.has_stabs = macho_file.has_stabs or self.debug_info != null;601 macho_file.has_stabs = macho_file.has_stabs or self.debug_info != null;
562602
563 next: {603 // Since there is no symbol to refer to this atom, we create
564 if (is_splittable) atoms: {604 // a temp one, unless we already did that when working out the relocations
565 if (filtered_nlists.len == 0) break :atoms;605 // of other atoms.
566606 const sym_name = try std.fmt.allocPrint(allocator, "l_{s}_{s}_{s}", .{
567 // If the first nlist does not match the start of the section,607 self.name,
568 // then we need to encapsulate the memory range [section start, first symbol)608 segmentName(sect),
569 // as a temporary symbol and insert the matching Atom.609 sectionName(sect),
570 const first_nlist = filtered_nlists[0].nlist;610 });
571 if (first_nlist.n_value > sect.addr) {611 defer allocator.free(sym_name);
572 const sym_name = try std.fmt.allocPrint(allocator, "l_{s}_{s}_{s}", .{612
573 self.name,613 const atom_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
574 segmentName(sect),614 const atom_local_sym_index = @intCast(u32, macho_file.locals.items.len);
575 sectionName(sect),615 try macho_file.locals.append(allocator, .{
576 });616 .n_strx = try macho_file.makeString(sym_name),
577 defer allocator.free(sym_name);617 .n_type = macho.N_SECT,
578618 .n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1),
579 const atom_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {619 .n_desc = 0,
580 const atom_local_sym_index = @intCast(u32, macho_file.locals.items.len);620 .n_value = 0,
581 try macho_file.locals.append(allocator, .{621 });
582 .n_strx = try macho_file.makeString(sym_name),622 try self.sections_as_symbols.putNoClobber(allocator, sect_id, atom_local_sym_index);
583 .n_type = macho.N_SECT,623 break :blk atom_local_sym_index;
584 .n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1),624 };
585 .n_desc = 0,625 const atom = try macho_file.createEmptyAtom(atom_local_sym_index, sect.size, sect.@"align");
586 .n_value = 0,
587 });
588 try self.sections_as_symbols.putNoClobber(allocator, sect_id, atom_local_sym_index);
589 break :blk atom_local_sym_index;
590 };
591 const atom_code = code[0 .. first_nlist.n_value - sect.addr];
592 const atom_size = atom_code.len;
593 const atom = try macho_file.createEmptyAtom(atom_local_sym_index, atom_size, sect.@"align");
594
595 const is_zerofill = blk: {
596 const section_type = commands.sectionType(sect);
597 break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL;
598 };
599 if (!is_zerofill) {
600 mem.copy(u8, atom.code.items, atom_code);
601 }
602
603 try atom.parseRelocs(relocs, .{
604 .base_addr = sect.addr,
605 .base_offset = 0,
606 .allocator = allocator,
607 .object = self,
608 .macho_file = macho_file,
609 .parsed_atoms = &parsed_atoms,
610 });
611
612 if (macho_file.has_dices) {
613 const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + atom_size);
614 try atom.dices.ensureTotalCapacity(allocator, dices.len);
615
616 for (dices) |dice| {
617 atom.dices.appendAssumeCapacity(.{
618 .offset = dice.offset - try math.cast(u32, sect.addr),
619 .length = dice.length,
620 .kind = dice.kind,
621 });
622 }
623 }
624
625 if (parsed_atoms.getPtr(match)) |last| {
626 last.*.next = atom;
627 atom.prev = last.*;
628 last.* = atom;
629 } else {
630 try parsed_atoms.putNoClobber(match, atom);
631 }
632 try self.atoms.append(allocator, atom);
633 }
634
635 var parser = AtomParser{
636 .section = sect,
637 .code = code,
638 .relocs = relocs,
639 .nlists = filtered_nlists,
640 };
641
642 while (try parser.next(.{
643 .allocator = allocator,
644 .object = self,
645 .macho_file = macho_file,
646 .match = match,
647 .parsed_atoms = &parsed_atoms,
648 })) |atom| {
649 const sym = macho_file.locals.items[atom.local_sym_index];
650 const is_ext = blk: {
651 const orig_sym_id = self.reverse_symbol_mapping.get(atom.local_sym_index) orelse unreachable;
652 break :blk MachO.symbolIsExt(self.symtab.items[orig_sym_id]);
653 };
654 if (is_ext) {
655 if (macho_file.symbol_resolver.get(sym.n_strx)) |resolv| {
656 assert(resolv.where == .global);
657 if (resolv.file != object_id) {
658 log.debug("deduping definition of {s} in {s}", .{
659 macho_file.getString(sym.n_strx),
660 self.name,
661 });
662 log.debug(" already defined in {s}", .{
663 macho_file.objects.items[resolv.file].name,
664 });
665 continue;
666 }
667 }
668 }
669626
670 if (sym.n_value == sect.addr) {627 const is_zerofill = blk: {
671 if (self.sections_as_symbols.get(sect_id)) |alias| {628 const section_type = commands.sectionType(sect);
672 // In x86_64 relocs, it can so happen that the compiler refers to the same629 break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL;
673 // atom by both the actual assigned symbol and the start of the section. In this630 };
674 // case, we need to link the two together so add an alias.631 if (!is_zerofill) {
675 try atom.aliases.append(allocator, alias);632 mem.copy(u8, atom.code.items, code);
676 }633 }
677 }
678634
679 if (parsed_atoms.getPtr(match)) |last| {635 try atom.parseRelocs(relocs, .{
680 last.*.next = atom;636 .base_addr = sect.addr,
681 atom.prev = last.*;637 .base_offset = 0,
682 last.* = atom;638 .allocator = allocator,
683 } else {639 .object = self,
684 try parsed_atoms.putNoClobber(match, atom);640 .macho_file = macho_file,
685 }641 });
686 try self.atoms.append(allocator, atom);
687 }
688642
689 break :next;643 if (macho_file.has_dices) {
690 }644 const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + sect.size);
645 try atom.dices.ensureTotalCapacity(allocator, dices.len);
691646
692 // Since there is no symbol to refer to this atom, we create647 for (dices) |dice| {
693 // a temp one, unless we already did that when working out the relocations648 atom.dices.appendAssumeCapacity(.{
694 // of other atoms.649 .offset = dice.offset - try math.cast(u32, sect.addr),
695 const sym_name = try std.fmt.allocPrint(allocator, "l_{s}_{s}_{s}", .{650 .length = dice.length,
696 self.name,651 .kind = dice.kind,
697 segmentName(sect),
698 sectionName(sect),
699 });
700 defer allocator.free(sym_name);
701
702 const atom_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
703 const atom_local_sym_index = @intCast(u32, macho_file.locals.items.len);
704 try macho_file.locals.append(allocator, .{
705 .n_strx = try macho_file.makeString(sym_name),
706 .n_type = macho.N_SECT,
707 .n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1),
708 .n_desc = 0,
709 .n_value = 0,
710 });652 });
711 try self.sections_as_symbols.putNoClobber(allocator, sect_id, atom_local_sym_index);
712 break :blk atom_local_sym_index;
713 };
714 const atom = try macho_file.createEmptyAtom(atom_local_sym_index, sect.size, sect.@"align");
715
716 const is_zerofill = blk: {
717 const section_type = commands.sectionType(sect);
718 break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL;
719 };
720 if (!is_zerofill) {
721 mem.copy(u8, atom.code.items, code);
722 }
723
724 try atom.parseRelocs(relocs, .{
725 .base_addr = sect.addr,
726 .base_offset = 0,
727 .allocator = allocator,
728 .object = self,
729 .macho_file = macho_file,
730 .parsed_atoms = &parsed_atoms,
731 });
732
733 if (macho_file.has_dices) {
734 const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + sect.size);
735 try atom.dices.ensureTotalCapacity(allocator, dices.len);
736
737 for (dices) |dice| {
738 atom.dices.appendAssumeCapacity(.{
739 .offset = dice.offset - try math.cast(u32, sect.addr),
740 .length = dice.length,
741 .kind = dice.kind,
742 });
743 }
744 }653 }
654 }
745655
746 // Since this is atom gets a helper local temporary symbol that didn't exist656 // Since this is atom gets a helper local temporary symbol that didn't exist
747 // in the object file which encompasses the entire section, we need traverse657 // in the object file which encompasses the entire section, we need traverse
748 // the filtered symbols and note which symbol is contained within so that658 // the filtered symbols and note which symbol is contained within so that
749 // we can properly allocate addresses down the line.659 // we can properly allocate addresses down the line.
750 // While we're at it, we need to update segment,section mapping of each symbol too.660 // While we're at it, we need to update segment,section mapping of each symbol too.
751 try atom.contained.ensureTotalCapacity(allocator, filtered_nlists.len);661 try atom.contained.ensureTotalCapacity(allocator, filtered_nlists.len);
752662
753 for (filtered_nlists) |nlist_with_index| {663 for (filtered_nlists) |nlist_with_index| {
754 const nlist = nlist_with_index.nlist;664 const nlist = nlist_with_index.nlist;
755 const local_sym_index = self.symbol_mapping.get(nlist_with_index.index) orelse unreachable;665 const local_sym_index = self.symbol_mapping.get(nlist_with_index.index) orelse unreachable;
756 const local = &macho_file.locals.items[local_sym_index];666 const local = &macho_file.locals.items[local_sym_index];
757 local.n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1);667 local.n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1);
758668
759 const stab: ?Atom.Stab = if (self.debug_info) |di| blk: {669 const stab: ?Atom.Stab = if (self.debug_info) |di| blk: {
760 // TODO there has to be a better to handle this.670 // TODO there has to be a better to handle this.
761 for (di.inner.func_list.items) |func| {671 for (di.inner.func_list.items) |func| {
762 if (func.pc_range) |range| {672 if (func.pc_range) |range| {
763 if (nlist.n_value >= range.start and nlist.n_value < range.end) {673 if (nlist.n_value >= range.start and nlist.n_value < range.end) {
764 break :blk Atom.Stab{674 break :blk Atom.Stab{
765 .function = range.end - range.start,675 .function = range.end - range.start,
766 };676 };
767 }
768 }677 }
769 }678 }
770 // TODO679 }
771 // if (zld.globals.contains(zld.getString(sym.strx))) break :blk .global;680 // TODO
772 break :blk .static;681 // if (zld.globals.contains(zld.getString(sym.strx))) break :blk .global;
773 } else null;682 break :blk .static;
774683 } else null;
775 atom.contained.appendAssumeCapacity(.{684
776 .local_sym_index = local_sym_index,685 atom.contained.appendAssumeCapacity(.{
777 .offset = nlist.n_value - sect.addr,686 .local_sym_index = local_sym_index,
778 .stab = stab,687 .offset = nlist.n_value - sect.addr,
779 });688 .stab = stab,
780 }689 });
690 }
781691
782 if (parsed_atoms.getPtr(match)) |last| {692 if (!self.start_atoms.contains(match)) {
783 last.*.next = atom;693 try self.start_atoms.putNoClobber(allocator, match, atom);
784 atom.prev = last.*;
785 last.* = atom;
786 } else {
787 try parsed_atoms.putNoClobber(match, atom);
788 }
789 try self.atoms.append(allocator, atom);
790 }694 }
791 }
792695
793 return parsed_atoms;696 if (self.end_atoms.getPtr(match)) |last| {
697 last.*.next = atom;
698 atom.prev = last.*;
699 last.* = atom;
700 } else {
701 try self.end_atoms.putNoClobber(allocator, match, atom);
702 }
703 try self.contained_atoms.append(allocator, atom);
704 }
794}705}
795706
796fn parseSymtab(self: *Object, allocator: *Allocator) !void {707fn parseSymtab(self: *Object, allocator: *Allocator) !void {