Commit 8856ba7505

Luuk de Gram <luuk@degram.dev>
2023-11-16 19:29:57
wasm-linker: parse symbols into atoms lazily
Rather than parsing every symbol into an atom, we now only parse them into an atom when such atom is marked. This means garbage-collected symbols will also not be parsed into atoms, and neither are discarded symbols which have been resolved by other symbols. (Such as multiple weak symbols). This also introduces a binary search for finding the start index into the list of relocations. This speeds up finding the corresponding relocations tremendously as they're ordered ascended by address. Lastly, we re-use the memory of atom's data as well as relocations instead of duplicating it. This means we half the memory usage of atom's data and relocations for linked object files. As we are aware of decls and synthetic atoms, we free the memory of those atoms indepedently of the atoms of object files to prevent double-frees.
1 parent c986c6c
Changed files (3)
src/link/Wasm/Atom.zig
@@ -23,6 +23,10 @@ alignment: Wasm.Alignment,
 /// Offset into the section where the atom lives, this already accounts
 /// for alignment.
 offset: u32,
+/// The original offset within the object file. This value is substracted from
+/// relocation offsets to determine where in the `data` to rewrite the value
+original_offset: u32,
+
 /// Represents the index of the file this atom was generated from.
 /// This is 'null' when the atom was generated by a Decl from Zig code.
 file: ?u16,
@@ -50,11 +54,11 @@ pub const empty: Atom = .{
     .prev = null,
     .size = 0,
     .sym_index = 0,
+    .original_offset = 0,
 };
 
 /// Frees all resources owned by this `Atom`.
-pub fn deinit(atom: *Atom, wasm: *Wasm) void {
-    const gpa = wasm.base.allocator;
+pub fn deinit(atom: *Atom, gpa: std.mem.Allocator) void {
     atom.relocs.deinit(gpa);
     atom.code.deinit(gpa);
     atom.locals.deinit(gpa);
@@ -114,10 +118,10 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
             .R_WASM_GLOBAL_INDEX_I32,
             .R_WASM_MEMORY_ADDR_I32,
             .R_WASM_SECTION_OFFSET_I32,
-            => std.mem.writeInt(u32, atom.code.items[reloc.offset..][0..4], @as(u32, @intCast(value)), .little),
+            => std.mem.writeInt(u32, atom.code.items[reloc.offset - atom.original_offset ..][0..4], @as(u32, @intCast(value)), .little),
             .R_WASM_TABLE_INDEX_I64,
             .R_WASM_MEMORY_ADDR_I64,
-            => std.mem.writeInt(u64, atom.code.items[reloc.offset..][0..8], value, .little),
+            => std.mem.writeInt(u64, atom.code.items[reloc.offset - atom.original_offset ..][0..8], value, .little),
             .R_WASM_GLOBAL_INDEX_LEB,
             .R_WASM_EVENT_INDEX_LEB,
             .R_WASM_FUNCTION_INDEX_LEB,
@@ -127,12 +131,12 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
             .R_WASM_TABLE_NUMBER_LEB,
             .R_WASM_TYPE_INDEX_LEB,
             .R_WASM_MEMORY_ADDR_TLS_SLEB,
-            => leb.writeUnsignedFixed(5, atom.code.items[reloc.offset..][0..5], @as(u32, @intCast(value))),
+            => leb.writeUnsignedFixed(5, atom.code.items[reloc.offset - atom.original_offset ..][0..5], @as(u32, @intCast(value))),
             .R_WASM_MEMORY_ADDR_LEB64,
             .R_WASM_MEMORY_ADDR_SLEB64,
             .R_WASM_TABLE_INDEX_SLEB64,
             .R_WASM_MEMORY_ADDR_TLS_SLEB64,
-            => leb.writeUnsignedFixed(10, atom.code.items[reloc.offset..][0..10], value),
+            => leb.writeUnsignedFixed(10, atom.code.items[reloc.offset - atom.original_offset ..][0..10], value),
         }
     }
 }
@@ -150,7 +154,7 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
         .R_WASM_TABLE_INDEX_I64,
         .R_WASM_TABLE_INDEX_SLEB,
         .R_WASM_TABLE_INDEX_SLEB64,
