Commit 795e7c64d5

Andrew Kelley <andrew@ziglang.org>
2024-11-05 02:26:17
wasm linker: aggressive DODification
The goals of this branch are to: * compile faster when using the wasm linker and backend * enable saving compiler state by directly copying in-memory linker state to disk. * more efficient compiler memory utilization * introduce integer type safety to wasm linker code * generate better WebAssembly code * fully participate in incremental compilation * do as much work as possible outside of flush(), while continuing to do linker garbage collection. * avoid unnecessary heap allocations * avoid unnecessary indirect function calls In order to accomplish this goals, this removes the ZigObject abstraction, as well as Symbol and Atom. These abstractions resulted in overly generic code, doing unnecessary work, and needless complications that simply go away by creating a better in-memory data model and emitting more things lazily. For example, this makes wasm codegen emit MIR which is then lowered to wasm code during linking, with optimal function indexes etc, or relocations are emitted if outputting an object. Previously, this would always emit relocations, which are fully unnecessary when emitting an executable, and required all function calls to use the maximum size LEB encoding. This branch introduces the concept of the "prelink" phase which occurs after all object files have been parsed, but before any Zcu updates are sent to the linker. This allows the linker to fully parse all objects into a compact memory model, which is guaranteed to be complete when Zcu code is generated. This commit is not a complete implementation of all these goals; it is not even passing semantic analysis.
1 parent 7727310
lib/std/Build/Step/CheckObject.zig
@@ -2682,7 +2682,7 @@ const WasmDumper = struct {
             else => unreachable,
         }
         const end_opcode = try std.leb.readUleb128(u8, reader);
-        if (end_opcode != std.wasm.opcode(.end)) {
+        if (end_opcode != @intFromEnum(std.wasm.Opcode.end)) {
             return step.fail("expected 'end' opcode in init expression", .{});
         }
     }
lib/std/wasm.zig
@@ -4,8 +4,6 @@
 const std = @import("std.zig");
 const testing = std.testing;
 
-// TODO: Add support for multi-byte ops (e.g. table operations)
-
 /// Wasm instruction opcodes
 ///
 /// All instructions are defined as per spec:
@@ -195,27 +193,6 @@ pub const Opcode = enum(u8) {
     _,
 };
 
-/// Returns the integer value of an `Opcode`. Used by the Zig compiler
-/// to write instructions to the wasm binary file
-pub fn opcode(op: Opcode) u8 {
-    return @intFromEnum(op);
-}
-
-test "opcodes" {
-    // Ensure our opcodes values remain intact as certain values are skipped due to them being reserved
-    const i32_const = opcode(.i32_const);
-    const end = opcode(.end);
-    const drop = opcode(.drop);
-    const local_get = opcode(.local_get);
-    const i64_extend32_s = opcode(.i64_extend32_s);
-
-    try testing.expectEqual(@as(u16, 0x41), i32_const);
-    try testing.expectEqual(@as(u16, 0x0B), end);
-    try testing.expectEqual(@as(u16, 0x1A), drop);
-    try testing.expectEqual(@as(u16, 0x20), local_get);
-    try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s);
-}
-
 /// Opcodes that require a prefix `0xFC`.
 /// Each opcode represents a varuint32, meaning
 /// they are encoded as leb128 in binary.
@@ -241,12 +218,6 @@ pub const MiscOpcode = enum(u32) {
     _,
 };
 
-/// Returns the integer value of an `MiscOpcode`. Used by the Zig compiler
-/// to write instructions to the wasm binary file
-pub fn miscOpcode(op: MiscOpcode) u32 {
-    return @intFromEnum(op);
-}
-
 /// Simd opcodes that require a prefix `0xFD`.
 /// Each opcode represents a varuint32, meaning
 /// they are encoded as leb128 in binary.
@@ -512,12 +483,6 @@ pub const SimdOpcode = enum(u32) {
     f32x4_relaxed_dot_bf16x8_add_f32x4 = 0x114,
 };
 
-/// Returns the integer value of an `SimdOpcode`. Used by the Zig compiler
-/// to write instructions to the wasm binary file
-pub fn simdOpcode(op: SimdOpcode) u32 {
-    return @intFromEnum(op);
-}
-
 /// Atomic opcodes that require a prefix `0xFE`.
 /// Each opcode represents a varuint32, meaning
 /// they are encoded as leb128 in binary.
@@ -592,12 +557,6 @@ pub const AtomicsOpcode = enum(u32) {
     i64_atomic_rmw32_cmpxchg_u = 0x4E,
 };
 
-/// Returns the integer value of an `AtomicsOpcode`. Used by the Zig compiler
-/// to write instructions to the wasm binary file
-pub fn atomicsOpcode(op: AtomicsOpcode) u32 {
-    return @intFromEnum(op);
-}
-
 /// Enum representing all Wasm value types as per spec:
 /// https://webassembly.github.io/spec/core/binary/types.html
 pub const Valtype = enum(u8) {
@@ -608,11 +567,6 @@ pub const Valtype = enum(u8) {
     v128 = 0x7B,
 };
 
-/// Returns the integer value of a `Valtype`
-pub fn valtype(value: Valtype) u8 {
-    return @intFromEnum(value);
-}
-
 /// Reference types, where the funcref references to a function regardless of its type
 /// and ref references an object from the embedder.
 pub const RefType = enum(u8) {
@@ -620,41 +574,17 @@ pub const RefType = enum(u8) {
     externref = 0x6F,
 };
 
-/// Returns the integer value of a `Reftype`
-pub fn reftype(value: RefType) u8 {
-    return @intFromEnum(value);
-}
-
-test "valtypes" {
-    const _i32 = valtype(.i32);
-    const _i64 = valtype(.i64);
-    const _f32 = valtype(.f32);
-    const _f64 = valtype(.f64);
-
-    try testing.expectEqual(@as(u8, 0x7F), _i32);
-    try testing.expectEqual(@as(u8, 0x7E), _i64);
-    try testing.expectEqual(@as(u8, 0x7D), _f32);
-    try testing.expectEqual(@as(u8, 0x7C), _f64);
-}
-
 /// Limits classify the size range of resizeable storage associated with memory types and table types.
 pub const Limits = struct {
-    flags: u8,
+    flags: Flags,
     min: u32,
     max: u32,
 
-    pub const Flags = enum(u8) {
-        WASM_LIMITS_FLAG_HAS_MAX = 0x1,
-        WASM_LIMITS_FLAG_IS_SHARED = 0x2,
+    pub const Flags = packed struct(u8) {
+        has_max: bool,
+        is_shared: bool,
+        reserved: u6 = 0,
     };
-
-    pub fn hasFlag(limits: Limits, flag: Flags) bool {
-        return limits.flags & @intFromEnum(flag) != 0;
-    }
-
-    pub fn setFlag(limits: *Limits, flag: Flags) void {
-        limits.flags |= @intFromEnum(flag);
-    }
 };
 
 /// Initialization expressions are used to set the initial value on an object
@@ -667,18 +597,6 @@ pub const InitExpression = union(enum) {
     global_get: u32,
 };
 
-/// Represents a function entry, holding the index to its type
-pub const Func = struct {
-    type_index: u32,
-};
-
-/// Tables are used to hold pointers to opaque objects.
-/// This can either by any function, or an object from the host.
-pub const Table = struct {
-    limits: Limits,
-    reftype: RefType,
-};
-
 /// Describes the layout of the memory where `min` represents
 /// the minimal amount of pages, and the optional `max` represents
 /// the max pages. When `null` will allow the host to determine the
@@ -687,88 +605,6 @@ pub const Memory = struct {
     limits: Limits,
 };
 
-/// Represents the type of a `Global` or an imported global.
-pub const GlobalType = struct {
-    valtype: Valtype,
-    mutable: bool,
-};
-
-pub const Global = struct {
-    global_type: GlobalType,
-    init: InitExpression,
-};
-
-/// Notates an object to be exported from wasm
-/// to the host.
-pub const Export = struct {
-    name: []const u8,
-    kind: ExternalKind,
-    index: u32,
-};
-
-/// Element describes the layout of the table that can
-/// be found at `table_index`
-pub const Element = struct {
-    table_index: u32,
-    offset: InitExpression,
-    func_indexes: []const u32,
-};
-
-/// Imports are used to import objects from the host
-pub const Import = struct {
-    module_name: []const u8,
-    name: []const u8,
-    kind: Kind,
-
-    pub const Kind = union(ExternalKind) {
-        function: u32,
-        table: Table,
-        memory: Limits,
-        global: GlobalType,
-    };
-};
-
-/// `Type` represents a function signature type containing both
-/// a slice of parameters as well as a slice of return values.
-pub const Type = struct {
-    params: []const Valtype,
-    returns: []const Valtype,
-
-    pub fn format(self: Type, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
-        if (fmt.len != 0) std.fmt.invalidFmtError(fmt, self);
-        _ = opt;
-        try writer.writeByte('(');
-        for (self.params, 0..) |param, i| {
-            try writer.print("{s}", .{@tagName(param)});
-            if (i + 1 != self.params.len) {
-                try writer.writeAll(", ");
-            }
-        }
-        try writer.writeAll(") -> ");
-        if (self.returns.len == 0) {
-            try writer.writeAll("nil");
-        } else {
-            for (self.returns, 0..) |return_ty, i| {
-                try writer.print("{s}", .{@tagName(return_ty)});
-                if (i + 1 != self.returns.len) {
-                    try writer.writeAll(", ");
-                }
-            }
-        }
-    }
-
-    pub fn eql(self: Type, other: Type) bool {
-        return std.mem.eql(Valtype, self.params, other.params) and
-            std.mem.eql(Valtype, self.returns, other.returns);
-    }
-
-    pub fn deinit(self: *Type, gpa: std.mem.Allocator) void {
-        gpa.free(self.params);
-        gpa.free(self.returns);
-        self.* = undefined;
-    }
-};
-
 /// Wasm module sections as per spec:
 /// https://webassembly.github.io/spec/core/binary/modules.html
 pub const Section = enum(u8) {
@@ -788,11 +624,6 @@ pub const Section = enum(u8) {
     _,
 };
 
-/// Returns the integer value of a given `Section`
-pub fn section(val: Section) u8 {
-    return @intFromEnum(val);
-}
-
 /// The kind of the type when importing or exporting to/from the host environment.
 /// https://webassembly.github.io/spec/core/syntax/modules.html
 pub const ExternalKind = enum(u8) {
@@ -802,11 +633,6 @@ pub const ExternalKind = enum(u8) {
     global,
 };
 
-/// Returns the integer value of a given `ExternalKind`
-pub fn externalKind(val: ExternalKind) u8 {
-    return @intFromEnum(val);
-}
-
 /// Defines the enum values for each subsection id for the "Names" custom section
 /// as described by:
 /// https://webassembly.github.io/spec/core/appendix/custom.html?highlight=name#name-section
src/arch/aarch64/CodeGen.zig
@@ -167,7 +167,7 @@ const DbgInfoReloc = struct {
     name: [:0]const u8,
     mcv: MCValue,
 
-    fn genDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
+    fn genDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void {
         switch (reloc.tag) {
             .arg,
             .dbg_arg_inline,
@@ -181,7 +181,7 @@ const DbgInfoReloc = struct {
         }
     }
 
-    fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
+    fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void {
         switch (function.debug_output) {
             .dwarf => |dw| {
                 const loc: link.File.Dwarf.Loc = switch (reloc.mcv) {
@@ -209,7 +209,7 @@ const DbgInfoReloc = struct {
         }
     }
 
-    fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
+    fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void {
         switch (function.debug_output) {
             .dwarf => |dwarf| {
                 const loc: link.File.Dwarf.Loc = switch (reloc.mcv) {
@@ -395,13 +395,13 @@ pub fn generate(
         try reloc.genDbgInfo(function);
     }
 
-    var mir = Mir{
+    var mir: Mir = .{
         .instructions = function.mir_instructions.toOwnedSlice(),
         .extra = try function.mir_extra.toOwnedSlice(gpa),
     };
     defer mir.deinit(gpa);
 
-    var emit = Emit{
+    var emit: Emit = .{
         .mir = mir,
         .bin_file = lf,
         .debug_output = debug_output,
@@ -723,7 +723,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .cmp_gt  => try self.airCmp(inst, .gt),
             .cmp_neq => try self.airCmp(inst, .neq),
 
-            .cmp_vector => try self.airCmpVector(inst),
+            .cmp_vector =>        try self.airCmpVector(inst),
             .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
 
             .alloc           => try self.airAlloc(inst),
@@ -744,7 +744,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .fpext           => try self.airFpext(inst),
             .intcast         => try self.airIntCast(inst),
             .trunc           => try self.airTrunc(inst),
-            .int_from_bool     => try self.airIntFromBool(inst),
+            .int_from_bool   => try self.airIntFromBool(inst),
             .is_non_null     => try self.airIsNonNull(inst),
             .is_non_null_ptr => try self.airIsNonNullPtr(inst),
             .is_null         => try self.airIsNull(inst),
@@ -756,7 +756,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .load            => try self.airLoad(inst),
             .loop            => try self.airLoop(inst),
             .not             => try self.airNot(inst),
-            .int_from_ptr        => try self.airIntFromPtr(inst),
+            .int_from_ptr    => try self.airIntFromPtr(inst),
             .ret             => try self.airRet(inst),
             .ret_safe        => try self.airRet(inst), // TODO
             .ret_load        => try self.airRetLoad(inst),
@@ -765,8 +765,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .struct_field_ptr=> try self.airStructFieldPtr(inst),
             .struct_field_val=> try self.airStructFieldVal(inst),
             .array_to_slice  => try self.airArrayToSlice(inst),
-            .float_from_int    => try self.airFloatFromInt(inst),
-            .int_from_float    => try self.airIntFromFloat(inst),
+            .float_from_int  => try self.airFloatFromInt(inst),
+            .int_from_float  => try self.airIntFromFloat(inst),
             .cmpxchg_strong  => try self.airCmpxchg(inst),
             .cmpxchg_weak    => try self.airCmpxchg(inst),
             .atomic_rmw      => try self.airAtomicRmw(inst),
@@ -1107,7 +1107,7 @@ fn spillCompareFlagsIfOccupied(self: *Self) !void {
 /// Copies a value to a register without tracking the register. The register is not considered
 /// allocated. A second call to `copyToTmpRegister` may return the same register.
 /// This can have a side effect of spilling instructions to the stack to free up a register.
-fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {
+fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) InnerError!Register {
     const raw_reg = try self.register_manager.allocReg(null, gp);
     const reg = self.registerAlias(raw_reg, ty);
     try self.genSetReg(ty, reg, mcv);
@@ -1125,12 +1125,12 @@ fn copyToNewRegister(self: *Self, reg_owner: Air.Inst.Index, mcv: MCValue) !MCVa
     return MCValue{ .register = reg };
 }
 
-fn airAlloc(self: *Self, inst: Air.Inst.Index) !void {
+fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const stack_offset = try self.allocMemPtr(inst);
     return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
 }
 
-fn airRetPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const result: MCValue = switch (self.ret_mcv) {
@@ -1152,19 +1152,19 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ .none, .none, .none });
 }
 
-fn airFptrunc(self: *Self, inst: Air.Inst.Index) !void {
+fn airFptrunc(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFptrunc for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airFpext(self: *Self, inst: Air.Inst.Index) !void {
+fn airFpext(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFpext for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airIntCast(self: *Self, inst: Air.Inst.Index) !void {
+fn airIntCast(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     if (self.liveness.isUnused(inst))
         return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none });
@@ -1293,7 +1293,7 @@ fn trunc(
     }
 }
 
-fn airTrunc(self: *Self, inst: Air.Inst.Index) !void {
+fn airTrunc(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const operand = try self.resolveInst(ty_op.operand);
     const operand_ty = self.typeOf(ty_op.operand);
@@ -1306,14 +1306,14 @@ fn airTrunc(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airIntFromBool(self: *Self, inst: Air.Inst.Index) !void {
+fn airIntFromBool(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const operand = try self.resolveInst(un_op);
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else operand;
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airNot(self: *Self, inst: Air.Inst.Index) !void {
+fn airNot(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const pt = self.pt;
     const zcu = pt.zcu;
@@ -1484,7 +1484,7 @@ fn minMax(
     }
 }
 
-fn airMinMax(self: *Self, inst: Air.Inst.Index) !void {
+fn airMinMax(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const lhs_ty = self.typeOf(bin_op.lhs);
@@ -1502,7 +1502,7 @@ fn airMinMax(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airSlice(self: *Self, inst: Air.Inst.Index) !void {
+fn airSlice(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
@@ -2440,7 +2440,7 @@ fn ptrArithmetic(
     }
 }
 
-fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
+fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) InnerError!void {
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const lhs_ty = self.typeOf(bin_op.lhs);
     const rhs_ty = self.typeOf(bin_op.rhs);
@@ -2490,7 +2490,7 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airPtrArithmetic(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
+fn airPtrArithmetic(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
     const lhs_ty = self.typeOf(bin_op.lhs);
@@ -2505,25 +2505,25 @@ fn airPtrArithmetic(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airAddSat(self: *Self, inst: Air.Inst.Index) !void {
+fn airAddSat(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airSubSat(self: *Self, inst: Air.Inst.Index) !void {
+fn airSubSat(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
+fn airMulSat(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airOverflow(self: *Self, inst: Air.Inst.Index) !void {
+fn airOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
@@ -2536,9 +2536,9 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void {
         const rhs_ty = self.typeOf(extra.rhs);
 
         const tuple_ty = self.typeOfIndex(inst);
-        const tuple_size = @as(u32, @intCast(tuple_ty.abiSize(zcu)));
+        const tuple_size: u32 = @intCast(tuple_ty.abiSize(zcu));
         const tuple_align = tuple_ty.abiAlignment(zcu);
-        const overflow_bit_offset = @as(u32, @intCast(tuple_ty.structFieldOffset(1, zcu)));
+        const overflow_bit_offset: u32 = @intCast(tuple_ty.structFieldOffset(1, zcu));
 
         switch (lhs_ty.zigTypeTag(zcu)) {
             .vector => return self.fail("TODO implement add_with_overflow/sub_with_overflow for vectors", .{}),
@@ -2652,7 +2652,7 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
 }
 
-fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
+fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
     if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ extra.lhs, extra.rhs, .none });
@@ -2876,7 +2876,7 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
 }
 
-fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
+fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
     if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ extra.lhs, extra.rhs, .none });
@@ -3012,13 +3012,13 @@ fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
 }
 
-fn airShlSat(self: *Self, inst: Air.Inst.Index) !void {
+fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) !void {
+fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const optional_ty = self.typeOf(ty_op.operand);
@@ -3055,13 +3055,13 @@ fn optionalPayload(self: *Self, inst: Air.Inst.Index, mcv: MCValue, optional_ty:
     }
 }
 
-fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
+fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
@@ -3137,7 +3137,7 @@ fn errUnionErr(
     }
 }
 
-fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
+fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_bind: ReadArg.Bind = .{ .inst = ty_op.operand };
@@ -3218,7 +3218,7 @@ fn errUnionPayload(
     }
 }
 
-fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
+fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_bind: ReadArg.Bind = .{ .inst = ty_op.operand };
@@ -3230,26 +3230,26 @@ fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
 }
 
 // *(E!T) -> E
-fn airUnwrapErrErrPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airUnwrapErrErrPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error ptr for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
 // *(E!T) -> *T
-fn airUnwrapErrPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airUnwrapErrPayloadPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union payload ptr for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
+fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .errunion_payload_ptr_set for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
+fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const result: MCValue = if (self.liveness.isUnused(inst))
         .dead
     else
@@ -3257,17 +3257,17 @@ fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ .none, .none, .none });
 }
 
-fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
+fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) InnerError!void {
     _ = inst;
     return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
 }
 
-fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
+fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) InnerError!void {
     _ = inst;
     return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
 }
 
-fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
+fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
@@ -3313,7 +3313,7 @@ fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
 }
 
 /// T to E!T
-fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
+fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
@@ -3338,7 +3338,7 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
 }
 
 /// E to E!T
-fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
+fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const pt = self.pt;
@@ -3379,7 +3379,7 @@ fn slicePtr(mcv: MCValue) MCValue {
     }
 }
 
-fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airSlicePtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const mcv = try self.resolveInst(ty_op.operand);
@@ -3388,7 +3388,7 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void {
+fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const ptr_bits = 64;
@@ -3412,7 +3412,7 @@ fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const ptr_bits = 64;
@@ -3429,7 +3429,7 @@ fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const mcv = try self.resolveInst(ty_op.operand);
@@ -3444,7 +3444,7 @@ fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void {
+fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
@@ -3487,7 +3487,7 @@ fn ptrElemVal(
     }
 }
 
-fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
@@ -3506,13 +3506,13 @@ fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
 }
 
-fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void {
+fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement array_elem_val for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
+fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
@@ -3526,7 +3526,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
@@ -3542,55 +3542,55 @@ fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
 }
 
-fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
+fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     _ = bin_op;
     return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch});
 }
 
-fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
+fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airClz(self: *Self, inst: Air.Inst.Index) !void {
+fn airClz(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airClz for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airCtz(self: *Self, inst: Air.Inst.Index) !void {
+fn airCtz(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCtz for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
+fn airPopcount(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airPopcount for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airAbs(self: *Self, inst: Air.Inst.Index) !void {
+fn airAbs(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airAbs for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airByteSwap(self: *Self, inst: Air.Inst.Index) !void {
+fn airByteSwap(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airByteSwap for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airBitReverse(self: *Self, inst: Air.Inst.Index) !void {
+fn airBitReverse(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airBitReverse for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void {
+fn airUnaryMath(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const result: MCValue = if (self.liveness.isUnused(inst))
         .dead
@@ -3885,7 +3885,7 @@ fn genInlineMemsetCode(
     // end:
 }
 
-fn airLoad(self: *Self, inst: Air.Inst.Index) !void {
+fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
@@ -4086,7 +4086,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
     }
 }
 
-fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) !void {
+fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) InnerError!void {
     if (safety) {
         // TODO if the value is undef, write 0xaa bytes to dest
     } else {
@@ -4103,14 +4103,14 @@ fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) !void {
     return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
     const result = try self.structFieldPtr(inst, extra.struct_operand, extra.field_index);
     return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none });
 }
 
-fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) !void {
+fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result = try self.structFieldPtr(inst, ty_op.operand, index);
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
@@ -4138,7 +4138,7 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde
     };
 }
 
-fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {
+fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
     const operand = extra.struct_operand;
@@ -4194,7 +4194,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none });
 }
 
-fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
@@ -4218,7 +4218,7 @@ fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.field_ptr, .none, .none });
 }
 
-fn airArg(self: *Self, inst: Air.Inst.Index) !void {
+fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!void {
     // skip zero-bit arguments as they don't have a corresponding arg instruction
     var arg_index = self.arg_index;
     while (self.args[arg_index] == .none) arg_index += 1;
@@ -4238,7 +4238,7 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ .none, .none, .none });
 }
 
-fn airTrap(self: *Self) !void {
+fn airTrap(self: *Self) InnerError!void {
     _ = try self.addInst(.{
         .tag = .brk,
         .data = .{ .imm16 = 0x0001 },
@@ -4246,7 +4246,7 @@ fn airTrap(self: *Self) !void {
     return self.finishAirBookkeeping();
 }
 
-fn airBreakpoint(self: *Self) !void {
+fn airBreakpoint(self: *Self) InnerError!void {
     _ = try self.addInst(.{
         .tag = .brk,
         .data = .{ .imm16 = 0xf000 },
@@ -4254,17 +4254,17 @@ fn airBreakpoint(self: *Self) !void {
     return self.finishAirBookkeeping();
 }
 
-fn airRetAddr(self: *Self, inst: Air.Inst.Index) !void {
+fn airRetAddr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airRetAddr for aarch64", .{});
     return self.finishAir(inst, result, .{ .none, .none, .none });
 }
 
-fn airFrameAddress(self: *Self, inst: Air.Inst.Index) !void {
+fn airFrameAddress(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFrameAddress for aarch64", .{});
     return self.finishAir(inst, result, .{ .none, .none, .none });
 }
 
-fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !void {
+fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) InnerError!void {
     if (modifier == .always_tail) return self.fail("TODO implement tail calls for aarch64", .{});
     const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
     const callee = pl_op.operand;
@@ -4422,7 +4422,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
     return bt.finishAir(result);
 }
 
-fn airRet(self: *Self, inst: Air.Inst.Index) !void {
+fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -4455,7 +4455,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, .dead, .{ un_op, .none, .none });
 }
 
-fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void {
+fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -4499,7 +4499,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, .dead, .{ un_op, .none, .none });
 }
 
-fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
+fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) InnerError!void {
     const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
     const lhs_ty = self.typeOf(bin_op.lhs);
 
@@ -4597,12 +4597,12 @@ fn cmp(
     }
 }
 
-fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void {
+fn airCmpVector(self: *Self, inst: Air.Inst.Index) InnerError!void {
     _ = inst;
     return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch});
 }
 
-fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
+fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const operand = try self.resolveInst(un_op);
     _ = operand;
@@ -4610,7 +4610,7 @@ fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
+fn airDbgStmt(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const dbg_stmt = self.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
 
     _ = try self.addInst(.{
@@ -4624,7 +4624,7 @@ fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAirBookkeeping();
 }
 
-fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void {
+fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
@@ -4635,7 +4635,7 @@ fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void {
     try self.lowerBlock(inst, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]));
 }
 
-fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void {
+fn airDbgVar(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
     const operand = pl_op.operand;
     const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
@@ -4686,7 +4686,7 @@ fn condBr(self: *Self, condition: MCValue) !Mir.Inst.Index {
     }
 }
 
-fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
+fn airCondBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
     const cond = try self.resolveInst(pl_op.operand);
     const extra = self.air.extraData(Air.CondBr, pl_op.payload);
@@ -4919,7 +4919,7 @@ fn isNonErr(
     }
 }
 
-fn airIsNull(self: *Self, inst: Air.Inst.Index) !void {
+fn airIsNull(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const operand = try self.resolveInst(un_op);
@@ -4930,7 +4930,7 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -4947,7 +4947,7 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void {
+fn airIsNonNull(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const operand = try self.resolveInst(un_op);
@@ -4958,7 +4958,7 @@ fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -4975,7 +4975,7 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airIsErr(self: *Self, inst: Air.Inst.Index) !void {
+fn airIsErr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_bind: ReadArg.Bind = .{ .inst = un_op };
@@ -4986,7 +4986,7 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -5003,7 +5003,7 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void {
+fn airIsNonErr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
         const error_union_bind: ReadArg.Bind = .{ .inst = un_op };
@@ -5014,7 +5014,7 @@ fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -5031,7 +5031,7 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
+fn airLoop(self: *Self, inst: Air.Inst.Index) InnerError!void {
     // A loop is a setup to be able to jump back to the beginning.
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const loop = self.air.extraData(Air.Block, ty_pl.payload);
@@ -5052,7 +5052,7 @@ fn jump(self: *Self, inst: Mir.Inst.Index) !void {
     });
 }
 
-fn airBlock(self: *Self, inst: Air.Inst.Index) !void {
+fn airBlock(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Block, ty_pl.payload);
     try self.lowerBlock(inst, @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]));
@@ -5090,7 +5090,7 @@ fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) !
     return self.finishAir(inst, result, .{ .none, .none, .none });
 }
 
-fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
+fn airSwitch(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const switch_br = self.air.unwrapSwitch(inst);
     const condition_ty = self.typeOf(switch_br.operand);
     const liveness = try self.liveness.getSwitchBr(
@@ -5224,7 +5224,7 @@ fn performReloc(self: *Self, inst: Mir.Inst.Index) !void {
     }
 }
 
-fn airBr(self: *Self, inst: Air.Inst.Index) !void {
+fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const branch = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
     try self.br(branch.block_inst, branch.operand);
     return self.finishAir(inst, .dead, .{ branch.operand, .none, .none });
@@ -5268,7 +5268,7 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void {
     }));
 }
 
-fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
+fn airAsm(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Asm, ty_pl.payload);
     const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
@@ -5601,7 +5601,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                 .tag = .ldr_ptr_stack,
                 .data = .{ .load_store_stack = .{
                     .rt = reg,
-                    .offset = @as(u32, @intCast(off)),
+                    .offset = @intCast(off),
                 } },
             });
         },
@@ -5617,13 +5617,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
         .immediate => |x| {
             _ = try self.addInst(.{
                 .tag = .movz,
-                .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @as(u16, @truncate(x)) } },
+                .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(x) } },
             });
 
             if (x & 0x0000_0000_ffff_0000 != 0) {
                 _ = try self.addInst(.{
                     .tag = .movk,
-                    .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @as(u16, @truncate(x >> 16)), .hw = 1 } },
+                    .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(x >> 16), .hw = 1 } },
                 });
             }
 
@@ -5631,13 +5631,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                 if (x & 0x0000_ffff_0000_0000 != 0) {
                     _ = try self.addInst(.{
                         .tag = .movk,
-                        .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @as(u16, @truncate(x >> 32)), .hw = 2 } },
+                        .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(x >> 32), .hw = 2 } },
                     });
                 }
                 if (x & 0xffff_0000_0000_0000 != 0) {
                     _ = try self.addInst(.{
                         .tag = .movk,
-                        .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @as(u16, @truncate(x >> 48)), .hw = 3 } },
+                        .data = .{ .r_imm16_sh = .{ .rd = reg, .imm16 = @truncate(x >> 48), .hw = 3 } },
                     });
                 }
             }
@@ -5709,7 +5709,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                         .tag = tag,
                         .data = .{ .load_store_stack = .{
                             .rt = reg,
-                            .offset = @as(u32, @intCast(off)),
+                            .offset = @intCast(off),
                         } },
                     });
                 },
@@ -5733,7 +5733,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                         .tag = tag,
                         .data = .{ .load_store_stack = .{
                             .rt = reg,
-                            .offset = @as(u32, @intCast(off)),
+                            .offset = @intCast(off),
                         } },
                     });
                 },
@@ -5918,13 +5918,13 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I
     }
 }
 
-fn airIntFromPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airIntFromPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const result = try self.resolveInst(un_op);
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airBitCast(self: *Self, inst: Air.Inst.Index) !void {
+fn airBitCast(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result = if (self.liveness.isUnused(inst)) .dead else result: {
         const operand = try self.resolveInst(ty_op.operand);
@@ -5945,7 +5945,7 @@ fn airBitCast(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void {
+fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
@@ -5963,7 +5963,7 @@ fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airFloatFromInt(self: *Self, inst: Air.Inst.Index) !void {
+fn airFloatFromInt(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFloatFromInt for {}", .{
         self.target.cpu.arch,
@@ -5971,7 +5971,7 @@ fn airFloatFromInt(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airIntFromFloat(self: *Self, inst: Air.Inst.Index) !void {
+fn airIntFromFloat(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airIntFromFloat for {}", .{
         self.target.cpu.arch,
@@ -5979,7 +5979,7 @@ fn airIntFromFloat(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void {
+fn airCmpxchg(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Block, ty_pl.payload);
     _ = extra;
@@ -5989,23 +5989,23 @@ fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void {
     });
 }
 
-fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void {
+fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) InnerError!void {
     _ = inst;
     return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch});
 }
 
-fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void {
+fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) InnerError!void {
     _ = inst;
     return self.fail("TODO implement airAtomicLoad for {}", .{self.target.cpu.arch});
 }
 
-fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void {
+fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) InnerError!void {
     _ = inst;
     _ = order;
     return self.fail("TODO implement airAtomicStore for {}", .{self.target.cpu.arch});
 }
 
-fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) !void {
+fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) InnerError!void {
     _ = inst;
     if (safety) {
         // TODO if the value is undef, write 0xaa bytes to dest
@@ -6015,12 +6015,12 @@ fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) !void {
     return self.fail("TODO implement airMemset for {}", .{self.target.cpu.arch});
 }
 
-fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
+fn airMemcpy(self: *Self, inst: Air.Inst.Index) InnerError!void {
     _ = inst;
     return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
 }
 
-fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
+fn airTagName(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const operand = try self.resolveInst(un_op);
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
@@ -6030,7 +6030,7 @@ fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
+fn airErrorName(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
     const operand = try self.resolveInst(un_op);
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
@@ -6040,33 +6040,33 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ un_op, .none, .none });
 }
 
-fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
+fn airSplat(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
 }
 
-fn airSelect(self: *Self, inst: Air.Inst.Index) !void {
+fn airSelect(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
     const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSelect for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ pl_op.operand, extra.lhs, extra.rhs });
 }
 
-fn airShuffle(self: *Self, inst: Air.Inst.Index) !void {
+fn airShuffle(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airShuffle for {}", .{self.target.cpu.arch});
     return self.finishAir(inst, result, .{ extra.a, extra.b, .none });
 }
 
-fn airReduce(self: *Self, inst: Air.Inst.Index) !void {
+fn airReduce(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const reduce = self.air.instructions.items(.data)[@intFromEnum(inst)].reduce;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airReduce for aarch64", .{});
     return self.finishAir(inst, result, .{ reduce.operand, .none, .none });
 }
 
-fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void {
+fn airAggregateInit(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const zcu = pt.zcu;
     const vector_ty = self.typeOfIndex(inst);
@@ -6090,19 +6090,19 @@ fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void {
     return bt.finishAir(result);
 }
 
-fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void {
+fn airUnionInit(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
     _ = extra;
     return self.fail("TODO implement airUnionInit for aarch64", .{});
 }
 
-fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void {
+fn airPrefetch(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const prefetch = self.air.instructions.items(.data)[@intFromEnum(inst)].prefetch;
     return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none });
 }
 
-fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void {
+fn airMulAdd(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
     const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
     const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
@@ -6111,7 +6111,7 @@ fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, pl_op.operand });
 }
 
-fn airTry(self: *Self, inst: Air.Inst.Index) !void {
+fn airTry(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const pt = self.pt;
     const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
     const extra = self.air.extraData(Air.Try, pl_op.payload);
@@ -6139,7 +6139,7 @@ fn airTry(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ pl_op.operand, .none, .none });
 }
 
-fn airTryPtr(self: *Self, inst: Air.Inst.Index) !void {
+fn airTryPtr(self: *Self, inst: Air.Inst.Index) InnerError!void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.TryPtr, ty_pl.payload);
     const body = self.air.extra[extra.end..][0..extra.data.body_len];
src/arch/arm/CodeGen.zig
@@ -245,7 +245,7 @@ const DbgInfoReloc = struct {
     name: [:0]const u8,
     mcv: MCValue,
 
-    fn genDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
+    fn genDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void {
         switch (reloc.tag) {
             .arg,
             .dbg_arg_inline,
@@ -259,7 +259,7 @@ const DbgInfoReloc = struct {
         }
     }
 
-    fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
+    fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void {
         switch (function.debug_output) {
             .dwarf => |dw| {
                 const loc: link.File.Dwarf.Loc = switch (reloc.mcv) {
@@ -287,7 +287,7 @@ const DbgInfoReloc = struct {
         }
     }
 
-    fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
+    fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) CodeGenError!void {
         switch (function.debug_output) {
             .dwarf => |dw| {
                 const loc: link.File.Dwarf.Loc = switch (reloc.mcv) {
src/arch/wasm/CodeGen.zig
@@ -6,7 +6,6 @@ const assert = std.debug.assert;
 const testing = std.testing;
 const leb = std.leb;
 const mem = std.mem;
-const wasm = std.wasm;
 const log = std.log.scoped(.codegen);
 
 const codegen = @import("../../codegen.zig");
@@ -55,22 +54,19 @@ const WValue = union(enum) {
     float32: f32,
     /// A constant 64bit float value
     float64: f64,
-    /// A value that represents a pointer to the data section
-    /// Note: The value contains the symbol index, rather than the actual address
-    /// as we use this to perform the relocation.
-    memory: u32,
+    /// A value that represents a pointer to the data section.
+    memory: InternPool.Index,
     /// A value that represents a parent pointer and an offset
     /// from that pointer. i.e. when slicing with constant values.
     memory_offset: struct {
-        /// The symbol of the parent pointer
-        pointer: u32,
+        pointer: InternPool.Index,
         /// Offset will be set as addend when relocating
         offset: u32,
     },
     /// Represents a function pointer
     /// In wasm function pointers are indexes into a function table,
     /// rather than an address in the data section.
-    function_index: u32,
+    function_index: InternPool.Index,
     /// Offset from the bottom of the virtual stack, with the offset
     /// pointing to where the value lives.
     stack_offset: struct {
@@ -119,7 +115,7 @@ const WValue = union(enum) {
         if (local_value < reserved + 2) return; // reserved locals may never be re-used. Also accounts for 2 stack locals.
 
         const index = local_value - reserved;
-        const valtype = @as(wasm.Valtype, @enumFromInt(gen.locals.items[index]));
+        const valtype: std.wasm.Valtype = @enumFromInt(gen.locals.items[index]);
         switch (valtype) {
             .i32 => gen.free_locals_i32.append(gen.gpa, local_value) catch return, // It's ok to fail any of those, a new local can be allocated instead
             .i64 => gen.free_locals_i64.append(gen.gpa, local_value) catch return,
@@ -132,8 +128,6 @@ const WValue = union(enum) {
     }
 };
 
-/// Wasm ops, but without input/output/signedness information
-/// Used for `buildOpcode`
 const Op = enum {
     @"unreachable",
     nop,
@@ -200,70 +194,42 @@ const Op = enum {
     extend,
 };
 
-/// Contains the settings needed to create an `Opcode` using `buildOpcode`.
-///
-/// The fields correspond to the opcode name. Here is an example
-///          i32_trunc_f32_s
-///          ^   ^     ^   ^
-///          |   |     |   |
-///   valtype1   |     |   |
-///     = .i32   |     |   |
-///              |     |   |
-///             op     |   |
-///       = .trunc     |   |
-///                    |   |
-///             valtype2   |
-///               = .f32   |
-///                        |
-///                width   |
-///               = null   |
-///                        |
-///                   signed
-///                   = true
-///
-/// There can be missing fields, here are some more examples:
-///   i64_load8_u
-///     --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false }
-///   i32_mul
-///     --> .{ .valtype1 = .i32, .op = .trunc }
-///   nop
-///     --> .{ .op = .nop }
 const OpcodeBuildArguments = struct {
     /// First valtype in the opcode (usually represents the type of the output)
-    valtype1: ?wasm.Valtype = null,
+    valtype1: ?std.wasm.Valtype = null,
     /// The operation (e.g. call, unreachable, div, min, sqrt, etc.)
     op: Op,
     /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s)
     width: ?u8 = null,
     /// Second valtype in the opcode name (usually represents the type of the input)
-    valtype2: ?wasm.Valtype = null,
+    valtype2: ?std.wasm.Valtype = null,
     /// Signedness of the op
     signedness: ?std.builtin.Signedness = null,
 };
 
-/// Helper function that builds an Opcode given the arguments needed
-fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
+/// TODO: deprecated, should be split up per tag.
+fn buildOpcode(args: OpcodeBuildArguments) std.wasm.Opcode {
     switch (args.op) {
-        .@"unreachable" => return .@"unreachable",
-        .nop => return .nop,
-        .block => return .block,
-        .loop => return .loop,
-        .@"if" => return .@"if",
-        .@"else" => return .@"else",
-        .end => return .end,
-        .br => return .br,
-        .br_if => return .br_if,
-        .br_table => return .br_table,
-        .@"return" => return .@"return",
-        .call => return .call,
-        .call_indirect => return .call_indirect,
-        .drop => return .drop,
-        .select => return .select,
-        .local_get => return .local_get,
-        .local_set => return .local_set,
-        .local_tee => return .local_tee,
-        .global_get => return .global_get,
-        .global_set => return .global_set,
+        .@"unreachable" => unreachable,
+        .nop => unreachable,
+        .block => unreachable,
+        .loop => unreachable,
+        .@"if" => unreachable,
+        .@"else" => unreachable,
+        .end => unreachable,
+        .br => unreachable,
+        .br_if => unreachable,
+        .br_table => unreachable,
+        .@"return" => unreachable,
+        .call => unreachable,
+        .call_indirect => unreachable,
+        .drop => unreachable,
+        .select => unreachable,
+        .local_get => unreachable,
+        .local_set => unreachable,
+        .local_tee => unreachable,
+        .global_get => unreachable,
+        .global_set => unreachable,
 
         .load => if (args.width) |width| switch (width) {
             8 => switch (args.valtype1.?) {
@@ -626,11 +592,11 @@ test "Wasm - buildOpcode" {
     const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed });
     const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 });
 
-    try testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const);
-    try testing.expectEqual(@as(wasm.Opcode, .end), end);
-    try testing.expectEqual(@as(wasm.Opcode, .local_get), local_get);
-    try testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s);
-    try testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
+    try testing.expectEqual(@as(std.wasm.Opcode, .i32_const), i32_const);
+    try testing.expectEqual(@as(std.wasm.Opcode, .end), end);
+    try testing.expectEqual(@as(std.wasm.Opcode, .local_get), local_get);
+    try testing.expectEqual(@as(std.wasm.Opcode, .i64_extend32_s), i64_extend32_s);
+    try testing.expectEqual(@as(std.wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
 }
 
 /// Hashmap to store generated `WValue` for each `Air.Inst.Ref`
@@ -806,13 +772,7 @@ fn resolveInst(func: *CodeGen, ref: Air.Inst.Ref) InnerError!WValue {
     // In the other cases, we will simply lower the constant to a value that fits
     // into a single local (such as a pointer, integer, bool, etc).
     const result: WValue = if (isByRef(ty, pt, func.target.*))
-        switch (try func.bin_file.lowerUav(pt, val.toIntern(), .none, func.src_loc)) {
-            .mcv => |mcv| .{ .memory = mcv.load_symbol },
-            .fail => |err_msg| {
-                func.err_msg = err_msg;
-                return error.CodegenFail;
-            },
-        }
+        .{ .memory = val.toIntern() }
     else
         try func.lowerConstant(val, ty);
 
@@ -919,7 +879,7 @@ fn addTag(func: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
     try func.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
 }
 
-fn addExtended(func: *CodeGen, opcode: wasm.MiscOpcode) error{OutOfMemory}!void {
+fn addExtended(func: *CodeGen, opcode: std.wasm.MiscOpcode) error{OutOfMemory}!void {
     const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
     try func.mir_extra.append(func.gpa, @intFromEnum(opcode));
     try func.addInst(.{ .tag = .misc_prefix, .data = .{ .payload = extra_index } });
@@ -929,6 +889,10 @@ fn addLabel(func: *CodeGen, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!vo
     try func.addInst(.{ .tag = tag, .data = .{ .label = label } });
 }
 
+fn addCallTagName(func: *CodeGen, ip_index: InternPool.Index) error{OutOfMemory}!void {
+    try func.addInst(.{ .tag = .call_tag_name, .data = .{ .ip_index = ip_index } });
+}
+
 /// Accepts an unsigned 32bit integer rather than a signed integer to
 /// prevent us from having to bitcast multiple times as most values
 /// within codegen are represented as unsigned rather than signed.
@@ -950,7 +914,7 @@ fn addImm128(func: *CodeGen, index: u32) error{OutOfMemory}!void {
     const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
     // tag + 128bit value
     try func.mir_extra.ensureUnusedCapacity(func.gpa, 5);
-    func.mir_extra.appendAssumeCapacity(std.wasm.simdOpcode(.v128_const));
+    func.mir_extra.appendAssumeCapacity(@intFromEnum(std.wasm.SimdOpcode.v128_const));
     func.mir_extra.appendSliceAssumeCapacity(@alignCast(mem.bytesAsSlice(u32, &simd_values)));
     try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
 }
@@ -968,15 +932,15 @@ fn addMemArg(func: *CodeGen, tag: Mir.Inst.Tag, mem_arg: Mir.MemArg) error{OutOf
 
 /// Inserts an instruction from the 'atomics' feature which accesses wasm's linear memory dependent on the
 /// given `tag`.
-fn addAtomicMemArg(func: *CodeGen, tag: wasm.AtomicsOpcode, mem_arg: Mir.MemArg) error{OutOfMemory}!void {
-    const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = wasm.atomicsOpcode(tag) }));
+fn addAtomicMemArg(func: *CodeGen, tag: std.wasm.AtomicsOpcode, mem_arg: Mir.MemArg) error{OutOfMemory}!void {
+    const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = @intFromEnum(tag) }));
     _ = try func.addExtra(mem_arg);
     try func.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } });
 }
 
 /// Helper function to emit atomic mir opcodes.
-fn addAtomicTag(func: *CodeGen, tag: wasm.AtomicsOpcode) error{OutOfMemory}!void {
-    const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = wasm.atomicsOpcode(tag) }));
+fn addAtomicTag(func: *CodeGen, tag: std.wasm.AtomicsOpcode) error{OutOfMemory}!void {
+    const extra_index = try func.addExtra(@as(struct { val: u32 }, .{ .val = @intFromEnum(tag) }));
     try func.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } });
 }
 
@@ -1003,7 +967,7 @@ fn addExtraAssumeCapacity(func: *CodeGen, extra: anytype) error{OutOfMemory}!u32
 }
 
 /// Using a given `Type`, returns the corresponding valtype for .auto callconv
-fn typeToValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) wasm.Valtype {
+fn typeToValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) std.wasm.Valtype {
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
     return switch (ty.zigTypeTag(zcu)) {
@@ -1044,7 +1008,7 @@ fn typeToValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) wasm.Valtype {
 
 /// Using a given `Type`, returns the byte representation of its wasm value type
 fn genValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) u8 {
-    return wasm.valtype(typeToValtype(ty, pt, target));
+    return @intFromEnum(typeToValtype(ty, pt, target));
 }
 
 /// Using a given `Type`, returns the corresponding wasm value type
@@ -1052,7 +1016,7 @@ fn genValtype(ty: Type, pt: Zcu.PerThread, target: std.Target) u8 {
 /// with no return type
 fn genBlockType(ty: Type, pt: Zcu.PerThread, target: std.Target) u8 {
     return switch (ty.ip_index) {
-        .void_type, .noreturn_type => wasm.block_empty,
+        .void_type, .noreturn_type => std.wasm.block_empty,
         else => genValtype(ty, pt, target),
     };
 }
@@ -1141,35 +1105,34 @@ fn ensureAllocLocal(func: *CodeGen, ty: Type) InnerError!WValue {
     return .{ .local = .{ .value = initial_index, .references = 1 } };
 }
 
-/// Generates a `wasm.Type` from a given function type.
-/// Memory is owned by the caller.
 fn genFunctype(
-    gpa: Allocator,
+    wasm: *link.File.Wasm,
     cc: std.builtin.CallingConvention,
     params: []const InternPool.Index,
     return_type: Type,
     pt: Zcu.PerThread,
     target: std.Target,
-) !wasm.Type {
+) !link.File.Wasm.FunctionType.Index {
     const zcu = pt.zcu;
-    var temp_params = std.ArrayList(wasm.Valtype).init(gpa);
-    defer temp_params.deinit();
-    var returns = std.ArrayList(wasm.Valtype).init(gpa);
-    defer returns.deinit();
+    const gpa = zcu.gpa;
+    var temp_params: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty;
+    defer temp_params.deinit(gpa);
+    var returns: std.ArrayListUnmanaged(std.wasm.Valtype) = .empty;
+    defer returns.deinit(gpa);
 
     if (firstParamSRet(cc, return_type, pt, target)) {
-        try temp_params.append(.i32); // memory address is always a 32-bit handle
+        try temp_params.append(gpa, .i32); // memory address is always a 32-bit handle
     } else if (return_type.hasRuntimeBitsIgnoreComptime(zcu)) {
         if (cc == .wasm_watc) {
             const res_classes = abi.classifyType(return_type, zcu);
             assert(res_classes[0] == .direct and res_classes[1] == .none);
             const scalar_type = abi.scalarType(return_type, zcu);
-            try returns.append(typeToValtype(scalar_type, pt, target));
+            try returns.append(gpa, typeToValtype(scalar_type, pt, target));
         } else {
-            try returns.append(typeToValtype(return_type, pt, target));
+            try returns.append(gpa, typeToValtype(return_type, pt, target));
         }
     } else if (return_type.isError(zcu)) {
-        try returns.append(.i32);
+        try returns.append(gpa, .i32);
     }
 
     // param types
@@ -1183,24 +1146,24 @@ fn genFunctype(
                 if (param_classes[1] == .none) {
                     if (param_classes[0] == .direct) {
                         const scalar_type = abi.scalarType(param_type, zcu);
-                        try temp_params.append(typeToValtype(scalar_type, pt, target));
+                        try temp_params.append(gpa, typeToValtype(scalar_type, pt, target));
                     } else {
-                        try temp_params.append(typeToValtype(param_type, pt, target));
+                        try temp_params.append(gpa, typeToValtype(param_type, pt, target));
                     }
                 } else {
                     // i128/f128
-                    try temp_params.append(.i64);
-                    try temp_params.append(.i64);
+                    try temp_params.append(gpa, .i64);
+                    try temp_params.append(gpa, .i64);
                 }
             },
-            else => try temp_params.append(typeToValtype(param_type, pt, target)),
+            else => try temp_params.append(gpa, typeToValtype(param_type, pt, target)),
         }
     }
 
-    return wasm.Type{
-        .params = try temp_params.toOwnedSlice(),
-        .returns = try returns.toOwnedSlice(),
-    };
+    return wasm.addFuncType(.{
+        .params = try wasm.internValtypeList(temp_params.items),
+        .returns = try wasm.internValtypeList(returns.items),
+    });
 }
 
 pub fn generate(
@@ -1244,14 +1207,13 @@ pub fn generate(
 }
 
 fn genFunc(func: *CodeGen) InnerError!void {
+    const wasm = func.bin_file;
     const pt = func.pt;
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
     const fn_ty = zcu.navValue(func.owner_nav).typeOf(zcu);
     const fn_info = zcu.typeToFunc(fn_ty).?;
-    var func_type = try genFunctype(func.gpa, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), pt, func.target.*);
-    defer func_type.deinit(func.gpa);
-    _ = try func.bin_file.storeNavType(func.owner_nav, func_type);
+    const fn_ty_index = try genFunctype(wasm, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), pt, func.target.*);
 
     var cc_result = try func.resolveCallingConventionValues(fn_ty);
     defer cc_result.deinit(func.gpa);
@@ -1273,7 +1235,8 @@ fn genFunc(func: *CodeGen) InnerError!void {
 
     // In case we have a return value, but the last instruction is a noreturn (such as a while loop)
     // we emit an unreachable instruction to tell the stack validator that part will never be reached.
-    if (func_type.returns.len != 0 and func.air.instructions.len > 0) {
+    const returns = fn_ty_index.ptr(wasm).returns.slice(wasm);
+    if (returns.len != 0 and func.air.instructions.len > 0) {
         const inst: Air.Inst.Index = @enumFromInt(func.air.instructions.len - 1);
         const last_inst_ty = func.typeOfIndex(inst);
         if (!last_inst_ty.hasRuntimeBitsIgnoreComptime(zcu) or last_inst_ty.isNoReturn(zcu)) {
@@ -1291,7 +1254,7 @@ fn genFunc(func: *CodeGen) InnerError!void {
         var prologue = std.ArrayList(Mir.Inst).init(func.gpa);
         defer prologue.deinit();
 
-        const sp = @intFromEnum(func.bin_file.zig_object.?.stack_pointer_sym);
+        const sp = @intFromEnum(wasm.zig_object.?.stack_pointer_sym);
         // load stack pointer
         try prologue.append(.{ .tag = .global_get, .data = .{ .label = sp } });
         // store stack pointer so we can restore it when we return from the function
@@ -1328,7 +1291,7 @@ fn genFunc(func: *CodeGen) InnerError!void {
 
     var emit: Emit = .{
         .mir = mir,
-        .bin_file = func.bin_file,
+        .bin_file = wasm,
         .code = func.code,
         .locals = func.locals.items,
         .owner_nav = func.owner_nav,
@@ -1643,8 +1606,8 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {
     try func.addLabel(.local_set, offset.local.value);
 
     // outer block to jump to when loop is done
-    try func.startBlock(.block, wasm.block_empty);
-    try func.startBlock(.loop, wasm.block_empty);
+    try func.startBlock(.block, std.wasm.block_empty);
+    try func.startBlock(.loop, std.wasm.block_empty);
 
     // loop condition (offset == length -> break)
     {
@@ -1792,7 +1755,7 @@ const SimdStoreStrategy = enum {
 /// features are enabled, the function will return `.direct`. This would allow to store
 /// it using a instruction, rather than an unrolled version.
 fn determineSimdStoreStrategy(ty: Type, zcu: *Zcu, target: std.Target) SimdStoreStrategy {
-    std.debug.assert(ty.zigTypeTag(zcu) == .vector);
+    assert(ty.zigTypeTag(zcu) == .vector);
     if (ty.bitSize(zcu) != 128) return .unrolled;
     const hasFeature = std.Target.wasm.featureSetHas;
     const features = target.cpu.features;
@@ -2186,10 +2149,11 @@ fn airRetLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
 }
 
 fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) InnerError!void {
+    const wasm = func.bin_file;
     if (modifier == .always_tail) return func.fail("TODO implement tail calls for wasm", .{});
     const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
     const extra = func.air.extraData(Air.Call, pl_op.payload);
-    const args = @as([]const Air.Inst.Ref, @ptrCast(func.air.extra[extra.end..][0..extra.data.args_len]));
+    const args: []const Air.Inst.Ref = @ptrCast(func.air.extra[extra.end..][0..extra.data.args_len]);
     const ty = func.typeOf(pl_op.operand);
 
     const pt = func.pt;
@@ -2208,43 +2172,14 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif
         const func_val = (try func.air.value(pl_op.operand, pt)) orelse break :blk null;
 
         switch (ip.indexToKey(func_val.toIntern())) {
-            .func => |function| {
-                _ = try func.bin_file.getOrCreateAtomForNav(pt, function.owner_nav);
-                break :blk function.owner_nav;
-            },
-            .@"extern" => |@"extern"| {
-                const ext_nav = ip.getNav(@"extern".owner_nav);
-                const ext_info = zcu.typeToFunc(Type.fromInterned(@"extern".ty)).?;
-                var func_type = try genFunctype(
-                    func.gpa,
-                    ext_info.cc,
-                    ext_info.param_types.get(ip),
-                    Type.fromInterned(ext_info.return_type),
-                    pt,
-                    func.target.*,
-                );
-                defer func_type.deinit(func.gpa);
-                const atom_index = try func.bin_file.getOrCreateAtomForNav(pt, @"extern".owner_nav);
-                const atom = func.bin_file.getAtomPtr(atom_index);
-                const type_index = try func.bin_file.storeNavType(@"extern".owner_nav, func_type);
-                try func.bin_file.addOrUpdateImport(
-                    ext_nav.name.toSlice(ip),
-                    atom.sym_index,
-                    @"extern".lib_name.toSlice(ip),
-                    type_index,
-                );
-                break :blk @"extern".owner_nav;
-            },
+            inline .func, .@"extern" => |x| break :blk x.owner_nav,
             .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) {
-                .nav => |nav| {
-                    _ = try func.bin_file.getOrCreateAtomForNav(pt, nav);
-                    break :blk nav;
-                },
+                .nav => |nav| break :blk nav,
                 else => {},
             },
             else => {},
         }
-        return func.fail("Expected a function, but instead found '{s}'", .{@tagName(ip.indexToKey(func_val.toIntern()))});
+        return func.fail("unable to lower callee to a function index", .{});
     };
 
     const sret: WValue = if (first_param_sret) blk: {
@@ -2262,21 +2197,17 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif
         try func.lowerArg(zcu.typeToFunc(fn_ty).?.cc, arg_ty, arg_val);
     }
 
-    if (callee) |direct| {
-        const atom_index = func.bin_file.zig_object.?.navs.get(direct).?.atom;
-        try func.addLabel(.call, @intFromEnum(func.bin_file.getAtom(atom_index).sym_index));
+    if (callee) |nav_index| {
+        try func.addNav(.call_nav, nav_index);
     } else {
         // in this case we call a function pointer
         // so load its value onto the stack
-        std.debug.assert(ty.zigTypeTag(zcu) == .pointer);
+        assert(ty.zigTypeTag(zcu) == .pointer);
         const operand = try func.resolveInst(pl_op.operand);
         try func.emitWValue(operand);
 
-        var fn_type = try genFunctype(func.gpa, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), pt, func.target.*);
-        defer fn_type.deinit(func.gpa);
-
-        const fn_type_index = try func.bin_file.zig_object.?.putOrGetFuncType(func.gpa, fn_type);
-        try func.addLabel(.call_indirect, fn_type_index);
+        const fn_type_index = try genFunctype(wasm, fn_info.cc, fn_info.param_types.get(ip), Type.fromInterned(fn_info.return_type), pt, func.target.*);
+        try func.addLabel(.call_indirect, @intFromEnum(fn_type_index));
     }
 
     const result_value = result_value: {
@@ -2418,7 +2349,7 @@ fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerE
                 const extra_index: u32 = @intCast(func.mir_extra.items.len);
                 // stores as := opcode, offset, alignment (opcode::memarg)
                 try func.mir_extra.appendSlice(func.gpa, &[_]u32{
-                    std.wasm.simdOpcode(.v128_store),
+                    @intFromEnum(std.wasm.SimdOpcode.v128_store),
                     offset + lhs.offset(),
                     @intCast(ty.abiAlignment(zcu).toByteUnits() orelse 0),
                 });
@@ -2533,7 +2464,7 @@ fn load(func: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValu
         const extra_index = @as(u32, @intCast(func.mir_extra.items.len));
         // stores as := opcode, offset, alignment (opcode::memarg)
         try func.mir_extra.appendSlice(func.gpa, &[_]u32{
-            std.wasm.simdOpcode(.v128_load),
+            @intFromEnum(std.wasm.SimdOpcode.v128_load),
             offset + operand.offset(),
             @intCast(ty.abiAlignment(zcu).toByteUnits().?),
         });
@@ -2664,7 +2595,7 @@ fn binOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!
         }
     }
 
-    const opcode: wasm.Opcode = buildOpcode(.{
+    const opcode: std.wasm.Opcode = buildOpcode(.{
         .op = op,
         .valtype1 = typeToValtype(ty, pt, func.target.*),
         .signedness = if (ty.isSignedInt(zcu)) .signed else .unsigned,
@@ -2988,7 +2919,7 @@ fn floatNeg(func: *CodeGen, ty: Type, arg: WValue) InnerError!WValue {
         },
         32, 64 => {
             try func.emitWValue(arg);
-            const val_type: wasm.Valtype = if (float_bits == 32) .f32 else .f64;
+            const val_type: std.wasm.Valtype = if (float_bits == 32) .f32 else .f64;
             const opcode = buildOpcode(.{ .op = .neg, .valtype1 = val_type });
             try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
             return .stack;
@@ -3197,20 +3128,14 @@ fn lowerUavRef(
         return .{ .imm32 = 0xaaaaaaaa };
     }
 
-    const decl_align = zcu.intern_pool.indexToKey(uav.orig_ty).ptr_type.flags.alignment;
-    const res = try func.bin_file.lowerUav(pt, uav.val, decl_align, func.src_loc);
-    const target_sym_index = switch (res) {
-        .mcv => |mcv| mcv.load_symbol,
-        .fail => |err_msg| {
-            func.err_msg = err_msg;
-            return error.CodegenFail;
-        },
-    };
-    if (is_fn_body) {
-        return .{ .function_index = target_sym_index };
-    } else if (offset == 0) {
-        return .{ .memory = target_sym_index };
-    } else return .{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } };
+    return if (is_fn_body) .{
+        .function_index = uav.val,
+    } else if (offset == 0) .{
+        .memory = uav.val,
+    } else .{ .memory_offset = .{
+        .pointer = uav.val,
+        .offset = offset,
+    } };
 }
 
 fn lowerNavRef(func: *CodeGen, nav_index: InternPool.Nav.Index, offset: u32) InnerError!WValue {
@@ -3334,13 +3259,7 @@ fn lowerConstant(func: *CodeGen, val: Value, ty: Type) InnerError!WValue {
             .f64 => |f64_val| return .{ .float64 = f64_val },
             else => unreachable,
         },
-        .slice => switch (try func.bin_file.lowerUav(pt, val.toIntern(), .none, func.src_loc)) {
-            .mcv => |mcv| return .{ .memory = mcv.load_symbol },
-            .fail => |err_msg| {
-                func.err_msg = err_msg;
-                return error.CodegenFail;
-            },
-        },
+        .slice => return .{ .memory = val.toIntern() },
         .ptr => return func.lowerPtr(val.toIntern(), 0),
         .opt => if (ty.optionalReprIsPayload(zcu)) {
             const pl_ty = ty.optionalChild(zcu);
@@ -3489,12 +3408,12 @@ fn lowerBlock(func: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []cons
     const wasm_block_ty = genBlockType(block_ty, pt, func.target.*);
 
     // if wasm_block_ty is non-empty, we create a register to store the temporary value
-    const block_result: WValue = if (wasm_block_ty != wasm.block_empty) blk: {
+    const block_result: WValue = if (wasm_block_ty != std.wasm.block_empty) blk: {
         const ty: Type = if (isByRef(block_ty, pt, func.target.*)) Type.u32 else block_ty;
         break :blk try func.ensureAllocLocal(ty); // make sure it's a clean local as it may never get overwritten
     } else .none;
 
-    try func.startBlock(.block, wasm.block_empty);
+    try func.startBlock(.block, std.wasm.block_empty);
     // Here we set the current block idx, so breaks know the depth to jump
     // to when breaking out.
     try func.blocks.putNoClobber(func.gpa, inst, .{
@@ -3512,7 +3431,7 @@ fn lowerBlock(func: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []cons
 }
 
 /// appends a new wasm block to the code section and increases the `block_depth` by 1
-fn startBlock(func: *CodeGen, block_tag: wasm.Opcode, valtype: u8) !void {
+fn startBlock(func: *CodeGen, block_tag: std.wasm.Opcode, valtype: u8) !void {
     func.block_depth += 1;
     try func.addInst(.{
         .tag = Mir.Inst.Tag.fromOpcode(block_tag),
@@ -3533,7 +3452,7 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
 
     // result type of loop is always 'noreturn', meaning we can always
     // emit the wasm type 'block_empty'.
-    try func.startBlock(.loop, wasm.block_empty);
+    try func.startBlock(.loop, std.wasm.block_empty);
 
     try func.loops.putNoClobber(func.gpa, inst, func.block_depth);
     defer assert(func.loops.remove(inst));
@@ -3553,7 +3472,7 @@ fn airCondBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const liveness_condbr = func.liveness.getCondBr(inst);
 
     // result type is always noreturn, so use `block_empty` as type.
-    try func.startBlock(.block, wasm.block_empty);
+    try func.startBlock(.block, std.wasm.block_empty);
     // emit the conditional value
     try func.emitWValue(condition);
 
@@ -3632,7 +3551,7 @@ fn cmp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: std.math.CompareO
     try func.lowerToStack(lhs);
     try func.lowerToStack(rhs);
 
-    const opcode: wasm.Opcode = buildOpcode(.{
+    const opcode: std.wasm.Opcode = buildOpcode(.{
         .valtype1 = typeToValtype(ty, pt, func.target.*),
         .op = switch (op) {
             .lt => .lt,
@@ -3674,7 +3593,7 @@ fn cmpFloat(func: *CodeGen, ty: Type, lhs: WValue, rhs: WValue, cmp_op: std.math
         32, 64 => {
             try func.emitWValue(lhs);
             try func.emitWValue(rhs);
-            const val_type: wasm.Valtype = if (float_bits == 32) .f32 else .f64;
+            const val_type: std.wasm.Valtype = if (float_bits == 32) .f32 else .f64;
             const opcode = buildOpcode(.{ .op = op, .valtype1 = val_type });
             try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
             return .stack;
@@ -4053,7 +3972,7 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const pt = func.pt;
     const zcu = pt.zcu;
     // result type is always 'noreturn'
-    const blocktype = wasm.block_empty;
+    const blocktype = std.wasm.block_empty;
     const switch_br = func.air.unwrapSwitch(inst);
     const target = try func.resolveInst(switch_br.operand);
     const target_ty = func.typeOf(switch_br.operand);
@@ -4245,7 +4164,7 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     return func.finishAir(inst, .none, &.{});
 }
 
-fn airIsErr(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!void {
+fn airIsErr(func: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerError!void {
     const pt = func.pt;
     const zcu = pt.zcu;
     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -4467,7 +4386,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro
     } else return func.load(operand, wanted, 0);
 }
 
-fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind: enum { value, ptr }) InnerError!void {
+fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode, op_kind: enum { value, ptr }) InnerError!void {
     const pt = func.pt;
     const zcu = pt.zcu;
     const un_op = func.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -4481,7 +4400,7 @@ fn airIsNull(func: *CodeGen, inst: Air.Inst.Index, opcode: wasm.Opcode, op_kind:
 
 /// For a given type and operand, checks if it's considered `null`.
 /// NOTE: Leaves the result on the stack
-fn isNull(func: *CodeGen, operand: WValue, optional_ty: Type, opcode: wasm.Opcode) InnerError!WValue {
+fn isNull(func: *CodeGen, operand: WValue, optional_ty: Type, opcode: std.wasm.Opcode) InnerError!WValue {
     const pt = func.pt;
     const zcu = pt.zcu;
     try func.emitWValue(operand);
@@ -4967,8 +4886,8 @@ fn memset(func: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue
     try func.addLabel(.local_set, end_ptr.local.value);
 
     // outer block to jump to when loop is done
-    try func.startBlock(.block, wasm.block_empty);
-    try func.startBlock(.loop, wasm.block_empty);
+    try func.startBlock(.block, std.wasm.block_empty);
+    try func.startBlock(.loop, std.wasm.block_empty);
 
     // check for condition for loop end
     try func.emitWValue(new_ptr);
@@ -5022,11 +4941,11 @@ fn airArrayElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
         try func.addTag(.i32_mul);
         try func.addTag(.i32_add);
     } else {
-        std.debug.assert(array_ty.zigTypeTag(zcu) == .vector);
+        assert(array_ty.zigTypeTag(zcu) == .vector);
 
         switch (index) {
             inline .imm32, .imm64 => |lane| {
-                const opcode: wasm.SimdOpcode = switch (elem_ty.bitSize(zcu)) {
+                const opcode: std.wasm.SimdOpcode = switch (elem_ty.bitSize(zcu)) {
                     8 => if (elem_ty.isSignedInt(zcu)) .i8x16_extract_lane_s else .i8x16_extract_lane_u,
                     16 => if (elem_ty.isSignedInt(zcu)) .i16x8_extract_lane_s else .i16x8_extract_lane_u,
                     32 => if (elem_ty.isInt(zcu)) .i32x4_extract_lane else .f32x4_extract_lane,
@@ -5034,7 +4953,7 @@ fn airArrayElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
                     else => unreachable,
                 };
 
-                var operands = [_]u32{ std.wasm.simdOpcode(opcode), @as(u8, @intCast(lane)) };
+                var operands = [_]u32{ @intFromEnum(opcode), @as(u8, @intCast(lane)) };
 
                 try func.emitWValue(array);
 
@@ -5171,10 +5090,10 @@ fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
             // the scalar value onto the stack.
             .stack_offset, .memory, .memory_offset => {
                 const opcode = switch (elem_ty.bitSize(zcu)) {
-                    8 => std.wasm.simdOpcode(.v128_load8_splat),
-                    16 => std.wasm.simdOpcode(.v128_load16_splat),
-                    32 => std.wasm.simdOpcode(.v128_load32_splat),
-                    64 => std.wasm.simdOpcode(.v128_load64_splat),
+                    8 => @intFromEnum(std.wasm.SimdOpcode.v128_load8_splat),
+                    16 => @intFromEnum(std.wasm.SimdOpcode.v128_load16_splat),
+                    32 => @intFromEnum(std.wasm.SimdOpcode.v128_load32_splat),
+                    64 => @intFromEnum(std.wasm.SimdOpcode.v128_load64_splat),
                     else => break :blk, // Cannot make use of simd-instructions
                 };
                 try func.emitWValue(operand);
@@ -5191,10 +5110,10 @@ fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
             },
             .local => {
                 const opcode = switch (elem_ty.bitSize(zcu)) {
-                    8 => std.wasm.simdOpcode(.i8x16_splat),
-                    16 => std.wasm.simdOpcode(.i16x8_splat),
-                    32 => if (elem_ty.isInt(zcu)) std.wasm.simdOpcode(.i32x4_splat) else std.wasm.simdOpcode(.f32x4_splat),
-                    64 => if (elem_ty.isInt(zcu)) std.wasm.simdOpcode(.i64x2_splat) else std.wasm.simdOpcode(.f64x2_splat),
+                    8 => @intFromEnum(std.wasm.SimdOpcode.i8x16_splat),
+                    16 => @intFromEnum(std.wasm.SimdOpcode.i16x8_splat),
+                    32 => if (elem_ty.isInt(zcu)) @intFromEnum(std.wasm.SimdOpcode.i32x4_splat) else @intFromEnum(std.wasm.SimdOpcode.f32x4_splat),
+                    64 => if (elem_ty.isInt(zcu)) @intFromEnum(std.wasm.SimdOpcode.i64x2_splat) else @intFromEnum(std.wasm.SimdOpcode.f64x2_splat),
                     else => break :blk, // Cannot make use of simd-instructions
                 };
                 try func.emitWValue(operand);
@@ -5267,7 +5186,7 @@ fn airShuffle(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
         return func.finishAir(inst, result, &.{ extra.a, extra.b });
     } else {
         var operands = [_]u32{
-            std.wasm.simdOpcode(.i8x16_shuffle),
+            @intFromEnum(std.wasm.SimdOpcode.i8x16_shuffle),
         } ++ [1]u32{undefined} ** 4;
 
         var lanes = mem.asBytes(operands[1..]);
@@ -5538,7 +5457,7 @@ fn cmpOptionals(func: *CodeGen, lhs: WValue, rhs: WValue, operand_ty: Type, op:
     var result = try func.ensureAllocLocal(Type.i32);
     defer result.free(func);
 
-    try func.startBlock(.block, wasm.block_empty);
+    try func.startBlock(.block, std.wasm.block_empty);
     _ = try func.isNull(lhs, operand_ty, .i32_eq);
     _ = try func.isNull(rhs, operand_ty, .i32_eq);
     try func.addTag(.i32_ne); // inverse so we can exit early
@@ -5678,7 +5597,7 @@ fn fpext(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!
             Type.f32,
             &.{operand},
         );
-        std.debug.assert(f32_result == .stack);
+        assert(f32_result == .stack);
 
         if (wanted_bits == 64) {
             try func.addTag(.f64_promote_f32);
@@ -6557,7 +6476,7 @@ fn lowerTry(
 
     if (!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu)) {
         // Block we can jump out of when error is not set
-        try func.startBlock(.block, wasm.block_empty);
+        try func.startBlock(.block, std.wasm.block_empty);
 
         // check if the error tag is set for the error union.
         try func.emitWValue(err_union);
@@ -7162,17 +7081,13 @@ fn callIntrinsic(
     args: []const WValue,
 ) InnerError!WValue {
     assert(param_types.len == args.len);
-    const symbol_index = func.bin_file.getGlobalSymbol(name, null) catch |err| {
-        return func.fail("Could not find or create global symbol '{s}'", .{@errorName(err)});
-    };
-
-    // Always pass over C-ABI
+    const wasm = func.bin_file;
     const pt = func.pt;
     const zcu = pt.zcu;
-    var func_type = try genFunctype(func.gpa, .{ .wasm_watc = .{} }, param_types, return_type, pt, func.target.*);
-    defer func_type.deinit(func.gpa);
-    const func_type_index = try func.bin_file.zig_object.?.putOrGetFuncType(func.gpa, func_type);
-    try func.bin_file.addOrUpdateImport(name, symbol_index, null, func_type_index);
+    const func_type_index = try genFunctype(wasm, .{ .wasm_watc = .{} }, param_types, return_type, pt, func.target.*);
+    const func_index = wasm.getOutputFunction(try wasm.internString(name), func_type_index);
+
+    // Always pass over C-ABI
 
     const want_sret_param = firstParamSRet(.{ .wasm_watc = .{} }, return_type, pt, func.target.*);
     // if we want return as first param, we allocate a pointer to stack,
@@ -7191,7 +7106,7 @@ fn callIntrinsic(
     }
 
     // Actually call our intrinsic
-    try func.addLabel(.call, @intFromEnum(symbol_index));
+    try func.addLabel(.call_func, func_index);
 
     if (!return_type.hasRuntimeBitsIgnoreComptime(zcu)) {
         return .none;
@@ -7210,177 +7125,14 @@ fn airTagName(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const operand = try func.resolveInst(un_op);
     const enum_ty = func.typeOf(un_op);
 
-    const func_sym_index = try func.getTagNameFunction(enum_ty);
-
     const result_ptr = try func.allocStack(func.typeOfIndex(inst));
     try func.lowerToStack(result_ptr);
     try func.emitWValue(operand);
-    try func.addLabel(.call, func_sym_index);
+    try func.addCallTagName(enum_ty.toIntern());
 
     return func.finishAir(inst, result_ptr, &.{un_op});
 }
 
-fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
-    const pt = func.pt;
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-
-    var arena_allocator = std.heap.ArenaAllocator.init(func.gpa);
-    defer arena_allocator.deinit();
-    const arena = arena_allocator.allocator();
-
-    const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{}", .{ip.loadEnumType(enum_ty.toIntern()).name.fmt(ip)});
-
-    // check if we already generated code for this.
-    if (func.bin_file.findGlobalSymbol(func_name)) |loc| {
-        return @intFromEnum(loc.index);
-    }
-
-    const int_tag_ty = enum_ty.intTagType(zcu);
-
-    if (int_tag_ty.bitSize(zcu) > 64) {
-        return func.fail("TODO: Implement @tagName for enums with tag size larger than 64 bits", .{});
-    }
-
-    var relocs = std.ArrayList(link.File.Wasm.Relocation).init(func.gpa);
-    defer relocs.deinit();
-
-    var body_list = std.ArrayList(u8).init(func.gpa);
-    defer body_list.deinit();
-    var writer = body_list.writer();
-
-    // The locals of the function body (always 0)
-    try leb.writeUleb128(writer, @as(u32, 0));
-
-    // outer block
-    try writer.writeByte(std.wasm.opcode(.block));
-    try writer.writeByte(std.wasm.block_empty);
-
-    // TODO: Make switch implementation generic so we can use a jump table for this when the tags are not sparse.
-    // generate an if-else chain for each tag value as well as constant.
-    const tag_names = enum_ty.enumFields(zcu);
-    for (0..tag_names.len) |tag_index| {
-        const tag_name = tag_names.get(ip)[tag_index];
-        const tag_name_len = tag_name.length(ip);
-        // for each tag name, create an unnamed const,
-        // and then get a pointer to its value.
-        const name_ty = try pt.arrayType(.{
-            .len = tag_name_len,
-            .child = .u8_type,
-            .sentinel = .zero_u8,
-        });
-        const name_val = try pt.intern(.{ .aggregate = .{
-            .ty = name_ty.toIntern(),
-            .storage = .{ .bytes = tag_name.toString() },
-        } });
-        const tag_sym_index = switch (try func.bin_file.lowerUav(pt, name_val, .none, func.src_loc)) {
-            .mcv => |mcv| mcv.load_symbol,
-            .fail => |err_msg| {
-                func.err_msg = err_msg;
-                return error.CodegenFail;
-            },
-        };
-
-        // block for this if case
-        try writer.writeByte(std.wasm.opcode(.block));
-        try writer.writeByte(std.wasm.block_empty);
-
-        // get actual tag value (stored in 2nd parameter);
-        try writer.writeByte(std.wasm.opcode(.local_get));
-        try leb.writeUleb128(writer, @as(u32, 1));
-
-        const tag_val = try pt.enumValueFieldIndex(enum_ty, @intCast(tag_index));
-        const tag_value = try func.lowerConstant(tag_val, enum_ty);
-
-        switch (tag_value) {
-            .imm32 => |value| {
-                try writer.writeByte(std.wasm.opcode(.i32_const));
-                try leb.writeIleb128(writer, @as(i32, @bitCast(value)));
-                try writer.writeByte(std.wasm.opcode(.i32_ne));
-            },
-            .imm64 => |value| {
-                try writer.writeByte(std.wasm.opcode(.i64_const));
-                try leb.writeIleb128(writer, @as(i64, @bitCast(value)));
-                try writer.writeByte(std.wasm.opcode(.i64_ne));
-            },
-            else => unreachable,
-        }
-        // if they're not equal, break out of current branch
-        try writer.writeByte(std.wasm.opcode(.br_if));
-        try leb.writeUleb128(writer, @as(u32, 0));
-
-        // store the address of the tagname in the pointer field of the slice
-        // get the address twice so we can also store the length.
-        try writer.writeByte(std.wasm.opcode(.local_get));
-        try leb.writeUleb128(writer, @as(u32, 0));
-        try writer.writeByte(std.wasm.opcode(.local_get));
-        try leb.writeUleb128(writer, @as(u32, 0));
-
-        // get address of tagname and emit a relocation to it
-        if (func.arch() == .wasm32) {
-            const encoded_alignment = @ctz(@as(u32, 4));
-            try writer.writeByte(std.wasm.opcode(.i32_const));
-            try relocs.append(.{
-                .relocation_type = .R_WASM_MEMORY_ADDR_LEB,
-                .offset = @as(u32, @intCast(body_list.items.len)),
-                .index = tag_sym_index,
-            });
-            try writer.writeAll(&[_]u8{0} ** 5); // will be relocated
-
-            // store pointer
-            try writer.writeByte(std.wasm.opcode(.i32_store));
-            try leb.writeUleb128(writer, encoded_alignment);
-            try leb.writeUleb128(writer, @as(u32, 0));
-
-            // store length
-            try writer.writeByte(std.wasm.opcode(.i32_const));
-            try leb.writeUleb128(writer, @as(u32, @intCast(tag_name_len)));
-            try writer.writeByte(std.wasm.opcode(.i32_store));
-            try leb.writeUleb128(writer, encoded_alignment);
-            try leb.writeUleb128(writer, @as(u32, 4));
-        } else {
-            const encoded_alignment = @ctz(@as(u32, 8));
-            try writer.writeByte(std.wasm.opcode(.i64_const));
-            try relocs.append(.{
-                .relocation_type = .R_WASM_MEMORY_ADDR_LEB64,
-                .offset = @as(u32, @intCast(body_list.items.len)),
-                .index = tag_sym_index,
-            });
-            try writer.writeAll(&[_]u8{0} ** 10); // will be relocated
-
-            // store pointer
-            try writer.writeByte(std.wasm.opcode(.i64_store));
-            try leb.writeUleb128(writer, encoded_alignment);
-            try leb.writeUleb128(writer, @as(u32, 0));
-
-            // store length
-            try writer.writeByte(std.wasm.opcode(.i64_const));
-            try leb.writeUleb128(writer, @as(u64, @intCast(tag_name_len)));
-            try writer.writeByte(std.wasm.opcode(.i64_store));
-            try leb.writeUleb128(writer, encoded_alignment);
-            try leb.writeUleb128(writer, @as(u32, 8));
-        }
-
-        // break outside blocks
-        try writer.writeByte(std.wasm.opcode(.br));
-        try leb.writeUleb128(writer, @as(u32, 1));
-
-        // end the block for this case
-        try writer.writeByte(std.wasm.opcode(.end));
-    }
-
-    try writer.writeByte(std.wasm.opcode(.@"unreachable")); // tag value does not have a name
-    // finish outer block
-    try writer.writeByte(std.wasm.opcode(.end));
-    // finish function body
-    try writer.writeByte(std.wasm.opcode(.end));
-
-    const slice_ty = Type.slice_const_u8_sentinel_0;
-    const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty.ip_index}, slice_ty, pt, func.target.*);
-    const sym_index = try func.bin_file.createFunction(func_name, func_type, &body_list, &relocs);
-    return @intFromEnum(sym_index);
-}
-
 fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const pt = func.pt;
     const zcu = pt.zcu;
@@ -7418,11 +7170,11 @@ fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     }
 
     // start block for 'true' branch
-    try func.startBlock(.block, wasm.block_empty);
+    try func.startBlock(.block, std.wasm.block_empty);
     // start block for 'false' branch
-    try func.startBlock(.block, wasm.block_empty);
+    try func.startBlock(.block, std.wasm.block_empty);
     // block for the jump table itself
-    try func.startBlock(.block, wasm.block_empty);
+    try func.startBlock(.block, std.wasm.block_empty);
 
     // lower operand to determine jump table target
     try func.emitWValue(operand);
@@ -7549,7 +7301,7 @@ fn airAtomicLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const ty = func.typeOfIndex(inst);
 
     if (func.useAtomicFeature()) {
-        const tag: wasm.AtomicsOpcode = switch (ty.abiSize(pt.zcu)) {
+        const tag: std.wasm.AtomicsOpcode = switch (ty.abiSize(pt.zcu)) {
             1 => .i32_atomic_load8_u,
             2 => .i32_atomic_load16_u,
             4 => .i32_atomic_load,
@@ -7589,7 +7341,7 @@ fn airAtomicRmw(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
                 const value = try tmp.toLocal(func, ty);
 
                 // create a loop to cmpxchg the new value
-                try func.startBlock(.loop, wasm.block_empty);
+                try func.startBlock(.loop, std.wasm.block_empty);
 
                 try func.emitWValue(ptr);
                 try func.emitWValue(value);
@@ -7639,7 +7391,7 @@ fn airAtomicRmw(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
             else => {
                 try func.emitWValue(ptr);
                 try func.emitWValue(operand);
-                const tag: wasm.AtomicsOpcode = switch (ty.abiSize(zcu)) {
+                const tag: std.wasm.AtomicsOpcode = switch (ty.abiSize(zcu)) {
                     1 => switch (op) {
                         .Xchg => .i32_atomic_rmw8_xchg_u,
                         .Add => .i32_atomic_rmw8_add_u,
@@ -7754,7 +7506,7 @@ fn airAtomicStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const ty = ptr_ty.childType(zcu);
 
     if (func.useAtomicFeature()) {
-        const tag: wasm.AtomicsOpcode = switch (ty.abiSize(zcu)) {
+        const tag: std.wasm.AtomicsOpcode = switch (ty.abiSize(zcu)) {
             1 => .i32_atomic_store8,
             2 => .i32_atomic_store16,
             4 => .i32_atomic_store,
src/arch/wasm/Emit.zig
@@ -3,12 +3,13 @@
 
 const Emit = @This();
 const std = @import("std");
+const leb128 = std.leb;
+
 const Mir = @import("Mir.zig");
 const link = @import("../../link.zig");
 const Zcu = @import("../../Zcu.zig");
 const InternPool = @import("../../InternPool.zig");
 const codegen = @import("../../codegen.zig");
-const leb128 = std.leb;
 
 /// Contains our list of instructions
 mir: Mir,
@@ -254,7 +255,8 @@ fn offset(self: Emit) u32 {
 fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
     @branchHint(.cold);
     std.debug.assert(emit.error_msg == null);
-    const comp = emit.bin_file.base.comp;
+    const wasm = emit.bin_file;
+    const comp = wasm.base.comp;
     const zcu = comp.zcu.?;
     const gpa = comp.gpa;
     emit.error_msg = try Zcu.ErrorMsg.create(gpa, zcu.navSrcLoc(emit.owner_nav), format, args);
@@ -287,7 +289,7 @@ fn emitBrTable(emit: *Emit, inst: Mir.Inst.Index) !void {
     const labels = emit.mir.extra[extra.end..][0..extra.data.length];
     const writer = emit.code.writer();
 
-    try emit.code.append(std.wasm.opcode(.br_table));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.br_table));
     try leb128.writeUleb128(writer, extra.data.length - 1); // Default label is not part of length/depth
     for (labels) |label| {
         try leb128.writeUleb128(writer, label);
@@ -301,7 +303,8 @@ fn emitLabel(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
 }
 
 fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
-    const comp = emit.bin_file.base.comp;
+    const wasm = emit.bin_file;
+    const comp = wasm.base.comp;
     const gpa = comp.gpa;
     const label = emit.mir.instructions.items(.data)[inst].label;
     try emit.code.append(@intFromEnum(tag));
@@ -310,38 +313,38 @@ fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
     const global_offset = emit.offset();
     try emit.code.appendSlice(&buf);
 
-    const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom;
-    const atom = emit.bin_file.getAtomPtr(atom_index);
-    try atom.relocs.append(gpa, .{
+    const zo = wasm.zig_object.?;
+    try zo.relocs.append(gpa, .{
+        .nav_index = emit.nav_index,
         .index = label,
         .offset = global_offset,
-        .relocation_type = .R_WASM_GLOBAL_INDEX_LEB,
+        .tag = .GLOBAL_INDEX_LEB,
     });
 }
 
 fn emitImm32(emit: *Emit, inst: Mir.Inst.Index) !void {
     const value: i32 = emit.mir.instructions.items(.data)[inst].imm32;
-    try emit.code.append(std.wasm.opcode(.i32_const));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.i32_const));
     try leb128.writeIleb128(emit.code.writer(), value);
 }
 
 fn emitImm64(emit: *Emit, inst: Mir.Inst.Index) !void {
     const extra_index = emit.mir.instructions.items(.data)[inst].payload;
     const value = emit.mir.extraData(Mir.Imm64, extra_index);
-    try emit.code.append(std.wasm.opcode(.i64_const));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.i64_const));
     try leb128.writeIleb128(emit.code.writer(), @as(i64, @bitCast(value.data.toU64())));
 }
 
 fn emitFloat32(emit: *Emit, inst: Mir.Inst.Index) !void {
     const value: f32 = emit.mir.instructions.items(.data)[inst].float32;
-    try emit.code.append(std.wasm.opcode(.f32_const));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.f32_const));
     try emit.code.writer().writeInt(u32, @bitCast(value), .little);
 }
 
 fn emitFloat64(emit: *Emit, inst: Mir.Inst.Index) !void {
     const extra_index = emit.mir.instructions.items(.data)[inst].payload;
     const value = emit.mir.extraData(Mir.Float64, extra_index);
-    try emit.code.append(std.wasm.opcode(.f64_const));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.f64_const));
     try emit.code.writer().writeInt(u64, value.data.toU64(), .little);
 }
 
@@ -360,105 +363,99 @@ fn encodeMemArg(mem_arg: Mir.MemArg, writer: anytype) !void {
 }
 
 fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {
-    const comp = emit.bin_file.base.comp;
+    const wasm = emit.bin_file;
+    const comp = wasm.base.comp;
     const gpa = comp.gpa;
     const label = emit.mir.instructions.items(.data)[inst].label;
-    try emit.code.append(std.wasm.opcode(.call));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.call));
     const call_offset = emit.offset();
     var buf: [5]u8 = undefined;
     leb128.writeUnsignedFixed(5, &buf, label);
     try emit.code.appendSlice(&buf);
 
-    if (label != 0) {
-        const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom;
-        const atom = emit.bin_file.getAtomPtr(atom_index);
-        try atom.relocs.append(gpa, .{
-            .offset = call_offset,
-            .index = label,
-            .relocation_type = .R_WASM_FUNCTION_INDEX_LEB,
-        });
-    }
+    const zo = wasm.zig_object.?;
+    try zo.relocs.append(gpa, .{
+        .offset = call_offset,
+        .index = label,
+        .tag = .FUNCTION_INDEX_LEB,
+    });
 }
 
 fn emitCallIndirect(emit: *Emit, inst: Mir.Inst.Index) !void {
+    const wasm = emit.bin_file;
     const type_index = emit.mir.instructions.items(.data)[inst].label;
-    try emit.code.append(std.wasm.opcode(.call_indirect));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.call_indirect));
     // NOTE: If we remove unused function types in the future for incremental
     // linking, we must also emit a relocation for this `type_index`
     const call_offset = emit.offset();
     var buf: [5]u8 = undefined;
     leb128.writeUnsignedFixed(5, &buf, type_index);
     try emit.code.appendSlice(&buf);
-    if (type_index != 0) {
-        const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom;
-        const atom = emit.bin_file.getAtomPtr(atom_index);
-        try atom.relocs.append(emit.bin_file.base.comp.gpa, .{
-            .offset = call_offset,
-            .index = type_index,
-            .relocation_type = .R_WASM_TYPE_INDEX_LEB,
-        });
-    }
+
+    const zo = wasm.zig_object.?;
+    try zo.relocs.append(wasm.base.comp.gpa, .{
+        .offset = call_offset,
+        .index = type_index,
+        .tag = .TYPE_INDEX_LEB,
+    });
+
     try leb128.writeUleb128(emit.code.writer(), @as(u32, 0)); // TODO: Emit relocation for table index
 }
 
 fn emitFunctionIndex(emit: *Emit, inst: Mir.Inst.Index) !void {
-    const comp = emit.bin_file.base.comp;
+    const wasm = emit.bin_file;
+    const comp = wasm.base.comp;
     const gpa = comp.gpa;
     const symbol_index = emit.mir.instructions.items(.data)[inst].label;
-    try emit.code.append(std.wasm.opcode(.i32_const));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.i32_const));
     const index_offset = emit.offset();
     var buf: [5]u8 = undefined;
     leb128.writeUnsignedFixed(5, &buf, symbol_index);
     try emit.code.appendSlice(&buf);
 
-    if (symbol_index != 0) {
-        const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom;
-        const atom = emit.bin_file.getAtomPtr(atom_index);
-        try atom.relocs.append(gpa, .{
-            .offset = index_offset,
-            .index = symbol_index,
-            .relocation_type = .R_WASM_TABLE_INDEX_SLEB,
-        });
-    }
+    const zo = wasm.zig_object.?;
+    try zo.relocs.append(gpa, .{
+        .offset = index_offset,
+        .index = symbol_index,
+        .tag = .TABLE_INDEX_SLEB,
+    });
 }
 
 fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
+    const wasm = emit.bin_file;
     const extra_index = emit.mir.instructions.items(.data)[inst].payload;
     const mem = emit.mir.extraData(Mir.Memory, extra_index).data;
     const mem_offset = emit.offset() + 1;
-    const comp = emit.bin_file.base.comp;
+    const comp = wasm.base.comp;
     const gpa = comp.gpa;
     const target = comp.root_mod.resolved_target.result;
     const is_wasm32 = target.cpu.arch == .wasm32;
     if (is_wasm32) {
-        try emit.code.append(std.wasm.opcode(.i32_const));
+        try emit.code.append(@intFromEnum(std.wasm.Opcode.i32_const));
         var buf: [5]u8 = undefined;
         leb128.writeUnsignedFixed(5, &buf, mem.pointer);
         try emit.code.appendSlice(&buf);
     } else {
-        try emit.code.append(std.wasm.opcode(.i64_const));
+        try emit.code.append(@intFromEnum(std.wasm.Opcode.i64_const));
         var buf: [10]u8 = undefined;
         leb128.writeUnsignedFixed(10, &buf, mem.pointer);
         try emit.code.appendSlice(&buf);
     }
 
-    if (mem.pointer != 0) {
-        const atom_index = emit.bin_file.zig_object.?.navs.get(emit.owner_nav).?.atom;
-        const atom = emit.bin_file.getAtomPtr(atom_index);
-        try atom.relocs.append(gpa, .{
-            .offset = mem_offset,
-            .index = mem.pointer,
-            .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_LEB else .R_WASM_MEMORY_ADDR_LEB64,
-            .addend = @as(i32, @intCast(mem.offset)),
-        });
-    }
+    const zo = wasm.zig_object.?;
+    try zo.relocs.append(gpa, .{
+        .offset = mem_offset,
+        .index = mem.pointer,
+        .tag = if (is_wasm32) .MEMORY_ADDR_LEB else .MEMORY_ADDR_LEB64,
+        .addend = @as(i32, @intCast(mem.offset)),
+    });
 }
 
 fn emitExtended(emit: *Emit, inst: Mir.Inst.Index) !void {
     const extra_index = emit.mir.instructions.items(.data)[inst].payload;
     const opcode = emit.mir.extra[extra_index];
     const writer = emit.code.writer();
-    try emit.code.append(std.wasm.opcode(.misc_prefix));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.misc_prefix));
     try leb128.writeUleb128(writer, opcode);
     switch (@as(std.wasm.MiscOpcode, @enumFromInt(opcode))) {
         // bulk-memory opcodes
@@ -497,7 +494,7 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {
     const extra_index = emit.mir.instructions.items(.data)[inst].payload;
     const opcode = emit.mir.extra[extra_index];
     const writer = emit.code.writer();
-    try emit.code.append(std.wasm.opcode(.simd_prefix));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.simd_prefix));
     try leb128.writeUleb128(writer, opcode);
     switch (@as(std.wasm.SimdOpcode, @enumFromInt(opcode))) {
         .v128_store,
@@ -548,7 +545,7 @@ fn emitAtomic(emit: *Emit, inst: Mir.Inst.Index) !void {
     const extra_index = emit.mir.instructions.items(.data)[inst].payload;
     const opcode = emit.mir.extra[extra_index];
     const writer = emit.code.writer();
-    try emit.code.append(std.wasm.opcode(.atomics_prefix));
+    try emit.code.append(@intFromEnum(std.wasm.Opcode.atomics_prefix));
     try leb128.writeUleb128(writer, opcode);
     switch (@as(std.wasm.AtomicsOpcode, @enumFromInt(opcode))) {
         .i32_atomic_load,
src/arch/wasm/Mir.zig
@@ -7,6 +7,8 @@
 //! and known jump labels for blocks.
 
 const Mir = @This();
+const InternPool = @import("../../InternPool.zig");
+const Wasm = @import("../../link/Wasm.zig");
 
 const std = @import("std");
 
@@ -78,22 +80,23 @@ pub const Inst = struct {
         ///
         /// Uses `nop`
         @"return" = 0x0F,
-        /// Calls a function by its index
-        ///
-        /// Uses `label`
-        call = 0x10,
+        /// Calls a function using `nav_index`.
+        call_nav,
+        /// Calls a function using `func_index`.
+        call_func,
         /// Calls a function pointer by its function signature
         /// and index into the function table.
         ///
         /// Uses `label`
         call_indirect = 0x11,
+        /// Calls a function by its index.
+        ///
+        /// The function is the auto-generated tag name function for the type
+        /// provided in `ip_index`.
+        call_tag_name,
         /// Contains a symbol to a function pointer
         /// uses `label`
-        ///
-        /// Note: This uses `0x16` as value which is reserved by the WebAssembly
-        /// specification but unused, meaning we must update this if the specification were to
-        /// use this value.
-        function_index = 0x16,
+        function_index,
         /// Pops three values from the stack and pushes
         /// the first or second value dependent on the third value.
         /// Uses `tag`
@@ -580,6 +583,10 @@ pub const Inst = struct {
         ///
         /// Used by e.g. `br_table`
         payload: u32,
+
+        ip_index: InternPool.Index,
+        nav_index: InternPool.Nav.Index,
+        func_index: Wasm.FunctionIndex,
     };
 };
 
src/codegen/llvm.zig
@@ -1059,9 +1059,10 @@ pub const Object = struct {
         lto: Compilation.Config.LtoMode,
     };
 
-    pub fn emit(o: *Object, options: EmitOptions) !void {
+    pub fn emit(o: *Object, options: EmitOptions) error{ LinkFailure, OutOfMemory }!void {
         const zcu = o.pt.zcu;
         const comp = zcu.comp;
+        const diags = &comp.link_diags;
 
         {
             try o.genErrorNameTable();
@@ -1223,27 +1224,30 @@ pub const Object = struct {
             o.builder.clearAndFree();
 
             if (options.pre_bc_path) |path| {
-                var file = try std.fs.cwd().createFile(path, .{});
+                var file = std.fs.cwd().createFile(path, .{}) catch |err|
+                    return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });
                 defer file.close();
 
                 const ptr: [*]const u8 = @ptrCast(bitcode.ptr);
-                try file.writeAll(ptr[0..(bitcode.len * 4)]);
+                file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err|
+                    return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) });
             }
 
             if (options.asm_path == null and options.bin_path == null and
                 options.post_ir_path == null and options.post_bc_path == null) return;
 
             if (options.post_bc_path) |path| {
-                var file = try std.fs.cwd().createFileZ(path, .{});
+                var file = std.fs.cwd().createFileZ(path, .{}) catch |err|
+                    return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });
                 defer file.close();
 
                 const ptr: [*]const u8 = @ptrCast(bitcode.ptr);
-                try file.writeAll(ptr[0..(bitcode.len * 4)]);
+                file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err|
+                    return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) });
             }
 
             if (!build_options.have_llvm or !comp.config.use_lib_llvm) {
-                log.err("emitting without libllvm not implemented", .{});
-                return error.FailedToEmit;
+                return diags.fail("emitting without libllvm not implemented", .{});
             }
 
             initializeLLVMTarget(comp.root_mod.resolved_target.result.cpu.arch);
@@ -1263,8 +1267,7 @@ pub const Object = struct {
 
             var module: *llvm.Module = undefined;
             if (context.parseBitcodeInContext2(bitcode_memory_buffer, &module).toBool() or context.getBrokenDebugInfo()) {
-                log.err("Failed to parse bitcode", .{});
-                return error.FailedToEmit;
+                return diags.fail("Failed to parse bitcode", .{});
             }
             break :emit .{ context, module };
         };
@@ -1274,12 +1277,7 @@ pub const Object = struct {
         var error_message: [*:0]const u8 = undefined;
         if (llvm.Target.getFromTriple(target_triple_sentinel, &target, &error_message).toBool()) {
             defer llvm.disposeMessage(error_message);
-
-            log.err("LLVM failed to parse '{s}': {s}", .{
-                target_triple_sentinel,
-                error_message,
-            });
-            @panic("Invalid LLVM triple");
+            return diags.fail("LLVM failed to parse '{s}': {s}", .{ target_triple_sentinel, error_message });
         }
 
         const optimize_mode = comp.root_mod.optimize_mode;
@@ -1374,10 +1372,9 @@ pub const Object = struct {
         if (options.asm_path != null and options.bin_path != null) {
             if (target_machine.emitToFile(module, &error_message, &lowered_options)) {
                 defer llvm.disposeMessage(error_message);
-                log.err("LLVM failed to emit bin={s} ir={s}: {s}", .{
+                return diags.fail("LLVM failed to emit bin={s} ir={s}: {s}", .{
                     emit_bin_msg, post_llvm_ir_msg, error_message,
                 });
-                return error.FailedToEmit;
             }
             lowered_options.bin_filename = null;
             lowered_options.llvm_ir_filename = null;
@@ -1386,11 +1383,9 @@ pub const Object = struct {
         lowered_options.asm_filename = options.asm_path;
         if (target_machine.emitToFile(module, &error_message, &lowered_options)) {
             defer llvm.disposeMessage(error_message);
-            log.err("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{
-                emit_asm_msg,  emit_bin_msg, post_llvm_ir_msg, post_llvm_bc_msg,
-                error_message,
+            return diags.fail("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{
+                emit_asm_msg, emit_bin_msg, post_llvm_ir_msg, post_llvm_bc_msg, error_message,
             });
-            return error.FailedToEmit;
         }
     }
 
@@ -1967,11 +1962,6 @@ pub const Object = struct {
         }
     }
 
-    pub fn freeDecl(self: *Object, decl_index: InternPool.DeclIndex) void {
-        const global = self.decl_map.get(decl_index) orelse return;
-        global.delete(&self.builder);
-    }
-
     fn getDebugFile(o: *Object, file_index: Zcu.File.Index) Allocator.Error!Builder.Metadata {
         const gpa = o.gpa;
         const gop = try o.debug_file_map.getOrPut(gpa, file_index);
src/link/Elf/relocatable.zig
@@ -2,7 +2,7 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) link.File.FlushError!v
     const gpa = comp.gpa;
     const diags = &comp.link_diags;
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 
     // First, we flush relocatable object file generated with our backends.
     if (elf_file.zigObjectPtr()) |zig_object| {
@@ -127,13 +127,13 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) link.File.FlushError!v
     try elf_file.base.file.?.setEndPos(total_size);
     try elf_file.base.file.?.pwriteAll(buffer.items, 0);
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 }
 
 pub fn flushObject(elf_file: *Elf, comp: *Compilation) link.File.FlushError!void {
     const diags = &comp.link_diags;
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 
     // Now, we are ready to resolve the symbols across all input files.
     // We will first resolve the files in the ZigObject, next in the parsed
@@ -179,7 +179,7 @@ pub fn flushObject(elf_file: *Elf, comp: *Compilation) link.File.FlushError!void
     try elf_file.writeShdrTable();
     try elf_file.writeElfHeader();
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 }
 
 fn claimUnresolved(elf_file: *Elf) void {
@@ -259,7 +259,7 @@ fn initComdatGroups(elf_file: *Elf) !void {
     }
 }
 
-fn updateSectionSizes(elf_file: *Elf) !void {
+fn updateSectionSizes(elf_file: *Elf) link.File.FlushError!void {
     const slice = elf_file.sections.slice();
     for (slice.items(.atom_list_2)) |*atom_list| {
         if (atom_list.atoms.keys().len == 0) continue;
src/link/Elf/ZigObject.zig
@@ -264,7 +264,7 @@ pub fn deinit(self: *ZigObject, allocator: Allocator) void {
     }
 }
 
-pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void {
+pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) link.File.FlushError!void {
     // Handle any lazy symbols that were emitted by incremental compilation.
     if (self.lazy_syms.getPtr(.anyerror_type)) |metadata| {
         const pt: Zcu.PerThread = .activate(elf_file.base.comp.zcu.?, tid);
@@ -278,7 +278,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void {
             .{ .kind = .code, .ty = .anyerror_type },
             metadata.text_symbol_index,
         ) catch |err| return switch (err) {
-            error.CodegenFail => error.FlushFailure,
+            error.CodegenFail => error.LinkFailure,
             else => |e| e,
         };
         if (metadata.rodata_state != .unused) self.updateLazySymbol(
@@ -287,7 +287,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void {
             .{ .kind = .const_data, .ty = .anyerror_type },
             metadata.rodata_symbol_index,
         ) catch |err| return switch (err) {
-            error.CodegenFail => error.FlushFailure,
+            error.CodegenFail => error.LinkFailure,
             else => |e| e,
         };
     }
@@ -933,6 +933,7 @@ pub fn getNavVAddr(
     const this_sym = self.symbol(this_sym_index);
     const vaddr = this_sym.address(.{}, elf_file);
     switch (reloc_info.parent) {
+        .none => unreachable,
         .atom_index => |atom_index| {
             const parent_atom = self.symbol(atom_index).atom(elf_file).?;
             const r_type = relocation.encode(.abs, elf_file.getTarget().cpu.arch);
@@ -965,6 +966,7 @@ pub fn getUavVAddr(
     const sym = self.symbol(sym_index);
     const vaddr = sym.address(.{}, elf_file);
     switch (reloc_info.parent) {
+        .none => unreachable,
         .atom_index => |atom_index| {
             const parent_atom = self.symbol(atom_index).atom(elf_file).?;
             const r_type = relocation.encode(.abs, elf_file.getTarget().cpu.arch);
@@ -1408,7 +1410,7 @@ pub fn updateFunc(
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
-) !void {
+) link.File.UpdateNavError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1615,7 +1617,7 @@ fn updateLazySymbol(
     pt: Zcu.PerThread,
     sym: link.File.LazySymbol,
     symbol_index: Symbol.Index,
-) !void {
+) link.File.FlushError!void {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
 
src/link/MachO/relocatable.zig
@@ -33,11 +33,11 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
             diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
     }
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 
     try macho_file.parseInputFiles();
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 
     try macho_file.resolveSymbols();
     try macho_file.dedupLiterals();
@@ -93,11 +93,11 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
             diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
     }
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 
     try parseInputFilesAr(macho_file);
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 
     // First, we flush relocatable object file generated with our backends.
     if (macho_file.getZigObject()) |zo| {
@@ -218,7 +218,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
     try macho_file.base.file.?.setEndPos(total_size);
     try macho_file.base.file.?.pwriteAll(buffer.items, 0);
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 }
 
 fn parseInputFilesAr(macho_file: *MachO) !void {
src/link/MachO/ZigObject.zig
@@ -560,7 +560,7 @@ pub fn flushModule(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id)
             .{ .kind = .code, .ty = .anyerror_type },
             metadata.text_symbol_index,
         ) catch |err| return switch (err) {
-            error.CodegenFail => error.FlushFailure,
+            error.CodegenFail => error.LinkFailure,
             else => |e| e,
         };
         if (metadata.const_state != .unused) self.updateLazySymbol(
@@ -569,7 +569,7 @@ pub fn flushModule(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id)
             .{ .kind = .const_data, .ty = .anyerror_type },
             metadata.const_symbol_index,
         ) catch |err| return switch (err) {
-            error.CodegenFail => error.FlushFailure,
+            error.CodegenFail => error.LinkFailure,
             else => |e| e,
         };
     }
src/link/Wasm/Archive.zig
@@ -142,7 +142,16 @@ pub fn parse(gpa: Allocator, file_contents: []const u8) !Archive {
 
 /// 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: *Wasm, file_contents: []const u8, path: Path) !Object {
+pub fn parseObject(
+    archive: Archive,
+    wasm: *Wasm,
+    file_contents: []const u8,
+    path: Path,
+    host_name: Wasm.String,
+    scratch_space: *Object.ScratchSpace,
+    must_link: bool,
+    gc_sections: bool,
+) !Object {
     const header = mem.bytesAsValue(Header, file_contents[0..@sizeOf(Header)]);
     if (!mem.eql(u8, &header.fmag, ARFMAG)) return error.BadHeaderDelimiter;
 
@@ -157,8 +166,9 @@ pub fn parseObject(archive: Archive, wasm: *Wasm, file_contents: []const u8, pat
     };
 
     const object_file_size = try header.parsedSize();
+    const contents = file_contents[@sizeOf(Header)..][0..object_file_size];
 
-    return Object.create(wasm, file_contents[@sizeOf(Header)..][0..object_file_size], path, object_name);
+    return Object.parse(wasm, contents, path, object_name, host_name, scratch_space, must_link, gc_sections);
 }
 
 const Archive = @This();
src/link/Wasm/Flush.zig
@@ -0,0 +1,1448 @@
+//! Temporary, dynamically allocated structures used only during flush.
+//! Could be constructed fresh each time, or kept around between updates to reduce heap allocations.
+
+const Flush = @This();
+const Wasm = @import("../Wasm.zig");
+const Object = @import("Object.zig");
+const Zcu = @import("../../Zcu.zig");
+const Alignment = Wasm.Alignment;
+const String = Wasm.String;
+const Relocation = Wasm.Relocation;
+const InternPool = @import("../../InternPool.zig");
+
+const build_options = @import("build_options");
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const mem = std.mem;
+const leb = std.leb;
+const log = std.log.scoped(.link);
+const assert = std.debug.assert;
+
+/// Ordered list of data segments that will appear in the final binary.
+/// When sorted, to-be-merged segments will be made adjacent.
+/// Values are offset relative to segment start.
+data_segments: std.AutoArrayHashMapUnmanaged(Wasm.DataSegment.Index, u32) = .empty,
+/// Each time a `data_segment` offset equals zero it indicates a new group, and
+/// the next element in this array will contain the total merged segment size.
+data_segment_groups: std.ArrayListUnmanaged(u32) = .empty,
+
+binary_bytes: std.ArrayListUnmanaged(u8) = .empty,
+missing_exports: std.AutoArrayHashMapUnmanaged(String, void) = .empty,
+
+indirect_function_table: std.AutoArrayHashMapUnmanaged(Wasm.OutputFunctionIndex, u32) = .empty,
+
+/// 0. Index into `data_segments`.
+const DataSegmentIndex = enum(u32) {
+    _,
+};
+
+pub fn clear(f: *Flush) void {
+    f.binary_bytes.clearRetainingCapacity();
+    f.function_imports.clearRetainingCapacity();
+    f.global_imports.clearRetainingCapacity();
+    f.functions.clearRetainingCapacity();
+    f.globals.clearRetainingCapacity();
+    f.data_segments.clearRetainingCapacity();
+    f.data_segment_groups.clearRetainingCapacity();
+    f.indirect_function_table.clearRetainingCapacity();
+    f.function_exports.clearRetainingCapacity();
+    f.global_exports.clearRetainingCapacity();
+}
+
+pub fn deinit(f: *Flush, gpa: Allocator) void {
+    f.binary_bytes.deinit(gpa);
+    f.function_imports.deinit(gpa);
+    f.global_imports.deinit(gpa);
+    f.functions.deinit(gpa);
+    f.globals.deinit(gpa);
+    f.data_segments.deinit(gpa);
+    f.data_segment_groups.deinit(gpa);
+    f.indirect_function_table.deinit(gpa);
+    f.function_exports.deinit(gpa);
+    f.global_exports.deinit(gpa);
+    f.* = undefined;
+}
+
+pub fn finish(f: *Flush, wasm: *Wasm, arena: Allocator) anyerror!void {
+    const comp = wasm.base.comp;
+    const shared_memory = comp.config.shared_memory;
+    const diags = &comp.link_diags;
+    const gpa = comp.gpa;
+    const import_memory = comp.config.import_memory;
+    const export_memory = comp.config.export_memory;
+    const target = comp.root_mod.resolved_target.result;
+    const is_obj = comp.config.output_mode == .Obj;
+    const allow_undefined = is_obj or wasm.import_symbols;
+    const zcu = wasm.base.comp.zcu.?;
+    const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed!
+
+    if (wasm.any_exports_updated) {
+        wasm.any_exports_updated = false;
+        wasm.function_exports.shrinkRetainingCapacity(wasm.function_exports_len);
+        wasm.global_exports.shrinkRetainingCapacity(wasm.global_exports_len);
+
+        const entry_name = if (wasm.entry_resolution.isNavOrUnresolved(wasm)) wasm.entry_name else .none;
+
+        try f.missing_exports.reinit(gpa, wasm.missing_exports_init, &.{});
+        for (wasm.nav_exports.keys()) |*nav_export| {
+            if (ip.isFunctionType(ip.getNav(nav_export.nav_index).typeOf(ip))) {
+                try wasm.function_exports.append(gpa, .fromNav(nav_export.nav_index, wasm));
+                if (nav_export.name.toOptional() == entry_name) {
+                    wasm.entry_resolution = .pack(wasm, .{ .nav = nav_export.nav_index });
+                } else {
+                    f.missing_exports.swapRemove(nav_export.name);
+                }
+            } else {
+                try wasm.global_exports.append(gpa, .fromNav(nav_export.nav_index));
+                f.missing_exports.swapRemove(nav_export.name);
+            }
+        }
+
+        for (f.missing_exports.keys()) |exp_name| {
+            if (exp_name != .none) continue;
+            diags.addError("manually specified export name '{s}' undefined", .{exp_name.slice(wasm)});
+        }
+
+        if (entry_name.unwrap()) |name| {
+            var err = try diags.addErrorWithNotes(1);
+            try err.addMsg("entry symbol '{s}' missing", .{name.slice(wasm)});
+            try err.addNote("'-fno-entry' suppresses this error", .{});
+        }
+    }
+
+    if (!allow_undefined) {
+        for (wasm.function_imports.keys()) |function_import_id| {
+            const name, const src_loc = function_import_id.nameAndLoc(wasm);
+            diags.addSrcError(src_loc, "undefined function: {s}", .{name.slice(wasm)});
+        }
+        for (wasm.global_imports.keys()) |global_import_id| {
+            const name, const src_loc = global_import_id.nameAndLoc(wasm);
+            diags.addSrcError(src_loc, "undefined global: {s}", .{name.slice(wasm)});
+        }
+        for (wasm.table_imports.keys()) |table_import_id| {
+            const name, const src_loc = table_import_id.nameAndLoc(wasm);
+            diags.addSrcError(src_loc, "undefined table: {s}", .{name.slice(wasm)});
+        }
+    }
+
+    if (diags.hasErrors()) return error.LinkFailure;
+
+    // TODO only include init functions for objects with must_link=true or
+    // which have any alive functions inside them.
+    if (wasm.object_init_funcs.items.len > 0) {
+        // Zig has no constructors so these are only for object file inputs.
+        mem.sortUnstable(Wasm.InitFunc, wasm.object_init_funcs.items, {}, Wasm.InitFunc.lessThan);
+        try f.functions.put(gpa, .__wasm_call_ctors, {});
+    }
+
+    var any_passive_inits = false;
+
+    // Merge and order the data segments. Depends on garbage collection so that
+    // unused segments can be omitted.
+    try f.ensureUnusedCapacity(gpa, wasm.object_data_segments.items.len);
+    for (wasm.object_data_segments.items, 0..) |*ds, i| {
+        if (!ds.flags.alive) continue;
+        any_passive_inits = any_passive_inits or ds.flags.is_passive or (import_memory and !isBss(wasm, ds.name));
+        f.data_segments.putAssumeCapacityNoClobber(@intCast(i), .{
+            .offset = undefined,
+        });
+    }
+
+    try f.functions.ensureUnusedCapacity(gpa, 3);
+
+    // Passive segments are used to avoid memory being reinitialized on each
+    // thread's instantiation. These passive segments are initialized and
+    // dropped in __wasm_init_memory, which is registered as the start function
+    // We also initialize bss segments (using memory.fill) as part of this
+    // function.
+    if (any_passive_inits) {
+        f.functions.putAssumeCapacity(.__wasm_init_memory, {});
+    }
+
+    // When we have TLS GOT entries and shared memory is enabled,
+    // we must perform runtime relocations or else we don't create the function.
+    if (shared_memory) {
+        if (f.need_tls_relocs) f.functions.putAssumeCapacity(.__wasm_apply_global_tls_relocs, {});
+        f.functions.putAssumeCapacity(gpa, .__wasm_init_tls, {});
+    }
+
+    // Sort order:
+    // 0. Whether the segment is TLS
+    // 1. Segment name prefix
+    // 2. Segment alignment
+    // 3. Segment name suffix
+    // 4. Segment index (to break ties, keeping it deterministic)
+    // TLS segments are intended to be merged with each other, and segments
+    // with a common prefix name are intended to be merged with each other.
+    // Sorting ensures the segments intended to be merged will be adjacent.
+    const Sort = struct {
+        wasm: *const Wasm,
+        segments: []const Wasm.DataSegment.Index,
+        pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
+            const lhs_segment_index = ctx.segments[lhs];
+            const rhs_segment_index = ctx.segments[rhs];
+            const lhs_segment = lhs_segment_index.ptr(wasm);
+            const rhs_segment = rhs_segment_index.ptr(wasm);
+            const lhs_tls = @intFromBool(lhs_segment.flags.tls);
+            const rhs_tls = @intFromBool(rhs_segment.flags.tls);
+            if (lhs_tls < rhs_tls) return true;
+            if (lhs_tls > rhs_tls) return false;
+            const lhs_prefix, const lhs_suffix = splitSegmentName(lhs_segment.name.unwrap().slice(ctx.wasm));
+            const rhs_prefix, const rhs_suffix = splitSegmentName(rhs_segment.name.unwrap().slice(ctx.wasm));
+            switch (mem.order(u8, lhs_prefix, rhs_prefix)) {
+                .lt => return true,
+                .gt => return false,
+                .eq => {},
+            }
+            switch (lhs_segment.flags.alignment.order(rhs_segment.flags.alignment)) {
+                .lt => return false,
+                .gt => return true,
+                .eq => {},
+            }
+            return switch (mem.order(u8, lhs_suffix, rhs_suffix)) {
+                .lt => true,
+                .gt => false,
+                .eq => @intFromEnum(lhs_segment_index) < @intFromEnum(rhs_segment_index),
+            };
+        }
+    };
+    f.data_segments.sortUnstable(@as(Sort, .{
+        .wasm = wasm,
+        .segments = f.data_segments.keys(),
+    }));
+
+    const page_size = std.wasm.page_size; // 64kb
+    const stack_alignment: Alignment = .@"16"; // wasm's stack alignment as specified by tool-convention
+    const heap_alignment: Alignment = .@"16"; // wasm's heap alignment as specified by tool-convention
+    const pointer_alignment: Alignment = .@"4";
+    // Always place the stack at the start by default unless the user specified the global-base flag.
+    const place_stack_first, var memory_ptr: u32 = if (wasm.global_base) |base| .{ false, base } else .{ true, 0 };
+
+    const VirtualAddrs = struct {
+        stack_pointer: u32,
+        heap_base: u32,
+        heap_end: u32,
+        tls_base: ?u32,
+        tls_align: ?u32,
+        tls_size: ?u32,
+        init_memory_flag: ?u32,
+    };
+    var virtual_addrs: VirtualAddrs = .{
+        .stack_pointer = undefined,
+        .heap_base = undefined,
+        .heap_end = undefined,
+        .tls_base = null,
+        .tls_align = null,
+        .tls_size = null,
+        .init_memory_flag = null,
+    };
+
+    if (place_stack_first and !is_obj) {
+        memory_ptr = stack_alignment.forward(memory_ptr);
+        memory_ptr += wasm.base.stack_size;
+        virtual_addrs.stack_pointer = memory_ptr;
+    }
+
+    const segment_indexes = f.data_segments.keys();
+    const segment_offsets = f.data_segments.values();
+    assert(f.data_segment_groups.items.len == 0);
+    {
+        var seen_tls: enum { before, during, after } = .before;
+        var offset: u32 = 0;
+        for (segment_indexes, segment_offsets, 0..) |segment_index, *segment_offset, i| {
+            const segment = segment_index.ptr(f);
+            memory_ptr = segment.alignment.forward(memory_ptr);
+
+            const want_new_segment = b: {
+                if (is_obj) break :b false;
+                switch (seen_tls) {
+                    .before => if (segment.flags.tls) {
+                        virtual_addrs.tls_base = if (shared_memory) 0 else memory_ptr;
+                        virtual_addrs.tls_align = segment.flags.alignment;
+                        seen_tls = .during;
+                        break :b true;
+                    },
+                    .during => if (!segment.flags.tls) {
+                        virtual_addrs.tls_size = memory_ptr - virtual_addrs.tls_base;
+                        virtual_addrs.tls_align = virtual_addrs.tls_align.maxStrict(segment.flags.alignment);
+                        seen_tls = .after;
+                        break :b true;
+                    },
+                    .after => {},
+                }
+                break :b i >= 1 and !wasm.wantSegmentMerge(segment_indexes[i - 1], segment_index);
+            };
+            if (want_new_segment) {
+                if (offset > 0) try f.data_segment_groups.append(gpa, offset);
+                offset = 0;
+            }
+
+            segment_offset.* = offset;
+            offset += segment.size;
+            memory_ptr += segment.size;
+        }
+        if (offset > 0) try f.data_segment_groups.append(gpa, offset);
+    }
+
+    if (shared_memory and any_passive_inits) {
+        memory_ptr = pointer_alignment.forward(memory_ptr);
+        virtual_addrs.init_memory_flag = memory_ptr;
+        memory_ptr += 4;
+    }
+
+    if (!place_stack_first and !is_obj) {
+        memory_ptr = stack_alignment.forward(memory_ptr);
+        memory_ptr += wasm.base.stack_size;
+        virtual_addrs.stack_pointer = memory_ptr;
+    }
+
+    memory_ptr = heap_alignment.forward(memory_ptr);
+    virtual_addrs.heap_base = memory_ptr;
+
+    if (wasm.initial_memory) |initial_memory| {
+        if (!mem.isAlignedGeneric(u64, initial_memory, page_size)) {
+            diags.addError("initial memory value {d} is not {d}-byte aligned", .{ initial_memory, page_size });
+        }
+        if (memory_ptr > initial_memory) {
+            diags.addError("initial memory value {d} insufficient; minimum {d}", .{ initial_memory, memory_ptr });
+        }
+        if (initial_memory > std.math.maxInt(u32)) {
+            diags.addError("initial memory value {d} exceeds 32-bit address space", .{initial_memory});
+        }
+        if (diags.hasErrors()) return error.LinkFailure;
+        memory_ptr = initial_memory;
+    } else {
+        memory_ptr = mem.alignForward(u64, memory_ptr, std.wasm.page_size);
+    }
+    virtual_addrs.heap_end = memory_ptr;
+
+    // In case we do not import memory, but define it ourselves, set the
+    // minimum amount of pages on the memory section.
+    wasm.memories.limits.min = @intCast(memory_ptr / page_size);
+    log.debug("total memory pages: {d}", .{wasm.memories.limits.min});
+
+    if (wasm.max_memory) |max_memory| {
+        if (!mem.isAlignedGeneric(u64, max_memory, page_size)) {
+            diags.addError("maximum memory value {d} is not {d}-byte aligned", .{ max_memory, page_size });
+        }
+        if (memory_ptr > max_memory) {
+            diags.addError("maximum memory value {d} insufficient; minimum {d}", .{ max_memory, memory_ptr });
+        }
+        if (max_memory > std.math.maxInt(u32)) {
+            diags.addError("maximum memory exceeds 32-bit address space", .{max_memory});
+        }
+        if (diags.hasErrors()) return error.LinkFailure;
+        wasm.memories.limits.max = @intCast(max_memory / page_size);
+        wasm.memories.limits.flags.has_max = true;
+        if (shared_memory) wasm.memories.limits.flags.is_shared = true;
+        log.debug("maximum memory pages: {?d}", .{wasm.memories.limits.max});
+    }
+
+    // Size of each section header
+    const header_size = 5 + 1;
+    var section_index: u32 = 0;
+    // Index of the code section. Used to tell relocation table where the section lives.
+    var code_section_index: ?u32 = null;
+    // Index of the data section. Used to tell relocation table where the section lives.
+    var data_section_index: ?u32 = null;
+
+    const binary_bytes = &f.binary_bytes;
+    assert(binary_bytes.items.len == 0);
+
+    try binary_bytes.appendSlice(gpa, std.wasm.magic ++ std.wasm.version);
+    assert(binary_bytes.items.len == 8);
+
+    const binary_writer = binary_bytes.writer(gpa);
+
+    // Type section
+    if (wasm.func_types.items.len != 0) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+        log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len});
+        for (wasm.func_types.items) |func_type| {
+            try leb.writeUleb128(binary_writer, std.wasm.function_type);
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(func_type.params.len)));
+            for (func_type.params) |param_ty| {
+                try leb.writeUleb128(binary_writer, std.wasm.valtype(param_ty));
+            }
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(func_type.returns.len)));
+            for (func_type.returns) |ret_ty| {
+                try leb.writeUleb128(binary_writer, std.wasm.valtype(ret_ty));
+            }
+        }
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .type,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            @intCast(wasm.func_types.items.len),
+        );
+        section_index += 1;
+    }
+
+    // Import section
+    const total_imports_len = wasm.function_imports.items.len + wasm.global_imports.items.len +
+        wasm.table_imports.items.len + wasm.memory_imports.items.len + @intFromBool(import_memory);
+
+    if (total_imports_len > 0) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+
+        for (wasm.function_imports.items) |*function_import| {
+            const module_name = function_import.module_name.slice(wasm);
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(module_name.len)));
+            try binary_writer.writeAll(module_name);
+
+            const name = function_import.name.slice(wasm);
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len)));
+            try binary_writer.writeAll(name);
+
+            try binary_writer.writeByte(@intFromEnum(std.wasm.ExternalKind.function));
+            try leb.writeUleb128(binary_writer, function_import.index);
+        }
+
+        for (wasm.table_imports.items) |*table_import| {
+            const module_name = table_import.module_name.slice(wasm);
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(module_name.len)));
+            try binary_writer.writeAll(module_name);
+
+            const name = table_import.name.slice(wasm);
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len)));
+            try binary_writer.writeAll(name);
+
+            try binary_writer.writeByte(@intFromEnum(std.wasm.ExternalKind.table));
+            try leb.writeUleb128(binary_writer, std.wasm.reftype(table_import.reftype));
+            try emitLimits(binary_writer, table_import.limits);
+        }
+
+        for (wasm.memory_imports.items) |*memory_import| {
+            try emitMemoryImport(wasm, binary_writer, memory_import);
+        } else if (import_memory) {
+            try emitMemoryImport(wasm, binary_writer, &.{
+                .module_name = wasm.host_name,
+                .name = if (is_obj) wasm.preloaded_strings.__linear_memory else wasm.preloaded_strings.memory,
+                .limits_min = wasm.memories.limits.min,
+                .limits_max = wasm.memories.limits.max,
+                .limits_has_max = wasm.memories.limits.flags.has_max,
+                .limits_is_shared = wasm.memories.limits.flags.is_shared,
+            });
+        }
+
+        for (wasm.global_imports.items) |*global_import| {
+            const module_name = global_import.module_name.slice(wasm);
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(module_name.len)));
+            try binary_writer.writeAll(module_name);
+
+            const name = global_import.name.slice(wasm);
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len)));
+            try binary_writer.writeAll(name);
+
+            try binary_writer.writeByte(@intFromEnum(std.wasm.ExternalKind.global));
+            try leb.writeUleb128(binary_writer, @intFromEnum(global_import.valtype));
+            try binary_writer.writeByte(@intFromBool(global_import.mutable));
+        }
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .import,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            @intCast(total_imports_len),
+        );
+        section_index += 1;
+    }
+
+    // Function section
+    if (wasm.functions.count() != 0) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+        for (wasm.functions.values()) |function| {
+            try leb.writeUleb128(binary_writer, function.func.type_index);
+        }
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .function,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            @intCast(wasm.functions.count()),
+        );
+        section_index += 1;
+    }
+
+    // Table section
+    if (wasm.tables.items.len > 0) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+
+        for (wasm.tables.items) |table| {
+            try leb.writeUleb128(binary_writer, std.wasm.reftype(table.reftype));
+            try emitLimits(binary_writer, table.limits);
+        }
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .table,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            @intCast(wasm.tables.items.len),
+        );
+        section_index += 1;
+    }
+
+    // Memory section
+    if (!import_memory) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+
+        try emitLimits(binary_writer, wasm.memories.limits);
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .memory,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            1, // wasm currently only supports 1 linear memory segment
+        );
+        section_index += 1;
+    }
+
+    // Global section (used to emit stack pointer)
+    if (wasm.output_globals.items.len > 0) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+
+        for (wasm.output_globals.items) |global| {
+            try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));
+            try binary_writer.writeByte(@intFromBool(global.global_type.mutable));
+            try emitInit(binary_writer, global.init);
+        }
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .global,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            @intCast(wasm.output_globals.items.len),
+        );
+        section_index += 1;
+    }
+
+    // Export section
+    if (wasm.exports.items.len != 0 or export_memory) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+
+        for (wasm.exports.items) |exp| {
+            const name = exp.name.slice(wasm);
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len)));
+            try binary_writer.writeAll(name);
+            try leb.writeUleb128(binary_writer, @intFromEnum(exp.kind));
+            try leb.writeUleb128(binary_writer, exp.index);
+        }
+
+        if (export_memory) {
+            try leb.writeUleb128(binary_writer, @as(u32, @intCast("memory".len)));
+            try binary_writer.writeAll("memory");
+            try binary_writer.writeByte(std.wasm.externalKind(.memory));
+            try leb.writeUleb128(binary_writer, @as(u32, 0));
+        }
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .@"export",
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            @intCast(wasm.exports.items.len + @intFromBool(export_memory)),
+        );
+        section_index += 1;
+    }
+
+    if (wasm.entry) |entry_index| {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .start,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            entry_index,
+        );
+    }
+
+    // element section (function table)
+    if (wasm.function_table.count() > 0) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+
+        const table_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?;
+        const table_sym = wasm.finalSymbolByLoc(table_loc);
+
+        const flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually
+        try leb.writeUleb128(binary_writer, flags);
+        if (flags == 0x02) {
+            try leb.writeUleb128(binary_writer, table_sym.index);
+        }
+        try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
+        if (flags == 0x02) {
+            try leb.writeUleb128(binary_writer, @as(u8, 0)); // represents funcref
+        }
+        try leb.writeUleb128(binary_writer, @as(u32, @intCast(wasm.function_table.count())));
+        var symbol_it = wasm.function_table.keyIterator();
+        while (symbol_it.next()) |symbol_loc_ptr| {
+            const sym = wasm.finalSymbolByLoc(symbol_loc_ptr.*);
+            assert(sym.flags.alive);
+            assert(sym.index < wasm.functions.count() + wasm.imported_functions_count);
+            try leb.writeUleb128(binary_writer, sym.index);
+        }
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .element,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            1,
+        );
+        section_index += 1;
+    }
+
+    // When the shared-memory option is enabled, we *must* emit the 'data count' section.
+    if (f.data_segment_groups.items.len > 0 and shared_memory) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .data_count,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            @intCast(f.data_segment_groups.items.len),
+        );
+    }
+
+    // Code section.
+    if (f.functions.count() != 0) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+        const start_offset = binary_bytes.items.len - 5; // minus 5 so start offset is 5 to include entry count
+
+        for (f.functions.keys()) |resolution| switch (resolution.unpack()) {
+            .unresolved => unreachable,
+            .__wasm_apply_global_tls_relocs => @panic("TODO lower __wasm_apply_global_tls_relocs"),
+            .__wasm_call_ctors => @panic("TODO lower __wasm_call_ctors"),
+            .__wasm_init_memory => @panic("TODO lower __wasm_init_memory "),
+            .__wasm_init_tls => @panic("TODO lower __wasm_init_tls "),
+            .__zig_error_names => @panic("TODO lower __zig_error_names "),
+            .object_function => |i| {
+                _ = i;
+                _ = start_offset;
+                @panic("TODO lower object function code and apply relocations");
+                //try leb.writeUleb128(binary_writer, atom.code.len);
+                //try binary_bytes.appendSlice(gpa, atom.code.slice(wasm));
+            },
+            .nav => |i| {
+                _ = i;
+                _ = start_offset;
+                @panic("TODO lower nav code and apply relocations");
+                //try leb.writeUleb128(binary_writer, atom.code.len);
+                //try binary_bytes.appendSlice(gpa, atom.code.slice(wasm));
+            },
+        };
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .code,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            @intCast(wasm.functions.count()),
+        );
+        code_section_index = section_index;
+        section_index += 1;
+    }
+
+    // Data section.
+    if (f.data_segment_groups.items.len != 0) {
+        const header_offset = try reserveVecSectionHeader(gpa, binary_bytes);
+
+        var group_index: u32 = 0;
+        var offset: u32 = undefined;
+        for (segment_indexes, segment_offsets) |segment_index, segment_offset| {
+            const segment = segment_index.ptr(wasm);
+            if (segment.size == 0) continue;
+            if (!import_memory and isBss(wasm, segment.name)) {
+                // It counted for virtual memory but it does not go into the binary.
+                continue;
+            }
+            if (segment_offset == 0) {
+                const group_size = f.data_segment_groups.items[group_index];
+                group_index += 1;
+                offset = 0;
+
+                const flags: Object.DataSegmentFlags = if (segment.flags.is_passive) .passive else .active;
+                try leb.writeUleb128(binary_writer, @intFromEnum(flags));
+                // when a segment is passive, it's initialized during runtime.
+                if (flags != .passive) {
+                    try emitInit(binary_writer, .{ .i32_const = @as(i32, @bitCast(segment_offset)) });
+                }
+                try leb.writeUleb128(binary_writer, group_size);
+            }
+
+            try binary_bytes.appendNTimes(gpa, 0, segment_offset - offset);
+            offset = segment_offset;
+            try binary_bytes.appendSlice(gpa, segment.payload.slice(wasm));
+            offset += segment.payload.len;
+            if (true) @panic("TODO apply data segment relocations");
+        }
+        assert(group_index == f.data_segment_groups.items.len);
+
+        try writeVecSectionHeader(
+            binary_bytes.items,
+            header_offset,
+            .data,
+            @intCast(binary_bytes.items.len - header_offset - header_size),
+            group_index,
+        );
+        data_section_index = section_index;
+        section_index += 1;
+    }
+
+    if (is_obj) {
+        @panic("TODO emit link section for object file and apply relocations");
+        //var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena);
+        //try wasm.emitLinkSection(binary_bytes, &symbol_table);
+        //if (code_section_index) |code_index| {
+        //    try wasm.emitCodeRelocations(binary_bytes, code_index, symbol_table);
+        //}
+        //if (data_section_index) |data_index| {
+        //    if (wasm.data_segments.count() > 0)
+        //        try wasm.emitDataRelocations(binary_bytes, data_index, symbol_table);
+        //}
+    } else if (comp.config.debug_format != .strip) {
+        try wasm.emitNameSection(binary_bytes, arena);
+    }
+
+    if (comp.config.debug_format != .strip) {
+        // The build id must be computed on the main sections only,
+        // so we have to do it now, before the debug sections.
+        switch (wasm.base.build_id) {
+            .none => {},
+            .fast => {
+                var id: [16]u8 = undefined;
+                std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{});
+                var uuid: [36]u8 = undefined;
+                _ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{
+                    std.fmt.fmtSliceHexLower(id[0..4]),
+                    std.fmt.fmtSliceHexLower(id[4..6]),
+                    std.fmt.fmtSliceHexLower(id[6..8]),
+                    std.fmt.fmtSliceHexLower(id[8..10]),
+                    std.fmt.fmtSliceHexLower(id[10..]),
+                });
+                try emitBuildIdSection(binary_bytes, &uuid);
+            },
+            .hexstring => |hs| {
+                var buffer: [32 * 2]u8 = undefined;
+                const str = std.fmt.bufPrint(&buffer, "{s}", .{
+                    std.fmt.fmtSliceHexLower(hs.toSlice()),
+                }) catch unreachable;
+                try emitBuildIdSection(binary_bytes, str);
+            },
+            else => |mode| {
+                var err = try diags.addErrorWithNotes(0);
+                try err.addMsg("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)});
+            },
+        }
+
+        var debug_bytes = std.ArrayList(u8).init(gpa);
+        defer debug_bytes.deinit();
+
+        try emitProducerSection(binary_bytes);
+        if (!target.cpu.features.isEmpty())
+            try emitFeaturesSection(binary_bytes, target.cpu.features);
+    }
+
+    // Finally, write the entire binary into the file.
+    const file = wasm.base.file.?;
+    try file.pwriteAll(binary_bytes.items, 0);
+    try file.setEndPos(binary_bytes.items.len);
+}
+
+fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayListUnmanaged(u8), arena: Allocator) !void {
+    const comp = wasm.base.comp;
+    const gpa = comp.gpa;
+    const import_memory = comp.config.import_memory;
+
+    // Deduplicate symbols that point to the same function.
+    var funcs: std.AutoArrayHashMapUnmanaged(u32, String) = .empty;
+    try funcs.ensureUnusedCapacityPrecise(arena, wasm.functions.count() + wasm.function_imports.items.len);
+
+    const NamedIndex = struct {
+        index: u32,
+        name: String,
+    };
+
+    var globals: std.MultiArrayList(NamedIndex) = .empty;
+    try globals.ensureTotalCapacityPrecise(arena, wasm.output_globals.items.len + wasm.global_imports.items.len);
+
+    var segments: std.MultiArrayList(NamedIndex) = .empty;
+    try segments.ensureTotalCapacityPrecise(arena, wasm.data_segments.count());
+
+    for (wasm.resolved_symbols.keys()) |sym_loc| {
+        const symbol = wasm.finalSymbolByLoc(sym_loc).*;
+        if (!symbol.flags.alive) continue;
+        const name = wasm.finalSymbolByLoc(sym_loc).name;
+        switch (symbol.tag) {
+            .function => {
+                const index = if (symbol.flags.undefined)
+                    @intFromEnum(symbol.pointee.function_import)
+                else
+                    wasm.function_imports.items.len + @intFromEnum(symbol.pointee.function);
+                const gop = funcs.getOrPutAssumeCapacity(index);
+                if (gop.found_existing) {
+                    assert(gop.value_ptr.* == name);
+                } else {
+                    gop.value_ptr.* = name;
+                }
+            },
+            .global => {
+                globals.appendAssumeCapacity(.{
+                    .index = if (symbol.flags.undefined)
+                        @intFromEnum(symbol.pointee.global_import)
+                    else
+                        @intFromEnum(symbol.pointee.global),
+                    .name = name,
+                });
+            },
+            else => {},
+        }
+    }
+
+    for (wasm.data_segments.keys(), 0..) |key, index| {
+        // bss section is not emitted when this condition holds true, so we also
+        // do not output a name for it.
+        if (!import_memory and mem.eql(u8, key, ".bss")) continue;
+        segments.appendAssumeCapacity(.{ .index = @intCast(index), .name = key });
+    }
+
+    const Sort = struct {
+        indexes: []const u32,
+        pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
+            return ctx.indexes[lhs] < ctx.indexes[rhs];
+        }
+    };
+    funcs.entries.sortUnstable(@as(Sort, .{ .indexes = funcs.keys() }));
+    globals.sortUnstable(@as(Sort, .{ .indexes = globals.items(.index) }));
+    // Data segments are already ordered.
+
+    const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+    const writer = binary_bytes.writer();
+    try leb.writeUleb128(writer, @as(u32, @intCast("name".len)));
+    try writer.writeAll("name");
+
+    try emitNameSubsection(wasm, binary_bytes, .function, funcs.keys(), funcs.values());
+    try emitNameSubsection(wasm, binary_bytes, .global, globals.items(.index), globals.items(.name));
+    try emitNameSubsection(wasm, binary_bytes, .data_segment, segments.items(.index), segments.items(.name));
+
+    try writeCustomSectionHeader(
+        binary_bytes.items,
+        header_offset,
+        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
+    );
+}
+
+fn writeCustomSectionHeader(buffer: []u8, offset: u32, size: u32) !void {
+    var buf: [1 + 5]u8 = undefined;
+    buf[0] = 0; // 0 = 'custom' section
+    leb.writeUnsignedFixed(5, buf[1..6], size);
+    buffer[offset..][0..buf.len].* = buf;
+}
+
+fn reserveCustomSectionHeader(gpa: Allocator, bytes: *std.ArrayListUnmanaged(u8)) error{OutOfMemory}!u32 {
+    // unlike regular section, we don't emit the count
+    const header_size = 1 + 5;
+    try bytes.appendNTimes(gpa, 0, header_size);
+    return @intCast(bytes.items.len - header_size);
+}
+
+fn emitNameSubsection(
+    wasm: *const Wasm,
+    binary_bytes: *std.ArrayListUnmanaged(u8),
+    section_id: std.wasm.NameSubsection,
+    indexes: []const u32,
+    names: []const String,
+) !void {
+    assert(indexes.len == names.len);
+    const gpa = wasm.base.comp.gpa;
+    // We must emit subsection size, so first write to a temporary list
+    var section_list: std.ArrayListUnmanaged(u8) = .empty;
+    defer section_list.deinit(gpa);
+    const sub_writer = section_list.writer(gpa);
+
+    try leb.writeUleb128(sub_writer, @as(u32, @intCast(names.len)));
+    for (indexes, names) |index, name_index| {
+        const name = name_index.slice(wasm);
+        log.debug("emit symbol '{s}' type({s})", .{ name, @tagName(section_id) });
+        try leb.writeUleb128(sub_writer, index);
+        try leb.writeUleb128(sub_writer, @as(u32, @intCast(name.len)));
+        try sub_writer.writeAll(name);
+    }
+
+    // From now, write to the actual writer
+    const writer = binary_bytes.writer(gpa);
+    try leb.writeUleb128(writer, @intFromEnum(section_id));
+    try leb.writeUleb128(writer, @as(u32, @intCast(section_list.items.len)));
+    try binary_bytes.appendSlice(gpa, section_list.items);
+}
+
+fn emitFeaturesSection(
+    gpa: Allocator,
+    binary_bytes: *std.ArrayListUnmanaged(u8),
+    features: []const Wasm.Feature,
+) !void {
+    const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+
+    const writer = binary_bytes.writer();
+    const target_features = "target_features";
+    try leb.writeUleb128(writer, @as(u32, @intCast(target_features.len)));
+    try writer.writeAll(target_features);
+
+    try leb.writeUleb128(writer, @as(u32, @intCast(features.len)));
+    for (features) |feature| {
+        assert(feature.prefix != .invalid);
+        try leb.writeUleb128(writer, @tagName(feature.prefix)[0]);
+        const name = @tagName(feature.tag);
+        try leb.writeUleb128(writer, @as(u32, name.len));
+        try writer.writeAll(name);
+    }
+
+    try writeCustomSectionHeader(
+        binary_bytes.items,
+        header_offset,
+        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
+    );
+}
+
+fn emitBuildIdSection(gpa: Allocator, binary_bytes: *std.ArrayListUnmanaged(u8), build_id: []const u8) !void {
+    const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+
+    const writer = binary_bytes.writer();
+    const hdr_build_id = "build_id";
+    try leb.writeUleb128(writer, @as(u32, @intCast(hdr_build_id.len)));
+    try writer.writeAll(hdr_build_id);
+
+    try leb.writeUleb128(writer, @as(u32, 1));
+    try leb.writeUleb128(writer, @as(u32, @intCast(build_id.len)));
+    try writer.writeAll(build_id);
+
+    try writeCustomSectionHeader(
+        binary_bytes.items,
+        header_offset,
+        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
+    );
+}
+
+fn emitProducerSection(gpa: Allocator, binary_bytes: *std.ArrayListUnmanaged(u8)) !void {
+    const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+
+    const writer = binary_bytes.writer();
+    const producers = "producers";
+    try leb.writeUleb128(writer, @as(u32, @intCast(producers.len)));
+    try writer.writeAll(producers);
+
+    try leb.writeUleb128(writer, @as(u32, 2)); // 2 fields: Language + processed-by
+
+    // language field
+    {
+        const language = "language";
+        try leb.writeUleb128(writer, @as(u32, @intCast(language.len)));
+        try writer.writeAll(language);
+
+        // field_value_count (TODO: Parse object files for producer sections to detect their language)
+        try leb.writeUleb128(writer, @as(u32, 1));
+
+        // versioned name
+        {
+            try leb.writeUleb128(writer, @as(u32, 3)); // len of "Zig"
+            try writer.writeAll("Zig");
+
+            try leb.writeUleb128(writer, @as(u32, @intCast(build_options.version.len)));
+            try writer.writeAll(build_options.version);
+        }
+    }
+
+    // processed-by field
+    {
+        const processed_by = "processed-by";
+        try leb.writeUleb128(writer, @as(u32, @intCast(processed_by.len)));
+        try writer.writeAll(processed_by);
+
+        // field_value_count (TODO: Parse object files for producer sections to detect other used tools)
+        try leb.writeUleb128(writer, @as(u32, 1));
+
+        // versioned name
+        {
+            try leb.writeUleb128(writer, @as(u32, 3)); // len of "Zig"
+            try writer.writeAll("Zig");
+
+            try leb.writeUleb128(writer, @as(u32, @intCast(build_options.version.len)));
+            try writer.writeAll(build_options.version);
+        }
+    }
+
+    try writeCustomSectionHeader(
+        binary_bytes.items,
+        header_offset,
+        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
+    );
+}
+
+///// For each relocatable section, emits a custom "relocation.<section_name>" section
+//fn emitCodeRelocations(
+//    wasm: *Wasm,
+//    binary_bytes: *std.ArrayListUnmanaged(u8),
+//    section_index: u32,
+//    symbol_table: std.AutoArrayHashMapUnmanaged(SymbolLoc, u32),
+//) !void {
+//    const comp = wasm.base.comp;
+//    const gpa = comp.gpa;
+//    const code_index = wasm.code_section_index.unwrap() orelse return;
+//    const writer = binary_bytes.writer();
+//    const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+//
+//    // write custom section information
+//    const name = "reloc.CODE";
+//    try leb.writeUleb128(writer, @as(u32, @intCast(name.len)));
+//    try writer.writeAll(name);
+//    try leb.writeUleb128(writer, section_index);
+//    const reloc_start = binary_bytes.items.len;
+//
+//    var count: u32 = 0;
+//    var atom: *Atom = wasm.atoms.get(code_index).?.ptr(wasm);
+//    // for each atom, we calculate the uleb size and append that
+//    var size_offset: u32 = 5; // account for code section size leb128
+//    while (true) {
+//        size_offset += getUleb128Size(atom.code.len);
+//        for (atom.relocSlice(wasm)) |relocation| {
+//            count += 1;
+//            const sym_loc: SymbolLoc = .{ .file = atom.file, .index = @enumFromInt(relocation.index) };
+//            const symbol_index = symbol_table.get(sym_loc).?;
+//            try leb.writeUleb128(writer, @intFromEnum(relocation.tag));
+//            const offset = atom.offset + relocation.offset + size_offset;
+//            try leb.writeUleb128(writer, offset);
+//            try leb.writeUleb128(writer, symbol_index);
+//            if (relocation.tag.addendIsPresent()) {
+//                try leb.writeIleb128(writer, relocation.addend);
+//            }
+//            log.debug("Emit relocation: {}", .{relocation});
+//        }
+//        if (atom.prev == .none) break;
+//        atom = atom.prev.ptr(wasm);
+//    }
+//    if (count == 0) return;
+//    var buf: [5]u8 = undefined;
+//    leb.writeUnsignedFixed(5, &buf, count);
+//    try binary_bytes.insertSlice(reloc_start, &buf);
+//    const size: u32 = @intCast(binary_bytes.items.len - header_offset - 6);
+//    try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
+//}
+
+//fn emitDataRelocations(
+//    wasm: *Wasm,
+//    binary_bytes: *std.ArrayList(u8),
+//    section_index: u32,
+//    symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
+//) !void {
+//    const comp = wasm.base.comp;
+//    const gpa = comp.gpa;
+//    const writer = binary_bytes.writer();
+//    const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+//
+//    // write custom section information
+//    const name = "reloc.DATA";
+//    try leb.writeUleb128(writer, @as(u32, @intCast(name.len)));
+//    try writer.writeAll(name);
+//    try leb.writeUleb128(writer, section_index);
+//    const reloc_start = binary_bytes.items.len;
+//
+//    var count: u32 = 0;
+//    // for each atom, we calculate the uleb size and append that
+//    var size_offset: u32 = 5; // account for code section size leb128
+//    for (wasm.data_segments.values()) |segment_index| {
+//        var atom: *Atom = wasm.atoms.get(segment_index).?.ptr(wasm);
+//        while (true) {
+//            size_offset += getUleb128Size(atom.code.len);
+//            for (atom.relocSlice(wasm)) |relocation| {
+//                count += 1;
+//                const sym_loc: SymbolLoc = .{ .file = atom.file, .index = @enumFromInt(relocation.index) };
+//                const symbol_index = symbol_table.get(sym_loc).?;
+//                try leb.writeUleb128(writer, @intFromEnum(relocation.tag));
+//                const offset = atom.offset + relocation.offset + size_offset;
+//                try leb.writeUleb128(writer, offset);
+//                try leb.writeUleb128(writer, symbol_index);
+//                if (relocation.tag.addendIsPresent()) {
+//                    try leb.writeIleb128(writer, relocation.addend);
+//                }
+//                log.debug("Emit relocation: {}", .{relocation});
+//            }
+//            if (atom.prev == .none) break;
+//            atom = atom.prev.ptr(wasm);
+//        }
+//    }
+//    if (count == 0) return;
+//
+//    var buf: [5]u8 = undefined;
+//    leb.writeUnsignedFixed(5, &buf, count);
+//    try binary_bytes.insertSlice(reloc_start, &buf);
+//    const size = @as(u32, @intCast(binary_bytes.items.len - header_offset - 6));
+//    try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
+//}
+
+fn isBss(wasm: *Wasm, name: String) bool {
+    const s = name.slice(wasm);
+    return mem.eql(u8, s, ".bss") or mem.startsWith(u8, s, ".bss.");
+}
+
+fn splitSegmentName(name: []const u8) struct { []const u8, []const u8 } {
+    const start = @intFromBool(name.len >= 1 and name[0] == '.');
+    const pivot = mem.indexOfScalarPos(u8, name, start, '.') orelse 0;
+    return .{ name[0..pivot], name[pivot..] };
+}
+
+fn wantSegmentMerge(wasm: *const Wasm, a_index: Wasm.DataSegment.Index, b_index: Wasm.DataSegment.Index) bool {
+    const a = a_index.ptr(wasm);
+    const b = b_index.ptr(wasm);
+    if (a.flags.tls and b.flags.tls) return true;
+    if (a.flags.tls != b.flags.tls) return false;
+    if (a.flags.is_passive != b.flags.is_passive) return false;
+    if (a.name == b.name) return true;
+    const a_prefix, _ = splitSegmentName(a.name.slice(wasm));
+    const b_prefix, _ = splitSegmentName(b.name.slice(wasm));
+    return a_prefix.len > 0 and mem.eql(u8, a_prefix, b_prefix);
+}
+
+fn reserveVecSectionHeader(gpa: Allocator, bytes: *std.ArrayListUnmanaged(u8)) error{OutOfMemory}!u32 {
+    // section id + fixed leb contents size + fixed leb vector length
+    const header_size = 1 + 5 + 5;
+    try bytes.appendNTimes(gpa, 0, header_size);
+    return @intCast(bytes.items.len - header_size);
+}
+
+fn writeVecSectionHeader(buffer: []u8, offset: u32, section: std.wasm.Section, size: u32, items: u32) !void {
+    var buf: [1 + 5 + 5]u8 = undefined;
+    buf[0] = @intFromEnum(section);
+    leb.writeUnsignedFixed(5, buf[1..6], size);
+    leb.writeUnsignedFixed(5, buf[6..], items);
+    buffer[offset..][0..buf.len].* = buf;
+}
+
+fn emitLimits(writer: anytype, limits: std.wasm.Limits) !void {
+    try writer.writeByte(limits.flags);
+    try leb.writeUleb128(writer, limits.min);
+    if (limits.flags.has_max) try leb.writeUleb128(writer, limits.max);
+}
+
+fn emitMemoryImport(wasm: *Wasm, writer: anytype, memory_import: *const Wasm.MemoryImport) error{OutOfMemory}!void {
+    const module_name = memory_import.module_name.slice(wasm);
+    try leb.writeUleb128(writer, @as(u32, @intCast(module_name.len)));
+    try writer.writeAll(module_name);
+
+    const name = memory_import.name.slice(wasm);
+    try leb.writeUleb128(writer, @as(u32, @intCast(name.len)));
+    try writer.writeAll(name);
+
+    try writer.writeByte(@intFromEnum(std.wasm.ExternalKind.memory));
+    try emitLimits(writer, memory_import.limits());
+}
+
+pub fn emitInit(writer: anytype, init_expr: std.wasm.InitExpression) !void {
+    switch (init_expr) {
+        .i32_const => |val| {
+            try writer.writeByte(@intFromEnum(std.wasm.Opcode.i32_const));
+            try leb.writeIleb128(writer, val);
+        },
+        .i64_const => |val| {
+            try writer.writeByte(@intFromEnum(std.wasm.Opcode.i64_const));
+            try leb.writeIleb128(writer, val);
+        },
+        .f32_const => |val| {
+            try writer.writeByte(@intFromEnum(std.wasm.Opcode.f32_const));
+            try writer.writeInt(u32, @bitCast(val), .little);
+        },
+        .f64_const => |val| {
+            try writer.writeByte(@intFromEnum(std.wasm.Opcode.f64_const));
+            try writer.writeInt(u64, @bitCast(val), .little);
+        },
+        .global_get => |val| {
+            try writer.writeByte(@intFromEnum(std.wasm.Opcode.global_get));
+            try leb.writeUleb128(writer, val);
+        },
+    }
+    try writer.writeByte(@intFromEnum(std.wasm.Opcode.end));
+}
+
+//fn emitLinkSection(
+//    wasm: *Wasm,
+//    binary_bytes: *std.ArrayListUnmanaged(u8),
+//    symbol_table: *std.AutoArrayHashMapUnmanaged(SymbolLoc, u32),
+//) !void {
+//    const gpa = wasm.base.comp.gpa;
+//    const offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+//    const writer = binary_bytes.writer();
+//    // emit "linking" custom section name
+//    const section_name = "linking";
+//    try leb.writeUleb128(writer, section_name.len);
+//    try writer.writeAll(section_name);
+//
+//    // meta data version, which is currently '2'
+//    try leb.writeUleb128(writer, @as(u32, 2));
+//
+//    // For each subsection type (found in Subsection) we can emit a section.
+//    // Currently, we only support emitting segment info and the symbol table.
+//    try wasm.emitSymbolTable(binary_bytes, symbol_table);
+//    try wasm.emitSegmentInfo(binary_bytes);
+//
+//    const size: u32 = @intCast(binary_bytes.items.len - offset - 6);
+//    try writeCustomSectionHeader(binary_bytes.items, offset, size);
+//}
+
+fn emitSegmentInfo(wasm: *Wasm, binary_bytes: *std.ArrayList(u8)) !void {
+    const writer = binary_bytes.writer();
+    try leb.writeUleb128(writer, @intFromEnum(Wasm.SubsectionType.segment_info));
+    const segment_offset = binary_bytes.items.len;
+
+    try leb.writeUleb128(writer, @as(u32, @intCast(wasm.segment_info.count())));
+    for (wasm.segment_info.values()) |segment_info| {
+        log.debug("Emit segment: {s} align({d}) flags({b})", .{
+            segment_info.name,
+            segment_info.alignment,
+            segment_info.flags,
+        });
+        try leb.writeUleb128(writer, @as(u32, @intCast(segment_info.name.len)));
+        try writer.writeAll(segment_info.name);
+        try leb.writeUleb128(writer, segment_info.alignment.toLog2Units());
+        try leb.writeUleb128(writer, segment_info.flags);
+    }
+
+    var buf: [5]u8 = undefined;
+    leb.writeUnsignedFixed(5, &buf, @as(u32, @intCast(binary_bytes.items.len - segment_offset)));
+    try binary_bytes.insertSlice(segment_offset, &buf);
+}
+
+//fn emitSymbolTable(
+//    wasm: *Wasm,
+//    binary_bytes: *std.ArrayListUnmanaged(u8),
+//    symbol_table: *std.AutoArrayHashMapUnmanaged(SymbolLoc, u32),
+//) !void {
+//    const gpa = wasm.base.comp.gpa;
+//    const writer = binary_bytes.writer(gpa);
+//
+//    try leb.writeUleb128(writer, @intFromEnum(SubsectionType.symbol_table));
+//    const table_offset = binary_bytes.items.len;
+//
+//    var symbol_count: u32 = 0;
+//    for (wasm.resolved_symbols.keys()) |sym_loc| {
+//        const symbol = wasm.finalSymbolByLoc(sym_loc).*;
+//        if (symbol.tag == .dead) continue;
+//        try symbol_table.putNoClobber(gpa, sym_loc, symbol_count);
+//        symbol_count += 1;
+//        log.debug("emit symbol: {}", .{symbol});
+//        try leb.writeUleb128(writer, @intFromEnum(symbol.tag));
+//        try leb.writeUleb128(writer, symbol.flags);
+//
+//        const sym_name = wasm.symbolLocName(sym_loc);
+//        switch (symbol.tag) {
+//            .data => {
+//                try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len)));
+//                try writer.writeAll(sym_name);
+//
+//                if (!symbol.flags.undefined) {
+//                    try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.data_out));
+//                    const atom_index = wasm.symbol_atom.get(sym_loc).?;
+//                    const atom = wasm.getAtom(atom_index);
+//                    try leb.writeUleb128(writer, @as(u32, atom.offset));
+//                    try leb.writeUleb128(writer, @as(u32, atom.code.len));
+//                }
+//            },
+//            .section => {
+//                try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.section));
+//            },
+//            .function => {
+//                if (symbol.flags.undefined) {
+//                    try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.function_import));
+//                } else {
+//                    try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.function));
+//                    try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len)));
+//                    try writer.writeAll(sym_name);
+//                }
+//            },
+//            .global => {
+//                if (symbol.flags.undefined) {
+//                    try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.global_import));
+//                } else {
+//                    try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.global));
+//                    try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len)));
+//                    try writer.writeAll(sym_name);
+//                }
+//            },
+//            .table => {
+//                if (symbol.flags.undefined) {
+//                    try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.table_import));
+//                } else {
+//                    try leb.writeUleb128(writer, @intFromEnum(symbol.pointee.table));
+//                    try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len)));
+//                    try writer.writeAll(sym_name);
+//                }
+//            },
+//            .event => unreachable,
+//            .dead => unreachable,
+//            .uninitialized => unreachable,
+//        }
+//    }
+//
+//    var buf: [10]u8 = undefined;
+//    leb.writeUnsignedFixed(5, buf[0..5], @intCast(binary_bytes.items.len - table_offset + 5));
+//    leb.writeUnsignedFixed(5, buf[5..], symbol_count);
+//    try binary_bytes.insertSlice(table_offset, &buf);
+//}
+
+///// Resolves the relocations within the atom, writing the new value
+///// at the calculated offset.
+//fn resolveAtomRelocs(wasm: *const Wasm, atom: *Atom) void {
+//    const symbol_name = wasm.symbolLocName(atom.symbolLoc());
+//    log.debug("resolving {d} relocs in atom '{s}'", .{ atom.relocs.len, symbol_name });
+//
+//    for (atom.relocSlice(wasm)) |reloc| {
+//        const value = atomRelocationValue(wasm, atom, reloc);
+//        log.debug("relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{
+//            wasm.symbolLocName(.{
+//                .file = atom.file,
+//                .index = @enumFromInt(reloc.index),
+//            }),
+//            symbol_name,
+//            reloc.offset,
+//            value,
+//        });
+//
+//        switch (reloc.tag) {
+//            .TABLE_INDEX_I32,
+//            .FUNCTION_OFFSET_I32,
+//            .GLOBAL_INDEX_I32,
+//            .MEMORY_ADDR_I32,
+//            .SECTION_OFFSET_I32,
+//            => mem.writeInt(u32, atom.code.slice(wasm)[reloc.offset - atom.original_offset ..][0..4], @as(u32, @truncate(value)), .little),
+//
+//            .TABLE_INDEX_I64,
+//            .MEMORY_ADDR_I64,
+//            => mem.writeInt(u64, atom.code.slice(wasm)[reloc.offset - atom.original_offset ..][0..8], value, .little),
+//
+//            .GLOBAL_INDEX_LEB,
+//            .EVENT_INDEX_LEB,
+//            .FUNCTION_INDEX_LEB,
+//            .MEMORY_ADDR_LEB,
+//            .MEMORY_ADDR_SLEB,
+//            .TABLE_INDEX_SLEB,
+//            .TABLE_NUMBER_LEB,
+//            .TYPE_INDEX_LEB,
+//            .MEMORY_ADDR_TLS_SLEB,
+//            => leb.writeUnsignedFixed(5, atom.code.slice(wasm)[reloc.offset - atom.original_offset ..][0..5], @as(u32, @truncate(value))),
+//
+//            .MEMORY_ADDR_LEB64,
+//            .MEMORY_ADDR_SLEB64,
+//            .TABLE_INDEX_SLEB64,
+//            .MEMORY_ADDR_TLS_SLEB64,
+//            => leb.writeUnsignedFixed(10, atom.code.slice(wasm)[reloc.offset - atom.original_offset ..][0..10], value),
+//        }
+//    }
+//}
+
+///// From a given `relocation` will return the new value to be written.
+///// All values will be represented as a `u64` as all values can fit within it.
+///// The final value must be casted to the correct size.
+//fn atomRelocationValue(wasm: *const Wasm, atom: *const Atom, relocation: *const Relocation) u64 {
+//    if (relocation.tag == .TYPE_INDEX_LEB) {
+//        // Eagerly resolved when parsing the object file.
+//        if (true) @panic("TODO the eager resolve when parsing");
+//        return relocation.index;
+//    }
+//    const target_loc = wasm.symbolLocFinalLoc(.{
+//        .file = atom.file,
+//        .index = @enumFromInt(relocation.index),
+//    });
+//    const symbol = wasm.finalSymbolByLoc(target_loc);
+//    if (symbol.tag != .section and !symbol.flags.alive) {
+//        const val = atom.tombstone(wasm) orelse relocation.addend;
+//        return @bitCast(val);
+//    }
+//    return switch (relocation.tag) {
+//        .FUNCTION_INDEX_LEB => if (symbol.flags.undefined)
+//            @intFromEnum(symbol.pointee.function_import)
+//        else
+//            @intFromEnum(symbol.pointee.function) + wasm.function_imports.items.len,
+//        .TABLE_NUMBER_LEB => if (symbol.flags.undefined)
+//            @intFromEnum(symbol.pointee.table_import)
+//        else
+//            @intFromEnum(symbol.pointee.table) + wasm.table_imports.items.len,
+//        .TABLE_INDEX_I32,
+//        .TABLE_INDEX_I64,
+//        .TABLE_INDEX_SLEB,
+//        .TABLE_INDEX_SLEB64,
+//        => wasm.function_table.get(.{ .file = atom.file, .index = @enumFromInt(relocation.index) }) orelse 0,
+//
+//        .TYPE_INDEX_LEB => unreachable, // handled above
+//        .GLOBAL_INDEX_I32, .GLOBAL_INDEX_LEB => if (symbol.flags.undefined)
+//            @intFromEnum(symbol.pointee.global_import)
+//        else
+//            @intFromEnum(symbol.pointee.global) + wasm.global_imports.items.len,
+//
+//        .MEMORY_ADDR_I32,
+//        .MEMORY_ADDR_I64,
+//        .MEMORY_ADDR_LEB,
+//        .MEMORY_ADDR_LEB64,
+//        .MEMORY_ADDR_SLEB,
+//        .MEMORY_ADDR_SLEB64,
+//        => {
+//            assert(symbol.tag == .data);
+//            if (symbol.flags.undefined) return 0;
+//            const va: i33 = symbol.virtual_address;
+//            return @intCast(va + relocation.addend);
+//        },
+//        .EVENT_INDEX_LEB => @panic("TODO: expose this as an error, events are unsupported"),
+//        .SECTION_OFFSET_I32 => {
+//            const target_atom_index = wasm.symbol_atom.get(target_loc).?;
+//            const target_atom = wasm.getAtom(target_atom_index);
+//            const rel_value: i33 = target_atom.offset;
+//            return @intCast(rel_value + relocation.addend);
+//        },
+//        .FUNCTION_OFFSET_I32 => {
+//            if (symbol.flags.undefined) {
+//                const val = atom.tombstone(wasm) orelse relocation.addend;
+//                return @bitCast(val);
+//            }
+//            const target_atom_index = wasm.symbol_atom.get(target_loc).?;
+//            const target_atom = wasm.getAtom(target_atom_index);
+//            const rel_value: i33 = target_atom.offset;
+//            return @intCast(rel_value + relocation.addend);
+//        },
+//        .MEMORY_ADDR_TLS_SLEB,
+//        .MEMORY_ADDR_TLS_SLEB64,
+//        => {
+//            const va: i33 = symbol.virtual_address;
+//            return @intCast(va + relocation.addend);
+//        },
+//    };
+//}
+
+///// For a given `Atom` returns whether it has a tombstone value or not.
+///// This defines whether we want a specific value when a section is dead.
+//fn tombstone(atom: Atom, wasm: *const Wasm) ?i64 {
+//    const atom_name = wasm.finalSymbolByLoc(atom.symbolLoc()).name;
+//    if (atom_name == wasm.custom_sections.@".debug_ranges".name or
+//        atom_name == wasm.custom_sections.@".debug_loc".name)
+//    {
+//        return -2;
+//    } else if (mem.startsWith(u8, atom_name.slice(wasm), ".debug_")) {
+//        return -1;
+//    } else {
+//        return null;
+//    }
+//}
+
+fn getUleb128Size(uint_value: anytype) u32 {
+    const T = @TypeOf(uint_value);
+    const U = if (@typeInfo(T).int.bits < 8) u8 else T;
+    var value = @as(U, @intCast(uint_value));
+
+    var size: u32 = 0;
+    while (value != 0) : (size += 1) {
+        value >>= 7;
+    }
+    return size;
+}
src/link/Wasm/Object.zig
@@ -1,23 +1,16 @@
-//! Object represents a wasm object file. When initializing a new
-//! `Object`, it will parse the contents of a given file handler, and verify
-//! the data on correctness. The result can then be used by the linker.
 const Object = @This();
 
 const Wasm = @import("../Wasm.zig");
-const Atom = Wasm.Atom;
 const Alignment = Wasm.Alignment;
-const Symbol = @import("Symbol.zig");
 
 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);
+const assert = std.debug.assert;
 
 /// Wasm spec version used for this `Object`
-version: u32 = 0,
+version: u32,
 /// For error reporting purposes only.
 /// Name (read path) of the object or archive file.
 path: Path,
@@ -25,817 +18,969 @@ path: Path,
 /// 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
-imports: []const Wasm.Import = &.{},
-/// Parsed function section
-functions: []const std.wasm.Func = &.{},
-/// Parsed table section
-tables: []const std.wasm.Table = &.{},
-/// Parsed memory section
-memories: []const std.wasm.Memory = &.{},
-/// Parsed global section
-globals: []const std.wasm.Global = &.{},
-/// Parsed export section
-exports: []const Wasm.Export = &.{},
-/// Parsed element section
-elements: []const std.wasm.Element = &.{},
 /// Represents the function ID that must be called on startup.
 /// This is `null` by default as runtimes may determine the startup
 /// function themselves. This is essentially legacy.
-start: ?u32 = null,
-/// A slice of features that tell the linker what features are mandatory,
-/// used (or therefore missing) and must generate an error when another
-/// object uses features that are not supported by the other.
-features: []const Wasm.Feature = &.{},
-/// A table that maps the relocations we must perform where the key represents
-/// the section that the list of relocations applies to.
-relocations: std.AutoArrayHashMapUnmanaged(u32, []Wasm.Relocation) = .empty,
-/// Table of symbols belonging to this Object file
-symtable: []Symbol = &.{},
-/// Extra metadata about the linking section, such as alignment of segments and their name
-segment_info: []const Wasm.NamedSegment = &.{},
-/// A sequence of function initializers that must be called on startup
-init_funcs: []const Wasm.InitFunc = &.{},
-/// Comdat information
-comdat_info: []const Wasm.Comdat = &.{},
-/// Represents non-synthetic sections that can essentially be mem-cpy'd into place
-/// after performing relocations.
-relocatable_data: std.AutoHashMapUnmanaged(RelocatableData.Tag, []RelocatableData) = .empty,
-/// Amount of functions in the `import` sections.
-imported_functions_count: u32 = 0,
-/// Amount of globals in the `import` section.
-imported_globals_count: u32 = 0,
-/// Amount of tables in the `import` section.
-imported_tables_count: u32 = 0,
-
-/// Represents a single item within a section (depending on its `type`)
-pub const RelocatableData = struct {
-    /// The type of the relocatable data
-    type: Tag,
-    /// Pointer to the data of the segment, where its length is written to `size`
-    data: [*]u8,
-    /// The size in bytes of the data representing the segment within the section
-    size: u32,
-    /// The index within the section itself, or in case of a debug section,
-    /// the offset within the `string_table`.
-    index: u32,
-    /// The offset within the section where the data starts
-    offset: u32,
-    /// Represents the index of the section it belongs to
-    section_index: u32,
-    /// Whether the relocatable section is represented by a symbol or not.
-    /// Can only be `true` for custom sections.
-    represented: bool = false,
-
-    const Tag = enum { data, code, custom };
-
-    /// Returns the alignment of the segment, by retrieving it from the segment
-    /// meta data of the given object file.
-    /// NOTE: Alignment is encoded as a power of 2, so we shift the symbol's
-    /// alignment to retrieve the natural alignment.
-    pub fn getAlignment(relocatable_data: RelocatableData, object: *const Object) Alignment {
-        if (relocatable_data.type != .data) return .@"1";
-        return object.segment_info[relocatable_data.index].alignment;
-    }
-
-    /// Returns the symbol kind that corresponds to the relocatable section
-    pub fn getSymbolKind(relocatable_data: RelocatableData) Symbol.Tag {
-        return switch (relocatable_data.type) {
-            .data => .data,
-            .code => .function,
-            .custom => .section,
-        };
-    }
-
-    /// Returns the index within a section, or in case of a custom section,
-    /// returns the section index within the object file.
-    pub fn getIndex(relocatable_data: RelocatableData) u32 {
-        if (relocatable_data.type == .custom) return relocatable_data.section_index;
-        return relocatable_data.index;
-    }
+start_function: Wasm.OptionalObjectFunctionIndex,
+/// A slice of features that tell the linker what features are mandatory, used
+/// (or therefore missing) and must generate an error when another object uses
+/// features that are not supported by the other.
+features: Wasm.Feature.Set,
+/// Points into Wasm functions
+functions: RelativeSlice,
+/// Points into Wasm object_globals_imports
+globals_imports: RelativeSlice,
+/// Points into Wasm object_tables_imports
+tables_imports: RelativeSlice,
+/// Points into Wasm object_custom_segments
+custom_segments: RelativeSlice,
+/// For calculating local section index from `Wasm.SectionIndex`.
+local_section_index_base: u32,
+/// Points into Wasm object_init_funcs
+init_funcs: RelativeSlice,
+/// Points into Wasm object_comdats
+comdats: RelativeSlice,
+
+pub const RelativeSlice = struct {
+    off: u32,
+    len: u32,
 };
 
-/// 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: *Wasm,
-    file_contents: []const u8,
-    path: Path,
-    archive_member_name: ?[]const u8,
-) !Object {
-    const gpa = wasm.base.comp.gpa;
-    var object: Object = .{
-        .path = path,
-        .archive_member_name = archive_member_name,
+pub const SegmentInfo = struct {
+    name: Wasm.String,
+    flags: Flags,
+
+    const Flags = packed struct(u32) {
+        /// Signals that the segment contains only null terminated strings allowing
+        /// the linker to perform merging.
+        strings: bool,
+        /// The segment contains thread-local data. This means that a unique copy
+        /// of this segment will be created for each thread.
+        tls: bool,
+        /// If the object file is included in the final link, the segment should be
+        /// retained in the final output regardless of whether it is used by the
+        /// program.
+        retain: bool,
+        alignment: Alignment,
+
+        _: u23 = 0,
     };
+};
 
-    var parser: Parser = .{
-        .object = &object,
-        .wasm = wasm,
-        .reader = std.io.fixedBufferStream(file_contents),
-    };
-    try parser.parseObject(gpa);
+pub const FunctionImport = struct {
+    module_name: Wasm.String,
+    name: Wasm.String,
+    function_index: ScratchSpace.FuncTypeIndex,
+};
 
-    return object;
-}
+pub const DataSegmentFlags = enum(u32) { active, passive, active_memidx };
 
-/// 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 {
-    for (object.func_types) |func_ty| {
-        gpa.free(func_ty.params);
-        gpa.free(func_ty.returns);
-    }
-    gpa.free(object.func_types);
-    gpa.free(object.functions);
-    gpa.free(object.imports);
-    gpa.free(object.tables);
-    gpa.free(object.memories);
-    gpa.free(object.globals);
-    gpa.free(object.exports);
-    for (object.elements) |el| {
-        gpa.free(el.func_indexes);
-    }
-    gpa.free(object.elements);
-    gpa.free(object.features);
-    for (object.relocations.values()) |val| {
-        gpa.free(val);
-    }
-    object.relocations.deinit(gpa);
-    gpa.free(object.symtable);
-    gpa.free(object.comdat_info);
-    gpa.free(object.init_funcs);
-    for (object.segment_info) |info| {
-        gpa.free(info.name);
-    }
-    gpa.free(object.segment_info);
-    {
-        var it = object.relocatable_data.valueIterator();
-        while (it.next()) |relocatable_data| {
-            for (relocatable_data.*) |rel_data| {
-                gpa.free(rel_data.data[0..rel_data.size]);
-            }
-            gpa.free(relocatable_data.*);
-        }
-    }
-    object.relocatable_data.deinit(gpa);
-    object.* = undefined;
-}
+pub const SubsectionType = enum(u8) {
+    segment_info = 5,
+    init_funcs = 6,
+    comdat_info = 7,
+    symbol_table = 8,
+};
 
-/// Finds the import within the list of imports from a given kind and index of that kind.
-/// Asserts the import exists
-pub fn findImport(object: *const Object, sym: Symbol) Wasm.Import {
-    var i: u32 = 0;
-    return for (object.imports) |import| {
-        if (std.meta.activeTag(import.kind) == sym.tag.externalType()) {
-            if (i == sym.index) return import;
-            i += 1;
-        }
-    } else unreachable; // Only existing imports are allowed to be found
-}
+pub const Symbol = struct {
+    flags: Wasm.SymbolFlags,
+    name: Wasm.OptionalString,
+    pointee: Pointee,
+
+    /// https://github.com/WebAssembly/tool-conventions/blob/df8d737539eb8a8f446ba5eab9dc670c40dfb81e/Linking.md#symbol-table-subsection
+    const Tag = enum(u8) {
+        function,
+        data,
+        global,
+        section,
+        event,
+        table,
+    };
 
-/// Checks if the object file is an MVP version.
-/// When that's the case, we check if there's an import table definition with its name
-/// set to '__indirect_function_table". When that's also the case,
-/// 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: *const Wasm) !?Symbol {
-    const diags = &wasm.base.comp.link_diags;
+    const Pointee = union(enum) {
+        function: Wasm.ObjectFunctionIndex,
+        function_import: ScratchSpace.FuncImportIndex,
+        data: struct {
+            segment_index: Wasm.DataSegment.Index,
+            segment_offset: u32,
+            size: u32,
+        },
+        data_import: void,
+        global: Wasm.ObjectGlobalIndex,
+        global_import: Wasm.ObjectGlobalImportIndex,
+        section: Wasm.ObjectSectionIndex,
+        table: Wasm.ObjectTableIndex,
+        table_import: Wasm.ObjectTableImportIndex,
+    };
+};
 
-    var table_count: usize = 0;
-    for (object.symtable) |sym| {
-        if (sym.tag == .table) table_count += 1;
-    }
+pub const ScratchSpace = struct {
+    func_types: std.ArrayListUnmanaged(Wasm.FunctionType.Index) = .empty,
+    func_type_indexes: std.ArrayListUnmanaged(FuncTypeIndex) = .empty,
+    func_imports: std.ArrayListUnmanaged(FunctionImport) = .empty,
+    symbol_table: std.ArrayListUnmanaged(Symbol) = .empty,
+    segment_info: std.ArrayListUnmanaged(SegmentInfo) = .empty,
 
-    // For each import table, we also have a symbol so this is not a legacy object file
-    if (object.imported_tables_count == table_count) return null;
+    /// Index into `func_imports`.
+    const FuncImportIndex = enum(u32) {
+        _,
 
-    if (table_count != 0) {
-        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,
-        });
-    }
-
-    // MVP object files cannot have any table definitions, only imports (for the indirect function table).
-    if (object.tables.len > 0) {
-        return diags.failParse(object.path, "unexpected table definition without representing table symbols.", .{});
-    }
+        fn ptr(index: FunctionImport, ss: *const ScratchSpace) *FunctionImport {
+            return &ss.func_imports.items[@intFromEnum(index)];
+        }
+    };
 
-    if (object.imported_tables_count != 1) {
-        return diags.failParse(object.path, "found more than one table import, but no representing table symbols", .{});
-    }
+    /// Index into `func_types`.
+    const FuncTypeIndex = enum(u32) {
+        _,
 
-    const table_import: Wasm.Import = for (object.imports) |imp| {
-        if (imp.kind == .table) {
-            break imp;
+        fn ptr(index: FuncTypeIndex, ss: *const ScratchSpace) *Wasm.FunctionType.Index {
+            return &ss.func_types.items[@intFromEnum(index)];
         }
-    } else unreachable;
+    };
 
-    if (table_import.name != wasm.preloaded_strings.__indirect_function_table) {
-        return diags.failParse(object.path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{
-            wasm.stringSlice(table_import.name),
-        });
+    pub fn deinit(ss: *ScratchSpace, gpa: Allocator) void {
+        ss.func_types.deinit(gpa);
+        ss.func_type_indexes.deinit(gpa);
+        ss.func_imports.deinit(gpa);
+        ss.symbol_table.deinit(gpa);
+        ss.segment_info.deinit(gpa);
+        ss.* = undefined;
     }
 
-    var table_symbol: Symbol = .{
-        .flags = 0,
-        .name = table_import.name,
-        .tag = .table,
-        .index = 0,
-        .virtual_address = undefined,
-    };
-    table_symbol.setFlag(.WASM_SYM_UNDEFINED);
-    table_symbol.setFlag(.WASM_SYM_NO_STRIP);
-    return table_symbol;
-}
+    fn clear(ss: *ScratchSpace) void {
+        ss.func_types.clearRetainingCapacity();
+        ss.func_type_indexes.clearRetainingCapacity();
+        ss.func_imports.clearRetainingCapacity();
+        ss.symbol_table.clearRetainingCapacity();
+        ss.segment_info.clearRetainingCapacity();
+    }
+};
 
-const Parser = struct {
-    reader: std.io.FixedBufferStream([]const u8),
-    /// Object file we're building
-    object: *Object,
-    /// Mutable so that the string table can be modified.
+fn parse(
     wasm: *Wasm,
+    bytes: []const u8,
+    path: Path,
+    archive_member_name: ?[]const u8,
+    host_name: Wasm.String,
+    ss: *ScratchSpace,
+    must_link: bool,
+    gc_sections: bool,
+) anyerror!Object {
+    const gpa = wasm.base.comp.gpa;
+    const diags = &wasm.base.comp.link_diags;
 
-    fn parseObject(parser: *Parser, gpa: Allocator) anyerror!void {
-        const wasm = parser.wasm;
-
-        {
-            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;
-        }
-
-        const version = try parser.reader.reader().readInt(u32, .little);
-        parser.object.version = version;
-
-        var saw_linking_section = false;
-
-        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")) {
-                        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 = @intFromEnum(try wasm.internString(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) };
+    var pos: usize = 0;
+
+    if (!std.mem.eql(u8, bytes[0..std.wasm.magic.len], &std.wasm.magic)) return error.BadObjectMagic;
+    pos += std.wasm.magic.len;
+
+    const version = std.mem.readInt(u32, bytes[pos..][0..4], .little);
+    pos += 4;
+
+    const data_segment_start: u32 = @intCast(wasm.object_data_segments.items.len);
+    const custom_segment_start: u32 = @intCast(wasm.object_custom_segments.items.len);
+    const imports_start: u32 = @intCast(wasm.object_imports.items.len);
+    const functions_start: u32 = @intCast(wasm.object_functions.items.len);
+    const tables_start: u32 = @intCast(wasm.object_tables.items.len);
+    const memories_start: u32 = @intCast(wasm.object_memories.items.len);
+    const globals_start: u32 = @intCast(wasm.object_globals.items.len);
+    const init_funcs_start: u32 = @intCast(wasm.object_init_funcs.items.len);
+    const comdats_start: u32 = @intCast(wasm.object_comdats.items.len);
+    const global_imports_start: u32 = @intCast(wasm.object_global_imports.items.len);
+    const table_imports_start: u32 = @intCast(wasm.object_table_imports.items.len);
+    const local_section_index_base = wasm.object_total_sections;
+    const source_location: Wasm.SourceLocation = .fromObjectIndex(wasm.objects.items.len);
+
+    ss.clear();
+
+    var start_function: Wasm.OptionalObjectFunctionIndex = .none;
+    var opt_features: ?Wasm.Feature.Set = null;
+    var saw_linking_section = false;
+    var has_tls = false;
+    var local_section_index: u32 = 0;
+    var table_count: usize = 0;
+    while (pos < bytes.len) : (local_section_index += 1) {
+        const section_index: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section_index);
+
+        const section_tag: std.wasm.Section = @enumFromInt(bytes[pos]);
+        pos += 1;
+
+        const len, pos = readLeb(u32, bytes, pos);
+        const section_end = pos + len;
+        switch (section_tag) {
+            .custom => {
+                const section_name, pos = readBytes(bytes, pos);
+                if (std.mem.eql(u8, section_name, "linking")) {
+                    saw_linking_section = true;
+                    const section_version, pos = readLeb(u32, bytes, pos);
+                    log.debug("link meta data version: {d}", .{section_version});
+                    if (section_version != 2) return error.UnsupportedVersion;
+                    while (pos < section_end) {
+                        const sub_type, pos = readLeb(u8, bytes, pos);
+                        log.debug("found subsection: {s}", .{@tagName(@as(SubsectionType, @enumFromInt(sub_type)))});
+                        const payload_len, pos = readLeb(u32, bytes, pos);
+                        if (payload_len == 0) break;
+
+                        const count, pos = readLeb(u32, bytes, pos);
+
+                        switch (@as(SubsectionType, @enumFromInt(sub_type))) {
+                            .segment_info => {
+                                for (try ss.segment_info.addManyAsSlice(gpa, count)) |*segment| {
+                                    const name, pos = readBytes(bytes, pos);
+                                    const alignment, pos = readLeb(u32, bytes, pos);
+                                    const flags_u32, pos = readLeb(u32, bytes, pos);
+                                    const flags: SegmentInfo.Flags = @bitCast(flags_u32);
+                                    const tls = flags.tls or
+                                        // Supports legacy object files that specified
+                                        // being TLS by the name instead of the TLS flag.
+                                        std.mem.startsWith(u8, name, ".tdata") or
+                                        std.mem.startsWith(u8, name, ".tbss");
+                                    has_tls = has_tls or tls;
+                                    segment.* = .{
+                                        .name = try wasm.internString(name),
+                                        .flags = .{
+                                            .strings = flags.strings,
+                                            .tls = tls,
+                                            .alignment = @enumFromInt(alignment),
+                                            .no_strip = flags.retain,
+                                        },
+                                    };
+                                }
                             },
-                            .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_funcs => {
+                                for (try wasm.object_init_funcs.addManyAsSlice(gpa, count)) |*func| {
+                                    const priority, pos = readLeb(u32, bytes, pos);
+                                    const symbol_index, pos = readLeb(u32, bytes, pos);
+                                    if (symbol_index > ss.symbol_table.items.len)
+                                        return diags.failParse(path, "init_funcs before symbol table", .{});
+                                    const sym = &ss.symbol_table.items[symbol_index];
+                                    if (sym.tag != .function) {
+                                        return diags.failParse(path, "init_func symbol '{s}' not a function", .{
+                                            wasm.stringSlice(sym.name),
+                                        });
+                                    } else if (sym.flags.undefined) {
+                                        return diags.failParse(path, "init_func symbol '{s}' is an import", .{
+                                            wasm.stringSlice(sym.name),
+                                        });
+                                    }
+                                    func.* = .{
+                                        .priority = priority,
+                                        .function_index = sym.pointee.function,
+                                    };
+                                }
                             },
-                            .table => val: {
-                                parser.object.imported_tables_count += 1;
-                                break :val .{ .table = .{
-                                    .reftype = try readEnum(std.wasm.RefType, reader),
-                                    .limits = try readLimits(reader),
-                                } };
+                            .comdat_info => {
+                                for (try wasm.object_comdats.addManyAsSlice(gpa, count)) |*comdat| {
+                                    const name, pos = readBytes(bytes, pos);
+                                    const flags, pos = readLeb(u32, bytes, pos);
+                                    if (flags != 0) return error.UnexpectedComdatFlags;
+                                    const symbol_count, pos = readLeb(u32, bytes, pos);
+                                    const start_off: u32 = @intCast(wasm.object_comdat_symbols.items.len);
+                                    for (try wasm.object_comdat_symbols.addManyAsSlice(gpa, symbol_count)) |*symbol| {
+                                        const kind, pos = readEnum(Wasm.Comdat.Symbol.Type, bytes, pos);
+                                        const index, pos = readLeb(u32, bytes, pos);
+                                        if (true) @panic("TODO rebase index depending on kind");
+                                        symbol.* = .{
+                                            .kind = kind,
+                                            .index = index,
+                                        };
+                                    }
+                                    comdat.* = .{
+                                        .name = try wasm.internString(name),
+                                        .flags = flags,
+                                        .symbols = .{
+                                            .off = start_off,
+                                            .len = @intCast(wasm.object_comdat_symbols.items.len - start_off),
+                                        },
+                                    };
+                                }
                             },
-                        };
-
-                        import.* = .{
-                            .module_name = try wasm.internString(module_name),
-                            .name = try wasm.internString(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 = .{
-                                .valtype = try readEnum(std.wasm.Valtype, reader),
-                                .mutable = (try reader.readByte()) == 0x01,
+                            .symbol_table => {
+                                for (try ss.symbol_table.addManyAsSlice(gpa, count)) |*symbol| {
+                                    const tag, pos = readEnum(Symbol.Tag, bytes, pos);
+                                    const flags, pos = readLeb(u32, bytes, pos);
+                                    symbol.* = .{
+                                        .flags = @bitCast(flags),
+                                        .name = .none,
+                                        .pointee = undefined,
+                                    };
+                                    symbol.flags.initZigSpecific(must_link, gc_sections);
+
+                                    switch (tag) {
+                                        .data => {
+                                            const name, pos = readBytes(bytes, pos);
+                                            symbol.name = (try wasm.internString(name)).toOptional();
+                                            if (symbol.flags.undefined) {
+                                                symbol.pointee = .data_import;
+                                            } else {
+                                                const segment_index, pos = readLeb(u32, bytes, pos);
+                                                const segment_offset, pos = readLeb(u32, bytes, pos);
+                                                const size, pos = readLeb(u32, bytes, pos);
+
+                                                symbol.pointee = .{ .data = .{
+                                                    .index = @enumFromInt(data_segment_start + segment_index),
+                                                    .segment_offset = segment_offset,
+                                                    .size = size,
+                                                } };
+                                            }
+                                        },
+                                        .section => {
+                                            const local_section, pos = readLeb(u32, bytes, pos);
+                                            const section: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section);
+                                            symbol.pointee = .{ .section = section };
+                                        },
+
+                                        .function => {
+                                            const local_index, pos = readLeb(u32, bytes, pos);
+                                            if (symbol.flags.undefined) {
+                                                symbol.pointee = .{ .function_import = @enumFromInt(local_index) };
+                                                if (flags.explicit_name) {
+                                                    const name, pos = readBytes(bytes, pos);
+                                                    symbol.name = (try wasm.internString(name)).toOptional();
+                                                }
+                                            } else {
+                                                symbol.pointee = .{ .function = @enumFromInt(functions_start + local_index) };
+                                                const name, pos = readBytes(bytes, pos);
+                                                symbol.name = (try wasm.internString(name)).toOptional();
+                                            }
+                                        },
+                                        .global => {
+                                            const local_index, pos = readLeb(u32, bytes, pos);
+                                            if (symbol.flags.undefined) {
+                                                symbol.pointee = .{ .global_import = @enumFromInt(global_imports_start + local_index) };
+                                                if (flags.explicit_name) {
+                                                    const name, pos = readBytes(bytes, pos);
+                                                    symbol.name = (try wasm.internString(name)).toOptional();
+                                                }
+                                            } else {
+                                                symbol.pointee = .{ .global = @enumFromInt(globals_start + local_index) };
+                                                const name, pos = readBytes(bytes, pos);
+                                                symbol.name = (try wasm.internString(name)).toOptional();
+                                            }
+                                        },
+                                        .table => {
+                                            table_count += 1;
+                                            const local_index, pos = readLeb(u32, bytes, pos);
+                                            if (symbol.flags.undefined) {
+                                                symbol.pointee = .{ .table_import = @enumFromInt(table_imports_start + local_index) };
+                                                if (flags.explicit_name) {
+                                                    const name, pos = readBytes(bytes, pos);
+                                                    symbol.name = (try wasm.internString(name)).toOptional();
+                                                }
+                                            } else {
+                                                symbol.pointee = .{ .table = @enumFromInt(tables_start + local_index) };
+                                                const name, pos = readBytes(bytes, pos);
+                                                symbol.name = (try wasm.internString(name)).toOptional();
+                                            }
+                                        },
+                                        else => {
+                                            log.debug("unrecognized symbol type tag: {x}", .{tag});
+                                            return error.UnrecognizedSymbolType;
+                                        },
+                                    }
+                                    log.debug("found symbol: {}", .{symbol});
+                                }
                             },
-                            .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 wasm.internString(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,
-                        });
+                } else if (std.mem.startsWith(u8, section_name, "reloc.")) {
+                    // 'The "reloc." custom sections must come after the "linking" custom section'
+                    if (!saw_linking_section) return error.RelocBeforeLinkingSection;
+
+                    // "Relocation sections start with an identifier specifying
+                    // which section they apply to, and must be sequenced in
+                    // the module after that section."
+                    // "Relocation sections can only target code, data and custom sections."
+                    const local_section, pos = readLeb(u32, bytes, pos);
+                    const count, pos = readLeb(u32, bytes, pos);
+                    const section: Wasm.SectionIndex = @enumFromInt(local_section_index_base + local_section);
+
+                    log.debug("found {d} relocations for section={d}", .{ count, section });
+
+                    var prev_offset: u32 = 0;
+                    try wasm.relocations.ensureUnusedCapacity(gpa, count);
+                    for (0..count) |_| {
+                        const tag: Wasm.Relocation.Tag = @enumFromInt(bytes[pos]);
+                        pos += 1;
+                        const offset, pos = readLeb(u32, bytes, pos);
+                        const index, pos = readLeb(u32, bytes, pos);
+
+                        if (offset < prev_offset)
+                            return diags.failParse(path, "relocation entries not sorted by offset", .{});
+                        prev_offset = offset;
+
+                        switch (tag) {
+                            .MEMORY_ADDR_LEB,
+                            .MEMORY_ADDR_SLEB,
+                            .MEMORY_ADDR_I32,
+                            .MEMORY_ADDR_REL_SLEB,
+                            .MEMORY_ADDR_LEB64,
+                            .MEMORY_ADDR_SLEB64,
+                            .MEMORY_ADDR_I64,
+                            .MEMORY_ADDR_REL_SLEB64,
+                            .MEMORY_ADDR_TLS_SLEB,
+                            .MEMORY_ADDR_LOCREL_I32,
+                            .MEMORY_ADDR_TLS_SLEB64,
+                            .FUNCTION_OFFSET_I32,
+                            .SECTION_OFFSET_I32,
+                            => {
+                                const addend: i32, pos = readLeb(i32, bytes, pos);
+                                wasm.relocations.appendAssumeCapacity(.{
+                                    .tag = tag,
+                                    .offset = offset,
+                                    .pointee = .{ .section = ss.symbol_table.items[index].pointee.section },
+                                    .addend = addend,
+                                });
+                            },
+                            .TYPE_INDEX_LEB => {
+                                wasm.relocations.appendAssumeCapacity(.{
+                                    .tag = tag,
+                                    .offset = offset,
+                                    .pointee = .{ .type_index = ss.func_types.items[index] },
+                                    .addend = undefined,
+                                });
+                            },
+                            .FUNCTION_INDEX_LEB,
+                            .GLOBAL_INDEX_LEB,
+                            => {
+                                wasm.relocations.appendAssumeCapacity(.{
+                                    .tag = tag,
+                                    .offset = offset,
+                                    .pointee = .{ .symbol_name = ss.symbol_table.items[index].name.unwrap().? },
+                                    .addend = undefined,
+                                });
+                            },
+                        }
                     }
-                    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;
-    }
-
-    /// 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,
-            };
-        }
-    }
-
-    /// 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 wasm = parser.wasm;
-        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),
-                    };
-                    log.debug("Found segment: {s} align({d}) flags({b})", .{
-                        segment.name,
-                        segment.alignment,
-                        segment.flags,
+                    try wasm.object_relocations_table.putNoClobber(gpa, section, .{
+                        .off = @intCast(wasm.relocations.items.len - count),
+                        .len = count,
                     });
-
-                    // 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);
+                } else if (std.mem.eql(u8, section_name, "target_features")) {
+                    opt_features, pos = try parseFeatures(wasm, bytes, pos, path);
+                } else if (std.mem.startsWith(u8, section_name, ".debug")) {
+                    const debug_content = bytes[pos..section_end];
+                    pos = section_end;
+
+                    const data_off: u32 = @enumFromInt(wasm.string_bytes.items.len);
+                    try wasm.string_bytes.appendSlice(gpa, debug_content);
+
+                    try wasm.object_custom_segments.put(gpa, section_index, .{
+                        .data_off = data_off,
+                        .flags = .{
+                            .data_len = @intCast(debug_content.len),
+                            .represented = false, // set when scanning symbol table
+                        },
+                        .section_name = try wasm.internString(section_name),
+                    });
+                } else {
+                    pos = section_end;
+                }
+            },
+            .type => {
+                const func_types_len, pos = readLeb(u32, bytes, pos);
+                for (ss.func_types.addManyAsSlice(gpa, func_types_len)) |*func_type| {
+                    if (bytes[pos] != std.wasm.function_type) return error.ExpectedFuncType;
+                    pos += 1;
+
+                    const params, pos = readBytes(bytes, pos);
+                    const returns, pos = readBytes(bytes, pos);
+                    func_type.* = try wasm.addFuncType(.{
+                        .params = .fromString(try wasm.internString(params)),
+                        .returns = .fromString(try wasm.internString(returns)),
+                    });
+                }
+            },
+            .import => {
+                const imports_len, pos = readLeb(u32, bytes, pos);
+                for (0..imports_len) |_| {
+                    const module_name, pos = readBytes(bytes, pos);
+                    const name, pos = readBytes(bytes, pos);
+                    const kind, pos = readEnum(std.wasm.ExternalKind, bytes, pos);
+                    const interned_module_name = try wasm.internString(module_name);
+                    const interned_name = try wasm.internString(name);
+                    switch (kind) {
+                        .function => {
+                            const function, pos = readLeb(u32, bytes, pos);
+                            try ss.function_imports.append(gpa, .{
+                                .module_name = interned_module_name,
+                                .name = interned_name,
+                                .index = function,
+                            });
+                        },
+                        .memory => {
+                            const limits, pos = readLimits(bytes, pos);
+                            try wasm.object_memory_imports.append(gpa, .{
+                                .module_name = interned_module_name,
+                                .name = interned_name,
+                                .limits_min = limits.min,
+                                .limits_max = limits.max,
+                                .limits_has_max = limits.flags.has_max,
+                                .limits_is_shared = limits.flags.is_shared,
+                            });
+                        },
+                        .global => {
+                            const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
+                            const mutable = bytes[pos] == 0x01;
+                            pos += 1;
+                            try wasm.object_global_imports.append(gpa, .{
+                                .module_name = interned_module_name,
+                                .name = interned_name,
+                                .mutable = mutable,
+                                .valtype = valtype,
+                            });
+                        },
+                        .table => {
+                            const reftype, pos = readEnum(std.wasm.RefType, bytes, pos);
+                            const limits, pos = readLimits(bytes, pos);
+                            try wasm.object_table_imports.append(gpa, .{
+                                .module_name = interned_module_name,
+                                .name = interned_name,
+                                .limits_min = limits.min,
+                                .limits_max = limits.max,
+                                .limits_has_max = limits.flags.has_max,
+                                .limits_is_shared = limits.flags.is_shared,
+                                .reftype = reftype,
+                            });
+                        },
                     }
                 }
-                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),
+            .function => {
+                const functions_len, pos = readLeb(u32, bytes, pos);
+                for (try ss.func_type_indexes.addManyAsSlice(gpa, functions_len)) |*func_type_index| {
+                    func_type_index.*, pos = readLeb(u32, bytes, pos);
+                }
+            },
+            .table => {
+                const tables_len, pos = readLeb(u32, bytes, pos);
+                for (try wasm.object_tables.addManyAsSlice(gpa, tables_len)) |*table| {
+                    const reftype, pos = readEnum(std.wasm.RefType, bytes, pos);
+                    const limits, pos = readLimits(bytes, pos);
+                    table.* = .{
+                        .reftype = reftype,
+                        .limits = limits,
                     };
-                    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,
+            .memory => {
+                const memories_len, pos = readLeb(u32, bytes, pos);
+                for (try wasm.object_memories.addManyAsSlice(gpa, memories_len)) |*memory| {
+                    const limits, pos = readLimits(bytes, pos);
+                    memory.* = .{ .limits = limits };
+                }
+            },
+            .global => {
+                const globals_len, pos = readLeb(u32, bytes, pos);
+                for (try wasm.object_globals.addManyAsSlice(gpa, globals_len)) |*global| {
+                    const valtype, pos = readEnum(std.wasm.Valtype, bytes, pos);
+                    const mutable = bytes[pos] == 0x01;
+                    pos += 1;
+                    const expr, pos = try readInit(wasm, bytes, pos);
+                    global.* = .{
+                        .valtype = valtype,
+                        .mutable = mutable,
+                        .expr = expr,
                     };
                 }
-
-                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),
-                        wasm.stringSlice(symbol.name),
-                        symbol.flags,
-                    });
+            .@"export" => {
+                const exports_len, pos = readLeb(u32, bytes, pos);
+                // TODO: instead, read into scratch space, and then later
+                // add this data as if it were extra symbol table entries,
+                // but allow merging with existing symbol table data if the name matches.
+                for (try wasm.object_exports.addManyAsSlice(gpa, exports_len)) |*exp| {
+                    const name, pos = readBytes(bytes, pos);
+                    const kind: std.wasm.ExternalKind = @enumFromInt(bytes[pos]);
+                    pos += 1;
+                    const index, pos = readLeb(u32, bytes, pos);
+                    const rebased_index = index + switch (kind) {
+                        .function => functions_start,
+                        .table => tables_start,
+                        .memory => memories_start,
+                        .global => globals_start,
+                    };
+                    exp.* = .{
+                        .name = try wasm.internString(name),
+                        .kind = kind,
+                        .index = rebased_index,
+                    };
                 }
-
-                // 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", .{});
+            },
+            .start => {
+                const index, pos = readLeb(u32, bytes, pos);
+                start_function = @enumFromInt(functions_start + index);
+            },
+            .element => {
+                log.warn("unimplemented: element section in {}", .{path});
+                pos = section_end;
+            },
+            .code => {
+                const start = pos;
+                const count, pos = readLeb(u32, bytes, pos);
+                for (try wasm.object_functions.addManyAsSlice(gpa, count)) |*elem| {
+                    const code_len, pos = readLeb(u32, bytes, pos);
+                    const offset: u32 = @intCast(pos - start);
+                    const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..code_len]);
+                    pos += code_len;
+                    elem.* = .{
+                        .flags = .{}, // populated from symbol table
+                        .name = .none, // populated from symbol table
+                        .type_index = undefined, // populated from func_types
+                        .code = payload,
+                        .offset = offset,
+                        .section_index = section_index,
+                        .source_location = source_location,
+                    };
                 }
-
-                // 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) {
-                            const name = wasm.castToString(data.index);
-                            try symbols.append(.{
-                                .name = name,
-                                .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}'", .{
-                                wasm.stringSlice(name),
-                            });
-                        }
+            },
+            .data => {
+                const start = pos;
+                const count, pos = readLeb(u32, bytes, pos);
+                for (try wasm.object_data_segments.addManyAsSlice(gpa, count)) |*elem| {
+                    const flags, pos = readEnum(DataSegmentFlags, bytes, pos);
+                    if (flags == .active_memidx) {
+                        const memidx, pos = readLeb(u32, bytes, pos);
+                        if (memidx != 0) return diags.failParse(path, "data section uses mem index {d}", .{memidx});
                     }
+                    //const expr, pos = if (flags != .passive) try readInit(wasm, bytes, pos) else .{ .none, pos };
+                    if (flags != .passive) pos = try skipInit(bytes, pos);
+                    const data_len, pos = readLeb(u32, bytes, pos);
+                    const segment_offset: u32 = @intCast(pos - start);
+                    const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..data_len]);
+                    pos += data_len;
+                    elem.* = .{
+                        .payload = payload,
+                        .segment_offset = segment_offset,
+                        .section_index = section_index,
+                        .name = .none, // Populated from symbol table
+                        .flags = .{}, // Populated from symbol table and segment_info
+                    };
                 }
-
-                parser.object.symtable = try symbols.toOwnedSlice();
             },
+            else => pos = section_end,
         }
+        if (pos != section_end) return error.MalformedSection;
     }
+    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: *Parser, gpa: Allocator, reader: anytype) !Symbol {
-        const wasm = parser.wasm;
-        const tag: 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,
-        };
+    wasm.object_total_sections = local_section_index_base + local_section_index;
 
-        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 wasm.internString(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);
+    if (has_tls) {
+        const cpu_features = wasm.base.comp.root_mod.resolved_target.result.cpu.features;
+        if (!std.Target.wasm.featureSetHas(cpu_features, .atomics))
+            return diags.failParse(path, "object has TLS segment but target CPU feature atomics is disabled", .{});
+        if (!std.Target.wasm.featureSetHas(cpu_features, .bulk_memory))
+            return diags.failParse(path, "object has TLS segment but target CPU feature bulk_memory is disabled", .{});
+    }
+
+    const features = opt_features orelse return error.MissingFeatures;
+    if (true) @panic("iterate features, match against target features");
+
+    // Apply function type information.
+    for (ss.func_types.items, wasm.object_functions.items[functions_start..]) |func_type, *func| {
+        func.type_index = func_type;
+    }
+
+    // Apply symbol table information.
+    for (ss.symbol_table.items) |symbol| switch (symbol.pointee) {
+        .function_import => |index| {
+            const ptr = index.ptr(ss);
+            const name = symbol.name.unwrap().?;
+            if (symbol.flags.binding == .local) {
+                diags.addParseError(path, "local symbol '{s}' references import", .{name.slice(wasm)});
+                continue;
+            }
+            const gop = try wasm.object_function_imports.getOrPut(gpa, name);
+            const fn_ty_index = ptr.function_index.ptr(ss).*;
+            if (gop.found_existing) {
+                if (gop.value_ptr.type != fn_ty_index) {
+                    var err = try diags.addErrorWithNotes(2);
+                    try err.addMsg("symbol '{s}' mismatching function signatures", .{name.slice(wasm)});
+                    try err.addSrcNote(gop.value_ptr.source_location, "imported as {} here", .{gop.value_ptr.type.fmt(wasm)});
+                    try err.addSrcNote(source_location, "imported as {} here", .{fn_ty_index.fmt(wasm)});
+                    continue;
                 }
-            },
-            .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 = wasm.castToString(data.index);
-                        data.represented = true;
-                        break;
-                    }
+                if (gop.value_ptr.module_name != ptr.module_name) {
+                    var err = try diags.addErrorWithNotes(2);
+                    try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
+                    try err.addSrcNote(gop.value_ptr.source_location, "module '{s}' here", .{gop.value_ptr.module_name.slice(wasm)});
+                    try err.addSrcNote(source_location, "module '{s}' here", .{ptr.module_name.slice(wasm)});
+                    continue;
                 }
-            },
-            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 wasm.internString(name);
-                } else parser.object.findImport(symbol).name;
-            },
+                if (symbol.flags.binding == .strong) gop.value_ptr.flags.binding = .strong;
+                if (!symbol.flags.visibility_hidden) gop.value_ptr.flags.visibility_hidden = false;
+                if (symbol.flags.no_strip) gop.value_ptr.flags.no_strip = true;
+            } else {
+                gop.value_ptr.* = .{
+                    .flags = symbol.flags,
+                    .module_name = ptr.module_name,
+                    .source_location = source_location,
+                    .resolution = .unresolved,
+                    .type = fn_ty_index,
+                };
+            }
+        },
+        .function => |index| {
+            assert(!symbol.flags.undefined);
+            const ptr = index.ptr();
+            ptr.name = symbol.name;
+            ptr.flags = symbol.flags;
+            if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
+            const name = symbol.name.unwrap().?;
+            const gop = try wasm.object_function_imports.getOrPut(gpa, name);
+            if (gop.found_existing) {
+                if (gop.value_ptr.type != ptr.type_index) {
+                    var err = try diags.addErrorWithNotes(2);
+                    try err.addMsg("function signature mismatch: {s}", .{name.slice(wasm)});
+                    try err.addSrcNote(gop.value_ptr.source_location, "exported as {} here", .{ptr.type_index.fmt(wasm)});
+                    const word = if (gop.value_ptr.resolution == .none) "imported" else "exported";
+                    try err.addSrcNote(source_location, "{s} as {} here", .{ word, gop.value_ptr.type.fmt(wasm) });
+                    continue;
+                }
+                if (gop.value_ptr.resolution == .none or gop.value_ptr.flags.binding == .weak) {
+                    // Intentional: if they're both weak, take the last one.
+                    gop.value_ptr.source_location = source_location;
+                    gop.value_ptr.module_name = host_name;
+                    gop.value_ptr.resolution = .fromObjectFunction(index);
+                    continue;
+                }
+                var err = try diags.addErrorWithNotes(2);
+                try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
+                try err.addSrcNote(gop.value_ptr.source_location, "exported as {} here", .{ptr.type_index.fmt(wasm)});
+                try err.addSrcNote(source_location, "exported as {} here", .{gop.value_ptr.type.fmt(wasm)});
+                continue;
+            } else {
+                gop.value_ptr.* = .{
+                    .flags = symbol.flags,
+                    .module_name = host_name,
+                    .source_location = source_location,
+                    .resolution = .fromObjectFunction(index),
+                    .type = ptr.type_index,
+                };
+            }
+        },
+
+        inline .global, .global_import, .table, .table_import => |i| {
+            const ptr = i.ptr(wasm);
+            ptr.name = symbol.name;
+            ptr.flags = symbol.flags;
+            if (symbol.flags.undefined and symbol.flags.binding == .local) {
+                const name = wasm.stringSlice(ptr.name.unwrap().?);
+                diags.addParseError(path, "local symbol '{s}' references import", .{name});
+            }
+        },
+        .section => |i| {
+            // Name is provided by the section directly; symbol table does not have it.
+            const ptr = i.ptr(wasm);
+            ptr.flags = symbol.flags;
+            if (symbol.flags.undefined and symbol.flags.binding == .local) {
+                const name = wasm.stringSlice(ptr.name);
+                diags.addParseError(path, "local symbol '{s}' references import", .{name});
+            }
+        },
+        .data_import => {
+            const name = symbol.name.unwrap().?;
+            log.warn("TODO data import '{s}'", .{name.slice(wasm)});
+        },
+        .data => |data| {
+            const ptr = data.ptr(wasm);
+            const is_passive = ptr.flags.is_passive;
+            ptr.name = symbol.name;
+            ptr.flags = symbol.flags;
+            ptr.flags.is_passive = is_passive;
+            ptr.offset = data.segment_offset;
+            ptr.size = data.size;
+        },
+    };
+
+    // Apply segment_info.
+    for (wasm.object_data_segments.items[data_segment_start..], ss.segment_info.items) |*data, info| {
+        data.name = info.name.toOptional();
+        data.flags.strings = info.flags.strings;
+        data.flags.tls = data.flags.tls or info.flags.tls;
+        data.flags.no_strip = info.flags.retain;
+        data.flags.alignment = info.flags.alignment;
+        if (data.flags.undefined and data.flags.binding == .local) {
+            const name = wasm.stringSlice(info.name);
+            diags.addParseError(path, "local symbol '{s}' references import", .{name});
         }
-        return symbol;
     }
-};
 
-/// First reads the count from the reader and then allocate
-/// a slice of ptr child's element type.
-fn readVec(ptr: anytype, reader: anytype, gpa: Allocator) ![]ElementType(@TypeOf(ptr)) {
-    const len = try readLeb(u32, reader);
-    const slice = try gpa.alloc(ElementType(@TypeOf(ptr)), len);
-    ptr.* = slice;
-    return slice;
-}
+    // Check for indirect function table in case of an MVP object file.
+    legacy_indirect_function_table: {
+        const table_imports = wasm.object_table_imports.items[table_imports_start..];
+        // If there is a symbol for each import table, this is not a legacy object file.
+        if (table_imports.len == table_count) break :legacy_indirect_function_table;
+        if (table_count != 0) {
+            return diags.failParse(path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
+                table_imports.len, table_count,
+            });
+        }
+        // MVP object files cannot have any table definitions, only
+        // imports (for the indirect function table).
+        const tables = wasm.object_tables.items[tables_start..];
+        if (tables.len > 0) {
+            return diags.failParse(path, "table definition without representing table symbols", .{});
+        }
+        if (table_imports.len != 1) {
+            return diags.failParse(path, "found more than one table import, but no representing table symbols", .{});
+        }
+        const table_import_name = table_imports[0].name;
+        if (table_import_name != wasm.preloaded_strings.__indirect_function_table) {
+            return diags.failParse(path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{
+                wasm.stringSlice(table_import_name),
+            });
+        }
+        table_imports[0].flags = .{
+            .undefined = true,
+            .no_strip = true,
+        };
+    }
 
-fn ElementType(comptime ptr: type) type {
-    return meta.Elem(meta.Child(ptr));
-}
+    for (wasm.object_init_funcs.items[init_funcs_start..]) |init_func| {
+        const func = init_func.function_index.ptr(wasm);
+        const params = func.type_index.ptr(wasm).params.slice(wasm);
+        if (params.len != 0) diags.addError("constructor function '{s}' has non-empty parameter list", .{
+            func.name.slice(wasm).?,
+        });
+    }
 
-/// Uses either `readIleb128` or `readUleb128` depending on the
-/// signedness of the given type `T`.
-/// Asserts `T` is an integer.
-fn readLeb(comptime T: type, reader: anytype) !T {
-    return switch (@typeInfo(T).int.signedness) {
-        .signed => try leb.readIleb128(T, reader),
-        .unsigned => try leb.readUleb128(T, reader),
+    return .{
+        .version = version,
+        .path = path,
+        .archive_member_name = archive_member_name,
+        .start_function = start_function,
+        .features = features,
+        .imports = .{
+            .off = imports_start,
+            .len = @intCast(wasm.object_imports.items.len - imports_start),
+        },
+        .functions = .{
+            .off = functions_start,
+            .len = @intCast(wasm.functions.items.len - functions_start),
+        },
+        .tables = .{
+            .off = tables_start,
+            .len = @intCast(wasm.object_tables.items.len - tables_start),
+        },
+        .memories = .{
+            .off = memories_start,
+            .len = @intCast(wasm.object_memories.items.len - memories_start),
+        },
+        .globals = .{
+            .off = globals_start,
+            .len = @intCast(wasm.object_globals.items.len - globals_start),
+        },
+        .init_funcs = .{
+            .off = init_funcs_start,
+            .len = @intCast(wasm.object_init_funcs.items.len - init_funcs_start),
+        },
+        .comdats = .{
+            .off = comdats_start,
+            .len = @intCast(wasm.object_comdats.items.len - comdats_start),
+        },
+        .custom_segments = .{
+            .off = custom_segment_start,
+            .len = @intCast(wasm.object_custom_segments.items.len - custom_segment_start),
+        },
+        .local_section_index_base = local_section_index_base,
     };
 }
 
-/// Reads an enum type from the given reader.
-/// Asserts `T` is an enum
-fn readEnum(comptime T: type, reader: anytype) !T {
-    switch (@typeInfo(T)) {
-        .@"enum" => |enum_type| return @as(T, @enumFromInt(try readLeb(enum_type.tag_type, reader))),
-        else => @compileError("T must be an enum. Instead was given type " ++ @typeName(T)),
+/// 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.
+fn parseFeatures(
+    wasm: *Wasm,
+    bytes: []const u8,
+    start_pos: usize,
+    path: Path,
+) error{ OutOfMemory, LinkFailure }!struct { Wasm.Feature.Set, usize } {
+    const gpa = wasm.base.comp.gpa;
+    const diags = &wasm.base.comp.link_diags;
+    const features_len, var pos = readLeb(u32, bytes, start_pos);
+    // This temporary allocation could be avoided by using the string_bytes buffer as a scratch space.
+    const feature_buffer = try gpa.alloc(Wasm.Feature, features_len);
+    defer gpa.free(feature_buffer);
+    for (feature_buffer) |*feature| {
+        const prefix: Wasm.Feature.Prefix = switch (bytes[pos]) {
+            '-' => .@"-",
+            '+' => .@"+",
+            '=' => .@"=",
+            else => return error.InvalidFeaturePrefix,
+        };
+        pos += 1;
+        const name, pos = readBytes(bytes, pos);
+        const tag = std.meta.stringToEnum(Wasm.Feature.Tag, name) orelse {
+            return diags.failParse(path, "unrecognized wasm feature in object: {s}", .{name});
+        };
+        feature.* = .{
+            .prefix = prefix,
+            .tag = tag,
+        };
     }
+    std.mem.sortUnstable(Wasm.Feature, feature_buffer, {}, Wasm.Feature.lessThan);
+
+    return .{
+        .fromString(try wasm.internString(@bitCast(feature_buffer))),
+        pos,
+    };
 }
 
-fn readLimits(reader: anytype) !std.wasm.Limits {
-    const flags = try reader.readByte();
-    const min = try readLeb(u32, reader);
-    var limits: std.wasm.Limits = .{
-        .flags = flags,
-        .min = min,
-        .max = undefined,
+fn readLeb(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } {
+    var fbr = std.io.fixedBufferStream(bytes[pos..]);
+    return .{
+        switch (@typeInfo(T).int.signedness) {
+            .signed => std.leb.readIleb128(T, fbr.reader()) catch unreachable,
+            .unsigned => std.leb.readUleb128(T, fbr.reader()) catch unreachable,
+        },
+        pos + fbr.pos,
     };
-    if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) {
-        limits.max = try readLeb(u32, reader);
-    }
-    return limits;
 }
 
-fn readInit(reader: anytype) !std.wasm.InitExpression {
-    const opcode = try reader.readByte();
-    const init_expr: std.wasm.InitExpression = switch (@as(std.wasm.Opcode, @enumFromInt(opcode))) {
-        .i32_const => .{ .i32_const = try readLeb(i32, reader) },
-        .global_get => .{ .global_get = try readLeb(u32, reader) },
-        else => @panic("TODO: initexpression for other opcodes"),
+fn readBytes(bytes: []const u8, start_pos: usize) struct { []const u8, usize } {
+    const len, const pos = readLeb(u32, bytes, start_pos);
+    return .{
+        bytes[pos..][0..len],
+        pos + len,
     };
+}
 
-    if ((try readEnum(std.wasm.Opcode, reader)) != .end) return error.MissingEndForExpression;
-    return init_expr;
+fn readEnum(comptime T: type, bytes: []const u8, pos: usize) struct { T, usize } {
+    const Tag = @typeInfo(T).@"enum".tag_type;
+    const int, const new_pos = readLeb(Tag, bytes, pos);
+    return .{ @enumFromInt(int), new_pos };
 }
 
-fn assertEnd(reader: anytype) !void {
-    var buf: [1]u8 = undefined;
-    const len = try reader.read(&buf);
-    if (len != 0) return error.MalformedSection;
-    if (reader.context.bytes_left != 0) return error.MalformedSection;
+fn readLimits(bytes: []const u8, start_pos: usize) struct { std.wasm.Limits, usize } {
+    const flags = bytes[start_pos];
+    const min, const max_pos = readLeb(u32, bytes, start_pos + 1);
+    const max, const end_pos = if (flags.has_max) readLeb(u32, bytes, max_pos) else .{ undefined, max_pos };
+    return .{ .{
+        .flags = flags,
+        .min = min,
+        .max = max,
+    }, end_pos };
+}
+
+fn readInit(wasm: *Wasm, bytes: []const u8, pos: usize) !struct { Wasm.Expr, usize } {
+    const end_pos = skipInit(bytes, pos); // one after the end opcode
+    return .{ try wasm.addExpr(bytes[pos..end_pos]), end_pos };
+}
+
+fn skipInit(bytes: []const u8, pos: usize) !usize {
+    const opcode = bytes[pos];
+    const end_pos = switch (@as(std.wasm.Opcode, @enumFromInt(opcode))) {
+        .i32_const => readLeb(i32, bytes, pos + 1)[1],
+        .i64_const => readLeb(i64, bytes, pos + 1)[1],
+        .f32_const => pos + 5,
+        .f64_const => pos + 9,
+        .global_get => readLeb(u32, bytes, pos + 1)[1],
+        else => return error.InvalidInitOpcode,
+    };
+    if (readEnum(std.wasm.Opcode, bytes, end_pos) != .end) return error.InitExprMissingEnd;
+    return end_pos + 1;
 }
src/link/Wasm/Symbol.zig
@@ -1,210 +0,0 @@
-//! Represents a WebAssembly symbol. Containing all of its properties,
-//! as well as providing helper methods to determine its functionality
-//! and how it will/must be linked.
-//! The name of the symbol can be found by providing the offset, found
-//! on the `name` field, to a string table in the wasm binary or object file.
-
-/// Bitfield containings flags for a symbol
-/// Can contain any of the flags defined in `Flag`
-flags: u32,
-/// Symbol name, when the symbol is undefined the name will be taken from the import.
-/// Note: This is an index into the wasm string table.
-name: wasm.String,
-/// Index into the list of objects based on set `tag`
-/// NOTE: This will be set to `undefined` when `tag` is `data`
-/// and the symbol is undefined.
-index: u32,
-/// Represents the kind of the symbol, such as a function or global.
-tag: Tag,
-/// Contains the virtual address of the symbol, relative to the start of its section.
-/// This differs from the offset of an `Atom` which is relative to the start of a segment.
-virtual_address: u32,
-
-/// Represents a symbol index where `null` represents an invalid index.
-pub const Index = enum(u32) {
-    null,
-    _,
-};
-
-pub const Tag = enum {
-    function,
-    data,
-    global,
-    section,
-    event,
-    table,
-    /// synthetic kind used by the wasm linker during incremental compilation
-    /// to notate a symbol has been freed, but still lives in the symbol list.
-    dead,
-    undefined,
-
-    /// From a given symbol tag, returns the `ExternalType`
-    /// Asserts the given tag can be represented as an external type.
-    pub fn externalType(tag: Tag) std.wasm.ExternalKind {
-        return switch (tag) {
-            .function => .function,
-            .global => .global,
-            .data => unreachable, // Data symbols will generate a global
-            .section => unreachable, // Not an external type
-            .event => unreachable, // Not an external type
-            .dead => unreachable, // Dead symbols should not be referenced
-            .undefined => unreachable,
-            .table => .table,
-        };
-    }
-};
-
-pub const Flag = enum(u32) {
-    /// Indicates a weak symbol.
-    /// When linking multiple modules defining the same symbol, all weak definitions are discarded
-    /// in favourite of the strong definition. When no strong definition exists, all weak but one definition is discarded.
-    /// If multiple definitions remain, we get an error: symbol collision.
-    WASM_SYM_BINDING_WEAK = 0x1,
-    /// Indicates a local, non-exported, non-module-linked symbol.
-    /// The names of local symbols are not required to be unique, unlike non-local symbols.
-    WASM_SYM_BINDING_LOCAL = 0x2,
-    /// Represents the binding of a symbol, indicating if it's local or not, and weak or not.
-    WASM_SYM_BINDING_MASK = 0x3,
-    /// Indicates a hidden symbol. Hidden symbols will not be exported to the link result, but may
-    /// link to other modules.
-    WASM_SYM_VISIBILITY_HIDDEN = 0x4,
-    /// Indicates an undefined symbol. For non-data symbols, this must match whether the symbol is
-    /// an import or is defined. For data symbols however, determines whether a segment is specified.
-    WASM_SYM_UNDEFINED = 0x10,
-    /// Indicates a symbol of which its intention is to be exported from the wasm module to the host environment.
-    /// This differs from the visibility flag as this flag affects the static linker.
-    WASM_SYM_EXPORTED = 0x20,
-    /// Indicates the symbol uses an explicit symbol name, rather than reusing the name from a wasm import.
-    /// Allows remapping imports from foreign WASM modules into local symbols with a different name.
-    WASM_SYM_EXPLICIT_NAME = 0x40,
-    /// Indicates the symbol is to be included in the linker output, regardless of whether it is used or has any references to it.
-    WASM_SYM_NO_STRIP = 0x80,
-    /// Indicates a symbol is TLS
-    WASM_SYM_TLS = 0x100,
-    /// Zig specific flag. Uses the most significant bit of the flag to annotate whether a symbol is
-    /// alive or not. Dead symbols are allowed to be garbage collected.
-    alive = 0x80000000,
-};
-
-/// Verifies if the given symbol should be imported from the
-/// host environment or not
-pub fn requiresImport(symbol: Symbol) bool {
-    if (symbol.tag == .data) return false;
-    if (!symbol.isUndefined()) return false;
-    if (symbol.isWeak()) return false;
-    // if (symbol.isDefined() and symbol.isWeak()) return true; //TODO: Only when building shared lib
-
-    return true;
-}
-
-/// Marks a symbol as 'alive', ensuring the garbage collector will not collect the trash.
-pub fn mark(symbol: *Symbol) void {
-    symbol.flags |= @intFromEnum(Flag.alive);
-}
-
-pub fn unmark(symbol: *Symbol) void {
-    symbol.flags &= ~@intFromEnum(Flag.alive);
-}
-
-pub fn isAlive(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.alive) != 0;
-}
-
-pub fn isDead(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.alive) == 0;
-}
-
-pub fn isTLS(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.WASM_SYM_TLS) != 0;
-}
-
-pub fn hasFlag(symbol: Symbol, flag: Flag) bool {
-    return symbol.flags & @intFromEnum(flag) != 0;
-}
-
-pub fn setFlag(symbol: *Symbol, flag: Flag) void {
-    symbol.flags |= @intFromEnum(flag);
-}
-
-pub fn isUndefined(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.WASM_SYM_UNDEFINED) != 0;
-}
-
-pub fn setUndefined(symbol: *Symbol, is_undefined: bool) void {
-    if (is_undefined) {
-        symbol.setFlag(.WASM_SYM_UNDEFINED);
-    } else {
-        symbol.flags &= ~@intFromEnum(Flag.WASM_SYM_UNDEFINED);
-    }
-}
-
-pub fn setGlobal(symbol: *Symbol, is_global: bool) void {
-    if (is_global) {
-        symbol.flags &= ~@intFromEnum(Flag.WASM_SYM_BINDING_LOCAL);
-    } else {
-        symbol.setFlag(.WASM_SYM_BINDING_LOCAL);
-    }
-}
-
-pub fn isDefined(symbol: Symbol) bool {
-    return !symbol.isUndefined();
-}
-
-pub fn isVisible(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.WASM_SYM_VISIBILITY_HIDDEN) == 0;
-}
-
-pub fn isLocal(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.WASM_SYM_BINDING_LOCAL) != 0;
-}
-
-pub fn isGlobal(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.WASM_SYM_BINDING_LOCAL) == 0;
-}
-
-pub fn isHidden(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.WASM_SYM_VISIBILITY_HIDDEN) != 0;
-}
-
-pub fn isNoStrip(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.WASM_SYM_NO_STRIP) != 0;
-}
-
-pub fn isExported(symbol: Symbol, is_dynamic: bool) bool {
-    if (symbol.isUndefined() or symbol.isLocal()) return false;
-    if (is_dynamic and symbol.isVisible()) return true;
-    return symbol.hasFlag(.WASM_SYM_EXPORTED);
-}
-
-pub fn isWeak(symbol: Symbol) bool {
-    return symbol.flags & @intFromEnum(Flag.WASM_SYM_BINDING_WEAK) != 0;
-}
-
-/// Formats the symbol into human-readable text
-pub fn format(symbol: Symbol, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-    _ = fmt;
-    _ = options;
-
-    const kind_fmt: u8 = switch (symbol.tag) {
-        .function => 'F',
-        .data => 'D',
-        .global => 'G',
-        .section => 'S',
-        .event => 'E',
-        .table => 'T',
-        .dead => '-',
-        .undefined => unreachable,
-    };
-    const visible: []const u8 = if (symbol.isVisible()) "yes" else "no";
-    const binding: []const u8 = if (symbol.isLocal()) "local" else "global";
-    const undef: []const u8 = if (symbol.isUndefined()) "undefined" else "";
-
-    try writer.print(
-        "{c} binding={s} visible={s} id={d} name_offset={d} {s}",
-        .{ kind_fmt, binding, visible, symbol.index, symbol.name, undef },
-    );
-}
-
-const std = @import("std");
-const Symbol = @This();
-const wasm = @import("../Wasm.zig");
src/link/Wasm/ZigObject.zig
@@ -1,1229 +0,0 @@
-//! 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.
-
-/// 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,
-/// List of function type signatures for this Zig module.
-func_types: std.ArrayListUnmanaged(std.wasm.Type) = .empty,
-/// List of `std.wasm.Func`. Each entry contains the function signature,
-/// rather than the actual body.
-functions: std.ArrayListUnmanaged(std.wasm.Func) = .empty,
-/// List of indexes pointing to an entry within the `functions` list which has been removed.
-functions_free_list: std.ArrayListUnmanaged(u32) = .empty,
-/// Map of symbol locations, represented by its `Wasm.Import`.
-imports: std.AutoHashMapUnmanaged(Symbol.Index, Wasm.Import) = .empty,
-/// List of WebAssembly globals.
-globals: std.ArrayListUnmanaged(std.wasm.Global) = .empty,
-/// Mapping between an `Atom` and its type index representing the Wasm
-/// type of the function signature.
-atom_types: std.AutoHashMapUnmanaged(Atom.Index, u32) = .empty,
-/// List of all symbols generated by Zig code.
-symbols: std.ArrayListUnmanaged(Symbol) = .empty,
-/// Map from symbol name to their index into the `symbols` list.
-global_syms: std.AutoHashMapUnmanaged(Wasm.String, Symbol.Index) = .empty,
-/// List of symbol indexes which are free to be used.
-symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .empty,
-/// Extra metadata about the linking section, such as alignment of segments and their name.
-segment_info: std.ArrayListUnmanaged(Wasm.NamedSegment) = .empty,
-/// List of indexes which contain a free slot in the `segment_info` list.
-segment_free_list: std.ArrayListUnmanaged(u32) = .empty,
-/// Map for storing anonymous declarations. Each anonymous decl maps to its Atom's index.
-uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Atom.Index) = .empty,
-/// List of atom indexes of functions that are generated by the backend.
-synthetic_functions: std.ArrayListUnmanaged(Atom.Index) = .empty,
-/// Represents the symbol index of the error name table
-/// When this is `null`, no code references an error using runtime `@errorName`.
-/// During initializion, a symbol with corresponding atom will be created that is
-/// used to perform relocations to the pointer of this table.
-/// The actual table is populated during `flush`.
-error_table_symbol: Symbol.Index = .null,
-/// Atom index of the table of symbol names. This is stored so we can clean up the atom.
-error_names_atom: Atom.Index = .null,
-/// Amount of functions in the `import` sections.
-imported_functions_count: u32 = 0,
-/// Amount of globals in the `import` section.
-imported_globals_count: u32 = 0,
-/// Symbol index representing the stack pointer. This will be set upon initializion
-/// of a new `ZigObject`. Codegen will make calls into this to create relocations for
-/// this symbol each time the stack pointer is moved.
-stack_pointer_sym: Symbol.Index,
-/// Debug information for the Zig module.
-dwarf: ?Dwarf = null,
-// Debug section atoms. These are only set when the current compilation
-// unit contains Zig code. The lifetime of these atoms are extended
-// until the end of the compiler's lifetime. Meaning they're not freed
-// during `flush()` in incremental-mode.
-debug_info_atom: ?Atom.Index = null,
-debug_line_atom: ?Atom.Index = null,
-debug_loc_atom: ?Atom.Index = null,
-debug_ranges_atom: ?Atom.Index = null,
-debug_abbrev_atom: ?Atom.Index = null,
-debug_str_atom: ?Atom.Index = null,
-debug_pubnames_atom: ?Atom.Index = null,
-debug_pubtypes_atom: ?Atom.Index = null,
-/// The index of the segment representing the custom '.debug_info' section.
-debug_info_index: ?u32 = null,
-/// The index of the segment representing the custom '.debug_line' section.
-debug_line_index: ?u32 = null,
-/// The index of the segment representing the custom '.debug_loc' section.
-debug_loc_index: ?u32 = null,
-/// The index of the segment representing the custom '.debug_ranges' section.
-debug_ranges_index: ?u32 = null,
-/// The index of the segment representing the custom '.debug_pubnames' section.
-debug_pubnames_index: ?u32 = null,
-/// The index of the segment representing the custom '.debug_pubtypes' section.
-debug_pubtypes_index: ?u32 = null,
-/// The index of the segment representing the custom '.debug_pubtypes' section.
-debug_str_index: ?u32 = null,
-/// The index of the segment representing the custom '.debug_pubtypes' section.
-debug_abbrev_index: ?u32 = null,
-
-const NavInfo = struct {
-    atom: Atom.Index = .null,
-    exports: std.ArrayListUnmanaged(Symbol.Index) = .empty,
-
-    fn @"export"(ni: NavInfo, zo: *const ZigObject, name: Wasm.String) ?Symbol.Index {
-        for (ni.exports.items) |sym_index| {
-            if (zo.symbol(sym_index).name == name) return sym_index;
-        }
-        return null;
-    }
-
-    fn appendExport(ni: *NavInfo, gpa: std.mem.Allocator, sym_index: Symbol.Index) !void {
-        return ni.exports.append(gpa, sym_index);
-    }
-
-    fn deleteExport(ni: *NavInfo, sym_index: Symbol.Index) void {
-        for (ni.exports.items, 0..) |idx, index| {
-            if (idx == sym_index) {
-                _ = ni.exports.swapRemove(index);
-                return;
-            }
-        }
-        unreachable; // invalid sym_index
-    }
-};
-
-/// Initializes the `ZigObject` with initial symbols.
-pub fn init(zig_object: *ZigObject, wasm: *Wasm) !void {
-    // Initialize an undefined global with the name __stack_pointer. Codegen will use
-    // this to generate relocations when moving the stack pointer. This symbol will be
-    // resolved automatically by the final linking stage.
-    try zig_object.createStackPointer(wasm);
-
-    // TODO: Initialize debug information when we reimplement Dwarf support.
-}
-
-fn createStackPointer(zig_object: *ZigObject, wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-    const sym_index = try zig_object.getGlobalSymbol(gpa, wasm.preloaded_strings.__stack_pointer);
-    const sym = zig_object.symbol(sym_index);
-    sym.index = zig_object.imported_globals_count;
-    sym.tag = .global;
-    const is_wasm32 = wasm.base.comp.root_mod.resolved_target.result.cpu.arch == .wasm32;
-    try zig_object.imports.putNoClobber(gpa, sym_index, .{
-        .name = sym.name,
-        .module_name = wasm.host_name,
-        .kind = .{ .global = .{ .valtype = if (is_wasm32) .i32 else .i64, .mutable = true } },
-    });
-    zig_object.imported_globals_count += 1;
-    zig_object.stack_pointer_sym = sym_index;
-}
-
-pub fn symbol(zig_object: *const ZigObject, index: Symbol.Index) *Symbol {
-    return &zig_object.symbols.items[@intFromEnum(index)];
-}
-
-/// Frees and invalidates all memory of the incrementally compiled Zig module.
-/// It is illegal behavior to access the `ZigObject` after calling `deinit`.
-pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void {
-    const gpa = wasm.base.comp.gpa;
-    for (zig_object.segment_info.items) |segment_info| {
-        gpa.free(segment_info.name);
-    }
-
-    {
-        var it = zig_object.navs.valueIterator();
-        while (it.next()) |nav_info| {
-            const atom = wasm.getAtomPtr(nav_info.atom);
-            for (atom.locals.items) |local_index| {
-                const local_atom = wasm.getAtomPtr(local_index);
-                local_atom.deinit(gpa);
-            }
-            atom.deinit(gpa);
-            nav_info.exports.deinit(gpa);
-        }
-    }
-    {
-        for (zig_object.uavs.values()) |atom_index| {
-            const atom = wasm.getAtomPtr(atom_index);
-            for (atom.locals.items) |local_index| {
-                const local_atom = wasm.getAtomPtr(local_index);
-                local_atom.deinit(gpa);
-            }
-            atom.deinit(gpa);
-        }
-    }
-    if (zig_object.global_syms.get(wasm.preloaded_strings.__zig_errors_len)) |sym_index| {
-        const atom_index = wasm.symbol_atom.get(.{ .file = .zig_object, .index = sym_index }).?;
-        wasm.getAtomPtr(atom_index).deinit(gpa);
-    }
-    if (wasm.symbol_atom.get(.{ .file = .zig_object, .index = zig_object.error_table_symbol })) |atom_index| {
-        const atom = wasm.getAtomPtr(atom_index);
-        atom.deinit(gpa);
-    }
-    for (zig_object.synthetic_functions.items) |atom_index| {
-        const atom = wasm.getAtomPtr(atom_index);
-        atom.deinit(gpa);
-    }
-    zig_object.synthetic_functions.deinit(gpa);
-    for (zig_object.func_types.items) |*ty| {
-        ty.deinit(gpa);
-    }
-    if (zig_object.error_names_atom != .null) {
-        const atom = wasm.getAtomPtr(zig_object.error_names_atom);
-        atom.deinit(gpa);
-    }
-    zig_object.global_syms.deinit(gpa);
-    zig_object.func_types.deinit(gpa);
-    zig_object.atom_types.deinit(gpa);
-    zig_object.functions.deinit(gpa);
-    zig_object.imports.deinit(gpa);
-    zig_object.navs.deinit(gpa);
-    zig_object.uavs.deinit(gpa);
-    zig_object.symbols.deinit(gpa);
-    zig_object.symbols_free_list.deinit(gpa);
-    zig_object.segment_info.deinit(gpa);
-    zig_object.segment_free_list.deinit(gpa);
-
-    if (zig_object.dwarf) |*dwarf| {
-        dwarf.deinit();
-    }
-    gpa.free(zig_object.path.sub_path);
-    zig_object.* = undefined;
-}
-
-/// Allocates a new symbol and returns its index.
-/// Will re-use slots when a symbol was freed at an earlier stage.
-pub fn allocateSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator) !Symbol.Index {
-    try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
-    const sym: Symbol = .{
-        .name = undefined, // will be set after updateDecl as well as during atom creation for decls
-        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
-        .tag = .undefined, // will be set after updateDecl
-        .index = std.math.maxInt(u32), // will be set during atom parsing
-        .virtual_address = std.math.maxInt(u32), // will be set during atom allocation
-    };
-    if (zig_object.symbols_free_list.popOrNull()) |index| {
-        zig_object.symbols.items[@intFromEnum(index)] = sym;
-        return index;
-    }
-    const index: Symbol.Index = @enumFromInt(zig_object.symbols.items.len);
-    zig_object.symbols.appendAssumeCapacity(sym);
-    return index;
-}
-
-// Generate code for the `Nav`, storing it in memory to be later written to
-// the file on flush().
-pub fn updateNav(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    nav_index: InternPool.Nav.Index,
-) !void {
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const nav = ip.getNav(nav_index);
-
-    const nav_val = zcu.navValue(nav_index);
-    const is_extern, const lib_name, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) {
-        .variable => |variable| .{ false, .none, Value.fromInterned(variable.init) },
-        .func => return,
-        .@"extern" => |@"extern"| if (ip.isFunctionType(nav.typeOf(ip)))
-            return
-        else
-            .{ true, @"extern".lib_name, nav_val },
-        else => .{ false, .none, nav_val },
-    };
-
-    if (nav_init.typeOf(zcu).hasRuntimeBits(zcu)) {
-        const gpa = wasm.base.comp.gpa;
-        const atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, nav_index);
-        const atom = wasm.getAtomPtr(atom_index);
-        atom.clear();
-
-        if (is_extern)
-            return zig_object.addOrUpdateImport(wasm, nav.name.toSlice(ip), atom.sym_index, lib_name.toSlice(ip), null);
-
-        var code_writer = std.ArrayList(u8).init(gpa);
-        defer code_writer.deinit();
-
-        const res = try codegen.generateSymbol(
-            &wasm.base,
-            pt,
-            zcu.navSrcLoc(nav_index),
-            nav_init,
-            &code_writer,
-            .{ .atom_index = @intFromEnum(atom.sym_index) },
-        );
-
-        const code = switch (res) {
-            .ok => code_writer.items,
-            .fail => |em| {
-                try zcu.failed_codegen.put(zcu.gpa, nav_index, em);
-                return;
-            },
-        };
-
-        try zig_object.finishUpdateNav(wasm, pt, nav_index, code);
-    }
-}
-
-pub fn updateFunc(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    func_index: InternPool.Index,
-    air: Air,
-    liveness: Liveness,
-) !void {
-    const zcu = pt.zcu;
-    const gpa = zcu.gpa;
-    const func = pt.zcu.funcInfo(func_index);
-    const atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, func.owner_nav);
-    const atom = wasm.getAtomPtr(atom_index);
-    atom.clear();
-
-    var code_writer = std.ArrayList(u8).init(gpa);
-    defer code_writer.deinit();
-    const result = try codegen.generateFunction(
-        &wasm.base,
-        pt,
-        zcu.navSrcLoc(func.owner_nav),
-        func_index,
-        air,
-        liveness,
-        &code_writer,
-        .none,
-    );
-
-    const code = switch (result) {
-        .ok => code_writer.items,
-        .fail => |em| {
-            try pt.zcu.failed_codegen.put(gpa, func.owner_nav, em);
-            return;
-        },
-    };
-
-    return zig_object.finishUpdateNav(wasm, pt, func.owner_nav, code);
-}
-
-fn finishUpdateNav(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    nav_index: InternPool.Nav.Index,
-    code: []const u8,
-) !void {
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const gpa = zcu.gpa;
-    const nav = ip.getNav(nav_index);
-    const nav_val = zcu.navValue(nav_index);
-    const nav_info = zig_object.navs.get(nav_index).?;
-    const atom_index = nav_info.atom;
-    const atom = wasm.getAtomPtr(atom_index);
-    const sym = zig_object.symbol(atom.sym_index);
-    sym.name = try wasm.internString(nav.fqn.toSlice(ip));
-    try atom.code.appendSlice(gpa, code);
-    atom.size = @intCast(code.len);
-
-    if (ip.isFunctionType(nav.typeOf(ip))) {
-        sym.index = try zig_object.appendFunction(gpa, .{ .type_index = zig_object.atom_types.get(atom_index).? });
-        sym.tag = .function;
-    } else {
-        const is_const, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) {
-            .variable => |variable| .{ false, variable.init },
-            .@"extern" => |@"extern"| .{ @"extern".is_const, .none },
-            else => .{ true, nav_val.toIntern() },
-        };
-        const segment_name = name: {
-            if (is_const) break :name ".rodata.";
-
-            if (nav_init != .none and Value.fromInterned(nav_init).isUndefDeep(zcu)) {
-                break :name switch (zcu.navFileScope(nav_index).mod.optimize_mode) {
-                    .Debug, .ReleaseSafe => ".data.",
-                    .ReleaseFast, .ReleaseSmall => ".bss.",
-                };
-            }
-            // when the decl is all zeroes, we store the atom in the bss segment,
-            // in all other cases it will be in the data segment.
-            for (atom.code.items) |byte| {
-                if (byte != 0) break :name ".data.";
-            }
-            break :name ".bss.";
-        };
-        if ((wasm.base.isObject() or wasm.base.comp.config.import_memory) and
-            std.mem.startsWith(u8, segment_name, ".bss"))
-        {
-            @memset(atom.code.items, 0);
-        }
-        // Will be freed upon freeing of decl or after cleanup of Wasm binary.
-        const full_segment_name = try std.mem.concat(gpa, u8, &.{
-            segment_name,
-            nav.fqn.toSlice(ip),
-        });
-        errdefer gpa.free(full_segment_name);
-        sym.tag = .data;
-        sym.index = try zig_object.createDataSegment(gpa, full_segment_name, pt.navAlignment(nav_index));
-    }
-    if (code.len == 0) return;
-    atom.alignment = pt.navAlignment(nav_index);
-}
-
-/// Creates and initializes a new segment in the 'Data' section.
-/// Reuses free slots in the list of segments and returns the index.
-fn createDataSegment(
-    zig_object: *ZigObject,
-    gpa: std.mem.Allocator,
-    name: []const u8,
-    alignment: InternPool.Alignment,
-) !u32 {
-    const segment_index: u32 = if (zig_object.segment_free_list.popOrNull()) |index|
-        index
-    else index: {
-        const idx: u32 = @intCast(zig_object.segment_info.items.len);
-        _ = try zig_object.segment_info.addOne(gpa);
-        break :index idx;
-    };
-    zig_object.segment_info.items[segment_index] = .{
-        .alignment = alignment,
-        .flags = 0,
-        .name = name,
-    };
-    return segment_index;
-}
-
-/// For a given `InternPool.Nav.Index` returns its corresponding `Atom.Index`.
-/// When the index was not found, a new `Atom` will be created, and its index will be returned.
-/// The newly created Atom is empty with default fields as specified by `Atom.empty`.
-pub fn getOrCreateAtomForNav(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    nav_index: InternPool.Nav.Index,
-) !Atom.Index {
-    const ip = &pt.zcu.intern_pool;
-    const gpa = pt.zcu.gpa;
-    const gop = try zig_object.navs.getOrPut(gpa, nav_index);
-    if (!gop.found_existing) {
-        const sym_index = try zig_object.allocateSymbol(gpa);
-        gop.value_ptr.* = .{ .atom = try wasm.createAtom(sym_index, .zig_object) };
-        const nav = ip.getNav(nav_index);
-        const sym = zig_object.symbol(sym_index);
-        sym.name = try wasm.internString(nav.fqn.toSlice(ip));
-    }
-    return gop.value_ptr.atom;
-}
-
-pub fn lowerUav(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    uav: InternPool.Index,
-    explicit_alignment: InternPool.Alignment,
-    src_loc: Zcu.LazySrcLoc,
-) !codegen.GenResult {
-    const gpa = wasm.base.comp.gpa;
-    const gop = try zig_object.uavs.getOrPut(gpa, uav);
-    if (!gop.found_existing) {
-        var name_buf: [32]u8 = undefined;
-        const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
-            @intFromEnum(uav),
-        }) catch unreachable;
-
-        switch (try zig_object.lowerConst(wasm, pt, name, Value.fromInterned(uav), src_loc)) {
-            .ok => |atom_index| zig_object.uavs.values()[gop.index] = atom_index,
-            .fail => |em| return .{ .fail = em },
-        }
-    }
-
-    const atom = wasm.getAtomPtr(zig_object.uavs.values()[gop.index]);
-    atom.alignment = switch (atom.alignment) {
-        .none => explicit_alignment,
-        else => switch (explicit_alignment) {
-            .none => atom.alignment,
-            else => atom.alignment.maxStrict(explicit_alignment),
-        },
-    };
-    return .{ .mcv = .{ .load_symbol = @intFromEnum(atom.sym_index) } };
-}
-
-const LowerConstResult = union(enum) {
-    ok: Atom.Index,
-    fail: *Zcu.ErrorMsg,
-};
-
-fn lowerConst(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    name: []const u8,
-    val: Value,
-    src_loc: Zcu.LazySrcLoc,
-) !LowerConstResult {
-    const gpa = wasm.base.comp.gpa;
-    const zcu = wasm.base.comp.zcu.?;
-
-    const ty = val.typeOf(zcu);
-
-    // Create and initialize a new local symbol and atom
-    const sym_index = try zig_object.allocateSymbol(gpa);
-    const atom_index = try wasm.createAtom(sym_index, .zig_object);
-    var value_bytes = std.ArrayList(u8).init(gpa);
-    defer value_bytes.deinit();
-
-    const code = code: {
-        const atom = wasm.getAtomPtr(atom_index);
-        atom.alignment = ty.abiAlignment(zcu);
-        const segment_name = try std.mem.concat(gpa, u8, &.{ ".rodata.", name });
-        errdefer gpa.free(segment_name);
-        zig_object.symbol(sym_index).* = .{
-            .name = try wasm.internString(name),
-            .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
-            .tag = .data,
-            .index = try zig_object.createDataSegment(
-                gpa,
-                segment_name,
-                ty.abiAlignment(zcu),
-            ),
-            .virtual_address = undefined,
-        };
-
-        const result = try codegen.generateSymbol(
-            &wasm.base,
-            pt,
-            src_loc,
-            val,
-            &value_bytes,
-            .{ .atom_index = @intFromEnum(atom.sym_index) },
-        );
-        break :code switch (result) {
-            .ok => value_bytes.items,
-            .fail => |em| {
-                return .{ .fail = em };
-            },
-        };
-    };
-
-    const atom = wasm.getAtomPtr(atom_index);
-    atom.size = @intCast(code.len);
-    try atom.code.appendSlice(gpa, code);
-    return .{ .ok = atom_index };
-}
-
-/// Returns the symbol index of the error name table.
-///
-/// When the symbol does not yet exist, it will create a new one instead.
-pub fn getErrorTableSymbol(zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThread) !Symbol.Index {
-    if (zig_object.error_table_symbol != .null) {
-        return zig_object.error_table_symbol;
-    }
-
-    // no error was referenced yet, so create a new symbol and atom for it
-    // and then return said symbol's index. The final table will be populated
-    // during `flush` when we know all possible error names.
-    const gpa = wasm.base.comp.gpa;
-    const sym_index = try zig_object.allocateSymbol(gpa);
-    const atom_index = try wasm.createAtom(sym_index, .zig_object);
-    const atom = wasm.getAtomPtr(atom_index);
-    const slice_ty = Type.slice_const_u8_sentinel_0;
-    atom.alignment = slice_ty.abiAlignment(pt.zcu);
-
-    const segment_name = try gpa.dupe(u8, ".rodata.__zig_err_name_table");
-    const sym = zig_object.symbol(sym_index);
-    sym.* = .{
-        .name = wasm.preloaded_strings.__zig_err_name_table,
-        .tag = .data,
-        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
-        .index = try zig_object.createDataSegment(gpa, segment_name, atom.alignment),
-        .virtual_address = undefined,
-    };
-
-    log.debug("Error name table was created with symbol index: ({d})", .{@intFromEnum(sym_index)});
-    zig_object.error_table_symbol = sym_index;
-    return sym_index;
-}
-
-/// Populates the error name table, when `error_table_symbol` is not null.
-///
-/// This creates a table that consists of pointers and length to each error name.
-/// The table is what is being pointed to within the runtime bodies that are generated.
-fn populateErrorNameTable(zig_object: *ZigObject, wasm: *Wasm, tid: Zcu.PerThread.Id) !void {
-    if (zig_object.error_table_symbol == .null) return;
-    const gpa = wasm.base.comp.gpa;
-    const atom_index = wasm.symbol_atom.get(.{ .file = .zig_object, .index = zig_object.error_table_symbol }).?;
-
-    // Rather than creating a symbol for each individual error name,
-    // we create a symbol for the entire region of error names. We then calculate
-    // the pointers into the list using addends which are appended to the relocation.
-    const names_sym_index = try zig_object.allocateSymbol(gpa);
-    const names_atom_index = try wasm.createAtom(names_sym_index, .zig_object);
-    const names_atom = wasm.getAtomPtr(names_atom_index);
-    names_atom.alignment = .@"1";
-    const segment_name = try gpa.dupe(u8, ".rodata.__zig_err_names");
-    const names_symbol = zig_object.symbol(names_sym_index);
-    names_symbol.* = .{
-        .name = wasm.preloaded_strings.__zig_err_names,
-        .tag = .data,
-        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
-        .index = try zig_object.createDataSegment(gpa, segment_name, names_atom.alignment),
-        .virtual_address = undefined,
-    };
-
-    log.debug("Populating error names", .{});
-
-    // Addend for each relocation to the table
-    var addend: u32 = 0;
-    const pt: Zcu.PerThread = .activate(wasm.base.comp.zcu.?, tid);
-    defer pt.deactivate();
-    const slice_ty = Type.slice_const_u8_sentinel_0;
-    const atom = wasm.getAtomPtr(atom_index);
-    {
-        // TODO: remove this unreachable entry
-        try atom.code.appendNTimes(gpa, 0, 4);
-        try atom.code.writer(gpa).writeInt(u32, 0, .little);
-        atom.size += @intCast(slice_ty.abiSize(pt.zcu));
-        addend += 1;
-
-        try names_atom.code.append(gpa, 0);
-    }
-    const ip = &pt.zcu.intern_pool;
-    for (ip.global_error_set.getNamesFromMainThread()) |error_name| {
-        const error_name_slice = error_name.toSlice(ip);
-        const len: u32 = @intCast(error_name_slice.len + 1); // names are 0-terminated
-
-        const offset = @as(u32, @intCast(atom.code.items.len));
-        // first we create the data for the slice of the name
-        try atom.code.appendNTimes(gpa, 0, 4); // ptr to name, will be relocated
-        try atom.code.writer(gpa).writeInt(u32, len - 1, .little);
-        // create relocation to the error name
-        try atom.relocs.append(gpa, .{
-            .index = @intFromEnum(names_atom.sym_index),
-            .relocation_type = .R_WASM_MEMORY_ADDR_I32,
-            .offset = offset,
-            .addend = @intCast(addend),
-        });
-        atom.size += @intCast(slice_ty.abiSize(pt.zcu));
-        addend += len;
-
-        // as we updated the error name table, we now store the actual name within the names atom
-        try names_atom.code.ensureUnusedCapacity(gpa, len);
-        names_atom.code.appendSliceAssumeCapacity(error_name_slice[0..len]);
-
-        log.debug("Populated error name: '{}'", .{error_name.fmt(ip)});
-    }
-    names_atom.size = addend;
-    zig_object.error_names_atom = names_atom_index;
-}
-
-/// Either creates a new import, or updates one if existing.
-/// When `type_index` is non-null, we assume an external function.
-/// In all other cases, a data-symbol will be created instead.
-pub fn addOrUpdateImport(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    /// Name of the import
-    name: []const u8,
-    /// Symbol index that is external
-    symbol_index: Symbol.Index,
-    /// Optional library name (i.e. `extern "c" fn foo() void`
-    lib_name: ?[:0]const u8,
-    /// The index of the type that represents the function signature
-    /// when the extern is a function. When this is null, a data-symbol
-    /// is asserted instead.
-    type_index: ?u32,
-) !void {
-    const gpa = wasm.base.comp.gpa;
-    std.debug.assert(symbol_index != .null);
-    // For the import name, we use the decl's name, rather than the fully qualified name
-    // Also mangle the name when the lib name is set and not equal to "C" so imports with the same
-    // name but different module can be resolved correctly.
-    const mangle_name = if (lib_name) |n| !std.mem.eql(u8, n, "c") else false;
-    const full_name = if (mangle_name)
-        try std.fmt.allocPrint(gpa, "{s}|{s}", .{ name, lib_name.? })
-    else
-        name;
-    defer if (mangle_name) gpa.free(full_name);
-
-    const decl_name_index = try wasm.internString(full_name);
-    const sym: *Symbol = &zig_object.symbols.items[@intFromEnum(symbol_index)];
-    sym.setUndefined(true);
-    sym.setGlobal(true);
-    sym.name = decl_name_index;
-    if (mangle_name) {
-        // we specified a specific name for the symbol that does not match the import name
-        sym.setFlag(.WASM_SYM_EXPLICIT_NAME);
-    }
-
-    if (type_index) |ty_index| {
-        const gop = try zig_object.imports.getOrPut(gpa, symbol_index);
-        const module_name = if (lib_name) |n| try wasm.internString(n) else wasm.host_name;
-        if (!gop.found_existing) zig_object.imported_functions_count += 1;
-        gop.value_ptr.* = .{
-            .module_name = module_name,
-            .name = try wasm.internString(name),
-            .kind = .{ .function = ty_index },
-        };
-        sym.tag = .function;
-    } else {
-        sym.tag = .data;
-    }
-}
-
-/// Returns the symbol index from a symbol of which its flag is set global,
-/// such as an exported or imported symbol.
-/// If the symbol does not yet exist, creates a new one symbol instead
-/// and then returns the index to it.
-pub fn getGlobalSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator, name_index: Wasm.String) !Symbol.Index {
-    const gop = try zig_object.global_syms.getOrPut(gpa, name_index);
-    if (gop.found_existing) {
-        return gop.value_ptr.*;
-    }
-
-    var sym: Symbol = .{
-        .name = name_index,
-        .flags = 0,
-        .index = undefined, // index to type will be set after merging symbols
-        .tag = .function,
-        .virtual_address = std.math.maxInt(u32),
-    };
-    sym.setGlobal(true);
-    sym.setUndefined(true);
-
-    const sym_index = if (zig_object.symbols_free_list.popOrNull()) |index| index else blk: {
-        const index: Symbol.Index = @enumFromInt(zig_object.symbols.items.len);
-        try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
-        zig_object.symbols.items.len += 1;
-        break :blk index;
-    };
-    zig_object.symbol(sym_index).* = sym;
-    gop.value_ptr.* = sym_index;
-    return sym_index;
-}
-
-/// For a given decl, find the given symbol index's atom, and create a relocation for the type.
-/// Returns the given pointer address
-pub fn getNavVAddr(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    nav_index: InternPool.Nav.Index,
-    reloc_info: link.File.RelocInfo,
-) !u64 {
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const gpa = zcu.gpa;
-    const nav = ip.getNav(nav_index);
-    const target = &zcu.navFileScope(nav_index).mod.resolved_target.result;
-
-    const target_atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, nav_index);
-    const target_atom = wasm.getAtom(target_atom_index);
-    const target_symbol_index = @intFromEnum(target_atom.sym_index);
-    if (nav.getExtern(ip)) |@"extern"| {
-        try zig_object.addOrUpdateImport(
-            wasm,
-            nav.name.toSlice(ip),
-            target_atom.sym_index,
-            @"extern".lib_name.toSlice(ip),
-            null,
-        );
-    }
-
-    std.debug.assert(reloc_info.parent.atom_index != 0);
-    const atom_index = wasm.symbol_atom.get(.{
-        .file = .zig_object,
-        .index = @enumFromInt(reloc_info.parent.atom_index),
-    }).?;
-    const atom = wasm.getAtomPtr(atom_index);
-    const is_wasm32 = target.cpu.arch == .wasm32;
-    if (ip.isFunctionType(ip.getNav(nav_index).typeOf(ip))) {
-        std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations
-        try atom.relocs.append(gpa, .{
-            .index = target_symbol_index,
-            .offset = @intCast(reloc_info.offset),
-            .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
-        });
-    } else {
-        try atom.relocs.append(gpa, .{
-            .index = target_symbol_index,
-            .offset = @intCast(reloc_info.offset),
-            .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
-            .addend = @intCast(reloc_info.addend),
-        });
-    }
-
-    // we do not know the final address at this point,
-    // as atom allocation will determine the address and relocations
-    // will calculate and rewrite this. Therefore, we simply return the symbol index
-    // that was targeted.
-    return target_symbol_index;
-}
-
-pub fn getUavVAddr(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    uav: InternPool.Index,
-    reloc_info: link.File.RelocInfo,
-) !u64 {
-    const gpa = wasm.base.comp.gpa;
-    const target = wasm.base.comp.root_mod.resolved_target.result;
-    const atom_index = zig_object.uavs.get(uav).?;
-    const target_symbol_index = @intFromEnum(wasm.getAtom(atom_index).sym_index);
-
-    const parent_atom_index = wasm.symbol_atom.get(.{
-        .file = .zig_object,
-        .index = @enumFromInt(reloc_info.parent.atom_index),
-    }).?;
-    const parent_atom = wasm.getAtomPtr(parent_atom_index);
-    const is_wasm32 = target.cpu.arch == .wasm32;
-    const zcu = wasm.base.comp.zcu.?;
-    const ty = Type.fromInterned(zcu.intern_pool.typeOf(uav));
-    if (ty.zigTypeTag(zcu) == .@"fn") {
-        std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations
-        try parent_atom.relocs.append(gpa, .{
-            .index = target_symbol_index,
-            .offset = @intCast(reloc_info.offset),
-            .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
-        });
-    } else {
-        try parent_atom.relocs.append(gpa, .{
-            .index = target_symbol_index,
-            .offset = @intCast(reloc_info.offset),
-            .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
-            .addend = @intCast(reloc_info.addend),
-        });
-    }
-
-    // we do not know the final address at this point,
-    // as atom allocation will determine the address and relocations
-    // will calculate and rewrite this. Therefore, we simply return the symbol index
-    // that was targeted.
-    return target_symbol_index;
-}
-
-pub fn deleteExport(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    exported: Zcu.Exported,
-    name: InternPool.NullTerminatedString,
-) void {
-    const zcu = wasm.base.comp.zcu.?;
-    const nav_index = switch (exported) {
-        .nav => |nav_index| nav_index,
-        .uav => @panic("TODO: implement Wasm linker code for exporting a constant value"),
-    };
-    const nav_info = zig_object.navs.getPtr(nav_index) orelse return;
-    const name_interned = wasm.getExistingString(name.toSlice(&zcu.intern_pool)).?;
-    if (nav_info.@"export"(zig_object, name_interned)) |sym_index| {
-        const sym = zig_object.symbol(sym_index);
-        nav_info.deleteExport(sym_index);
-        std.debug.assert(zig_object.global_syms.remove(sym.name));
-        std.debug.assert(wasm.symbol_atom.remove(.{ .file = .zig_object, .index = sym_index }));
-        zig_object.symbols_free_list.append(wasm.base.comp.gpa, sym_index) catch {};
-        sym.tag = .dead;
-    }
-}
-
-pub fn updateExports(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    exported: Zcu.Exported,
-    export_indices: []const u32,
-) !void {
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const nav_index = switch (exported) {
-        .nav => |nav| nav,
-        .uav => |uav| {
-            _ = uav;
-            @panic("TODO: implement Wasm linker code for exporting a constant value");
-        },
-    };
-    const nav = ip.getNav(nav_index);
-    const atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, nav_index);
-    const nav_info = zig_object.navs.getPtr(nav_index).?;
-    const atom = wasm.getAtom(atom_index);
-    const atom_sym = wasm.symbolLocSymbol(atom.symbolLoc()).*;
-    const gpa = zcu.gpa;
-    log.debug("Updating exports for decl '{}'", .{nav.name.fmt(ip)});
-
-    for (export_indices) |export_idx| {
-        const exp = zcu.all_exports.items[export_idx];
-        if (exp.opts.section.toSlice(ip)) |section| {
-            try zcu.failed_exports.putNoClobber(gpa, export_idx, try Zcu.ErrorMsg.create(
-                gpa,
-                zcu.navSrcLoc(nav_index),
-                "Unimplemented: ExportOptions.section '{s}'",
-                .{section},
-            ));
-            continue;
-        }
-
-        const export_name = try wasm.internString(exp.opts.name.toSlice(ip));
-        const sym_index = if (nav_info.@"export"(zig_object, export_name)) |idx| idx else index: {
-            const sym_index = try zig_object.allocateSymbol(gpa);
-            try nav_info.appendExport(gpa, sym_index);
-            break :index sym_index;
-        };
-
-        const sym = zig_object.symbol(sym_index);
-        sym.setGlobal(true);
-        sym.setUndefined(false);
-        sym.index = atom_sym.index;
-        sym.tag = atom_sym.tag;
-        sym.name = export_name;
-
-        switch (exp.opts.linkage) {
-            .internal => {
-                sym.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-            },
-            .weak => {
-                sym.setFlag(.WASM_SYM_BINDING_WEAK);
-            },
-            .strong => {}, // symbols are strong by default
-            .link_once => {
-                try zcu.failed_exports.putNoClobber(gpa, export_idx, try Zcu.ErrorMsg.create(
-                    gpa,
-                    zcu.navSrcLoc(nav_index),
-                    "Unimplemented: LinkOnce",
-                    .{},
-                ));
-                continue;
-            },
-        }
-        if (exp.opts.visibility == .hidden) {
-            sym.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-        }
-        log.debug("  with name '{s}' - {}", .{ wasm.stringSlice(export_name), sym });
-        try zig_object.global_syms.put(gpa, export_name, sym_index);
-        try wasm.symbol_atom.put(gpa, .{ .file = .zig_object, .index = sym_index }, atom_index);
-    }
-}
-
-pub fn freeNav(zig_object: *ZigObject, wasm: *Wasm, nav_index: InternPool.Nav.Index) void {
-    const gpa = wasm.base.comp.gpa;
-    const zcu = wasm.base.comp.zcu.?;
-    const ip = &zcu.intern_pool;
-    const nav_info = zig_object.navs.getPtr(nav_index).?;
-    const atom_index = nav_info.atom;
-    const atom = wasm.getAtomPtr(atom_index);
-    zig_object.symbols_free_list.append(gpa, atom.sym_index) catch {};
-    for (nav_info.exports.items) |exp_sym_index| {
-        const exp_sym = zig_object.symbol(exp_sym_index);
-        exp_sym.tag = .dead;
-        zig_object.symbols_free_list.append(exp_sym_index) catch {};
-    }
-    nav_info.exports.deinit(gpa);
-    std.debug.assert(zig_object.navs.remove(nav_index));
-    const sym = &zig_object.symbols.items[atom.sym_index];
-    for (atom.locals.items) |local_atom_index| {
-        const local_atom = wasm.getAtom(local_atom_index);
-        const local_symbol = &zig_object.symbols.items[local_atom.sym_index];
-        std.debug.assert(local_symbol.tag == .data);
-        zig_object.symbols_free_list.append(gpa, local_atom.sym_index) catch {};
-        std.debug.assert(wasm.symbol_atom.remove(local_atom.symbolLoc()));
-        local_symbol.tag = .dead; // also for any local symbol
-        const segment = &zig_object.segment_info.items[local_atom.sym_index];
-        gpa.free(segment.name);
-        segment.name = &.{}; // Ensure no accidental double free
-    }
-
-    const nav = ip.getNav(nav_index);
-    if (nav.getExtern(ip) != null) {
-        std.debug.assert(zig_object.imports.remove(atom.sym_index));
-    }
-    std.debug.assert(wasm.symbol_atom.remove(atom.symbolLoc()));
-
-    // if (wasm.dwarf) |*dwarf| {
-    //     dwarf.freeDecl(decl_index);
-    // }
-
-    atom.prev = null;
-    sym.tag = .dead;
-    if (sym.isGlobal()) {
-        std.debug.assert(zig_object.global_syms.remove(atom.sym_index));
-    }
-    if (ip.isFunctionType(nav.typeOf(ip))) {
-        zig_object.functions_free_list.append(gpa, sym.index) catch {};
-        std.debug.assert(zig_object.atom_types.remove(atom_index));
-    } else {
-        zig_object.segment_free_list.append(gpa, sym.index) catch {};
-        const segment = &zig_object.segment_info.items[sym.index];
-        gpa.free(segment.name);
-        segment.name = &.{}; // Prevent accidental double free
-    }
-}
-
-fn getTypeIndex(zig_object: *const ZigObject, func_type: std.wasm.Type) ?u32 {
-    var index: u32 = 0;
-    while (index < zig_object.func_types.items.len) : (index += 1) {
-        if (zig_object.func_types.items[index].eql(func_type)) return index;
-    }
-    return null;
-}
-
-/// Searches for a matching function signature. When no matching signature is found,
-/// a new entry will be made. The value returned is the index of the type within `wasm.func_types`.
-pub fn putOrGetFuncType(zig_object: *ZigObject, gpa: std.mem.Allocator, func_type: std.wasm.Type) !u32 {
-    if (zig_object.getTypeIndex(func_type)) |index| {
-        return index;
-    }
-
-    // functype does not exist.
-    const index: u32 = @intCast(zig_object.func_types.items.len);
-    const params = try gpa.dupe(std.wasm.Valtype, func_type.params);
-    errdefer gpa.free(params);
-    const returns = try gpa.dupe(std.wasm.Valtype, func_type.returns);
-    errdefer gpa.free(returns);
-    try zig_object.func_types.append(gpa, .{
-        .params = params,
-        .returns = returns,
-    });
-    return index;
-}
-
-/// Generates an atom containing the global error set' size.
-/// This will only be generated if the symbol exists.
-fn setupErrorsLen(zig_object: *ZigObject, wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-    const sym_index = zig_object.global_syms.get(wasm.preloaded_strings.__zig_errors_len) orelse return;
-
-    const errors_len = 1 + wasm.base.comp.zcu.?.intern_pool.global_error_set.getNamesFromMainThread().len;
-    // overwrite existing atom if it already exists (maybe the error set has increased)
-    // if not, allocate a new atom.
-    const atom_index = if (wasm.symbol_atom.get(.{ .file = .zig_object, .index = sym_index })) |index| blk: {
-        const atom = wasm.getAtomPtr(index);
-        atom.prev = .null;
-        atom.deinit(gpa);
-        break :blk index;
-    } else idx: {
-        // We found a call to __zig_errors_len so make the symbol a local symbol
-        // and define it, so the final binary or resulting object file will not attempt
-        // to resolve it.
-        const sym = zig_object.symbol(sym_index);
-        sym.setGlobal(false);
-        sym.setUndefined(false);
-        sym.tag = .data;
-        const segment_name = try gpa.dupe(u8, ".rodata.__zig_errors_len");
-        sym.index = try zig_object.createDataSegment(gpa, segment_name, .@"2");
-        break :idx try wasm.createAtom(sym_index, .zig_object);
-    };
-
-    const atom = wasm.getAtomPtr(atom_index);
-    atom.code.clearRetainingCapacity();
-    atom.sym_index = sym_index;
-    atom.size = 2;
-    atom.alignment = .@"2";
-    try atom.code.writer(gpa).writeInt(u16, @intCast(errors_len), .little);
-}
-
-/// Initializes symbols and atoms for the debug sections
-/// Initialization is only done when compiling Zig code.
-/// When Zig is invoked as a linker instead, the atoms
-/// and symbols come from the object files instead.
-pub fn initDebugSections(zig_object: *ZigObject) !void {
-    if (zig_object.dwarf == null) return; // not compiling Zig code, so no need to pre-initialize debug sections
-    std.debug.assert(zig_object.debug_info_index == null);
-    // this will create an Atom and set the index for us.
-    zig_object.debug_info_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_info_index, ".debug_info");
-    zig_object.debug_line_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_line_index, ".debug_line");
-    zig_object.debug_loc_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_loc_index, ".debug_loc");
-    zig_object.debug_abbrev_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_abbrev_index, ".debug_abbrev");
-    zig_object.debug_ranges_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_ranges_index, ".debug_ranges");
-    zig_object.debug_str_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_str_index, ".debug_str");
-    zig_object.debug_pubnames_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_pubnames_index, ".debug_pubnames");
-    zig_object.debug_pubtypes_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_pubtypes_index, ".debug_pubtypes");
-}
-
-/// From a given index variable, creates a new debug section.
-/// This initializes the index, appends a new segment,
-/// and finally, creates a managed `Atom`.
-pub fn createDebugSectionForIndex(zig_object: *ZigObject, wasm: *Wasm, index: *?u32, name: []const u8) !Atom.Index {
-    const gpa = wasm.base.comp.gpa;
-    const new_index: u32 = @intCast(zig_object.segments.items.len);
-    index.* = new_index;
-    try zig_object.appendDummySegment();
-
-    const sym_index = try zig_object.allocateSymbol(gpa);
-    const atom_index = try wasm.createAtom(sym_index, .zig_object);
-    const atom = wasm.getAtomPtr(atom_index);
-    zig_object.symbols.items[sym_index] = .{
-        .tag = .section,
-        .name = try wasm.internString(name),
-        .index = 0,
-        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
-    };
-
-    atom.alignment = .@"1"; // debug sections are always 1-byte-aligned
-    return atom_index;
-}
-
-pub fn updateLineNumber(zig_object: *ZigObject, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void {
-    if (zig_object.dwarf) |*dw| {
-        try dw.updateLineNumber(pt.zcu, ti_id);
-    }
-}
-
-/// Allocates debug atoms into their respective debug sections
-/// to merge them with maybe-existing debug atoms from object files.
-fn allocateDebugAtoms(zig_object: *ZigObject) !void {
-    if (zig_object.dwarf == null) return;
-
-    const allocAtom = struct {
-        fn f(ctx: *ZigObject, maybe_index: *?u32, atom_index: Atom.Index) !void {
-            const index = maybe_index.* orelse idx: {
-                const index = @as(u32, @intCast(ctx.segments.items.len));
-                try ctx.appendDummySegment();
-                maybe_index.* = index;
-                break :idx index;
-            };
-            const atom = ctx.getAtomPtr(atom_index);
-            atom.size = @as(u32, @intCast(atom.code.items.len));
-            ctx.symbols.items[atom.sym_index].index = index;
-            try ctx.appendAtomAtIndex(index, atom_index);
-        }
-    }.f;
-
-    try allocAtom(zig_object, &zig_object.debug_info_index, zig_object.debug_info_atom.?);
-    try allocAtom(zig_object, &zig_object.debug_line_index, zig_object.debug_line_atom.?);
-    try allocAtom(zig_object, &zig_object.debug_loc_index, zig_object.debug_loc_atom.?);
-    try allocAtom(zig_object, &zig_object.debug_str_index, zig_object.debug_str_atom.?);
-    try allocAtom(zig_object, &zig_object.debug_ranges_index, zig_object.debug_ranges_atom.?);
-    try allocAtom(zig_object, &zig_object.debug_abbrev_index, zig_object.debug_abbrev_atom.?);
-    try allocAtom(zig_object, &zig_object.debug_pubnames_index, zig_object.debug_pubnames_atom.?);
-    try allocAtom(zig_object, &zig_object.debug_pubtypes_index, zig_object.debug_pubtypes_atom.?);
-}
-
-/// For the given `decl_index`, stores the corresponding type representing the function signature.
-/// Asserts declaration has an associated `Atom`.
-/// Returns the index into the list of types.
-pub fn storeDeclType(zig_object: *ZigObject, gpa: std.mem.Allocator, nav_index: InternPool.Nav.Index, func_type: std.wasm.Type) !u32 {
-    const nav_info = zig_object.navs.get(nav_index).?;
-    const index = try zig_object.putOrGetFuncType(gpa, func_type);
-    try zig_object.atom_types.put(gpa, nav_info.atom, index);
-    return index;
-}
-
-/// The symbols in ZigObject are already represented by an atom as we need to store its data.
-/// So rather than creating a new Atom and returning its index, we use this opportunity to scan
-/// its relocations and create any GOT symbols or function table indexes it may require.
-pub fn parseSymbolIntoAtom(zig_object: *ZigObject, wasm: *Wasm, index: Symbol.Index) !Atom.Index {
-    const gpa = wasm.base.comp.gpa;
-    const loc: Wasm.SymbolLoc = .{ .file = .zig_object, .index = index };
-    const atom_index = wasm.symbol_atom.get(loc).?;
-    const final_index = try wasm.getMatchingSegment(.zig_object, index);
-    try wasm.appendAtomAtIndex(final_index, atom_index);
-    const atom = wasm.getAtom(atom_index);
-    for (atom.relocs.items) |reloc| {
-        const reloc_index: Symbol.Index = @enumFromInt(reloc.index);
-        switch (reloc.relocation_type) {
-            .R_WASM_TABLE_INDEX_I32,
-            .R_WASM_TABLE_INDEX_I64,
-            .R_WASM_TABLE_INDEX_SLEB,
-            .R_WASM_TABLE_INDEX_SLEB64,
-            => {
-                try wasm.function_table.put(gpa, .{
-                    .file = .zig_object,
-                    .index = reloc_index,
-                }, 0);
-            },
-            .R_WASM_GLOBAL_INDEX_I32,
-            .R_WASM_GLOBAL_INDEX_LEB,
-            => {
-                const sym = zig_object.symbol(reloc_index);
-                if (sym.tag != .global) {
-                    try wasm.got_symbols.append(gpa, .{
-                        .file = .zig_object,
-                        .index = reloc_index,
-                    });
-                }
-            },
-            else => {},
-        }
-    }
-    return atom_index;
-}
-
-/// Creates a new Wasm function with a given symbol name and body.
-/// Returns the symbol index of the new function.
-pub fn createFunction(
-    zig_object: *ZigObject,
-    wasm: *Wasm,
-    symbol_name: []const u8,
-    func_ty: std.wasm.Type,
-    function_body: *std.ArrayList(u8),
-    relocations: *std.ArrayList(Wasm.Relocation),
-) !Symbol.Index {
-    const gpa = wasm.base.comp.gpa;
-    const sym_index = try zig_object.allocateSymbol(gpa);
-    const sym = zig_object.symbol(sym_index);
-    sym.tag = .function;
-    sym.name = try wasm.internString(symbol_name);
-    const type_index = try zig_object.putOrGetFuncType(gpa, func_ty);
-    sym.index = try zig_object.appendFunction(gpa, .{ .type_index = type_index });
-
-    const atom_index = try wasm.createAtom(sym_index, .zig_object);
-    const atom = wasm.getAtomPtr(atom_index);
-    atom.size = @intCast(function_body.items.len);
-    atom.code = function_body.moveToUnmanaged();
-    atom.relocs = relocations.moveToUnmanaged();
-
-    try zig_object.synthetic_functions.append(gpa, atom_index);
-    return sym_index;
-}
-
-/// Appends a new `std.wasm.Func` to the list of functions and returns its index.
-fn appendFunction(zig_object: *ZigObject, gpa: std.mem.Allocator, func: std.wasm.Func) !u32 {
-    const index: u32 = if (zig_object.functions_free_list.popOrNull()) |idx|
-        idx
-    else idx: {
-        const len: u32 = @intCast(zig_object.functions.items.len);
-        _ = try zig_object.functions.addOne(gpa);
-        break :idx len;
-    };
-    zig_object.functions.items[index] = func;
-
-    return index;
-}
-
-pub fn flushModule(zig_object: *ZigObject, wasm: *Wasm, tid: Zcu.PerThread.Id) !void {
-    try zig_object.populateErrorNameTable(wasm, tid);
-    try zig_object.setupErrorsLen(wasm);
-}
-
-const build_options = @import("build_options");
-const builtin = @import("builtin");
-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;
-const Dwarf = @import("../Dwarf.zig");
-const InternPool = @import("../../InternPool.zig");
-const Liveness = @import("../../Liveness.zig");
-const Zcu = @import("../../Zcu.zig");
-const Symbol = @import("Symbol.zig");
-const Type = @import("../../Type.zig");
-const Value = @import("../../Value.zig");
-const Wasm = @import("../Wasm.zig");
-const AnalUnit = InternPool.AnalUnit;
-const ZigObject = @This();
src/link/C.zig
@@ -175,21 +175,13 @@ pub fn deinit(self: *C) void {
     self.lazy_code_buf.deinit(gpa);
 }
 
-pub fn freeDecl(self: *C, decl_index: InternPool.DeclIndex) void {
-    const gpa = self.base.comp.gpa;
-    if (self.decl_table.fetchSwapRemove(decl_index)) |kv| {
-        var decl_block = kv.value;
-        decl_block.deinit(gpa);
-    }
-}
-
 pub fn updateFunc(
     self: *C,
     pt: Zcu.PerThread,
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
-) !void {
+) link.File.UpdateNavError!void {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const func = zcu.funcInfo(func_index);
@@ -313,7 +305,7 @@ fn updateUav(self: *C, pt: Zcu.PerThread, i: usize) !void {
     };
 }
 
-pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
+pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.File.UpdateNavError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -390,7 +382,7 @@ pub fn updateLineNumber(self: *C, pt: Zcu.PerThread, ti_id: InternPool.TrackedIn
     _ = ti_id;
 }
 
-pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {
+pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
     return self.flushModule(arena, tid, prog_node);
 }
 
@@ -409,7 +401,7 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
     return defines;
 }
 
-pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {
+pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
     _ = arena; // Has the same lifetime as the call to Compilation.update.
 
     const tracy = trace(@src());
@@ -419,6 +411,7 @@ pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
     defer sub_prog_node.end();
 
     const comp = self.base.comp;
+    const diags = &comp.link_diags;
     const gpa = comp.gpa;
     const zcu = self.base.comp.zcu.?;
     const ip = &zcu.intern_pool;
@@ -554,8 +547,10 @@ pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
     }, self.getString(av_block.code));
 
     const file = self.base.file.?;
-    try file.setEndPos(f.file_size);
-    try file.pwritevAll(f.all_buffers.items, 0);
+    file.setEndPos(f.file_size) catch |err| return diags.fail("failed to allocate file: {s}", .{@errorName(err)});
+    file.pwritevAll(f.all_buffers.items, 0) catch |err| return diags.fail("failed to write to '{'}': {s}", .{
+        self.base.emit, @errorName(err),
+    });
 }
 
 const Flush = struct {
src/link/Coff.zig
@@ -408,7 +408,7 @@ pub fn createEmpty(
                 max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data;
             }
         }
-        try coff.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset);
+        try coff.pwriteAll(&[_]u8{0}, max_file_offset);
     }
 
     return coff;
@@ -858,7 +858,7 @@ fn writeAtom(coff: *Coff, atom_index: Atom.Index, code: []u8) !void {
     }
 
     coff.resolveRelocs(atom_index, relocs.items, code, coff.image_base);
-    try coff.base.file.?.pwriteAll(code, file_offset);
+    try coff.pwriteAll(code, file_offset);
 
     // Now we can mark the relocs as resolved.
     while (relocs.popOrNull()) |reloc| {
@@ -891,7 +891,7 @@ fn writeOffsetTableEntry(coff: *Coff, index: usize) !void {
     const sect_id = coff.got_section_index.?;
 
     if (coff.got_table_count_dirty) {
-        const needed_size = @as(u32, @intCast(coff.got_table.entries.items.len * coff.ptr_width.size()));
+        const needed_size: u32 = @intCast(coff.got_table.entries.items.len * coff.ptr_width.size());
         try coff.growSection(sect_id, needed_size);
         coff.got_table_count_dirty = false;
     }
@@ -908,13 +908,13 @@ fn writeOffsetTableEntry(coff: *Coff, index: usize) !void {
     switch (coff.ptr_width) {
         .p32 => {
             var buf: [4]u8 = undefined;
-            mem.writeInt(u32, &buf, @as(u32, @intCast(entry_value + coff.image_base)), .little);
-            try coff.base.file.?.pwriteAll(&buf, file_offset);
+            mem.writeInt(u32, &buf, @intCast(entry_value + coff.image_base), .little);
+            try coff.pwriteAll(&buf, file_offset);
         },
         .p64 => {
             var buf: [8]u8 = undefined;
             mem.writeInt(u64, &buf, entry_value + coff.image_base, .little);
-            try coff.base.file.?.pwriteAll(&buf, file_offset);
+            try coff.pwriteAll(&buf, file_offset);
         },
     }
 
@@ -1093,7 +1093,13 @@ fn freeAtom(coff: *Coff, atom_index: Atom.Index) void {
     coff.getAtomPtr(atom_index).sym_index = 0;
 }
 
-pub fn updateFunc(coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+pub fn updateFunc(
+    coff: *Coff,
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+    air: Air,
+    liveness: Liveness,
+) link.File.UpdateNavError!void {
     if (build_options.skip_non_native and builtin.object_format != .coff) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
     }
@@ -1106,8 +1112,9 @@ pub fn updateFunc(coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index,
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const func = zcu.funcInfo(func_index);
+    const nav_index = func.owner_nav;
 
-    const atom_index = try coff.getOrCreateAtomForNav(func.owner_nav);
+    const atom_index = try coff.getOrCreateAtomForNav(nav_index);
     coff.freeRelocations(atom_index);
 
     coff.navs.getPtr(func.owner_nav).?.section = coff.text_section_index.?;
@@ -1115,25 +1122,38 @@ pub fn updateFunc(coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index,
     var code_buffer = std.ArrayList(u8).init(gpa);
     defer code_buffer.deinit();
 
-    const res = try codegen.generateFunction(
+    const res = codegen.generateFunction(
         &coff.base,
         pt,
-        zcu.navSrcLoc(func.owner_nav),
+        zcu.navSrcLoc(nav_index),
         func_index,
         air,
         liveness,
         &code_buffer,
         .none,
-    );
+    ) catch |err| switch (err) {
+        error.CodegenFail => return error.CodegenFail,
+        error.OutOfMemory => return error.OutOfMemory,
+        else => |e| {
+            try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
+                gpa,
+                zcu.navSrcLoc(nav_index),
+                "unable to codegen: {s}",
+                .{@errorName(e)},
+            ));
+            try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index }));
+            return error.CodegenFail;
+        },
+    };
     const code = switch (res) {
         .ok => code_buffer.items,
         .fail => |em| {
-            try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, em);
+            try zcu.failed_codegen.put(zcu.gpa, nav_index, em);
             return;
         },
     };
 
-    try coff.updateNavCode(pt, func.owner_nav, code, .FUNCTION);
+    try coff.updateNavCode(pt, nav_index, code, .FUNCTION);
 
     // Exports will be updated by `Zcu.processExports` after the update.
 }
@@ -1258,9 +1278,11 @@ fn updateLazySymbolAtom(
     sym: link.File.LazySymbol,
     atom_index: Atom.Index,
     section_index: u16,
-) !void {
+) link.File.FlushError!void {
     const zcu = pt.zcu;
-    const gpa = zcu.gpa;
+    const comp = coff.base.comp;
+    const gpa = comp.gpa;
+    const diags = &comp.link_diags;
 
     var required_alignment: InternPool.Alignment = .none;
     var code_buffer = std.ArrayList(u8).init(gpa);
@@ -1276,7 +1298,7 @@ fn updateLazySymbolAtom(
     const local_sym_index = atom.getSymbolIndex().?;
 
     const src = Type.fromInterned(sym.ty).srcLocOrNull(zcu) orelse Zcu.LazySrcLoc.unneeded;
-    const res = try codegen.generateLazySymbol(
+    const res = codegen.generateLazySymbol(
         &coff.base,
         pt,
         src,
@@ -1285,7 +1307,10 @@ fn updateLazySymbolAtom(
         &code_buffer,
         .none,
         .{ .atom_index = local_sym_index },
-    );
+    ) catch |err| switch (err) {
+        error.CodegenFail => return error.LinkFailure,
+        else => |e| return diags.fail("failed to generate lazy symbol: {s}", .{@errorName(e)}),
+    };
     const code = switch (res) {
         .ok => code_buffer.items,
         .fail => |em| {
@@ -1387,7 +1412,7 @@ fn updateNavCode(
     nav_index: InternPool.Nav.Index,
     code: []u8,
     complex_type: coff_util.ComplexType,
-) !void {
+) link.File.UpdateNavError!void {
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
     const nav = ip.getNav(nav_index);
@@ -1405,12 +1430,12 @@ fn updateNavCode(
     const atom = coff.getAtom(atom_index);
     const sym_index = atom.getSymbolIndex().?;
     const sect_index = nav_metadata.section;
-    const code_len = @as(u32, @intCast(code.len));
+    const code_len: u32 = @intCast(code.len);
 
     if (atom.size != 0) {
         const sym = atom.getSymbolPtr(coff);
         try coff.setSymbolName(sym, nav.fqn.toSlice(ip));
-        sym.section_number = @as(coff_util.SectionNumber, @enumFromInt(sect_index + 1));
+        sym.section_number = @enumFromInt(sect_index + 1);
         sym.type = .{ .complex_type = complex_type, .base_type = .NULL };
 
         const capacity = atom.capacity(coff);
@@ -1434,7 +1459,7 @@ fn updateNavCode(
     } else {
         const sym = atom.getSymbolPtr(coff);
         try coff.setSymbolName(sym, nav.fqn.toSlice(ip));
-        sym.section_number = @as(coff_util.SectionNumber, @enumFromInt(sect_index + 1));
+        sym.section_number = @enumFromInt(sect_index + 1);
         sym.type = .{ .complex_type = complex_type, .base_type = .NULL };
 
         const vaddr = try coff.allocateAtom(atom_index, code_len, @intCast(required_alignment.toByteUnits() orelse 0));
@@ -1453,7 +1478,6 @@ pub fn freeNav(coff: *Coff, nav_index: InternPool.NavIndex) void {
     if (coff.llvm_object) |llvm_object| return llvm_object.freeNav(nav_index);
 
     const gpa = coff.base.comp.gpa;
-    log.debug("freeDecl 0x{x}", .{nav_index});
 
     if (coff.decls.fetchOrderedRemove(nav_index)) |const_kv| {
         var kv = const_kv;
@@ -1674,9 +1698,10 @@ pub fn flush(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st
     if (use_lld) {
         return coff.linkWithLLD(arena, tid, prog_node);
     }
+    const diags = &comp.link_diags;
     switch (comp.config.output_mode) {
         .Exe, .Obj => return coff.flushModule(arena, tid, prog_node),
-        .Lib => return error.TODOImplementWritingLibFiles,
+        .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}),
     }
 }
 
@@ -2224,7 +2249,7 @@ pub fn flushModule(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
     defer sub_prog_node.end();
 
     const pt: Zcu.PerThread = .activate(
-        comp.zcu orelse return error.LinkingWithoutZigSourceUnimplemented,
+        comp.zcu orelse return diags.fail("linking without zig source is not yet implemented", .{}),
         tid,
     );
     defer pt.deactivate();
@@ -2232,24 +2257,18 @@ pub fn flushModule(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_no
     if (coff.lazy_syms.getPtr(.anyerror_type)) |metadata| {
         // Most lazy symbols can be updated on first use, but
         // anyerror needs to wait for everything to be flushed.
-        if (metadata.text_state != .unused) coff.updateLazySymbolAtom(
+        if (metadata.text_state != .unused) try coff.updateLazySymbolAtom(
             pt,
             .{ .kind = .code, .ty = .anyerror_type },
             metadata.text_atom,
             coff.text_section_index.?,
-        ) catch |err| return switch (err) {
-            error.CodegenFail => error.FlushFailure,
-            else => |e| e,
-        };
-        if (metadata.rdata_state != .unused) coff.updateLazySymbolAtom(
+        );
+        if (metadata.rdata_state != .unused) try coff.updateLazySymbolAtom(
             pt,
             .{ .kind = .const_data, .ty = .anyerror_type },
             metadata.rdata_atom,
             coff.rdata_section_index.?,
-        ) catch |err| return switch (err) {
-            error.CodegenFail => error.FlushFailure,
-            else => |e| e,
-        };
+        );
     }
     for (coff.lazy_syms.values()) |*metadata| {
         if (metadata.text_state != .unused) metadata.text_state = .flushed;
@@ -2594,7 +2613,7 @@ fn writeBaseRelocations(coff: *Coff) !void {
     const needed_size = @as(u32, @intCast(buffer.items.len));
     try coff.growSection(coff.reloc_section_index.?, needed_size);
 
-    try coff.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data);
+    try coff.pwriteAll(buffer.items, header.pointer_to_raw_data);
 
     coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.BASERELOC)] = .{
         .virtual_address = header.virtual_address,
@@ -2727,7 +2746,7 @@ fn writeImportTables(coff: *Coff) !void {
 
     assert(dll_names_offset == needed_size);
 
-    try coff.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data);
+    try coff.pwriteAll(buffer.items, header.pointer_to_raw_data);
 
     coff.data_directories[@intFromEnum(coff_util.DirectoryEntry.IMPORT)] = .{
         .virtual_address = header.virtual_address + iat_size,
@@ -2741,20 +2760,22 @@ fn writeImportTables(coff: *Coff) !void {
     coff.imports_count_dirty = false;
 }
 
-fn writeStrtab(coff: *Coff) !void {
+fn writeStrtab(coff: *Coff) link.File.FlushError!void {
     if (coff.strtab_offset == null) return;
 
+    const comp = coff.base.comp;
+    const gpa = comp.gpa;
+    const diags = &comp.link_diags;
     const allocated_size = coff.allocatedSize(coff.strtab_offset.?);
-    const needed_size = @as(u32, @intCast(coff.strtab.buffer.items.len));
+    const needed_size: u32 = @intCast(coff.strtab.buffer.items.len);
 
     if (needed_size > allocated_size) {
         coff.strtab_offset = null;
-        coff.strtab_offset = @as(u32, @intCast(coff.findFreeSpace(needed_size, @alignOf(u32))));
+        coff.strtab_offset = @intCast(coff.findFreeSpace(needed_size, @alignOf(u32)));
     }
 
     log.debug("writing strtab from 0x{x} to 0x{x}", .{ coff.strtab_offset.?, coff.strtab_offset.? + needed_size });
 
-    const gpa = coff.base.comp.gpa;
     var buffer = std.ArrayList(u8).init(gpa);
     defer buffer.deinit();
     try buffer.ensureTotalCapacityPrecise(needed_size);
@@ -2763,17 +2784,19 @@ fn writeStrtab(coff: *Coff) !void {
     // we write the length of the strtab to a temporary buffer that goes to file.
     mem.writeInt(u32, buffer.items[0..4], @as(u32, @intCast(coff.strtab.buffer.items.len)), .little);
 
-    try coff.base.file.?.pwriteAll(buffer.items, coff.strtab_offset.?);
+    coff.pwriteAll(buffer.items, coff.strtab_offset.?) catch |err| {
+        return diags.fail("failed to write: {s}", .{@errorName(err)});
+    };
 }
 
 fn writeSectionHeaders(coff: *Coff) !void {
     const offset = coff.getSectionHeadersOffset();
-    try coff.base.file.?.pwriteAll(mem.sliceAsBytes(coff.sections.items(.header)), offset);
+    try coff.pwriteAll(mem.sliceAsBytes(coff.sections.items(.header)), offset);
 }
 
 fn writeDataDirectoriesHeaders(coff: *Coff) !void {
     const offset = coff.getDataDirectoryHeadersOffset();
-    try coff.base.file.?.pwriteAll(mem.sliceAsBytes(&coff.data_directories), offset);
+    try coff.pwriteAll(mem.sliceAsBytes(&coff.data_directories), offset);
 }
 
 fn writeHeader(coff: *Coff) !void {
@@ -2913,7 +2936,7 @@ fn writeHeader(coff: *Coff) !void {
         },
     }
 
-    try coff.base.file.?.pwriteAll(buffer.items, 0);
+    try coff.pwriteAll(buffer.items, 0);
 }
 
 pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
@@ -3710,6 +3733,14 @@ const ImportTable = struct {
     const ImportIndex = u32;
 };
 
+fn pwriteAll(coff: *Coff, bytes: []const u8, offset: u64) error{LinkFailure}!void {
+    const comp = coff.base.comp;
+    const diags = &comp.link_diags;
+    coff.base.file.?.pwriteAll(bytes, offset) catch |err| {
+        return diags.fail("failed to write: {s}", .{@errorName(err)});
+    };
+}
+
 const Coff = @This();
 
 const std = @import("std");
src/link/Dwarf.zig
@@ -21,20 +21,10 @@ debug_rnglists: DebugRngLists,
 debug_str: StringSection,
 
 pub const UpdateError = error{
+    /// Indicates the error is already reported on `failed_codegen` in the Zcu.
     CodegenFail,
-    ReinterpretDeclRef,
-    Unimplemented,
     OutOfMemory,
-    EndOfStream,
-    Overflow,
-    Underflow,
-    UnexpectedEndOfFile,
-} ||
-    std.fs.File.OpenError ||
-    std.fs.File.SetEndPosError ||
-    std.fs.File.CopyRangeError ||
-    std.fs.File.PReadError ||
-    std.fs.File.PWriteError;
+};
 
 pub const FlushError =
     UpdateError ||
src/link/Elf.zig
@@ -842,12 +842,12 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
         .Exe => {},
     }
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 
     // If we haven't already, create a linker-generated input file comprising of
     // linker-defined synthetic symbols only such as `_DYNAMIC`, etc.
     if (self.linker_defined_index == null) {
-        const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
+        const index: File.Index = @intCast(try self.files.addOne(gpa));
         self.files.set(index, .{ .linker_defined = .{ .index = index } });
         self.linker_defined_index = index;
         const object = self.linkerDefinedPtr().?;
@@ -878,7 +878,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
     }
 
     self.checkDuplicates() catch |err| switch (err) {
-        error.HasDuplicates => return error.FlushFailure,
+        error.HasDuplicates => return error.LinkFailure,
         else => |e| return e,
     };
 
@@ -956,14 +956,14 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
                 error.RelocFailure, error.RelaxFailure => has_reloc_errors = true,
                 error.UnsupportedCpuArch => {
                     try self.reportUnsupportedCpuArch();
-                    return error.FlushFailure;
+                    return error.LinkFailure;
                 },
                 else => |e| return e,
             };
             try self.base.file.?.pwriteAll(code, file_offset);
         }
 
-        if (has_reloc_errors) return error.FlushFailure;
+        if (has_reloc_errors) return error.LinkFailure;
     }
 
     try self.writePhdrTable();
@@ -972,10 +972,10 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
     try self.writeMergeSections();
 
     self.writeSyntheticSections() catch |err| switch (err) {
-        error.RelocFailure => return error.FlushFailure,
+        error.RelocFailure => return error.LinkFailure,
         error.UnsupportedCpuArch => {
             try self.reportUnsupportedCpuArch();
-            return error.FlushFailure;
+            return error.LinkFailure;
         },
         else => |e| return e,
     };
@@ -989,7 +989,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
         try self.writeElfHeader();
     }
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 }
 
 fn dumpArgvInit(self: *Elf, arena: Allocator) !void {
@@ -1389,7 +1389,7 @@ fn scanRelocs(self: *Elf) !void {
             error.RelaxFailure => unreachable,
             error.UnsupportedCpuArch => {
                 try self.reportUnsupportedCpuArch();
-                return error.FlushFailure;
+                return error.LinkFailure;
             },
             error.RelocFailure => has_reloc_errors = true,
             else => |e| return e,
@@ -1400,7 +1400,7 @@ fn scanRelocs(self: *Elf) !void {
             error.RelaxFailure => unreachable,
             error.UnsupportedCpuArch => {
                 try self.reportUnsupportedCpuArch();
-                return error.FlushFailure;
+                return error.LinkFailure;
             },
             error.RelocFailure => has_reloc_errors = true,
             else => |e| return e,
@@ -1409,7 +1409,7 @@ fn scanRelocs(self: *Elf) !void {
 
     try self.reportUndefinedSymbols(&undefs);
 
-    if (has_reloc_errors) return error.FlushFailure;
+    if (has_reloc_errors) return error.LinkFailure;
 
     if (self.zigObjectPtr()) |zo| {
         try zo.asFile().createSymbolIndirection(self);
@@ -2327,7 +2327,13 @@ pub fn freeNav(self: *Elf, nav: InternPool.Nav.Index) void {
     return self.zigObjectPtr().?.freeNav(self, nav);
 }
 
-pub fn updateFunc(self: *Elf, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+pub fn updateFunc(
+    self: *Elf,
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+    air: Air,
+    liveness: Liveness,
+) link.File.UpdateNavError!void {
     if (build_options.skip_non_native and builtin.object_format != .elf) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
     }
@@ -2426,7 +2432,7 @@ pub fn addCommentString(self: *Elf) !void {
     self.comment_merge_section_index = msec_index;
 }
 
-pub fn resolveMergeSections(self: *Elf) !void {
+pub fn resolveMergeSections(self: *Elf) link.File.FlushError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2441,7 +2447,7 @@ pub fn resolveMergeSections(self: *Elf) !void {
         };
     }
 
-    if (has_errors) return error.FlushFailure;
+    if (has_errors) return error.LinkFailure;
 
     for (self.objects.items) |index| {
         const object = self.file(index).?.object;
@@ -3658,7 +3664,7 @@ fn writeAtoms(self: *Elf) !void {
         atom_list.write(&buffer, &undefs, self) catch |err| switch (err) {
             error.UnsupportedCpuArch => {
                 try self.reportUnsupportedCpuArch();
-                return error.FlushFailure;
+                return error.LinkFailure;
             },
             error.RelocFailure, error.RelaxFailure => has_reloc_errors = true,
             else => |e| return e,
@@ -3666,7 +3672,7 @@ fn writeAtoms(self: *Elf) !void {
     }
 
     try self.reportUndefinedSymbols(&undefs);
-    if (has_reloc_errors) return error.FlushFailure;
+    if (has_reloc_errors) return error.LinkFailure;
 
     if (self.requiresThunks()) {
         for (self.thunks.items) |th| {
src/link/MachO.zig
@@ -481,7 +481,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
         }
     };
 
-    if (diags.hasErrors()) return error.FlushFailure;
+    if (diags.hasErrors()) return error.LinkFailure;
 
     {
         const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
@@ -501,7 +501,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
     }
 
     self.checkDuplicates() catch |err| switch (err) {
-        error.HasDuplicates => return error.FlushFailure,
+        error.HasDuplicates => return error.LinkFailure,
         else => |e| return diags.fail("failed to check for duplicate symbol definitions: {s}", .{@errorName(e)}),
     };
 
@@ -516,7 +516,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
     self.claimUnresolved();
 
     self.scanRelocs() catch |err| switch (err) {
-        error.HasUndefinedSymbols => return error.FlushFailure,
+        error.HasUndefinedSymbols => return error.LinkFailure,
         else => |e| return diags.fail("failed to scan relocations: {s}", .{@errorName(e)}),
     };
 
@@ -543,7 +543,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
 
     if (self.getZigObject()) |zo| {
         zo.resolveRelocs(self) catch |err| switch (err) {
-            error.ResolveFailed => return error.FlushFailure,
+            error.ResolveFailed => return error.LinkFailure,
             else => |e| return e,
         };
     }
@@ -2998,7 +2998,13 @@ pub fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void {
     try self.base.file.?.pwriteAll(buffer.items, offset);
 }
 
-pub fn updateFunc(self: *MachO, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+pub fn updateFunc(
+    self: *MachO,
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+    air: Air,
+    liveness: Liveness,
+) link.File.UpdateNavError!void {
     if (build_options.skip_non_native and builtin.object_format != .macho) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
     }
@@ -3006,7 +3012,7 @@ pub fn updateFunc(self: *MachO, pt: Zcu.PerThread, func_index: InternPool.Index,
     return self.getZigObject().?.updateFunc(self, pt, func_index, air, liveness);
 }
 
-pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !void {
+pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
     if (build_options.skip_non_native and builtin.object_format != .macho) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
     }
src/link/NvPtx.zig
@@ -82,11 +82,17 @@ pub fn deinit(self: *NvPtx) void {
     self.llvm_object.deinit();
 }
 
-pub fn updateFunc(self: *NvPtx, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+pub fn updateFunc(
+    self: *NvPtx,
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+    air: Air,
+    liveness: Liveness,
+) link.File.UpdateNavError!void {
     try self.llvm_object.updateFunc(pt, func_index, air, liveness);
 }
 
-pub fn updateNav(self: *NvPtx, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !void {
+pub fn updateNav(self: *NvPtx, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
     return self.llvm_object.updateNav(pt, nav);
 }
 
@@ -102,10 +108,6 @@ pub fn updateExports(
     return self.llvm_object.updateExports(pt, exported, export_indices);
 }
 
-pub fn freeDecl(self: *NvPtx, decl_index: InternPool.DeclIndex) void {
-    return self.llvm_object.freeDecl(decl_index);
-}
-
 pub fn flush(self: *NvPtx, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
     return self.flushModule(arena, tid, prog_node);
 }
src/link/Plan9.zig
@@ -385,7 +385,13 @@ fn addPathComponents(self: *Plan9, path: []const u8, a: *std.ArrayList(u8)) !voi
     }
 }
 
-pub fn updateFunc(self: *Plan9, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+pub fn updateFunc(
+    self: *Plan9,
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+    air: Air,
+    liveness: Liveness,
+) link.File.UpdateNavError!void {
     if (build_options.skip_non_native and builtin.object_format != .plan9) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
     }
@@ -437,7 +443,7 @@ pub fn updateFunc(self: *Plan9, pt: Zcu.PerThread, func_index: InternPool.Index,
     return self.updateFinish(pt, func.owner_nav);
 }
 
-pub fn updateNav(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
+pub fn updateNav(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.File.UpdateNavError!void {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
@@ -619,7 +625,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
             .{ .kind = .code, .ty = .anyerror_type },
             metadata.text_atom,
         ) catch |err| return switch (err) {
-            error.CodegenFail => error.FlushFailure,
+            error.CodegenFail => error.LinkFailure,
             else => |e| e,
         };
         if (metadata.rodata_state != .unused) self.updateLazySymbolAtom(
@@ -627,7 +633,7 @@ pub fn flushModule(self: *Plan9, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
             .{ .kind = .const_data, .ty = .anyerror_type },
             metadata.rodata_atom,
         ) catch |err| return switch (err) {
-            error.CodegenFail => error.FlushFailure,
+            error.CodegenFail => error.LinkFailure,
             else => |e| e,
         };
     }
@@ -947,50 +953,6 @@ fn addNavExports(
     }
 }
 
-pub fn freeDecl(self: *Plan9, decl_index: InternPool.DeclIndex) void {
-    const gpa = self.base.comp.gpa;
-    // TODO audit the lifetimes of decls table entries. It's possible to get
-    // freeDecl without any updateDecl in between.
-    const zcu = self.base.comp.zcu.?;
-    const decl = zcu.declPtr(decl_index);
-    const is_fn = decl.val.isFuncBody(zcu);
-    if (is_fn) {
-        const symidx_and_submap = self.fn_decl_table.get(decl.getFileScope(zcu)).?;
-        var submap = symidx_and_submap.functions;
-        if (submap.fetchSwapRemove(decl_index)) |removed_entry| {
-            gpa.free(removed_entry.value.code);
-            gpa.free(removed_entry.value.lineinfo);
-        }
-        if (submap.count() == 0) {
-            self.syms.items[symidx_and_submap.sym_index] = aout.Sym.undefined_symbol;
-            self.syms_index_free_list.append(gpa, symidx_and_submap.sym_index) catch {};
-            submap.deinit(gpa);
-        }
-    } else {
-        if (self.data_decl_table.fetchSwapRemove(decl_index)) |removed_entry| {
-            gpa.free(removed_entry.value);
-        }
-    }
-    if (self.decls.fetchRemove(decl_index)) |const_kv| {
-        var kv = const_kv;
-        const atom = self.getAtom(kv.value.index);
-        if (atom.got_index) |i| {
-            // TODO: if this catch {} is triggered, an assertion in flushModule will be triggered, because got_index_free_list will have the wrong length
-            self.got_index_free_list.append(gpa, i) catch {};
-        }
-        if (atom.sym_index) |i| {
-            self.syms_index_free_list.append(gpa, i) catch {};
-            self.syms.items[i] = aout.Sym.undefined_symbol;
-        }
-        kv.value.exports.deinit(gpa);
-    }
-    {
-        const atom_index = self.decls.get(decl_index).?.index;
-        const relocs = self.relocs.getPtr(atom_index) orelse return;
-        relocs.clearAndFree(gpa);
-        assert(self.relocs.remove(atom_index));
-    }
-}
 fn createAtom(self: *Plan9) !Atom.Index {
     const gpa = self.base.comp.gpa;
     const index = @as(Atom.Index, @intCast(self.atoms.items.len));
src/link/SpirV.zig
@@ -122,7 +122,13 @@ pub fn deinit(self: *SpirV) void {
     self.object.deinit();
 }
 
-pub fn updateFunc(self: *SpirV, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+pub fn updateFunc(
+    self: *SpirV,
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+    air: Air,
+    liveness: Liveness,
+) link.File.UpdateNavError!void {
     if (build_options.skip_non_native) {
         @panic("Attempted to compile for architecture that was disabled by build configuration");
     }
@@ -134,7 +140,7 @@ pub fn updateFunc(self: *SpirV, pt: Zcu.PerThread, func_index: InternPool.Index,
     try self.object.updateFunc(pt, func_index, air, liveness);
 }
 
-pub fn updateNav(self: *SpirV, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !void {
+pub fn updateNav(self: *SpirV, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void {
     if (build_options.skip_non_native) {
         @panic("Attempted to compile for architecture that was disabled by build configuration");
     }
@@ -196,11 +202,6 @@ pub fn updateExports(
     // TODO: Export regular functions, variables, etc using Linkage attributes.
 }
 
-pub fn freeDecl(self: *SpirV, decl_index: InternPool.DeclIndex) void {
-    _ = self;
-    _ = decl_index;
-}
-
 pub fn flush(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
     return self.flushModule(arena, tid, prog_node);
 }
@@ -266,7 +267,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
         error.OutOfMemory => return error.OutOfMemory,
         else => |other| {
             log.err("error while linking: {s}", .{@errorName(other)});
-            return error.FlushFailure;
+            return error.LinkFailure;
         },
     };
 
src/link/Wasm.zig
@@ -1,44 +1,49 @@
 const Wasm = @This();
-const build_options = @import("build_options");
+const Archive = @import("Wasm/Archive.zig");
+const Object = @import("Wasm/Object.zig");
+const Flush = @import("Wasm/Flush.zig");
 
 const builtin = @import("builtin");
 const native_endian = builtin.cpu.arch.endian();
 
+const build_options = @import("build_options");
+
 const std = @import("std");
 const Allocator = std.mem.Allocator;
 const Cache = std.Build.Cache;
 const Path = Cache.Path;
 const assert = std.debug.assert;
 const fs = std.fs;
-const gc_log = std.log.scoped(.gc);
 const leb = std.leb;
 const log = std.log.scoped(.link);
 const mem = std.mem;
 
 const Air = @import("../Air.zig");
-const Archive = @import("Wasm/Archive.zig");
 const CodeGen = @import("../arch/wasm/CodeGen.zig");
 const Compilation = @import("../Compilation.zig");
 const Dwarf = @import("Dwarf.zig");
 const InternPool = @import("../InternPool.zig");
 const Liveness = @import("../Liveness.zig");
 const LlvmObject = @import("../codegen/llvm.zig").Object;
-const Object = @import("Wasm/Object.zig");
-const Symbol = @import("Wasm/Symbol.zig");
-const Type = @import("../Type.zig");
-const Value = @import("../Value.zig");
 const Zcu = @import("../Zcu.zig");
-const ZigObject = @import("Wasm/ZigObject.zig");
 const codegen = @import("../codegen.zig");
 const dev = @import("../dev.zig");
 const link = @import("../link.zig");
 const lldMain = @import("../main.zig").lldMain;
 const trace = @import("../tracy.zig").trace;
 const wasi_libc = @import("../wasi_libc.zig");
+const Value = @import("../Value.zig");
 
 base: link.File,
 /// Null-terminated strings, indexes have type String and string_table provides
 /// lookup.
+///
+/// There are a couple of sites that add things here without adding
+/// corresponding string_table entries. For such cases, when implementing
+/// serialization/deserialization, they should be adjusted to prefix that data
+/// with a null byte so that deserialization does not attempt to create
+/// string_table entries for them. Alternately those sites could be moved to
+/// use a different byte array for this purpose.
 string_bytes: std.ArrayListUnmanaged(u8),
 /// Omitted when serializing linker state.
 string_table: String.Table,
@@ -62,118 +67,637 @@ export_table: bool,
 name: []const u8,
 /// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
 llvm_object: ?LlvmObject.Ptr = null,
-zig_object: ?*ZigObject,
 /// List of relocatable files to be linked into the final binary.
 objects: std.ArrayListUnmanaged(Object) = .{},
+
+func_types: std.AutoArrayHashMapUnmanaged(FunctionType, void) = .empty,
+/// Provides a mapping of both imports and provided functions to symbol name.
+/// Local functions may be unnamed.
+object_function_imports: std.AutoArrayHashMapUnmanaged(String, FunctionImport) = .empty,
+/// All functions for all objects.
+object_functions: std.ArrayListUnmanaged(Function) = .empty,
+
+/// Provides a mapping of both imports and provided globals to symbol name.
+/// Local globals may be unnamed.
+object_global_imports: std.AutoArrayHashMapUnmanaged(String, GlobalImport) = .empty,
+/// All globals for all objects.
+object_globals: std.ArrayListUnmanaged(Global) = .empty,
+
+/// All table imports for all objects.
+object_table_imports: std.ArrayListUnmanaged(TableImport) = .empty,
+/// All parsed table sections for all objects.
+object_tables: std.ArrayListUnmanaged(Table) = .empty,
+
+/// All memory imports for all objects.
+object_memory_imports: std.ArrayListUnmanaged(MemoryImport) = .empty,
+/// All parsed memory sections for all objects.
+object_memories: std.ArrayListUnmanaged(std.wasm.Memory) = .empty,
+
+/// List of initialization functions. These must be called in order of priority
+/// by the (synthetic) __wasm_call_ctors function.
+object_init_funcs: std.ArrayListUnmanaged(InitFunc) = .empty,
+/// All relocations from all objects concatenated. `relocs_start` marks the end
+/// point of object relocations and start point of Zcu relocations.
+relocations: std.MultiArrayList(Relocation) = .empty,
+
+/// Non-synthetic section that can essentially be mem-cpy'd into place after performing relocations.
+object_data_segments: std.ArrayListUnmanaged(DataSegment) = .empty,
+/// Non-synthetic section that can essentially be mem-cpy'd into place after performing relocations.
+object_custom_segments: std.AutoArrayHashMapUnmanaged(ObjectSectionIndex, CustomSegment) = .empty,
+
+/// All comdat information for all objects.
+object_comdats: std.ArrayListUnmanaged(Comdat) = .empty,
+/// A table that maps the relocations to be performed where the key represents
+/// the section (across all objects) that the slice of relocations applies to.
+object_relocations_table: std.AutoArrayHashMapUnmanaged(ObjectSectionIndex, Relocation.Slice) = .empty,
+/// Incremented across all objects in order to enable calculation of `ObjectSectionIndex` values.
+object_total_sections: u32 = 0,
+/// All comdat symbols from all objects concatenated.
+object_comdat_symbols: std.MultiArrayList(Comdat.Symbol) = .empty,
+
 /// When importing objects from the host environment, a name must be supplied.
 /// LLVM uses "env" by default when none is given. This would be a good default for Zig
 /// to support existing code.
 /// TODO: Allow setting this through a flag?
 host_name: String,
-/// List of symbols generated by the linker.
-synthetic_symbols: std.ArrayListUnmanaged(Symbol) = .empty,
-/// Maps atoms to their segment index
-atoms: std.AutoHashMapUnmanaged(Segment.Index, Atom.Index) = .empty,
-/// List of all atoms.
-managed_atoms: std.ArrayListUnmanaged(Atom) = .empty,
-
-/// The count of imported functions. This number will be appended
-/// to the function indexes as their index starts at the lowest non-extern function.
-imported_functions_count: u32 = 0,
-/// The count of imported wasm globals. This number will be appended
-/// to the global indexes when sections are merged.
-imported_globals_count: u32 = 0,
-/// The count of imported tables. This number will be appended
-/// to the table indexes when sections are merged.
-imported_tables_count: u32 = 0,
-/// Map of symbol locations, represented by its `Import`
-imports: std.AutoHashMapUnmanaged(SymbolLoc, Import) = .empty,
-/// Represents non-synthetic section entries.
-/// Used for code, data and custom sections.
-segments: std.ArrayListUnmanaged(Segment) = .empty,
-/// Maps a data segment key (such as .rodata) to the index into `segments`.
-data_segments: std.StringArrayHashMapUnmanaged(Segment.Index) = .empty,
-/// A table of `NamedSegment` which provide meta data
-/// about a data symbol such as its name where the key is
-/// the segment index, which can be found from `data_segments`
-segment_info: std.AutoArrayHashMapUnmanaged(Segment.Index, NamedSegment) = .empty,
-
-// Output sections
-/// Output type section
-func_types: std.ArrayListUnmanaged(std.wasm.Type) = .empty,
-/// Output function section where the key is the original
-/// function index and the value is function.
-/// This allows us to map multiple symbols to the same function.
-functions: std.AutoArrayHashMapUnmanaged(
-    struct {
-        /// `none` in the case of synthetic sections.
-        file: OptionalObjectId,
-        index: u32,
-    },
-    struct {
-        func: std.wasm.Func,
-        sym_index: Symbol.Index,
-    },
-) = .{},
-/// Output global section
-wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .empty,
+
 /// Memory section
 memories: std.wasm.Memory = .{ .limits = .{
     .min = 0,
     .max = undefined,
-    .flags = 0,
+    .flags = .{ .has_max = false, .is_shared = false },
 } },
-/// Output table section
-tables: std.ArrayListUnmanaged(std.wasm.Table) = .empty,
-/// Output export section
-exports: std.ArrayListUnmanaged(Export) = .empty,
-/// List of initialization functions. These must be called in order of priority
-/// by the (synthetic) __wasm_call_ctors function.
-init_funcs: std.ArrayListUnmanaged(InitFuncLoc) = .empty,
-/// Index to a function defining the entry of the wasm file
-entry: ?u32 = null,
-
-/// Indirect function table, used to call function pointers
-/// When this is non-zero, we must emit a table entry,
-/// as well as an 'elements' section.
-///
-/// Note: Key is symbol location, value represents the index into the table
-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.
-/// None of this data is serialized to disk because it is trivially reloaded
-/// from unchanged archive files on the next start of the compiler process,
-/// or if those files have changed, the prelink phase needs to be restarted.
-lazy_archives: std.ArrayListUnmanaged(LazyArchive) = .empty,
-
-/// A map of global names to their symbol location
-globals: std.AutoArrayHashMapUnmanaged(String, SymbolLoc) = .empty,
-/// The list of GOT symbols and their location
-got_symbols: std.ArrayListUnmanaged(SymbolLoc) = .empty,
-/// Maps discarded symbols and their positions to the location of the symbol
-/// it was resolved to
-discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .empty,
-/// List of all symbol locations which have been resolved by the linker and will be emit
-/// into the final binary.
-resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .empty,
-/// Symbols that remain undefined after symbol resolution.
-undefs: std.AutoArrayHashMapUnmanaged(String, SymbolLoc) = .empty,
-/// Maps a symbol's location to an atom. This can be used to find meta
-/// data of a symbol, such as its size, or its offset to perform a relocation.
-/// 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`.
 /// String data is allocated into Compilation arena.
 dump_argv_list: std.ArrayListUnmanaged([]const u8),
 
-/// Represents the index into `segments` where the 'code' section lives.
-code_section_index: Segment.OptionalIndex = .none,
-custom_sections: CustomSections,
 preloaded_strings: PreloadedStrings,
 
+navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, Nav) = .empty,
+nav_exports: std.AutoArrayHashMapUnmanaged(NavExport, Zcu.Export.Index) = .empty,
+uav_exports: std.AutoArrayHashMapUnmanaged(UavExport, Zcu.Export.Index) = .empty,
+imports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty,
+
+dwarf: ?Dwarf = null,
+debug_sections: DebugSections = .{},
+
+flush_buffer: Flush = .{},
+
+missing_exports_init: []String = &.{},
+entry_resolution: FunctionImport.Resolution = .unresolved,
+
+/// Empty when outputting an object.
+function_exports: std.ArrayListUnmanaged(FunctionIndex) = .empty,
+/// Tracks the value at the end of prelink.
+function_exports_len: u32 = 0,
+global_exports: std.ArrayListUnmanaged(GlobalIndex) = .empty,
+/// Tracks the value at the end of prelink.
+global_exports_len: u32 = 0,
+
+/// Ordered list of non-import functions that will appear in the final binary.
+/// Empty until prelink.
+functions: std.AutoArrayHashMapUnmanaged(FunctionImport.Resolution, void) = .empty,
+/// Tracks the value at the end of prelink, at which point `functions`
+/// contains only object file functions, and nothing from the Zcu yet.
+functions_len: u32 = 0,
+/// Immutable after prelink. The undefined functions coming only from all object files.
+/// The Zcu must satisfy these.
+function_imports_init: []FunctionImportId = &.{},
+/// Initialized as copy of `function_imports_init`; entries are deleted as
+/// they are satisfied by the Zcu.
+function_imports: std.AutoArrayHashMapUnmanaged(FunctionImportId, void) = .empty,
+
+/// Ordered list of non-import globals that will appear in the final binary.
+/// Empty until prelink.
+globals: std.AutoArrayHashMapUnmanaged(GlobalImport.Resolution, void) = .empty,
+/// Tracks the value at the end of prelink, at which point `globals`
+/// contains only object file globals, and nothing from the Zcu yet.
+globals_len: u32 = 0,
+global_imports_init: []GlobalImportId = &.{},
+global_imports: std.AutoArrayHashMapUnmanaged(GlobalImportId, void) = .empty,
+
+/// Ordered list of non-import tables that will appear in the final binary.
+/// Empty until prelink.
+tables: std.AutoArrayHashMapUnmanaged(TableImport.Resolution, void) = .empty,
+table_imports: std.AutoArrayHashMapUnmanaged(ObjectTableImportIndex, void) = .empty,
+
+any_exports_updated: bool = true,
+
+/// Index into `functions`.
+pub const FunctionIndex = enum(u32) {
+    _,
+
+    pub fn fromNav(nav_index: InternPool.Nav.Index, wasm: *const Wasm) FunctionIndex {
+        return @enumFromInt(wasm.functions.getIndex(.pack(wasm, .{ .nav = nav_index })).?);
+    }
+};
+
+/// 0. Index into `function_imports`
+/// 1. Index into `functions`.
+pub const OutputFunctionIndex = enum(u32) {
+    _,
+};
+
+/// Index into `globals`.
+const GlobalIndex = enum(u32) {
+    _,
+
+    fn key(index: GlobalIndex, f: *const Flush) *Wasm.GlobalImport.Resolution {
+        return &f.globals.items[@intFromEnum(index)];
+    }
+};
+
+/// The first N indexes correspond to input objects (`objects`) array.
+/// After that, the indexes correspond to the `source_locations` array,
+/// representing a location in a Zig source file that can be pinpointed
+/// precisely via AST node and token.
+pub const SourceLocation = enum(u32) {
+    /// From the Zig compilation unit but no precise source location.
+    zig_object_nofile = std.math.maxInt(u32) - 1,
+    none = std.math.maxInt(u32),
+    _,
+};
+
+/// The lower bits of this ABI-match the flags here:
+/// https://github.com/WebAssembly/tool-conventions/blob/df8d737539eb8a8f446ba5eab9dc670c40dfb81e/Linking.md#symbol-table-subsection
+/// The upper bits are used for nefarious purposes.
+pub const SymbolFlags = packed struct(u32) {
+    binding: Binding = .strong,
+    /// Indicating that this is a hidden symbol. Hidden symbols are not to be
+    /// exported when performing the final link, but may be linked to other
+    /// modules.
+    visibility_hidden: bool = false,
+    padding0: u1 = 0,
+    /// For non-data symbols, this must match whether the symbol is an import
+    /// or is defined; for data symbols, determines whether a segment is
+    /// specified.
+    undefined: bool = false,
+    /// The symbol is intended to be exported from the wasm module to the host
+    /// environment. This differs from the visibility flags in that it affects
+    /// static linking.
+    exported: bool = false,
+    /// The symbol uses an explicit symbol name, rather than reusing the name
+    /// from a wasm import. This allows it to remap imports from foreign
+    /// WebAssembly modules into local symbols with different names.
+    explicit_name: bool = false,
+    /// The symbol is intended to be included in the linker output, regardless
+    /// of whether it is used by the program. Same meaning as `retain`.
+    no_strip: bool = false,
+    /// The symbol resides in thread local storage.
+    tls: bool = false,
+    /// The symbol represents an absolute address. This means its offset is
+    /// relative to the start of the wasm memory as opposed to being relative
+    /// to a data segment.
+    absolute: bool = false,
+
+    // Above here matches the tooling conventions ABI.
+
+    padding1: u8 = 0,
+    /// Zig-specific. Dead things are allowed to be garbage collected.
+    alive: bool = false,
+    /// Zig-specific. Segments only. Signals that the segment contains only
+    /// null terminated strings allowing the linker to perform merging.
+    strings: bool = false,
+    /// Zig-specific. This symbol comes from an object that must be included in
+    /// the final link.
+    must_link: bool = false,
+    /// Zig-specific. Data segments only.
+    is_passive: bool = false,
+    /// Zig-specific. Data segments only.
+    alignment: Alignment = .none,
+    /// Zig-specific. Globals only.
+    global_type: Global.Type = .zero,
+
+    pub const Binding = enum(u2) {
+        strong = 0,
+        /// Indicating that this is a weak symbol. When linking multiple modules
+        /// defining the same symbol, all weak definitions are discarded if any
+        /// strong definitions exist; then if multiple weak definitions exist all
+        /// but one (unspecified) are discarded; and finally it is an error if more
+        /// than one definition remains.
+        weak = 1,
+        /// Indicating that this is a local symbol. Local symbols are not to be
+        /// exported, or linked to other modules/sections. The names of all
+        /// non-local symbols must be unique, but the names of local symbols
+        /// are not considered for uniqueness. A local function or global
+        /// symbol cannot reference an import.
+        local = 2,
+    };
+
+    pub fn initZigSpecific(flags: *SymbolFlags, must_link: bool, no_strip: bool) void {
+        flags.alive = false;
+        flags.strings = false;
+        flags.must_link = must_link;
+        flags.no_strip = no_strip;
+        flags.alignment = .none;
+        flags.global_type = .zero;
+        flags.is_passive = false;
+    }
+
+    pub fn isIncluded(flags: SymbolFlags, is_dynamic: bool) bool {
+        return flags.exported or
+            (is_dynamic and !flags.visibility_hidden) or
+            (flags.no_strip and flags.must_link);
+    }
+
+    pub fn isExported(flags: SymbolFlags, is_dynamic: bool) bool {
+        if (flags.undefined or flags.binding == .local) return false;
+        if (is_dynamic and !flags.visibility_hidden) return true;
+        return flags.exported;
+    }
+
+    pub fn requiresImport(flags: SymbolFlags, is_data: bool) bool {
+        if (is_data) return false;
+        if (!flags.undefined) return false;
+        if (flags.binding == .weak) return false;
+        return true;
+    }
+
+    /// Returns the name as how it will be output into the final object
+    /// file or binary. When `merge` is true, this will return the
+    /// short name. i.e. ".rodata". When false, it returns the entire name instead.
+    pub fn outputName(flags: SymbolFlags, name: []const u8, merge: bool) []const u8 {
+        if (flags.tls) return ".tdata";
+        if (!merge) return name;
+        if (mem.startsWith(u8, name, ".rodata.")) return ".rodata";
+        if (mem.startsWith(u8, name, ".text.")) return ".text";
+        if (mem.startsWith(u8, name, ".data.")) return ".data";
+        if (mem.startsWith(u8, name, ".bss.")) return ".bss";
+        return name;
+    }
+
+    /// Masks off the Zig-specific stuff.
+    pub fn toAbiInteger(flags: SymbolFlags) u32 {
+        var copy = flags;
+        copy.initZigSpecific(false, false);
+        return @bitCast(copy);
+    }
+};
+
+pub const Nav = extern struct {
+    code: DataSegment.Payload,
+    relocs: Relocation.Slice,
+
+    pub const Code = DataSegment.Payload;
+
+    /// Index into `navs`.
+    /// Note that swapRemove is sometimes performed on `navs`.
+    pub const Index = enum(u32) {
+        _,
+
+        pub fn key(i: @This(), wasm: *const Wasm) *InternPool.Nav.Index {
+            return &wasm.navs.keys()[@intFromEnum(i)];
+        }
+
+        pub fn value(i: @This(), wasm: *const Wasm) *Nav {
+            return &wasm.navs.values()[@intFromEnum(i)];
+        }
+    };
+};
+
+pub const NavExport = extern struct {
+    name: String,
+    nav_index: InternPool.Nav.Index,
+};
+
+pub const UavExport = extern struct {
+    name: String,
+    uav_index: InternPool.Index,
+};
+
+const DebugSections = struct {
+    abbrev: DebugSection = .{},
+    info: DebugSection = .{},
+    line: DebugSection = .{},
+    loc: DebugSection = .{},
+    pubnames: DebugSection = .{},
+    pubtypes: DebugSection = .{},
+    ranges: DebugSection = .{},
+    str: DebugSection = .{},
+};
+
+const DebugSection = struct {};
+
+pub const FunctionImport = extern struct {
+    flags: SymbolFlags,
+    module_name: String,
+    source_location: SourceLocation,
+    resolution: Resolution,
+    type: FunctionType.Index,
+
+    /// Represents a synthetic function, a function from an object, or a
+    /// function from the Zcu.
+    pub const Resolution = enum(u32) {
+        unresolved,
+        __wasm_apply_global_tls_relocs,
+        __wasm_call_ctors,
+        __wasm_init_memory,
+        __wasm_init_tls,
+        __zig_error_names,
+        // Next, index into `object_functions`.
+        // Next, index into `navs`.
+        _,
+
+        const first_object_function = @intFromEnum(Resolution.__zig_error_names) + 1;
+
+        pub const Unpacked = union(enum) {
+            unresolved,
+            __wasm_apply_global_tls_relocs,
+            __wasm_call_ctors,
+            __wasm_init_memory,
+            __wasm_init_tls,
+            __zig_error_names,
+            object_function: ObjectFunctionIndex,
+            nav: Nav.Index,
+        };
+
+        pub fn unpack(r: Resolution, wasm: *const Wasm) Unpacked {
+            return switch (r) {
+                .unresolved => .unresolved,
+                .__wasm_apply_global_tls_relocs => .__wasm_apply_global_tls_relocs,
+                .__wasm_call_ctors => .__wasm_call_ctors,
+                .__wasm_init_memory => .__wasm_init_memory,
+                .__wasm_init_tls => .__wasm_init_tls,
+                .__zig_error_names => .__zig_error_names,
+                _ => {
+                    const i: u32 = @intFromEnum(r);
+                    const object_function_index = i - first_object_function;
+                    if (object_function_index < wasm.object_functions.items.len)
+                        return .{ .object_function = @enumFromInt(object_function_index) };
+                    const nav_index = object_function_index - wasm.object_functions.items.len;
+                    return .{ .nav = @enumFromInt(nav_index) };
+                },
+            };
+        }
+
+        pub fn pack(wasm: *const Wasm, unpacked: Unpacked) Resolution {
+            return switch (unpacked) {
+                .unresolved => .unresolved,
+                .__wasm_apply_global_tls_relocs => .__wasm_apply_global_tls_relocs,
+                .__wasm_call_ctors => .__wasm_call_ctors,
+                .__wasm_init_memory => .__wasm_init_memory,
+                .__wasm_init_tls => .__wasm_init_tls,
+                .__zig_error_names => .__zig_error_names,
+                .object_function => |i| @enumFromInt(first_object_function + @intFromEnum(i)),
+                .nav => |i| @enumFromInt(first_object_function + wasm.object_functions.items.len + @intFromEnum(i)),
+            };
+        }
+
+        pub fn isNavOrUnresolved(r: Resolution, wasm: *const Wasm) bool {
+            return switch (r.unpack(wasm)) {
+                .unresolved, .nav => true,
+                else => false,
+            };
+        }
+    };
+
+    /// Index into `object_function_imports`.
+    pub const Index = enum(u32) {
+        _,
+    };
+};
+
+pub const Function = extern struct {
+    flags: SymbolFlags,
+    /// `none` if this function has no symbol describing it.
+    name: OptionalString,
+    type_index: FunctionType.Index,
+    code: Code,
+    /// The offset within the section where the data starts.
+    offset: u32,
+    section_index: ObjectSectionIndex,
+    source_location: SourceLocation,
+
+    pub const Code = DataSegment.Payload;
+};
+
+pub const GlobalImport = extern struct {
+    flags: SymbolFlags,
+    module_name: String,
+    source_location: SourceLocation,
+    resolution: Resolution,
+
+    /// Represents a synthetic global, or a global from an object.
+    pub const Resolution = enum(u32) {
+        unresolved,
+        __heap_base,
+        __heap_end,
+        __stack_pointer,
+        __tls_align,
+        __tls_base,
+        __tls_size,
+        __zig_error_name_table,
+        // Next, index into `object_globals`.
+        // Next, index into `navs`.
+        _,
+    };
+};
+
+pub const Global = extern struct {
+    /// `none` if this function has no symbol describing it.
+    name: OptionalString,
+    flags: SymbolFlags,
+    expr: Expr,
+
+    pub const Type = packed struct(u4) {
+        valtype: Valtype,
+        mutable: bool,
+
+        pub const zero: Type = @bitCast(@as(u4, 0));
+    };
+
+    pub const Valtype = enum(u3) {
+        i32,
+        i64,
+        f32,
+        f64,
+        v128,
+
+        pub fn from(v: std.wasm.Valtype) Valtype {
+            return switch (v) {
+                .i32 => .i32,
+                .i64 => .i64,
+                .f32 => .f32,
+                .f64 => .f64,
+                .v128 => .v128,
+            };
+        }
+
+        pub fn to(v: Valtype) std.wasm.Valtype {
+            return switch (v) {
+                .i32 => .i32,
+                .i64 => .i64,
+                .f32 => .f32,
+                .f64 => .f64,
+                .v128 => .v128,
+            };
+        }
+    };
+};
+
+pub const TableImport = extern struct {
+    flags: SymbolFlags,
+    module_name: String,
+    source_location: SourceLocation,
+    resolution: Resolution,
+
+    /// Represents a synthetic table, or a table from an object.
+    pub const Resolution = enum(u32) {
+        unresolved,
+        __indirect_function_table,
+        // Next, index into `object_tables`.
+        _,
+    };
+};
+
+pub const Table = extern struct {
+    module_name: String,
+    name: String,
+    flags: SymbolFlags,
+    limits_min: u32,
+    limits_max: u32,
+    limits_has_max: bool,
+    limits_is_shared: bool,
+    reftype: std.wasm.RefType,
+    padding: [1]u8 = .{0},
+};
+
+/// Uniquely identifies a section across all objects. Each Object has a section_start field.
+/// By subtracting that value from this one, the Object section index is obtained.
+pub const ObjectSectionIndex = enum(u32) {
+    _,
+};
+
+/// Index into `object_function_imports`.
+pub const ObjectFunctionImportIndex = enum(u32) {
+    _,
+
+    pub fn ptr(index: ObjectFunctionImportIndex, wasm: *const Wasm) *FunctionImport {
+        return &wasm.object_function_imports.items[@intFromEnum(index)];
+    }
+};
+
+/// Index into `object_global_imports`.
+pub const ObjectGlobalImportIndex = enum(u32) {
+    _,
+};
+
+/// Index into `object_table_imports`.
+pub const ObjectTableImportIndex = enum(u32) {
+    _,
+};
+
+/// Index into `object_tables`.
+pub const ObjectTableIndex = enum(u32) {
+    _,
+
+    pub fn ptr(index: ObjectTableIndex, wasm: *const Wasm) *Table {
+        return &wasm.object_tables.items[@intFromEnum(index)];
+    }
+};
+
+/// Index into `global_imports`.
+pub const GlobalImportIndex = enum(u32) {
+    _,
+};
+
+/// Index into `object_globals`.
+pub const ObjectGlobalIndex = enum(u32) {
+    _,
+};
+
+/// Index into `object_functions`.
+pub const ObjectFunctionIndex = enum(u32) {
+    _,
+
+    pub fn ptr(index: ObjectFunctionIndex, wasm: *const Wasm) *Function {
+        return &wasm.object_functions.items[@intFromEnum(index)];
+    }
+
+    pub fn toOptional(i: ObjectFunctionIndex) OptionalObjectFunctionIndex {
+        const result: OptionalObjectFunctionIndex = @enumFromInt(@intFromEnum(i));
+        assert(result != .none);
+        return result;
+    }
+};
+
+/// Index into `object_functions`, or null.
+pub const OptionalObjectFunctionIndex = enum(u32) {
+    none = std.math.maxInt(u32),
+    _,
+
+    pub fn unwrap(i: OptionalObjectFunctionIndex) ?ObjectFunctionIndex {
+        if (i == .none) return null;
+        return @enumFromInt(@intFromEnum(i));
+    }
+};
+
+pub const DataSegment = extern struct {
+    /// `none` if no symbol describes it.
+    name: OptionalString,
+    flags: SymbolFlags,
+    payload: Payload,
+    /// From the data segment start to the first byte of payload.
+    segment_offset: u32,
+    section_index: ObjectSectionIndex,
+
+    pub const Payload = extern struct {
+        /// Points into string_bytes. No corresponding string_table entry.
+        off: u32,
+        /// The size in bytes of the data representing the segment within the section.
+        len: u32,
+
+        fn slice(p: DataSegment.Payload, wasm: *const Wasm) []const u8 {
+            return wasm.string_bytes.items[p.off..][0..p.len];
+        }
+    };
+
+    /// Index into `object_data_segments`.
+    pub const Index = enum(u32) {
+        _,
+    };
+};
+
+pub const CustomSegment = extern struct {
+    payload: Payload,
+    flags: SymbolFlags,
+    section_name: String,
+
+    pub const Payload = DataSegment.Payload;
+};
+
+/// An index into string_bytes where a wasm expression is found.
+pub const Expr = enum(u32) {
+    _,
+};
+
+pub const FunctionType = extern struct {
+    params: ValtypeList,
+    returns: ValtypeList,
+
+    /// Index into func_types
+    pub const Index = enum(u32) {
+        _,
+
+        pub fn ptr(i: FunctionType.Index, wasm: *const Wasm) *FunctionType {
+            return &wasm.func_types.keys()[@intFromEnum(i)];
+        }
+    };
+
+    pub const format = @compileError("can't format without *Wasm reference");
+
+    pub fn eql(a: FunctionType, b: FunctionType) bool {
+        return a.params == b.params and a.returns == b.returns;
+    }
+};
+
+/// Represents a function entry, holding the index to its type
+pub const Func = extern struct {
+    type_index: FunctionType.Index,
+};
+
 /// Type reflection is used on the field names to autopopulate each field
 /// during initialization.
 const PreloadedStrings = struct {
@@ -190,31 +714,14 @@ const PreloadedStrings = struct {
     __wasm_init_memory: String,
     __wasm_init_memory_flag: String,
     __wasm_init_tls: String,
-    __zig_err_name_table: String,
-    __zig_err_names: String,
+    __zig_error_name_table: String,
+    __zig_error_names: String,
     __zig_errors_len: String,
     _initialize: String,
     _start: String,
     memory: String,
 };
 
-/// Type reflection is used on the field names to autopopulate each inner `name` field.
-const CustomSections = struct {
-    @".debug_info": CustomSection,
-    @".debug_pubtypes": CustomSection,
-    @".debug_abbrev": CustomSection,
-    @".debug_line": CustomSection,
-    @".debug_str": CustomSection,
-    @".debug_pubnames": CustomSection,
-    @".debug_loc": CustomSection,
-    @".debug_ranges": CustomSection,
-};
-
-const CustomSection = struct {
-    name: String,
-    index: Segment.OptionalIndex,
-};
-
 /// Index into string_bytes
 pub const String = enum(u32) {
     _,
@@ -246,6 +753,11 @@ pub const String = enum(u32) {
         }
     };
 
+    pub fn slice(index: String, wasm: *const Wasm) [:0]const u8 {
+        const start_slice = wasm.string_bytes.items[@intFromEnum(index)..];
+        return start_slice[0..mem.indexOfScalar(u8, start_slice, 0).? :0];
+    }
+
     pub fn toOptional(i: String) OptionalString {
         const result: OptionalString = @enumFromInt(@intFromEnum(i));
         assert(result != .none);
@@ -261,160 +773,242 @@ pub const OptionalString = enum(u32) {
         if (i == .none) return null;
         return @enumFromInt(@intFromEnum(i));
     }
-};
-
-/// Index into objects array or the zig object.
-pub const ObjectId = enum(u16) {
-    zig_object = std.math.maxInt(u16) - 1,
-    _,
 
-    pub fn toOptional(i: ObjectId) OptionalObjectId {
-        const result: OptionalObjectId = @enumFromInt(@intFromEnum(i));
-        assert(result != .none);
-        return result;
+    pub fn slice(index: OptionalString, wasm: *const Wasm) ?[:0]const u8 {
+        return (index.unwrap() orelse return null).slice(wasm);
     }
 };
 
-/// Optional index into objects array or the zig object.
-pub const OptionalObjectId = enum(u16) {
-    zig_object = std.math.maxInt(u16) - 1,
-    none = std.math.maxInt(u16),
+/// Stored identically to `String`. The bytes are reinterpreted as
+/// `std.wasm.Valtype` elements.
+pub const ValtypeList = enum(u32) {
     _,
 
-    pub fn unwrap(i: OptionalObjectId) ?ObjectId {
-        if (i == .none) return null;
-        return @enumFromInt(@intFromEnum(i));
+    pub fn fromString(s: String) ValtypeList {
+        return @enumFromInt(@intFromEnum(s));
     }
-};
 
-/// None of this data is serialized since it can be re-loaded from disk, or if
-/// it has been changed, the data must be discarded.
-const LazyArchive = struct {
-    path: Path,
-    file_contents: []const u8,
-    archive: Archive,
-
-    fn deinit(la: *LazyArchive, gpa: Allocator) void {
-        la.archive.deinit(gpa);
-        gpa.free(la.path.sub_path);
-        gpa.free(la.file_contents);
-        la.* = undefined;
+    pub fn slice(index: ValtypeList, wasm: *const Wasm) []const std.wasm.Valtype {
+        return @bitCast(String.slice(@enumFromInt(@intFromEnum(index)), wasm));
     }
 };
 
-pub const Segment = struct {
-    alignment: Alignment,
-    size: u32,
-    offset: u32,
-    flags: u32,
+/// 0. Index into `object_function_imports`.
+/// 1. Index into `imports`.
+pub const FunctionImportId = enum(u32) {
+    _,
+};
 
-    const Index = enum(u32) {
-        _,
+/// 0. Index into `object_global_imports`.
+/// 1. Index into `imports`.
+pub const GlobalImportId = enum(u32) {
+    _,
+};
 
-        pub fn toOptional(i: Index) OptionalIndex {
-            const result: OptionalIndex = @enumFromInt(@intFromEnum(i));
-            assert(result != .none);
-            return result;
-        }
+pub const Relocation = struct {
+    tag: Tag,
+    /// Offset of the value to rewrite relative to the relevant section's contents.
+    /// When `offset` is zero, its position is immediately after the id and size of the section.
+    offset: u32,
+    pointee: Pointee,
+    /// Populated only for `MEMORY_ADDR_*`, `FUNCTION_OFFSET_I32` and `SECTION_OFFSET_I32`.
+    addend: i32,
+
+    pub const Pointee = union {
+        symbol_name: String,
+        type_index: FunctionType.Index,
+        section: ObjectSectionIndex,
+        nav_index: InternPool.Nav.Index,
+        uav_index: InternPool.Index,
     };
 
-    const OptionalIndex = enum(u32) {
-        none = std.math.maxInt(u32),
-        _,
+    pub const Slice = extern struct {
+        /// Index into `relocations`.
+        off: u32,
+        len: u32,
 
-        pub fn unwrap(i: OptionalIndex) ?Index {
-            if (i == .none) return null;
-            return @enumFromInt(@intFromEnum(i));
+        pub fn slice(s: Slice, wasm: *const Wasm) []Relocation {
+            return wasm.relocations.items[s.off..][0..s.len];
         }
     };
 
-    pub const Flag = enum(u32) {
-        WASM_DATA_SEGMENT_IS_PASSIVE = 0x01,
-        WASM_DATA_SEGMENT_HAS_MEMINDEX = 0x02,
+    pub const Tag = enum(u8) {
+        /// Uses `symbol_name`.
+        FUNCTION_INDEX_LEB = 0,
+        /// Uses `table_index`.
+        TABLE_INDEX_SLEB = 1,
+        /// Uses `table_index`.
+        TABLE_INDEX_I32 = 2,
+        MEMORY_ADDR_LEB = 3,
+        MEMORY_ADDR_SLEB = 4,
+        MEMORY_ADDR_I32 = 5,
+        /// Uses `type_index`.
+        TYPE_INDEX_LEB = 6,
+        /// Uses `symbol_name`.
+        GLOBAL_INDEX_LEB = 7,
+        FUNCTION_OFFSET_I32 = 8,
+        SECTION_OFFSET_I32 = 9,
+        TAG_INDEX_LEB = 10,
+        MEMORY_ADDR_REL_SLEB = 11,
+        TABLE_INDEX_REL_SLEB = 12,
+        /// Uses `symbol_name`.
+        GLOBAL_INDEX_I32 = 13,
+        MEMORY_ADDR_LEB64 = 14,
+        MEMORY_ADDR_SLEB64 = 15,
+        MEMORY_ADDR_I64 = 16,
+        MEMORY_ADDR_REL_SLEB64 = 17,
+        /// Uses `table_index`.
+        TABLE_INDEX_SLEB64 = 18,
+        /// Uses `table_index`.
+        TABLE_INDEX_I64 = 19,
+        TABLE_NUMBER_LEB = 20,
+        MEMORY_ADDR_TLS_SLEB = 21,
+        FUNCTION_OFFSET_I64 = 22,
+        MEMORY_ADDR_LOCREL_I32 = 23,
+        TABLE_INDEX_REL_SLEB64 = 24,
+        MEMORY_ADDR_TLS_SLEB64 = 25,
+        /// Uses `symbol_name`.
+        FUNCTION_INDEX_I32 = 26,
+
+        // Above here, the tags correspond to symbol table ABI described in
+        // https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
+        // Below, the tags are compiler-internal.
+
+        /// Uses `nav_index`. 4 or 8 bytes depending on wasm32 or wasm64.
+        nav_index,
+        /// Uses `uav_index`. 4 or 8 bytes depending on wasm32 or wasm64.
+        uav_index,
     };
-
-    pub fn isPassive(segment: Segment) bool {
-        return segment.flags & @intFromEnum(Flag.WASM_DATA_SEGMENT_IS_PASSIVE) != 0;
-    }
-
-    /// For a given segment, determines if it needs passive initialization
-    fn needsPassiveInitialization(segment: Segment, import_mem: bool, name: []const u8) bool {
-        if (import_mem and !std.mem.eql(u8, name, ".bss")) {
-            return true;
-        }
-        return segment.isPassive();
-    }
 };
 
-pub const SymbolLoc = struct {
-    /// The index of the symbol within the specified file
-    index: Symbol.Index,
-    /// The index of the object file where the symbol resides.
-    file: OptionalObjectId,
+pub const MemoryImport = extern struct {
+    module_name: String,
+    name: String,
+    limits_min: u32,
+    limits_max: u32,
+    limits_has_max: bool,
+    limits_is_shared: bool,
+    padding: [2]u8 = .{ 0, 0 },
 };
 
-/// From a given location, returns the corresponding symbol in the wasm binary
-pub fn symbolLocSymbol(wasm: *const Wasm, loc: SymbolLoc) *Symbol {
-    if (wasm.discarded.get(loc)) |new_loc| {
-        return symbolLocSymbol(wasm, new_loc);
-    }
-    return switch (loc.file) {
-        .none => &wasm.synthetic_symbols.items[@intFromEnum(loc.index)],
-        .zig_object => wasm.zig_object.?.symbol(loc.index),
-        _ => &wasm.objects.items[@intFromEnum(loc.file)].symtable[@intFromEnum(loc.index)],
-    };
-}
+pub const Alignment = InternPool.Alignment;
 
-/// From a given location, returns the name of the symbol.
-pub fn symbolLocName(wasm: *const Wasm, loc: SymbolLoc) [:0]const u8 {
-    return wasm.stringSlice(wasm.symbolLocSymbol(loc).name);
-}
+pub const InitFunc = extern struct {
+    priority: u32,
+    function_index: ObjectFunctionIndex,
 
-/// From a given symbol location, returns the final location.
-/// e.g. when a symbol was resolved and replaced by the symbol
-/// in a different file, this will return said location.
-/// If the symbol wasn't replaced by another, this will return
-/// the given location itwasm.
-pub fn symbolLocFinalLoc(wasm: *const Wasm, loc: SymbolLoc) SymbolLoc {
-    if (wasm.discarded.get(loc)) |new_loc| {
-        return symbolLocFinalLoc(wasm, new_loc);
+    fn lessThan(ctx: void, lhs: InitFunc, rhs: InitFunc) bool {
+        _ = ctx;
+        if (lhs.priority == rhs.priority) {
+            return @intFromEnum(lhs.function_index) < @intFromEnum(rhs.function_index);
+        } else {
+            return lhs.priority < rhs.priority;
+        }
     }
-    return loc;
-}
+};
 
-// Contains the location of the function symbol, as well as
-/// the priority itself of the initialization function.
-pub const InitFuncLoc = struct {
-    /// object file index in the list of objects.
-    /// Unlike `SymbolLoc` this cannot be `null` as we never define
-    /// our own ctors.
-    file: ObjectId,
-    /// Symbol index within the corresponding object file.
-    index: Symbol.Index,
-    /// The priority in which the constructor must be called.
-    priority: u32,
+pub const Comdat = struct {
+    name: String,
+    /// Must be zero, no flags are currently defined by the tool-convention.
+    flags: u32,
+    symbols: Comdat.Symbol.Slice,
 
-    /// From a given `InitFuncLoc` returns the corresponding function symbol
-    fn getSymbol(loc: InitFuncLoc, wasm: *const Wasm) *Symbol {
-        return wasm.symbolLocSymbol(getSymbolLoc(loc));
-    }
+    pub const Symbol = struct {
+        kind: Comdat.Symbol.Type,
+        /// Index of the data segment/function/global/event/table within a WASM module.
+        /// The object must not be an import.
+        index: u32,
 
-    /// Turns the given `InitFuncLoc` into a `SymbolLoc`
-    fn getSymbolLoc(loc: InitFuncLoc) SymbolLoc {
-        return .{
-            .file = loc.file.toOptional(),
-            .index = loc.index,
+        pub const Slice = struct {
+            /// Index into Wasm object_comdat_symbols
+            off: u32,
+            len: u32,
         };
-    }
 
-    /// Returns true when `lhs` has a higher priority (e.i. value closer to 0) than `rhs`.
-    fn lessThan(ctx: void, lhs: InitFuncLoc, rhs: InitFuncLoc) bool {
-        _ = ctx;
-        return lhs.priority < rhs.priority;
-    }
+        pub const Type = enum(u8) {
+            data = 0,
+            function = 1,
+            global = 2,
+            event = 3,
+            table = 4,
+            section = 5,
+        };
+    };
 };
 
-pub fn open(
+/// Stored as a u8 so it can reuse the string table mechanism.
+pub const Feature = packed struct(u8) {
+    prefix: Prefix,
+    /// Type of the feature, must be unique in the sequence of features.
+    tag: Tag,
+
+    /// Stored identically to `String`. The bytes are reinterpreted as `Feature`
+    /// elements. Elements must be sorted before string-interning.
+    pub const Set = enum(u32) {
+        _,
+
+        pub fn fromString(s: String) Set {
+            return @enumFromInt(@intFromEnum(s));
+        }
+    };
+
+    /// Unlike `std.Target.wasm.Feature` this also contains linker-features such as shared-mem.
+    /// Additionally the name uses convention matching the wasm binary format.
+    pub const Tag = enum(u6) {
+        atomics,
+        @"bulk-memory",
+        @"exception-handling",
+        @"extended-const",
+        @"half-precision",
+        multimemory,
+        multivalue,
+        @"mutable-globals",
+        @"nontrapping-fptoint",
+        @"reference-types",
+        @"relaxed-simd",
+        @"sign-ext",
+        simd128,
+        @"tail-call",
+        @"shared-mem",
+
+        pub fn fromCpuFeature(feature: std.Target.wasm.Feature) Tag {
+            return @enumFromInt(@intFromEnum(feature));
+        }
+
+        pub const format = @compileError("use @tagName instead");
+    };
+
+    /// Provides information about the usage of the feature.
+    pub const Prefix = enum(u2) {
+        /// Reserved so that a 0-byte Feature is invalid and therefore can be a sentinel.
+        invalid,
+        /// '0x2b': Object uses this feature, and the link fails if feature is
+        /// not in the allowed set.
+        @"+",
+        /// '0x2d': Object does not use this feature, and the link fails if
+        /// this feature is in the allowed set.
+        @"-",
+        /// '0x3d': Object uses this feature, and the link fails if this
+        /// feature is not in the allowed set, or if any object does not use
+        /// this feature.
+        @"=",
+    };
+
+    pub fn format(feature: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
+        _ = opt;
+        _ = fmt;
+        try writer.print("{s} {s}", .{ @tagName(feature.prefix), @tagName(feature.tag) });
+    }
+
+    pub fn lessThan(_: void, a: Feature, b: Feature) bool {
+        assert(a != b);
+        const a_int: u8 = @bitCast(a);
+        const b_int: u8 = @bitCast(b);
+        return a_int < b_int;
+    }
+};
+
+pub fn open(
     arena: Allocator,
     comp: *Compilation,
     emit: Path,
@@ -431,14 +1025,12 @@ pub fn createEmpty(
     emit: Path,
     options: link.File.OpenOptions,
 ) !*Wasm {
-    const gpa = comp.gpa;
     const target = comp.root_mod.resolved_target.result;
     assert(target.ofmt == .wasm);
 
     const use_lld = build_options.have_llvm and comp.config.use_lld;
     const use_llvm = comp.config.use_llvm;
     const output_mode = comp.config.output_mode;
-    const shared_memory = comp.config.shared_memory;
     const wasi_exec_model = comp.config.wasi_exec_model;
 
     // If using LLD to link, this code should produce an object file so that it
@@ -458,6 +1050,11 @@ pub fn createEmpty(
             .comp = comp,
             .emit = emit,
             .zcu_object_sub_path = zcu_object_sub_path,
+            // Garbage collection is so crucial to WebAssembly that we design
+            // the linker around the assumption that it will be on in the vast
+            // majority of cases, and therefore express "no garbage collection"
+            // in terms of setting the no_strip and must_link flags on all
+            // symbols.
             .gc_sections = options.gc_sections orelse (output_mode != .Obj),
             .print_gc_sections = options.print_gc_sections,
             .stack_size = options.stack_size orelse switch (target.os.tag) {
@@ -481,10 +1078,8 @@ pub fn createEmpty(
         .max_memory = options.max_memory,
 
         .entry_name = undefined,
-        .zig_object = null,
         .dump_argv_list = .empty,
         .host_name = undefined,
-        .custom_sections = undefined,
         .preloaded_strings = undefined,
     };
     if (use_llvm and comp.config.have_zcu) {
@@ -494,13 +1089,6 @@ pub fn createEmpty(
 
     wasm.host_name = try wasm.internString("env");
 
-    inline for (@typeInfo(CustomSections).@"struct".fields) |field| {
-        @field(wasm.custom_sections, field.name) = .{
-            .index = .none,
-            .name = try wasm.internString(field.name),
-        };
-    }
-
     inline for (@typeInfo(PreloadedStrings).@"struct".fields) |field| {
         @field(wasm.preloaded_strings, field.name) = try wasm.internString(field.name);
     }
@@ -535,181 +1123,9 @@ pub fn createEmpty(
     });
     wasm.name = sub_path;
 
-    // create stack pointer symbol
-    {
-        const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__stack_pointer, .global);
-        const symbol = wasm.symbolLocSymbol(loc);
-        // For object files we will import the stack pointer symbol
-        if (output_mode == .Obj) {
-            symbol.setUndefined(true);
-            symbol.index = @intCast(wasm.imported_globals_count);
-            wasm.imported_globals_count += 1;
-            try wasm.imports.putNoClobber(gpa, loc, .{
-                .module_name = wasm.host_name,
-                .name = symbol.name,
-                .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
-            });
-        } else {
-            symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len);
-            symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-            const global = try wasm.wasm_globals.addOne(gpa);
-            global.* = .{
-                .global_type = .{
-                    .valtype = .i32,
-                    .mutable = true,
-                },
-                .init = .{ .i32_const = 0 },
-            };
-        }
-    }
-
-    // create indirect function pointer symbol
-    {
-        const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__indirect_function_table, .table);
-        const symbol = wasm.symbolLocSymbol(loc);
-        const table: std.wasm.Table = .{
-            .limits = .{ .flags = 0, .min = 0, .max = undefined }, // will be overwritten during `mapFunctionTable`
-            .reftype = .funcref,
-        };
-        if (output_mode == .Obj or options.import_table) {
-            symbol.setUndefined(true);
-            symbol.index = @intCast(wasm.imported_tables_count);
-            wasm.imported_tables_count += 1;
-            try wasm.imports.put(gpa, loc, .{
-                .module_name = wasm.host_name,
-                .name = symbol.name,
-                .kind = .{ .table = table },
-            });
-        } else {
-            symbol.index = @as(u32, @intCast(wasm.imported_tables_count + wasm.tables.items.len));
-            try wasm.tables.append(gpa, table);
-            if (wasm.export_table) {
-                symbol.setFlag(.WASM_SYM_EXPORTED);
-            } else {
-                symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-            }
-        }
-    }
-
-    // create __wasm_call_ctors
-    {
-        const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_call_ctors, .function);
-        const symbol = wasm.symbolLocSymbol(loc);
-        symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-        // we do not know the function index until after we merged all sections.
-        // Therefore we set `symbol.index` and create its corresponding references
-        // at the end during `initializeCallCtorsFunction`.
-    }
-
-    // shared-memory symbols for TLS support
-    if (shared_memory) {
-        {
-            const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_base, .global);
-            const symbol = wasm.symbolLocSymbol(loc);
-            symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-            symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len);
-            symbol.mark();
-            try wasm.wasm_globals.append(gpa, .{
-                .global_type = .{ .valtype = .i32, .mutable = true },
-                .init = .{ .i32_const = undefined },
-            });
-        }
-        {
-            const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_size, .global);
-            const symbol = wasm.symbolLocSymbol(loc);
-            symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-            symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len);
-            symbol.mark();
-            try wasm.wasm_globals.append(gpa, .{
-                .global_type = .{ .valtype = .i32, .mutable = false },
-                .init = .{ .i32_const = undefined },
-            });
-        }
-        {
-            const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_align, .global);
-            const symbol = wasm.symbolLocSymbol(loc);
-            symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-            symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len);
-            symbol.mark();
-            try wasm.wasm_globals.append(gpa, .{
-                .global_type = .{ .valtype = .i32, .mutable = false },
-                .init = .{ .i32_const = undefined },
-            });
-        }
-        {
-            const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_tls, .function);
-            const symbol = wasm.symbolLocSymbol(loc);
-            symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-        }
-    }
-
-    if (comp.zcu) |zcu| {
-        if (!use_llvm) {
-            const zig_object = try arena.create(ZigObject);
-            wasm.zig_object = zig_object;
-            zig_object.* = .{
-                .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);
-        }
-    }
-
     return wasm;
 }
 
-pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {
-    var index: u32 = 0;
-    while (index < wasm.func_types.items.len) : (index += 1) {
-        if (wasm.func_types.items[index].eql(func_type)) return index;
-    }
-    return null;
-}
-
-/// Either creates a new import, or updates one if existing.
-/// When `type_index` is non-null, we assume an external function.
-/// In all other cases, a data-symbol will be created instead.
-pub fn addOrUpdateImport(
-    wasm: *Wasm,
-    /// Name of the import
-    name: []const u8,
-    /// Symbol index that is external
-    symbol_index: Symbol.Index,
-    /// Optional library name (i.e. `extern "c" fn foo() void`
-    lib_name: ?[:0]const u8,
-    /// The index of the type that represents the function signature
-    /// when the extern is a function. When this is null, a data-symbol
-    /// is asserted instead.
-    type_index: ?u32,
-) !void {
-    return wasm.zig_object.?.addOrUpdateImport(wasm, name, symbol_index, lib_name, type_index);
-}
-
-/// For a given name, creates a new global synthetic symbol.
-/// Leaves index undefined and the default flags (0).
-fn createSyntheticSymbol(wasm: *Wasm, name: String, tag: Symbol.Tag) !SymbolLoc {
-    return wasm.createSyntheticSymbolOffset(name, tag);
-}
-
-fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: String, tag: Symbol.Tag) !SymbolLoc {
-    const sym_index: Symbol.Index = @enumFromInt(wasm.synthetic_symbols.items.len);
-    const loc: SymbolLoc = .{ .index = sym_index, .file = .none };
-    const gpa = wasm.base.comp.gpa;
-    try wasm.synthetic_symbols.append(gpa, .{
-        .name = name_offset,
-        .flags = 0,
-        .tag = tag,
-        .index = undefined,
-        .virtual_address = undefined,
-    });
-    try wasm.resolved_symbols.putNoClobber(gpa, loc, {});
-    try wasm.globals.put(gpa, name_offset, loc);
-    return loc;
-}
-
 fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {
     const diags = &wasm.base.comp.link_diags;
     const obj = link.openObject(path, false, false) catch |err| {
@@ -725,8 +1141,11 @@ fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {
 }
 
 fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
-    defer obj.file.close();
     const gpa = wasm.base.comp.gpa;
+    const gc_sections = wasm.base.gc_sections;
+
+    defer obj.file.close();
+
     try wasm.objects.ensureUnusedCapacity(gpa, 1);
     const stat = try obj.file.stat();
     const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
@@ -737,33 +1156,16 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
     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`
-pub fn createAtom(wasm: *Wasm, sym_index: Symbol.Index, object_index: OptionalObjectId) !Atom.Index {
-    const gpa = wasm.base.comp.gpa;
-    const index: Atom.Index = @enumFromInt(wasm.managed_atoms.items.len);
-    const atom = try wasm.managed_atoms.addOne(gpa);
-    atom.* = .{
-        .file = object_index,
-        .sym_index = sym_index,
-    };
-    try wasm.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), index);
-
-    return index;
-}
+    var ss: Object.ScratchSpace = .{};
+    defer ss.deinit(gpa);
 
-pub fn getAtom(wasm: *const Wasm, index: Atom.Index) Atom {
-    return wasm.managed_atoms.items[@intFromEnum(index)];
-}
-
-pub fn getAtomPtr(wasm: *Wasm, index: Atom.Index) *Atom {
-    return &wasm.managed_atoms.items[@intFromEnum(index)];
+    const object = try Object.parse(wasm, file_contents, obj.path, null, wasm.host_name, &ss, obj.must_link, gc_sections);
+    wasm.objects.appendAssumeCapacity(object);
 }
 
 fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
     const gpa = wasm.base.comp.gpa;
+    const gc_sections = wasm.base.gc_sections;
 
     defer obj.file.close();
 
@@ -771,28 +1173,12 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
     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);
+    defer gpa.free(file_contents);
 
     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.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
@@ -806,2597 +1192,538 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
         }
     }
 
+    var ss: Object.ScratchSpace = .{};
+    defer ss.deinit(gpa);
+
+    try wasm.objects.ensureUnusedCapacity(gpa, offsets.count());
     for (offsets.keys()) |file_offset| {
-        const object = try archive.parseObject(wasm, file_contents[file_offset..], obj.path);
-        try wasm.objects.append(gpa, object);
+        const contents = file_contents[file_offset..];
+        const object = try archive.parseObject(wasm, contents, obj.path, wasm.host_name, &ss, obj.must_link, gc_sections);
+        wasm.objects.appendAssumeCapacity(object);
     }
 }
 
-fn requiresTLSReloc(wasm: *const Wasm) bool {
-    for (wasm.got_symbols.items) |loc| {
-        if (wasm.symbolLocSymbol(loc).isTLS()) {
-            return true;
-        }
-    }
-    return false;
-}
+pub fn deinit(wasm: *Wasm) void {
+    const gpa = wasm.base.comp.gpa;
+    if (wasm.llvm_object) |llvm_object| llvm_object.deinit();
 
-fn objectPath(wasm: *const Wasm, object_id: ObjectId) Path {
-    const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.path;
-    return obj.path;
-}
+    wasm.navs.deinit(gpa);
+    wasm.nav_exports.deinit(gpa);
+    wasm.uav_exports.deinit(gpa);
+    wasm.imports.deinit(gpa);
 
-fn objectSymbols(wasm: *const Wasm, object_id: ObjectId) []const Symbol {
-    const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.symbols.items;
-    return obj.symtable;
-}
+    wasm.flush_buffer.deinit(gpa);
+
+    if (wasm.dwarf) |*dwarf| dwarf.deinit();
+
+    wasm.object_function_imports.deinit(gpa);
+    wasm.object_functions.deinit(gpa);
+    wasm.object_global_imports.deinit(gpa);
+    wasm.object_globals.deinit(gpa);
+    wasm.object_table_imports.deinit(gpa);
+    wasm.object_tables.deinit(gpa);
+    wasm.object_memory_imports.deinit(gpa);
+    wasm.object_memories.deinit(gpa);
+
+    wasm.object_data_segments.deinit(gpa);
+    wasm.object_relocatable_codes.deinit(gpa);
+    wasm.object_custom_segments.deinit(gpa);
+    wasm.object_symbols.deinit(gpa);
+    wasm.object_named_segments.deinit(gpa);
+    wasm.object_init_funcs.deinit(gpa);
+    wasm.object_comdats.deinit(gpa);
+    wasm.object_relocations.deinit(gpa);
+    wasm.object_relocations_table.deinit(gpa);
+    wasm.object_comdat_symbols.deinit(gpa);
+    wasm.objects.deinit(gpa);
 
-fn objectSymbol(wasm: *const Wasm, object_id: ObjectId, index: Symbol.Index) *Symbol {
-    const obj = wasm.objectById(object_id) orelse return &wasm.zig_object.?.symbols.items[@intFromEnum(index)];
-    return &obj.symtable[@intFromEnum(index)];
-}
+    wasm.atoms.deinit(gpa);
 
-fn objectFunction(wasm: *const Wasm, object_id: ObjectId, sym_index: Symbol.Index) std.wasm.Func {
-    const obj = wasm.objectById(object_id) orelse {
-        const zo = wasm.zig_object.?;
-        const sym = zo.symbols.items[@intFromEnum(sym_index)];
-        return zo.functions.items[sym.index];
-    };
-    const sym = obj.symtable[@intFromEnum(sym_index)];
-    return obj.functions[sym.index - obj.imported_functions_count];
-}
+    wasm.synthetic_symbols.deinit(gpa);
+    wasm.globals.deinit(gpa);
+    wasm.undefs.deinit(gpa);
+    wasm.discarded.deinit(gpa);
+    wasm.segments.deinit(gpa);
+    wasm.segment_info.deinit(gpa);
 
-fn objectImportedFunctions(wasm: *const Wasm, object_id: ObjectId) u32 {
-    const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.imported_functions_count;
-    return obj.imported_functions_count;
-}
+    wasm.global_imports.deinit(gpa);
+    wasm.func_types.deinit(gpa);
+    wasm.functions.deinit(gpa);
+    wasm.output_globals.deinit(gpa);
+    wasm.exports.deinit(gpa);
 
-fn objectGlobals(wasm: *const Wasm, object_id: ObjectId) []const std.wasm.Global {
-    const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.globals.items;
-    return obj.globals;
+    wasm.string_bytes.deinit(gpa);
+    wasm.string_table.deinit(gpa);
+    wasm.dump_argv_list.deinit(gpa);
 }
 
-fn objectFuncTypes(wasm: *const Wasm, object_id: ObjectId) []const std.wasm.Type {
-    const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.func_types.items;
-    return obj.func_types;
-}
+pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+    if (build_options.skip_non_native and builtin.object_format != .wasm) {
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+    }
+    if (wasm.llvm_object) |llvm_object| return llvm_object.updateFunc(pt, func_index, air, liveness);
 
-fn objectSegmentInfo(wasm: *const Wasm, object_id: ObjectId) []const NamedSegment {
-    const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.segment_info.items;
-    return obj.segment_info;
-}
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const func = pt.zcu.funcInfo(func_index);
+    const nav_index = func.owner_nav;
+
+    const code_start: u32 = @intCast(wasm.string_bytes.items.len);
+    const relocs_start: u32 = @intCast(wasm.relocations.items.len);
+    wasm.string_bytes_lock.lock();
+
+    const wasm_codegen = @import("../../arch/wasm/CodeGen.zig");
+    dev.check(.wasm_backend);
+    const result = try wasm_codegen.generate(
+        &wasm.base,
+        pt,
+        zcu.navSrcLoc(nav_index),
+        func_index,
+        air,
+        liveness,
+        &wasm.string_bytes,
+        .none,
+    );
 
-/// For a given symbol index, find its corresponding import.
-/// Asserts import exists.
-fn objectImport(wasm: *const Wasm, object_id: ObjectId, symbol_index: Symbol.Index) Import {
-    const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.imports.get(symbol_index).?;
-    return obj.findImport(obj.symtable[@intFromEnum(symbol_index)]);
-}
+    const code_len: u32 = @intCast(wasm.string_bytes.items.len - code_start);
+    const relocs_len: u32 = @intCast(wasm.relocations.items.len - relocs_start);
+    wasm.string_bytes_lock.unlock();
+
+    const code: Nav.Code = switch (result) {
+        .ok => .{
+            .off = code_start,
+            .len = code_len,
+        },
+        .fail => |em| {
+            try pt.zcu.failed_codegen.put(gpa, nav_index, em);
+            return;
+        },
+    };
 
-/// Returns the object element pointer, or null if it is the ZigObject.
-fn objectById(wasm: *const Wasm, object_id: ObjectId) ?*Object {
-    if (object_id == .zig_object) return null;
-    return &wasm.objects.items[@intFromEnum(object_id)];
+    const gop = try wasm.navs.getOrPut(gpa, nav_index);
+    if (gop.found_existing) {
+        @panic("TODO reuse these resources");
+    } else {
+        _ = wasm.imports.swapRemove(nav_index);
+    }
+    gop.value_ptr.* = .{
+        .code = code,
+        .relocs = .{
+            .off = relocs_start,
+            .len = relocs_len,
+        },
+    };
 }
 
-fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void {
+// Generate code for the "Nav", storing it in memory to be later written to
+// the file on flush().
+pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
+    if (build_options.skip_non_native and builtin.object_format != .wasm) {
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+    }
+    if (wasm.llvm_object) |llvm_object| return llvm_object.updateNav(pt, nav_index);
+    const zcu = pt.zcu;
+    const ip = &zcu.intern_pool;
+    const nav = ip.getNav(nav_index);
     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: '{'}'", .{obj_path});
-    const symbols = objectSymbols(wasm, object_id);
-
-    for (symbols, 0..) |symbol, i| {
-        const sym_index: Symbol.Index = @enumFromInt(i);
-        const location: SymbolLoc = .{
-            .file = object_id.toOptional(),
-            .index = sym_index,
-        };
-        if (symbol.name == wasm.preloaded_strings.__indirect_function_table) continue;
-
-        if (symbol.isLocal()) {
-            if (symbol.isUndefined()) {
-                diags.addParseError(obj_path, "local symbol '{s}' references import", .{
-                    wasm.stringSlice(symbol.name),
-                });
-            }
-            try wasm.resolved_symbols.putNoClobber(gpa, location, {});
-            continue;
-        }
 
-        const maybe_existing = try wasm.globals.getOrPut(gpa, symbol.name);
-        if (!maybe_existing.found_existing) {
-            maybe_existing.value_ptr.* = location;
-            try wasm.resolved_symbols.putNoClobber(gpa, location, {});
-
-            if (symbol.isUndefined()) {
-                try wasm.undefs.putNoClobber(gpa, symbol.name, location);
-            }
-            continue;
-        }
+    const nav_val = zcu.navValue(nav_index);
+    const is_extern, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) {
+        .variable => |variable| .{ false, Value.fromInterned(variable.init) },
+        .func => unreachable,
+        .@"extern" => b: {
+            assert(!ip.isFunctionType(nav.typeOf(ip)));
+            break :b .{ true, nav_val };
+        },
+        else => .{ false, nav_val },
+    };
 
-        const existing_loc = maybe_existing.value_ptr.*;
-        const existing_sym: *Symbol = wasm.symbolLocSymbol(existing_loc);
-        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 (!nav_init.typeOf(zcu).hasRuntimeBits(zcu)) {
+        _ = wasm.imports.swapRemove(nav_index);
+        _ = wasm.navs.swapRemove(nav_index); // TODO reclaim resources
+        return;
+    }
 
-        if (!existing_sym.isUndefined()) outer: {
-            if (!symbol.isUndefined()) inner: {
-                if (symbol.isWeak()) {
-                    break :inner; // ignore the new symbol (discard it)
-                }
-                if (existing_sym.isWeak()) {
-                    break :outer; // existing is weak, while new one isn't. Replace it.
-                }
-                // both are defined and weak, we have a symbol collision.
-                var err = try diags.addErrorWithNotes(2);
-                try err.addMsg("symbol '{s}' defined multiple times", .{wasm.stringSlice(symbol.name)});
-                try err.addNote("first definition in '{'}'", .{existing_file_path});
-                try err.addNote("next definition in '{'}'", .{obj_path});
-            }
+    if (is_extern) {
+        try wasm.imports.put(nav_index, {});
+        _ = wasm.navs.swapRemove(nav_index); // TODO reclaim resources
+        return;
+    }
 
-            try wasm.discarded.put(gpa, location, existing_loc);
-            continue; // Do not overwrite defined symbols with undefined symbols
-        }
+    const code_start: u32 = @intCast(wasm.string_bytes.items.len);
+    const relocs_start: u32 = @intCast(wasm.relocations.items.len);
+    wasm.string_bytes_lock.lock();
 
-        if (symbol.tag != existing_sym.tag) {
-            var err = try diags.addErrorWithNotes(2);
-            try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{
-                wasm.stringSlice(symbol.name), @tagName(symbol.tag), @tagName(existing_sym.tag),
-            });
-            try err.addNote("first definition in '{'}'", .{existing_file_path});
-            try err.addNote("next definition in '{'}'", .{obj_path});
-        }
+    const res = try codegen.generateSymbol(
+        &wasm.base,
+        pt,
+        zcu.navSrcLoc(nav_index),
+        nav_init,
+        &wasm.string_bytes,
+        .none,
+    );
 
-        if (existing_sym.isUndefined() and symbol.isUndefined()) {
-            // only verify module/import name for function symbols
-            if (symbol.tag == .function) {
-                const existing_name = if (existing_loc.file.unwrap()) |existing_obj_id|
-                    objectImport(wasm, existing_obj_id, existing_loc.index).module_name
-                else
-                    wasm.imports.get(existing_loc).?.module_name;
-
-                const module_name = objectImport(wasm, object_id, sym_index).module_name;
-                if (existing_name != module_name) {
-                    var err = try diags.addErrorWithNotes(2);
-                    try err.addMsg("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{
-                        wasm.stringSlice(symbol.name),
-                        wasm.stringSlice(existing_name),
-                        wasm.stringSlice(module_name),
-                    });
-                    try err.addNote("first definition in '{'}'", .{existing_file_path});
-                    try err.addNote("next definition in '{'}'", .{obj_path});
-                }
-            }
+    const code_len: u32 = @intCast(wasm.string_bytes.items.len - code_start);
+    const relocs_len: u32 = @intCast(wasm.relocations.items.len - relocs_start);
+    wasm.string_bytes_lock.unlock();
 
-            // both undefined so skip overwriting existing symbol and discard the new symbol
-            try wasm.discarded.put(gpa, location, existing_loc);
-            continue;
-        }
+    const code: Nav.Code = switch (res) {
+        .ok => .{
+            .off = code_start,
+            .len = code_len,
+        },
+        .fail => |em| {
+            try zcu.failed_codegen.put(gpa, nav_index, em);
+            return;
+        },
+    };
 
-        if (existing_sym.tag == .global) {
-            const existing_ty = wasm.getGlobalType(existing_loc);
-            const new_ty = wasm.getGlobalType(location);
-            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", .{wasm.stringSlice(symbol.name)});
-                try err.addNote("first definition in '{'}'", .{existing_file_path});
-                try err.addNote("next definition in '{'}'", .{obj_path});
-            }
-        }
+    const gop = try wasm.navs.getOrPut(gpa, nav_index);
+    if (gop.found_existing) {
+        @panic("TODO reuse these resources");
+    } else {
+        _ = wasm.imports.swapRemove(nav_index);
+    }
+    gop.value_ptr.* = .{
+        .code = code,
+        .relocs = .{
+            .off = relocs_start,
+            .len = relocs_len,
+        },
+    };
+}
 
-        if (existing_sym.tag == .function) {
-            const existing_ty = wasm.getFunctionSignature(existing_loc);
-            const new_ty = wasm.getFunctionSignature(location);
-            if (!existing_ty.eql(new_ty)) {
-                var err = try diags.addErrorWithNotes(3);
-                try err.addMsg("symbol '{s}' mismatching function signatures.", .{wasm.stringSlice(symbol.name)});
-                try err.addNote("expected signature {}, but found signature {}", .{ existing_ty, new_ty });
-                try err.addNote("first definition in '{'}'", .{existing_file_path});
-                try err.addNote("next definition in '{'}'", .{obj_path});
-            }
-        }
+pub fn updateLineNumber(wasm: *Wasm, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void {
+    if (wasm.dwarf) |*dw| {
+        try dw.updateLineNumber(pt.zcu, ti_id);
+    }
+}
 
-        // when both symbols are weak, we skip overwriting unless the existing
-        // symbol is weak and the new one isn't, in which case we *do* overwrite it.
-        if (existing_sym.isWeak() and symbol.isWeak()) blk: {
-            if (existing_sym.isUndefined() and !symbol.isUndefined()) break :blk;
-            try wasm.discarded.put(gpa, location, existing_loc);
-            continue;
-        }
+pub fn deleteExport(
+    wasm: *Wasm,
+    exported: Zcu.Exported,
+    name: InternPool.NullTerminatedString,
+) void {
+    if (wasm.llvm_object != null) return;
 
-        // simply overwrite with the new symbol
-        log.debug("Overwriting symbol '{s}'", .{wasm.stringSlice(symbol.name)});
-        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, symbol.name, location);
-        try wasm.resolved_symbols.put(gpa, location, {});
-        assert(wasm.resolved_symbols.swapRemove(existing_loc));
-        if (existing_sym.isUndefined()) {
-            _ = wasm.undefs.swapRemove(symbol.name);
-        }
+    const zcu = wasm.base.comp.zcu.?;
+    const ip = &zcu.intern_pool;
+    const export_name = wasm.getExistingString(name.toSlice(ip)).?;
+    switch (exported) {
+        .nav => |nav_index| assert(wasm.nav_exports.swapRemove(.{ .nav_index = nav_index, .name = export_name })),
+        .uav => |uav_index| assert(wasm.uav_exports.swapRemove(.{ .uav_index = uav_index, .name = export_name })),
     }
+    wasm.any_exports_updated = true;
 }
 
-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;
-
-    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.lazy_archives.items) |lazy_archive| {
-            const sym_name = wasm.stringSlice(sym_name_index);
-            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 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));
+pub fn updateExports(
+    wasm: *Wasm,
+    pt: Zcu.PerThread,
+    exported: Zcu.Exported,
+    export_indices: []const u32,
+) !void {
+    if (build_options.skip_non_native and builtin.object_format != .wasm) {
+        @panic("Attempted to compile for object format that was disabled by build configuration");
+    }
+    if (wasm.llvm_object) |llvm_object| return llvm_object.updateExports(pt, exported, export_indices);
 
-            // continue loop for any remaining undefined symbols that still exist
-            // after resolving last object file
-            continue :undef_loop;
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
+    for (export_indices) |export_idx| {
+        const exp = export_idx.ptr(zcu);
+        const name = try wasm.internString(exp.opts.name.toSlice(ip));
+        switch (exported) {
+            .nav => |nav_index| wasm.nav_exports.put(gpa, .{ .nav_index = nav_index, .name = name }, export_idx),
+            .uav => |uav_index| wasm.uav_exports.put(gpa, .{ .uav_index = uav_index, .name = name }, export_idx),
         }
-        index += 1;
     }
+    wasm.any_exports_updated = true;
 }
 
-/// Writes an unsigned 32-bit integer as a LEB128-encoded 'i32.const' value.
-fn writeI32Const(writer: anytype, val: u32) !void {
-    try writer.writeByte(std.wasm.opcode(.i32_const));
-    try leb.writeIleb128(writer, @as(i32, @bitCast(val)));
-}
-
-fn setupInitMemoryFunction(wasm: *Wasm) !void {
+pub fn loadInput(wasm: *Wasm, input: link.Input) !void {
     const comp = wasm.base.comp;
     const gpa = comp.gpa;
-    const shared_memory = comp.config.shared_memory;
-    const import_memory = comp.config.import_memory;
-
-    // Passive segments are used to avoid memory being reinitialized on each
-    // thread's instantiation. These passive segments are initialized and
-    // dropped in __wasm_init_memory, which is registered as the start function
-    // We also initialize bss segments (using memory.fill) as part of this
-    // function.
-    if (!wasm.hasPassiveInitializationSegments()) {
-        return;
-    }
-    const sym_loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_memory, .function);
-    wasm.symbolLocSymbol(sym_loc).mark();
-
-    const flag_address: u32 = if (shared_memory) address: {
-        // when we have passive initialization segments and shared memory
-        // `setupMemory` will create this symbol and set its virtual address.
-        const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_init_memory_flag).?;
-        break :address wasm.symbolLocSymbol(loc).virtual_address;
-    } else 0;
-
-    var function_body = std.ArrayList(u8).init(gpa);
-    defer function_body.deinit();
-    const writer = function_body.writer();
-
-    // we have 0 locals
-    try leb.writeUleb128(writer, @as(u32, 0));
-
-    if (shared_memory) {
-        // destination blocks
-        // based on values we jump to corresponding label
-        try writer.writeByte(std.wasm.opcode(.block)); // $drop
-        try writer.writeByte(std.wasm.block_empty); // block type
-
-        try writer.writeByte(std.wasm.opcode(.block)); // $wait
-        try writer.writeByte(std.wasm.block_empty); // block type
-
-        try writer.writeByte(std.wasm.opcode(.block)); // $init
-        try writer.writeByte(std.wasm.block_empty); // block type
-
-        // atomically check
-        try writeI32Const(writer, flag_address);
-        try writeI32Const(writer, 0);
-        try writeI32Const(writer, 1);
-        try writer.writeByte(std.wasm.opcode(.atomics_prefix));
-        try leb.writeUleb128(writer, std.wasm.atomicsOpcode(.i32_atomic_rmw_cmpxchg));
-        try leb.writeUleb128(writer, @as(u32, 2)); // alignment
-        try leb.writeUleb128(writer, @as(u32, 0)); // offset
-
-        // based on the value from the atomic check, jump to the label.
-        try writer.writeByte(std.wasm.opcode(.br_table));
-        try leb.writeUleb128(writer, @as(u32, 2)); // length of the table (we have 3 blocks but because of the mandatory default the length is 2).
-        try leb.writeUleb128(writer, @as(u32, 0)); // $init
-        try leb.writeUleb128(writer, @as(u32, 1)); // $wait
-        try leb.writeUleb128(writer, @as(u32, 2)); // $drop
-        try writer.writeByte(std.wasm.opcode(.end));
-    }
 
-    for (wasm.data_segments.keys(), wasm.data_segments.values(), 0..) |key, value, segment_index_usize| {
-        const segment_index: u32 = @intCast(segment_index_usize);
-        const segment = wasm.segmentPtr(value);
-        if (segment.needsPassiveInitialization(import_memory, key)) {
-            // For passive BSS segments we can simple issue a memory.fill(0).
-            // For non-BSS segments we do a memory.init.  Both these
-            // instructions take as their first argument the destination
-            // address.
-            try writeI32Const(writer, segment.offset);
-
-            if (shared_memory and std.mem.eql(u8, key, ".tdata")) {
-                // When we initialize the TLS segment we also set the `__tls_base`
-                // global.  This allows the runtime to use this static copy of the
-                // TLS data for the first/main thread.
-                try writeI32Const(writer, segment.offset);
-                try writer.writeByte(std.wasm.opcode(.global_set));
-                const loc = wasm.globals.get(wasm.preloaded_strings.__tls_base).?;
-                try leb.writeUleb128(writer, wasm.symbolLocSymbol(loc).index);
-            }
+    if (comp.verbose_link) {
+        comp.mutex.lock(); // protect comp.arena
+        defer comp.mutex.unlock();
 
-            try writeI32Const(writer, 0);
-            try writeI32Const(writer, segment.size);
-            try writer.writeByte(std.wasm.opcode(.misc_prefix));
-            if (std.mem.eql(u8, key, ".bss")) {
-                // fill bss segment with zeroes
-                try leb.writeUleb128(writer, std.wasm.miscOpcode(.memory_fill));
-            } else {
-                // initialize the segment
-                try leb.writeUleb128(writer, std.wasm.miscOpcode(.memory_init));
-                try leb.writeUleb128(writer, segment_index);
-            }
-            try writer.writeByte(0); // memory index immediate
+        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)),
         }
     }
 
-    if (shared_memory) {
-        // we set the init memory flag to value '2'
-        try writeI32Const(writer, flag_address);
-        try writeI32Const(writer, 2);
-        try writer.writeByte(std.wasm.opcode(.atomics_prefix));
-        try leb.writeUleb128(writer, std.wasm.atomicsOpcode(.i32_atomic_store));
-        try leb.writeUleb128(writer, @as(u32, 2)); // alignment
-        try leb.writeUleb128(writer, @as(u32, 0)); // offset
-
-        // notify any waiters for segment initialization completion
-        try writeI32Const(writer, flag_address);
-        try writer.writeByte(std.wasm.opcode(.i32_const));
-        try leb.writeIleb128(writer, @as(i32, -1)); // number of waiters
-        try writer.writeByte(std.wasm.opcode(.atomics_prefix));
-        try leb.writeUleb128(writer, std.wasm.atomicsOpcode(.memory_atomic_notify));
-        try leb.writeUleb128(writer, @as(u32, 2)); // alignment
-        try leb.writeUleb128(writer, @as(u32, 0)); // offset
-        try writer.writeByte(std.wasm.opcode(.drop));
-
-        // branch and drop segments
-        try writer.writeByte(std.wasm.opcode(.br));
-        try leb.writeUleb128(writer, @as(u32, 1));
-
-        // wait for thread to initialize memory segments
-        try writer.writeByte(std.wasm.opcode(.end)); // end $wait
-        try writeI32Const(writer, flag_address);
-        try writeI32Const(writer, 1); // expected flag value
-        try writer.writeByte(std.wasm.opcode(.i64_const));
-        try leb.writeIleb128(writer, @as(i64, -1)); // timeout
-        try writer.writeByte(std.wasm.opcode(.atomics_prefix));
-        try leb.writeUleb128(writer, std.wasm.atomicsOpcode(.memory_atomic_wait32));
-        try leb.writeUleb128(writer, @as(u32, 2)); // alignment
-        try leb.writeUleb128(writer, @as(u32, 0)); // offset
-        try writer.writeByte(std.wasm.opcode(.drop));
-
-        try writer.writeByte(std.wasm.opcode(.end)); // end $drop
+    switch (input) {
+        .res => unreachable,
+        .dso_exact => unreachable,
+        .dso => unreachable,
+        .object => |obj| try parseObject(wasm, obj),
+        .archive => |obj| try parseArchive(wasm, obj),
     }
+}
 
-    for (wasm.data_segments.keys(), wasm.data_segments.values(), 0..) |name, value, segment_index_usize| {
-        const segment_index: u32 = @intCast(segment_index_usize);
-        const segment = wasm.segmentPtr(value);
-        if (segment.needsPassiveInitialization(import_memory, name) and
-            !std.mem.eql(u8, name, ".bss"))
-        {
-            // The TLS region should not be dropped since its is needed
-            // during the initialization of each thread (__wasm_init_tls).
-            if (shared_memory and std.mem.eql(u8, name, ".tdata")) {
-                continue;
-            }
+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;
 
-            try writer.writeByte(std.wasm.opcode(.misc_prefix));
-            try leb.writeUleb128(writer, std.wasm.miscOpcode(.data_drop));
-            try leb.writeUleb128(writer, segment_index);
-        }
+    if (use_lld) {
+        return wasm.linkWithLLD(arena, tid, prog_node);
     }
+    return wasm.flushModule(arena, tid, prog_node);
+}
 
-    // End of the function body
-    try writer.writeByte(std.wasm.opcode(.end));
-
-    try wasm.createSyntheticFunction(
-        wasm.preloaded_strings.__wasm_init_memory,
-        std.wasm.Type{ .params = &.{}, .returns = &.{} },
-        &function_body,
-    );
-}
-
-/// Constructs a synthetic function that performs runtime relocations for
-/// TLS symbols. This function is called by `__wasm_init_tls`.
-fn setupTLSRelocationsFunction(wasm: *Wasm) !void {
-    const comp = wasm.base.comp;
-    const gpa = comp.gpa;
-    const shared_memory = comp.config.shared_memory;
-
-    // When we have TLS GOT entries and shared memory is enabled,
-    // we must perform runtime relocations or else we don't create the function.
-    if (!shared_memory or !wasm.requiresTLSReloc()) {
-        return;
-    }
-
-    const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_apply_global_tls_relocs, .function);
-    wasm.symbolLocSymbol(loc).mark();
-    var function_body = std.ArrayList(u8).init(gpa);
-    defer function_body.deinit();
-    const writer = function_body.writer();
-
-    // locals (we have none)
-    try writer.writeByte(0);
-    for (wasm.got_symbols.items, 0..) |got_loc, got_index| {
-        const sym: *Symbol = wasm.symbolLocSymbol(got_loc);
-        if (!sym.isTLS()) continue; // only relocate TLS symbols
-        if (sym.tag == .data and sym.isDefined()) {
-            // get __tls_base
-            try writer.writeByte(std.wasm.opcode(.global_get));
-            try leb.writeUleb128(writer, wasm.symbolLocSymbol(wasm.globals.get(wasm.preloaded_strings.__tls_base).?).index);
-
-            // add the virtual address of the symbol
-            try writer.writeByte(std.wasm.opcode(.i32_const));
-            try leb.writeUleb128(writer, sym.virtual_address);
-        } else if (sym.tag == .function) {
-            @panic("TODO: relocate GOT entry of function");
-        } else continue;
-
-        try writer.writeByte(std.wasm.opcode(.i32_add));
-        try writer.writeByte(std.wasm.opcode(.global_set));
-        try leb.writeUleb128(writer, wasm.imported_globals_count + @as(u32, @intCast(wasm.wasm_globals.items.len + got_index)));
-    }
-    try writer.writeByte(std.wasm.opcode(.end));
+pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!void {
+    const tracy = trace(@src());
+    defer tracy.end();
 
-    try wasm.createSyntheticFunction(
-        wasm.preloaded_strings.__wasm_apply_global_tls_relocs,
-        std.wasm.Type{ .params = &.{}, .returns = &.{} },
-        &function_body,
-    );
-}
+    const sub_prog_node = prog_node.start("Wasm Prelink", 0);
+    defer sub_prog_node.end();
 
-fn validateFeatures(
-    wasm: *const Wasm,
-    to_emit: *[@typeInfo(Feature.Tag).@"enum".fields.len]bool,
-    emit_features_count: *u32,
-) !void {
     const comp = wasm.base.comp;
-    const diags = &wasm.base.comp.link_diags;
-    const target = comp.root_mod.resolved_target.result;
-    const shared_memory = comp.config.shared_memory;
-    const cpu_features = target.cpu.features;
-    const infer = cpu_features.isEmpty(); // when the user did not define any features, we infer them from linked objects.
-    const known_features_count = @typeInfo(Feature.Tag).@"enum".fields.len;
-
-    var allowed = [_]bool{false} ** known_features_count;
-    var used = [_]u17{0} ** known_features_count;
-    var disallowed = [_]u17{0} ** known_features_count;
-    var required = [_]u17{0} ** known_features_count;
-
-    // when false, we fail linking. We only verify this after a loop to catch all invalid features.
-    var valid_feature_set = true;
-    // will be set to true when there's any TLS segment found in any of the object files
-    var has_tls = false;
-
-    // When the user has given an explicit list of features to enable,
-    // we extract them and insert each into the 'allowed' list.
-    if (!infer) {
-        inline for (@typeInfo(std.Target.wasm.Feature).@"enum".fields) |feature_field| {
-            if (cpu_features.isEnabled(feature_field.value)) {
-                allowed[feature_field.value] = true;
-                emit_features_count.* += 1;
-            }
-        }
-    }
-
-    // extract all the used, disallowed and required features from each
-    // linked object file so we can test them.
-    for (wasm.objects.items, 0..) |*object, file_index| {
-        for (object.features) |feature| {
-            const value = (@as(u16, @intCast(file_index)) << 1) | 1;
-            switch (feature.prefix) {
-                .used => {
-                    used[@intFromEnum(feature.tag)] = value;
-                },
-                .disallowed => {
-                    disallowed[@intFromEnum(feature.tag)] = value;
-                },
-                .required => {
-                    required[@intFromEnum(feature.tag)] = value;
-                    used[@intFromEnum(feature.tag)] = value;
-                },
-            }
-        }
-
-        for (object.segment_info) |segment| {
-            if (segment.isTLS()) {
-                has_tls = true;
-            }
-        }
-    }
-
-    // when we infer the features, we allow each feature found in the 'used' set
-    // and insert it into the 'allowed' set. When features are not inferred,
-    // we validate that a used feature is allowed.
-    for (used, 0..) |used_set, used_index| {
-        const is_enabled = @as(u1, @truncate(used_set)) != 0;
-        if (infer) {
-            allowed[used_index] = is_enabled;
-            emit_features_count.* += @intFromBool(is_enabled);
-        } else if (is_enabled and !allowed[used_index]) {
-            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;
-        }
-    }
-
-    if (!valid_feature_set) {
-        return error.FlushFailure;
-    }
-
-    if (shared_memory) {
-        const disallowed_feature = disallowed[@intFromEnum(Feature.Tag.shared_mem)];
-        if (@as(u1, @truncate(disallowed_feature)) != 0) {
-            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;
-        }
+    const gpa = comp.gpa;
+    const rdynamic = comp.config.rdynamic;
 
-        for ([_]Feature.Tag{ .atomics, .bulk_memory }) |feature| {
-            if (!allowed[@intFromEnum(feature)]) {
-                var err = try diags.addErrorWithNotes(0);
-                try err.addMsg("feature '{}' is not used but is required for shared-memory", .{feature});
+    {
+        var missing_exports: std.AutoArrayHashMapUnmanaged(String, void) = .empty;
+        defer missing_exports.deinit(gpa);
+        for (wasm.export_symbol_names) |exp_name| {
+            const exp_name_interned = try wasm.internString(exp_name);
+            if (wasm.object_function_imports.getPtr(exp_name_interned)) |import| {
+                if (import.resolution != .unresolved) {
+                    import.flags.exported = true;
+                    continue;
+                }
             }
-        }
-    }
-
-    if (has_tls) {
-        for ([_]Feature.Tag{ .atomics, .bulk_memory }) |feature| {
-            if (!allowed[@intFromEnum(feature)]) {
-                var err = try diags.addErrorWithNotes(0);
-                try err.addMsg("feature '{}' is not used but is required for thread-local storage", .{feature});
+            if (wasm.object_global_imports.getPtr(exp_name_interned)) |import| {
+                if (import.resolution != .unresolved) {
+                    import.flags.exported = true;
+                    continue;
+                }
             }
+            try wasm.missing_exports.put(exp_name_interned, {});
         }
+        wasm.missing_exports_init = try gpa.dupe(String, wasm.missing_exports.keys());
     }
-    // For each linked object, validate the required and disallowed features
-    for (wasm.objects.items) |*object| {
-        var object_used_features = [_]bool{false} ** known_features_count;
-        for (object.features) |feature| {
-            if (feature.prefix == .disallowed) continue; // already defined in 'disallowed' set.
-            // from here a feature is always used
-            const disallowed_feature = disallowed[@intFromEnum(feature.tag)];
-            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 '{'}'", .{wasm.objects.items[disallowed_feature >> 1].path});
-                try err.addNote("used in '{'}'", .{object.path});
-                valid_feature_set = false;
-            }
-
-            object_used_features[@intFromEnum(feature.tag)] = true;
-        }
 
-        // validate the linked object file has each required feature
-        for (required, 0..) |required_feature, feature_index| {
-            const is_required = @as(u1, @truncate(required_feature)) != 0;
-            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 '{'}'", .{wasm.objects.items[required_feature >> 1].path});
-                try err.addNote("missing in '{'}'", .{object.path});
-                valid_feature_set = false;
+    if (wasm.entry_name.unwrap()) |entry_name| {
+        if (wasm.object_function_imports.getPtr(entry_name)) |import| {
+            if (import.resolution != .unresolved) {
+                import.flags.exported = true;
+                wasm.entry_resolution = import.resolution;
             }
         }
     }
 
-    if (!valid_feature_set) {
-        return error.FlushFailure;
-    }
-
-    to_emit.* = allowed;
-}
-
-/// Creates synthetic linker-symbols, but only if they are being referenced from
-/// any object file. For instance, the `__heap_base` symbol will only be created,
-/// if one or multiple undefined references exist. When none exist, the symbol will
-/// not be created, ensuring we don't unnecessarily emit unreferenced symbols.
-fn resolveLazySymbols(wasm: *Wasm) !void {
-    const comp = wasm.base.comp;
-    const gpa = comp.gpa;
-    const shared_memory = comp.config.shared_memory;
-
-    if (wasm.getExistingString("__heap_base")) |name_offset| {
-        if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| {
-            const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data);
-            try wasm.discarded.putNoClobber(gpa, kv.value, loc);
-            _ = wasm.resolved_symbols.swapRemove(loc); // we don't want to emit this symbol, only use it for relocations.
+    // These loops do both recursive marking of alive symbols well as checking for undefined symbols.
+    // At the end, output functions and globals will be populated.
+    for (wasm.object_function_imports.keys(), wasm.object_function_imports.values(), 0..) |name, *import, i| {
+        if (import.flags.isIncluded(rdynamic)) {
+            try markFunction(wasm, name, import, @enumFromInt(i));
+            continue;
         }
     }
+    wasm.functions_len = @intCast(wasm.functions.items.len);
+    wasm.function_imports_init = try gpa.dupe(FunctionImportId, wasm.functions.keys());
+    wasm.function_exports_len = @intCast(wasm.function_exports.items.len);
 
-    if (wasm.getExistingString("__heap_end")) |name_offset| {
-        if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| {
-            const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data);
-            try wasm.discarded.putNoClobber(gpa, kv.value, loc);
-            _ = wasm.resolved_symbols.swapRemove(loc);
+    for (wasm.object_global_imports.keys(), wasm.object_global_imports.values(), 0..) |name, *import, i| {
+        if (import.flags.isIncluded(rdynamic)) {
+            try markGlobal(wasm, name, import, @enumFromInt(i));
+            continue;
         }
     }
+    wasm.globals_len = @intCast(wasm.globals.items.len);
+    wasm.global_imports_init = try gpa.dupe(GlobalImportId, wasm.globals.keys());
+    wasm.global_exports_len = @intCast(wasm.global_exports.items.len);
 
-    if (!shared_memory) {
-        if (wasm.getExistingString("__tls_base")) |name_offset| {
-            if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| {
-                const loc = try wasm.createSyntheticSymbolOffset(name_offset, .global);
-                try wasm.discarded.putNoClobber(gpa, kv.value, loc);
-                _ = wasm.resolved_symbols.swapRemove(kv.value);
-                const symbol = wasm.symbolLocSymbol(loc);
-                symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
-                symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len);
-                try wasm.wasm_globals.append(gpa, .{
-                    .global_type = .{ .valtype = .i32, .mutable = true },
-                    .init = .{ .i32_const = undefined },
-                });
-            }
+    for (wasm.object_table_imports.keys(), wasm.object_table_imports.values(), 0..) |name, *import, i| {
+        if (import.flags.isIncluded(rdynamic)) {
+            try markTable(wasm, name, import, @enumFromInt(i));
+            continue;
         }
     }
+    wasm.tables_len = @intCast(wasm.tables.items.len);
 }
 
-pub fn findGlobalSymbol(wasm: *const Wasm, name: []const u8) ?SymbolLoc {
-    const name_index = wasm.getExistingString(name) orelse return null;
-    return wasm.globals.get(name_index);
-}
+/// Recursively mark alive everything referenced by the function.
+fn markFunction(
+    wasm: *Wasm,
+    name: String,
+    import: *FunctionImport,
+    func_index: ObjectFunctionImportIndex,
+) error{OutOfMemory}!void {
+    if (import.flags.alive) return;
+    import.flags.alive = true;
 
-fn checkUndefinedSymbols(wasm: *const Wasm) !void {
     const comp = wasm.base.comp;
-    const diags = &wasm.base.comp.link_diags;
-    if (comp.config.output_mode == .Obj) return;
-    if (wasm.import_symbols) return;
-
-    var found_undefined_symbols = false;
-    for (wasm.undefs.values()) |undef| {
-        const symbol = wasm.symbolLocSymbol(undef);
-        if (symbol.tag == .data) {
-            found_undefined_symbols = true;
-            const symbol_name = wasm.symbolLocName(undef);
-            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.LinkFailure;
-    }
-}
-
-pub fn deinit(wasm: *Wasm) void {
-    const gpa = wasm.base.comp.gpa;
-    if (wasm.llvm_object) |llvm_object| llvm_object.deinit();
-
-    for (wasm.func_types.items) |*func_type| {
-        func_type.deinit(gpa);
-    }
-    for (wasm.segment_info.values()) |segment_info| {
-        gpa.free(segment_info.name);
-    }
-    if (wasm.zig_object) |zig_obj| {
-        zig_obj.deinit(wasm);
-    }
-    for (wasm.objects.items) |*object| {
-        object.deinit(gpa);
-    }
-
-    for (wasm.lazy_archives.items) |*lazy_archive| lazy_archive.deinit(gpa);
-    wasm.lazy_archives.deinit(gpa);
-
-    if (wasm.globals.get(wasm.preloaded_strings.__wasm_init_tls)) |loc| {
-        const atom = wasm.symbol_atom.get(loc).?;
-        wasm.getAtomPtr(atom).deinit(gpa);
-    }
-
-    wasm.synthetic_symbols.deinit(gpa);
-    wasm.globals.deinit(gpa);
-    wasm.resolved_symbols.deinit(gpa);
-    wasm.undefs.deinit(gpa);
-    wasm.discarded.deinit(gpa);
-    wasm.symbol_atom.deinit(gpa);
-    wasm.atoms.deinit(gpa);
-    wasm.managed_atoms.deinit(gpa);
-    wasm.segments.deinit(gpa);
-    wasm.data_segments.deinit(gpa);
-    wasm.segment_info.deinit(gpa);
-    wasm.objects.deinit(gpa);
-
-    // free output sections
-    wasm.imports.deinit(gpa);
-    wasm.func_types.deinit(gpa);
-    wasm.functions.deinit(gpa);
-    wasm.wasm_globals.deinit(gpa);
-    wasm.function_table.deinit(gpa);
-    wasm.tables.deinit(gpa);
-    wasm.init_funcs.deinit(gpa);
-    wasm.exports.deinit(gpa);
-
-    wasm.string_bytes.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 {
-    if (build_options.skip_non_native and builtin.object_format != .wasm) {
-        @panic("Attempted to compile for object format that was disabled by build configuration");
-    }
-    if (wasm.llvm_object) |llvm_object| return llvm_object.updateFunc(pt, func_index, air, liveness);
-    try wasm.zig_object.?.updateFunc(wasm, pt, func_index, air, liveness);
-}
-
-// Generate code for the "Nav", storing it in memory to be later written to
-// the file on flush().
-pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !void {
-    if (build_options.skip_non_native and builtin.object_format != .wasm) {
-        @panic("Attempted to compile for object format that was disabled by build configuration");
-    }
-    if (wasm.llvm_object) |llvm_object| return llvm_object.updateNav(pt, nav);
-    try wasm.zig_object.?.updateNav(wasm, pt, nav);
-}
+    const gpa = comp.gpa;
+    const rdynamic = comp.config.rdynamic;
+    const is_obj = comp.config.output_mode == .Obj;
 
-pub fn updateLineNumber(wasm: *Wasm, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void {
-    if (wasm.llvm_object) |_| return;
-    try wasm.zig_object.?.updateLineNumber(pt, ti_id);
-}
+    try wasm.functions.ensureUnusedCapacity(gpa, 1);
+
+    if (import.resolution == .unresolved) {
+        if (name == wasm.preloaded_strings.__wasm_init_memory) {
+            import.resolution = .__wasm_init_memory;
+            wasm.functions.putAssumeCapacity(.__wasm_init_memory, {});
+        } else if (name == wasm.preloaded_strings.__wasm_apply_global_tls_relocs) {
+            import.resolution = .__wasm_apply_global_tls_relocs;
+            wasm.functions.putAssumeCapacity(.__wasm_apply_global_tls_relocs, {});
+        } else if (name == wasm.preloaded_strings.__wasm_call_ctors) {
+            import.resolution = .__wasm_call_ctors;
+            wasm.functions.putAssumeCapacity(.__wasm_call_ctors, {});
+        } else if (name == wasm.preloaded_strings.__wasm_init_tls) {
+            import.resolution = .__wasm_init_tls;
+            wasm.functions.putAssumeCapacity(.__wasm_init_tls, {});
+        } else {
+            try wasm.function_imports.put(gpa, .fromObject(func_index), {});
+        }
+    } else {
+        const gop = wasm.functions.getOrPutAssumeCapacity(import.resolution);
 
-/// From a given symbol location, returns its `wasm.GlobalType`.
-/// Asserts the Symbol represents a global.
-fn getGlobalType(wasm: *const Wasm, loc: SymbolLoc) std.wasm.GlobalType {
-    const symbol = wasm.symbolLocSymbol(loc);
-    assert(symbol.tag == .global);
-    const is_undefined = symbol.isUndefined();
-    switch (loc.file) {
-        .zig_object => {
-            const zo = wasm.zig_object.?;
-            return if (is_undefined)
-                zo.imports.get(loc.index).?.kind.global
-            else
-                zo.globals.items[symbol.index - zo.imported_globals_count].global_type;
-        },
-        .none => {
-            return if (is_undefined)
-                wasm.imports.get(loc).?.kind.global
-            else
-                wasm.wasm_globals.items[symbol.index].global_type;
-        },
-        _ => {
-            const obj = &wasm.objects.items[@intFromEnum(loc.file)];
-            return if (is_undefined)
-                obj.findImport(obj.symtable[@intFromEnum(loc.index)]).kind.global
-            else
-                obj.globals[symbol.index - obj.imported_globals_count].global_type;
-        },
-    }
-}
+        if (!is_obj and import.flags.isExported(rdynamic))
+            try wasm.function_exports.append(gpa, @intCast(gop.index));
 
-/// From a given symbol location, returns its `wasm.Type`.
-/// Asserts the Symbol represents a function.
-fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type {
-    const symbol = wasm.symbolLocSymbol(loc);
-    assert(symbol.tag == .function);
-    const is_undefined = symbol.isUndefined();
-    switch (loc.file) {
-        .zig_object => {
-            const zo = wasm.zig_object.?;
-            if (is_undefined) {
-                const type_index = zo.imports.get(loc.index).?.kind.function;
-                return zo.func_types.items[type_index];
-            }
-            const sym = zo.symbols.items[@intFromEnum(loc.index)];
-            const type_index = zo.functions.items[sym.index].type_index;
-            return zo.func_types.items[type_index];
-        },
-        .none => {
-            if (is_undefined) {
-                const type_index = wasm.imports.get(loc).?.kind.function;
-                return wasm.func_types.items[type_index];
-            }
-            return wasm.func_types.items[
-                wasm.functions.get(.{
-                    .file = .none,
-                    .index = symbol.index,
-                }).?.func.type_index
-            ];
-        },
-        _ => {
-            const obj = &wasm.objects.items[@intFromEnum(loc.file)];
-            if (is_undefined) {
-                const type_index = obj.findImport(obj.symtable[@intFromEnum(loc.index)]).kind.function;
-                return obj.func_types[type_index];
-            }
-            const sym = obj.symtable[@intFromEnum(loc.index)];
-            const type_index = obj.functions[sym.index - obj.imported_functions_count].type_index;
-            return obj.func_types[type_index];
-        },
+        for (wasm.functionResolutionRelocSlice(import.resolution)) |reloc|
+            try wasm.markReloc(reloc);
     }
 }
 
-/// Returns the symbol index from a symbol of which its flag is set global,
-/// such as an exported or imported symbol.
-/// If the symbol does not yet exist, creates a new one symbol instead
-/// and then returns the index to it.
-pub fn getGlobalSymbol(wasm: *Wasm, name: []const u8, lib_name: ?[]const u8) !Symbol.Index {
-    _ = lib_name;
-    const name_index = try wasm.internString(name);
-    return wasm.zig_object.?.getGlobalSymbol(wasm.base.comp.gpa, name_index);
-}
-
-/// For a given `Nav`, find the given symbol index's atom, and create a relocation for the type.
-/// Returns the given pointer address
-pub fn getNavVAddr(
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    nav: InternPool.Nav.Index,
-    reloc_info: link.File.RelocInfo,
-) !u64 {
-    return wasm.zig_object.?.getNavVAddr(wasm, pt, nav, reloc_info);
-}
-
-pub fn lowerUav(
-    wasm: *Wasm,
-    pt: Zcu.PerThread,
-    uav: InternPool.Index,
-    explicit_alignment: Alignment,
-    src_loc: Zcu.LazySrcLoc,
-) !codegen.GenResult {
-    return wasm.zig_object.?.lowerUav(wasm, pt, uav, explicit_alignment, src_loc);
-}
-
-pub fn getUavVAddr(wasm: *Wasm, uav: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 {
-    return wasm.zig_object.?.getUavVAddr(wasm, uav, reloc_info);
-}
-
-pub fn deleteExport(
-    wasm: *Wasm,
-    exported: Zcu.Exported,
-    name: InternPool.NullTerminatedString,
-) void {
-    if (wasm.llvm_object) |_| return;
-    return wasm.zig_object.?.deleteExport(wasm, exported, name);
-}
-
-pub fn updateExports(
+/// Recursively mark alive everything referenced by the global.
+fn markGlobal(
     wasm: *Wasm,
-    pt: Zcu.PerThread,
-    exported: Zcu.Exported,
-    export_indices: []const u32,
+    name: String,
+    import: *GlobalImport,
+    global_index: ObjectGlobalImportIndex,
 ) !void {
-    if (build_options.skip_non_native and builtin.object_format != .wasm) {
-        @panic("Attempted to compile for object format that was disabled by build configuration");
-    }
-    if (wasm.llvm_object) |llvm_object| return llvm_object.updateExports(pt, exported, export_indices);
-    return wasm.zig_object.?.updateExports(wasm, pt, exported, export_indices);
-}
+    if (import.flags.alive) return;
+    import.flags.alive = true;
 
-pub fn freeDecl(wasm: *Wasm, decl_index: InternPool.DeclIndex) void {
-    if (wasm.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index);
-    return wasm.zig_object.?.freeDecl(wasm, decl_index);
-}
+    const comp = wasm.base.comp;
+    const gpa = comp.gpa;
+    const rdynamic = comp.config.rdynamic;
+    const is_obj = comp.config.output_mode == .Obj;
 
-/// Assigns indexes to all indirect functions.
-/// Starts at offset 1, where the value `0` represents an unresolved function pointer
-/// or null-pointer
-fn mapFunctionTable(wasm: *Wasm) void {
-    var it = wasm.function_table.iterator();
-    var index: u32 = 1;
-    while (it.next()) |entry| {
-        const symbol = wasm.symbolLocSymbol(entry.key_ptr.*);
-        if (symbol.isAlive()) {
-            entry.value_ptr.* = index;
-            index += 1;
+    try wasm.globals.ensureUnusedCapacity(gpa, 1);
+
+    if (import.resolution == .unresolved) {
+        if (name == wasm.preloaded_strings.__heap_base) {
+            import.resolution = .__heap_base;
+            wasm.globals.putAssumeCapacity(.__heap_base, {});
+        } else if (name == wasm.preloaded_strings.__heap_end) {
+            import.resolution = .__heap_end;
+            wasm.globals.putAssumeCapacity(.__heap_end, {});
+        } else if (name == wasm.preloaded_strings.__stack_pointer) {
+            import.resolution = .__stack_pointer;
+            wasm.globals.putAssumeCapacity(.__stack_pointer, {});
+        } else if (name == wasm.preloaded_strings.__tls_align) {
+            import.resolution = .__tls_align;
+            wasm.globals.putAssumeCapacity(.__tls_align, {});
+        } else if (name == wasm.preloaded_strings.__tls_base) {
+            import.resolution = .__tls_base;
+            wasm.globals.putAssumeCapacity(.__tls_base, {});
+        } else if (name == wasm.preloaded_strings.__tls_size) {
+            import.resolution = .__tls_size;
+            wasm.globals.putAssumeCapacity(.__tls_size, {});
         } else {
-            wasm.function_table.removeByPtr(entry.key_ptr);
+            try wasm.global_imports.put(gpa, .fromObject(global_index), {});
         }
-    }
-
-    if (wasm.import_table or wasm.base.comp.config.output_mode == .Obj) {
-        const sym_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?;
-        const import = wasm.imports.getPtr(sym_loc).?;
-        import.kind.table.limits.min = index - 1; // we start at index 1.
-    } else if (index > 1) {
-        log.debug("Appending indirect function table", .{});
-        const sym_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?;
-        const symbol = wasm.symbolLocSymbol(sym_loc);
-        const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
-        table.limits = .{ .min = index, .max = index, .flags = 0x1 };
-    }
-}
-
-/// From a given index, append the given `Atom` at the back of the linked list.
-/// Simply inserts it into the map of atoms when it doesn't exist yet.
-pub fn appendAtomAtIndex(wasm: *Wasm, index: Segment.Index, atom_index: Atom.Index) !void {
-    const gpa = wasm.base.comp.gpa;
-    const atom = wasm.getAtomPtr(atom_index);
-    if (wasm.atoms.getPtr(index)) |last_index_ptr| {
-        atom.prev = last_index_ptr.*;
-        last_index_ptr.* = atom_index;
     } else {
-        try wasm.atoms.putNoClobber(gpa, index, atom_index);
-    }
-}
-
-fn allocateAtoms(wasm: *Wasm) !void {
-    // first sort the data segments
-    try sortDataSegments(wasm);
-
-    var it = wasm.atoms.iterator();
-    while (it.next()) |entry| {
-        const segment = wasm.segmentPtr(entry.key_ptr.*);
-        var atom_index = entry.value_ptr.*;
-        if (entry.key_ptr.toOptional() == wasm.code_section_index) {
-            // Code section is allocated upon writing as they are required to be ordered
-            // to synchronise with the function section.
-            continue;
-        }
-        var offset: u32 = 0;
-        while (true) {
-            const atom = wasm.getAtomPtr(atom_index);
-            const symbol_loc = atom.symbolLoc();
-            // Ensure we get the original symbol, so we verify the correct symbol on whether
-            // it is dead or not and ensure an atom is removed when dead.
-            // This is required as we may have parsed aliases into atoms.
-            const sym = switch (symbol_loc.file) {
-                .zig_object => wasm.zig_object.?.symbols.items[@intFromEnum(symbol_loc.index)],
-                .none => wasm.synthetic_symbols.items[@intFromEnum(symbol_loc.index)],
-                _ => wasm.objects.items[@intFromEnum(symbol_loc.file)].symtable[@intFromEnum(symbol_loc.index)],
-            };
-
-            // Dead symbols must be unlinked from the linked-list to prevent them
-            // from being emit into the binary.
-            if (sym.isDead()) {
-                if (entry.value_ptr.* == atom_index and atom.prev != .null) {
-                    // When the atom is dead and is also the first atom retrieved from wasm.atoms(index) we update
-                    // the entry to point it to the previous atom to ensure we do not start with a dead symbol that
-                    // was removed and therefore do not emit any code at all.
-                    entry.value_ptr.* = atom.prev;
-                }
-                if (atom.prev == .null) break;
-                atom_index = atom.prev;
-                atom.prev = .null;
-                continue;
-            }
-            offset = @intCast(atom.alignment.forward(offset));
-            atom.offset = offset;
-            log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{
-                wasm.symbolLocName(symbol_loc),
-                offset,
-                offset + atom.size,
-                atom.size,
-            });
-            offset += atom.size;
-            if (atom.prev == .null) break;
-            atom_index = atom.prev;
-        }
-        segment.size = @intCast(segment.alignment.forward(offset));
-    }
-}
+        const gop = wasm.globals.getOrPutAssumeCapacity(import.resolution);
 
-/// For each data symbol, sets the virtual address.
-fn allocateVirtualAddresses(wasm: *Wasm) void {
-    for (wasm.resolved_symbols.keys()) |loc| {
-        const symbol = wasm.symbolLocSymbol(loc);
-        if (symbol.tag != .data or symbol.isDead()) {
-            // Only data symbols have virtual addresses.
-            // Dead symbols do not get allocated, so we don't need to set their virtual address either.
-            continue;
-        }
-        const atom_index = wasm.symbol_atom.get(loc) orelse {
-            // synthetic symbol that does not contain an atom
-            continue;
-        };
+        if (!is_obj and import.flags.isExported(rdynamic))
+            try wasm.global_exports.append(gpa, @intCast(gop.index));
 
-        const atom = wasm.getAtom(atom_index);
-        const merge_segment = wasm.base.comp.config.output_mode != .Obj;
-        const segment_info = switch (atom.file) {
-            .zig_object => wasm.zig_object.?.segment_info.items,
-            .none => wasm.segment_info.values(),
-            _ => wasm.objects.items[@intFromEnum(atom.file)].segment_info,
-        };
-        const segment_name = segment_info[symbol.index].outputName(merge_segment);
-        const segment_index = wasm.data_segments.get(segment_name).?;
-        const segment = wasm.segmentPtr(segment_index);
-
-        // TLS symbols have their virtual address set relative to their own TLS segment,
-        // rather than the entire Data section.
-        if (symbol.hasFlag(.WASM_SYM_TLS)) {
-            symbol.virtual_address = atom.offset;
-        } else {
-            symbol.virtual_address = atom.offset + segment.offset;
-        }
+        for (wasm.globalResolutionRelocSlice(import.resolution)) |reloc|
+            try wasm.markReloc(reloc);
     }
 }
 
-fn sortDataSegments(wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-    var new_mapping: std.StringArrayHashMapUnmanaged(Segment.Index) = .empty;
-    try new_mapping.ensureUnusedCapacity(gpa, wasm.data_segments.count());
-    errdefer new_mapping.deinit(gpa);
-
-    const keys = try gpa.dupe([]const u8, wasm.data_segments.keys());
-    defer gpa.free(keys);
-
-    const SortContext = struct {
-        fn sort(_: void, lhs: []const u8, rhs: []const u8) bool {
-            return order(lhs) < order(rhs);
-        }
+fn markTable(
+    wasm: *Wasm,
+    name: String,
+    import: *TableImport,
+    table_index: ObjectTableImportIndex,
+) !void {
+    if (import.flags.alive) return;
+    import.flags.alive = true;
 
-        fn order(name: []const u8) u8 {
-            if (mem.startsWith(u8, name, ".rodata")) return 0;
-            if (mem.startsWith(u8, name, ".data")) return 1;
-            if (mem.startsWith(u8, name, ".text")) return 2;
-            return 3;
-        }
-    };
+    const comp = wasm.base.comp;
+    const gpa = comp.gpa;
 
-    mem.sort([]const u8, keys, {}, SortContext.sort);
-    for (keys) |key| {
-        const segment_index = wasm.data_segments.get(key).?;
-        new_mapping.putAssumeCapacity(key, segment_index);
-    }
-    wasm.data_segments.deinit(gpa);
-    wasm.data_segments = new_mapping;
-}
+    try wasm.tables.ensureUnusedCapacity(gpa, 1);
 
-/// Obtains all initfuncs from each object file, verifies its function signature,
-/// and then appends it to our final `init_funcs` list.
-/// After all functions have been inserted, the functions will be ordered based
-/// on their priority.
-/// NOTE: This function must be called before we merged any other section.
-/// This is because all init funcs in the object files contain references to the
-/// original functions and their types. We need to know the type to verify it doesn't
-/// contain any parameters.
-fn setupInitFunctions(wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-    const diags = &wasm.base.comp.link_diags;
-    // There's no constructors for Zig so we can simply search through linked object files only.
-    for (wasm.objects.items, 0..) |*object, object_index| {
-        try wasm.init_funcs.ensureUnusedCapacity(gpa, object.init_funcs.len);
-        for (object.init_funcs) |init_func| {
-            const symbol = object.symtable[init_func.symbol_index];
-            const ty: std.wasm.Type = if (symbol.isUndefined()) ty: {
-                const imp: Import = object.findImport(symbol);
-                break :ty object.func_types[imp.kind.function];
-            } else ty: {
-                const func_index = symbol.index - object.imported_functions_count;
-                const func = object.functions[func_index];
-                break :ty object.func_types[func.type_index];
-            };
-            if (ty.params.len != 0) {
-                var err = try diags.addErrorWithNotes(0);
-                try err.addMsg("constructor functions cannot take arguments: '{s}'", .{wasm.stringSlice(symbol.name)});
-            }
-            log.debug("appended init func '{s}'\n", .{wasm.stringSlice(symbol.name)});
-            wasm.init_funcs.appendAssumeCapacity(.{
-                .index = @enumFromInt(init_func.symbol_index),
-                .file = @enumFromInt(object_index),
-                .priority = init_func.priority,
-            });
-            try wasm.mark(.{
-                .index = @enumFromInt(init_func.symbol_index),
-                .file = @enumFromInt(object_index),
-            });
+    if (import.resolution == .unresolved) {
+        if (name == wasm.preloaded_strings.__indirect_function_table) {
+            import.resolution = .__indirect_function_table;
+            wasm.tables.putAssumeCapacity(.__indirect_function_table, {});
+        } else {
+            try wasm.table_imports.put(gpa, .fromObject(table_index), {});
         }
-    }
-
-    // sort the initfunctions based on their priority
-    mem.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
-
-    if (wasm.init_funcs.items.len > 0) {
-        const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_call_ctors).?;
-        try wasm.mark(loc);
+    } else {
+        wasm.tables.putAssumeCapacity(import.resolution, {});
+        // Tables have no relocations.
     }
 }
 
-/// Creates a function body for the `__wasm_call_ctors` symbol.
-/// Loops over all constructors found in `init_funcs` and calls them
-/// respectively based on their priority which was sorted by `setupInitFunctions`.
-/// NOTE: This function must be called after we merged all sections to ensure the
-/// references to the function stored in the symbol have been finalized so we end
-/// up calling the resolved function.
-fn initializeCallCtorsFunction(wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-    // No code to emit, so also no ctors to call
-    if (wasm.code_section_index == .none) {
-        // Make sure to remove it from the resolved symbols so we do not emit
-        // it within any section. TODO: Remove this once we implement garbage collection.
-        const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_call_ctors).?;
-        assert(wasm.resolved_symbols.swapRemove(loc));
-        return;
-    }
-
-    var function_body = std.ArrayList(u8).init(gpa);
-    defer function_body.deinit();
-    const writer = function_body.writer();
-
-    // Create the function body
-    {
-        // Write locals count (we have none)
-        try leb.writeUleb128(writer, @as(u32, 0));
-
-        // call constructors
-        for (wasm.init_funcs.items) |init_func_loc| {
-            const symbol = init_func_loc.getSymbol(wasm);
-            const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count].func;
-            const ty = wasm.func_types.items[func.type_index];
-
-            // Call function by its function index
-            try writer.writeByte(std.wasm.opcode(.call));
-            try leb.writeUleb128(writer, symbol.index);
-
-            // drop all returned values from the stack as __wasm_call_ctors has no return value
-            for (ty.returns) |_| {
-                try writer.writeByte(std.wasm.opcode(.drop));
-            }
-        }
-
-        // End function body
-        try writer.writeByte(std.wasm.opcode(.end));
-    }
-
-    try wasm.createSyntheticFunction(
-        wasm.preloaded_strings.__wasm_call_ctors,
-        std.wasm.Type{ .params = &.{}, .returns = &.{} },
-        &function_body,
-    );
+fn globalResolutionRelocSlice(wasm: *Wasm, resolution: GlobalImport.Resolution) ![]const Relocation {
+    assert(resolution != .none);
+    _ = wasm;
+    @panic("TODO");
 }
 
-fn createSyntheticFunction(
-    wasm: *Wasm,
-    symbol_name: String,
-    func_ty: std.wasm.Type,
-    function_body: *std.ArrayList(u8),
-) !void {
-    const gpa = wasm.base.comp.gpa;
-    const loc = wasm.globals.get(symbol_name).?;
-    const symbol = wasm.symbolLocSymbol(loc);
-    if (symbol.isDead()) {
-        return;
-    }
-    const ty_index = try wasm.putOrGetFuncType(func_ty);
-    // create function with above type
-    const func_index = wasm.imported_functions_count + @as(u32, @intCast(wasm.functions.count()));
-    try wasm.functions.putNoClobber(
-        gpa,
-        .{ .file = .none, .index = func_index },
-        .{ .func = .{ .type_index = ty_index }, .sym_index = loc.index },
-    );
-    symbol.index = func_index;
-
-    // create the atom that will be output into the final binary
-    const atom_index = try wasm.createAtom(loc.index, .none);
-    const atom = wasm.getAtomPtr(atom_index);
-    atom.size = @intCast(function_body.items.len);
-    atom.code = function_body.moveToUnmanaged();
-    try wasm.appendAtomAtIndex(wasm.code_section_index.unwrap().?, atom_index);
+fn functionResolutionRelocSlice(wasm: *Wasm, resolution: FunctionImport.Resolution) ![]const Relocation {
+    assert(resolution != .none);
+    _ = wasm;
+    @panic("TODO");
 }
 
-/// Unlike `createSyntheticFunction` this function is to be called by
-/// the codegeneration backend. This will not allocate the created Atom yet.
-/// Returns the index of the symbol.
-pub fn createFunction(
+pub fn flushModule(
     wasm: *Wasm,
-    symbol_name: []const u8,
-    func_ty: std.wasm.Type,
-    function_body: *std.ArrayList(u8),
-    relocations: *std.ArrayList(Relocation),
-) !Symbol.Index {
-    return wasm.zig_object.?.createFunction(wasm, symbol_name, func_ty, function_body, relocations);
-}
-
-/// If required, sets the function index in the `start` section.
-fn setupStartSection(wasm: *Wasm) !void {
-    if (wasm.globals.get(wasm.preloaded_strings.__wasm_init_memory)) |loc| {
-        wasm.entry = wasm.symbolLocSymbol(loc).index;
-    }
-}
-
-fn initializeTLSFunction(wasm: *Wasm) !void {
-    const comp = wasm.base.comp;
-    const gpa = comp.gpa;
-    const shared_memory = comp.config.shared_memory;
-
-    if (!shared_memory) return;
-
-    // ensure function is marked as we must emit it
-    wasm.symbolLocSymbol(wasm.globals.get(wasm.preloaded_strings.__wasm_init_tls).?).mark();
-
-    var function_body = std.ArrayList(u8).init(gpa);
-    defer function_body.deinit();
-    const writer = function_body.writer();
-
-    // locals
-    try writer.writeByte(0);
-
-    // If there's a TLS segment, initialize it during runtime using the bulk-memory feature
-    if (wasm.data_segments.getIndex(".tdata")) |data_index| {
-        const segment_index = wasm.data_segments.entries.items(.value)[data_index];
-        const segment = wasm.segmentPtr(segment_index);
-
-        const param_local: u32 = 0;
-
-        try writer.writeByte(std.wasm.opcode(.local_get));
-        try leb.writeUleb128(writer, param_local);
-
-        const tls_base_loc = wasm.globals.get(wasm.preloaded_strings.__tls_base).?;
-        try writer.writeByte(std.wasm.opcode(.global_set));
-        try leb.writeUleb128(writer, wasm.symbolLocSymbol(tls_base_loc).index);
-
-        // load stack values for the bulk-memory operation
-        {
-            try writer.writeByte(std.wasm.opcode(.local_get));
-            try leb.writeUleb128(writer, param_local);
-
-            try writer.writeByte(std.wasm.opcode(.i32_const));
-            try leb.writeUleb128(writer, @as(u32, 0)); //segment offset
-
-            try writer.writeByte(std.wasm.opcode(.i32_const));
-            try leb.writeUleb128(writer, @as(u32, segment.size)); //segment offset
-        }
-
-        // perform the bulk-memory operation to initialize the data segment
-        try writer.writeByte(std.wasm.opcode(.misc_prefix));
-        try leb.writeUleb128(writer, std.wasm.miscOpcode(.memory_init));
-        // segment immediate
-        try leb.writeUleb128(writer, @as(u32, @intCast(data_index)));
-        // memory index immediate (always 0)
-        try leb.writeUleb128(writer, @as(u32, 0));
-    }
-
-    // If we have to perform any TLS relocations, call the corresponding function
-    // which performs all runtime TLS relocations. This is a synthetic function,
-    // generated by the linker.
-    if (wasm.globals.get(wasm.preloaded_strings.__wasm_apply_global_tls_relocs)) |loc| {
-        try writer.writeByte(std.wasm.opcode(.call));
-        try leb.writeUleb128(writer, wasm.symbolLocSymbol(loc).index);
-        wasm.symbolLocSymbol(loc).mark();
-    }
-
-    try writer.writeByte(std.wasm.opcode(.end));
-
-    try wasm.createSyntheticFunction(
-        wasm.preloaded_strings.__wasm_init_tls,
-        std.wasm.Type{ .params = &.{.i32}, .returns = &.{} },
-        &function_body,
-    );
-}
-
-fn setupImports(wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-    log.debug("Merging imports", .{});
-    for (wasm.resolved_symbols.keys()) |symbol_loc| {
-        const object_id = symbol_loc.file.unwrap() orelse {
-            // Synthetic symbols will already exist in the `import` section
-            continue;
-        };
-
-        const symbol = wasm.symbolLocSymbol(symbol_loc);
-        if (symbol.isDead()) continue;
-        if (!symbol.requiresImport()) continue;
-        if (symbol.name == wasm.preloaded_strings.__indirect_function_table) continue;
-
-        log.debug("Symbol '{s}' will be imported from the host", .{wasm.stringSlice(symbol.name)});
-        const import = objectImport(wasm, object_id, symbol_loc.index);
-
-        // We copy the import to a new import to ensure the names contain references
-        // to the internal string table, rather than of the object file.
-        const new_imp: Import = .{
-            .module_name = import.module_name,
-            .name = import.name,
-            .kind = import.kind,
-        };
-        // TODO: De-duplicate imports when they contain the same names and type
-        try wasm.imports.putNoClobber(gpa, symbol_loc, new_imp);
-    }
-
-    // Assign all indexes of the imports to their representing symbols
-    var function_index: u32 = 0;
-    var global_index: u32 = 0;
-    var table_index: u32 = 0;
-    var it = wasm.imports.iterator();
-    while (it.next()) |entry| {
-        const symbol = wasm.symbolLocSymbol(entry.key_ptr.*);
-        const import: Import = entry.value_ptr.*;
-        switch (import.kind) {
-            .function => {
-                symbol.index = function_index;
-                function_index += 1;
-            },
-            .global => {
-                symbol.index = global_index;
-                global_index += 1;
-            },
-            .table => {
-                symbol.index = table_index;
-                table_index += 1;
-            },
-            else => unreachable,
-        }
-    }
-    wasm.imported_functions_count = function_index;
-    wasm.imported_globals_count = global_index;
-    wasm.imported_tables_count = table_index;
-
-    log.debug("Merged ({d}) functions, ({d}) globals, and ({d}) tables into import section", .{
-        function_index,
-        global_index,
-        table_index,
-    });
-}
-
-/// Takes the global, function and table section from each linked object file
-/// and merges it into a single section for each.
-fn mergeSections(wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-
-    var removed_duplicates = std.ArrayList(SymbolLoc).init(gpa);
-    defer removed_duplicates.deinit();
-
-    for (wasm.resolved_symbols.keys()) |sym_loc| {
-        const object_id = sym_loc.file.unwrap() orelse {
-            // Synthetic symbols already live in the corresponding sections.
-            continue;
-        };
-
-        const symbol = objectSymbol(wasm, object_id, sym_loc.index);
-        if (symbol.isDead() or symbol.isUndefined()) {
-            // Skip undefined symbols as they go in the `import` section
-            continue;
-        }
-
-        switch (symbol.tag) {
-            .function => {
-                const gop = try wasm.functions.getOrPut(
-                    gpa,
-                    .{ .file = sym_loc.file, .index = symbol.index },
-                );
-                if (gop.found_existing) {
-                    // We found an alias to the same function, discard this symbol in favor of
-                    // the original symbol and point the discard function to it. This ensures
-                    // we only emit a single function, instead of duplicates.
-                    // we favor keeping the global over a local.
-                    const original_loc: SymbolLoc = .{ .file = gop.key_ptr.file, .index = gop.value_ptr.sym_index };
-                    const original_sym = wasm.symbolLocSymbol(original_loc);
-                    if (original_sym.isLocal() and symbol.isGlobal()) {
-                        original_sym.unmark();
-                        try wasm.discarded.put(gpa, original_loc, sym_loc);
-                        try removed_duplicates.append(original_loc);
-                    } else {
-                        symbol.unmark();
-                        try wasm.discarded.putNoClobber(gpa, sym_loc, original_loc);
-                        try removed_duplicates.append(sym_loc);
-                        continue;
-                    }
-                }
-                gop.value_ptr.* = .{
-                    .func = objectFunction(wasm, object_id, sym_loc.index),
-                    .sym_index = sym_loc.index,
-                };
-                symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count;
-            },
-            .global => {
-                const index = symbol.index - objectImportedFunctions(wasm, object_id);
-                const original_global = objectGlobals(wasm, object_id)[index];
-                symbol.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count;
-                try wasm.wasm_globals.append(gpa, original_global);
-            },
-            .table => {
-                const index = symbol.index - objectImportedFunctions(wasm, object_id);
-                // assert it's a regular relocatable object file as `ZigObject` will never
-                // contain a table.
-                const original_table = wasm.objectById(object_id).?.tables[index];
-                symbol.index = @as(u32, @intCast(wasm.tables.items.len)) + wasm.imported_tables_count;
-                try wasm.tables.append(gpa, original_table);
-            },
-            .dead, .undefined => unreachable,
-            else => {},
-        }
-    }
-
-    // For any removed duplicates, remove them from the resolved symbols list
-    for (removed_duplicates.items) |sym_loc| {
-        assert(wasm.resolved_symbols.swapRemove(sym_loc));
-        gc_log.debug("Removed duplicate for function '{s}'", .{wasm.symbolLocName(sym_loc)});
-    }
-
-    log.debug("Merged ({d}) functions", .{wasm.functions.count()});
-    log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len});
-    log.debug("Merged ({d}) tables", .{wasm.tables.items.len});
-}
-
-/// Merges function types of all object files into the final
-/// 'types' section, while assigning the type index to the representing
-/// section (import, export, function).
-fn mergeTypes(wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-    // A map to track which functions have already had their
-    // type inserted. If we do this for the same function multiple times,
-    // it will be overwritten with the incorrect type.
-    var dirty = std.AutoHashMap(u32, void).init(gpa);
-    try dirty.ensureUnusedCapacity(@as(u32, @intCast(wasm.functions.count())));
-    defer dirty.deinit();
-
-    for (wasm.resolved_symbols.keys()) |sym_loc| {
-        const object_id = sym_loc.file.unwrap() orelse {
-            // zig code-generated symbols are already present in final type section
-            continue;
-        };
-
-        const symbol = objectSymbol(wasm, object_id, sym_loc.index);
-        if (symbol.tag != .function or symbol.isDead()) {
-            // Only functions have types. Only retrieve the type of referenced functions.
-            continue;
-        }
-
-        if (symbol.isUndefined()) {
-            log.debug("Adding type from extern function '{s}'", .{wasm.symbolLocName(sym_loc)});
-            const import: *Import = wasm.imports.getPtr(sym_loc) orelse continue;
-            const original_type = objectFuncTypes(wasm, object_id)[import.kind.function];
-            import.kind.function = try wasm.putOrGetFuncType(original_type);
-        } else if (!dirty.contains(symbol.index)) {
-            log.debug("Adding type from function '{s}'", .{wasm.symbolLocName(sym_loc)});
-            const func = &wasm.functions.values()[symbol.index - wasm.imported_functions_count].func;
-            func.type_index = try wasm.putOrGetFuncType(objectFuncTypes(wasm, object_id)[func.type_index]);
-            dirty.putAssumeCapacityNoClobber(symbol.index, {});
-        }
-    }
-    log.debug("Completed merging and deduplicating types. Total count: ({d})", .{wasm.func_types.items.len});
-}
-
-fn checkExportNames(wasm: *Wasm) !void {
-    const force_exp_names = wasm.export_symbol_names;
-    const diags = &wasm.base.comp.link_diags;
-    if (force_exp_names.len > 0) {
-        var failed_exports = false;
-
-        for (force_exp_names) |exp_name| {
-            const exp_name_interned = try wasm.internString(exp_name);
-            const loc = wasm.globals.get(exp_name_interned) orelse {
-                var err = try diags.addErrorWithNotes(0);
-                try err.addMsg("could not export '{s}', symbol not found", .{exp_name});
-                failed_exports = true;
-                continue;
-            };
-
-            const symbol = wasm.symbolLocSymbol(loc);
-            symbol.setFlag(.WASM_SYM_EXPORTED);
-        }
-
-        if (failed_exports) {
-            return error.FlushFailure;
-        }
-    }
-}
-
-fn setupExports(wasm: *Wasm) !void {
-    const comp = wasm.base.comp;
-    const gpa = comp.gpa;
-    if (comp.config.output_mode == .Obj) return;
-    log.debug("Building exports from symbols", .{});
-
-    for (wasm.resolved_symbols.keys()) |sym_loc| {
-        const symbol = wasm.symbolLocSymbol(sym_loc);
-        if (!symbol.isExported(comp.config.rdynamic)) continue;
-
-        const exp: Export = if (symbol.tag == .data) exp: {
-            const global_index = @as(u32, @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len));
-            try wasm.wasm_globals.append(gpa, .{
-                .global_type = .{ .valtype = .i32, .mutable = false },
-                .init = .{ .i32_const = @as(i32, @intCast(symbol.virtual_address)) },
-            });
-            break :exp .{
-                .name = symbol.name,
-                .kind = .global,
-                .index = global_index,
-            };
-        } else .{
-            .name = symbol.name,
-            .kind = symbol.tag.externalType(),
-            .index = symbol.index,
-        };
-        log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{
-            wasm.stringSlice(symbol.name),
-            wasm.stringSlice(exp.name),
-            exp.index,
-        });
-        try wasm.exports.append(gpa, exp);
-    }
-
-    log.debug("Completed building exports. Total count: ({d})", .{wasm.exports.items.len});
-}
-
-fn setupStart(wasm: *Wasm) !void {
-    const comp = wasm.base.comp;
-    const diags = &wasm.base.comp.link_diags;
-    // do not export entry point if user set none or no default was set.
-    const entry_name = wasm.entry_name.unwrap() orelse return;
-
-    const symbol_loc = wasm.globals.get(entry_name) orelse {
-        var err = try diags.addErrorWithNotes(1);
-        try err.addMsg("entry symbol '{s}' missing", .{wasm.stringSlice(entry_name)});
-        try err.addNote("'-fno-entry' suppresses this error", .{});
-        return error.LinkFailure;
-    };
-
-    const symbol = wasm.symbolLocSymbol(symbol_loc);
-    if (symbol.tag != .function)
-        return diags.fail("entry symbol '{s}' is not a function", .{wasm.stringSlice(entry_name)});
-
-    // Ensure the symbol is exported so host environment can access it
-    if (comp.config.output_mode != .Obj) {
-        symbol.setFlag(.WASM_SYM_EXPORTED);
-    }
-}
-
-/// Sets up the memory section of the wasm module, as well as the stack.
-fn setupMemory(wasm: *Wasm) !void {
-    const comp = wasm.base.comp;
-    const diags = &wasm.base.comp.link_diags;
-    const shared_memory = comp.config.shared_memory;
-    log.debug("Setting up memory layout", .{});
-    const page_size = std.wasm.page_size; // 64kb
-    const stack_alignment: Alignment = .@"16"; // wasm's stack alignment as specified by tool-convention
-    const heap_alignment: Alignment = .@"16"; // wasm's heap alignment as specified by tool-convention
-
-    // Always place the stack at the start by default
-    // unless the user specified the global-base flag
-    var place_stack_first = true;
-    var memory_ptr: u64 = if (wasm.global_base) |base| blk: {
-        place_stack_first = false;
-        break :blk base;
-    } else 0;
-
-    const is_obj = comp.config.output_mode == .Obj;
-
-    const stack_ptr = if (wasm.globals.get(wasm.preloaded_strings.__stack_pointer)) |loc| index: {
-        const sym = wasm.symbolLocSymbol(loc);
-        break :index sym.index - wasm.imported_globals_count;
-    } else null;
-
-    if (place_stack_first and !is_obj) {
-        memory_ptr = stack_alignment.forward(memory_ptr);
-        memory_ptr += wasm.base.stack_size;
-        // We always put the stack pointer global at index 0
-        if (stack_ptr) |index| {
-            wasm.wasm_globals.items[index].init.i32_const = @as(i32, @bitCast(@as(u32, @intCast(memory_ptr))));
-        }
-    }
-
-    var offset: u32 = @as(u32, @intCast(memory_ptr));
-    var data_seg_it = wasm.data_segments.iterator();
-    while (data_seg_it.next()) |entry| {
-        const segment = wasm.segmentPtr(entry.value_ptr.*);
-        memory_ptr = segment.alignment.forward(memory_ptr);
-
-        // set TLS-related symbols
-        if (mem.eql(u8, entry.key_ptr.*, ".tdata")) {
-            if (wasm.globals.get(wasm.preloaded_strings.__tls_size)) |loc| {
-                const sym = wasm.symbolLocSymbol(loc);
-                wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.size);
-            }
-            if (wasm.globals.get(wasm.preloaded_strings.__tls_align)) |loc| {
-                const sym = wasm.symbolLocSymbol(loc);
-                wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.alignment.toByteUnits().?);
-            }
-            if (wasm.globals.get(wasm.preloaded_strings.__tls_base)) |loc| {
-                const sym = wasm.symbolLocSymbol(loc);
-                wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = if (shared_memory)
-                    @as(i32, 0)
-                else
-                    @as(i32, @intCast(memory_ptr));
-            }
-        }
-
-        memory_ptr += segment.size;
-        segment.offset = offset;
-        offset += segment.size;
-    }
-
-    // create the memory init flag which is used by the init memory function
-    if (shared_memory and wasm.hasPassiveInitializationSegments()) {
-        // align to pointer size
-        memory_ptr = mem.alignForward(u64, memory_ptr, 4);
-        const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_memory_flag, .data);
-        const sym = wasm.symbolLocSymbol(loc);
-        sym.mark();
-        sym.virtual_address = @as(u32, @intCast(memory_ptr));
-        memory_ptr += 4;
-    }
-
-    if (!place_stack_first and !is_obj) {
-        memory_ptr = stack_alignment.forward(memory_ptr);
-        memory_ptr += wasm.base.stack_size;
-        if (stack_ptr) |index| {
-            wasm.wasm_globals.items[index].init.i32_const = @as(i32, @bitCast(@as(u32, @intCast(memory_ptr))));
-        }
-    }
-
-    // One of the linked object files has a reference to the __heap_base symbol.
-    // We must set its virtual address so it can be used in relocations.
-    if (wasm.globals.get(wasm.preloaded_strings.__heap_base)) |loc| {
-        const symbol = wasm.symbolLocSymbol(loc);
-        symbol.virtual_address = @intCast(heap_alignment.forward(memory_ptr));
-    }
-
-    // Setup the max amount of pages
-    // For now we only support wasm32 by setting the maximum allowed memory size 2^32-1
-    const max_memory_allowed: u64 = (1 << 32) - 1;
-
-    if (wasm.initial_memory) |initial_memory| {
-        if (!std.mem.isAlignedGeneric(u64, initial_memory, page_size)) {
-            var err = try diags.addErrorWithNotes(0);
-            try err.addMsg("Initial memory must be {d}-byte aligned", .{page_size});
-        }
-        if (memory_ptr > initial_memory) {
-            var err = try diags.addErrorWithNotes(0);
-            try err.addMsg("Initial memory too small, must be at least {d} bytes", .{memory_ptr});
-        }
-        if (initial_memory > max_memory_allowed) {
-            var err = try diags.addErrorWithNotes(0);
-            try err.addMsg("Initial memory exceeds maximum memory {d}", .{max_memory_allowed});
-        }
-        memory_ptr = initial_memory;
-    }
-    memory_ptr = mem.alignForward(u64, memory_ptr, std.wasm.page_size);
-    // In case we do not import memory, but define it ourselves,
-    // set the minimum amount of pages on the memory section.
-    wasm.memories.limits.min = @as(u32, @intCast(memory_ptr / page_size));
-    log.debug("Total memory pages: {d}", .{wasm.memories.limits.min});
-
-    if (wasm.globals.get(wasm.preloaded_strings.__heap_end)) |loc| {
-        const symbol = wasm.symbolLocSymbol(loc);
-        symbol.virtual_address = @as(u32, @intCast(memory_ptr));
-    }
-
-    if (wasm.max_memory) |max_memory| {
-        if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) {
-            var err = try diags.addErrorWithNotes(0);
-            try err.addMsg("Maximum memory must be {d}-byte aligned", .{page_size});
-        }
-        if (memory_ptr > max_memory) {
-            var err = try diags.addErrorWithNotes(0);
-            try err.addMsg("Maximum memory too small, must be at least {d} bytes", .{memory_ptr});
-        }
-        if (max_memory > max_memory_allowed) {
-            var err = try diags.addErrorWithNotes(0);
-            try err.addMsg("Maximum memory exceeds maximum amount {d}", .{max_memory_allowed});
-        }
-        wasm.memories.limits.max = @as(u32, @intCast(max_memory / page_size));
-        wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_HAS_MAX);
-        if (shared_memory) {
-            wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_IS_SHARED);
-        }
-        log.debug("Maximum memory pages: {?d}", .{wasm.memories.limits.max});
-    }
-}
-
-/// From a given object's index and the index of the segment, returns the corresponding
-/// index of the segment within the final data section. When the segment does not yet
-/// exist, a new one will be initialized and appended. The new index will be returned in that case.
-pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol.Index) !Segment.Index {
-    const comp = wasm.base.comp;
-    const gpa = comp.gpa;
-    const diags = &wasm.base.comp.link_diags;
-    const symbol = objectSymbols(wasm, object_id)[@intFromEnum(symbol_index)];
-    const index: Segment.Index = @enumFromInt(wasm.segments.items.len);
-    const shared_memory = comp.config.shared_memory;
-
-    switch (symbol.tag) {
-        .data => {
-            const segment_info = objectSegmentInfo(wasm, object_id)[symbol.index];
-            const merge_segment = comp.config.output_mode != .Obj;
-            const result = try wasm.data_segments.getOrPut(gpa, segment_info.outputName(merge_segment));
-            if (!result.found_existing) {
-                result.value_ptr.* = index;
-                var flags: u32 = 0;
-                if (shared_memory) {
-                    flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
-                }
-                try wasm.segments.append(gpa, .{
-                    .alignment = .@"1",
-                    .size = 0,
-                    .offset = 0,
-                    .flags = flags,
-                });
-                try wasm.segment_info.putNoClobber(gpa, index, .{
-                    .name = try gpa.dupe(u8, segment_info.name),
-                    .alignment = segment_info.alignment,
-                    .flags = segment_info.flags,
-                });
-                return index;
-            } else return result.value_ptr.*;
-        },
-        .function => return wasm.code_section_index.unwrap() orelse blk: {
-            wasm.code_section_index = index.toOptional();
-            try wasm.appendDummySegment();
-            break :blk index;
-        },
-        .section => {
-            const section_name = wasm.objectSymbol(object_id, symbol_index).name;
-
-            inline for (@typeInfo(CustomSections).@"struct".fields) |field| {
-                if (@field(wasm.custom_sections, field.name).name == section_name) {
-                    const field_ptr = &@field(wasm.custom_sections, field.name).index;
-                    return field_ptr.unwrap() orelse {
-                        field_ptr.* = index.toOptional();
-                        try wasm.appendDummySegment();
-                        return index;
-                    };
-                }
-            } else {
-                return diags.failParse(objectPath(wasm, object_id), "unknown section: {s}", .{
-                    wasm.stringSlice(section_name),
-                });
-            }
-        },
-        else => unreachable,
-    }
-}
-
-/// Appends a new segment with default field values
-fn appendDummySegment(wasm: *Wasm) !void {
-    const gpa = wasm.base.comp.gpa;
-    try wasm.segments.append(gpa, .{
-        .alignment = .@"1",
-        .size = 0,
-        .offset = 0,
-        .flags = 0,
-    });
-}
-
-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;
-
-    if (use_lld) {
-        return wasm.linkWithLLD(arena, tid, prog_node);
-    }
-    return wasm.flushModule(arena, tid, prog_node);
-}
-
-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();
-
-    const comp = wasm.base.comp;
-    const diags = &comp.link_diags;
-    if (wasm.llvm_object) |llvm_object| {
-        try wasm.base.emitLlvmObject(arena, llvm_object, prog_node);
-        const use_lld = build_options.have_llvm and comp.config.use_lld;
-        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 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;
-
-    if (wasm.zig_object) |zig_object| try zig_object.flushModule(wasm, tid);
-
-    if (module_obj_path) |path| openParseObjectReportingFailure(wasm, path);
-
-    if (wasm.zig_object != null) {
-        try wasm.resolveSymbolsInObject(.zig_object);
-    }
-    if (diags.hasErrors()) return error.FlushFailure;
-    for (0..wasm.objects.items.len) |object_index| {
-        try wasm.resolveSymbolsInObject(@enumFromInt(object_index));
-    }
-    if (diags.hasErrors()) return error.FlushFailure;
-
-    var emit_features_count: u32 = 0;
-    var enabled_features: [@typeInfo(Feature.Tag).@"enum".fields.len]bool = undefined;
-    try wasm.validateFeatures(&enabled_features, &emit_features_count);
-    try wasm.resolveSymbolsInArchives();
-    if (diags.hasErrors()) return error.FlushFailure;
-    try wasm.resolveLazySymbols();
-    try wasm.checkUndefinedSymbols();
-    try wasm.checkExportNames();
-
-    try wasm.setupInitFunctions();
-    if (diags.hasErrors()) return error.FlushFailure;
-    try wasm.setupStart();
-
-    try wasm.markReferences();
-    try wasm.setupImports();
-    try wasm.mergeSections();
-    try wasm.mergeTypes();
-    try wasm.allocateAtoms();
-    try wasm.setupMemory();
-    if (diags.hasErrors()) return error.FlushFailure;
-    wasm.allocateVirtualAddresses();
-    wasm.mapFunctionTable();
-    try wasm.initializeCallCtorsFunction();
-    try wasm.setupInitMemoryFunction();
-    try wasm.setupTLSRelocationsFunction();
-    try wasm.initializeTLSFunction();
-    try wasm.setupStartSection();
-    try wasm.setupExports();
-    try wasm.writeToFile(enabled_features, emit_features_count, arena);
-    if (diags.hasErrors()) return error.FlushFailure;
-}
-
-/// Writes the WebAssembly in-memory module to the file
-fn writeToFile(
-    wasm: *Wasm,
-    enabled_features: [@typeInfo(Feature.Tag).@"enum".fields.len]bool,
-    feature_count: u32,
-    arena: Allocator,
-) !void {
-    const comp = wasm.base.comp;
-    const diags = &comp.link_diags;
-    const gpa = comp.gpa;
-    const use_llvm = comp.config.use_llvm;
-    const use_lld = build_options.have_llvm and comp.config.use_lld;
-    const shared_memory = comp.config.shared_memory;
-    const import_memory = comp.config.import_memory;
-    const export_memory = comp.config.export_memory;
-
-    // Size of each section header
-    const header_size = 5 + 1;
-    // The amount of sections that will be written
-    var section_count: u32 = 0;
-    // Index of the code section. Used to tell relocation table where the section lives.
-    var code_section_index: ?u32 = null;
-    // Index of the data section. Used to tell relocation table where the section lives.
-    var data_section_index: ?u32 = null;
-    const is_obj = comp.config.output_mode == .Obj or (!use_llvm and use_lld);
-
-    var binary_bytes = std.ArrayList(u8).init(gpa);
-    defer binary_bytes.deinit();
-    const binary_writer = binary_bytes.writer();
-
-    // We write the magic bytes at the end so they will only be written
-    // if everything succeeded as expected. So populate with 0's for now.
-    try binary_writer.writeAll(&[_]u8{0} ** 8);
-    // (Re)set file pointer to 0
-    try wasm.base.file.?.setEndPos(0);
-    try wasm.base.file.?.seekTo(0);
-
-    // Type section
-    if (wasm.func_types.items.len != 0) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-        log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len});
-        for (wasm.func_types.items) |func_type| {
-            try leb.writeUleb128(binary_writer, std.wasm.function_type);
-            try leb.writeUleb128(binary_writer, @as(u32, @intCast(func_type.params.len)));
-            for (func_type.params) |param_ty| {
-                try leb.writeUleb128(binary_writer, std.wasm.valtype(param_ty));
-            }
-            try leb.writeUleb128(binary_writer, @as(u32, @intCast(func_type.returns.len)));
-            for (func_type.returns) |ret_ty| {
-                try leb.writeUleb128(binary_writer, std.wasm.valtype(ret_ty));
-            }
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .type,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(wasm.func_types.items.len),
-        );
-        section_count += 1;
-    }
-
-    // Import section
-    if (wasm.imports.count() != 0 or import_memory) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-
-        var it = wasm.imports.iterator();
-        while (it.next()) |entry| {
-            assert(wasm.symbolLocSymbol(entry.key_ptr.*).isUndefined());
-            const import = entry.value_ptr.*;
-            try wasm.emitImport(binary_writer, import);
-        }
-
-        if (import_memory) {
-            const mem_imp: Import = .{
-                .module_name = wasm.host_name,
-                .name = if (is_obj) wasm.preloaded_strings.__linear_memory else wasm.preloaded_strings.memory,
-                .kind = .{ .memory = wasm.memories.limits },
-            };
-            try wasm.emitImport(binary_writer, mem_imp);
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .import,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(wasm.imports.count() + @intFromBool(import_memory)),
-        );
-        section_count += 1;
-    }
-
-    // Function section
-    if (wasm.functions.count() != 0) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-        for (wasm.functions.values()) |function| {
-            try leb.writeUleb128(binary_writer, function.func.type_index);
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .function,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(wasm.functions.count()),
-        );
-        section_count += 1;
-    }
-
-    // Table section
-    if (wasm.tables.items.len > 0) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-
-        for (wasm.tables.items) |table| {
-            try leb.writeUleb128(binary_writer, std.wasm.reftype(table.reftype));
-            try emitLimits(binary_writer, table.limits);
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .table,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(wasm.tables.items.len),
-        );
-        section_count += 1;
-    }
-
-    // Memory section
-    if (!import_memory) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-
-        try emitLimits(binary_writer, wasm.memories.limits);
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .memory,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            1, // wasm currently only supports 1 linear memory segment
-        );
-        section_count += 1;
-    }
-
-    // Global section (used to emit stack pointer)
-    if (wasm.wasm_globals.items.len > 0) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-
-        for (wasm.wasm_globals.items) |global| {
-            try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));
-            try binary_writer.writeByte(@intFromBool(global.global_type.mutable));
-            try emitInit(binary_writer, global.init);
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .global,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(wasm.wasm_globals.items.len),
-        );
-        section_count += 1;
-    }
-
-    // Export section
-    if (wasm.exports.items.len != 0 or export_memory) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-
-        for (wasm.exports.items) |exp| {
-            const name = wasm.stringSlice(exp.name);
-            try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len)));
-            try binary_writer.writeAll(name);
-            try leb.writeUleb128(binary_writer, @intFromEnum(exp.kind));
-            try leb.writeUleb128(binary_writer, exp.index);
-        }
-
-        if (export_memory) {
-            try leb.writeUleb128(binary_writer, @as(u32, @intCast("memory".len)));
-            try binary_writer.writeAll("memory");
-            try binary_writer.writeByte(std.wasm.externalKind(.memory));
-            try leb.writeUleb128(binary_writer, @as(u32, 0));
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .@"export",
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(wasm.exports.items.len + @intFromBool(export_memory)),
-        );
-        section_count += 1;
-    }
-
-    if (wasm.entry) |entry_index| {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .start,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            entry_index,
-        );
-    }
-
-    // element section (function table)
-    if (wasm.function_table.count() > 0) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-
-        const table_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?;
-        const table_sym = wasm.symbolLocSymbol(table_loc);
-
-        const flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually
-        try leb.writeUleb128(binary_writer, flags);
-        if (flags == 0x02) {
-            try leb.writeUleb128(binary_writer, table_sym.index);
-        }
-        try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
-        if (flags == 0x02) {
-            try leb.writeUleb128(binary_writer, @as(u8, 0)); // represents funcref
-        }
-        try leb.writeUleb128(binary_writer, @as(u32, @intCast(wasm.function_table.count())));
-        var symbol_it = wasm.function_table.keyIterator();
-        while (symbol_it.next()) |symbol_loc_ptr| {
-            const sym = wasm.symbolLocSymbol(symbol_loc_ptr.*);
-            std.debug.assert(sym.isAlive());
-            std.debug.assert(sym.index < wasm.functions.count() + wasm.imported_functions_count);
-            try leb.writeUleb128(binary_writer, sym.index);
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .element,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            1,
-        );
-        section_count += 1;
-    }
-
-    // When the shared-memory option is enabled, we *must* emit the 'data count' section.
-    const data_segments_count = wasm.data_segments.count() - @intFromBool(wasm.data_segments.contains(".bss") and !import_memory);
-    if (data_segments_count != 0 and shared_memory) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .data_count,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(data_segments_count),
-        );
-    }
-
-    // Code section
-    if (wasm.code_section_index != .none) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-        const start_offset = binary_bytes.items.len - 5; // minus 5 so start offset is 5 to include entry count
-
-        var func_it = wasm.functions.iterator();
-        while (func_it.next()) |entry| {
-            const sym_loc: SymbolLoc = .{ .index = entry.value_ptr.sym_index, .file = entry.key_ptr.file };
-            const atom_index = wasm.symbol_atom.get(sym_loc).?;
-            const atom = wasm.getAtomPtr(atom_index);
-
-            if (!is_obj) {
-                atom.resolveRelocs(wasm);
-            }
-            atom.offset = @intCast(binary_bytes.items.len - start_offset);
-            try leb.writeUleb128(binary_writer, atom.size);
-            try binary_writer.writeAll(atom.code.items);
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .code,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(wasm.functions.count()),
-        );
-        code_section_index = section_count;
-        section_count += 1;
-    }
-
-    // Data section
-    if (data_segments_count != 0) {
-        const header_offset = try reserveVecSectionHeader(&binary_bytes);
-
-        var it = wasm.data_segments.iterator();
-        var segment_count: u32 = 0;
-        while (it.next()) |entry| {
-            // do not output 'bss' section unless we import memory and therefore
-            // want to guarantee the data is zero initialized
-            if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;
-            const segment_index = entry.value_ptr.*;
-            const segment = wasm.segmentPtr(segment_index);
-            if (segment.size == 0) continue; // do not emit empty segments
-            segment_count += 1;
-            var atom_index = wasm.atoms.get(segment_index).?;
-
-            try leb.writeUleb128(binary_writer, segment.flags);
-            if (segment.flags & @intFromEnum(Wasm.Segment.Flag.WASM_DATA_SEGMENT_HAS_MEMINDEX) != 0) {
-                try leb.writeUleb128(binary_writer, @as(u32, 0)); // memory is always index 0 as we only have 1 memory entry
-            }
-            // when a segment is passive, it's initialized during runtime.
-            if (!segment.isPassive()) {
-                try emitInit(binary_writer, .{ .i32_const = @as(i32, @bitCast(segment.offset)) });
-            }
-            // offset into data section
-            try leb.writeUleb128(binary_writer, segment.size);
-
-            // fill in the offset table and the data segments
-            var current_offset: u32 = 0;
-            while (true) {
-                const atom = wasm.getAtomPtr(atom_index);
-                if (!is_obj) {
-                    atom.resolveRelocs(wasm);
-                }
-
-                // Pad with zeroes to ensure all segments are aligned
-                if (current_offset != atom.offset) {
-                    const diff = atom.offset - current_offset;
-                    try binary_writer.writeByteNTimes(0, diff);
-                    current_offset += diff;
-                }
-                assert(current_offset == atom.offset);
-                assert(atom.code.items.len == atom.size);
-                try binary_writer.writeAll(atom.code.items);
-
-                current_offset += atom.size;
-                if (atom.prev != .null) {
-                    atom_index = atom.prev;
-                } else {
-                    // also pad with zeroes when last atom to ensure
-                    // segments are aligned.
-                    if (current_offset != segment.size) {
-                        try binary_writer.writeByteNTimes(0, segment.size - current_offset);
-                        current_offset += segment.size - current_offset;
-                    }
-                    break;
-                }
-            }
-            assert(current_offset == segment.size);
-        }
-
-        try writeVecSectionHeader(
-            binary_bytes.items,
-            header_offset,
-            .data,
-            @intCast(binary_bytes.items.len - header_offset - header_size),
-            @intCast(segment_count),
-        );
-        data_section_index = section_count;
-        section_count += 1;
-    }
-
-    if (is_obj) {
-        // relocations need to point to the index of a symbol in the final symbol table. To save memory,
-        // we never store all symbols in a single table, but store a location reference instead.
-        // This means that for a relocatable object file, we need to generate one and provide it to the relocation sections.
-        var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena);
-        try wasm.emitLinkSection(&binary_bytes, &symbol_table);
-        if (code_section_index) |code_index| {
-            try wasm.emitCodeRelocations(&binary_bytes, code_index, symbol_table);
-        }
-        if (data_section_index) |data_index| {
-            try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table);
-        }
-    } else if (comp.config.debug_format != .strip) {
-        try wasm.emitNameSection(&binary_bytes, arena);
-    }
-
-    if (comp.config.debug_format != .strip) {
-        // The build id must be computed on the main sections only,
-        // so we have to do it now, before the debug sections.
-        switch (wasm.base.build_id) {
-            .none => {},
-            .fast => {
-                var id: [16]u8 = undefined;
-                std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{});
-                var uuid: [36]u8 = undefined;
-                _ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{
-                    std.fmt.fmtSliceHexLower(id[0..4]),
-                    std.fmt.fmtSliceHexLower(id[4..6]),
-                    std.fmt.fmtSliceHexLower(id[6..8]),
-                    std.fmt.fmtSliceHexLower(id[8..10]),
-                    std.fmt.fmtSliceHexLower(id[10..]),
-                });
-                try emitBuildIdSection(&binary_bytes, &uuid);
-            },
-            .hexstring => |hs| {
-                var buffer: [32 * 2]u8 = undefined;
-                const str = std.fmt.bufPrint(&buffer, "{s}", .{
-                    std.fmt.fmtSliceHexLower(hs.toSlice()),
-                }) catch unreachable;
-                try emitBuildIdSection(&binary_bytes, str);
-            },
-            else => |mode| {
-                var err = try diags.addErrorWithNotes(0);
-                try err.addMsg("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)});
-            },
-        }
-
-        var debug_bytes = std.ArrayList(u8).init(gpa);
-        defer debug_bytes.deinit();
-
-        inline for (@typeInfo(CustomSections).@"struct".fields) |field| {
-            if (@field(wasm.custom_sections, field.name).index.unwrap()) |index| {
-                var atom = wasm.getAtomPtr(wasm.atoms.get(index).?);
-                while (true) {
-                    atom.resolveRelocs(wasm);
-                    try debug_bytes.appendSlice(atom.code.items);
-                    if (atom.prev == .null) break;
-                    atom = wasm.getAtomPtr(atom.prev);
-                }
-                try emitDebugSection(&binary_bytes, debug_bytes.items, field.name);
-                debug_bytes.clearRetainingCapacity();
-            }
-        }
-
-        try emitProducerSection(&binary_bytes);
-        if (feature_count > 0) {
-            try emitFeaturesSection(&binary_bytes, &enabled_features, feature_count);
-        }
-    }
-
-    // Only when writing all sections executed properly we write the magic
-    // bytes. This allows us to easily detect what went wrong while generating
-    // the final binary.
-    {
-        const src = std.wasm.magic ++ std.wasm.version;
-        binary_bytes.items[0..src.len].* = src;
-    }
-
-    // finally, write the entire binary into the file.
-    var iovec = [_]std.posix.iovec_const{.{
-        .base = binary_bytes.items.ptr,
-        .len = binary_bytes.items.len,
-    }};
-    try wasm.base.file.?.writevAll(&iovec);
-}
-
-fn emitDebugSection(binary_bytes: *std.ArrayList(u8), data: []const u8, name: []const u8) !void {
-    if (data.len == 0) return;
-    const header_offset = try reserveCustomSectionHeader(binary_bytes);
-    const writer = binary_bytes.writer();
-    try leb.writeUleb128(writer, @as(u32, @intCast(name.len)));
-    try writer.writeAll(name);
-
-    const start = binary_bytes.items.len - header_offset;
-    log.debug("Emit debug section: '{s}' start=0x{x:0>8} end=0x{x:0>8}", .{ name, start, start + data.len });
-    try writer.writeAll(data);
-
-    try writeCustomSectionHeader(
-        binary_bytes.items,
-        header_offset,
-        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
-    );
-}
-
-fn emitProducerSection(binary_bytes: *std.ArrayList(u8)) !void {
-    const header_offset = try reserveCustomSectionHeader(binary_bytes);
-
-    const writer = binary_bytes.writer();
-    const producers = "producers";
-    try leb.writeUleb128(writer, @as(u32, @intCast(producers.len)));
-    try writer.writeAll(producers);
-
-    try leb.writeUleb128(writer, @as(u32, 2)); // 2 fields: Language + processed-by
-
-    // used for the Zig version
-    var version_buf: [100]u8 = undefined;
-    const version = try std.fmt.bufPrint(&version_buf, "{}", .{build_options.semver});
-
-    // language field
-    {
-        const language = "language";
-        try leb.writeUleb128(writer, @as(u32, @intCast(language.len)));
-        try writer.writeAll(language);
-
-        // field_value_count (TODO: Parse object files for producer sections to detect their language)
-        try leb.writeUleb128(writer, @as(u32, 1));
-
-        // versioned name
-        {
-            try leb.writeUleb128(writer, @as(u32, 3)); // len of "Zig"
-            try writer.writeAll("Zig");
-
-            try leb.writeUleb128(writer, @as(u32, @intCast(version.len)));
-            try writer.writeAll(version);
-        }
-    }
-
-    // processed-by field
-    {
-        const processed_by = "processed-by";
-        try leb.writeUleb128(writer, @as(u32, @intCast(processed_by.len)));
-        try writer.writeAll(processed_by);
-
-        // field_value_count (TODO: Parse object files for producer sections to detect other used tools)
-        try leb.writeUleb128(writer, @as(u32, 1));
-
-        // versioned name
-        {
-            try leb.writeUleb128(writer, @as(u32, 3)); // len of "Zig"
-            try writer.writeAll("Zig");
-
-            try leb.writeUleb128(writer, @as(u32, @intCast(version.len)));
-            try writer.writeAll(version);
-        }
-    }
-
-    try writeCustomSectionHeader(
-        binary_bytes.items,
-        header_offset,
-        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
-    );
-}
-
-fn emitBuildIdSection(binary_bytes: *std.ArrayList(u8), build_id: []const u8) !void {
-    const header_offset = try reserveCustomSectionHeader(binary_bytes);
-
-    const writer = binary_bytes.writer();
-    const hdr_build_id = "build_id";
-    try leb.writeUleb128(writer, @as(u32, @intCast(hdr_build_id.len)));
-    try writer.writeAll(hdr_build_id);
-
-    try leb.writeUleb128(writer, @as(u32, 1));
-    try leb.writeUleb128(writer, @as(u32, @intCast(build_id.len)));
-    try writer.writeAll(build_id);
-
-    try writeCustomSectionHeader(
-        binary_bytes.items,
-        header_offset,
-        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
-    );
-}
-
-fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []const bool, features_count: u32) !void {
-    const header_offset = try reserveCustomSectionHeader(binary_bytes);
-
-    const writer = binary_bytes.writer();
-    const target_features = "target_features";
-    try leb.writeUleb128(writer, @as(u32, @intCast(target_features.len)));
-    try writer.writeAll(target_features);
-
-    try leb.writeUleb128(writer, features_count);
-    for (enabled_features, 0..) |enabled, feature_index| {
-        if (enabled) {
-            const feature: Feature = .{ .prefix = .used, .tag = @as(Feature.Tag, @enumFromInt(feature_index)) };
-            try leb.writeUleb128(writer, @intFromEnum(feature.prefix));
-            var buf: [100]u8 = undefined;
-            const string = try std.fmt.bufPrint(&buf, "{}", .{feature.tag});
-            try leb.writeUleb128(writer, @as(u32, @intCast(string.len)));
-            try writer.writeAll(string);
-        }
-    }
-
-    try writeCustomSectionHeader(
-        binary_bytes.items,
-        header_offset,
-        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
-    );
-}
-
-fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem.Allocator) !void {
-    const comp = wasm.base.comp;
-    const import_memory = comp.config.import_memory;
-    const Name = struct {
-        index: u32,
-        name: []const u8,
-
-        fn lessThan(context: void, lhs: @This(), rhs: @This()) bool {
-            _ = context;
-            return lhs.index < rhs.index;
-        }
-    };
-
-    // we must de-duplicate symbols that point to the same function
-    var funcs = std.AutoArrayHashMap(u32, Name).init(arena);
-    try funcs.ensureUnusedCapacity(wasm.functions.count() + wasm.imported_functions_count);
-    var globals = try std.ArrayList(Name).initCapacity(arena, wasm.wasm_globals.items.len + wasm.imported_globals_count);
-    var segments = try std.ArrayList(Name).initCapacity(arena, wasm.data_segments.count());
-
-    for (wasm.resolved_symbols.keys()) |sym_loc| {
-        const symbol = wasm.symbolLocSymbol(sym_loc).*;
-        if (symbol.isDead()) {
-            continue;
-        }
-        const name = wasm.symbolLocName(sym_loc);
-        switch (symbol.tag) {
-            .function => {
-                const gop = funcs.getOrPutAssumeCapacity(symbol.index);
-                if (!gop.found_existing) {
-                    gop.value_ptr.* = .{ .index = symbol.index, .name = name };
-                }
-            },
-            .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),
-            else => {},
-        }
-    }
-    // data segments are already 'ordered'
-    var data_segment_index: u32 = 0;
-    for (wasm.data_segments.keys()) |key| {
-        // bss section is not emitted when this condition holds true, so we also
-        // do not output a name for it.
-        if (!import_memory and std.mem.eql(u8, key, ".bss")) continue;
-        segments.appendAssumeCapacity(.{ .index = data_segment_index, .name = key });
-        data_segment_index += 1;
-    }
-
-    mem.sort(Name, funcs.values(), {}, Name.lessThan);
-    mem.sort(Name, globals.items, {}, Name.lessThan);
-
-    const header_offset = try reserveCustomSectionHeader(binary_bytes);
-    const writer = binary_bytes.writer();
-    try leb.writeUleb128(writer, @as(u32, @intCast("name".len)));
-    try writer.writeAll("name");
-
-    try wasm.emitNameSubsection(.function, funcs.values(), writer);
-    try wasm.emitNameSubsection(.global, globals.items, writer);
-    try wasm.emitNameSubsection(.data_segment, segments.items, writer);
-
-    try writeCustomSectionHeader(
-        binary_bytes.items,
-        header_offset,
-        @as(u32, @intCast(binary_bytes.items.len - header_offset - 6)),
-    );
-}
-
-fn emitNameSubsection(wasm: *Wasm, section_id: std.wasm.NameSubsection, names: anytype, writer: anytype) !void {
-    const gpa = wasm.base.comp.gpa;
+    arena: Allocator,
+    tid: Zcu.PerThread.Id,
+    prog_node: std.Progress.Node,
+) link.File.FlushError!void {
+    // The goal is to never use this because it's only needed if we need to
+    // write to InternPool, but flushModule is too late to be writing to the
+    // InternPool.
+    _ = tid;
+    const comp = wasm.base.comp;
+    const use_lld = build_options.have_llvm and comp.config.use_lld;
 
-    // We must emit subsection size, so first write to a temporary list
-    var section_list = std.ArrayList(u8).init(gpa);
-    defer section_list.deinit();
-    const sub_writer = section_list.writer();
-
-    try leb.writeUleb128(sub_writer, @as(u32, @intCast(names.len)));
-    for (names) |name| {
-        log.debug("Emit symbol '{s}' type({s})", .{ name.name, @tagName(section_id) });
-        try leb.writeUleb128(sub_writer, name.index);
-        try leb.writeUleb128(sub_writer, @as(u32, @intCast(name.name.len)));
-        try sub_writer.writeAll(name.name);
+    if (wasm.llvm_object) |llvm_object| {
+        try wasm.base.emitLlvmObject(arena, llvm_object, prog_node);
+        if (use_lld) return;
     }
 
-    // From now, write to the actual writer
-    try leb.writeUleb128(writer, @intFromEnum(section_id));
-    try leb.writeUleb128(writer, @as(u32, @intCast(section_list.items.len)));
-    try writer.writeAll(section_list.items);
-}
+    if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items);
 
-fn emitLimits(writer: anytype, limits: std.wasm.Limits) !void {
-    try writer.writeByte(limits.flags);
-    try leb.writeUleb128(writer, limits.min);
-    if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) {
-        try leb.writeUleb128(writer, limits.max);
+    if (wasm.base.zcu_object_sub_path) |path| {
+        const module_obj_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,
+        };
+        openParseObjectReportingFailure(wasm, module_obj_path);
+        try prelink(wasm, prog_node);
     }
-}
 
-fn emitInit(writer: anytype, init_expr: std.wasm.InitExpression) !void {
-    switch (init_expr) {
-        .i32_const => |val| {
-            try writer.writeByte(std.wasm.opcode(.i32_const));
-            try leb.writeIleb128(writer, val);
-        },
-        .i64_const => |val| {
-            try writer.writeByte(std.wasm.opcode(.i64_const));
-            try leb.writeIleb128(writer, val);
-        },
-        .f32_const => |val| {
-            try writer.writeByte(std.wasm.opcode(.f32_const));
-            try writer.writeInt(u32, @bitCast(val), .little);
-        },
-        .f64_const => |val| {
-            try writer.writeByte(std.wasm.opcode(.f64_const));
-            try writer.writeInt(u64, @bitCast(val), .little);
-        },
-        .global_get => |val| {
-            try writer.writeByte(std.wasm.opcode(.global_get));
-            try leb.writeUleb128(writer, val);
-        },
-    }
-    try writer.writeByte(std.wasm.opcode(.end));
-}
+    const tracy = trace(@src());
+    defer tracy.end();
 
-fn emitImport(wasm: *Wasm, writer: anytype, import: Import) !void {
-    const module_name = wasm.stringSlice(import.module_name);
-    try leb.writeUleb128(writer, @as(u32, @intCast(module_name.len)));
-    try writer.writeAll(module_name);
-
-    const name = wasm.stringSlice(import.name);
-    try leb.writeUleb128(writer, @as(u32, @intCast(name.len)));
-    try writer.writeAll(name);
-
-    try writer.writeByte(@intFromEnum(import.kind));
-    switch (import.kind) {
-        .function => |type_index| try leb.writeUleb128(writer, type_index),
-        .global => |global_type| {
-            try leb.writeUleb128(writer, std.wasm.valtype(global_type.valtype));
-            try writer.writeByte(@intFromBool(global_type.mutable));
-        },
-        .table => |table| {
-            try leb.writeUleb128(writer, std.wasm.reftype(table.reftype));
-            try emitLimits(writer, table.limits);
-        },
-        .memory => |limits| {
-            try emitLimits(writer, limits);
-        },
-    }
+    const sub_prog_node = prog_node.start("Wasm Flush", 0);
+    defer sub_prog_node.end();
+
+    wasm.flush_buffer.clear();
+    defer wasm.flush_buffer.subsequent = true;
+    return wasm.flush_buffer.finish(wasm, arena);
 }
 
 fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {
@@ -3811,281 +2138,6 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
     }
 }
 
-fn reserveVecSectionHeader(bytes: *std.ArrayList(u8)) !u32 {
-    // section id + fixed leb contents size + fixed leb vector length
-    const header_size = 1 + 5 + 5;
-    const offset = @as(u32, @intCast(bytes.items.len));
-    try bytes.appendSlice(&[_]u8{0} ** header_size);
-    return offset;
-}
-
-fn reserveCustomSectionHeader(bytes: *std.ArrayList(u8)) !u32 {
-    // unlike regular section, we don't emit the count
-    const header_size = 1 + 5;
-    const offset = @as(u32, @intCast(bytes.items.len));
-    try bytes.appendSlice(&[_]u8{0} ** header_size);
-    return offset;
-}
-
-fn writeVecSectionHeader(buffer: []u8, offset: u32, section: std.wasm.Section, size: u32, items: u32) !void {
-    var buf: [1 + 5 + 5]u8 = undefined;
-    buf[0] = @intFromEnum(section);
-    leb.writeUnsignedFixed(5, buf[1..6], size);
-    leb.writeUnsignedFixed(5, buf[6..], items);
-    buffer[offset..][0..buf.len].* = buf;
-}
-
-fn writeCustomSectionHeader(buffer: []u8, offset: u32, size: u32) !void {
-    var buf: [1 + 5]u8 = undefined;
-    buf[0] = 0; // 0 = 'custom' section
-    leb.writeUnsignedFixed(5, buf[1..6], size);
-    buffer[offset..][0..buf.len].* = buf;
-}
-
-fn emitLinkSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
-    const offset = try reserveCustomSectionHeader(binary_bytes);
-    const writer = binary_bytes.writer();
-    // emit "linking" custom section name
-    const section_name = "linking";
-    try leb.writeUleb128(writer, section_name.len);
-    try writer.writeAll(section_name);
-
-    // meta data version, which is currently '2'
-    try leb.writeUleb128(writer, @as(u32, 2));
-
-    // For each subsection type (found in Subsection) we can emit a section.
-    // Currently, we only support emitting segment info and the symbol table.
-    try wasm.emitSymbolTable(binary_bytes, symbol_table);
-    try wasm.emitSegmentInfo(binary_bytes);
-
-    const size: u32 = @intCast(binary_bytes.items.len - offset - 6);
-    try writeCustomSectionHeader(binary_bytes.items, offset, size);
-}
-
-fn emitSymbolTable(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
-    const writer = binary_bytes.writer();
-
-    try leb.writeUleb128(writer, @intFromEnum(SubsectionType.WASM_SYMBOL_TABLE));
-    const table_offset = binary_bytes.items.len;
-
-    var symbol_count: u32 = 0;
-    for (wasm.resolved_symbols.keys()) |sym_loc| {
-        const symbol = wasm.symbolLocSymbol(sym_loc).*;
-        if (symbol.tag == .dead) continue; // Do not emit dead symbols
-        try symbol_table.putNoClobber(sym_loc, symbol_count);
-        symbol_count += 1;
-        log.debug("Emit symbol: {}", .{symbol});
-        try leb.writeUleb128(writer, @intFromEnum(symbol.tag));
-        try leb.writeUleb128(writer, symbol.flags);
-
-        const sym_name = wasm.symbolLocName(sym_loc);
-        switch (symbol.tag) {
-            .data => {
-                try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len)));
-                try writer.writeAll(sym_name);
-
-                if (symbol.isDefined()) {
-                    try leb.writeUleb128(writer, symbol.index);
-                    const atom_index = wasm.symbol_atom.get(sym_loc).?;
-                    const atom = wasm.getAtom(atom_index);
-                    try leb.writeUleb128(writer, @as(u32, atom.offset));
-                    try leb.writeUleb128(writer, @as(u32, atom.size));
-                }
-            },
-            .section => {
-                try leb.writeUleb128(writer, symbol.index);
-            },
-            else => {
-                try leb.writeUleb128(writer, symbol.index);
-                if (symbol.isDefined()) {
-                    try leb.writeUleb128(writer, @as(u32, @intCast(sym_name.len)));
-                    try writer.writeAll(sym_name);
-                }
-            },
-        }
-    }
-
-    var buf: [10]u8 = undefined;
-    leb.writeUnsignedFixed(5, buf[0..5], @intCast(binary_bytes.items.len - table_offset + 5));
-    leb.writeUnsignedFixed(5, buf[5..], symbol_count);
-    try binary_bytes.insertSlice(table_offset, &buf);
-}
-
-fn emitSegmentInfo(wasm: *Wasm, binary_bytes: *std.ArrayList(u8)) !void {
-    const writer = binary_bytes.writer();
-    try leb.writeUleb128(writer, @intFromEnum(SubsectionType.WASM_SEGMENT_INFO));
-    const segment_offset = binary_bytes.items.len;
-
-    try leb.writeUleb128(writer, @as(u32, @intCast(wasm.segment_info.count())));
-    for (wasm.segment_info.values()) |segment_info| {
-        log.debug("Emit segment: {s} align({d}) flags({b})", .{
-            segment_info.name,
-            segment_info.alignment,
-            segment_info.flags,
-        });
-        try leb.writeUleb128(writer, @as(u32, @intCast(segment_info.name.len)));
-        try writer.writeAll(segment_info.name);
-        try leb.writeUleb128(writer, segment_info.alignment.toLog2Units());
-        try leb.writeUleb128(writer, segment_info.flags);
-    }
-
-    var buf: [5]u8 = undefined;
-    leb.writeUnsignedFixed(5, &buf, @as(u32, @intCast(binary_bytes.items.len - segment_offset)));
-    try binary_bytes.insertSlice(segment_offset, &buf);
-}
-
-pub fn getUleb128Size(uint_value: anytype) u32 {
-    const T = @TypeOf(uint_value);
-    const U = if (@typeInfo(T).int.bits < 8) u8 else T;
-    var value = @as(U, @intCast(uint_value));
-
-    var size: u32 = 0;
-    while (value != 0) : (size += 1) {
-        value >>= 7;
-    }
-    return size;
-}
-
-/// For each relocatable section, emits a custom "relocation.<section_name>" section
-fn emitCodeRelocations(
-    wasm: *Wasm,
-    binary_bytes: *std.ArrayList(u8),
-    section_index: u32,
-    symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
-) !void {
-    const code_index = wasm.code_section_index.unwrap() orelse return;
-    const writer = binary_bytes.writer();
-    const header_offset = try reserveCustomSectionHeader(binary_bytes);
-
-    // write custom section information
-    const name = "reloc.CODE";
-    try leb.writeUleb128(writer, @as(u32, @intCast(name.len)));
-    try writer.writeAll(name);
-    try leb.writeUleb128(writer, section_index);
-    const reloc_start = binary_bytes.items.len;
-
-    var count: u32 = 0;
-    var atom: *Atom = wasm.getAtomPtr(wasm.atoms.get(code_index).?);
-    // for each atom, we calculate the uleb size and append that
-    var size_offset: u32 = 5; // account for code section size leb128
-    while (true) {
-        size_offset += getUleb128Size(atom.size);
-        for (atom.relocs.items) |relocation| {
-            count += 1;
-            const sym_loc: SymbolLoc = .{ .file = atom.file, .index = @enumFromInt(relocation.index) };
-            const symbol_index = symbol_table.get(sym_loc).?;
-            try leb.writeUleb128(writer, @intFromEnum(relocation.relocation_type));
-            const offset = atom.offset + relocation.offset + size_offset;
-            try leb.writeUleb128(writer, offset);
-            try leb.writeUleb128(writer, symbol_index);
-            if (relocation.relocation_type.addendIsPresent()) {
-                try leb.writeIleb128(writer, relocation.addend);
-            }
-            log.debug("Emit relocation: {}", .{relocation});
-        }
-        if (atom.prev == .null) break;
-        atom = wasm.getAtomPtr(atom.prev);
-    }
-    if (count == 0) return;
-    var buf: [5]u8 = undefined;
-    leb.writeUnsignedFixed(5, &buf, count);
-    try binary_bytes.insertSlice(reloc_start, &buf);
-    const size: u32 = @intCast(binary_bytes.items.len - header_offset - 6);
-    try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
-}
-
-fn emitDataRelocations(
-    wasm: *Wasm,
-    binary_bytes: *std.ArrayList(u8),
-    section_index: u32,
-    symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
-) !void {
-    if (wasm.data_segments.count() == 0) return;
-    const writer = binary_bytes.writer();
-    const header_offset = try reserveCustomSectionHeader(binary_bytes);
-
-    // write custom section information
-    const name = "reloc.DATA";
-    try leb.writeUleb128(writer, @as(u32, @intCast(name.len)));
-    try writer.writeAll(name);
-    try leb.writeUleb128(writer, section_index);
-    const reloc_start = binary_bytes.items.len;
-
-    var count: u32 = 0;
-    // for each atom, we calculate the uleb size and append that
-    var size_offset: u32 = 5; // account for code section size leb128
-    for (wasm.data_segments.values()) |segment_index| {
-        var atom: *Atom = wasm.getAtomPtr(wasm.atoms.get(segment_index).?);
-        while (true) {
-            size_offset += getUleb128Size(atom.size);
-            for (atom.relocs.items) |relocation| {
-                count += 1;
-                const sym_loc: SymbolLoc = .{ .file = atom.file, .index = @enumFromInt(relocation.index) };
-                const symbol_index = symbol_table.get(sym_loc).?;
-                try leb.writeUleb128(writer, @intFromEnum(relocation.relocation_type));
-                const offset = atom.offset + relocation.offset + size_offset;
-                try leb.writeUleb128(writer, offset);
-                try leb.writeUleb128(writer, symbol_index);
-                if (relocation.relocation_type.addendIsPresent()) {
-                    try leb.writeIleb128(writer, relocation.addend);
-                }
-                log.debug("Emit relocation: {}", .{relocation});
-            }
-            if (atom.prev == .null) break;
-            atom = wasm.getAtomPtr(atom.prev);
-        }
-    }
-    if (count == 0) return;
-
-    var buf: [5]u8 = undefined;
-    leb.writeUnsignedFixed(5, &buf, count);
-    try binary_bytes.insertSlice(reloc_start, &buf);
-    const size = @as(u32, @intCast(binary_bytes.items.len - header_offset - 6));
-    try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
-}
-
-fn hasPassiveInitializationSegments(wasm: *const Wasm) bool {
-    const comp = wasm.base.comp;
-    const import_memory = comp.config.import_memory;
-
-    var it = wasm.data_segments.iterator();
-    while (it.next()) |entry| {
-        const segment = wasm.segmentPtr(entry.value_ptr.*);
-        if (segment.needsPassiveInitialization(import_memory, entry.key_ptr.*)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-/// Searches for a matching function signature. When no matching signature is found,
-/// a new entry will be made. The value returned is the index of the type within `wasm.func_types`.
-pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 {
-    if (wasm.getTypeIndex(func_type)) |index| {
-        return index;
-    }
-
-    // functype does not exist.
-    const gpa = wasm.base.comp.gpa;
-    const index: u32 = @intCast(wasm.func_types.items.len);
-    const params = try gpa.dupe(std.wasm.Valtype, func_type.params);
-    errdefer gpa.free(params);
-    const returns = try gpa.dupe(std.wasm.Valtype, func_type.returns);
-    errdefer gpa.free(returns);
-    try wasm.func_types.append(gpa, .{
-        .params = params,
-        .returns = returns,
-    });
-    return index;
-}
-
-/// For the given `nav`, stores the corresponding type representing the function signature.
-/// Asserts declaration has an associated `Atom`.
-/// Returns the index into the list of types.
-pub fn storeNavType(wasm: *Wasm, nav: InternPool.Nav.Index, func_type: std.wasm.Type) !u32 {
-    return wasm.zig_object.?.storeDeclType(wasm.base.comp.gpa, nav, func_type);
-}
-
 /// Returns the symbol index of the error name table.
 ///
 /// When the symbol does not yet exist, it will create a new one instead.
@@ -4094,69 +2146,6 @@ pub fn getErrorTableSymbol(wasm: *Wasm, pt: Zcu.PerThread) !u32 {
     return @intFromEnum(sym_index);
 }
 
-/// For a given `InternPool.DeclIndex` returns its corresponding `Atom.Index`.
-/// When the index was not found, a new `Atom` will be created, and its index will be returned.
-/// The newly created Atom is empty with default fields as specified by `Atom.empty`.
-pub fn getOrCreateAtomForNav(wasm: *Wasm, pt: Zcu.PerThread, nav: InternPool.Nav.Index) !Atom.Index {
-    return wasm.zig_object.?.getOrCreateAtomForNav(wasm, pt, nav);
-}
-
-/// Verifies all resolved symbols and checks whether itself needs to be marked alive,
-/// as well as any of its references.
-fn markReferences(wasm: *Wasm) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const do_garbage_collect = wasm.base.gc_sections;
-    const comp = wasm.base.comp;
-
-    for (wasm.resolved_symbols.keys()) |sym_loc| {
-        const sym = wasm.symbolLocSymbol(sym_loc);
-        if (sym.isExported(comp.config.rdynamic) or sym.isNoStrip() or !do_garbage_collect) {
-            try wasm.mark(sym_loc);
-            continue;
-        }
-
-        // Debug sections may require to be parsed and marked when it contains
-        // relocations to alive symbols.
-        if (sym.tag == .section and comp.config.debug_format != .strip) {
-            const object_id = sym_loc.file.unwrap() orelse continue; // Incremental debug info is done independently
-            _ = try wasm.parseSymbolIntoAtom(object_id, sym_loc.index);
-            sym.mark();
-        }
-    }
-}
-
-/// Marks a symbol as 'alive' recursively so itself and any references it contains to
-/// other symbols will not be omit from the binary.
-fn mark(wasm: *Wasm, loc: SymbolLoc) !void {
-    const symbol = wasm.symbolLocSymbol(loc);
-    if (symbol.isAlive()) {
-        // Symbol is already marked alive, including its references.
-        // This means we can skip it so we don't end up marking the same symbols
-        // multiple times.
-        return;
-    }
-    symbol.mark();
-    gc_log.debug("Marked symbol '{s}'", .{wasm.symbolLocName(loc)});
-    if (symbol.isUndefined()) {
-        // undefined symbols do not have an associated `Atom` and therefore also
-        // do not contain relocations.
-        return;
-    }
-
-    const atom_index = if (loc.file.unwrap()) |object_id|
-        try wasm.parseSymbolIntoAtom(object_id, loc.index)
-    else
-        wasm.symbol_atom.get(loc) orelse return;
-
-    const atom = wasm.getAtom(atom_index);
-    for (atom.relocs.items) |reloc| {
-        const target_loc: SymbolLoc = .{ .index = @enumFromInt(reloc.index), .file = loc.file };
-        try wasm.mark(wasm.symbolLocFinalLoc(target_loc));
-    }
-}
-
 fn defaultEntrySymbolName(
     preloaded_strings: *const PreloadedStrings,
     wasi_exec_model: std.builtin.WasiExecModel,
@@ -4167,573 +2156,8 @@ fn defaultEntrySymbolName(
     };
 }
 
-pub const Atom = struct {
-    /// Represents the index of the file this atom was generated from.
-    /// This is `none` when the atom was generated by a synthetic linker symbol.
-    file: OptionalObjectId,
-    /// symbol index of the symbol representing this atom
-    sym_index: Symbol.Index,
-    /// Size of the atom, used to calculate section sizes in the final binary
-    size: u32 = 0,
-    /// List of relocations belonging to this atom
-    relocs: std.ArrayListUnmanaged(Relocation) = .empty,
-    /// Contains the binary data of an atom, which can be non-relocated
-    code: std.ArrayListUnmanaged(u8) = .empty,
-    /// For code this is 1, for data this is set to the highest value of all segments
-    alignment: Wasm.Alignment = .@"1",
-    /// Offset into the section where the atom lives, this already accounts
-    /// for alignment.
-    offset: u32 = 0,
-    /// The original offset within the object file. This value is subtracted from
-    /// relocation offsets to determine where in the `data` to rewrite the value
-    original_offset: u32 = 0,
-    /// Previous atom in relation to this atom.
-    /// is null when this atom is the first in its order
-    prev: Atom.Index = .null,
-    /// Contains atoms local to a decl, all managed by this `Atom`.
-    /// When the parent atom is being freed, it will also do so for all local atoms.
-    locals: std.ArrayListUnmanaged(Atom.Index) = .empty,
-
-    /// Represents the index of an Atom where `null` is considered
-    /// an invalid atom.
-    pub const Index = enum(u32) {
-        null = std.math.maxInt(u32),
-        _,
-    };
-
-    /// Frees all resources owned by this `Atom`.
-    pub fn deinit(atom: *Atom, gpa: std.mem.Allocator) void {
-        atom.relocs.deinit(gpa);
-        atom.code.deinit(gpa);
-        atom.locals.deinit(gpa);
-        atom.* = undefined;
-    }
-
-    /// Sets the length of relocations and code to '0',
-    /// effectively resetting them and allowing them to be re-populated.
-    pub fn clear(atom: *Atom) void {
-        atom.relocs.clearRetainingCapacity();
-        atom.code.clearRetainingCapacity();
-    }
-
-    pub fn format(atom: Atom, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = fmt;
-        _ = options;
-        try writer.print("Atom{{ .sym_index = {d}, .alignment = {d}, .size = {d}, .offset = 0x{x:0>8} }}", .{
-            @intFromEnum(atom.sym_index),
-            atom.alignment,
-            atom.size,
-            atom.offset,
-        });
-    }
-
-    /// Returns the location of the symbol that represents this `Atom`
-    pub fn symbolLoc(atom: Atom) Wasm.SymbolLoc {
-        return .{
-            .file = atom.file,
-            .index = atom.sym_index,
-        };
-    }
-
-    /// Resolves the relocations within the atom, writing the new value
-    /// at the calculated offset.
-    pub fn resolveRelocs(atom: *Atom, wasm: *const Wasm) void {
-        if (atom.relocs.items.len == 0) return;
-        const symbol_name = wasm.symbolLocName(atom.symbolLoc());
-        log.debug("Resolving relocs in atom '{s}' count({d})", .{
-            symbol_name,
-            atom.relocs.items.len,
-        });
-
-        for (atom.relocs.items) |reloc| {
-            const value = atom.relocationValue(reloc, wasm);
-            log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{
-                wasm.symbolLocName(.{
-                    .file = atom.file,
-                    .index = @enumFromInt(reloc.index),
-                }),
-                symbol_name,
-                reloc.offset,
-                value,
-            });
-
-            switch (reloc.relocation_type) {
-                .R_WASM_TABLE_INDEX_I32,
-                .R_WASM_FUNCTION_OFFSET_I32,
-                .R_WASM_GLOBAL_INDEX_I32,
-                .R_WASM_MEMORY_ADDR_I32,
-                .R_WASM_SECTION_OFFSET_I32,
-                => std.mem.writeInt(u32, atom.code.items[reloc.offset - atom.original_offset ..][0..4], @as(u32, @truncate(value)), .little),
-                .R_WASM_TABLE_INDEX_I64,
-                .R_WASM_MEMORY_ADDR_I64,
-                => std.mem.writeInt(u64, atom.code.items[reloc.offset - atom.original_offset ..][0..8], value, .little),
-                .R_WASM_GLOBAL_INDEX_LEB,
-                .R_WASM_EVENT_INDEX_LEB,
-                .R_WASM_FUNCTION_INDEX_LEB,
-                .R_WASM_MEMORY_ADDR_LEB,
-                .R_WASM_MEMORY_ADDR_SLEB,
-                .R_WASM_TABLE_INDEX_SLEB,
-                .R_WASM_TABLE_NUMBER_LEB,
-                .R_WASM_TYPE_INDEX_LEB,
-                .R_WASM_MEMORY_ADDR_TLS_SLEB,
-                => leb.writeUnsignedFixed(5, atom.code.items[reloc.offset - atom.original_offset ..][0..5], @as(u32, @truncate(value))),
-                .R_WASM_MEMORY_ADDR_LEB64,
-                .R_WASM_MEMORY_ADDR_SLEB64,
-                .R_WASM_TABLE_INDEX_SLEB64,
-                .R_WASM_MEMORY_ADDR_TLS_SLEB64,
-                => leb.writeUnsignedFixed(10, atom.code.items[reloc.offset - atom.original_offset ..][0..10], value),
-            }
-        }
-    }
-
-    /// From a given `relocation` will return the new value to be written.
-    /// All values will be represented as a `u64` as all values can fit within it.
-    /// The final value must be casted to the correct size.
-    fn relocationValue(atom: Atom, relocation: Relocation, wasm: *const Wasm) u64 {
-        const target_loc = wasm.symbolLocFinalLoc(.{
-            .file = atom.file,
-            .index = @enumFromInt(relocation.index),
-        });
-        const symbol = wasm.symbolLocSymbol(target_loc);
-        if (relocation.relocation_type != .R_WASM_TYPE_INDEX_LEB and
-            symbol.tag != .section and
-            symbol.isDead())
-        {
-            const val = atom.tombstone(wasm) orelse relocation.addend;
-            return @bitCast(val);
-        }
-        switch (relocation.relocation_type) {
-            .R_WASM_FUNCTION_INDEX_LEB => return symbol.index,
-            .R_WASM_TABLE_NUMBER_LEB => return symbol.index,
-            .R_WASM_TABLE_INDEX_I32,
-            .R_WASM_TABLE_INDEX_I64,
-            .R_WASM_TABLE_INDEX_SLEB,
-            .R_WASM_TABLE_INDEX_SLEB64,
-            => return wasm.function_table.get(.{ .file = atom.file, .index = @enumFromInt(relocation.index) }) orelse 0,
-            .R_WASM_TYPE_INDEX_LEB => {
-                const object_id = atom.file.unwrap() orelse return relocation.index;
-                const original_type = objectFuncTypes(wasm, object_id)[relocation.index];
-                return wasm.getTypeIndex(original_type).?;
-            },
-            .R_WASM_GLOBAL_INDEX_I32,
-            .R_WASM_GLOBAL_INDEX_LEB,
-            => return symbol.index,
-            .R_WASM_MEMORY_ADDR_I32,
-            .R_WASM_MEMORY_ADDR_I64,
-            .R_WASM_MEMORY_ADDR_LEB,
-            .R_WASM_MEMORY_ADDR_LEB64,
-            .R_WASM_MEMORY_ADDR_SLEB,
-            .R_WASM_MEMORY_ADDR_SLEB64,
-            => {
-                std.debug.assert(symbol.tag == .data);
-                if (symbol.isUndefined()) {
-                    return 0;
-                }
-                const va: i33 = @intCast(symbol.virtual_address);
-                return @intCast(va + relocation.addend);
-            },
-            .R_WASM_EVENT_INDEX_LEB => return symbol.index,
-            .R_WASM_SECTION_OFFSET_I32 => {
-                const target_atom_index = wasm.symbol_atom.get(target_loc).?;
-                const target_atom = wasm.getAtom(target_atom_index);
-                const rel_value: i33 = @intCast(target_atom.offset);
-                return @intCast(rel_value + relocation.addend);
-            },
-            .R_WASM_FUNCTION_OFFSET_I32 => {
-                if (symbol.isUndefined()) {
-                    const val = atom.tombstone(wasm) orelse relocation.addend;
-                    return @bitCast(val);
-                }
-                const target_atom_index = wasm.symbol_atom.get(target_loc).?;
-                const target_atom = wasm.getAtom(target_atom_index);
-                const rel_value: i33 = @intCast(target_atom.offset);
-                return @intCast(rel_value + relocation.addend);
-            },
-            .R_WASM_MEMORY_ADDR_TLS_SLEB,
-            .R_WASM_MEMORY_ADDR_TLS_SLEB64,
-            => {
-                const va: i33 = @intCast(symbol.virtual_address);
-                return @intCast(va + relocation.addend);
-            },
-        }
-    }
-
-    // For a given `Atom` returns whether it has a tombstone value or not.
-    /// This defines whether we want a specific value when a section is dead.
-    fn tombstone(atom: Atom, wasm: *const Wasm) ?i64 {
-        const atom_name = wasm.symbolLocSymbol(atom.symbolLoc()).name;
-        if (atom_name == wasm.custom_sections.@".debug_ranges".name or
-            atom_name == wasm.custom_sections.@".debug_loc".name)
-        {
-            return -2;
-        } else if (std.mem.startsWith(u8, wasm.stringSlice(atom_name), ".debug_")) {
-            return -1;
-        } else {
-            return null;
-        }
-    }
-};
-
-pub const Relocation = struct {
-    /// Represents the type of the `Relocation`
-    relocation_type: RelocationType,
-    /// Offset of the value to rewrite relative to the relevant section's contents.
-    /// When `offset` is zero, its position is immediately after the id and size of the section.
-    offset: u32,
-    /// The index of the symbol used.
-    /// When the type is `R_WASM_TYPE_INDEX_LEB`, it represents the index of the type.
-    index: u32,
-    /// Addend to add to the address.
-    /// This field is only non-zero for `R_WASM_MEMORY_ADDR_*`, `R_WASM_FUNCTION_OFFSET_I32` and `R_WASM_SECTION_OFFSET_I32`.
-    addend: i32 = 0,
-
-    /// All possible relocation types currently existing.
-    /// This enum is exhaustive as the spec is WIP and new types
-    /// can be added which means that a generated binary will be invalid,
-    /// so instead we will show an error in such cases.
-    pub const RelocationType = enum(u8) {
-        R_WASM_FUNCTION_INDEX_LEB = 0,
-        R_WASM_TABLE_INDEX_SLEB = 1,
-        R_WASM_TABLE_INDEX_I32 = 2,
-        R_WASM_MEMORY_ADDR_LEB = 3,
-        R_WASM_MEMORY_ADDR_SLEB = 4,
-        R_WASM_MEMORY_ADDR_I32 = 5,
-        R_WASM_TYPE_INDEX_LEB = 6,
-        R_WASM_GLOBAL_INDEX_LEB = 7,
-        R_WASM_FUNCTION_OFFSET_I32 = 8,
-        R_WASM_SECTION_OFFSET_I32 = 9,
-        R_WASM_EVENT_INDEX_LEB = 10,
-        R_WASM_GLOBAL_INDEX_I32 = 13,
-        R_WASM_MEMORY_ADDR_LEB64 = 14,
-        R_WASM_MEMORY_ADDR_SLEB64 = 15,
-        R_WASM_MEMORY_ADDR_I64 = 16,
-        R_WASM_TABLE_INDEX_SLEB64 = 18,
-        R_WASM_TABLE_INDEX_I64 = 19,
-        R_WASM_TABLE_NUMBER_LEB = 20,
-        R_WASM_MEMORY_ADDR_TLS_SLEB = 21,
-        R_WASM_MEMORY_ADDR_TLS_SLEB64 = 25,
-
-        /// Returns true for relocation types where the `addend` field is present.
-        pub fn addendIsPresent(self: RelocationType) bool {
-            return switch (self) {
-                .R_WASM_MEMORY_ADDR_LEB,
-                .R_WASM_MEMORY_ADDR_SLEB,
-                .R_WASM_MEMORY_ADDR_I32,
-                .R_WASM_MEMORY_ADDR_LEB64,
-                .R_WASM_MEMORY_ADDR_SLEB64,
-                .R_WASM_MEMORY_ADDR_I64,
-                .R_WASM_MEMORY_ADDR_TLS_SLEB,
-                .R_WASM_MEMORY_ADDR_TLS_SLEB64,
-                .R_WASM_FUNCTION_OFFSET_I32,
-                .R_WASM_SECTION_OFFSET_I32,
-                => true,
-                else => false,
-            };
-        }
-    };
-
-    /// Verifies the relocation type of a given `Relocation` and returns
-    /// true when the relocation references a function call or address to a function.
-    pub fn isFunction(self: Relocation) bool {
-        return switch (self.relocation_type) {
-            .R_WASM_FUNCTION_INDEX_LEB,
-            .R_WASM_TABLE_INDEX_SLEB,
-            => true,
-            else => false,
-        };
-    }
-
-    pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = fmt;
-        _ = options;
-        try writer.print("{s} offset=0x{x:0>6} symbol={d}", .{
-            @tagName(self.relocation_type),
-            self.offset,
-            self.index,
-        });
-    }
-};
-
-/// Unlike the `Import` object defined by the wasm spec, and existing
-/// in the std.wasm namespace, this construct saves the 'module name' and 'name'
-/// of the import using offsets into a string table, rather than the slices itself.
-/// This saves us (potentially) 24 bytes per import on 64bit machines.
-pub const Import = struct {
-    module_name: String,
-    name: String,
-    kind: std.wasm.Import.Kind,
-};
-
-/// Unlike the `Export` object defined by the wasm spec, and existing
-/// in the std.wasm namespace, this construct saves the 'name'
-/// of the export using offsets into a string table, rather than the slice itself.
-/// This saves us (potentially) 12 bytes per export on 64bit machines.
-pub const Export = struct {
-    name: String,
-    index: u32,
-    kind: std.wasm.ExternalKind,
-};
-
-pub const SubsectionType = enum(u8) {
-    WASM_SEGMENT_INFO = 5,
-    WASM_INIT_FUNCS = 6,
-    WASM_COMDAT_INFO = 7,
-    WASM_SYMBOL_TABLE = 8,
-};
-
-pub const Alignment = @import("../InternPool.zig").Alignment;
-
-pub const NamedSegment = struct {
-    /// Segment's name, encoded as UTF-8 bytes.
-    name: []const u8,
-    /// The required alignment of the segment, encoded as a power of 2
-    alignment: Alignment,
-    /// Bitfield containing flags for a segment
-    flags: u32,
-
-    pub fn isTLS(segment: NamedSegment) bool {
-        return segment.flags & @intFromEnum(Flags.WASM_SEG_FLAG_TLS) != 0;
-    }
-
-    /// Returns the name as how it will be output into the final object
-    /// file or binary. When `merge_segments` is true, this will return the
-    /// short name. i.e. ".rodata". When false, it returns the entire name instead.
-    pub fn outputName(segment: NamedSegment, merge_segments: bool) []const u8 {
-        if (segment.isTLS()) {
-            return ".tdata";
-        } else if (!merge_segments) {
-            return segment.name;
-        } else if (std.mem.startsWith(u8, segment.name, ".rodata.")) {
-            return ".rodata";
-        } else if (std.mem.startsWith(u8, segment.name, ".text.")) {
-            return ".text";
-        } else if (std.mem.startsWith(u8, segment.name, ".data.")) {
-            return ".data";
-        } else if (std.mem.startsWith(u8, segment.name, ".bss.")) {
-            return ".bss";
-        }
-        return segment.name;
-    }
-
-    pub const Flags = enum(u32) {
-        WASM_SEG_FLAG_STRINGS = 0x1,
-        WASM_SEG_FLAG_TLS = 0x2,
-    };
-};
-
-pub const InitFunc = struct {
-    /// Priority of the init function
-    priority: u32,
-    /// The symbol index of init function (not the function index).
-    symbol_index: u32,
-};
-
-pub const Comdat = struct {
-    name: []const u8,
-    /// Must be zero, no flags are currently defined by the tool-convention.
-    flags: u32,
-    symbols: []const ComdatSym,
-};
-
-pub const ComdatSym = struct {
-    kind: @This().Type,
-    /// Index of the data segment/function/global/event/table within a WASM module.
-    /// The object must not be an import.
-    index: u32,
-
-    pub const Type = enum(u8) {
-        WASM_COMDAT_DATA = 0,
-        WASM_COMDAT_FUNCTION = 1,
-        WASM_COMDAT_GLOBAL = 2,
-        WASM_COMDAT_EVENT = 3,
-        WASM_COMDAT_TABLE = 4,
-        WASM_COMDAT_SECTION = 5,
-    };
-};
-
-pub const Feature = struct {
-    /// Provides information about the usage of the feature.
-    /// - '0x2b' (+): Object uses this feature, and the link fails if feature is not in the allowed set.
-    /// - '0x2d' (-): Object does not use this feature, and the link fails if this feature is in the allowed set.
-    /// - '0x3d' (=): Object uses this feature, and the link fails if this feature is not in the allowed set,
-    /// or if any object does not use this feature.
-    prefix: Prefix,
-    /// Type of the feature, must be unique in the sequence of features.
-    tag: Tag,
-
-    /// Unlike `std.Target.wasm.Feature` this also contains linker-features such as shared-mem
-    pub const Tag = enum {
-        atomics,
-        bulk_memory,
-        exception_handling,
-        extended_const,
-        half_precision,
-        multimemory,
-        multivalue,
-        mutable_globals,
-        nontrapping_fptoint,
-        reference_types,
-        relaxed_simd,
-        sign_ext,
-        simd128,
-        tail_call,
-        shared_mem,
-
-        /// From a given cpu feature, returns its linker feature
-        pub fn fromCpuFeature(feature: std.Target.wasm.Feature) Tag {
-            return @as(Tag, @enumFromInt(@intFromEnum(feature)));
-        }
-
-        pub fn format(tag: Tag, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
-            _ = fmt;
-            _ = opt;
-            try writer.writeAll(switch (tag) {
-                .atomics => "atomics",
-                .bulk_memory => "bulk-memory",
-                .exception_handling => "exception-handling",
-                .extended_const => "extended-const",
-                .half_precision => "half-precision",
-                .multimemory => "multimemory",
-                .multivalue => "multivalue",
-                .mutable_globals => "mutable-globals",
-                .nontrapping_fptoint => "nontrapping-fptoint",
-                .reference_types => "reference-types",
-                .relaxed_simd => "relaxed-simd",
-                .sign_ext => "sign-ext",
-                .simd128 => "simd128",
-                .tail_call => "tail-call",
-                .shared_mem => "shared-mem",
-            });
-        }
-    };
-
-    pub const Prefix = enum(u8) {
-        used = '+',
-        disallowed = '-',
-        required = '=',
-    };
-
-    pub fn format(feature: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
-        _ = opt;
-        _ = fmt;
-        try writer.print("{c} {}", .{ feature.prefix, feature.tag });
-    }
-};
-
-pub const known_features = std.StaticStringMap(Feature.Tag).initComptime(.{
-    .{ "atomics", .atomics },
-    .{ "bulk-memory", .bulk_memory },
-    .{ "exception-handling", .exception_handling },
-    .{ "extended-const", .extended_const },
-    .{ "half-precision", .half_precision },
-    .{ "multimemory", .multimemory },
-    .{ "multivalue", .multivalue },
-    .{ "mutable-globals", .mutable_globals },
-    .{ "nontrapping-fptoint", .nontrapping_fptoint },
-    .{ "reference-types", .reference_types },
-    .{ "relaxed-simd", .relaxed_simd },
-    .{ "sign-ext", .sign_ext },
-    .{ "simd128", .simd128 },
-    .{ "tail-call", .tail_call },
-    .{ "shared-mem", .shared_mem },
-});
-
-/// Parses an object file into atoms, for code and data sections
-fn parseSymbolIntoAtom(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol.Index) !Atom.Index {
-    const object = wasm.objectById(object_id) orelse
-        return wasm.zig_object.?.parseSymbolIntoAtom(wasm, symbol_index);
-    const comp = wasm.base.comp;
-    const gpa = comp.gpa;
-    const symbol = &object.symtable[@intFromEnum(symbol_index)];
-    const relocatable_data: Object.RelocatableData = switch (symbol.tag) {
-        .function => object.relocatable_data.get(.code).?[symbol.index - object.imported_functions_count],
-        .data => object.relocatable_data.get(.data).?[symbol.index],
-        .section => blk: {
-            const data = object.relocatable_data.get(.custom).?;
-            for (data) |dat| {
-                if (dat.section_index == symbol.index) {
-                    break :blk dat;
-                }
-            }
-            unreachable;
-        },
-        else => unreachable,
-    };
-    const final_index = try wasm.getMatchingSegment(object_id, symbol_index);
-    const atom_index = try wasm.createAtom(symbol_index, object_id.toOptional());
-    try wasm.appendAtomAtIndex(final_index, atom_index);
-
-    const atom = wasm.getAtomPtr(atom_index);
-    atom.size = relocatable_data.size;
-    atom.alignment = relocatable_data.getAlignment(object);
-    atom.code = std.ArrayListUnmanaged(u8).fromOwnedSlice(relocatable_data.data[0..relocatable_data.size]);
-    atom.original_offset = relocatable_data.offset;
-
-    const segment = wasm.segmentPtr(final_index);
-    if (relocatable_data.type == .data) { //code section and custom sections are 1-byte aligned
-        segment.alignment = segment.alignment.max(atom.alignment);
-    }
-
-    if (object.relocations.get(relocatable_data.section_index)) |relocations| {
-        const start = searchRelocStart(relocations, relocatable_data.offset);
-        const len = searchRelocEnd(relocations[start..], relocatable_data.offset + atom.size);
-        atom.relocs = std.ArrayListUnmanaged(Wasm.Relocation).fromOwnedSlice(relocations[start..][0..len]);
-        for (atom.relocs.items) |reloc| {
-            switch (reloc.relocation_type) {
-                .R_WASM_TABLE_INDEX_I32,
-                .R_WASM_TABLE_INDEX_I64,
-                .R_WASM_TABLE_INDEX_SLEB,
-                .R_WASM_TABLE_INDEX_SLEB64,
-                => {
-                    try wasm.function_table.put(gpa, .{
-                        .file = object_id.toOptional(),
-                        .index = @enumFromInt(reloc.index),
-                    }, 0);
-                },
-                .R_WASM_GLOBAL_INDEX_I32,
-                .R_WASM_GLOBAL_INDEX_LEB,
-                => {
-                    const sym = object.symtable[reloc.index];
-                    if (sym.tag != .global) {
-                        try wasm.got_symbols.append(gpa, .{
-                            .file = object_id.toOptional(),
-                            .index = @enumFromInt(reloc.index),
-                        });
-                    }
-                },
-                else => {},
-            }
-        }
-    }
-
-    return atom_index;
-}
-
-fn searchRelocStart(relocs: []const Wasm.Relocation, address: u32) usize {
-    var min: usize = 0;
-    var max: usize = relocs.len;
-    while (min < max) {
-        const index = (min + max) / 2;
-        const curr = relocs[index];
-        if (curr.offset < address) {
-            min = index + 1;
-        } else {
-            max = index;
-        }
-    }
-    return min;
-}
-
-fn searchRelocEnd(relocs: []const Wasm.Relocation, address: u32) usize {
-    for (relocs, 0..relocs.len) |reloc, index| {
-        if (reloc.offset > address) {
-            return index;
-        }
-    }
-    return relocs.len;
-}
-
 pub fn internString(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!String {
+    assert(mem.indexOfScalar(u8, bytes, 0) == null);
     const gpa = wasm.base.comp.gpa;
     const gop = try wasm.string_table.getOrPutContextAdapted(
         gpa,
@@ -4755,25 +2179,34 @@ pub fn internString(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!String {
 }
 
 pub fn getExistingString(wasm: *const Wasm, bytes: []const u8) ?String {
+    assert(mem.indexOfScalar(u8, bytes, 0) == null);
     return wasm.string_table.getKeyAdapted(bytes, @as(String.TableIndexAdapter, .{
         .bytes = wasm.string_bytes.items,
     }));
 }
 
-pub fn stringSlice(wasm: *const Wasm, index: String) [:0]const u8 {
-    const slice = wasm.string_bytes.items[@intFromEnum(index)..];
-    return slice[0..mem.indexOfScalar(u8, slice, 0).? :0];
+pub fn internValtypeList(wasm: *Wasm, valtype_list: []const std.wasm.Valtype) error{OutOfMemory}!ValtypeList {
+    return .fromString(try internString(wasm, @ptrCast(valtype_list)));
 }
 
-pub fn optionalStringSlice(wasm: *const Wasm, index: OptionalString) ?[:0]const u8 {
-    return stringSlice(wasm, index.unwrap() orelse return null);
+pub fn addFuncType(wasm: *Wasm, ft: FunctionType) error{OutOfMemory}!FunctionType.Index {
+    const gpa = wasm.base.comp.gpa;
+    const gop = try wasm.func_types.getOrPut(gpa, ft);
+    return @enumFromInt(gop.index);
 }
 
-pub fn castToString(wasm: *const Wasm, index: u32) String {
-    assert(index == 0 or wasm.string_bytes.items[index - 1] == 0);
-    return @enumFromInt(index);
+pub fn addExpr(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!Expr {
+    const gpa = wasm.base.comp.gpa;
+    // We can't use string table deduplication here since these expressions can
+    // have null bytes in them however it may be interesting to explore since
+    // it is likely for globals to share initialization values. Then again
+    // there may not be very many globals in total.
+    try wasm.string_bytes.appendSlice(gpa, bytes);
+    return @enumFromInt(wasm.string_bytes.items.len - bytes.len);
 }
 
-fn segmentPtr(wasm: *const Wasm, index: Segment.Index) *Segment {
-    return &wasm.segments.items[@intFromEnum(index)];
+pub fn addRelocatableDataPayload(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!DataSegment.Payload {
+    const gpa = wasm.base.comp.gpa;
+    try wasm.string_bytes.appendSlice(gpa, bytes);
+    return @enumFromInt(wasm.string_bytes.items.len - bytes.len);
 }
src/Zcu/PerThread.zig
@@ -1722,22 +1722,19 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
         // Correcting this failure will involve changing a type this function
         // depends on, hence triggering re-analysis of this function, so this
         // interacts correctly with incremental compilation.
-        // TODO: do we need to mark this failure anywhere? I don't think so, since compilation
-        // will fail due to the type error anyway.
     } else if (comp.bin_file) |lf| {
         lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) {
             error.OutOfMemory => return error.OutOfMemory,
-            error.AnalysisFail => {
-                assert(zcu.failed_codegen.contains(nav_index));
-            },
-            else => {
-                try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
+            error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)),
+            error.LinkFailure => assert(comp.link_diags.hasErrors()),
+            error.Overflow => {
+                try zcu.failed_codegen.putNoClobber(nav_index, try Zcu.ErrorMsg.create(
                     gpa,
                     zcu.navSrcLoc(nav_index),
                     "unable to codegen: {s}",
                     .{@errorName(err)},
                 ));
-                try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index }));
+                // Not a retryable failure.
             },
         };
     } else if (zcu.llvm_object) |llvm_object| {
@@ -3100,6 +3097,7 @@ pub fn populateTestFunctions(
 pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error{OutOfMemory}!void {
     const zcu = pt.zcu;
     const comp = zcu.comp;
+    const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
 
     const nav = zcu.intern_pool.getNav(nav_index);
@@ -3113,26 +3111,16 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error
     } else if (comp.bin_file) |lf| {
         lf.updateNav(pt, nav_index) catch |err| switch (err) {
             error.OutOfMemory => return error.OutOfMemory,
-            error.AnalysisFail => {
-                assert(zcu.failed_codegen.contains(nav_index));
-            },
-            else => {
-                const gpa = zcu.gpa;
-                try zcu.failed_codegen.ensureUnusedCapacity(gpa, 1);
-                zcu.failed_codegen.putAssumeCapacityNoClobber(nav_index, try Zcu.ErrorMsg.create(
+            error.CodegenFail => assert(zcu.failed_codegen.contains(nav_index)),
+            error.LinkFailure => assert(comp.link_diags.hasErrors()),
+            error.Overflow => {
+                try zcu.failed_codegen.putNoClobber(nav_index, try Zcu.ErrorMsg.create(
                     gpa,
                     zcu.navSrcLoc(nav_index),
                     "unable to codegen: {s}",
                     .{@errorName(err)},
                 ));
-                if (nav.analysis != null) {
-                    try zcu.retryable_failures.append(zcu.gpa, .wrap(.{ .nav_val = nav_index }));
-                } else {
-                    // TODO: we don't have a way to indicate that this failure is retryable!
-                    // Since these are really rare, we could as a cop-out retry the whole build next update.
-                    // But perhaps we can do better...
-                    @panic("TODO: retryable failure codegenning non-declaration Nav");
-                }
+                // Not a retryable failure.
             },
         };
     } else if (zcu.llvm_object) |llvm_object| {
src/codegen.zig
@@ -25,18 +25,19 @@ const Alignment = InternPool.Alignment;
 const dev = @import("dev.zig");
 
 pub const Result = union(enum) {
-    /// The `code` parameter passed to `generateSymbol` has the value ok.
+    /// The `code` parameter passed to `generateSymbol` has the value.
     ok,
-
     /// There was a codegen error.
     fail: *ErrorMsg,
 };
 
 pub const CodeGenError = error{
     OutOfMemory,
+    /// Compiler was asked to operate on a number larger than supported.
     Overflow,
+    /// Indicates the error is already stored in Zcu `failed_codegen`.
     CodegenFail,
-} || link.File.UpdateDebugInfoError;
+};
 
 fn devFeatureForBackend(comptime backend: std.builtin.CompilerBackend) dev.Feature {
     comptime assert(mem.startsWith(u8, @tagName(backend), "stage2_"));
@@ -49,7 +50,6 @@ fn importBackend(comptime backend: std.builtin.CompilerBackend) type {
         .stage2_arm => @import("arch/arm/CodeGen.zig"),
         .stage2_riscv64 => @import("arch/riscv64/CodeGen.zig"),
         .stage2_sparc64 => @import("arch/sparc64/CodeGen.zig"),
-        .stage2_wasm => @import("arch/wasm/CodeGen.zig"),
         .stage2_x86_64 => @import("arch/x86_64/CodeGen.zig"),
         else => unreachable,
     };
@@ -74,7 +74,6 @@ pub fn generateFunction(
         .stage2_arm,
         .stage2_riscv64,
         .stage2_sparc64,
-        .stage2_wasm,
         .stage2_x86_64,
         => |backend| {
             dev.check(devFeatureForBackend(backend));
@@ -96,9 +95,7 @@ pub fn generateLazyFunction(
     const target = zcu.fileByIndex(file).mod.resolved_target.result;
     switch (target_util.zigBackend(target, false)) {
         else => unreachable,
-        inline .stage2_x86_64,
-        .stage2_riscv64,
-        => |backend| {
+        inline .stage2_x86_64, .stage2_riscv64 => |backend| {
             dev.check(devFeatureForBackend(backend));
             return importBackend(backend).generateLazy(lf, pt, src_loc, lazy_sym, code, debug_output);
         },
@@ -694,6 +691,7 @@ fn lowerUavRef(
     offset: u64,
 ) CodeGenError!Result {
     const zcu = pt.zcu;
+    const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
     const target = lf.comp.root_mod.resolved_target.result;
 
@@ -704,7 +702,7 @@ fn lowerUavRef(
     const is_fn_body = uav_ty.zigTypeTag(zcu) == .@"fn";
     if (!is_fn_body and !uav_ty.hasRuntimeBits(zcu)) {
         try code.appendNTimes(0xaa, ptr_width_bytes);
-        return Result.ok;
+        return .ok;
     }
 
     const uav_align = ip.indexToKey(uav.orig_ty).ptr_type.flags.alignment;
@@ -714,6 +712,26 @@ fn lowerUavRef(
         .fail => |em| return .{ .fail = em },
     }
 
+    switch (lf.tag) {
+        .c => unreachable,
+        .spirv => unreachable,
+        .nvptx => unreachable,
+        .wasm => {
+            dev.check(link.File.Tag.wasm.devFeature());
+            const wasm = lf.cast(.wasm).?;
+            assert(reloc_parent == .none);
+            try wasm.relocations.append(gpa, .{
+                .tag = .uav_index,
+                .addend = @intCast(offset),
+                .offset = @intCast(code.items.len),
+                .pointee = .{ .uav_index = uav.val },
+            });
+            try code.appendNTimes(0, ptr_width_bytes);
+            return .ok;
+        },
+        else => {},
+    }
+
     const vaddr = try lf.getUavVAddr(uav_val, .{
         .parent = reloc_parent,
         .offset = code.items.len,
@@ -741,31 +759,52 @@ fn lowerNavRef(
 ) CodeGenError!Result {
     _ = src_loc;
     const zcu = pt.zcu;
+    const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
     const target = zcu.navFileScope(nav_index).mod.resolved_target.result;
 
-    const ptr_width = target.ptrBitWidth();
+    const ptr_width_bytes = @divExact(target.ptrBitWidth(), 8);
     const nav_ty = Type.fromInterned(ip.getNav(nav_index).typeOf(ip));
     const is_fn_body = nav_ty.zigTypeTag(zcu) == .@"fn";
     if (!is_fn_body and !nav_ty.hasRuntimeBits(zcu)) {
-        try code.appendNTimes(0xaa, @divExact(ptr_width, 8));
+        try code.appendNTimes(0xaa, ptr_width_bytes);
         return Result.ok;
     }
 
+    switch (lf.tag) {
+        .c => unreachable,
+        .spirv => unreachable,
+        .nvptx => unreachable,
+        .wasm => {
+            dev.check(link.File.Tag.wasm.devFeature());
+            const wasm = lf.cast(.wasm).?;
+            assert(reloc_parent == .none);
+            try wasm.relocations.append(gpa, .{
+                .tag = .nav_index,
+                .addend = @intCast(offset),
+                .offset = @intCast(code.items.len),
+                .pointee = .{ .nav_index = nav_index },
+            });
+            try code.appendNTimes(0, ptr_width_bytes);
+            return .ok;
+        },
+        else => {},
+    }
+
     const vaddr = try lf.getNavVAddr(pt, nav_index, .{
         .parent = reloc_parent,
         .offset = code.items.len,
         .addend = @intCast(offset),
     });
     const endian = target.cpu.arch.endian();
-    switch (ptr_width) {
-        16 => mem.writeInt(u16, try code.addManyAsArray(2), @intCast(vaddr), endian),
-        32 => mem.writeInt(u32, try code.addManyAsArray(4), @intCast(vaddr), endian),
-        64 => mem.writeInt(u64, try code.addManyAsArray(8), vaddr, endian),
+    switch (ptr_width_bytes) {
+        2 => mem.writeInt(u16, try code.addManyAsArray(2), @intCast(vaddr), endian),
+        4 => mem.writeInt(u32, try code.addManyAsArray(4), @intCast(vaddr), endian),
+        8 => mem.writeInt(u64, try code.addManyAsArray(8), vaddr, endian),
         else => unreachable,
     }
 
-    return Result.ok;
+    return .ok;
 }
 
 /// Helper struct to denote that the value is in memory but requires a linker relocation fixup:
src/Compilation.zig
@@ -2438,9 +2438,8 @@ fn flush(
     if (comp.bin_file) |lf| {
         // This is needed before reading the error flags.
         lf.flush(arena, tid, prog_node) catch |err| switch (err) {
-            error.FlushFailure, error.LinkFailure => {}, // error reported through link_diags.flags
-            error.LLDReportedFailure => {}, // error reported via lockAndParseLldStderr
-            else => |e| return e,
+            error.LinkFailure => {}, // Already reported.
+            error.OutOfMemory => return error.OutOfMemory,
         };
     }
 
src/link.zig
@@ -633,42 +633,15 @@ pub const File = struct {
     pub const FlushDebugInfoError = Dwarf.FlushError;
 
     pub const UpdateNavError = error{
-        OutOfMemory,
         Overflow,
-        Underflow,
-        FileTooBig,
-        InputOutput,
-        FilesOpenedWithWrongFlags,
-        IsDir,
-        NoSpaceLeft,
-        Unseekable,
-        PermissionDenied,
-        SwapFile,
-        CorruptedData,
-        SystemResources,
-        OperationAborted,
-        BrokenPipe,
-        ConnectionResetByPeer,
-        ConnectionTimedOut,
-        SocketNotConnected,
-        NotOpenForReading,
-        WouldBlock,
-        Canceled,
-        AccessDenied,
-        Unexpected,
-        DiskQuota,
-        NotOpenForWriting,
-        AnalysisFail,
+        OutOfMemory,
+        /// Indicates the error is already reported and stored in
+        /// `failed_codegen` on the Zcu.
         CodegenFail,
-        EmitFail,
-        NameTooLong,
-        CurrentWorkingDirectoryUnlinked,
-        LockViolation,
-        NetNameDeleted,
-        DeviceBusy,
-        InvalidArgument,
-        HotSwapUnavailableOnHostOperatingSystem,
-    } || UpdateDebugInfoError;
+        /// Indicates the error is already reported and stored in `link_diags`
+        /// on the Compilation.
+        LinkFailure,
+    };
 
     /// Called from within CodeGen to retrieve the symbol index of a global symbol.
     /// If no symbol exists yet with this name, a new undefined global symbol will
@@ -771,83 +744,11 @@ pub const File = struct {
         }
     }
 
-    /// TODO audit this error set. most of these should be collapsed into one error,
-    /// and Diags.Flags should be updated to convey the meaning to the user.
     pub const FlushError = error{
-        CacheCheckFailed,
-        CurrentWorkingDirectoryUnlinked,
-        DivisionByZero,
-        DllImportLibraryNotFound,
-        ExpectedFuncType,
-        FailedToEmit,
-        FileSystem,
-        FilesOpenedWithWrongFlags,
-        /// Deprecated. Use `LinkFailure` instead.
-        /// Formerly used to indicate an error will be present in `Compilation.link_errors`.
-        FlushFailure,
         /// Indicates an error will be present in `Compilation.link_errors`.
         LinkFailure,
-        FunctionSignatureMismatch,
-        GlobalTypeMismatch,
-        HotSwapUnavailableOnHostOperatingSystem,
-        InvalidCharacter,
-        InvalidEntryKind,
-        InvalidFeatureSet,
-        InvalidFormat,
-        InvalidIndex,
-        InvalidInitFunc,
-        InvalidMagicByte,
-        InvalidWasmVersion,
-        LLDCrashed,
-        LLDReportedFailure,
-        LLD_LinkingIsTODO_ForSpirV,
-        LibCInstallationMissingCrtDir,
-        LibCInstallationNotAvailable,
-        LinkingWithoutZigSourceUnimplemented,
-        MalformedArchive,
-        MalformedDwarf,
-        MalformedSection,
-        MemoryTooBig,
-        MemoryTooSmall,
-        MissAlignment,
-        MissingEndForBody,
-        MissingEndForExpression,
-        MissingSymbol,
-        MissingTableSymbols,
-        ModuleNameMismatch,
-        NoObjectsToLink,
-        NotObjectFile,
-        NotSupported,
         OutOfMemory,
-        Overflow,
-        PermissionDenied,
-        StreamTooLong,
-        SwapFile,
-        SymbolCollision,
-        SymbolMismatchingType,
-        TODOImplementPlan9Objs,
-        TODOImplementWritingLibFiles,
-        UnableToSpawnSelf,
-        UnableToSpawnWasm,
-        UnableToWriteArchive,
-        UndefinedLocal,
-        UndefinedSymbol,
-        Underflow,
-        UnexpectedRemainder,
-        UnexpectedTable,
-        UnexpectedValue,
-        UnknownFeature,
-        UnrecognizedVolume,
-        Unseekable,
-        UnsupportedCpuArchitecture,
-        UnsupportedVersion,
-        UnexpectedEndOfFile,
-    } ||
-        fs.File.WriteFileError ||
-        fs.File.OpenError ||
-        std.process.Child.SpawnError ||
-        fs.Dir.CopyFileError ||
-        FlushDebugInfoError;
+    };
 
     /// Commit pending changes and write headers. Takes into account final output mode
     /// and `use_lld`, not only `effectiveOutputMode`.
@@ -864,7 +765,12 @@ pub const File = struct {
             assert(comp.c_object_table.count() == 1);
             const the_key = comp.c_object_table.keys()[0];
             const cached_pp_file_path = the_key.status.success.object_path;
-            try cached_pp_file_path.root_dir.handle.copyFile(cached_pp_file_path.sub_path, emit.root_dir.handle, emit.sub_path, .{});
+            cached_pp_file_path.root_dir.handle.copyFile(cached_pp_file_path.sub_path, emit.root_dir.handle, emit.sub_path, .{}) catch |err| {
+                const diags = &base.comp.link_diags;
+                return diags.fail("failed to copy '{'}' to '{'}': {s}", .{
+                    @as(Path, cached_pp_file_path), @as(Path, emit), @errorName(err),
+                });
+            };
             return;
         }
 
@@ -893,16 +799,6 @@ pub const File = struct {
         }
     }
 
-    /// Called when a Decl is deleted from the Zcu.
-    pub fn freeDecl(base: *File, decl_index: InternPool.DeclIndex) void {
-        switch (base.tag) {
-            inline else => |tag| {
-                dev.check(tag.devFeature());
-                @as(*tag.Type(), @fieldParentPtr("base", base)).freeDecl(decl_index);
-            },
-        }
-    }
-
     pub const UpdateExportsError = error{
         OutOfMemory,
         AnalysisFail,
@@ -932,6 +828,7 @@ pub const File = struct {
         addend: u32,
 
         pub const Parent = union(enum) {
+            none,
             atom_index: u32,
             debug_output: DebugInfoOutput,
         };
@@ -948,6 +845,7 @@ pub const File = struct {
             .c => unreachable,
             .spirv => unreachable,
             .nvptx => unreachable,
+            .wasm => unreachable,
             inline else => |tag| {
                 dev.check(tag.devFeature());
                 return @as(*tag.Type(), @fieldParentPtr("base", base)).getNavVAddr(pt, nav_index, reloc_info);
@@ -966,6 +864,7 @@ pub const File = struct {
             .c => unreachable,
             .spirv => unreachable,
             .nvptx => unreachable,
+            .wasm => unreachable,
             inline else => |tag| {
                 dev.check(tag.devFeature());
                 return @as(*tag.Type(), @fieldParentPtr("base", base)).lowerUav(pt, decl_val, decl_align, src_loc);
@@ -978,6 +877,7 @@ pub const File = struct {
             .c => unreachable,
             .spirv => unreachable,
             .nvptx => unreachable,
+            .wasm => unreachable,
             inline else => |tag| {
                 dev.check(tag.devFeature());
                 return @as(*tag.Type(), @fieldParentPtr("base", base)).getUavVAddr(decl_val, reloc_info);
@@ -1099,6 +999,26 @@ pub const File = struct {
         }
     }
 
+    /// Called when all linker inputs have been sent via `loadInput`. After
+    /// this, `loadInput` will not be called anymore.
+    pub fn prelink(base: *File) FlushError!void {
+        const use_lld = build_options.have_llvm and base.comp.config.use_lld;
+        if (use_lld) return;
+
+        // In this case, an object file is created by the LLVM backend, so
+        // there is no prelink phase. The Zig code is linked as a standard
+        // object along with the others.
+        if (base.zcu_object_sub_path != null) return;
+
+        switch (base.tag) {
+            inline .wasm => |tag| {
+                dev.check(tag.devFeature());
+                return @as(*tag.Type(), @fieldParentPtr("base", base)).prelink();
+            },
+            else => {},
+        }
+    }
+
     pub fn linkAsArchive(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
         dev.check(.lld_linker);
 
src/main.zig
@@ -75,6 +75,10 @@ pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
     process.exit(1);
 }
 
+/// Shaming all the locations that inappropriately use an O(N) search algorithm.
+/// Please delete this and fix the compilation errors!
+pub const @"bad O(N)" = void;
+
 const normal_usage =
     \\Usage: zig [command] [options]
     \\
src/register_manager.zig
@@ -14,19 +14,14 @@ const link = @import("link.zig");
 
 const log = std.log.scoped(.register_manager);
 
-pub const AllocateRegistersError = error{
-    /// No registers are available anymore
+pub const AllocationError = error{
     OutOfRegisters,
-    /// Can happen when spilling an instruction in codegen runs out of
-    /// memory, so we propagate that error
     OutOfMemory,
-    /// Can happen when spilling an instruction in codegen triggers integer
-    /// overflow, so we propagate that error
+    /// Compiler was asked to operate on a number larger than supported.
     Overflow,
-    /// Can happen when spilling an instruction triggers a codegen
-    /// error, so we propagate that error
+    /// Indicates the error is already stored in `failed_codegen` on the Zcu.
     CodegenFail,
-} || link.File.UpdateDebugInfoError;
+};
 
 pub fn RegisterManager(
     comptime Function: type,
@@ -281,7 +276,7 @@ pub fn RegisterManager(
             comptime count: comptime_int,
             insts: [count]?Air.Inst.Index,
             register_class: RegisterBitSet,
-        ) AllocateRegistersError![count]Register {
+        ) AllocationError![count]Register {
             comptime assert(count > 0 and count <= tracked_registers.len);
 
             var locked_registers = self.locked_registers;
@@ -338,7 +333,7 @@ pub fn RegisterManager(
             self: *Self,
             inst: ?Air.Inst.Index,
             register_class: RegisterBitSet,
-        ) AllocateRegistersError!Register {
+        ) AllocationError!Register {
             return (try self.allocRegs(1, .{inst}, register_class))[0];
         }
 
@@ -349,7 +344,7 @@ pub fn RegisterManager(
             self: *Self,
             tracked_index: TrackedIndex,
             inst: ?Air.Inst.Index,
-        ) AllocateRegistersError!void {
+        ) AllocationError!void {
             log.debug("getReg {} for inst {?}", .{ regAtTrackedIndex(tracked_index), inst });
             if (!self.isRegIndexFree(tracked_index)) {
                 // Move the instruction that was previously there to a
@@ -362,7 +357,7 @@ pub fn RegisterManager(
             }
             self.getRegIndexAssumeFree(tracked_index, inst);
         }
-        pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) AllocateRegistersError!void {
+        pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) AllocationError!void {
             log.debug("getting reg: {}", .{reg});
             return self.getRegIndex(indexOfRegIntoTracked(reg) orelse return, inst);
         }
@@ -370,7 +365,7 @@ pub fn RegisterManager(
             self: *Self,
             comptime reg: Register,
             inst: ?Air.Inst.Index,
-        ) AllocateRegistersError!void {
+        ) AllocationError!void {
             return self.getRegIndex((comptime indexOfRegIntoTracked(reg)) orelse return, inst);
         }
 
src/Zcu.zig
@@ -524,6 +524,15 @@ pub const Export = struct {
         section: InternPool.OptionalNullTerminatedString = .none,
         visibility: std.builtin.SymbolVisibility = .default,
     };
+
+    /// Index into `all_exports`.
+    pub const Index = enum(u32) {
+        _,
+
+        pub fn ptr(i: Index, zcu: *const Zcu) *Export {
+            return &zcu.all_exports.items[@intFromEnum(i)];
+        }
+    };
 };
 
 pub const Reference = struct {
CMakeLists.txt
@@ -643,9 +643,9 @@ set(ZIG_STAGE2_SOURCES
     src/link/StringTable.zig
     src/link/Wasm.zig
     src/link/Wasm/Archive.zig
+    src/link/Wasm/Flush.zig
     src/link/Wasm/Object.zig
     src/link/Wasm/Symbol.zig
-    src/link/Wasm/ZigObject.zig
     src/link/aarch64.zig
     src/link/riscv.zig
     src/link/table_section.zig