Commit ba0e84a411

Luuk de Gram <luuk@degram.dev>
2024-01-13 17:48:21
wasm: move Zig module-linkage to ZigObject
Rather than specializing the linker-driver to be able to handle objects generated by a ZCU, we store all data in-memory in ZigObject. ZigObject acts more like a regular object file which will allow us to treat it as us. This will make linking much more simple, but will also reduce the complexity of incremental-linking as we can simply update ZigObject and relink it.
1 parent 5c0766b
Changed files (1)
src
link
src/link/Wasm/ZigObject.zig
@@ -0,0 +1,998 @@
+//! ZigObject encapsulates the state of the incrementally compiled Zig module.
+//! It stores the associated input local and global symbols, allocated atoms,
+//! and any relocations that may have been emitted.
+//! Think about this as fake in-memory Object file for the Zig module.
+
+/// List of all `Decl` that are currently alive.
+/// Each index maps to the corresponding `Atom.Index`.
+decls: std.AutoHashMapUnmanaged(InternPool.DeclIndex, Atom.Index) = .{},
+/// List of function type signatures for this Zig module.
+func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
+/// Map of symbol locations, represented by its `types.Import`.
+imports: std.AutoHashMapUnmanaged(u32, types.Import) = .{},
+/// List of WebAssembly globals.
+globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
+/// Mapping between an `Atom` and its type index representing the Wasm
+/// type of the function signature.
+atom_types: std.AutoHashMapUnmanaged(Atom.Index, u32) = .{},
+/// List of all symbols generated by Zig code.
+symbols: std.ArrayListUnmanaged(Symbol) = .{},
+/// Map from symbol name offset to their index into the `symbols` list.
+global_syms: std.AutoHashMapUnmanaged(u32, u32) = .{},
+/// List of symbol indexes which are free to be used.
+symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
+/// Extra metadata about the linking section, such as alignment of segments and their name.
+segment_info: std.ArrayListUnmanage(types.Segment) = &.{},
+/// File encapsulated string table, used to deduplicate strings within the generated file.
+string_table: StringTable = .{},
+/// Map for storing anonymous declarations. Each anonymous decl maps to its Atom's index.
+anon_decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, Atom.Index) = .{},
+/// 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: ?u32 = 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: u32,
+
+/// 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, gpa: std.mem.Allocator) void {
+    for (zig_object.segment_info.values()) |segment_info| {
+        gpa.free(segment_info.name);
+    }
+
+    // For decls and anon decls we free the memory of its atoms.
+    // The memory of atoms parsed from object files is managed by
+    // the object file itself, and therefore we can skip those.
+    {
+        var it = zig_object.decls.valueIterator();
+        while (it.next()) |atom_index_ptr| {
+            const atom = zig_object.getAtomPtr(atom_index_ptr.*);
+            for (atom.locals.items) |local_index| {
+                const local_atom = zig_object.getAtomPtr(local_index);
+                local_atom.deinit(gpa);
+            }
+            atom.deinit(gpa);
+        }
+    }
+    {
+        for (zig_object.anon_decls.values()) |atom_index| {
+            const atom = zig_object.getAtomPtr(atom_index);
+            for (atom.locals.items) |local_index| {
+                const local_atom = zig_object.getAtomPtr(local_index);
+                local_atom.deinit(gpa);
+            }
+            atom.deinit(gpa);
+        }
+    }
+    zig_object.decls.deinit(gpa);
+    zig_object.anon_decls.deinit(gpa);
+    zig_object.symbols.deinit(gpa);
+    zig_object.symbols_free_list.deinit(gpa);
+    zig_object.segment_info.deinit(gpa);
+
+    zig_object.string_table.deinit(gpa);
+    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) !u32 {
+    try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
+    const symbol: Symbol = .{
+        .name = std.math.maxInt(u32), // 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[index] = symbol;
+        return index;
+    }
+    const index = @as(u32, @intCast(zig_object.symbols.items.len));
+    zig_object.symbols.appendAssumeCapacity(symbol);
+    return index;
+}
+
+// Generate code for the Decl, storing it in memory to be later written to
+// the file on flush().
+pub fn updateDecl(zig_object: *ZigObject, wasm_file: *Wasm, mod: *Module, decl_index: InternPool.DeclIndex) !void {
+    const decl = mod.declPtr(decl_index);
+    if (decl.val.getFunction(mod)) |_| {
+        return;
+    } else if (decl.val.getExternFunc(mod)) |_| {
+        return;
+    }
+
+    const gpa = wasm_file.base.comp.gpa;
+    const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index);
+    const atom = wasm_file.getAtomPtr(atom_index);
+    atom.clear();
+
+    if (decl.isExtern(mod)) {
+        const variable = decl.getOwnedVariable(mod).?;
+        const name = mod.intern_pool.stringToSlice(decl.name);
+        const lib_name = mod.intern_pool.stringToSliceUnwrap(variable.lib_name);
+        return wasm_file.addOrUpdateImport(name, atom.sym_index, lib_name, null);
+    }
+    const val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
+
+    var code_writer = std.ArrayList(u8).init(gpa);
+    defer code_writer.deinit();
+
+    const res = try codegen.generateSymbol(
+        &wasm_file.base,
+        decl.srcLoc(mod),
+        .{ .ty = decl.ty, .val = val },
+        &code_writer,
+        .none,
+        .{ .parent_atom_index = atom.sym_index },
+    );
+
+    const code = switch (res) {
+        .ok => code_writer.items,
+        .fail => |em| {
+            decl.analysis = .codegen_failure;
+            try mod.failed_decls.put(mod.gpa, decl_index, em);
+            return;
+        },
+    };
+
+    return wasm_file.finishUpdateDecl(decl_index, code, .data);
+}
+
+pub fn updateFunc(zig_object: *ZigObject, wasm_file: *Wasm, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+    const gpa = wasm_file.base.comp.gpa;
+    const func = mod.funcInfo(func_index);
+    const decl_index = func.owner_decl;
+    const decl = mod.declPtr(decl_index);
+    const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index);
+    const atom = wasm_file.getAtomPtr(atom_index);
+    atom.clear();
+
+    var code_writer = std.ArrayList(u8).init(gpa);
+    defer code_writer.deinit();
+    const result = try codegen.generateFunction(
+        &wasm_file.base,
+        decl.srcLoc(mod),
+        func_index,
+        air,
+        liveness,
+        &code_writer,
+        .none,
+    );
+
+    const code = switch (result) {
+        .ok => code_writer.items,
+        .fail => |em| {
+            decl.analysis = .codegen_failure;
+            try mod.failed_decls.put(mod.gpa, decl_index, em);
+            return;
+        },
+    };
+
+    return zig_object.finishUpdateDecl(wasm_file, decl_index, code, .function);
+}
+
+fn finishUpdateDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex, code: []const u8, symbol_tag: Symbol.Tag) !void {
+    const gpa = wasm_file.base.comp.gpa;
+    const mod = wasm_file.base.comp.module.?;
+    const decl = mod.declPtr(decl_index);
+    const atom_index = zig_object.decls.get(decl_index).?;
+    const atom = wasm_file.getAtomPtr(atom_index);
+    const symbol = &zig_object.symbols.items[atom.sym_index];
+    const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
+    symbol.name = try zig_object.string_table.insert(gpa, full_name);
+    symbol.tag = symbol_tag;
+    try atom.code.appendSlice(gpa, code);
+    try wasm_file.resolved_symbols.put(gpa, atom.symbolLoc(), {});
+
+    atom.size = @intCast(code.len);
+    if (code.len == 0) return;
+    atom.alignment = decl.getAlignment(mod);
+}
+
+/// 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 getOrCreateAtomForDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) !Atom.Index {
+    const gpa = wasm_file.base.comp.gpa;
+    const gop = try zig_object.decls.getOrPut(gpa, decl_index);
+    if (!gop.found_existing) {
+        const atom_index = try wasm_file.createAtom();
+        gop.value_ptr.* = atom_index;
+        const atom = wasm_file.getAtom(atom_index);
+        const symbol = atom.symbolLoc().getSymbol(wasm_file);
+        const mod = wasm_file.base.comp.module.?;
+        const decl = mod.declPtr(decl_index);
+        const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
+        symbol.name = try wasm_file.string_table.insert(gpa, full_name);
+    }
+    return gop.value_ptr.*;
+}
+
+pub fn lowerAnonDecl(
+    zig_object: *ZigObject,
+    wasm_file: *Wasm,
+    decl_val: InternPool.Index,
+    explicit_alignment: InternPool.Alignment,
+    src_loc: Module.SrcLoc,
+) !codegen.Result {
+    const gpa = wasm_file.base.comp.gpa;
+    const gop = try zig_object.anon_decls.getOrPut(gpa, decl_val);
+    if (!gop.found_existing) {
+        const mod = wasm_file.base.comp.module.?;
+        const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
+        const tv: TypedValue = .{ .ty = ty, .val = Value.fromInterned(decl_val) };
+        var name_buf: [32]u8 = undefined;
+        const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
+            @intFromEnum(decl_val),
+        }) catch unreachable;
+
+        switch (try zig_object.lowerConst(name, tv, src_loc)) {
+            .ok => |atom_index| zig_object.anon_decls.values()[gop.index] = atom_index,
+            .fail => |em| return .{ .fail = em },
+        }
+    }
+
+    const atom = wasm_file.getAtomPtr(zig_object.anon_decls.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 .ok;
+}
+
+/// Lowers a constant typed value to a local symbol and atom.
+/// Returns the symbol index of the local
+/// The given `decl` is the parent decl whom owns the constant.
+pub fn lowerUnnamedConst(zig_object: *ZigObject, wasm_file: *Wasm, tv: TypedValue, decl_index: InternPool.DeclIndex) !u32 {
+    const gpa = wasm_file.base.comp.gpa;
+    const mod = wasm_file.base.comp.module.?;
+    std.debug.assert(tv.ty.zigTypeTag(mod) != .Fn); // cannot create local symbols for functions
+    const decl = mod.declPtr(decl_index);
+
+    const parent_atom_index = try zig_object.getOrCreateAtomForDecl(decl_index);
+    const parent_atom = wasm_file.getAtom(parent_atom_index);
+    const local_index = parent_atom.locals.items.len;
+    const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
+    const name = try std.fmt.allocPrintZ(gpa, "__unnamed_{s}_{d}", .{
+        fqn, local_index,
+    });
+    defer gpa.free(name);
+
+    switch (try zig_object.lowerConst(name, tv, decl.srcLoc(mod))) {
+        .ok => |atom_index| {
+            try wasm_file.getAtomPtr(parent_atom_index).locals.append(gpa, atom_index);
+            return wasm_file.getAtom(atom_index).getSymbolIndex().?;
+        },
+        .fail => |em| {
+            decl.analysis = .codegen_failure;
+            try mod.failed_decls.put(mod.gpa, decl_index, em);
+            return error.CodegenFail;
+        },
+    }
+}
+
+const LowerConstResult = union(enum) {
+    ok: Atom.Index,
+    fail: *Module.ErrorMsg,
+};
+
+fn lowerConst(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8, tv: TypedValue, src_loc: Module.SrcLoc) !LowerConstResult {
+    const gpa = wasm_file.base.comp.gpa;
+    const mod = wasm_file.base.comp.module.?;
+
+    // Create and initialize a new local symbol and atom
+    const atom_index = try wasm_file.createAtom();
+    var value_bytes = std.ArrayList(u8).init(gpa);
+    defer value_bytes.deinit();
+
+    const code = code: {
+        const atom = wasm_file.getAtomPtr(atom_index);
+        atom.alignment = tv.ty.abiAlignment(mod);
+        zig_object.symbols.items[atom.sym_index] = .{
+            .name = try zig_object.string_table.insert(gpa, name),
+            .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
+            .tag = .data,
+            .index = undefined,
+            .virtual_address = undefined,
+        };
+
+        const result = try codegen.generateSymbol(
+            &wasm_file.base,
+            src_loc,
+            tv,
+            &value_bytes,
+            .none,
+            .{
+                .parent_atom_index = atom.sym_index,
+                .addend = null,
+            },
+        );
+        break :code switch (result) {
+            .ok => value_bytes.items,
+            .fail => |em| {
+                return .{ .fail = em };
+            },
+        };
+    };
+
+    const atom = wasm_file.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_file: *Wasm) !u32 {
+    if (zig_object.error_table_symbol) |symbol| {
+        return 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_file.base.gpa;
+    const sym_index = try zig_object.allocateSymbol(gpa);
+    const atom_index = try wasm_file.createAtom(sym_index);
+    const atom = wasm_file.getAtomPtr(atom_index);
+    const slice_ty = Type.slice_const_u8_sentinel_0;
+    const mod = wasm_file.base.comp.module.?;
+    atom.alignment = slice_ty.abiAlignment(mod);
+
+    const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_name_table");
+    const symbol = &zig_object.symbols.items[sym_index];
+    symbol.* = .{
+        .name = sym_name,
+        .tag = .data,
+        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
+        .index = 0,
+        .virtual_address = undefined,
+    };
+    symbol.mark();
+
+    log.debug("Error name table was created with symbol index: ({d})", .{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_file: *Wasm) !void {
+    const symbol_index = zig_object.error_table_symbol orelse return;
+    const gpa = wasm_file.base.comp.gpa;
+    const atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = symbol_index }).?;
+
+    // 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_file.createAtom(names_sym_index);
+    const names_atom = wasm_file.getAtomPtr(names_atom_index);
+    names_atom.alignment = .@"1";
+    const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_names");
+    const names_symbol = &zig_object.symbols.items[names_sym_index];
+    names_symbol.* = .{
+        .name = sym_name,
+        .tag = .data,
+        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
+        .index = 0,
+        .virtual_address = undefined,
+    };
+    names_symbol.mark();
+
+    log.debug("Populating error names", .{});
+
+    // Addend for each relocation to the table
+    var addend: u32 = 0;
+    const mod = wasm_file.base.comp.module.?;
+    for (mod.global_error_set.keys()) |error_name_nts| {
+        const atom = wasm_file.getAtomPtr(atom_index);
+
+        const error_name = mod.intern_pool.stringToSlice(error_name_nts);
+        const len = @as(u32, @intCast(error_name.len + 1)); // names are 0-termianted
+
+        const slice_ty = Type.slice_const_u8_sentinel_0;
+        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 = names_atom.sym_index,
+            .relocation_type = .R_WASM_MEMORY_ADDR_I32,
+            .offset = offset,
+            .addend = @as(i32, @intCast(addend)),
+        });
+        atom.size += @as(u32, @intCast(slice_ty.abiSize(mod)));
+        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);
+        names_atom.code.appendAssumeCapacity(0);
+
+        log.debug("Populated error name: '{s}'", .{error_name});
+    }
+    names_atom.size = addend;
+
+    // link the atoms with the rest of the binary so they can be allocated
+    // and relocations will be performed.
+    try wasm_file.parseAtom(atom_index, .{ .data = .read_only });
+    try wasm_file.parseAtom(names_atom_index, .{ .data = .read_only });
+}
+
+/// 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_file: *Wasm,
+    /// Name of the import
+    name: []const u8,
+    /// Symbol index that is external
+    symbol_index: u32,
+    /// 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_file.base.comp.gpa;
+    std.debug.assert(symbol_index != 0);
+    // 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 = lib_name != null and
+        !std.mem.eql(u8, lib_name.?, "c");
+    const full_name = if (mangle_name) full_name: {
+        break :full_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 zig_object.string_table.insert(gpa, full_name);
+    const symbol: *Symbol = &zig_object.symbols.items[symbol_index];
+    symbol.setUndefined(true);
+    symbol.setGlobal(true);
+    symbol.name = decl_name_index;
+    if (mangle_name) {
+        // we specified a specific name for the symbol that does not match the import name
+        symbol.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) |l_name| blk: {
+            break :blk l_name;
+        } else wasm_file.host_name;
+        if (!gop.found_existing) {
+            gop.value_ptr.* = .{
+                .module_name = try zig_object.string_table.insert(gpa, module_name),
+                .name = try zig_object.string_table.insert(gpa, name),
+                .kind = .{ .function = ty_index },
+            };
+            zig_object.imported_functions_count += 1;
+        }
+    }
+}
+
+/// 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, wasm_file: *Wasm, name: []const u8) !u32 {
+    const gpa = wasm_file.base.comp.gpa;
+    const name_index = try zig_object.string_table.insert(gpa, name);
+    const gop = try zig_object.global_syms.getOrPut(gpa, name_index);
+    if (gop.found_existing) {
+        return gop.value_ptr.index;
+    }
+
+    var symbol: Symbol = .{
+        .name = name_index,
+        .flags = 0,
+        .index = undefined, // index to type will be set after merging function symbols
+        .tag = .function,
+        .virtual_address = undefined,
+    };
+    symbol.setGlobal(true);
+    symbol.setUndefined(true);
+
+    const sym_index = if (zig_object.symbol.popOrNull()) |index| index else blk: {
+        const index: u32 = @intCast(zig_object.symbols.items.len);
+        try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
+        zig_object.symbols.items.len += 1;
+        break :blk index;
+    };
+    zig_object.symbols.items[sym_index] = symbol;
+    gop.value_ptr.* = .{ .index = sym_index, .file = null };
+    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 getDeclVAddr(
+    zig_object: *ZigObject,
+    wasm_file: *Wasm,
+    decl_index: InternPool.DeclIndex,
+    reloc_info: link.File.RelocInfo,
+) !u64 {
+    const target = wasm_file.base.comp.root_mod.resolved_target.result;
+    const gpa = wasm_file.base.comp.gpa;
+    const mod = wasm_file.base.comp.module.?;
+    const decl = mod.declPtr(decl_index);
+
+    const target_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
+    const target_symbol_index = wasm_file.getAtom(target_atom_index).sym_index;
+
+    std.debug.assert(reloc_info.parent_atom_index != 0);
+    const atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = reloc_info.parent_atom_index }).?;
+    const atom = wasm_file.getAtomPtr(atom_index);
+    const is_wasm32 = target.cpu.arch == .wasm32;
+    if (decl.ty.zigTypeTag(mod) == .Fn) {
+        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 getAnonDeclVAddr(
+    zig_object: *ZigObject,
+    wasm_file: *Wasm,
+    decl_val: InternPool.Index,
+    reloc_info: link.File.RelocInfo,
+) !u64 {
+    const gpa = wasm_file.base.comp.gpa;
+    const target = wasm_file.base.comp.root_mod.resolved_target.result;
+    const atom_index = zig_object.anon_decls.get(decl_val).?;
+    const target_symbol_index = wasm_file.getAtom(atom_index).getSymbolIndex().?;
+
+    const parent_atom_index = wasm_file.symbol_atom.get(.{ .file = null, .index = reloc_info.parent_atom_index }).?;
+    const parent_atom = wasm_file.getAtomPtr(parent_atom_index);
+    const is_wasm32 = target.cpu.arch == .wasm32;
+    const mod = wasm_file.base.comp.module.?;
+    const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
+    if (ty.zigTypeTag(mod) == .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 deleteDeclExport(
+    zig_object: *ZigObject,
+    wasm_file: *Wasm,
+    decl_index: InternPool.DeclIndex,
+) void {
+    const atom_index = zig_object.decls.get(decl_index) orelse return;
+    const sym_index = wasm_file.getAtom(atom_index).sym_index;
+    const loc: Wasm.SymbolLoc = .{ .file = null, .index = sym_index };
+    const symbol = loc.getSymbol(wasm_file);
+    std.debug.assert(zig_object.global_syms.remove(symbol.name));
+}
+
+pub fn updateExports(
+    zig_object: *ZigObject,
+    wasm_file: *Wasm,
+    mod: *Module,
+    exported: Module.Exported,
+    exports: []const *Module.Export,
+) !void {
+    const decl_index = switch (exported) {
+        .decl_index => |i| i,
+        .value => |val| {
+            _ = val;
+            @panic("TODO: implement Wasm linker code for exporting a constant value");
+        },
+    };
+    const decl = mod.declPtr(decl_index);
+    const atom_index = try zig_object.getOrCreateAtomForDecl(decl_index);
+    const atom = wasm_file.getAtom(atom_index);
+    const atom_sym = atom.symbolLoc().getSymbol(wasm_file).*;
+    const gpa = mod.gpa;
+
+    for (exports) |exp| {
+        if (mod.intern_pool.stringToSliceUnwrap(exp.opts.section)) |section| {
+            try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
+                gpa,
+                decl.srcLoc(mod),
+                "Unimplemented: ExportOptions.section '{s}'",
+                .{section},
+            ));
+            continue;
+        }
+
+        const exported_decl_index = switch (exp.exported) {
+            .value => {
+                try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
+                    gpa,
+                    decl.srcLoc(mod),
+                    "Unimplemented: exporting a named constant value",
+                    .{},
+                ));
+                continue;
+            },
+            .decl_index => |i| i,
+        };
+        const exported_atom_index = try zig_object.getOrCreateAtomForDecl(exported_decl_index);
+        const exported_atom = wasm_file.getAtom(exported_atom_index);
+        // const export_name = try zig_object.string_table.put(gpa, mod.intern_pool.stringToSlice(exp.opts.name));
+        const sym_loc = exported_atom.symbolLoc();
+        const symbol = sym_loc.getSymbol(wasm_file);
+        symbol.setGlobal(true);
+        symbol.setUndefined(false);
+        symbol.index = atom_sym.index;
+        symbol.tag = atom_sym.tag;
+        symbol.name = atom_sym.name;
+
+        switch (exp.opts.linkage) {
+            .Internal => {
+                symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
+                symbol.setFlag(.WASM_SYM_BINDING_WEAK);
+            },
+            .Weak => {
+                symbol.setFlag(.WASM_SYM_BINDING_WEAK);
+            },
+            .Strong => {}, // symbols are strong by default
+            .LinkOnce => {
+                try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
+                    gpa,
+                    decl.srcLoc(mod),
+                    "Unimplemented: LinkOnce",
+                    .{},
+                ));
+                continue;
+            },
+        }
+
+        // TODO: Revisit this
+        // if (zig_object.global_syms.get(export_name)) |existing_loc| {
+        //     if (existing_loc.index == atom.sym_index) continue;
+        //     const existing_sym: Symbol = existing_loc.getSymbol(wasm_file).*;
+
+        //     if (!existing_sym.isUndefined()) blk: {
+        //         if (symbol.isWeak()) {
+        //             try wasm_file.discarded.put(gpa, existing_loc, sym_loc);
+        //             continue; // to-be-exported symbol is weak, so we keep the existing symbol
+        //         }
+
+        //         // new symbol is not weak while existing is, replace existing symbol
+        //         if (existing_sym.isWeak()) {
+        //             break :blk;
+        //         }
+        //         // When both the to-be-exported symbol and the already existing symbol
+        //         // are strong symbols, we have a linker error.
+        //         // In the other case we replace one with the other.
+        //         try mod.failed_exports.put(gpa, exp, try Module.ErrorMsg.create(
+        //             gpa,
+        //             decl.srcLoc(mod),
+        //             \\LinkError: symbol '{}' defined multiple times
+        //             \\  first definition in '{s}'
+        //             \\  next definition in '{s}'
+        //         ,
+        //             .{ exp.opts.name.fmt(&mod.intern_pool), wasm_file.name, wasm_file.name },
+        //         ));
+        //         continue;
+        //     }
+
+        //     // in this case the existing symbol must be replaced either because it's weak or undefined.
+        //     try wasm.discarded.put(gpa, existing_loc, sym_loc);
+        //     _ = wasm.imports.remove(existing_loc);
+        //     _ = wasm.undefs.swapRemove(existing_sym.name);
+        // }
+
+        // // Ensure the symbol will be exported using the given name
+        // if (!mod.intern_pool.stringEqlSlice(exp.opts.name, sym_loc.getName(wasm))) {
+        //     try wasm.export_names.put(gpa, sym_loc, export_name);
+        // }
+
+        // try wasm.globals.put(
+        //     gpa,
+        //     export_name,
+        //     sym_loc,
+        // );
+    }
+}
+
+pub fn freeDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) void {
+    const gpa = wasm_file.base.comp.gpa;
+    const mod = wasm_file.base.comp.module.?;
+    const decl = mod.declPtr(decl_index);
+    const atom_index = zig_object.decls.get(decl_index).?;
+    const atom = wasm_file.getAtomPtr(atom_index);
+    zig_object.symbols_free_list.append(gpa, atom.sym_index) catch {};
+    _ = zig_object.decls.remove(decl_index);
+    zig_object.symbols.items[atom.sym_index].tag = .dead;
+    for (atom.locals.items) |local_atom_index| {
+        const local_atom = wasm_file.getAtom(local_atom_index);
+        const local_symbol = &zig_object.symbols.items[local_atom.sym_index];
+        local_symbol.tag = .dead; // also for any local symbol
+        zig_object.symbols_free_list.append(gpa, local_atom.sym_index) catch {};
+        std.denug.assert(wasm_file.symbol_atom.remove(local_atom.symbolLoc()));
+    }
+
+    if (decl.isExtern(mod)) {
+        _ = zig_object.imports.remove(atom.getSymbolIndex().?);
+    }
+    _ = wasm_file.symbol_atom.remove(atom.symbolLoc());
+
+    // if (wasm.dwarf) |*dwarf| {
+    //     dwarf.freeDecl(decl_index);
+    // }
+
+    if (atom.next) |next_atom_index| {
+        const next_atom = wasm_file.getAtomPtr(next_atom_index);
+        next_atom.prev = atom.prev;
+        atom.next = null;
+    }
+    if (atom.prev) |prev_index| {
+        const prev_atom = wasm_file.getAtomPtr(prev_index);
+        prev_atom.next = atom.next;
+        atom.prev = null;
+    }
+}
+
+pub 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;
+}
+
+/// Kind represents the type of an Atom, which is only
+/// used to parse a decl into an Atom to define in which section
+/// or segment it should be placed.
+const Kind = union(enum) {
+    /// Represents the segment the data symbol should
+    /// be inserted into.
+    /// TODO: Add TLS segments
+    data: enum {
+        read_only,
+        uninitialized,
+        initialized,
+    },
+    function: void,
+
+    /// Returns the segment name the data kind represents.
+    /// Asserts `kind` has its active tag set to `data`.
+    fn segmentName(kind: Kind) []const u8 {
+        switch (kind.data) {
+            .read_only => return ".rodata.",
+            .uninitialized => return ".bss.",
+            .initialized => return ".data.",
+        }
+    }
+};
+
+/// Parses an Atom and inserts its metadata into the corresponding sections.
+pub fn parseAtom(zig_object: *ZigObject, wasm_file: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
+    // TODO: Revisit
+    _ = zig_object;
+    _ = wasm_file;
+    _ = atom_index;
+    _ = kind;
+    //     const comp = wasm.base.comp;
+    //     const gpa = comp.gpa;
+    //     const shared_memory = comp.config.shared_memory;
+    //     const import_memory = comp.config.import_memory;
+    //     const atom = wasm.getAtomPtr(atom_index);
+    //     const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(wasm);
+    //     const do_garbage_collect = wasm.base.gc_sections;
+
+    //     if (symbol.isDead() and do_garbage_collect) {
+    //         // Prevent unreferenced symbols from being parsed.
+    //         return;
+    //     }
+
+    //     const final_index: u32 = switch (kind) {
+    //         .function => result: {
+    //             const index: u32 = @intCast(wasm.functions.count() + wasm.imported_functions_count);
+    //             const type_index = wasm.atom_types.get(atom_index).?;
+    //             try wasm.functions.putNoClobber(
+    //                 gpa,
+    //                 .{ .file = null, .index = index },
+    //                 .{ .func = .{ .type_index = type_index }, .sym_index = atom.sym_index },
+    //             );
+    //             symbol.tag = .function;
+    //             symbol.index = index;
+
+    //             if (wasm.code_section_index == null) {
+    //                 wasm.code_section_index = @intCast(wasm.segments.items.len);
+    //                 try wasm.segments.append(gpa, .{
+    //                     .alignment = atom.alignment,
+    //                     .size = atom.size,
+    //                     .offset = 0,
+    //                     .flags = 0,
+    //                 });
+    //             }
+
+    //             break :result wasm.code_section_index.?;
+    //         },
+    //         .data => result: {
+    //             const segment_name = try std.mem.concat(gpa, u8, &.{
+    //                 kind.segmentName(),
+    //                 wasm.string_table.get(symbol.name),
+    //             });
+    //             errdefer gpa.free(segment_name);
+    //             const segment_info: types.Segment = .{
+    //                 .name = segment_name,
+    //                 .alignment = atom.alignment,
+    //                 .flags = 0,
+    //             };
+    //             symbol.tag = .data;
+
+    //             // when creating an object file, or importing memory and the data belongs in the .bss segment
+    //             // we set the entire region of it to zeroes.
+    //             // We do not have to do this when exporting the memory (the default) because the runtime
+    //             // will do it for us, and we do not emit the bss segment at all.
+    //             if ((wasm.base.comp.config.output_mode == .Obj or import_memory) and kind.data == .uninitialized) {
+    //                 @memset(atom.code.items, 0);
+    //             }
+
+    //             const should_merge = wasm.base.comp.config.output_mode != .Obj;
+    //             const gop = try wasm.data_segments.getOrPut(gpa, segment_info.outputName(should_merge));
+    //             if (gop.found_existing) {
+    //                 const index = gop.value_ptr.*;
+    //                 wasm.segments.items[index].size += atom.size;
+
+    //                 symbol.index = @intCast(wasm.segment_info.getIndex(index).?);
+    //                 // segment info already exists, so free its memory
+    //                 gpa.free(segment_name);
+    //                 break :result index;
+    //             } else {
+    //                 const index: u32 = @intCast(wasm.segments.items.len);
+    //                 var flags: u32 = 0;
+    //                 if (shared_memory) {
+    //                     flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
+    //                 }
+    //                 try wasm.segments.append(gpa, .{
+    //                     .alignment = atom.alignment,
+    //                     .size = 0,
+    //                     .offset = 0,
+    //                     .flags = flags,
+    //                 });
+    //                 gop.value_ptr.* = index;
+
+    //                 const info_index: u32 = @intCast(wasm.segment_info.count());
+    //                 try wasm.segment_info.put(gpa, index, segment_info);
+    //                 symbol.index = info_index;
+    //                 break :result index;
+    //             }
+    //         },
+    //     };
+
+    //     const segment: *Segment = &wasm.segments.items[final_index];
+    //     segment.alignment = segment.alignment.max(atom.alignment);
+
+    //     try wasm.appendAtomAtIndex(final_index, atom_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_file: *Wasm) !void {
+    const gpa = wasm_file.base.comp.gpa;
+    const loc = zig_object.findGlobalSymbol("__zig_errors_len") orelse return;
+
+    const errors_len = wasm_file.base.comp.module.?.global_error_set.count();
+    // overwrite existing atom if it already exists (maybe the error set has increased)
+    // if not, allcoate a new atom.
+    const atom_index = if (wasm_file.symbol_atom.get(loc)) |index| blk: {
+        const atom = wasm_file.getAtomPtr(index);
+        if (atom.next) |next_atom_index| {
+            const next_atom = wasm_file.getAtomPtr(next_atom_index);
+            next_atom.prev = atom.prev;
+            atom.next = null;
+        }
+        if (atom.prev) |prev_index| {
+            const prev_atom = wasm_file.getAtomPtr(prev_index);
+            prev_atom.next = atom.next;
+            atom.prev = null;
+        }
+        atom.deinit(gpa);
+        break :blk index;
+    } else new_atom: {
+        const atom_index: Atom.Index = @intCast(wasm_file.managed_atoms.items.len);
+        try wasm_file.symbol_atom.put(gpa, loc, atom_index);
+        try wasm_file.managed_atoms.append(gpa, undefined);
+        break :new_atom atom_index;
+    };
+    const atom = wasm_file.getAtomPtr(atom_index);
+    atom.* = Atom.empty;
+    atom.sym_index = loc.index;
+    atom.size = 2;
+    try atom.code.writer(gpa).writeInt(u16, @intCast(errors_len), .little);
+
+    // try wasm.parseAtom(atom_index, .{ .data = .read_only });
+}
+
+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 types = @import("types.zig");
+
+const Air = @import("../../Air.zig");
+const Atom = @import("Atom.zig");
+const InternPool = @import("../../InternPool.zig");
+const Liveness = @import("../../Liveness.zig");
+const Module = @import("../../Module.zig");
+const StringTable = @import("../StringTable.zig");
+const Symbol = @import("Symbol.zig");
+const Type = @import("../../type.zig").Type;
+const TypedValue = @import("../../TypedValue.zig");
+const Value = @import("../../value.zig").Value;
+const Wasm = @import("../Wasm.zig");
+const ZigObject = @This();