Commit f2dcfe0e40

Andrew Kelley <andrew@ziglang.org>
2024-10-31 02:33:06
link.File.Wasm: parse inputs in compilation pipeline
Primarily, this moves linker input parsing from flush() into the linker task queue, which is executed simultaneously with the frontend. I also made it avoid redundantly opening the same archive file N times for each object file inside. Furthermore, hard code fixed buffer stream rather than using a generic stream type. Finally, I fixed the error handling of the Wasm.Archive.parse function. Please pay attention to this pattern of returning a struct rather than accepting a mutable struct as an argument. This ensures function-level atomicity and makes resource management straightforward. Deletes the file and path fields from Archive and Object. Removed a well-meaning but ultimately misguided suggestion about how to think about ZigObject since thinking about it that way has led to problematic anti-DOD patterns.
1 parent f5ade5e
src/link/Wasm/Archive.zig
@@ -1,18 +1,17 @@
-file: fs.File,
-name: []const u8,
-
-header: ar_hdr = undefined,
+header: ar_hdr,
 
 /// A list of long file names, delimited by a LF character (0x0a).
 /// This is stored as a single slice of bytes, as the header-names
 /// point to the character index of a file name, rather than the index
 /// in the list.
-long_file_names: []const u8 = undefined,
+long_file_names: []const u8,
 
 /// Parsed table of contents.
 /// Each symbol name points to a list of all definition
 /// sites within the current static archive.
-toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .empty,
+toc: Toc,
+
+const Toc = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32));
 
 // Archive files start with the ARMAG identifying string.  Then follows a
 // `struct ar_hdr', and as many bytes of member file data as its `ar_size'
@@ -82,35 +81,39 @@ const ar_hdr = extern struct {
     }
 };
 
-pub fn deinit(archive: *Archive, allocator: Allocator) void {
-    archive.file.close();
-    for (archive.toc.keys()) |*key| {
-        allocator.free(key.*);
-    }
-    for (archive.toc.values()) |*value| {
-        value.deinit(allocator);
-    }
-    archive.toc.deinit(allocator);
-    allocator.free(archive.long_file_names);
+pub fn deinit(archive: *Archive, gpa: Allocator) void {
+    deinitToc(gpa, &archive.toc);
+    gpa.free(archive.long_file_names);
+    archive.* = undefined;
+}
+
+fn deinitToc(gpa: Allocator, toc: *Toc) void {
+    for (toc.keys()) |key| gpa.free(key);
+    for (toc.values()) |*value| value.deinit(gpa);
+    toc.deinit(gpa);
 }
 
-pub fn parse(archive: *Archive, allocator: Allocator) !void {
-    const reader = archive.file.reader();
+pub fn parse(gpa: Allocator, file_contents: []const u8) !Archive {
+    var fbs = std.io.fixedBufferStream(file_contents);
+    const reader = fbs.reader();
 
     const magic = try reader.readBytesNoEof(SARMAG);
-    if (!mem.eql(u8, &magic, ARMAG)) {
-        log.debug("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic });
-        return error.NotArchive;
-    }
+    if (!mem.eql(u8, &magic, ARMAG)) return error.BadArchiveMagic;
 
-    archive.header = try reader.readStruct(ar_hdr);
-    if (!mem.eql(u8, &archive.header.ar_fmag, ARFMAG)) {
-        log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, archive.header.ar_fmag });
-        return error.NotArchive;
-    }
+    const header = try reader.readStruct(ar_hdr);
+    if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) return error.BadHeaderDelimiter;
 
-    try archive.parseTableOfContents(allocator, reader);
-    try archive.parseNameTable(allocator, reader);
+    var toc = try parseTableOfContents(gpa, header, reader);
+    errdefer deinitToc(gpa, &toc);
+
+    const long_file_names = try parseNameTable(gpa, reader);
+    errdefer gpa.free(long_file_names);
+
+    return .{
+        .header = header,
+        .toc = toc,
+        .long_file_names = long_file_names,
+    };
 }
 
 fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 {
@@ -124,24 +127,27 @@ fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 {
     }
 }
 
-fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype) !void {
+fn parseTableOfContents(gpa: Allocator, header: ar_hdr, reader: anytype) !Toc {
     // size field can have extra spaces padded in front as well as the end,
     // so we trim those first before parsing the ASCII value.
-    const size_trimmed = mem.trim(u8, &archive.header.ar_size, " ");
+    const size_trimmed = mem.trim(u8, &header.ar_size, " ");
     const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10);
 
     const num_symbols = try reader.readInt(u32, .big);
-    const symbol_positions = try allocator.alloc(u32, num_symbols);
-    defer allocator.free(symbol_positions);
+    const symbol_positions = try gpa.alloc(u32, num_symbols);
+    defer gpa.free(symbol_positions);
     for (symbol_positions) |*index| {
         index.* = try reader.readInt(u32, .big);
     }
 
-    const sym_tab = try allocator.alloc(u8, sym_tab_size - 4 - (4 * num_symbols));
-    defer allocator.free(sym_tab);
+    const sym_tab = try gpa.alloc(u8, sym_tab_size - 4 - (4 * num_symbols));
+    defer gpa.free(sym_tab);
 
     reader.readNoEof(sym_tab) catch return error.IncompleteSymbolTable;
 
+    var toc: Toc = .empty;
+    errdefer deinitToc(gpa, &toc);
+
     var i: usize = 0;
     var pos: usize = 0;
     while (i < num_symbols) : (i += 1) {
@@ -149,19 +155,21 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype
         pos += string.len + 1;
         if (string.len == 0) continue;
 
-        const name = try allocator.dupe(u8, string);
-        errdefer allocator.free(name);
-        const gop = try archive.toc.getOrPut(allocator, name);
+        const name = try gpa.dupe(u8, string);
+        errdefer gpa.free(name);
+        const gop = try toc.getOrPut(gpa, name);
         if (gop.found_existing) {
-            allocator.free(name);
+            gpa.free(name);
         } else {
             gop.value_ptr.* = .{};
         }
-        try gop.value_ptr.append(allocator, symbol_positions[i]);
+        try gop.value_ptr.append(gpa, symbol_positions[i]);
     }
+
+    return toc;
 }
 
-fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !void {
+fn parseNameTable(gpa: Allocator, reader: anytype) ![]const u8 {
     const header: ar_hdr = try reader.readStruct(ar_hdr);
     if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {
         return error.InvalidHeaderDelimiter;
@@ -170,40 +178,25 @@ fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !voi
         return error.MissingTableName;
     }
     const table_size = try header.size();
-    const long_file_names = try allocator.alloc(u8, table_size);
-    errdefer allocator.free(long_file_names);
+    const long_file_names = try gpa.alloc(u8, table_size);
+    errdefer gpa.free(long_file_names);
     try reader.readNoEof(long_file_names);
-    archive.long_file_names = long_file_names;
+
+    return long_file_names;
 }
 
 /// From a given file offset, starts reading for a file header.
 /// When found, parses the object file into an `Object` and returns it.
-pub fn parseObject(archive: Archive, wasm_file: *const Wasm, file_offset: u32) !Object {
-    const gpa = wasm_file.base.comp.gpa;
-    try archive.file.seekTo(file_offset);
-    const reader = archive.file.reader();
-    const header = try reader.readStruct(ar_hdr);
-    const current_offset = try archive.file.getPos();
-    try archive.file.seekTo(0);
+pub fn parseObject(archive: Archive, wasm: *const Wasm, file_contents: []const u8, path: Path) !Object {
+    var fbs = std.io.fixedBufferStream(file_contents);
+    const header = try fbs.reader().readStruct(ar_hdr);
 
-    if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {
-        return error.InvalidHeaderDelimiter;
-    }
+    if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) return error.BadArchiveHeaderDelimiter;
 
     const object_name = try archive.parseName(header);
-    const name = name: {
-        var buffer: [std.fs.max_path_bytes]u8 = undefined;
-        const path = try std.posix.realpath(archive.name, &buffer);
-        break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name });
-    };
-    defer gpa.free(name);
-
-    const object_file = try std.fs.cwd().openFile(archive.name, .{});
-    errdefer object_file.close();
-
     const object_file_size = try header.size();
-    try object_file.seekTo(current_offset);
-    return Object.create(wasm_file, object_file, name, object_file_size);
+
+    return Object.create(wasm, file_contents[@sizeOf(ar_hdr)..][0..object_file_size], path, object_name);
 }
 
 const std = @import("std");
@@ -211,6 +204,7 @@ const assert = std.debug.assert;
 const fs = std.fs;
 const log = std.log.scoped(.archive);
 const mem = std.mem;
+const Path = std.Build.Cache.Path;
 
 const Allocator = mem.Allocator;
 const Object = @import("Object.zig");
src/link/Wasm/Object.zig
@@ -12,15 +12,19 @@ const std = @import("std");
 const Allocator = std.mem.Allocator;
 const leb = std.leb;
 const meta = std.meta;
+const Path = std.Build.Cache.Path;
 
 const log = std.log.scoped(.object);
 
 /// Wasm spec version used for this `Object`
 version: u32 = 0,
-/// The file descriptor that represents the wasm object file.
-file: ?std.fs.File = null,
-/// Name (read path) of the object file.
-path: []const u8,
+/// For error reporting purposes only.
+/// Name (read path) of the object or archive file.
+path: Path,
+/// For error reporting purposes only.
+/// If this represents an object in an archive, it's the basename of the
+/// object, and path refers to the archive.
+archive_member_name: ?[]const u8,
 /// Parsed type section
 func_types: []const std.wasm.Type = &.{},
 /// A list of all imports for this module
@@ -117,40 +121,28 @@ pub const RelocatableData = struct {
     }
 };
 
-pub const InitError = error{NotObjectFile} || ParseError || std.fs.File.ReadError;
-
 /// Initializes a new `Object` from a wasm object file.
 /// This also parses and verifies the object file.
 /// When a max size is given, will only parse up to the given size,
 /// else will read until the end of the file.
-pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe_max_size: ?usize) InitError!Object {
-    const gpa = wasm_file.base.comp.gpa;
+pub fn create(
+    wasm: *const Wasm,
+    file_contents: []const u8,
+    path: Path,
+    archive_member_name: ?[]const u8,
+) !Object {
+    const gpa = wasm.base.comp.gpa;
     var object: Object = .{
-        .file = file,
-        .path = try gpa.dupe(u8, name),
+        .path = path,
+        .archive_member_name = archive_member_name,
     };
 
-    var is_object_file: bool = false;
-    const size = maybe_max_size orelse size: {
-        errdefer gpa.free(object.path);
-        const stat = try file.stat();
-        break :size @as(usize, @intCast(stat.size));
+    var parser: Parser = .{
+        .object = &object,
+        .wasm = wasm,
+        .reader = std.io.fixedBufferStream(file_contents),
     };
-
-    const file_contents = try gpa.alloc(u8, size);
-    defer gpa.free(file_contents);
-    var file_reader = file.reader();
-    var read: usize = 0;
-    while (read < size) {
-        const n = try file_reader.read(file_contents[read..]);
-        std.debug.assert(n != 0);
-        read += n;
-    }
-    var fbs = std.io.fixedBufferStream(file_contents);
-
-    try object.parse(gpa, wasm_file, fbs.reader(), &is_object_file);
-    errdefer object.deinit(gpa);
-    if (!is_object_file) return error.NotObjectFile;
+    try parser.parseObject(gpa);
 
     return object;
 }
@@ -158,9 +150,6 @@ pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe
 /// Frees all memory of `Object` at once. The given `Allocator` must be
 /// the same allocator that was used when `init` was called.
 pub fn deinit(object: *Object, gpa: Allocator) void {
-    if (object.file) |file| {
-        file.close();
-    }
     for (object.func_types) |func_ty| {
         gpa.free(func_ty.params);
         gpa.free(func_ty.returns);
@@ -199,7 +188,6 @@ pub fn deinit(object: *Object, gpa: Allocator) void {
     }
     object.relocatable_data.deinit(gpa);
     object.string_table.deinit(gpa);
-    gpa.free(object.path);
     object.* = undefined;
 }
 
@@ -221,8 +209,8 @@ pub fn findImport(object: *const Object, sym: Symbol) Wasm.Import {
 /// we initialize a new table symbol that corresponds to that import and return that symbol.
 ///
 /// When the object file is *NOT* MVP, we return `null`.
-fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?Symbol {
-    const diags = &wasm_file.base.comp.link_diags;
+fn checkLegacyIndirectFunctionTable(object: *Object, wasm: *const Wasm) !?Symbol {
+    const diags = &wasm.base.comp.link_diags;
 
     var table_count: usize = 0;
     for (object.symtable) |sym| {
@@ -233,28 +221,19 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S
     if (object.imported_tables_count == table_count) return null;
 
     if (table_count != 0) {
-        var err = try diags.addErrorWithNotes(1);
-        try err.addMsg("Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
+        return diags.failParse(object.path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
             object.imported_tables_count,
             table_count,
         });
-        try err.addNote("defined in '{s}'", .{object.path});
-        return error.MissingTableSymbols;
     }
 
     // MVP object files cannot have any table definitions, only imports (for the indirect function table).
     if (object.tables.len > 0) {
-        var err = try diags.addErrorWithNotes(1);
-        try err.addMsg("Unexpected table definition without representing table symbols.", .{});
-        try err.addNote("defined in '{s}'", .{object.path});
-        return error.UnexpectedTable;
+        return diags.failParse(object.path, "unexpected table definition without representing table symbols.", .{});
     }
 
     if (object.imported_tables_count != 1) {
-        var err = try diags.addErrorWithNotes(1);
-        try err.addMsg("Found more than one table import, but no representing table symbols", .{});
-        try err.addNote("defined in '{s}'", .{object.path});
-        return error.MissingTableSymbols;
+        return diags.failParse(object.path, "found more than one table import, but no representing table symbols", .{});
     }
 
     const table_import: Wasm.Import = for (object.imports) |imp| {
@@ -264,10 +243,9 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S
     } else unreachable;
 
     if (!std.mem.eql(u8, object.string_table.get(table_import.name), "__indirect_function_table")) {
-        var err = try diags.addErrorWithNotes(1);
-        try err.addMsg("Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)});
-        try err.addNote("defined in '{s}'", .{object.path});
-        return error.MissingTableSymbols;
+        return diags.failParse(object.path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{
+            object.string_table.get(table_import.name),
+        });
     }
 
     var table_symbol: Symbol = .{
@@ -282,576 +260,518 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S
     return table_symbol;
 }
 
-/// Error set containing parsing errors.
-/// Merged with reader's errorset by `Parser`
-pub const ParseError = error{
-    /// The magic byte is either missing or does not contain \0Asm
-    InvalidMagicByte,
-    /// The wasm version is either missing or does not match the supported version.
-    InvalidWasmVersion,
-    /// Expected the functype byte while parsing the Type section but did not find it.
-    ExpectedFuncType,
-    /// Missing an 'end' opcode when defining a constant expression.
-    MissingEndForExpression,
-    /// Missing an 'end' opcode at the end of a body expression.
-    MissingEndForBody,
-    /// The size defined in the section code mismatches with the actual payload size.
-    MalformedSection,
-    /// Stream has reached the end. Unreachable for caller and must be handled internally
-    /// by the parser.
-    EndOfStream,
-    /// Ran out of memory when allocating.
-    OutOfMemory,
-    /// A non-zero flag was provided for comdat info
-    UnexpectedValue,
-    /// An import symbol contains an index to an import that does
-    /// not exist, or no imports were defined.
-    InvalidIndex,
-    /// The section "linking" contains a version that is not supported.
-    UnsupportedVersion,
-    /// When reading the data in leb128 compressed format, its value was overflown.
-    Overflow,
-    /// Found table definitions but no corresponding table symbols
-    MissingTableSymbols,
-    /// Did not expect a table definition, but did find one
-    UnexpectedTable,
-    /// Object file contains a feature that is unknown to the linker
-    UnknownFeature,
-};
+const Parser = struct {
+    reader: std.io.FixedBufferStream([]const u8),
+    /// Object file we're building
+    object: *Object,
+    /// Read-only reference to the WebAssembly linker
+    wasm: *const Wasm,
 
-fn parse(object: *Object, gpa: Allocator, wasm_file: *const Wasm, reader: anytype, is_object_file: *bool) Parser(@TypeOf(reader)).Error!void {
-    var parser = Parser(@TypeOf(reader)).init(object, wasm_file, reader);
-    return parser.parseObject(gpa, is_object_file);
-}
+    fn parseObject(parser: *Parser, gpa: Allocator) anyerror!void {
+        {
+            var magic_bytes: [4]u8 = undefined;
+            try parser.reader.reader().readNoEof(&magic_bytes);
+            if (!std.mem.eql(u8, &magic_bytes, &std.wasm.magic)) return error.BadObjectMagic;
+        }
 
-fn Parser(comptime ReaderType: type) type {
-    return struct {
-        const ObjectParser = @This();
-        const Error = ReaderType.Error || ParseError;
+        const version = try parser.reader.reader().readInt(u32, .little);
+        parser.object.version = version;
 
-        reader: std.io.CountingReader(ReaderType),
-        /// Object file we're building
-        object: *Object,
-        /// Read-only reference to the WebAssembly linker
-        wasm_file: *const Wasm,
+        var saw_linking_section = false;
 
-        fn init(object: *Object, wasm_file: *const Wasm, reader: ReaderType) ObjectParser {
-            return .{ .object = object, .wasm_file = wasm_file, .reader = std.io.countingReader(reader) };
-        }
+        var section_index: u32 = 0;
+        while (parser.reader.reader().readByte()) |byte| : (section_index += 1) {
+            const len = try readLeb(u32, parser.reader.reader());
+            var limited_reader = std.io.limitedReader(parser.reader.reader(), len);
+            const reader = limited_reader.reader();
+            switch (@as(std.wasm.Section, @enumFromInt(byte))) {
+                .custom => {
+                    const name_len = try readLeb(u32, reader);
+                    const name = try gpa.alloc(u8, name_len);
+                    defer gpa.free(name);
+                    try reader.readNoEof(name);
 
-        /// Verifies that the first 4 bytes contains \0Asm
-        fn verifyMagicBytes(parser: *ObjectParser) Error!void {
-            var magic_bytes: [4]u8 = undefined;
+                    if (std.mem.eql(u8, name, "linking")) {
+                        saw_linking_section = true;
+                        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) = .empty;
+                        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(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, .{});
+                    }
+                },
+                .type => {
+                    for (try readVec(&parser.object.func_types, reader, gpa)) |*type_val| {
+                        if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType;
 
-            try parser.reader.reader().readNoEof(&magic_bytes);
-            if (!std.mem.eql(u8, &magic_bytes, &std.wasm.magic)) {
-                log.debug("Invalid magic bytes '{s}'", .{&magic_bytes});
-                return error.InvalidMagicByte;
-            }
-        }
+                        for (try readVec(&type_val.params, reader, gpa)) |*param| {
+                            param.* = try readEnum(std.wasm.Valtype, reader);
+                        }
+
+                        for (try readVec(&type_val.returns, reader, gpa)) |*result| {
+                            result.* = try readEnum(std.wasm.Valtype, reader);
+                        }
+                    }
+                    try assertEnd(reader);
+                },
+                .import => {
+                    for (try readVec(&parser.object.imports, reader, gpa)) |*import| {
+                        const module_len = try readLeb(u32, reader);
+                        const module_name = try gpa.alloc(u8, module_len);
+                        defer gpa.free(module_name);
+                        try reader.readNoEof(module_name);
 
-        fn parseObject(parser: *ObjectParser, gpa: Allocator, is_object_file: *bool) Error!void {
-            errdefer parser.object.deinit(gpa);
-            try parser.verifyMagicBytes();
-            const version = try parser.reader.reader().readInt(u32, .little);
-            parser.object.version = version;
-
-            var section_index: u32 = 0;
-            while (parser.reader.reader().readByte()) |byte| : (section_index += 1) {
-                const len = try readLeb(u32, parser.reader.reader());
-                var limited_reader = std.io.limitedReader(parser.reader.reader(), len);
-                const reader = limited_reader.reader();
-                switch (@as(std.wasm.Section, @enumFromInt(byte))) {
-                    .custom => {
                         const name_len = try readLeb(u32, reader);
                         const name = try gpa.alloc(u8, name_len);
                         defer gpa.free(name);
                         try reader.readNoEof(name);
 
-                        if (std.mem.eql(u8, name, "linking")) {
-                            is_object_file.* = true;
-                            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) = .empty;
-                            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(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, .{});
-                        }
-                    },
-                    .type => {
-                        for (try readVec(&parser.object.func_types, reader, gpa)) |*type_val| {
-                            if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType;
-
-                            for (try readVec(&type_val.params, reader, gpa)) |*param| {
-                                param.* = try readEnum(std.wasm.Valtype, reader);
-                            }
-
-                            for (try readVec(&type_val.returns, reader, gpa)) |*result| {
-                                result.* = try readEnum(std.wasm.Valtype, reader);
-                            }
-                        }
-                        try assertEnd(reader);
-                    },
-                    .import => {
-                        for (try readVec(&parser.object.imports, reader, gpa)) |*import| {
-                            const module_len = try readLeb(u32, reader);
-                            const module_name = try gpa.alloc(u8, module_len);
-                            defer gpa.free(module_name);
-                            try reader.readNoEof(module_name);
-
-                            const name_len = try readLeb(u32, reader);
-                            const name = try gpa.alloc(u8, name_len);
-                            defer gpa.free(name);
-                            try reader.readNoEof(name);
-
-                            const kind = try readEnum(std.wasm.ExternalKind, reader);
-                            const kind_value: std.wasm.Import.Kind = switch (kind) {
-                                .function => val: {
-                                    parser.object.imported_functions_count += 1;
-                                    break :val .{ .function = try readLeb(u32, reader) };
-                                },
-                                .memory => .{ .memory = try readLimits(reader) },
-                                .global => val: {
-                                    parser.object.imported_globals_count += 1;
-                                    break :val .{ .global = .{
-                                        .valtype = try readEnum(std.wasm.Valtype, reader),
-                                        .mutable = (try reader.readByte()) == 0x01,
-                                    } };
-                                },
-                                .table => val: {
-                                    parser.object.imported_tables_count += 1;
-                                    break :val .{ .table = .{
-                                        .reftype = try readEnum(std.wasm.RefType, reader),
-                                        .limits = try readLimits(reader),
-                                    } };
-                                },
-                            };
-
-                            import.* = .{
-                                .module_name = try parser.object.string_table.put(gpa, module_name),
-                                .name = try parser.object.string_table.put(gpa, name),
-                                .kind = kind_value,
-                            };
-                        }
-                        try assertEnd(reader);
-                    },
-                    .function => {
-                        for (try readVec(&parser.object.functions, reader, gpa)) |*func| {
-                            func.* = .{ .type_index = try readLeb(u32, reader) };
-                        }
-                        try assertEnd(reader);
-                    },
-                    .table => {
-                        for (try readVec(&parser.object.tables, reader, gpa)) |*table| {
-                            table.* = .{
-                                .reftype = try readEnum(std.wasm.RefType, reader),
-                                .limits = try readLimits(reader),
-                            };
-                        }
-                        try assertEnd(reader);
-                    },
-                    .memory => {
-                        for (try readVec(&parser.object.memories, reader, gpa)) |*memory| {
-                            memory.* = .{ .limits = try readLimits(reader) };
-                        }
-                        try assertEnd(reader);
-                    },
-                    .global => {
-                        for (try readVec(&parser.object.globals, reader, gpa)) |*global| {
-                            global.* = .{
-                                .global_type = .{
+                        const kind = try readEnum(std.wasm.ExternalKind, reader);
+                        const kind_value: std.wasm.Import.Kind = switch (kind) {
+                            .function => val: {
+                                parser.object.imported_functions_count += 1;
+                                break :val .{ .function = try readLeb(u32, reader) };
+                            },
+                            .memory => .{ .memory = try readLimits(reader) },
+                            .global => val: {
+                                parser.object.imported_globals_count += 1;
+                                break :val .{ .global = .{
                                     .valtype = try readEnum(std.wasm.Valtype, reader),
                                     .mutable = (try reader.readByte()) == 0x01,
-                                },
-                                .init = try readInit(reader),
-                            };
-                        }
-                        try assertEnd(reader);
-                    },
-                    .@"export" => {
-                        for (try readVec(&parser.object.exports, reader, gpa)) |*exp| {
-                            const name_len = try readLeb(u32, reader);
-                            const name = try gpa.alloc(u8, name_len);
-                            defer gpa.free(name);
-                            try reader.readNoEof(name);
-                            exp.* = .{
-                                .name = try parser.object.string_table.put(gpa, name),
-                                .kind = try readEnum(std.wasm.ExternalKind, reader),
-                                .index = try readLeb(u32, reader),
-                            };
-                        }
-                        try assertEnd(reader);
-                    },
-                    .start => {
-                        parser.object.start = try readLeb(u32, reader);
-                        try assertEnd(reader);
-                    },
-                    .element => {
-                        for (try readVec(&parser.object.elements, reader, gpa)) |*elem| {
-                            elem.table_index = try readLeb(u32, reader);
-                            elem.offset = try readInit(reader);
-
-                            for (try readVec(&elem.func_indexes, reader, gpa)) |*idx| {
-                                idx.* = try readLeb(u32, reader);
-                            }
-                        }
-                        try assertEnd(reader);
-                    },
-                    .code => {
-                        const start = reader.context.bytes_left;
-                        var index: u32 = 0;
-                        const count = try readLeb(u32, reader);
-                        const imported_function_count = parser.object.imported_functions_count;
-                        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);
-                            relocatable_data.appendAssumeCapacity(.{
-                                .type = .code,
-                                .data = data.ptr,
-                                .size = code_len,
-                                .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);
-                            _ = flags; // TODO: Do we need to check flags to detect passive/active memory?
-                            _ = data_offset;
-                            const data_len = try readLeb(u32, reader);
-                            const offset = @as(u32, @intCast(start - reader.context.bytes_left));
-                            const data = try gpa.alloc(u8, data_len);
-                            errdefer gpa.free(data);
-                            try reader.readNoEof(data);
-                            relocatable_data.appendAssumeCapacity(.{
-                                .type = .data,
-                                .data = data.ptr,
-                                .size = data_len,
-                                .index = index,
-                                .offset = offset,
-                                .section_index = section_index,
-                            });
-                        }
-                        try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice());
-                    },
-                    else => try parser.reader.reader().skipBytes(len, .{}),
-                }
-            } else |err| switch (err) {
-                error.EndOfStream => {}, // finished parsing the file
-                else => |e| return e,
-            }
-        }
-
-        /// Based on the "features" custom section, parses it into a list of
-        /// features that tell the linker what features were enabled and may be mandatory
-        /// to be able to link.
-        /// Logs an info message when an undefined feature is detected.
-        fn parseFeatures(parser: *ObjectParser, gpa: Allocator) !void {
-            const diags = &parser.wasm_file.base.comp.link_diags;
-            const reader = parser.reader.reader();
-            for (try readVec(&parser.object.features, reader, gpa)) |*feature| {
-                const prefix = try readEnum(Wasm.Feature.Prefix, reader);
-                const name_len = try leb.readUleb128(u32, reader);
-                const name = try gpa.alloc(u8, name_len);
-                defer gpa.free(name);
-                try reader.readNoEof(name);
-
-                const tag = Wasm.known_features.get(name) orelse {
-                    var err = try diags.addErrorWithNotes(1);
-                    try err.addMsg("Object file contains unknown feature: {s}", .{name});
-                    try err.addNote("defined in '{s}'", .{parser.object.path});
-                    return error.UnknownFeature;
-                };
-                feature.* = .{
-                    .prefix = prefix,
-                    .tag = tag,
-                };
-            }
-        }
-
-        /// Parses a "reloc" custom section into a list of relocations.
-        /// The relocations are mapped into `Object` where the key is the section
-        /// they apply to.
-        fn parseRelocations(parser: *ObjectParser, gpa: Allocator) !void {
-            const reader = parser.reader.reader();
-            const section = try leb.readUleb128(u32, reader);
-            const count = try leb.readUleb128(u32, reader);
-            const relocations = try gpa.alloc(Wasm.Relocation, count);
-            errdefer gpa.free(relocations);
-
-            log.debug("Found {d} relocations for section ({d})", .{
-                count,
-                section,
-            });
-
-            for (relocations) |*relocation| {
-                const rel_type = try reader.readByte();
-                const rel_type_enum = std.meta.intToEnum(Wasm.Relocation.RelocationType, rel_type) catch return error.MalformedSection;
-                relocation.* = .{
-                    .relocation_type = rel_type_enum,
-                    .offset = try leb.readUleb128(u32, reader),
-                    .index = try leb.readUleb128(u32, reader),
-                    .addend = if (rel_type_enum.addendIsPresent()) try leb.readIleb128(i32, reader) else 0,
-                };
-                log.debug("Found relocation: type({s}) offset({d}) index({d}) addend({?d})", .{
-                    @tagName(relocation.relocation_type),
-                    relocation.offset,
-                    relocation.index,
-                    relocation.addend,
-                });
-            }
-
-            try parser.object.relocations.putNoClobber(gpa, section, relocations);
-        }
-
-        /// Parses the "linking" custom section. Versions that are not
-        /// supported will be an error. `payload_size` is required to be able
-        /// to calculate the subsections we need to parse, as that data is not
-        /// available within the section itparser.
-        fn parseMetadata(parser: *ObjectParser, gpa: Allocator, payload_size: usize) !void {
-            var limited = std.io.limitedReader(parser.reader.reader(), payload_size);
-            const limited_reader = limited.reader();
-
-            const version = try leb.readUleb128(u32, limited_reader);
-            log.debug("Link meta data version: {d}", .{version});
-            if (version != 2) return error.UnsupportedVersion;
-
-            while (limited.bytes_left > 0) {
-                try parser.parseSubsection(gpa, limited_reader);
-            }
-        }
-
-        /// Parses a `spec.Subsection`.
-        /// The `reader` param for this is to provide a `LimitedReader`, which allows
-        /// us to only read until a max length.
-        ///
-        /// `parser` is used to provide access to other sections that may be needed,
-        /// such as access to the `import` section to find the name of a symbol.
-        fn parseSubsection(parser: *ObjectParser, gpa: Allocator, reader: anytype) !void {
-            const sub_type = try leb.readUleb128(u8, reader);
-            log.debug("Found subsection: {s}", .{@tagName(@as(Wasm.SubsectionType, @enumFromInt(sub_type)))});
-            const payload_len = try leb.readUleb128(u32, reader);
-            if (payload_len == 0) return;
-
-            var limited = std.io.limitedReader(reader, payload_len);
-            const limited_reader = limited.reader();
-
-            // every subsection contains a 'count' field
-            const count = try leb.readUleb128(u32, limited_reader);
-
-            switch (@as(Wasm.SubsectionType, @enumFromInt(sub_type))) {
-                .WASM_SEGMENT_INFO => {
-                    const segments = try gpa.alloc(Wasm.NamedSegment, count);
-                    errdefer gpa.free(segments);
-                    for (segments) |*segment| {
-                        const name_len = try leb.readUleb128(u32, reader);
-                        const name = try gpa.alloc(u8, name_len);
-                        errdefer gpa.free(name);
-                        try reader.readNoEof(name);
-                        segment.* = .{
-                            .name = name,
-                            .alignment = @enumFromInt(try leb.readUleb128(u32, reader)),
-                            .flags = try leb.readUleb128(u32, reader),
+                                } };
+                            },
+                            .table => val: {
+                                parser.object.imported_tables_count += 1;
+                                break :val .{ .table = .{
+                                    .reftype = try readEnum(std.wasm.RefType, reader),
+                                    .limits = try readLimits(reader),
+                                } };
+                            },
                         };
-                        log.debug("Found segment: {s} align({d}) flags({b})", .{
-                            segment.name,
-                            segment.alignment,
-                            segment.flags,
-                        });
 
-                        // support legacy object files that specified being TLS by the name instead of the TLS flag.
-                        if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) {
-                            // set the flag so we can simply check for the flag in the rest of the linker.
-                            segment.flags |= @intFromEnum(Wasm.NamedSegment.Flags.WASM_SEG_FLAG_TLS);
-                        }
+                        import.* = .{
+                            .module_name = try parser.object.string_table.put(gpa, module_name),
+                            .name = try parser.object.string_table.put(gpa, name),
+                            .kind = kind_value,
+                        };
+                    }
+                    try assertEnd(reader);
+                },
+                .function => {
+                    for (try readVec(&parser.object.functions, reader, gpa)) |*func| {
+                        func.* = .{ .type_index = try readLeb(u32, reader) };
+                    }
+                    try assertEnd(reader);
+                },
+                .table => {
+                    for (try readVec(&parser.object.tables, reader, gpa)) |*table| {
+                        table.* = .{
+                            .reftype = try readEnum(std.wasm.RefType, reader),
+                            .limits = try readLimits(reader),
+                        };
+                    }
+                    try assertEnd(reader);
+                },
+                .memory => {
+                    for (try readVec(&parser.object.memories, reader, gpa)) |*memory| {
+                        memory.* = .{ .limits = try readLimits(reader) };
                     }
-                    parser.object.segment_info = segments;
+                    try assertEnd(reader);
                 },
-                .WASM_INIT_FUNCS => {
-                    const funcs = try gpa.alloc(Wasm.InitFunc, count);
-                    errdefer gpa.free(funcs);
-                    for (funcs) |*func| {
-                        func.* = .{
-                            .priority = try leb.readUleb128(u32, reader),
-                            .symbol_index = try leb.readUleb128(u32, reader),
+                .global => {
+                    for (try readVec(&parser.object.globals, reader, gpa)) |*global| {
+                        global.* = .{
+                            .global_type = .{
+                                .valtype = try readEnum(std.wasm.Valtype, reader),
+                                .mutable = (try reader.readByte()) == 0x01,
+                            },
+                            .init = try readInit(reader),
                         };
-                        log.debug("Found function - prio: {d}, index: {d}", .{ func.priority, func.symbol_index });
                     }
-                    parser.object.init_funcs = funcs;
+                    try assertEnd(reader);
                 },
-                .WASM_COMDAT_INFO => {
-                    const comdats = try gpa.alloc(Wasm.Comdat, count);
-                    errdefer gpa.free(comdats);
-                    for (comdats) |*comdat| {
-                        const name_len = try leb.readUleb128(u32, reader);
+                .@"export" => {
+                    for (try readVec(&parser.object.exports, reader, gpa)) |*exp| {
+                        const name_len = try readLeb(u32, reader);
                         const name = try gpa.alloc(u8, name_len);
-                        errdefer gpa.free(name);
+                        defer gpa.free(name);
                         try reader.readNoEof(name);
-
-                        const flags = try leb.readUleb128(u32, reader);
-                        if (flags != 0) {
-                            return error.UnexpectedValue;
-                        }
-
-                        const symbol_count = try leb.readUleb128(u32, reader);
-                        const symbols = try gpa.alloc(Wasm.ComdatSym, symbol_count);
-                        errdefer gpa.free(symbols);
-                        for (symbols) |*symbol| {
-                            symbol.* = .{
-                                .kind = @as(Wasm.ComdatSym.Type, @enumFromInt(try leb.readUleb128(u8, reader))),
-                                .index = try leb.readUleb128(u32, reader),
-                            };
-                        }
-
-                        comdat.* = .{
-                            .name = name,
-                            .flags = flags,
-                            .symbols = symbols,
+                        exp.* = .{
+                            .name = try parser.object.string_table.put(gpa, name),
+                            .kind = try readEnum(std.wasm.ExternalKind, reader),
+                            .index = try readLeb(u32, reader),
                         };
                     }
+                    try assertEnd(reader);
+                },
+                .start => {
+                    parser.object.start = try readLeb(u32, reader);
+                    try assertEnd(reader);
+                },
+                .element => {
+                    for (try readVec(&parser.object.elements, reader, gpa)) |*elem| {
+                        elem.table_index = try readLeb(u32, reader);
+                        elem.offset = try readInit(reader);
 
-                    parser.object.comdat_info = comdats;
+                        for (try readVec(&elem.func_indexes, reader, gpa)) |*idx| {
+                            idx.* = try readLeb(u32, reader);
+                        }
+                    }
+                    try assertEnd(reader);
                 },
-                .WASM_SYMBOL_TABLE => {
-                    var symbols = try std.ArrayList(Symbol).initCapacity(gpa, count);
-
-                    var i: usize = 0;
-                    while (i < count) : (i += 1) {
-                        const symbol = symbols.addOneAssumeCapacity();
-                        symbol.* = try parser.parseSymbol(gpa, reader);
-                        log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{
-                            @tagName(symbol.tag),
-                            parser.object.string_table.get(symbol.name),
-                            symbol.flags,
+                .code => {
+                    const start = reader.context.bytes_left;
+                    var index: u32 = 0;
+                    const count = try readLeb(u32, reader);
+                    const imported_function_count = parser.object.imported_functions_count;
+                    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);
+                        relocatable_data.appendAssumeCapacity(.{
+                            .type = .code,
+                            .data = data.ptr,
+                            .size = code_len,
+                            .index = imported_function_count + index,
+                            .offset = offset,
+                            .section_index = section_index,
                         });
                     }
-
-                    // we found all symbols, check for indirect function table
-                    // in case of an MVP object file
-                    if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm_file)) |symbol| {
-                        try symbols.append(symbol);
-                        log.debug("Found legacy indirect function table. Created symbol", .{});
-                    }
-
-                    // Not all debug sections may be represented by a symbol, for those sections
-                    // we manually create a symbol.
-                    if (parser.object.relocatable_data.get(.custom)) |custom_sections| {
-                        for (custom_sections) |*data| {
-                            if (!data.represented) {
-                                try symbols.append(.{
-                                    .name = data.index,
-                                    .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
-                                    .tag = .section,
-                                    .virtual_address = 0,
-                                    .index = data.section_index,
-                                });
-                                data.represented = true;
-                                log.debug("Created synthetic custom section symbol for '{s}'", .{parser.object.string_table.get(data.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);
+                        _ = flags; // TODO: Do we need to check flags to detect passive/active memory?
+                        _ = data_offset;
+                        const data_len = try readLeb(u32, reader);
+                        const offset = @as(u32, @intCast(start - reader.context.bytes_left));
+                        const data = try gpa.alloc(u8, data_len);
+                        errdefer gpa.free(data);
+                        try reader.readNoEof(data);
+                        relocatable_data.appendAssumeCapacity(.{
+                            .type = .data,
+                            .data = data.ptr,
+                            .size = data_len,
+                            .index = index,
+                            .offset = offset,
+                            .section_index = section_index,
+                        });
                     }
-
-                    parser.object.symtable = try symbols.toOwnedSlice();
+                    try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice());
                 },
+                else => try parser.reader.reader().skipBytes(len, .{}),
             }
+        } else |err| switch (err) {
+            error.EndOfStream => {}, // finished parsing the file
+            else => |e| return e,
         }
+        if (!saw_linking_section) return error.MissingLinkingSection;
+    }
 
-        /// Parses the symbol information based on its kind,
-        /// requires access to `Object` to find the name of a symbol when it's
-        /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set.
-        fn parseSymbol(parser: *ObjectParser, gpa: Allocator, reader: anytype) !Symbol {
-            const tag = @as(Symbol.Tag, @enumFromInt(try leb.readUleb128(u8, reader)));
-            const flags = try leb.readUleb128(u32, reader);
-            var symbol: Symbol = .{
-                .flags = flags,
+    /// Based on the "features" custom section, parses it into a list of
+    /// features that tell the linker what features were enabled and may be mandatory
+    /// to be able to link.
+    /// Logs an info message when an undefined feature is detected.
+    fn parseFeatures(parser: *Parser, gpa: Allocator) !void {
+        const diags = &parser.wasm.base.comp.link_diags;
+        const reader = parser.reader.reader();
+        for (try readVec(&parser.object.features, reader, gpa)) |*feature| {
+            const prefix = try readEnum(Wasm.Feature.Prefix, reader);
+            const name_len = try leb.readUleb128(u32, reader);
+            const name = try gpa.alloc(u8, name_len);
+            defer gpa.free(name);
+            try reader.readNoEof(name);
+
+            const tag = Wasm.known_features.get(name) orelse {
+                return diags.failParse(parser.object.path, "object file contains unknown feature: {s}", .{name});
+            };
+            feature.* = .{
+                .prefix = prefix,
                 .tag = tag,
-                .name = undefined,
-                .index = undefined,
-                .virtual_address = undefined,
             };
+        }
+    }
 
-            switch (tag) {
-                .data => {
+    /// Parses a "reloc" custom section into a list of relocations.
+    /// The relocations are mapped into `Object` where the key is the section
+    /// they apply to.
+    fn parseRelocations(parser: *Parser, gpa: Allocator) !void {
+        const reader = parser.reader.reader();
+        const section = try leb.readUleb128(u32, reader);
+        const count = try leb.readUleb128(u32, reader);
+        const relocations = try gpa.alloc(Wasm.Relocation, count);
+        errdefer gpa.free(relocations);
+
+        log.debug("Found {d} relocations for section ({d})", .{
+            count,
+            section,
+        });
+
+        for (relocations) |*relocation| {
+            const rel_type = try reader.readByte();
+            const rel_type_enum = std.meta.intToEnum(Wasm.Relocation.RelocationType, rel_type) catch return error.MalformedSection;
+            relocation.* = .{
+                .relocation_type = rel_type_enum,
+                .offset = try leb.readUleb128(u32, reader),
+                .index = try leb.readUleb128(u32, reader),
+                .addend = if (rel_type_enum.addendIsPresent()) try leb.readIleb128(i32, reader) else 0,
+            };
+            log.debug("Found relocation: type({s}) offset({d}) index({d}) addend({?d})", .{
+                @tagName(relocation.relocation_type),
+                relocation.offset,
+                relocation.index,
+                relocation.addend,
+            });
+        }
+
+        try parser.object.relocations.putNoClobber(gpa, section, relocations);
+    }
+
+    /// Parses the "linking" custom section. Versions that are not
+    /// supported will be an error. `payload_size` is required to be able
+    /// to calculate the subsections we need to parse, as that data is not
+    /// available within the section itparser.
+    fn parseMetadata(parser: *Parser, gpa: Allocator, payload_size: usize) !void {
+        var limited = std.io.limitedReader(parser.reader.reader(), payload_size);
+        const limited_reader = limited.reader();
+
+        const version = try leb.readUleb128(u32, limited_reader);
+        log.debug("Link meta data version: {d}", .{version});
+        if (version != 2) return error.UnsupportedVersion;
+
+        while (limited.bytes_left > 0) {
+            try parser.parseSubsection(gpa, limited_reader);
+        }
+    }
+
+    /// Parses a `spec.Subsection`.
+    /// The `reader` param for this is to provide a `LimitedReader`, which allows
+    /// us to only read until a max length.
+    ///
+    /// `parser` is used to provide access to other sections that may be needed,
+    /// such as access to the `import` section to find the name of a symbol.
+    fn parseSubsection(parser: *Parser, gpa: Allocator, reader: anytype) !void {
+        const sub_type = try leb.readUleb128(u8, reader);
+        log.debug("Found subsection: {s}", .{@tagName(@as(Wasm.SubsectionType, @enumFromInt(sub_type)))});
+        const payload_len = try leb.readUleb128(u32, reader);
+        if (payload_len == 0) return;
+
+        var limited = std.io.limitedReader(reader, payload_len);
+        const limited_reader = limited.reader();
+
+        // every subsection contains a 'count' field
+        const count = try leb.readUleb128(u32, limited_reader);
+
+        switch (@as(Wasm.SubsectionType, @enumFromInt(sub_type))) {
+            .WASM_SEGMENT_INFO => {
+                const segments = try gpa.alloc(Wasm.NamedSegment, count);
+                errdefer gpa.free(segments);
+                for (segments) |*segment| {
                     const name_len = try leb.readUleb128(u32, reader);
                     const name = try gpa.alloc(u8, name_len);
-                    defer gpa.free(name);
+                    errdefer gpa.free(name);
                     try reader.readNoEof(name);
-                    symbol.name = try parser.object.string_table.put(gpa, name);
-
-                    // Data symbols only have the following fields if the symbol is defined
-                    if (symbol.isDefined()) {
-                        symbol.index = try leb.readUleb128(u32, reader);
-                        // @TODO: We should verify those values
-                        _ = try leb.readUleb128(u32, reader);
-                        _ = try leb.readUleb128(u32, reader);
+                    segment.* = .{
+                        .name = name,
+                        .alignment = @enumFromInt(try leb.readUleb128(u32, reader)),
+                        .flags = try leb.readUleb128(u32, reader),
+                    };
+                    log.debug("Found segment: {s} align({d}) flags({b})", .{
+                        segment.name,
+                        segment.alignment,
+                        segment.flags,
+                    });
+
+                    // support legacy object files that specified being TLS by the name instead of the TLS flag.
+                    if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) {
+                        // set the flag so we can simply check for the flag in the rest of the linker.
+                        segment.flags |= @intFromEnum(Wasm.NamedSegment.Flags.WASM_SEG_FLAG_TLS);
                     }
-                },
-                .section => {
-                    symbol.index = try leb.readUleb128(u32, reader);
-                    const section_data = parser.object.relocatable_data.get(.custom).?;
-                    for (section_data) |*data| {
-                        if (data.section_index == symbol.index) {
-                            symbol.name = data.index;
+                }
+                parser.object.segment_info = segments;
+            },
+            .WASM_INIT_FUNCS => {
+                const funcs = try gpa.alloc(Wasm.InitFunc, count);
+                errdefer gpa.free(funcs);
+                for (funcs) |*func| {
+                    func.* = .{
+                        .priority = try leb.readUleb128(u32, reader),
+                        .symbol_index = try leb.readUleb128(u32, reader),
+                    };
+                    log.debug("Found function - prio: {d}, index: {d}", .{ func.priority, func.symbol_index });
+                }
+                parser.object.init_funcs = funcs;
+            },
+            .WASM_COMDAT_INFO => {
+                const comdats = try gpa.alloc(Wasm.Comdat, count);
+                errdefer gpa.free(comdats);
+                for (comdats) |*comdat| {
+                    const name_len = try leb.readUleb128(u32, reader);
+                    const name = try gpa.alloc(u8, name_len);
+                    errdefer gpa.free(name);
+                    try reader.readNoEof(name);
+
+                    const flags = try leb.readUleb128(u32, reader);
+                    if (flags != 0) {
+                        return error.UnexpectedValue;
+                    }
+
+                    const symbol_count = try leb.readUleb128(u32, reader);
+                    const symbols = try gpa.alloc(Wasm.ComdatSym, symbol_count);
+                    errdefer gpa.free(symbols);
+                    for (symbols) |*symbol| {
+                        symbol.* = .{
+                            .kind = @as(Wasm.ComdatSym.Type, @enumFromInt(try leb.readUleb128(u8, reader))),
+                            .index = try leb.readUleb128(u32, reader),
+                        };
+                    }
+
+                    comdat.* = .{
+                        .name = name,
+                        .flags = flags,
+                        .symbols = symbols,
+                    };
+                }
+
+                parser.object.comdat_info = comdats;
+            },
+            .WASM_SYMBOL_TABLE => {
+                var symbols = try std.ArrayList(Symbol).initCapacity(gpa, count);
+
+                var i: usize = 0;
+                while (i < count) : (i += 1) {
+                    const symbol = symbols.addOneAssumeCapacity();
+                    symbol.* = try parser.parseSymbol(gpa, reader);
+                    log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{
+                        @tagName(symbol.tag),
+                        parser.object.string_table.get(symbol.name),
+                        symbol.flags,
+                    });
+                }
+
+                // we found all symbols, check for indirect function table
+                // in case of an MVP object file
+                if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm)) |symbol| {
+                    try symbols.append(symbol);
+                    log.debug("Found legacy indirect function table. Created symbol", .{});
+                }
+
+                // Not all debug sections may be represented by a symbol, for those sections
+                // we manually create a symbol.
+                if (parser.object.relocatable_data.get(.custom)) |custom_sections| {
+                    for (custom_sections) |*data| {
+                        if (!data.represented) {
+                            try symbols.append(.{
+                                .name = data.index,
+                                .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
+                                .tag = .section,
+                                .virtual_address = 0,
+                                .index = data.section_index,
+                            });
                             data.represented = true;
-                            break;
+                            log.debug("Created synthetic custom section symbol for '{s}'", .{parser.object.string_table.get(data.index)});
                         }
                     }
-                },
-                else => {
+                }
+
+                parser.object.symtable = try symbols.toOwnedSlice();
+            },
+        }
+    }
+
+    /// Parses the symbol information based on its kind,
+    /// requires access to `Object` to find the name of a symbol when it's
+    /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set.
+    fn parseSymbol(parser: *Parser, gpa: Allocator, reader: anytype) !Symbol {
+        const tag = @as(Symbol.Tag, @enumFromInt(try leb.readUleb128(u8, reader)));
+        const flags = try leb.readUleb128(u32, reader);
+        var symbol: Symbol = .{
+            .flags = flags,
+            .tag = tag,
+            .name = undefined,
+            .index = undefined,
+            .virtual_address = undefined,
+        };
+
+        switch (tag) {
+            .data => {
+                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);
+
+                // Data symbols only have the following fields if the symbol is defined
+                if (symbol.isDefined()) {
                     symbol.index = try leb.readUleb128(u32, reader);
-                    const is_undefined = symbol.isUndefined();
-                    const explicit_name = symbol.hasFlag(.WASM_SYM_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);
-                        break :name try parser.object.string_table.put(gpa, name);
-                    } else parser.object.findImport(symbol).name;
-                },
-            }
-            return symbol;
+                    // @TODO: We should verify those values
+                    _ = try leb.readUleb128(u32, reader);
+                    _ = try leb.readUleb128(u32, reader);
+                }
+            },
+            .section => {
+                symbol.index = try leb.readUleb128(u32, reader);
+                const section_data = parser.object.relocatable_data.get(.custom).?;
+                for (section_data) |*data| {
+                    if (data.section_index == symbol.index) {
+                        symbol.name = data.index;
+                        data.represented = true;
+                        break;
+                    }
+                }
+            },
+            else => {
+                symbol.index = try leb.readUleb128(u32, reader);
+                const is_undefined = symbol.isUndefined();
+                const explicit_name = symbol.hasFlag(.WASM_SYM_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);
+                    break :name try parser.object.string_table.put(gpa, name);
+                } else parser.object.findImport(symbol).name;
+            },
         }
-    };
-}
+        return symbol;
+    }
+};
 
 /// First reads the count from the reader and then allocate
 /// a slice of ptr child's element type.
src/link/Wasm/ZigObject.zig
@@ -1,9 +1,9 @@
 //! ZigObject encapsulates the state of the incrementally compiled Zig module.
 //! It stores the associated input local and global symbols, allocated atoms,
 //! and any relocations that may have been emitted.
-//! Think about this as fake in-memory Object file for the Zig module.
 
-path: []const u8,
+/// For error reporting purposes only.
+path: Path,
 /// Map of all `Nav` that are currently alive.
 /// Each index maps to the corresponding `NavInfo`.
 navs: std.AutoHashMapUnmanaged(InternPool.Nav.Index, NavInfo) = .empty,
@@ -210,7 +210,7 @@ pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void {
     if (zig_object.dwarf) |*dwarf| {
         dwarf.deinit();
     }
-    gpa.free(zig_object.path);
+    gpa.free(zig_object.path.sub_path);
     zig_object.* = undefined;
 }
 
@@ -1236,6 +1236,7 @@ const codegen = @import("../../codegen.zig");
 const link = @import("../../link.zig");
 const log = std.log.scoped(.zig_object);
 const std = @import("std");
+const Path = std.Build.Cache.Path;
 
 const Air = @import("../../Air.zig");
 const Atom = Wasm.Atom;
src/link/Elf.zig
@@ -823,9 +823,8 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
     const sub_prog_node = prog_node.start("ELF Flush", 0);
     defer sub_prog_node.end();
 
-    const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type.
     const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{
-        .root_dir = directory,
+        .root_dir = self.base.emit.root_dir,
         .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname|
             try fs.path.join(arena, &.{ dirname, path })
         else
@@ -1104,7 +1103,7 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void {
 pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {
     const diags = &self.base.comp.link_diags;
     const obj = link.openObject(path, false, false) catch |err| {
-        switch (diags.failParse(path, "failed to open object {}: {s}", .{ path, @errorName(err) })) {
+        switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) {
             error.LinkFailure => return,
         }
     };
src/link/Wasm.zig
@@ -156,7 +156,7 @@ function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .empty,
 
 /// All archive files that are lazy loaded.
 /// e.g. when an undefined symbol references a symbol from the archive.
-archives: std.ArrayListUnmanaged(Archive) = .empty,
+lazy_archives: std.ArrayListUnmanaged(LazyArchive) = .empty,
 
 /// A map of global names (read: offset into string table) to their symbol location
 globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .empty,
@@ -176,6 +176,10 @@ undefs: std.AutoArrayHashMapUnmanaged(u32, SymbolLoc) = .empty,
 /// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped.
 symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .empty,
 
+/// `--verbose-link` output.
+/// Initialized on creation, appended to as inputs are added, printed during `flush`.
+dump_argv_list: std.ArrayListUnmanaged([]const u8),
+
 /// Index into objects array or the zig object.
 pub const ObjectId = enum(u16) {
     zig_object = std.math.maxInt(u16) - 1,
@@ -200,6 +204,18 @@ pub const OptionalObjectId = enum(u16) {
     }
 };
 
+const LazyArchive = struct {
+    path: Path,
+    file_contents: []const u8,
+    archive: Archive,
+
+    fn deinit(la: *LazyArchive, gpa: Allocator) void {
+        gpa.free(la.path.sub_path);
+        gpa.free(la.file_contents);
+        la.* = undefined;
+    }
+};
+
 pub const Segment = struct {
     alignment: Alignment,
     size: u32,
@@ -450,6 +466,7 @@ pub fn createEmpty(
             .named => |name| name,
         },
         .zig_object = null,
+        .dump_argv_list = .empty,
     };
     if (use_llvm and comp.config.have_zcu) {
         wasm.llvm_object = try LlvmObject.create(arena, comp);
@@ -596,7 +613,10 @@ pub fn createEmpty(
             const zig_object = try arena.create(ZigObject);
             wasm.zig_object = zig_object;
             zig_object.* = .{
-                .path = try std.fmt.allocPrint(gpa, "{s}.o", .{std.fs.path.stem(zcu.main_mod.root_src_path)}),
+                .path = .{
+                    .root_dir = std.Build.Cache.Directory.cwd(),
+                    .sub_path = try std.fmt.allocPrint(gpa, "{s}.o", .{fs.path.stem(zcu.main_mod.root_src_path)}),
+                },
                 .stack_pointer_sym = .null,
             };
             try zig_object.init(wasm);
@@ -657,28 +677,34 @@ fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: u32, tag: Symbol.Tag) !
     return loc;
 }
 
-/// Parses the object file from given path. Returns true when the given file was an object
-/// file and parsed successfully. Returns false when file is not an object file.
-/// May return an error instead when parsing failed.
-fn parseObjectFile(wasm: *Wasm, path: []const u8) !bool {
+fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {
     const diags = &wasm.base.comp.link_diags;
+    const obj = link.openObject(path, false, false) catch |err| {
+        switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) {
+            error.LinkFailure => return,
+        }
+    };
+    wasm.parseObject(obj) catch |err| {
+        switch (diags.failParse(path, "failed to parse object: {s}", .{@errorName(err)})) {
+            error.LinkFailure => return,
+        }
+    };
+}
 
-    const obj_file = try fs.cwd().openFile(path, .{});
-    errdefer obj_file.close();
-
+fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
+    defer obj.file.close();
     const gpa = wasm.base.comp.gpa;
-    var object = Object.create(wasm, obj_file, path, null) catch |err| switch (err) {
-        error.InvalidMagicByte, error.NotObjectFile => return false,
-        else => |e| {
-            var err_note = try diags.addErrorWithNotes(1);
-            try err_note.addMsg("Failed parsing object file: {s}", .{@errorName(e)});
-            try err_note.addNote("while parsing '{s}'", .{path});
-            return error.FlushFailure;
-        },
-    };
-    errdefer object.deinit(gpa);
-    try wasm.objects.append(gpa, object);
-    return true;
+    try wasm.objects.ensureUnusedCapacity(gpa, 1);
+    const stat = try obj.file.stat();
+    const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
+
+    const file_contents = try gpa.alloc(u8, size);
+    defer gpa.free(file_contents);
+
+    const n = try obj.file.preadAll(file_contents, 0);
+    if (n != file_contents.len) return error.UnexpectedEndOfFile;
+
+    wasm.objects.appendAssumeCapacity(try Object.create(wasm, file_contents, obj.path, null));
 }
 
 /// Creates a new empty `Atom` and returns its `Atom.Index`
@@ -703,43 +729,37 @@ pub fn getAtomPtr(wasm: *Wasm, index: Atom.Index) *Atom {
     return &wasm.managed_atoms.items[@intFromEnum(index)];
 }
 
-/// Parses an archive file and will then parse each object file
-/// that was found in the archive file.
-/// Returns false when the file is not an archive file.
-/// May return an error instead when parsing failed.
-///
-/// When `force_load` is `true`, it will for link all object files in the archive.
-/// When false, it will only link with object files that contain symbols that
-/// are referenced by other object files or Zig code.
-fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
+fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
     const gpa = wasm.base.comp.gpa;
-    const diags = &wasm.base.comp.link_diags;
 
-    const archive_file = try fs.cwd().openFile(path, .{});
-    errdefer archive_file.close();
+    defer obj.file.close();
 
-    var archive: Archive = .{
-        .file = archive_file,
-        .name = path,
-    };
-    archive.parse(gpa) catch |err| switch (err) {
-        error.EndOfStream, error.NotArchive => {
-            archive.deinit(gpa);
-            return false;
-        },
-        else => |e| {
-            var err_note = try diags.addErrorWithNotes(1);
-            try err_note.addMsg("Failed parsing archive: {s}", .{@errorName(e)});
-            try err_note.addNote("while parsing archive {s}", .{path});
-            return error.FlushFailure;
-        },
-    };
+    const stat = try obj.file.stat();
+    const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
+
+    const file_contents = try gpa.alloc(u8, size);
+    var keep_file_contents = false;
+    defer if (!keep_file_contents) gpa.free(file_contents);
 
-    if (!force_load) {
+    const n = try obj.file.preadAll(file_contents, 0);
+    if (n != file_contents.len) return error.UnexpectedEndOfFile;
+
+    var archive = try Archive.parse(gpa, file_contents);
+
+    if (!obj.must_link) {
         errdefer archive.deinit(gpa);
-        try wasm.archives.append(gpa, archive);
-        return true;
+        try wasm.lazy_archives.append(gpa, .{
+            .path = .{
+                .root_dir = obj.path.root_dir,
+                .sub_path = try gpa.dupe(u8, obj.path.sub_path),
+            },
+            .file_contents = file_contents,
+            .archive = archive,
+        });
+        keep_file_contents = true;
+        return;
     }
+
     defer archive.deinit(gpa);
 
     // In this case we must force link all embedded object files within the archive
@@ -754,16 +774,9 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
     }
 
     for (offsets.keys()) |file_offset| {
-        const object = archive.parseObject(wasm, file_offset) catch |e| {
-            var err_note = try diags.addErrorWithNotes(1);
-            try err_note.addMsg("Failed parsing object: {s}", .{@errorName(e)});
-            try err_note.addNote("while parsing object in archive {s}", .{path});
-            return error.FlushFailure;
-        };
+        const object = try archive.parseObject(wasm, file_contents[file_offset..], obj.path);
         try wasm.objects.append(gpa, object);
     }
-
-    return true;
 }
 
 fn requiresTLSReloc(wasm: *const Wasm) bool {
@@ -775,7 +788,7 @@ fn requiresTLSReloc(wasm: *const Wasm) bool {
     return false;
 }
 
-fn objectPath(wasm: *const Wasm, object_id: ObjectId) []const u8 {
+fn objectPath(wasm: *const Wasm, object_id: ObjectId) Path {
     const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.path;
     return obj.path;
 }
@@ -854,7 +867,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
     const gpa = wasm.base.comp.gpa;
     const diags = &wasm.base.comp.link_diags;
     const obj_path = objectPath(wasm, object_id);
-    log.debug("Resolving symbols in object: '{s}'", .{obj_path});
+    log.debug("Resolving symbols in object: '{'}'", .{obj_path});
     const symbols = objectSymbols(wasm, object_id);
 
     for (symbols, 0..) |symbol, i| {
@@ -871,9 +884,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
 
         if (symbol.isLocal()) {
             if (symbol.isUndefined()) {
-                var err = try diags.addErrorWithNotes(1);
-                try err.addMsg("Local symbols are not allowed to reference imports", .{});
-                try err.addNote("symbol '{s}' defined in '{s}'", .{ sym_name, obj_path });
+                diags.addParseError(obj_path, "local symbol '{s}' references import", .{sym_name});
             }
             try wasm.resolved_symbols.putNoClobber(gpa, location, {});
             continue;
@@ -892,7 +903,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
 
         const existing_loc = maybe_existing.value_ptr.*;
         const existing_sym: *Symbol = wasm.symbolLocSymbol(existing_loc);
-        const existing_file_path = if (existing_loc.file.unwrap()) |id| objectPath(wasm, id) else wasm.name;
+        const existing_file_path: Path = if (existing_loc.file.unwrap()) |id| objectPath(wasm, id) else .{
+            .root_dir = std.Build.Cache.Directory.cwd(),
+            .sub_path = wasm.name,
+        };
 
         if (!existing_sym.isUndefined()) outer: {
             if (!symbol.isUndefined()) inner: {
@@ -905,8 +919,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
                 // both are defined and weak, we have a symbol collision.
                 var err = try diags.addErrorWithNotes(2);
                 try err.addMsg("symbol '{s}' defined multiple times", .{sym_name});
-                try err.addNote("first definition in '{s}'", .{existing_file_path});
-                try err.addNote("next definition in '{s}'", .{obj_path});
+                try err.addNote("first definition in '{'}'", .{existing_file_path});
+                try err.addNote("next definition in '{'}'", .{obj_path});
             }
 
             try wasm.discarded.put(gpa, location, existing_loc);
@@ -916,8 +930,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
         if (symbol.tag != existing_sym.tag) {
             var err = try diags.addErrorWithNotes(2);
             try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) });
-            try err.addNote("first definition in '{s}'", .{existing_file_path});
-            try err.addNote("next definition in '{s}'", .{obj_path});
+            try err.addNote("first definition in '{'}'", .{existing_file_path});
+            try err.addNote("next definition in '{'}'", .{obj_path});
         }
 
         if (existing_sym.isUndefined() and symbol.isUndefined()) {
@@ -940,8 +954,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
                         existing_name,
                         module_name,
                     });
-                    try err.addNote("first definition in '{s}'", .{existing_file_path});
-                    try err.addNote("next definition in '{s}'", .{obj_path});
+                    try err.addNote("first definition in '{'}'", .{existing_file_path});
+                    try err.addNote("next definition in '{'}'", .{obj_path});
                 }
             }
 
@@ -956,8 +970,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
             if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) {
                 var err = try diags.addErrorWithNotes(2);
                 try err.addMsg("symbol '{s}' mismatching global types", .{sym_name});
-                try err.addNote("first definition in '{s}'", .{existing_file_path});
-                try err.addNote("next definition in '{s}'", .{obj_path});
+                try err.addNote("first definition in '{'}'", .{existing_file_path});
+                try err.addNote("next definition in '{'}'", .{obj_path});
             }
         }
 
@@ -968,8 +982,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
                 var err = try diags.addErrorWithNotes(3);
                 try err.addMsg("symbol '{s}' mismatching function signatures.", .{sym_name});
                 try err.addNote("expected signature {}, but found signature {}", .{ existing_ty, new_ty });
-                try err.addNote("first definition in '{s}'", .{existing_file_path});
-                try err.addNote("next definition in '{s}'", .{obj_path});
+                try err.addNote("first definition in '{'}'", .{existing_file_path});
+                try err.addNote("next definition in '{'}'", .{obj_path});
             }
         }
 
@@ -983,8 +997,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
 
         // simply overwrite with the new symbol
         log.debug("Overwriting symbol '{s}'", .{sym_name});
-        log.debug("  old definition in '{s}'", .{existing_file_path});
-        log.debug("  new definition in '{s}'", .{obj_path});
+        log.debug("  old definition in '{'}'", .{existing_file_path});
+        log.debug("  new definition in '{'}'", .{obj_path});
         try wasm.discarded.putNoClobber(gpa, existing_loc, location);
         maybe_existing.value_ptr.* = location;
         try wasm.globals.put(gpa, sym_name_index, location);
@@ -997,31 +1011,29 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
 }
 
 fn resolveSymbolsInArchives(wasm: *Wasm) !void {
+    if (wasm.lazy_archives.items.len == 0) return;
     const gpa = wasm.base.comp.gpa;
     const diags = &wasm.base.comp.link_diags;
-    if (wasm.archives.items.len == 0) return;
 
-    log.debug("Resolving symbols in archives", .{});
+    log.debug("Resolving symbols in lazy_archives", .{});
     var index: u32 = 0;
     undef_loop: while (index < wasm.undefs.count()) {
         const sym_name_index = wasm.undefs.keys()[index];
 
-        for (wasm.archives.items) |archive| {
+        for (wasm.lazy_archives.items) |lazy_archive| {
             const sym_name = wasm.string_table.get(sym_name_index);
-            log.debug("Detected symbol '{s}' in archive '{s}', parsing objects..", .{ sym_name, archive.name });
-            const offset = archive.toc.get(sym_name) orelse {
-                // symbol does not exist in this archive
-                continue;
-            };
+            log.debug("Detected symbol '{s}' in archive '{'}', parsing objects..", .{
+                sym_name, lazy_archive.path,
+            });
+            const offset = lazy_archive.archive.toc.get(sym_name) orelse continue; // symbol does not exist in this archive
 
             // Symbol is found in unparsed object file within current archive.
             // Parse object and and resolve symbols again before we check remaining
             // undefined symbols.
-            const object = archive.parseObject(wasm, offset.items[0]) catch |e| {
-                var err_note = try diags.addErrorWithNotes(1);
-                try err_note.addMsg("Failed parsing object: {s}", .{@errorName(e)});
-                try err_note.addNote("while parsing object in archive {s}", .{archive.name});
-                return error.FlushFailure;
+            const file_contents = lazy_archive.file_contents[offset.items[0]..];
+            const object = lazy_archive.archive.parseObject(wasm, file_contents, lazy_archive.path) catch |err| {
+                // TODO this fails to include information to identify which object failed
+                return diags.failParse(lazy_archive.path, "failed to parse object in archive: {s}", .{@errorName(err)});
             };
             try wasm.objects.append(gpa, object);
             try wasm.resolveSymbolsInObject(@enumFromInt(wasm.objects.items.len - 1));
@@ -1323,9 +1335,11 @@ fn validateFeatures(
             allowed[used_index] = is_enabled;
             emit_features_count.* += @intFromBool(is_enabled);
         } else if (is_enabled and !allowed[used_index]) {
-            var err = try diags.addErrorWithNotes(1);
-            try err.addMsg("feature '{}' not allowed, but used by linked object", .{@as(Feature.Tag, @enumFromInt(used_index))});
-            try err.addNote("defined in '{s}'", .{wasm.objects.items[used_set >> 1].path});
+            diags.addParseError(
+                wasm.objects.items[used_set >> 1].path,
+                "feature '{}' not allowed, but used by linked object",
+                .{@as(Feature.Tag, @enumFromInt(used_index))},
+            );
             valid_feature_set = false;
         }
     }
@@ -1337,10 +1351,10 @@ fn validateFeatures(
     if (shared_memory) {
         const disallowed_feature = disallowed[@intFromEnum(Feature.Tag.shared_mem)];
         if (@as(u1, @truncate(disallowed_feature)) != 0) {
-            var err = try diags.addErrorWithNotes(0);
-            try err.addMsg(
-                "shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled",
-                .{wasm.objects.items[disallowed_feature >> 1].path},
+            diags.addParseError(
+                wasm.objects.items[disallowed_feature >> 1].path,
+                "shared-memory is disallowed because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled",
+                .{},
             );
             valid_feature_set = false;
         }
@@ -1371,8 +1385,8 @@ fn validateFeatures(
             if (@as(u1, @truncate(disallowed_feature)) != 0) {
                 var err = try diags.addErrorWithNotes(2);
                 try err.addMsg("feature '{}' is disallowed, but used by linked object", .{feature.tag});
-                try err.addNote("disallowed by '{s}'", .{wasm.objects.items[disallowed_feature >> 1].path});
-                try err.addNote("used in '{s}'", .{object.path});
+                try err.addNote("disallowed by '{'}'", .{wasm.objects.items[disallowed_feature >> 1].path});
+                try err.addNote("used in '{'}'", .{object.path});
                 valid_feature_set = false;
             }
 
@@ -1385,8 +1399,8 @@ fn validateFeatures(
             if (is_required and !object_used_features[feature_index]) {
                 var err = try diags.addErrorWithNotes(2);
                 try err.addMsg("feature '{}' is required but not used in linked object", .{@as(Feature.Tag, @enumFromInt(feature_index))});
-                try err.addNote("required by '{s}'", .{wasm.objects.items[required_feature >> 1].path});
-                try err.addNote("missing in '{s}'", .{object.path});
+                try err.addNote("required by '{'}'", .{wasm.objects.items[required_feature >> 1].path});
+                try err.addNote("missing in '{'}'", .{object.path});
                 valid_feature_set = false;
             }
         }
@@ -1460,19 +1474,25 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {
         const symbol = wasm.symbolLocSymbol(undef);
         if (symbol.tag == .data) {
             found_undefined_symbols = true;
-            const file_name = switch (undef.file) {
-                .zig_object => wasm.zig_object.?.path,
-                .none => wasm.name,
-                _ => wasm.objects.items[@intFromEnum(undef.file)].path,
-            };
             const symbol_name = wasm.symbolLocName(undef);
-            var err = try diags.addErrorWithNotes(1);
-            try err.addMsg("could not resolve undefined symbol '{s}'", .{symbol_name});
-            try err.addNote("defined in '{s}'", .{file_name});
+            switch (undef.file) {
+                .zig_object => {
+                    // TODO: instead of saying the zig compilation unit, attach an actual source location
+                    // to this diagnostic
+                    diags.addError("unresolved symbol in Zig compilation unit: {s}", .{symbol_name});
+                },
+                .none => {
+                    diags.addError("internal linker bug: unresolved synthetic symbol: {s}", .{symbol_name});
+                },
+                _ => {
+                    const path = wasm.objects.items[@intFromEnum(undef.file)].path;
+                    diags.addParseError(path, "unresolved symbol: {s}", .{symbol_name});
+                },
+            }
         }
     }
     if (found_undefined_symbols) {
-        return error.FlushFailure;
+        return error.LinkFailure;
     }
 }
 
@@ -1493,9 +1513,8 @@ pub fn deinit(wasm: *Wasm) void {
         object.deinit(gpa);
     }
 
-    for (wasm.archives.items) |*archive| {
-        archive.deinit(gpa);
-    }
+    for (wasm.lazy_archives.items) |*lazy_archive| lazy_archive.deinit(gpa);
+    wasm.lazy_archives.deinit(gpa);
 
     if (wasm.findGlobalSymbol("__wasm_init_tls")) |loc| {
         const atom = wasm.symbol_atom.get(loc).?;
@@ -1514,7 +1533,6 @@ pub fn deinit(wasm: *Wasm) void {
     wasm.data_segments.deinit(gpa);
     wasm.segment_info.deinit(gpa);
     wasm.objects.deinit(gpa);
-    wasm.archives.deinit(gpa);
 
     // free output sections
     wasm.imports.deinit(gpa);
@@ -1527,6 +1545,7 @@ pub fn deinit(wasm: *Wasm) void {
     wasm.exports.deinit(gpa);
 
     wasm.string_table.deinit(gpa);
+    wasm.dump_argv_list.deinit(gpa);
 }
 
 pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
@@ -2584,7 +2603,7 @@ pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol
             } else {
                 var err = try diags.addErrorWithNotes(1);
                 try err.addMsg("found unknown section '{s}'", .{section_name});
-                try err.addNote("defined in '{s}'", .{objectPath(wasm, object_id)});
+                try err.addNote("defined in '{'}'", .{objectPath(wasm, object_id)});
                 return error.UnexpectedValue;
             }
         },
@@ -2603,6 +2622,32 @@ fn appendDummySegment(wasm: *Wasm) !void {
     });
 }
 
+pub fn loadInput(wasm: *Wasm, input: link.Input) !void {
+    const comp = wasm.base.comp;
+    const gpa = comp.gpa;
+
+    if (comp.verbose_link) {
+        comp.mutex.lock(); // protect comp.arena
+        defer comp.mutex.unlock();
+
+        const argv = &wasm.dump_argv_list;
+        switch (input) {
+            .res => unreachable,
+            .dso_exact => unreachable,
+            .dso => unreachable,
+            .object, .archive => |obj| try argv.append(gpa, try obj.path.toString(comp.arena)),
+        }
+    }
+
+    switch (input) {
+        .res => unreachable,
+        .dso_exact => unreachable,
+        .dso => unreachable,
+        .object => |obj| try parseObject(wasm, obj),
+        .archive => |obj| try parseArchive(wasm, obj),
+    }
+}
+
 pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
     const comp = wasm.base.comp;
     const use_lld = build_options.have_llvm and comp.config.use_lld;
@@ -2613,7 +2658,6 @@ pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st
     return wasm.flushModule(arena, tid, prog_node);
 }
 
-/// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary.
 pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
@@ -2626,85 +2670,22 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
         if (use_lld) return;
     }
 
+    if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items);
+
     const sub_prog_node = prog_node.start("Wasm Flush", 0);
     defer sub_prog_node.end();
 
-    const directory = wasm.base.emit.root_dir; // Just an alias to make it shorter to type.
-    const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path});
-    const module_obj_path: ?[]const u8 = if (wasm.base.zcu_object_sub_path) |path| blk: {
-        if (fs.path.dirname(full_out_path)) |dirname| {
-            break :blk try fs.path.join(arena, &.{ dirname, path });
-        } else {
-            break :blk path;
-        }
+    const module_obj_path: ?Path = if (wasm.base.zcu_object_sub_path) |path| .{
+        .root_dir = wasm.base.emit.root_dir,
+        .sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname|
+            try fs.path.join(arena, &.{ dirname, path })
+        else
+            path,
     } else null;
 
-    // Positional arguments to the linker such as object files and static archives.
-    // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.
-    var positionals = std.ArrayList([]const u8).init(arena);
-    try positionals.ensureUnusedCapacity(comp.link_inputs.len);
+    if (wasm.zig_object) |zig_object| try zig_object.flushModule(wasm, tid);
 
-    const target = comp.root_mod.resolved_target.result;
-    const output_mode = comp.config.output_mode;
-    const link_mode = comp.config.link_mode;
-    const link_libc = comp.config.link_libc;
-    const link_libcpp = comp.config.link_libcpp;
-    const wasi_exec_model = comp.config.wasi_exec_model;
-
-    if (wasm.zig_object) |zig_object| {
-        try zig_object.flushModule(wasm, tid);
-    }
-
-    // When the target os is WASI, we allow linking with WASI-LIBC
-    if (target.os.tag == .wasi) {
-        const is_exe_or_dyn_lib = output_mode == .Exe or
-            (output_mode == .Lib and link_mode == .dynamic);
-        if (is_exe_or_dyn_lib) {
-            for (comp.wasi_emulated_libs) |crt_file| {
-                try positionals.append(try comp.crtFileAsString(
-                    arena,
-                    wasi_libc.emulatedLibCRFileLibName(crt_file),
-                ));
-            }
-
-            if (link_libc) {
-                try positionals.append(try comp.crtFileAsString(
-                    arena,
-                    wasi_libc.execModelCrtFileFullName(wasi_exec_model),
-                ));
-                try positionals.append(try comp.crtFileAsString(arena, "libc.a"));
-            }
-
-            if (link_libcpp) {
-                try positionals.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena));
-                try positionals.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena));
-            }
-        }
-    }
-
-    if (module_obj_path) |path| {
-        try positionals.append(path);
-    }
-
-    for (comp.link_inputs) |link_input| switch (link_input) {
-        .object, .archive => |obj| try positionals.append(try obj.path.toString(arena)),
-        .dso => |dso| try positionals.append(try dso.path.toString(arena)),
-        .dso_exact => unreachable, // forbidden by frontend
-        .res => unreachable, // windows only
-    };
-
-    for (comp.c_object_table.keys()) |c_object| {
-        try positionals.append(try c_object.status.success.object_path.toString(arena));
-    }
-
-    if (comp.compiler_rt_lib) |lib| try positionals.append(try lib.full_object_path.toString(arena));
-    if (comp.compiler_rt_obj) |obj| try positionals.append(try obj.full_object_path.toString(arena));
-
-    for (positionals.items) |path| {
-        if (try wasm.parseObjectFile(path)) continue;
-        if (try wasm.parseArchive(path, false)) continue; // load archives lazily
-        log.warn("Unexpected file format at path: '{s}'", .{path});
-    }
+    if (module_obj_path) |path| openParseObjectReportingFailure(wasm, path);
 
     if (wasm.zig_object != null) {
         try wasm.resolveSymbolsInObject(.zig_object);
@@ -3594,7 +3575,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
             // regarding eliding redundant object -> object transformations.
             return error.NoObjectsToLink;
         };
-        try std.fs.Dir.copyFile(
+        try fs.Dir.copyFile(
             the_object_path.root_dir.handle,
             the_object_path.sub_path,
             directory.handle,
src/link.zig
@@ -1085,7 +1085,7 @@ pub const File = struct {
         const use_lld = build_options.have_llvm and base.comp.config.use_lld;
         if (use_lld) return;
         switch (base.tag) {
-            inline .elf => |tag| {
+            inline .elf, .wasm => |tag| {
                 dev.check(tag.devFeature());
                 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
             },