Commit 7d224516c4

Andrew Kelley <andrew@ziglang.org>
2024-12-30 05:21:26
wasm linker: chase relocations for references
1 parent c582287
Changed files (5)
src/arch/wasm/Emit.zig
@@ -108,7 +108,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
                 try wasm.out_relocs.append(gpa, .{
                     .offset = @intCast(code.items.len),
                     .pointee = .{ .symbol_index = try wasm.errorNameTableSymbolIndex() },
-                    .tag = if (is_wasm32) .MEMORY_ADDR_LEB else .MEMORY_ADDR_LEB64,
+                    .tag = if (is_wasm32) .memory_addr_leb else .memory_addr_leb64,
                     .addend = 0,
                 });
                 code.appendNTimesAssumeCapacity(0, if (is_wasm32) 5 else 10);
@@ -162,7 +162,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
                 try wasm.out_relocs.append(gpa, .{
                     .offset = @intCast(code.items.len),
                     .pointee = .{ .symbol_index = try wasm.navSymbolIndex(datas[inst].nav_index) },
-                    .tag = .FUNCTION_INDEX_LEB,
+                    .tag = .function_index_leb,
                     .addend = 0,
                 });
                 code.appendNTimesAssumeCapacity(0, 5);
@@ -182,7 +182,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
                 try wasm.out_relocs.append(gpa, .{
                     .offset = @intCast(code.items.len),
                     .pointee = .{ .type_index = func_ty_index },
-                    .tag = .TYPE_INDEX_LEB,
+                    .tag = .type_index_leb,
                     .addend = 0,
                 });
                 code.appendNTimesAssumeCapacity(0, 5);
@@ -202,7 +202,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
                 try wasm.out_relocs.append(gpa, .{
                     .offset = @intCast(code.items.len),
                     .pointee = .{ .symbol_index = try wasm.tagNameSymbolIndex(datas[inst].ip_index) },
-                    .tag = .FUNCTION_INDEX_LEB,
+                    .tag = .function_index_leb,
                     .addend = 0,
                 });
                 code.appendNTimesAssumeCapacity(0, 5);
@@ -226,7 +226,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
                 try wasm.out_relocs.append(gpa, .{
                     .offset = @intCast(code.items.len),
                     .pointee = .{ .symbol_index = try wasm.symbolNameIndex(symbol_name) },
-                    .tag = .FUNCTION_INDEX_LEB,
+                    .tag = .function_index_leb,
                     .addend = 0,
                 });
                 code.appendNTimesAssumeCapacity(0, 5);
@@ -245,7 +245,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
                 try wasm.out_relocs.append(gpa, .{
                     .offset = @intCast(code.items.len),
                     .pointee = .{ .symbol_index = try wasm.stackPointerSymbolIndex() },
-                    .tag = .GLOBAL_INDEX_LEB,
+                    .tag = .global_index_leb,
                     .addend = 0,
                 });
                 code.appendNTimesAssumeCapacity(0, 5);