-        => return wasm_bin.function_table.get(target_loc) orelse 0,
+        => return wasm_bin.function_table.get(.{ .file = atom.file, .index = relocation.index }) orelse 0,
         .R_WASM_TYPE_INDEX_LEB => {
             const file_index = atom.file orelse {
                 return relocation.index;
src/link/Wasm/Object.zig
@@ -59,20 +59,16 @@ init_funcs: []const types.InitFunc = &.{},
 comdat_info: []const types.Comdat = &.{},
 /// Represents non-synthetic sections that can essentially be mem-cpy'd into place
 /// after performing relocations.
-relocatable_data: []const RelocatableData = &.{},
+relocatable_data: std.AutoHashMapUnmanaged(RelocatableData.Tag, []RelocatableData) = .{},
 /// String table for all strings required by the object file, such as symbol names,
 /// import name, module name and export names. Each string will be deduplicated
 /// and returns an offset into the table.
 string_table: Wasm.StringTable = .{},
-/// All the names of each debug section found in the current object file.
-/// Each name is terminated by a null-terminator. The name can be found,
-/// from the `index` offset within the `RelocatableData`.
-debug_names: [:0]const u8,
 
 /// Represents a single item within a section (depending on its `type`)
 const RelocatableData = struct {
     /// The type of the relocatable data
-    type: enum { data, code, debug },
+    type: Tag,
     /// Pointer to the data of the segment, where its length is written to `size`
     data: [*]u8,
     /// The size in bytes of the data representing the segment within the section
@@ -85,6 +81,8 @@ const RelocatableData = struct {
     /// Represents the index of the section it belongs to
     section_index: u32,
 
+    const Tag = enum { data, code, custom };
+
     /// Returns the alignment of the segment, by retrieving it from the segment
     /// meta data of the given object file.
     /// NOTE: Alignment is encoded as a power of 2, so we shift the symbol's
@@ -99,14 +97,14 @@ const RelocatableData = struct {
         return switch (relocatable_data.type) {
             .data => .data,
             .code => .function,
-            .debug => .section,
+            .custom => .section,
         };
     }
 
-    /// Returns the index within a section itrelocatable_data, or in case of a debug section,
+    /// Returns the index within a section, or in case of a custom section,
     /// returns the section index within the object file.
     pub fn getIndex(relocatable_data: RelocatableData) u32 {
-        if (relocatable_data.type == .debug) return relocatable_data.section_index;
+        if (relocatable_data.type == .custom) return relocatable_data.section_index;
         return relocatable_data.index;
     }
 };
@@ -121,7 +119,6 @@ pub fn create(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_siz
     var object: Object = .{
         .file = file,
         .name = try gpa.dupe(u8, name),
-        .debug_names = &.{},
     };
 
     var is_object_file: bool = false;
@@ -182,10 +179,16 @@ pub fn deinit(object: *Object, gpa: Allocator) void {
         gpa.free(info.name);
     }
     gpa.free(object.segment_info);
-    for (object.relocatable_data) |rel_data| {
-        gpa.free(rel_data.data[0..rel_data.size]);
+    {
+        var it = object.relocatable_data.valueIterator();
+        while (it.next()) |relocatable_data| {
+            for (relocatable_data.*) |rel_data| {
+                gpa.free(rel_data.data[0..rel_data.size]);
+            }
+            gpa.free(relocatable_data.*);
+        }
     }
-    gpa.free(object.relocatable_data);
+    object.relocatable_data.deinit(gpa);
     object.string_table.deinit(gpa);
     gpa.free(object.name);
     object.* = undefined;
@@ -345,23 +348,7 @@ fn Parser(comptime ReaderType: type) type {
             errdefer parser.object.deinit(gpa);
             try parser.verifyMagicBytes();
             const version = try parser.reader.reader().readInt(u32, .little);
-
             parser.object.version = version;
-            var relocatable_data = std.ArrayList(RelocatableData).init(gpa);
-            var debug_names = std.ArrayList(u8).init(gpa);
-
-            errdefer {
-                // only free the inner contents of relocatable_data if we didn't
-                // assign it to the object yet.
-                if (parser.object.relocatable_data.len == 0) {
-                    for (relocatable_data.items) |rel_data| {
-                        gpa.free(rel_data.data[0..rel_data.size]);
-                    }
-                    relocatable_data.deinit();
-                }
-                gpa.free(debug_names.items);
-                debug_names.deinit();
-            }
 
             var section_index: u32 = 0;
             while (parser.reader.reader().readByte()) |byte| : (section_index += 1) {
@@ -377,26 +364,34 @@ fn Parser(comptime ReaderType: type) type {
 
                         if (std.mem.eql(u8, name, "linking")) {
                             is_object_file.* = true;
-                            parser.object.relocatable_data = relocatable_data.items; // at this point no new relocatable sections will appear so we're free to store them.
                             try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left)));
                         } else if (std.mem.startsWith(u8, name, "reloc")) {
                             try parser.parseRelocations(gpa);
                         } else if (std.mem.eql(u8, name, "target_features")) {
                             try parser.parseFeatures(gpa);
                         } else if (std.mem.startsWith(u8, name, ".debug")) {
+                            const gop = try parser.object.relocatable_data.getOrPut(gpa, .custom);
+                            var relocatable_data: std.ArrayListUnmanaged(RelocatableData) = .{};
+                            defer relocatable_data.deinit(gpa);
+                            if (!gop.found_existing) {
+                                gop.value_ptr.* = &.{};
+                            } else {
+                                relocatable_data = std.ArrayListUnmanaged(RelocatableData).fromOwnedSlice(gop.value_ptr.*);
+                            }
                             const debug_size = @as(u32, @intCast(reader.context.bytes_left));
                             const debug_content = try gpa.alloc(u8, debug_size);
                             errdefer gpa.free(debug_content);
                             try reader.readNoEof(debug_content);
 
-                            try relocatable_data.append(.{
-                                .type = .debug,
+                            try relocatable_data.append(gpa, .{
+                                .type = .custom,
                                 .data = debug_content.ptr,
                                 .size = debug_size,
                                 .index = try parser.object.string_table.put(gpa, name),
                                 .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset
                                 .section_index = section_index,
                             });
+                            gop.value_ptr.* = try relocatable_data.toOwnedSlice(gpa);
                         } else {
                             try reader.skipBytes(reader.context.bytes_left, .{});
                         }
@@ -515,26 +510,32 @@ fn Parser(comptime ReaderType: type) type {
                         const start = reader.context.bytes_left;
                         var index: u32 = 0;
                         const count = try readLeb(u32, reader);
+                        const imported_function_count = parser.object.importedCountByKind(.function);
+                        var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
+                        defer relocatable_data.deinit();
                         while (index < count) : (index += 1) {
                             const code_len = try readLeb(u32, reader);
                             const offset = @as(u32, @intCast(start - reader.context.bytes_left));
                             const data = try gpa.alloc(u8, code_len);
                             errdefer gpa.free(data);
                             try reader.readNoEof(data);
-                            try relocatable_data.append(.{
+                            relocatable_data.appendAssumeCapacity(.{
                                 .type = .code,
                                 .data = data.ptr,
                                 .size = code_len,
-                                .index = parser.object.importedCountByKind(.function) + index,
+                                .index = imported_function_count + index,
                                 .offset = offset,
                                 .section_index = section_index,
                             });
                         }
+                        try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice());
                     },
                     .data => {
                         const start = reader.context.bytes_left;
                         var index: u32 = 0;
                         const count = try readLeb(u32, reader);
+                        var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
+                        defer relocatable_data.deinit();
                         while (index < count) : (index += 1) {
                             const flags = try readLeb(u32, reader);
                             const data_offset = try readInit(reader);
@@ -545,7 +546,7 @@ fn Parser(comptime ReaderType: type) type {
                             const data = try gpa.alloc(u8, data_len);
                             errdefer gpa.free(data);
                             try reader.readNoEof(data);
-                            try relocatable_data.append(.{
+                            relocatable_data.appendAssumeCapacity(.{
                                 .type = .data,
                                 .data = data.ptr,
                                 .size = data_len,
@@ -554,6 +555,7 @@ fn Parser(comptime ReaderType: type) type {
                                 .section_index = section_index,
                             });
                         }
+                        try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice());
                     },
                     else => try parser.reader.reader().skipBytes(len, .{}),
                 }
@@ -561,7 +563,6 @@ fn Parser(comptime ReaderType: type) type {
                 error.EndOfStream => {}, // finished parsing the file
                 else => |e| return e,
             }
-            parser.object.relocatable_data = try relocatable_data.toOwnedSlice();
         }
 
         /// Based on the "features" custom section, parses it into a list of
@@ -789,7 +790,8 @@ fn Parser(comptime ReaderType: type) type {
                 },
                 .section => {
                     symbol.index = try leb.readULEB128(u32, reader);
-                    for (parser.object.relocatable_data) |data| {
+                    const section_data = parser.object.relocatable_data.get(.custom).?;
+                    for (section_data) |data| {
                         if (data.section_index == symbol.index) {
                             symbol.name = data.index;
                             break;
@@ -798,22 +800,15 @@ fn Parser(comptime ReaderType: type) type {
                 },
                 else => {
                     symbol.index = try leb.readULEB128(u32, reader);
-                    var maybe_import: ?types.Import = null;
-
                     const is_undefined = symbol.isUndefined();
-                    if (is_undefined) {
-                        maybe_import = parser.object.findImport(symbol.tag.externalType(), symbol.index);
-                    }
                     const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME);
-                    if (!(is_undefined and !explicit_name)) {
+                    symbol.name = if (!is_undefined or (is_undefined and explicit_name)) name: {
                         const name_len = try leb.readULEB128(u32, reader);
                         const name = try gpa.alloc(u8, name_len);
                         defer gpa.free(name);
                         try reader.readNoEof(name);
-                        symbol.name = try parser.object.string_table.put(gpa, name);
-                    } else {
-                        symbol.name = maybe_import.?.name;
-                    }
+                        break :name try parser.object.string_table.put(gpa, name);
+                    } else parser.object.findImport(symbol.tag.externalType(), symbol.index).name;
                 },
             }
             return symbol;
@@ -887,110 +882,95 @@ fn assertEnd(reader: anytype) !void {
 }
 
 /// Parses an object file into atoms, for code and data sections
-pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_bin: *Wasm) !void {
-    const Key = struct {
-        kind: Symbol.Tag,
-        index: u32,
-    };
-    var symbol_for_segment = std.AutoArrayHashMap(Key, std.ArrayList(u32)).init(gpa);
-    defer for (symbol_for_segment.values()) |*list| {
-        list.deinit();
-    } else symbol_for_segment.deinit();
-
-    for (object.symtable, 0..) |symbol, symbol_index| {
-        switch (symbol.tag) {
-            .function, .data, .section => if (!symbol.isUndefined()) {
-                const gop = try symbol_for_segment.getOrPut(.{ .kind = symbol.tag, .index = symbol.index });
-                const sym_idx = @as(u32, @intCast(symbol_index));
-                if (!gop.found_existing) {
-                    gop.value_ptr.* = std.ArrayList(u32).init(gpa);
+pub fn parseSymbolIntoAtom(object: *Object, object_index: u16, symbol_index: u32, wasm: *Wasm) !Atom.Index {
+    const symbol = &object.symtable[symbol_index];
+    const relocatable_data: RelocatableData = switch (symbol.tag) {
+        .function => object.relocatable_data.get(.code).?[symbol.index - object.importedCountByKind(.function)],
+        .data => object.relocatable_data.get(.data).?[symbol.index],
+        .section => blk: {
+            const data = object.relocatable_data.get(.custom).?;
+            for (data) |dat| {
+                if (dat.section_index == symbol.index) {
+                    break :blk dat;
                 }
-                try gop.value_ptr.*.append(sym_idx);
-            },
-            else => continue,
-        }
+            }
+            unreachable;
+        },
+        else => unreachable,
+    };
+    const final_index = try wasm.getMatchingSegment(object_index, symbol_index);
+    const atom_index = @as(Atom.Index, @intCast(wasm.managed_atoms.items.len));
+    const atom = try wasm.managed_atoms.addOne(wasm.base.allocator);
+    atom.* = Atom.empty;
+    try wasm.appendAtomAtIndex(final_index, atom_index);
+
+    atom.sym_index = symbol_index;
+    atom.file = object_index;
+    atom.size = relocatable_data.size;
+    atom.alignment = relocatable_data.getAlignment(object);
+    atom.code = std.ArrayListUnmanaged(u8).fromOwnedSlice(relocatable_data.data[0..relocatable_data.size]);
+    atom.original_offset = relocatable_data.offset;
+    try wasm.symbol_atom.putNoClobber(wasm.base.allocator, atom.symbolLoc(), atom_index);
+    const segment: *Wasm.Segment = &wasm.segments.items[final_index];
+    if (relocatable_data.type == .data) { //code section and custom sections are 1-byte aligned
+        segment.alignment = segment.alignment.max(atom.alignment);
     }
 
-    for (object.relocatable_data, 0..) |relocatable_data, index| {
-        const final_index = (try wasm_bin.getMatchingSegment(object_index, @as(u32, @intCast(index)))) orelse {
-            continue; // found unknown section, so skip parsing into atom as we do not know how to handle it.
-        };
-
-        const atom_index: Atom.Index = @intCast(wasm_bin.managed_atoms.items.len);
-        const atom = try wasm_bin.managed_atoms.addOne(gpa);
-        atom.* = Atom.empty;
-        atom.file = object_index;
-        atom.size = relocatable_data.size;
-        atom.alignment = relocatable_data.getAlignment(object);
-
-        const relocations: []types.Relocation = object.relocations.get(relocatable_data.section_index) orelse &.{};
-        for (relocations) |relocation| {
-            if (isInbetween(relocatable_data.offset, atom.size, relocation.offset)) {
-                // set the offset relative to the offset of the segment itobject,
-                // rather than within the entire section.
-                var reloc = relocation;
-                reloc.offset -= relocatable_data.offset;
-                try atom.relocs.append(gpa, reloc);
-
-                switch (relocation.relocation_type) {
-                    .R_WASM_TABLE_INDEX_I32,
-                    .R_WASM_TABLE_INDEX_I64,
-                    .R_WASM_TABLE_INDEX_SLEB,
-                    .R_WASM_TABLE_INDEX_SLEB64,
-                    => {
-                        try wasm_bin.function_table.put(gpa, .{
-                            .file = object_index,
-                            .index = relocation.index,
-                        }, 0);
-                    },
-                    .R_WASM_GLOBAL_INDEX_I32,
-                    .R_WASM_GLOBAL_INDEX_LEB,
-                    => {
-                        const sym = object.symtable[relocation.index];
-                        if (sym.tag != .global) {
-                            try wasm_bin.got_symbols.append(
-                                wasm_bin.base.allocator,
-                                .{ .file = object_index, .index = relocation.index },
-                            );
-                        }
-                    },
-                    else => {},
-                }
+    if (object.relocations.get(relocatable_data.section_index)) |relocations| {
+        const start = searchRelocStart(relocations, relocatable_data.offset);
+        const len = searchRelocEnd(relocations[start..], relocatable_data.offset + atom.size);
+        atom.relocs = std.ArrayListUnmanaged(types.Relocation).fromOwnedSlice(relocations[start..][0..len]);
+        for (atom.relocs.items) |*reloc| {
+            switch (reloc.relocation_type) {
+                .R_WASM_TABLE_INDEX_I32,
+                .R_WASM_TABLE_INDEX_I64,
+                .R_WASM_TABLE_INDEX_SLEB,
+                .R_WASM_TABLE_INDEX_SLEB64,
+                => {
+                    try wasm.function_table.put(wasm.base.allocator, .{
+                        .file = object_index,
+                        .index = reloc.index,
+                    }, 0);
+                },
+                .R_WASM_GLOBAL_INDEX_I32,
+                .R_WASM_GLOBAL_INDEX_LEB,
+                => {
+                    const sym = object.symtable[reloc.index];
+                    if (sym.tag != .global) {
+                        try wasm.got_symbols.append(
+                            wasm.base.allocator,
+                            .{ .file = object_index, .index = reloc.index },
+                        );
+                    }
+                },
+                else => {},
             }
         }
+    }
 
-        try atom.code.appendSlice(gpa, relocatable_data.data[0..relocatable_data.size]);
-
-        if (symbol_for_segment.getPtr(.{
-            .kind = relocatable_data.getSymbolKind(),
-            .index = relocatable_data.getIndex(),
-        })) |symbols| {
-            atom.sym_index = symbols.pop();
-            try wasm_bin.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), atom_index);
-
-            // symbols referencing the same atom will be added as alias
-            // or as 'parent' when they are global.
-            while (symbols.popOrNull()) |idx| {
-                try wasm_bin.symbol_atom.putNoClobber(gpa, .{ .file = atom.file, .index = idx }, atom_index);
-                const alias_symbol = object.symtable[idx];
-                if (alias_symbol.isGlobal()) {
-                    atom.sym_index = idx;
-                }
-            }
-        }
+    return atom_index;
+}
 
-        const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index];
-        if (relocatable_data.type == .data) { //code section and debug sections are 1-byte aligned
-            segment.alignment = segment.alignment.max(atom.alignment);
+fn searchRelocStart(relocs: []const types.Relocation, address: u32) usize {
+    var min: usize = 0;
+    var max: usize = relocs.len;
+    while (min < max) {
+        const index = (min + max) / 2;
+        const curr = relocs[index];
+        if (curr.offset < address) {
+            min = index + 1;
+        } else {
+            max = index;
         }
-
-        try wasm_bin.appendAtomAtIndex(final_index, atom_index);
-        log.debug("Parsed into atom: '{s}' at segment index {d}", .{ object.string_table.get(object.symtable[atom.sym_index].name), final_index });
     }
+    return min;
 }
 
-/// Verifies if a given value is in between a minimum -and maximum value.
-/// The maxmimum value is calculated using the length, both start and end are inclusive.
-inline fn isInbetween(min: u32, length: u32, value: u32) bool {
-    return value >= min and value <= min + length;
+fn searchRelocEnd(relocs: []const types.Relocation, address: u32) usize {
+    for (relocs, 0..relocs.len) |reloc, index| {
+        if (reloc.offset > address) {
+            return index;
+        }
+    }
+    return relocs.len;
 }
src/link/Wasm.zig
@@ -1309,6 +1309,31 @@ pub fn deinit(wasm: *Wasm) void {
         archive.deinit(gpa);
     }
 
+    // For decls and anon decls we free the memory of its atoms.
+    // The memory of atoms parsed from object files is managed by
+    // the object file itself, and therefore we can skip those.
+    {
+        var it = wasm.decls.valueIterator();
+        while (it.next()) |atom_index_ptr| {
+            const atom = wasm.getAtomPtr(atom_index_ptr.*);
+            for (atom.locals.items) |local_index| {
+                const local_atom = wasm.getAtomPtr(local_index);
+                local_atom.deinit(gpa);
+            }
+            atom.deinit(gpa);
+        }
+    }
+    {
+        for (wasm.anon_decls.values()) |atom_index| {
+            const atom = wasm.getAtomPtr(atom_index);
+            for (atom.locals.items) |local_index| {
+                const local_atom = wasm.getAtomPtr(local_index);
+                local_atom.deinit(gpa);
+            }
+            atom.deinit(gpa);
+        }
+    }
+
     wasm.decls.deinit(gpa);
     wasm.anon_decls.deinit(gpa);
     wasm.atom_types.deinit(gpa);
@@ -1321,9 +1346,6 @@ pub fn deinit(wasm: *Wasm) void {
     wasm.symbol_atom.deinit(gpa);
     wasm.export_names.deinit(gpa);
     wasm.atoms.deinit(gpa);
-    for (wasm.managed_atoms.items) |*managed_atom| {
-        managed_atom.deinit(wasm);
-    }
     wasm.managed_atoms.deinit(gpa);
     wasm.segments.deinit(gpa);
     wasm.data_segments.deinit(gpa);
@@ -1342,6 +1364,10 @@ pub fn deinit(wasm: *Wasm) void {
     wasm.exports.deinit(gpa);
 
     wasm.string_table.deinit(gpa);
+    for (wasm.synthetic_functions.items) |atom_index| {
+        const atom = wasm.getAtomPtr(atom_index);
+        atom.deinit(gpa);
+    }
     wasm.synthetic_functions.deinit(gpa);
 
     if (wasm.dwarf) |*dwarf| {
@@ -2406,7 +2432,7 @@ fn setupErrorsLen(wasm: *Wasm) !void {
             prev_atom.next = atom.next;
             atom.prev = null;
         }
-        atom.deinit(wasm);
+        atom.deinit(wasm.base.allocator);
         break :blk index;
     } else new_atom: {
         const atom_index: Atom.Index = @intCast(wasm.managed_atoms.items.len);
@@ -2509,6 +2535,7 @@ fn createSyntheticFunction(
         .next = null,
         .prev = null,
         .code = function_body.moveToUnmanaged(),
+        .original_offset = 0,
     };
     try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom_index);
     try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom_index);
@@ -2545,6 +2572,7 @@ pub fn createFunction(
         .prev = null,
         .code = function_body.moveToUnmanaged(),
         .relocs = relocations.moveToUnmanaged(),
+        .original_offset = 0,
     };
     const symbol = loc.getSymbol(wasm);
     symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); // ensure function does not get exported
@@ -3016,14 +3044,14 @@ fn setupMemory(wasm: *Wasm) !void {
 /// From a given object's index and the index of the segment, returns the corresponding
 /// index of the segment within the final data section. When the segment does not yet
 /// exist, a new one will be initialized and appended. The new index will be returned in that case.
-pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32) !?u32 {
+pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, symbol_index: u32) !u32 {
     const object: Object = wasm.objects.items[object_index];
-    const relocatable_data = object.relocatable_data[relocatable_index];
+    const symbol = object.symtable[symbol_index];
     const index = @as(u32, @intCast(wasm.segments.items.len));
 
-    switch (relocatable_data.type) {
+    switch (symbol.tag) {
         .data => {
-            const segment_info = object.segment_info[relocatable_data.index];
+            const segment_info = object.segment_info[symbol.index];
             const merge_segment = wasm.base.options.output_mode != .Obj;
             const result = try wasm.data_segments.getOrPut(wasm.base.allocator, segment_info.outputName(merge_segment));
             if (!result.found_existing) {
@@ -3041,67 +3069,67 @@ pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32
                 return index;
             } else return result.value_ptr.*;
         },
-        .code => return wasm.code_section_index orelse blk: {
+        .function => return wasm.code_section_index orelse blk: {
             wasm.code_section_index = index;
             try wasm.appendDummySegment();
             break :blk index;
         },
-        .debug => {
-            const debug_name = object.getDebugName(relocatable_data);
-            if (mem.eql(u8, debug_name, ".debug_info")) {
+        .section => {
+            const section_name = object.string_table.get(symbol.name);
+            if (mem.eql(u8, section_name, ".debug_info")) {
                 return wasm.debug_info_index orelse blk: {
                     wasm.debug_info_index = index;
                     try wasm.appendDummySegment();
                     break :blk index;
                 };
-            } else if (mem.eql(u8, debug_name, ".debug_line")) {
+            } else if (mem.eql(u8, section_name, ".debug_line")) {
                 return wasm.debug_line_index orelse blk: {
                     wasm.debug_line_index = index;
                     try wasm.appendDummySegment();
                     break :blk index;
                 };
-            } else if (mem.eql(u8, debug_name, ".debug_loc")) {
+            } else if (mem.eql(u8, section_name, ".debug_loc")) {
                 return wasm.debug_loc_index orelse blk: {
                     wasm.debug_loc_index = index;
                     try wasm.appendDummySegment();
                     break :blk index;
                 };
-            } else if (mem.eql(u8, debug_name, ".debug_ranges")) {
+            } else if (mem.eql(u8, section_name, ".debug_ranges")) {
                 return wasm.debug_line_index orelse blk: {
                     wasm.debug_ranges_index = index;
                     try wasm.appendDummySegment();
                     break :blk index;
                 };
-            } else if (mem.eql(u8, debug_name, ".debug_pubnames")) {
+            } else if (mem.eql(u8, section_name, ".debug_pubnames")) {
                 return wasm.debug_pubnames_index orelse blk: {
                     wasm.debug_pubnames_index = index;
                     try wasm.appendDummySegment();
                     break :blk index;
                 };
-            } else if (mem.eql(u8, debug_name, ".debug_pubtypes")) {
+            } else if (mem.eql(u8, section_name, ".debug_pubtypes")) {
                 return wasm.debug_pubtypes_index orelse blk: {
                     wasm.debug_pubtypes_index = index;
                     try wasm.appendDummySegment();
                     break :blk index;
                 };
-            } else if (mem.eql(u8, debug_name, ".debug_abbrev")) {
+            } else if (mem.eql(u8, section_name, ".debug_abbrev")) {
                 return wasm.debug_abbrev_index orelse blk: {
                     wasm.debug_abbrev_index = index;
                     try wasm.appendDummySegment();
                     break :blk index;
                 };
-            } else if (mem.eql(u8, debug_name, ".debug_str")) {
+            } else if (mem.eql(u8, section_name, ".debug_str")) {
                 return wasm.debug_str_index orelse blk: {
                     wasm.debug_str_index = index;
                     try wasm.appendDummySegment();
                     break :blk index;
                 };
             } else {
-                log.warn("found unknown debug section '{s}'", .{debug_name});
-                log.warn("  debug section will be skipped", .{});
-                return null;
+                log.warn("found unknown section '{s}'", .{section_name});
+                return error.UnexpectedValue;
             }
         },
+        else => unreachable,
     }
 }
 
@@ -3468,11 +3496,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
     try wasm.setupInitFunctions();
     try wasm.setupStart();
 
-    for (wasm.objects.items, 0..) |*object, object_index| {
-        try object.parseIntoAtoms(gpa, @as(u16, @intCast(object_index)), wasm);
-    }
-
-    wasm.markReferences();
+    try wasm.markReferences();
     try wasm.setupImports();
     try wasm.allocateAtoms();
     try wasm.setupMemory();
@@ -3558,7 +3582,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     try wasm.setupInitFunctions();
     try wasm.setupErrorsLen();
     try wasm.setupStart();
-    wasm.markReferences();
+    try wasm.markReferences();
     try wasm.setupImports();
     if (wasm.base.options.module) |mod| {
         var decl_it = wasm.decls.iterator();
@@ -3615,10 +3639,6 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
         }
     }
 
-    for (wasm.objects.items, 0..) |*object, object_index| {
-        try object.parseIntoAtoms(wasm.base.allocator, @as(u16, @intCast(object_index)), wasm);
-    }
-
     try wasm.allocateAtoms();
     try wasm.setupMemory();
     wasm.allocateVirtualAddresses();
@@ -3885,18 +3905,15 @@ fn writeToFile(
         var atom_index = wasm.atoms.get(code_index).?;
 
         // The code section must be sorted in line with the function order.
-        var sorted_atoms = try std.ArrayList(*Atom).initCapacity(wasm.base.allocator, wasm.functions.count());
+        var sorted_atoms = try std.ArrayList(*const Atom).initCapacity(wasm.base.allocator, wasm.functions.count());
         defer sorted_atoms.deinit();
 
         while (true) {
-            var atom = wasm.getAtomPtr(atom_index);
-            if (wasm.resolved_symbols.contains(atom.symbolLoc())) {
-                if (!is_obj) {
-                    atom.resolveRelocs(wasm);
-                }
-                sorted_atoms.appendAssumeCapacity(atom);
+            const atom = wasm.getAtomPtr(atom_index);
+            if (!is_obj) {
+                atom.resolveRelocs(wasm);
             }
-            // atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break;
+            sorted_atoms.appendAssumeCapacity(atom); // found more code atoms than functions
             atom_index = atom.prev orelse break;
         }
 
@@ -3908,7 +3925,7 @@ fn writeToFile(
             }
         }.sort;
 
-        mem.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn);
+        mem.sort(*const Atom, sorted_atoms.items, wasm, atom_sort_fn);
 
         for (sorted_atoms.items) |sorted_atom| {
             try leb.writeULEB128(binary_writer, sorted_atom.size);
@@ -5060,20 +5077,20 @@ pub fn storeDeclType(wasm: *Wasm, decl_index: InternPool.DeclIndex, func_type: s
 
 /// Verifies all resolved symbols and checks whether itself needs to be marked alive,
 /// as well as any of its references.
-fn markReferences(wasm: *Wasm) void {
+fn markReferences(wasm: *Wasm) !void {
     const tracy = trace(@src());
     defer tracy.end();
     for (wasm.resolved_symbols.keys()) |sym_loc| {
         const sym = sym_loc.getSymbol(wasm);
         if (sym.isExported(wasm.base.options.rdynamic) or sym.isNoStrip()) {
-            wasm.mark(sym_loc);
+            try wasm.mark(sym_loc);
         }
     }
 }
 
 /// Marks a symbol as 'alive' recursively so itself and any references it contains to
 /// other symbols will not be omit from the binary.
-fn mark(wasm: *Wasm, loc: SymbolLoc) void {
+fn mark(wasm: *Wasm, loc: SymbolLoc) !void {
     const symbol = loc.getSymbol(wasm);
     if (symbol.isAlive()) {
         // Symbol is already marked alive, including its references.
@@ -5082,13 +5099,20 @@ fn mark(wasm: *Wasm, loc: SymbolLoc) void {
         return;
     }
     symbol.mark();
+    if (symbol.isUndefined()) {
+        // undefined symbols do not have an associated `Atom` and therefore also
+        // do not contain relocations.
+        return;
+    }
 
-    if (wasm.symbol_atom.get(loc)) |atom_index| {
-        const atom = wasm.getAtom(atom_index);
-        const relocations: []const types.Relocation = atom.relocs.items;
-        for (relocations) |reloc| {
-            const target_loc: SymbolLoc = .{ .index = reloc.index, .file = loc.file };
-            wasm.mark(target_loc.finalLoc(wasm));
-        }
+    const file = loc.file orelse return; // Marking synthetic and Zig symbols is done seperately
+    const object = &wasm.objects.items[file];
+    const atom_index = try Object.parseSymbolIntoAtom(object, file, loc.index, wasm);
+
+    const atom = wasm.getAtom(atom_index);
+    const relocations: []const types.Relocation = atom.relocs.items;
+    for (relocations) |reloc| {
+        const target_loc: SymbolLoc = .{ .index = reloc.index, .file = file };
+        try wasm.mark(target_loc.finalLoc(wasm));
     }
 }