@@ -922,7 +922,7 @@ fn uavRefOffObj(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.UavRef
     try wasm.out_relocs.append(gpa, .{
         .offset = @intCast(code.items.len),
         .pointee = .{ .symbol_index = try wasm.uavSymbolIndex(data.uav_obj.key(wasm).*) },
-        .tag = if (is_wasm32) .MEMORY_ADDR_LEB else .MEMORY_ADDR_LEB64,
+        .tag = if (is_wasm32) .memory_addr_leb else .memory_addr_leb64,
         .addend = data.offset,
     });
     code.appendNTimesAssumeCapacity(0, if (is_wasm32) 5 else 10);
@@ -957,7 +957,7 @@ fn navRefOff(wasm: *Wasm, code: *std.ArrayListUnmanaged(u8), data: Mir.NavRefOff
         try wasm.out_relocs.append(gpa, .{
             .offset = @intCast(code.items.len),
             .pointee = .{ .symbol_index = try wasm.navSymbolIndex(data.nav_index) },
-            .tag = if (is_wasm32) .MEMORY_ADDR_LEB else .MEMORY_ADDR_LEB64,
+            .tag = if (is_wasm32) .memory_addr_leb else .memory_addr_leb64,
             .addend = data.offset,
         });
         code.appendNTimesAssumeCapacity(0, if (is_wasm32) 5 else 10);
src/link/Wasm/Flush.zig
@@ -1398,148 +1398,6 @@ fn emitSegmentInfo(wasm: *Wasm, binary_bytes: *std.ArrayList(u8)) !void {
 //    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) + f.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.indirect_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) + f.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 uleb128size(x: u32) u32 {
     var value = x;
     var size: u32 = 0;
src/link/Wasm/Object.zig
@@ -36,12 +36,16 @@ global_imports: RelativeSlice,
 table_imports: RelativeSlice,
 /// Points into Wasm object_custom_segments
 custom_segments: RelativeSlice,
-/// For calculating local section index from `Wasm.ObjectSectionIndex`.
-local_section_index_base: u32,
 /// Points into Wasm object_init_funcs
 init_funcs: RelativeSlice,
 /// Points into Wasm object_comdats
 comdats: RelativeSlice,
+/// Guaranteed to be non-null when functions has nonzero length.
+code_section_index: ?Wasm.ObjectSectionIndex,
+/// Guaranteed to be non-null when globals has nonzero length.
+global_section_index: ?Wasm.ObjectSectionIndex,
+/// Guaranteed to be non-null when data segments has nonzero length.
+data_section_index: ?Wasm.ObjectSectionIndex,
 
 pub const RelativeSlice = struct {
     off: u32,
@@ -52,7 +56,8 @@ pub const SegmentInfo = struct {
     name: Wasm.String,
     flags: Flags,
 
-    const Flags = packed struct(u32) {
+    /// Matches the ABI.
+    pub const Flags = packed struct(u32) {
         /// Signals that the segment contains only null terminated strings allowing
         /// the linker to perform merging.
         strings: bool,
@@ -101,6 +106,37 @@ pub const SubsectionType = enum(u8) {
     symbol_table = 8,
 };
 
+/// Specified by https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
+pub const RelocationType = enum(u8) {
+    function_index_leb = 0,
+    table_index_sleb = 1,
+    table_index_i32 = 2,
+    memory_addr_leb = 3,
+    memory_addr_sleb = 4,
+    memory_addr_i32 = 5,
+    type_index_leb = 6,
+    global_index_leb = 7,
+    function_offset_i32 = 8,
+    section_offset_i32 = 9,
+    event_index_leb = 10,
+    memory_addr_rel_sleb = 11,
+    table_index_rel_sleb = 12,
+    global_index_i32 = 13,
+    memory_addr_leb64 = 14,
+    memory_addr_sleb64 = 15,
+    memory_addr_i64 = 16,
+    memory_addr_rel_sleb64 = 17,
+    table_index_sleb64 = 18,
+    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,
+    function_index_i32 = 26,
+};
+
 pub const Symbol = struct {
     flags: Wasm.SymbolFlags,
     name: Wasm.OptionalString,
@@ -245,7 +281,8 @@ pub fn parse(
     const global_imports_start: u32 = @intCast(wasm.object_global_imports.entries.len);
     const table_imports_start: u32 = @intCast(wasm.object_table_imports.entries.len);
     const local_section_index_base = wasm.object_total_sections;
-    const source_location: Wasm.SourceLocation = .fromObject(@enumFromInt(wasm.objects.items.len), wasm);
+    const object_index: Wasm.ObjectIndex = @enumFromInt(wasm.objects.items.len);
+    const source_location: Wasm.SourceLocation = .fromObject(object_index, wasm);
 
     ss.clear();
 
@@ -254,6 +291,9 @@ pub fn parse(
     var saw_linking_section = false;
     var has_tls = false;
     var table_import_symbol_count: usize = 0;
+    var code_section_index: ?Wasm.ObjectSectionIndex = null;
+    var global_section_index: ?Wasm.ObjectSectionIndex = null;
+    var data_section_index: ?Wasm.ObjectSectionIndex = null;
     while (pos < bytes.len) : (wasm.object_total_sections += 1) {
         const section_index: Wasm.ObjectSectionIndex = @enumFromInt(wasm.object_total_sections);
 
@@ -365,7 +405,8 @@ pub fn parse(
                                     switch (tag) {
                                         .data => {
                                             const name, pos = readBytes(bytes, pos);
-                                            symbol.name = (try wasm.internString(name)).toOptional();
+                                            const interned_name = try wasm.internString(name);
+                                            symbol.name = interned_name.toOptional();
                                             if (symbol.flags.undefined) {
                                                 symbol.pointee = .data_import;
                                             } else {
@@ -376,7 +417,7 @@ pub fn parse(
                                                     .segment = @enumFromInt(data_segment_start + segment_index),
                                                     .offset = segment_offset,
                                                     .size = size,
-                                                    .name = symbol.name,
+                                                    .name = interned_name,
                                                     .flags = symbol.flags,
                                                 });
                                                 symbol.pointee = .{
@@ -468,7 +509,7 @@ pub fn parse(
                     var prev_offset: u32 = 0;
                     try wasm.object_relocations.ensureUnusedCapacity(gpa, count);
                     for (0..count) |_| {
-                        const tag: Wasm.ObjectRelocation.Tag = @enumFromInt(bytes[pos]);
+                        const tag: RelocationType = @enumFromInt(bytes[pos]);
                         pos += 1;
                         const offset, pos = readLeb(u32, bytes, pos);
                         const index, pos = readLeb(u32, bytes, pos);
@@ -477,75 +518,134 @@ pub fn parse(
                             return diags.failParse(path, "relocation entries not sorted by offset", .{});
                         prev_offset = offset;
 
+                        const sym = &ss.symbol_table.items[index];
+
                         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,
+                            .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,
                             => {
                                 const addend: i32, pos = readLeb(i32, bytes, pos);
-                                wasm.object_relocations.appendAssumeCapacity(.{
-                                    .tag = tag,
-                                    .offset = offset,
-                                    .pointee = .{ .data = ss.symbol_table.items[index].pointee.data },
-                                    .addend = addend,
+                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
+                                    .data => |data| .{
+                                        .tag = .fromType(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .data = data },
+                                        .addend = addend,
+                                    },
+                                    .data_import => .{
+                                        .tag = .fromTypeImport(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
+                                        .addend = addend,
+                                    },
+                                    else => unreachable,
                                 });
                             },
-                            .FUNCTION_OFFSET_I32,
-                            .FUNCTION_OFFSET_I64,
-                            => {
+                            .function_offset_i32, .function_offset_i64 => {
                                 const addend: i32, pos = readLeb(i32, bytes, pos);
-                                wasm.object_relocations.appendAssumeCapacity(.{
-                                    .tag = tag,
-                                    .offset = offset,
-                                    .pointee = .{ .function = ss.symbol_table.items[index].pointee.function },
-                                    .addend = addend,
+                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
+                                    .function => .{
+                                        .tag = .fromType(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .function = sym.pointee.function },
+                                        .addend = addend,
+                                    },
+                                    .function_import => .{
+                                        .tag = .fromTypeImport(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
+                                        .addend = addend,
+                                    },
+                                    else => unreachable,
                                 });
                             },
-                            .SECTION_OFFSET_I32 => {
+                            .section_offset_i32 => {
                                 const addend: i32, pos = readLeb(i32, bytes, pos);
                                 wasm.object_relocations.appendAssumeCapacity(.{
-                                    .tag = tag,
+                                    .tag = .section_offset_i32,
                                     .offset = offset,
-                                    .pointee = .{ .section = ss.symbol_table.items[index].pointee.section },
+                                    .pointee = .{ .section = sym.pointee.section },
                                     .addend = addend,
                                 });
                             },
-                            .TYPE_INDEX_LEB => {
+                            .type_index_leb => {
                                 wasm.object_relocations.appendAssumeCapacity(.{
-                                    .tag = tag,
+                                    .tag = .type_index_leb,
                                     .offset = offset,
                                     .pointee = .{ .type_index = ss.func_types.items[index] },
                                     .addend = undefined,
                                 });
                             },
-                            .FUNCTION_INDEX_LEB,
-                            .FUNCTION_INDEX_I32,
-                            .GLOBAL_INDEX_LEB,
-                            .GLOBAL_INDEX_I32,
-                            .TABLE_INDEX_SLEB,
-                            .TABLE_INDEX_I32,
-                            .TABLE_INDEX_SLEB64,
-                            .TABLE_INDEX_I64,
-                            .TABLE_NUMBER_LEB,
-                            .TABLE_INDEX_REL_SLEB,
-                            .TABLE_INDEX_REL_SLEB64,
-                            .TAG_INDEX_LEB,
+                            .function_index_leb,
+                            .function_index_i32,
+                            .table_index_sleb,
+                            .table_index_i32,
+                            .table_index_sleb64,
+                            .table_index_i64,
+                            .table_index_rel_sleb,
+                            .table_index_rel_sleb64,
                             => {
-                                wasm.object_relocations.appendAssumeCapacity(.{
-                                    .tag = tag,
-                                    .offset = offset,
-                                    .pointee = .{ .symbol_name = ss.symbol_table.items[index].name.unwrap().? },
-                                    .addend = undefined,
+                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
+                                    .function => .{
+                                        .tag = .fromType(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .function = sym.pointee.function },
+                                        .addend = undefined,
+                                    },
+                                    .function_import => .{
+                                        .tag = .fromTypeImport(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
+                                        .addend = undefined,
+                                    },
+                                    else => unreachable,
+                                });
+                            },
+                            .global_index_leb, .global_index_i32 => {
+                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
+                                    .global => .{
+                                        .tag = .fromType(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .global = sym.pointee.global },
+                                        .addend = undefined,
+                                    },
+                                    .global_import => .{
+                                        .tag = .fromTypeImport(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
+                                        .addend = undefined,
+                                    },
+                                    else => unreachable,
+                                });
+                            },
+
+                            .table_number_leb => {
+                                wasm.object_relocations.appendAssumeCapacity(switch (sym.pointee) {
+                                    .table => .{
+                                        .tag = .fromType(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .table = sym.pointee.table },
+                                        .addend = undefined,
+                                    },
+                                    .table_import => .{
+                                        .tag = .fromTypeImport(tag),
+                                        .offset = offset,
+                                        .pointee = .{ .symbol_name = sym.name.unwrap().? },
+                                        .addend = undefined,
+                                    },
+                                    else => unreachable,
                                 });
                             },
+                            .event_index_leb => return diags.failParse(path, "unsupported relocation: R_WASM_EVENT_INDEX_LEB", .{}),
                         }
                     }
 
@@ -684,11 +784,17 @@ pub fn parse(
                 }
             },
             .global => {
+                if (global_section_index != null)
+                    return diags.failParse(path, "object has more than one global section", .{});
+                global_section_index = section_index;
+
+                const section_start = pos;
                 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 init_start = pos;
                     const expr, pos = try readInit(wasm, bytes, pos);
                     global.* = .{
                         .name = .none,
@@ -699,6 +805,9 @@ pub fn parse(
                             },
                         },
                         .expr = expr,
+                        .object_index = object_index,
+                        .offset = @intCast(init_start - section_start),
+                        .size = @intCast(pos - init_start),
                     };
                 }
             },
@@ -728,10 +837,14 @@ pub fn parse(
                 start_function = @enumFromInt(functions_start + index);
             },
             .element => {
-                log.warn("unimplemented: element section in {}", .{path});
+                log.warn("unimplemented: element section in {} {s}", .{ path, archive_member_name.? });
                 pos = section_end;
             },
             .code => {
+                if (code_section_index != null)
+                    return diags.failParse(path, "object has more than one code section", .{});
+                code_section_index = section_index;
+
                 const start = pos;
                 const count, pos = readLeb(u32, bytes, pos);
                 for (try wasm.object_functions.addManyAsSlice(gpa, count)) |*elem| {
@@ -745,12 +858,16 @@ pub fn parse(
                         .type_index = undefined, // populated from func_types
                         .code = payload,
                         .offset = offset,
-                        .section_index = section_index,
-                        .source_location = source_location,
+                        .object_index = object_index,
                     };
                 }
             },
             .data => {
+                if (data_section_index != null)
+                    return diags.failParse(path, "object has more than one data section", .{});
+                data_section_index = section_index;
+
+                const section_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);
@@ -761,12 +878,17 @@ pub fn parse(
                     //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_start = pos;
                     const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..data_len]);
                     pos += data_len;
                     elem.* = .{
                         .payload = payload,
-                        .name = .none, // Populated from symbol table
-                        .flags = .{}, // Populated from symbol table and segment_info
+                        .name = .none, // Populated from segment_info
+                        .flags = .{
+                            .is_passive = flags == .passive,
+                        }, // Remainder populated from segment_info
+                        .offset = @intCast(segment_start - section_start),
+                        .object_index = object_index,
                     };
                 }
             },
@@ -1033,8 +1155,10 @@ pub fn parse(
             }
         },
         .data_import => {
-            const name = symbol.name.unwrap().?;
-            log.warn("TODO data import '{s}'", .{name.slice(wasm)});
+            if (symbol.flags.undefined and symbol.flags.binding == .local) {
+                const name = symbol.name.slice(wasm).?;
+                diags.addParseError(path, "local symbol '{s}' references import", .{name});
+            }
         },
         .data => continue, // `wasm.object_datas` has already been populated.
     };
@@ -1055,16 +1179,21 @@ pub fn parse(
     }
 
     // Apply segment_info.
-    for (wasm.object_data_segments.items[data_segment_start..], ss.segment_info.items) |*data, info| {
+    const data_segments = wasm.object_data_segments.items[data_segment_start..];
+    if (data_segments.len != ss.segment_info.items.len) {
+        return diags.failParse(path, "expected {d} segment_info entries; found {d}", .{
+            data_segments.len, ss.segment_info.items.len,
+        });
+    }
+    for (data_segments, 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 = info.name.slice(wasm);
-            diags.addParseError(path, "local symbol '{s}' references import", .{name});
-        }
+        data.flags = .{
+            .is_passive = data.flags.is_passive,
+            .strings = info.flags.strings,
+            .tls = info.flags.tls,
+            .retain = info.flags.retain,
+            .alignment = info.flags.alignment,
+        };
     }
 
     // Check for indirect function table in case of an MVP object file.
@@ -1106,6 +1235,10 @@ pub fn parse(
         });
     }
 
+    const functions_len: u32 = @intCast(wasm.object_functions.items.len - functions_start);
+    if (functions_len > 0 and code_section_index == null)
+        return diags.failParse(path, "code section missing ({d} functions)", .{functions_len});
+
     return .{
         .version = version,
         .path = path,
@@ -1114,7 +1247,7 @@ pub fn parse(
         .features = features,
         .functions = .{
             .off = functions_start,
-            .len = @intCast(wasm.object_functions.items.len - functions_start),
+            .len = functions_len,
         },
         .function_imports = .{
             .off = function_imports_start,
@@ -1140,7 +1273,9 @@ pub fn parse(
             .off = custom_segment_start,
             .len = @intCast(wasm.object_custom_segments.entries.len - custom_segment_start),
         },
-        .local_section_index_base = local_section_index_base,
+        .code_section_index = code_section_index,
+        .global_section_index = global_section_index,
+        .data_section_index = data_section_index,
     };
 }
 
src/link/Wasm.zig
@@ -93,13 +93,13 @@ func_types: std.AutoArrayHashMapUnmanaged(FunctionType, void) = .empty,
 /// Local functions may be unnamed.
 object_function_imports: std.AutoArrayHashMapUnmanaged(String, FunctionImport) = .empty,
 /// All functions for all objects.
-object_functions: std.ArrayListUnmanaged(Function) = .empty,
+object_functions: std.ArrayListUnmanaged(ObjectFunction) = .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,
+object_globals: std.ArrayListUnmanaged(ObjectGlobal) = .empty,
 
 /// All table imports for all objects.
 object_table_imports: std.AutoArrayHashMapUnmanaged(String, TableImport) = .empty,
@@ -386,7 +386,7 @@ pub const GlobalIndex = enum(u32) {
     pub const stack_pointer: GlobalIndex = @enumFromInt(0);
 
     /// Same as `stack_pointer` but with a safety assertion.
-    pub fn stackPointer(wasm: *const Wasm) Global.Index {
+    pub fn stackPointer(wasm: *const Wasm) ObjectGlobal.Index {
         const comp = wasm.base.comp;
         assert(comp.config.output_mode != .Obj);
         assert(comp.zcu != null);
@@ -513,26 +513,19 @@ pub const SymbolFlags = packed struct(u32) {
 
     // Above here matches the tooling conventions ABI.
 
-    padding1: u5 = 0,
+    padding1: u13 = 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.
+    /// Zig-specific.
     global_type: GlobalType4 = .zero,
-    /// Zig-specific. Tables only.
+    /// Zig-specific.
     limits_has_max: bool = false,
-    /// Zig-specific. Tables only.
+    /// Zig-specific.
     limits_is_shared: bool = false,
-    /// Zig-specific. Tables only.
+    /// Zig-specific.
     ref_type: RefType1 = .funcref,
 
     pub const Binding = enum(u2) {
@@ -554,10 +547,7 @@ pub const SymbolFlags = packed struct(u32) {
     pub fn initZigSpecific(flags: *SymbolFlags, must_link: bool, no_strip: bool) void {
         flags.no_strip = no_strip;
         flags.alive = false;
-        flags.strings = false;
         flags.must_link = must_link;
-        flags.is_passive = false;
-        flags.alignment = .none;
         flags.global_type = .zero;
         flags.limits_has_max = false;
         flags.limits_is_shared = false;
@@ -603,7 +593,7 @@ pub const GlobalType4 = packed struct(u4) {
 
     pub const zero: GlobalType4 = @bitCast(@as(u4, 0));
 
-    pub fn to(gt: GlobalType4) Global.Type {
+    pub fn to(gt: GlobalType4) ObjectGlobal.Type {
         return .{
             .valtype = gt.valtype.to(),
             .mutable = gt.mutable,
@@ -1001,18 +991,24 @@ pub const FunctionImport = extern struct {
     };
 };
 
-pub const Function = extern struct {
+pub const ObjectFunction = 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.
+    /// The offset within the code section where the data starts.
     offset: u32,
-    section_index: ObjectSectionIndex,
-    source_location: SourceLocation,
+    /// The object file whose code section contains this function.
+    object_index: ObjectIndex,
 
     pub const Code = DataPayload;
+
+    fn relocations(of: *const ObjectFunction, wasm: *const Wasm) ObjectRelocation.IterableSlice {
+        const code_section_index = of.object_index.ptr(wasm).code_section_index.?;
+        const relocs = wasm.object_relocations_table.get(code_section_index) orelse return .empty;
+        return .init(relocs, of.offset, of.code.len, wasm);
+    }
 };
 
 pub const GlobalImport = extern struct {
@@ -1101,6 +1097,10 @@ pub const GlobalImport = extern struct {
             });
         }
 
+        fn fromObjectGlobal(wasm: *const Wasm, object_global: ObjectGlobalIndex) Resolution {
+            return pack(wasm, .{ .object_global = object_global });
+        }
+
         pub fn name(r: Resolution, wasm: *const Wasm) ?[]const u8 {
             return switch (unpack(r, wasm)) {
                 .unresolved => unreachable,
@@ -1137,22 +1137,32 @@ pub const GlobalImport = extern struct {
             return index.value(wasm).module_name;
         }
 
-        pub fn globalType(index: Index, wasm: *const Wasm) Global.Type {
+        pub fn globalType(index: Index, wasm: *const Wasm) ObjectGlobal.Type {
             return value(index, wasm).flags.global_type.to();
         }
     };
 };
 
-pub const Global = extern struct {
+pub const ObjectGlobal = extern struct {
     /// `none` if this function has no symbol describing it.
     name: OptionalString,
     flags: SymbolFlags,
     expr: Expr,
+    /// The object file whose global section contains this global.
+    object_index: ObjectIndex,
+    offset: u32,
+    size: u32,
 
     pub const Type = struct {
         valtype: std.wasm.Valtype,
         mutable: bool,
     };
+
+    fn relocations(og: *const ObjectGlobal, wasm: *const Wasm) ObjectRelocation.IterableSlice {
+        const global_section_index = og.object_index.ptr(wasm).global_section_index.?;
+        const relocs = wasm.object_relocations_table.get(global_section_index) orelse return .empty;
+        return .init(relocs, og.offset, og.size, wasm);
+    }
 };
 
 pub const RefType1 = enum(u1) {
@@ -1205,6 +1215,18 @@ pub const TableImport = extern struct {
             };
         }
 
+        fn pack(unpacked: Unpacked) Resolution {
+            return switch (unpacked) {
+                .unresolved => .unresolved,
+                .__indirect_function_table => .__indirect_function_table,
+                .object_table => |i| @enumFromInt(first_object_table + @intFromEnum(i)),
+            };
+        }
+
+        fn fromObjectTable(object_table: ObjectTableIndex) Resolution {
+            return pack(.{ .object_table = object_table });
+        }
+
         pub fn refType(r: Resolution, wasm: *const Wasm) std.wasm.RefType {
             return switch (unpack(r)) {
                 .unresolved => unreachable,
@@ -1298,7 +1320,7 @@ pub const ObjectTableIndex = enum(u32) {
 pub const ObjectGlobalIndex = enum(u32) {
     _,
 
-    pub fn ptr(index: ObjectGlobalIndex, wasm: *const Wasm) *Global {
+    pub fn ptr(index: ObjectGlobalIndex, wasm: *const Wasm) *ObjectGlobal {
         return &wasm.object_globals.items[@intFromEnum(index)];
     }
 
@@ -1338,7 +1360,7 @@ pub const ObjectMemory = extern struct {
 pub const ObjectFunctionIndex = enum(u32) {
     _,
 
-    pub fn ptr(index: ObjectFunctionIndex, wasm: *const Wasm) *Function {
+    pub fn ptr(index: ObjectFunctionIndex, wasm: *const Wasm) *ObjectFunction {
         return &wasm.object_functions.items[@intFromEnum(index)];
     }
 
@@ -1363,8 +1385,28 @@ pub const OptionalObjectFunctionIndex = enum(u32) {
 pub const ObjectDataSegment = extern struct {
     /// `none` if segment info custom subsection is missing.
     name: OptionalString,
-    flags: SymbolFlags,
+    flags: Flags,
     payload: DataPayload,
+    offset: u32,
+    object_index: ObjectIndex,
+
+    pub const Flags = packed struct(u32) {
+        alive: bool = false,
+        is_passive: bool = false,
+        alignment: Alignment = .none,
+        /// Signals that the segment contains only null terminated strings allowing
+        /// the linker to perform merging.
+        strings: bool = false,
+        /// The segment contains thread-local data. This means that a unique copy
+        /// of this segment will be created for each thread.
+        tls: bool = false,
+        /// 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 = false,
+
+        _: u21 = 0,
+    };
 
     /// Index into `Wasm.object_data_segments`.
     pub const Index = enum(u32) {
@@ -1374,6 +1416,12 @@ pub const ObjectDataSegment = extern struct {
             return &wasm.object_data_segments.items[@intFromEnum(i)];
         }
     };
+
+    fn relocations(ods: *const ObjectDataSegment, wasm: *const Wasm) ObjectRelocation.IterableSlice {
+        const data_section_index = ods.object_index.ptr(wasm).data_section_index.?;
+        const relocs = wasm.object_relocations_table.get(data_section_index) orelse return .empty;
+        return .init(relocs, ods.offset, ods.payload.len, wasm);
+    }
 };
 
 /// A local or exported global const from an object file.
@@ -1383,8 +1431,7 @@ pub const ObjectData = extern struct {
     offset: u32,
     /// May be zero. `offset + size` must be <= the segment's size.
     size: u32,
-    /// `none` if no symbol describes it.
-    name: OptionalString,
+    name: String,
     flags: SymbolFlags,
 
     /// Index into `Wasm.object_datas`.
@@ -1845,7 +1892,7 @@ pub const ZcuImportIndex = enum(u32) {
         return getExistingFunctionType(wasm, fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target).?;
     }
 
-    pub fn globalType(index: ZcuImportIndex, wasm: *const Wasm) Global.Type {
+    pub fn globalType(index: ZcuImportIndex, wasm: *const Wasm) ObjectGlobal.Type {
         _ = index;
         _ = wasm;
         unreachable; // Zig has no way to create Wasm globals yet.
@@ -1997,7 +2044,7 @@ pub const GlobalImportId = enum(u32) {
         };
     }
 
-    pub fn globalType(id: GlobalImportId, wasm: *Wasm) Global.Type {
+    pub fn globalType(id: GlobalImportId, wasm: *Wasm) ObjectGlobal.Type {
         return switch (unpack(id, wasm)) {
             inline .object_global_import, .zcu_import => |i| i.globalType(wasm),
         };
@@ -2014,7 +2061,7 @@ pub const SymbolTableIndex = enum(u32) {
 };
 
 pub const OutReloc = struct {
-    tag: ObjectRelocation.Tag,
+    tag: Object.RelocationType,
     offset: u32,
     pointee: Pointee,
     addend: i32,
@@ -2041,15 +2088,144 @@ pub const ObjectRelocation = struct {
     /// 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`.
+    /// Populated only for `memory_addr_*`, `function_offset_i32` and `section_offset_i32`.
     addend: i32,
 
+    pub const Tag = enum(u8) {
+        // These use `Pointee.function`.
+        function_index_i32,
+        function_index_leb,
+        function_offset_i32,
+        function_offset_i64,
+        table_index_i32,
+        table_index_i64,
+        table_index_rel_sleb,
+        table_index_rel_sleb64,
+        table_index_sleb,
+        table_index_sleb64,
+        // These use `Pointee.symbol_name`.
+        function_import_index_i32,
+        function_import_index_leb,
+        function_import_offset_i32,
+        function_import_offset_i64,
+        table_import_index_i32,
+        table_import_index_i64,
+        table_import_index_rel_sleb,
+        table_import_index_rel_sleb64,
+        table_import_index_sleb,
+        table_import_index_sleb64,
+        // These use `Pointee.global`.
+        global_index_i32,
+        global_index_leb,
+        // These use `Pointee.symbol_name`.
+        global_import_index_i32,
+        global_import_index_leb,
+        // These use `Pointee.data`.
+        memory_addr_i32,
+        memory_addr_i64,
+        memory_addr_leb,
+        memory_addr_leb64,
+        memory_addr_locrel_i32,
+        memory_addr_rel_sleb,
+        memory_addr_rel_sleb64,
+        memory_addr_sleb,
+        memory_addr_sleb64,
+        memory_addr_tls_sleb,
+        memory_addr_tls_sleb64,
+        // These use `Pointee.symbol_name`.
+        memory_addr_import_i32,
+        memory_addr_import_i64,
+        memory_addr_import_leb,
+        memory_addr_import_leb64,
+        memory_addr_import_locrel_i32,
+        memory_addr_import_rel_sleb,
+        memory_addr_import_rel_sleb64,
+        memory_addr_import_sleb,
+        memory_addr_import_sleb64,
+        memory_addr_import_tls_sleb,
+        memory_addr_import_tls_sleb64,
+        /// Uses `Pointee.section`.
+        section_offset_i32,
+        /// Uses `Pointee.table`.
+        table_number_leb,
+        /// Uses `Pointee.symbol_name`.
+        table_import_number_leb,
+        /// Uses `Pointee.type_index`.
+        type_index_leb,
+
+        pub fn fromType(t: Object.RelocationType) Tag {
+            return switch (t) {
+                .event_index_leb => unreachable,
+                .function_index_i32 => .function_index_i32,
+                .function_index_leb => .function_index_leb,
+                .function_offset_i32 => .function_offset_i32,
+                .function_offset_i64 => .function_offset_i64,
+                .global_index_i32 => .global_index_i32,
+                .global_index_leb => .global_index_leb,
+                .memory_addr_i32 => .memory_addr_i32,
+                .memory_addr_i64 => .memory_addr_i64,
+                .memory_addr_leb => .memory_addr_leb,
+                .memory_addr_leb64 => .memory_addr_leb64,
+                .memory_addr_locrel_i32 => .memory_addr_locrel_i32,
+                .memory_addr_rel_sleb => .memory_addr_rel_sleb,
+                .memory_addr_rel_sleb64 => .memory_addr_rel_sleb64,
+                .memory_addr_sleb => .memory_addr_sleb,
+                .memory_addr_sleb64 => .memory_addr_sleb64,
+                .memory_addr_tls_sleb => .memory_addr_tls_sleb,
+                .memory_addr_tls_sleb64 => .memory_addr_tls_sleb64,
+                .section_offset_i32 => .section_offset_i32,
+                .table_index_i32 => .table_index_i32,
+                .table_index_i64 => .table_index_i64,
+                .table_index_rel_sleb => .table_index_rel_sleb,
+                .table_index_rel_sleb64 => .table_index_rel_sleb64,
+                .table_index_sleb => .table_index_sleb,
+                .table_index_sleb64 => .table_index_sleb64,
+                .table_number_leb => .table_number_leb,
+                .type_index_leb => .type_index_leb,
+            };
+        }
+
+        pub fn fromTypeImport(t: Object.RelocationType) Tag {
+            return switch (t) {
+                .event_index_leb => unreachable,
+                .function_index_i32 => .function_import_index_i32,
+                .function_index_leb => .function_import_index_leb,
+                .function_offset_i32 => .function_import_offset_i32,
+                .function_offset_i64 => .function_import_offset_i64,
+                .global_index_i32 => .global_import_index_i32,
+                .global_index_leb => .global_import_index_leb,
+                .memory_addr_i32 => .memory_addr_import_i32,
+                .memory_addr_i64 => .memory_addr_import_i64,
+                .memory_addr_leb => .memory_addr_import_leb,
+                .memory_addr_leb64 => .memory_addr_import_leb64,
+                .memory_addr_locrel_i32 => .memory_addr_import_locrel_i32,
+                .memory_addr_rel_sleb => .memory_addr_import_rel_sleb,
+                .memory_addr_rel_sleb64 => .memory_addr_import_rel_sleb64,
+                .memory_addr_sleb => .memory_addr_import_sleb,
+                .memory_addr_sleb64 => .memory_addr_import_sleb64,
+                .memory_addr_tls_sleb => .memory_addr_import_tls_sleb,
+                .memory_addr_tls_sleb64 => .memory_addr_import_tls_sleb64,
+                .section_offset_i32 => unreachable,
+                .table_index_i32 => .table_import_index_i32,
+                .table_index_i64 => .table_import_index_i64,
+                .table_index_rel_sleb => .table_import_index_rel_sleb,
+                .table_index_rel_sleb64 => .table_import_index_rel_sleb64,
+                .table_index_sleb => .table_import_index_sleb,
+                .table_index_sleb64 => .table_import_index_sleb64,
+                .table_number_leb => .table_import_number_leb,
+                .type_index_leb => unreachable,
+            };
+        }
+    };
+
     pub const Pointee = union {
         symbol_name: String,
+        data: ObjectData.Index,
         type_index: FunctionType.Index,
         section: ObjectSectionIndex,
-        data: ObjectData.Index,
-        function: Wasm.ObjectFunctionIndex,
+        function: ObjectFunctionIndex,
+        global: ObjectGlobalIndex,
+        table: ObjectTableIndex,
     };
 
     pub const Slice = extern struct {
@@ -2057,63 +2233,47 @@ pub const ObjectRelocation = struct {
         off: u32,
         len: u32,
 
-        pub fn slice(s: Slice, wasm: *const Wasm) []ObjectRelocation {
-            return wasm.relocations.items[s.off..][0..s.len];
+        const empty: Slice = .{ .off = 0, .len = 0 };
+
+        fn tags(s: Slice, wasm: *const Wasm) []const ObjectRelocation.Tag {
+            return wasm.object_relocations.items(.tag)[s.off..][0..s.len];
+        }
+
+        fn offsets(s: Slice, wasm: *const Wasm) []const u32 {
+            return wasm.object_relocations.items(.offset)[s.off..][0..s.len];
+        }
+
+        fn pointees(s: Slice, wasm: *const Wasm) []const Pointee {
+            return wasm.object_relocations.items(.pointee)[s.off..][0..s.len];
+        }
+
+        fn addends(s: Slice, wasm: *const Wasm) []const i32 {
+            return wasm.object_relocations.items(.addend)[s.off..][0..s.len];
         }
     };
 
-    pub const Tag = enum(u8) {
-        /// Uses `function`.
-        FUNCTION_INDEX_LEB = 0,
-        /// Uses `table_index`.
-        TABLE_INDEX_SLEB = 1,
-        /// Uses `table_index`.
-        TABLE_INDEX_I32 = 2,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_LEB = 3,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_SLEB = 4,
-        /// Uses `data_segment`.
-        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,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_REL_SLEB = 11,
-        TABLE_INDEX_REL_SLEB = 12,
-        /// Uses `symbol_name`.
-        GLOBAL_INDEX_I32 = 13,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_LEB64 = 14,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_SLEB64 = 15,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_I64 = 16,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_REL_SLEB64 = 17,
-        /// Uses `table_index`.
-        TABLE_INDEX_SLEB64 = 18,
-        /// Uses `table_index`.
-        TABLE_INDEX_I64 = 19,
-        TABLE_NUMBER_LEB = 20,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_TLS_SLEB = 21,
-        FUNCTION_OFFSET_I64 = 22,
-        /// Uses `data_segment`.
-        MEMORY_ADDR_LOCREL_I32 = 23,
-        TABLE_INDEX_REL_SLEB64 = 24,
-        /// Uses `data_segment`.
-        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.
+    pub const IterableSlice = struct {
+        slice: Slice,
+        /// Offset at which point to stop iterating.
+        end: u32,
+
+        const empty: IterableSlice = .{ .slice = .empty, .end = 0 };
+
+        fn init(relocs: Slice, offset: u32, size: u32, wasm: *const Wasm) IterableSlice {
+            const offsets = relocs.offsets(wasm);
+            const start = std.sort.lowerBound(u32, offsets, offset, order);
+            return .{
+                .slice = .{
+                    .off = @intCast(relocs.off + start),
+                    .len = @intCast(relocs.len - start),
+                },
+                .end = offset + size,
+            };
+        }
+
+        fn order(lhs: u32, rhs: u32) std.math.Order {
+            return std.math.order(lhs, rhs);
+        }
     };
 };
 
@@ -2781,7 +2941,7 @@ pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!v
     // 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));
+            try markFunctionImport(wasm, name, import, @enumFromInt(i));
         }
     }
     wasm.functions_end_prelink = @intCast(wasm.functions.entries.len);
@@ -2789,7 +2949,7 @@ pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!v
 
     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));
+            try markGlobalImport(wasm, name, import, @enumFromInt(i));
         }
     }
     wasm.globals_end_prelink = @intCast(wasm.globals.entries.len);
@@ -2797,13 +2957,12 @@ pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!v
 
     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));
+            try markTableImport(wasm, name, import, @enumFromInt(i));
         }
     }
 }
 
-/// Recursively mark alive everything referenced by the function.
-fn markFunction(
+fn markFunctionImport(
     wasm: *Wasm,
     name: String,
     import: *FunctionImport,
@@ -2814,8 +2973,6 @@ fn markFunction(
 
     const comp = wasm.base.comp;
     const gpa = comp.gpa;
-    const rdynamic = comp.config.rdynamic;
-    const is_obj = comp.config.output_mode == .Obj;
 
     try wasm.functions.ensureUnusedCapacity(gpa, 1);
 
@@ -2836,20 +2993,31 @@ fn markFunction(
             try wasm.function_imports.put(gpa, name, .fromObject(func_index, wasm));
         }
     } else {
-        const gop = wasm.functions.getOrPutAssumeCapacity(import.resolution);
+        try markFunction(wasm, import.resolution.unpack(wasm).object_function);
+    }
+}
 
-        if (!is_obj and import.flags.isExported(rdynamic)) try wasm.function_exports.append(gpa, .{
-            .name = name,
-            .function_index = @enumFromInt(gop.index),
-        });
+/// Recursively mark alive everything referenced by the function.
+fn markFunction(wasm: *Wasm, i: ObjectFunctionIndex) Allocator.Error!void {
+    const comp = wasm.base.comp;
+    const gpa = comp.gpa;
+    const gop = try wasm.functions.getOrPut(gpa, .fromObjectFunction(wasm, i));
+    if (gop.found_existing) return;
 
-        for (try wasm.functionResolutionRelocSlice(import.resolution)) |reloc|
-            try wasm.markReloc(reloc);
-    }
+    const rdynamic = comp.config.rdynamic;
+    const is_obj = comp.config.output_mode == .Obj;
+    const function = i.ptr(wasm);
+
+    if (!is_obj and function.flags.isExported(rdynamic)) try wasm.function_exports.append(gpa, .{
+        .name = function.name.unwrap().?,
+        .function_index = @enumFromInt(gop.index),
+    });
+
+    try wasm.markRelocations(function.relocations(wasm));
 }
 
 /// Recursively mark alive everything referenced by the global.
-fn markGlobal(
+fn markGlobalImport(
     wasm: *Wasm,
     name: String,
     import: *GlobalImport,
@@ -2860,8 +3028,6 @@ fn markGlobal(
 
     const comp = wasm.base.comp;
     const gpa = comp.gpa;
-    const rdynamic = comp.config.rdynamic;
-    const is_obj = comp.config.output_mode == .Obj;
 
     try wasm.globals.ensureUnusedCapacity(gpa, 1);
 
@@ -2888,19 +3054,29 @@ fn markGlobal(
             try wasm.global_imports.put(gpa, name, .fromObject(global_index, wasm));
         }
     } else {
-        const gop = wasm.globals.getOrPutAssumeCapacity(import.resolution);
+        try markGlobal(wasm, import.resolution.unpack(wasm).object_global);
+    }
+}
 
-        if (!is_obj and import.flags.isExported(rdynamic)) try wasm.global_exports.append(gpa, .{
-            .name = name,
-            .global_index = @enumFromInt(gop.index),
-        });
+fn markGlobal(wasm: *Wasm, i: ObjectGlobalIndex) Allocator.Error!void {
+    const comp = wasm.base.comp;
+    const gpa = comp.gpa;
+    const gop = try wasm.globals.getOrPut(gpa, .fromObjectGlobal(wasm, i));
+    if (gop.found_existing) return;
 
-        for (try wasm.globalResolutionRelocSlice(import.resolution)) |reloc|
-            try wasm.markReloc(reloc);
-    }
+    const rdynamic = comp.config.rdynamic;
+    const is_obj = comp.config.output_mode == .Obj;
+    const global = i.ptr(wasm);
+
+    if (!is_obj and global.flags.isExported(rdynamic)) try wasm.global_exports.append(gpa, .{
+        .name = global.name.unwrap().?,
+        .global_index = @enumFromInt(gop.index),
+    });
+
+    try wasm.markRelocations(global.relocations(wasm));
 }
 
-fn markTable(
+fn markTableImport(
     wasm: *Wasm,
     name: String,
     import: *TableImport,
@@ -2927,22 +3103,101 @@ fn markTable(
     }
 }
 
-fn globalResolutionRelocSlice(wasm: *Wasm, resolution: GlobalImport.Resolution) ![]const ObjectRelocation {
-    assert(resolution != .unresolved);
-    _ = wasm;
-    @panic("TODO");
-}
+fn markDataSegment(wasm: *Wasm, segment_index: ObjectDataSegment.Index) Allocator.Error!void {
+    const segment = segment_index.ptr(wasm);
+    if (segment.flags.alive) return;
+    segment.flags.alive = true;
 
-fn functionResolutionRelocSlice(wasm: *Wasm, resolution: FunctionImport.Resolution) ![]const ObjectRelocation {
-    assert(resolution != .unresolved);
-    _ = wasm;
-    @panic("TODO");
+    try wasm.markRelocations(segment.relocations(wasm));
 }
 
-fn markReloc(wasm: *Wasm, reloc: ObjectRelocation) !void {
-    _ = wasm;
-    _ = reloc;
-    @panic("TODO");
+fn markRelocations(wasm: *Wasm, relocs: ObjectRelocation.IterableSlice) Allocator.Error!void {
+    for (relocs.slice.tags(wasm), relocs.slice.pointees(wasm), relocs.slice.offsets(wasm)) |tag, *pointee, offset| {
+        if (offset >= relocs.end) break;
+        switch (tag) {
+            .function_import_index_leb,
+            .function_import_index_i32,
+            .function_import_offset_i32,
+            .function_import_offset_i64,
+            .table_import_index_sleb,
+            .table_import_index_i32,
+            .table_import_index_sleb64,
+            .table_import_index_i64,
+            .table_import_index_rel_sleb,
+            .table_import_index_rel_sleb64,
+            => {
+                const name = pointee.symbol_name;
+                const i: FunctionImport.Index = @enumFromInt(wasm.object_function_imports.getIndex(name).?);
+                try markFunctionImport(wasm, name, i.value(wasm), i);
+            },
+            .global_import_index_leb, .global_import_index_i32 => {
+                const name = pointee.symbol_name;
+                const i: GlobalImport.Index = @enumFromInt(wasm.object_global_imports.getIndex(name).?);
+                try markGlobalImport(wasm, name, i.value(wasm), i);
+            },
+            .table_import_number_leb => {
+                const name = pointee.symbol_name;
+                const i: TableImport.Index = @enumFromInt(wasm.object_table_imports.getIndex(name).?);
+                try markTableImport(wasm, name, i.value(wasm), i);
+            },
+
+            .function_index_leb,
+            .function_index_i32,
+            .function_offset_i32,
+            .function_offset_i64,
+            .table_index_sleb,
+            .table_index_i32,
+            .table_index_sleb64,
+            .table_index_i64,
+            .table_index_rel_sleb,
+            .table_index_rel_sleb64,
+            => try markFunction(wasm, pointee.function),
+            .global_index_leb,
+            .global_index_i32,
+            => try markGlobal(wasm, pointee.global),
+            .table_number_leb,
+            => try wasm.tables.put(wasm.base.comp.gpa, .fromObjectTable(pointee.table), {}),
+
+            .section_offset_i32 => {
+                log.warn("TODO: ensure section {d} is included in output", .{pointee.section});
+            },
+            .memory_addr_import_leb,
+            .memory_addr_import_sleb,
+            .memory_addr_import_i32,
+            .memory_addr_import_rel_sleb,
+            .memory_addr_import_leb64,
+            .memory_addr_import_sleb64,
+            .memory_addr_import_i64,
+            .memory_addr_import_rel_sleb64,
+            .memory_addr_import_tls_sleb,
+            .memory_addr_import_locrel_i32,
+            .memory_addr_import_tls_sleb64,
+            => {
+                const name = pointee.symbol_name;
+                if (name == wasm.preloaded_strings.__heap_end or
+                    name == wasm.preloaded_strings.__heap_base)
+                {
+                    continue;
+                }
+                log.warn("TODO: ensure data symbol {s} is included in output", .{name.slice(wasm)});
+            },
+
+            .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,
+            => try markDataSegment(wasm, pointee.data.ptr(wasm).segment),
+
+            .type_index_leb => continue,
+        }
+    }
 }
 
 pub fn flushModule(
src/codegen.zig
@@ -670,7 +670,7 @@ fn lowerUavRef(
                 try wasm.out_relocs.append(gpa, .{
                     .offset = @intCast(code.items.len),
                     .pointee = .{ .symbol_index = try wasm.uavSymbolIndex(uav.val) },
-                    .tag = if (ptr_width_bytes == 4) .MEMORY_ADDR_I32 else .MEMORY_ADDR_I64,
+                    .tag = if (ptr_width_bytes == 4) .memory_addr_i32 else .memory_addr_i64,
                     .addend = @intCast(offset),
                 });
             } else {
@@ -742,7 +742,7 @@ fn lowerNavRef(
                 try wasm.out_relocs.append(gpa, .{
                     .offset = @intCast(code.items.len),
                     .pointee = .{ .symbol_index = try wasm.navSymbolIndex(nav_index) },
-                    .tag = if (ptr_width_bytes == 4) .MEMORY_ADDR_I32 else .MEMORY_ADDR_I64,
+                    .tag = if (ptr_width_bytes == 4) .memory_addr_i32 else .memory_addr_i64,
                     .addend = @intCast(offset),
                 });
             } else {