Commit 1a4c5837fe

Andrew Kelley <andrew@ziglang.org>
2024-12-24 03:18:54
wasm linker: fix crashes when parsing compiler_rt
1 parent 4b9dc29
src/link/Elf/Atom.zig
@@ -523,7 +523,7 @@ fn reportUnhandledRelocError(self: Atom, rel: elf.Elf64_Rela, elf_file: *Elf) Re
         relocation.fmtRelocType(rel.r_type(), elf_file.getTarget().cpu.arch),
         rel.r_offset,
     });
-    try err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
+    err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
     return error.RelocFailure;
 }
 
@@ -539,7 +539,7 @@ fn reportTextRelocError(
         rel.r_offset,
         symbol.name(elf_file),
     });
-    try err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
+    err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
     return error.RelocFailure;
 }
 
@@ -555,8 +555,8 @@ fn reportPicError(
         rel.r_offset,
         symbol.name(elf_file),
     });
-    try err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
-    try err.addNote("recompile with -fPIC", .{});
+    err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
+    err.addNote("recompile with -fPIC", .{});
     return error.RelocFailure;
 }
 
@@ -572,8 +572,8 @@ fn reportNoPicError(
         rel.r_offset,
         symbol.name(elf_file),
     });
-    try err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
-    try err.addNote("recompile with -fno-PIC", .{});
+    err.addNote("in {}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
+    err.addNote("recompile with -fno-PIC", .{});
     return error.RelocFailure;
 }
 
@@ -1187,7 +1187,7 @@ const x86_64 = struct {
                     x86_64.relaxGotPcTlsDesc(code[r_offset - 3 ..]) catch {
                         var err = try diags.addErrorWithNotes(1);
                         try err.addMsg("could not relax {s}", .{@tagName(r_type)});
-                        try err.addNote("in {}:{s} at offset 0x{x}", .{
+                        err.addNote("in {}:{s} at offset 0x{x}", .{
                             atom.file(elf_file).?.fmtPath(),
                             atom.name(elf_file),
                             rel.r_offset,
@@ -1332,7 +1332,7 @@ const x86_64 = struct {
                     relocation.fmtRelocType(rels[0].r_type(), .x86_64),
                     relocation.fmtRelocType(rels[1].r_type(), .x86_64),
                 });
-                try err.addNote("in {}:{s} at offset 0x{x}", .{
+                err.addNote("in {}:{s} at offset 0x{x}", .{
                     self.file(elf_file).?.fmtPath(),
                     self.name(elf_file),
                     rels[0].r_offset,
@@ -1388,7 +1388,7 @@ const x86_64 = struct {
                     relocation.fmtRelocType(rels[0].r_type(), .x86_64),
                     relocation.fmtRelocType(rels[1].r_type(), .x86_64),
                 });
-                try err.addNote("in {}:{s} at offset 0x{x}", .{
+                err.addNote("in {}:{s} at offset 0x{x}", .{
                     self.file(elf_file).?.fmtPath(),
                     self.name(elf_file),
                     rels[0].r_offset,
@@ -1485,7 +1485,7 @@ const x86_64 = struct {
                     relocation.fmtRelocType(rels[0].r_type(), .x86_64),
                     relocation.fmtRelocType(rels[1].r_type(), .x86_64),
                 });
-                try err.addNote("in {}:{s} at offset 0x{x}", .{
+                err.addNote("in {}:{s} at offset 0x{x}", .{
                     self.file(elf_file).?.fmtPath(),
                     self.name(elf_file),
                     rels[0].r_offset,
@@ -1672,7 +1672,7 @@ const aarch64 = struct {
                 // TODO: relax
                 var err = try diags.addErrorWithNotes(1);
                 try err.addMsg("TODO: relax ADR_GOT_PAGE", .{});
-                try err.addNote("in {}:{s} at offset 0x{x}", .{
+                err.addNote("in {}:{s} at offset 0x{x}", .{
                     atom.file(elf_file).?.fmtPath(),
                     atom.name(elf_file),
                     r_offset,
@@ -1959,7 +1959,7 @@ const riscv = struct {
                     // TODO: implement searching forward
                     var err = try diags.addErrorWithNotes(1);
                     try err.addMsg("TODO: find HI20 paired reloc scanning forward", .{});
-                    try err.addNote("in {}:{s} at offset 0x{x}", .{
+                    err.addNote("in {}:{s} at offset 0x{x}", .{
                         atom.file(elf_file).?.fmtPath(),
                         atom.name(elf_file),
                         rel.r_offset,
src/link/Elf/eh_frame.zig
@@ -611,7 +611,7 @@ fn reportInvalidReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela) !void {
         relocation.fmtRelocType(rel.r_type(), elf_file.getTarget().cpu.arch),
         rel.r_offset,
     });
-    try err.addNote("in {}:.eh_frame", .{elf_file.file(rec.file_index).?.fmtPath()});
+    err.addNote("in {}:.eh_frame", .{elf_file.file(rec.file_index).?.fmtPath()});
     return error.RelocFailure;
 }
 
src/link/Elf/Object.zig
@@ -797,7 +797,7 @@ pub fn initInputMergeSections(self: *Object, elf_file: *Elf) !void {
                 if (!isNull(data[end .. end + sh_entsize])) {
                     var err = try diags.addErrorWithNotes(1);
                     try err.addMsg("string not null terminated", .{});
-                    try err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
+                    err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
                     return error.LinkFailure;
                 }
                 end += sh_entsize;
@@ -812,7 +812,7 @@ pub fn initInputMergeSections(self: *Object, elf_file: *Elf) !void {
             if (shdr.sh_size % sh_entsize != 0) {
                 var err = try diags.addErrorWithNotes(1);
                 try err.addMsg("size not a multiple of sh_entsize", .{});
-                try err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
+                err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
                 return error.LinkFailure;
             }
 
@@ -889,8 +889,8 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) error{
         const res = imsec.findSubsection(@intCast(esym.st_value)) orelse {
             var err = try diags.addErrorWithNotes(2);
             try err.addMsg("invalid symbol value: {x}", .{esym.st_value});
-            try err.addNote("for symbol {s}", .{sym.name(elf_file)});
-            try err.addNote("in {}", .{self.fmtPath()});
+            err.addNote("for symbol {s}", .{sym.name(elf_file)});
+            err.addNote("in {}", .{self.fmtPath()});
             return error.LinkFailure;
         };
 
@@ -915,7 +915,7 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) error{
             const res = imsec.findSubsection(@intCast(@as(i64, @intCast(esym.st_value)) + rel.r_addend)) orelse {
                 var err = try diags.addErrorWithNotes(1);
                 try err.addMsg("invalid relocation at offset 0x{x}", .{rel.r_offset});
-                try err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
+                err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
                 return error.LinkFailure;
             };
 
src/link/MachO/Atom.zig
@@ -909,8 +909,8 @@ const x86_64 = struct {
                     rel.offset,
                     rel.fmtPretty(.x86_64),
                 });
-                try err.addNote("expected .mov instruction but found .{s}", .{@tagName(x)});
-                try err.addNote("while parsing {}", .{self.getFile(macho_file).fmtPath()});
+                err.addNote("expected .mov instruction but found .{s}", .{@tagName(x)});
+                err.addNote("while parsing {}", .{self.getFile(macho_file).fmtPath()});
                 return error.RelaxFailUnexpectedInstruction;
             },
         }
src/link/Wasm/Flush.zig
@@ -22,7 +22,7 @@ const assert = std.debug.assert;
 /// Ordered list of data segments that will appear in the final binary.
 /// When sorted, to-be-merged segments will be made adjacent.
 /// Values are virtual address.
-data_segments: std.AutoArrayHashMapUnmanaged(Wasm.DataSegment.Id, u32) = .empty,
+data_segments: std.AutoArrayHashMapUnmanaged(Wasm.DataId, u32) = .empty,
 /// Each time a `data_segment` offset equals zero it indicates a new group, and
 /// the next element in this array will contain the total merged segment size.
 /// Value is the virtual memory address of the end of the segment.
@@ -120,7 +120,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
             if (wasm.entry_resolution == .unresolved) {
                 var err = try diags.addErrorWithNotes(1);
                 try err.addMsg("entry symbol '{s}' missing", .{name.slice(wasm)});
-                try err.addNote("'-fno-entry' suppresses this error", .{});
+                err.addNote("'-fno-entry' suppresses this error", .{});
             }
         }
     }
@@ -176,10 +176,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
     }), @as(u32, undefined));
     for (wasm.object_data_segments.items, 0..) |*ds, i| {
         if (!ds.flags.alive) continue;
-        const data_segment_index: Wasm.ObjectDataSegmentIndex = @enumFromInt(i);
+        const obj_seg_index: Wasm.ObjectDataSegment.Index = @enumFromInt(i);
         any_passive_inits = any_passive_inits or ds.flags.is_passive or (import_memory and !wasm.isBss(ds.name));
         _ = f.data_segments.putAssumeCapacityNoClobber(.pack(wasm, .{
-            .object = data_segment_index,
+            .object = obj_seg_index,
         }), @as(u32, undefined));
     }
     if (wasm.error_name_table_ref_count > 0) {
@@ -229,7 +229,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
     // For the purposes of sorting, they are implicitly all named ".data".
     const Sort = struct {
         wasm: *const Wasm,
-        segments: []const Wasm.DataSegment.Id,
+        segments: []const Wasm.DataId,
         pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
             const lhs_segment = ctx.segments[lhs];
             const rhs_segment = ctx.segments[rhs];
@@ -312,7 +312,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
     const data_vaddr: u32 = @intCast(memory_ptr);
     {
         var seen_tls: enum { before, during, after } = .before;
-        var category: Wasm.DataSegment.Category = undefined;
+        var category: Wasm.DataId.Category = undefined;
         for (segment_ids, segment_vaddrs, 0..) |segment_id, *segment_vaddr, i| {
             const alignment = segment_id.alignment(wasm);
             category = segment_id.category(wasm);
@@ -707,7 +707,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
 
     if (!is_obj) {
         for (wasm.uav_fixups.items) |uav_fixup| {
-            const ds_id: Wasm.DataSegment.Id = .pack(wasm, .{ .uav_exe = uav_fixup.uavs_exe_index });
+            const ds_id: Wasm.DataId = .pack(wasm, .{ .uav_exe = uav_fixup.uavs_exe_index });
             const vaddr = f.data_segments.get(ds_id).?;
             if (!is64) {
                 mem.writeInt(u32, wasm.string_bytes.items[uav_fixup.offset..][0..4], vaddr, .little);
@@ -716,7 +716,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
             }
         }
         for (wasm.nav_fixups.items) |nav_fixup| {
-            const ds_id: Wasm.DataSegment.Id = .pack(wasm, .{ .nav_exe = nav_fixup.navs_exe_index });
+            const ds_id: Wasm.DataId = .pack(wasm, .{ .nav_exe = nav_fixup.navs_exe_index });
             const vaddr = f.data_segments.get(ds_id).?;
             if (!is64) {
                 mem.writeInt(u32, wasm.string_bytes.items[nav_fixup.offset..][0..4], vaddr, .little);
@@ -862,7 +862,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
 
 fn emitNameSection(
     wasm: *Wasm,
-    data_segments: *const std.AutoArrayHashMapUnmanaged(Wasm.DataSegment.Id, u32),
+    data_segments: *const std.AutoArrayHashMapUnmanaged(Wasm.DataId, u32),
     binary_bytes: *std.ArrayListUnmanaged(u8),
 ) !void {
     const f = &wasm.flush_buffer;
@@ -1137,9 +1137,9 @@ fn splitSegmentName(name: []const u8) struct { []const u8, []const u8 } {
 
 fn wantSegmentMerge(
     wasm: *const Wasm,
-    a_id: Wasm.DataSegment.Id,
-    b_id: Wasm.DataSegment.Id,
-    b_category: Wasm.DataSegment.Category,
+    a_id: Wasm.DataId,
+    b_id: Wasm.DataId,
+    b_category: Wasm.DataId.Category,
 ) bool {
     const a_category = a_id.category(wasm);
     if (a_category != b_category) return false;
src/link/Wasm/Object.zig
@@ -102,11 +102,7 @@ pub const Symbol = struct {
     const Pointee = union(enum) {
         function: Wasm.ObjectFunctionIndex,
         function_import: ScratchSpace.FuncImportIndex,
-        data: struct {
-            segment_index: Wasm.ObjectDataSegmentIndex,
-            segment_offset: u32,
-            size: u32,
-        },
+        data: Wasm.ObjectData.Index,
         data_import: void,
         global: Wasm.ObjectGlobalIndex,
         global_import: Wasm.GlobalImport.Index,
@@ -131,7 +127,7 @@ pub const ScratchSpace = struct {
         const Pointee = union(std.wasm.ExternalKind) {
             function: Wasm.ObjectFunctionIndex,
             table: Wasm.ObjectTableIndex,
-            memory: Wasm.ObjectMemoryIndex,
+            memory: Wasm.ObjectMemory.Index,
             global: Wasm.ObjectGlobalIndex,
         };
     };
@@ -184,8 +180,9 @@ pub fn parse(
     must_link: bool,
     gc_sections: bool,
 ) anyerror!Object {
-    const gpa = wasm.base.comp.gpa;
-    const diags = &wasm.base.comp.link_diags;
+    const comp = wasm.base.comp;
+    const gpa = comp.gpa;
+    const diags = &comp.link_diags;
 
     var pos: usize = 0;
 
@@ -334,12 +331,16 @@ pub fn parse(
                                                 const segment_index, pos = readLeb(u32, bytes, pos);
                                                 const segment_offset, pos = readLeb(u32, bytes, pos);
                                                 const size, pos = readLeb(u32, bytes, pos);
-
-                                                symbol.pointee = .{ .data = .{
-                                                    .segment_index = @enumFromInt(data_segment_start + segment_index),
-                                                    .segment_offset = segment_offset,
+                                                try wasm.object_datas.append(gpa, .{
+                                                    .segment = @enumFromInt(data_segment_start + segment_index),
+                                                    .offset = segment_offset,
                                                     .size = size,
-                                                } };
+                                                    .name = symbol.name,
+                                                    .flags = symbol.flags,
+                                                });
+                                                symbol.pointee = .{
+                                                    .data = @enumFromInt(wasm.object_datas.items.len - 1),
+                                                };
                                             }
                                         },
                                         .section => {
@@ -405,7 +406,6 @@ pub fn parse(
                                             return error.UnrecognizedSymbolType;
                                         },
                                     }
-                                    log.debug("found symbol: {}", .{symbol});
                                 }
                             },
                         }
@@ -450,22 +450,10 @@ pub fn parse(
                             .MEMORY_ADDR_TLS_SLEB64,
                             => {
                                 const addend: i32, pos = readLeb(i32, bytes, pos);
-                                const sym_section = ss.symbol_table.items[index].pointee.data;
-                                if (sym_section.segment_offset != 0) {
-                                    return diags.failParse(path, "data symbol {d} has nonzero offset {d}", .{
-                                        index, sym_section.segment_offset,
-                                    });
-                                }
-                                const seg_size = sym_section.segment_index.ptr(wasm).payload.len;
-                                if (sym_section.size != seg_size) {
-                                    return diags.failParse(path, "data symbol {d} has size {d}, inequal to corresponding data segment {d} size {d}", .{
-                                        index, sym_section.size, @intFromEnum(sym_section.segment_index), seg_size,
-                                    });
-                                }
                                 wasm.object_relocations.appendAssumeCapacity(.{
                                     .tag = tag,
                                     .offset = offset,
-                                    .pointee = .{ .data_segment = sym_section.segment_index },
+                                    .pointee = .{ .data = ss.symbol_table.items[index].pointee.data },
                                     .addend = addend,
                                 });
                             },
@@ -651,7 +639,15 @@ pub fn parse(
                 const memories_len, pos = readLeb(u32, bytes, pos);
                 for (try wasm.object_memories.addManyAsSlice(gpa, memories_len)) |*memory| {
                     const limits, pos = readLimits(bytes, pos);
-                    memory.* = .{ .limits = limits };
+                    memory.* = .{
+                        .name = .none,
+                        .flags = .{
+                            .limits_has_max = limits.flags.has_max,
+                            .limits_is_shared = limits.flags.is_shared,
+                        },
+                        .limits_min = limits.min,
+                        .limits_max = limits.max,
+                    };
                 }
             },
             .global => {
@@ -722,7 +718,6 @@ pub fn parse(
                 }
             },
             .data => {
-                const start = pos;
                 const count, pos = readLeb(u32, bytes, pos);
                 for (try wasm.object_data_segments.addManyAsSlice(gpa, count)) |*elem| {
                     const flags, pos = readEnum(DataSegmentFlags, bytes, pos);
@@ -733,13 +728,10 @@ 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_offset: u32 = @intCast(pos - start);
                     const payload = try wasm.addRelocatableDataPayload(bytes[pos..][0..data_len]);
                     pos += data_len;
                     elem.* = .{
                         .payload = payload,
-                        .segment_offset = segment_offset,
-                        .section_index = section_index,
                         .name = .none, // Populated from symbol table
                         .flags = .{}, // Populated from symbol table and segment_info
                     };
@@ -751,20 +743,56 @@ pub fn parse(
     }
     if (!saw_linking_section) return error.MissingLinkingSection;
 
+    const target_features = comp.root_mod.resolved_target.result.cpu.features;
+
     if (has_tls) {
-        const cpu_features = wasm.base.comp.root_mod.resolved_target.result.cpu.features;
-        if (!std.Target.wasm.featureSetHas(cpu_features, .atomics))
+        if (!std.Target.wasm.featureSetHas(target_features, .atomics))
             return diags.failParse(path, "object has TLS segment but target CPU feature atomics is disabled", .{});
-        if (!std.Target.wasm.featureSetHas(cpu_features, .bulk_memory))
+        if (!std.Target.wasm.featureSetHas(target_features, .bulk_memory))
             return diags.failParse(path, "object has TLS segment but target CPU feature bulk_memory is disabled", .{});
     }
 
     const features = opt_features orelse return error.MissingFeatures;
-    if (true) @panic("iterate features, match against target features");
+    for (features.slice(wasm)) |feat| {
+        log.debug("feature: {s}{s}", .{ @tagName(feat.prefix), @tagName(feat.tag) });
+        switch (feat.prefix) {
+            .invalid => unreachable,
+            .@"-" => switch (feat.tag) {
+                .@"shared-mem" => if (comp.config.shared_memory) {
+                    return diags.failParse(path, "object forbids shared-mem but compilation enables it", .{});
+                },
+                else => {
+                    const f = feat.tag.toCpuFeature().?;
+                    if (std.Target.wasm.featureSetHas(target_features, f)) {
+                        return diags.failParse(
+                            path,
+                            "object forbids {s} but specified target features include {s}",
+                            .{ @tagName(feat.tag), @tagName(f) },
+                        );
+                    }
+                },
+            },
+            .@"+", .@"=" => switch (feat.tag) {
+                .@"shared-mem" => if (!comp.config.shared_memory) {
+                    return diags.failParse(path, "object requires shared-mem but compilation disables it", .{});
+                },
+                else => {
+                    const f = feat.tag.toCpuFeature().?;
+                    if (!std.Target.wasm.featureSetHas(target_features, f)) {
+                        return diags.failParse(
+                            path,
+                            "object requires {s} but specified target features exclude {s}",
+                            .{ @tagName(feat.tag), @tagName(f) },
+                        );
+                    }
+                },
+            },
+        }
+    }
 
     // Apply function type information.
-    for (ss.func_types.items, wasm.object_functions.items[functions_start..]) |func_type, *func| {
-        func.type_index = func_type;
+    for (ss.func_type_indexes.items, wasm.object_functions.items[functions_start..]) |func_type, *func| {
+        func.type_index = func_type.ptr(ss).*;
     }
 
     // Apply symbol table information.
@@ -782,15 +810,21 @@ pub fn parse(
                 if (gop.value_ptr.type != fn_ty_index) {
                     var err = try diags.addErrorWithNotes(2);
                     try err.addMsg("symbol '{s}' mismatching function signatures", .{name.slice(wasm)});
-                    try err.addSrcNote(gop.value_ptr.source_location, "imported as {} here", .{gop.value_ptr.type.fmt(wasm)});
-                    try err.addSrcNote(source_location, "imported as {} here", .{fn_ty_index.fmt(wasm)});
+                    gop.value_ptr.source_location.addNote(wasm, &err, "imported as {} here", .{
+                        gop.value_ptr.type.fmt(wasm),
+                    });
+                    source_location.addNote(wasm, &err, "imported as {} here", .{fn_ty_index.fmt(wasm)});
                     continue;
                 }
-                if (gop.value_ptr.module_name != ptr.module_name) {
+                if (gop.value_ptr.module_name != ptr.module_name.toOptional()) {
                     var err = try diags.addErrorWithNotes(2);
                     try err.addMsg("symbol '{s}' mismatching module names", .{name.slice(wasm)});
-                    try err.addSrcNote(gop.value_ptr.source_location, "module '{s}' here", .{gop.value_ptr.module_name.slice(wasm)});
-                    try err.addSrcNote(source_location, "module '{s}' here", .{ptr.module_name.slice(wasm)});
+                    if (gop.value_ptr.module_name.slice(wasm)) |module_name| {
+                        gop.value_ptr.source_location.addNote(wasm, &err, "module '{s}' here", .{module_name});
+                    } else {
+                        gop.value_ptr.source_location.addNote(wasm, &err, "no module here", .{});
+                    }
+                    source_location.addNote(wasm, &err, "module '{s}' here", .{ptr.module_name.slice(wasm)});
                     continue;
                 }
                 if (symbol.flags.binding == .strong) gop.value_ptr.flags.binding = .strong;
@@ -799,7 +833,7 @@ pub fn parse(
             } else {
                 gop.value_ptr.* = .{
                     .flags = symbol.flags,
-                    .module_name = ptr.module_name,
+                    .module_name = ptr.module_name.toOptional(),
                     .source_location = source_location,
                     .resolution = .unresolved,
                     .type = fn_ty_index,
@@ -808,7 +842,7 @@ pub fn parse(
         },
         .function => |index| {
             assert(!symbol.flags.undefined);
-            const ptr = index.ptr();
+            const ptr = index.ptr(wasm);
             ptr.name = symbol.name;
             ptr.flags = symbol.flags;
             if (symbol.flags.binding == .local) continue; // No participation in symbol resolution.
@@ -818,35 +852,46 @@ pub fn parse(
                 if (gop.value_ptr.type != ptr.type_index) {
                     var err = try diags.addErrorWithNotes(2);
                     try err.addMsg("function signature mismatch: {s}", .{name.slice(wasm)});
-                    try err.addSrcNote(gop.value_ptr.source_location, "exported as {} here", .{ptr.type_index.fmt(wasm)});
-                    const word = if (gop.value_ptr.resolution == .none) "imported" else "exported";
-                    try err.addSrcNote(source_location, "{s} as {} here", .{ word, gop.value_ptr.type.fmt(wasm) });
+                    gop.value_ptr.source_location.addNote(wasm, &err, "exported as {} here", .{
+                        ptr.type_index.fmt(wasm),
+                    });
+                    const word = if (gop.value_ptr.resolution == .unresolved) "imported" else "exported";
+                    source_location.addNote(wasm, &err, "{s} as {} here", .{ word, gop.value_ptr.type.fmt(wasm) });
                     continue;
                 }
-                if (gop.value_ptr.resolution == .none or gop.value_ptr.flags.binding == .weak) {
+                if (gop.value_ptr.resolution == .unresolved or gop.value_ptr.flags.binding == .weak) {
                     // Intentional: if they're both weak, take the last one.
                     gop.value_ptr.source_location = source_location;
                     gop.value_ptr.module_name = host_name;
-                    gop.value_ptr.resolution = .fromObjectFunction(index);
+                    gop.value_ptr.resolution = .fromObjectFunction(wasm, index);
                     continue;
                 }
                 var err = try diags.addErrorWithNotes(2);
                 try err.addMsg("symbol collision: {s}", .{name.slice(wasm)});
-                try err.addSrcNote(gop.value_ptr.source_location, "exported as {} here", .{ptr.type_index.fmt(wasm)});
-                try err.addSrcNote(source_location, "exported as {} here", .{gop.value_ptr.type.fmt(wasm)});
+                gop.value_ptr.source_location.addNote(wasm, &err, "exported as {} here", .{ptr.type_index.fmt(wasm)});
+                source_location.addNote(wasm, &err, "exported as {} here", .{gop.value_ptr.type.fmt(wasm)});
                 continue;
             } else {
                 gop.value_ptr.* = .{
                     .flags = symbol.flags,
                     .module_name = host_name,
                     .source_location = source_location,
-                    .resolution = .fromObjectFunction(index),
+                    .resolution = .fromObjectFunction(wasm, index),
                     .type = ptr.type_index,
                 };
             }
         },
 
-        inline .global, .global_import, .table, .table_import => |i| {
+        inline .global_import, .table_import => |i| {
+            const ptr = i.value(wasm);
+            assert(i.key(wasm).toOptional() == symbol.name); // TODO
+            ptr.flags = symbol.flags;
+            if (symbol.flags.undefined and symbol.flags.binding == .local) {
+                const name = i.key(wasm).slice(wasm);
+                diags.addParseError(path, "local symbol '{s}' references import", .{name});
+            }
+        },
+        inline .global, .table => |i| {
             const ptr = i.ptr(wasm);
             ptr.name = symbol.name;
             ptr.flags = symbol.flags;
@@ -857,10 +902,11 @@ pub fn parse(
         },
         .section => |i| {
             // Name is provided by the section directly; symbol table does not have it.
-            const ptr = i.ptr(wasm);
-            ptr.flags = symbol.flags;
+            //const ptr = i.ptr(wasm);
+            //ptr.flags = symbol.flags;
+            _ = i;
             if (symbol.flags.undefined and symbol.flags.binding == .local) {
-                const name = ptr.name.slice(wasm);
+                const name = symbol.name.slice(wasm).?;
                 diags.addParseError(path, "local symbol '{s}' references import", .{name});
             }
         },
@@ -868,15 +914,7 @@ pub fn parse(
             const name = symbol.name.unwrap().?;
             log.warn("TODO data import '{s}'", .{name.slice(wasm)});
         },
-        .data => |data| {
-            const ptr = data.ptr(wasm);
-            const is_passive = ptr.flags.is_passive;
-            ptr.name = symbol.name;
-            ptr.flags = symbol.flags;
-            ptr.flags.is_passive = is_passive;
-            ptr.offset = data.segment_offset;
-            ptr.size = data.size;
-        },
+        .data => continue, // `wasm.object_datas` has already been populated.
     };
 
     // Apply export section info. This is done after the symbol table above so
@@ -957,18 +995,6 @@ pub fn parse(
             .off = functions_start,
             .len = @intCast(wasm.object_functions.items.len - functions_start),
         },
-        .globals = .{
-            .off = globals_start,
-            .len = @intCast(wasm.object_globals.items.len - globals_start),
-        },
-        .tables = .{
-            .off = tables_start,
-            .len = @intCast(wasm.object_tables.items.len - tables_start),
-        },
-        .memories = .{
-            .off = memories_start,
-            .len = @intCast(wasm.object_memories.items.len - memories_start),
-        },
         .function_imports = .{
             .off = function_imports_start,
             .len = @intCast(wasm.object_function_imports.entries.len - function_imports_start),
@@ -979,7 +1005,7 @@ pub fn parse(
         },
         .table_imports = .{
             .off = table_imports_start,
-            .len = @intCast(wasm.object_table_imports.items.len - table_imports_start),
+            .len = @intCast(wasm.object_table_imports.entries.len - table_imports_start),
         },
         .init_funcs = .{
             .off = init_funcs_start,
src/link/Elf.zig
@@ -3394,7 +3394,7 @@ fn allocatePhdrTable(self: *Elf) error{OutOfMemory}!void {
         // TODO verify `getMaxNumberOfPhdrs()` is accurate and convert this into no-op
         var err = try diags.addErrorWithNotes(1);
         try err.addMsg("fatal linker error: not enough space reserved for EHDR and PHDR table", .{});
-        try err.addNote("required 0x{x}, available 0x{x}", .{ needed_size, available_space });
+        err.addNote("required 0x{x}, available 0x{x}", .{ needed_size, available_space });
     }
 
     phdr_table_load.p_filesz = needed_size + ehsize;
@@ -4545,12 +4545,12 @@ fn reportUndefinedSymbols(self: *Elf, undefs: anytype) !void {
         for (refs.items[0..nrefs]) |ref| {
             const atom_ptr = self.atom(ref).?;
             const file_ptr = atom_ptr.file(self).?;
-            try err.addNote("referenced by {s}:{s}", .{ file_ptr.fmtPath(), atom_ptr.name(self) });
+            err.addNote("referenced by {s}:{s}", .{ file_ptr.fmtPath(), atom_ptr.name(self) });
         }
 
         if (refs.items.len > max_notes) {
             const remaining = refs.items.len - max_notes;
-            try err.addNote("referenced {d} more times", .{remaining});
+            err.addNote("referenced {d} more times", .{remaining});
         }
     }
 }
@@ -4567,17 +4567,17 @@ fn reportDuplicates(self: *Elf, dupes: anytype) error{ HasDuplicates, OutOfMemor
 
         var err = try diags.addErrorWithNotes(nnotes + 1);
         try err.addMsg("duplicate symbol definition: {s}", .{sym.name(self)});
-        try err.addNote("defined by {}", .{sym.file(self).?.fmtPath()});
+        err.addNote("defined by {}", .{sym.file(self).?.fmtPath()});
 
         var inote: usize = 0;
         while (inote < @min(notes.items.len, max_notes)) : (inote += 1) {
             const file_ptr = self.file(notes.items[inote]).?;
-            try err.addNote("defined by {}", .{file_ptr.fmtPath()});
+            err.addNote("defined by {}", .{file_ptr.fmtPath()});
         }
 
         if (notes.items.len > max_notes) {
             const remaining = notes.items.len - max_notes;
-            try err.addNote("defined {d} more times", .{remaining});
+            err.addNote("defined {d} more times", .{remaining});
         }
     }
 
@@ -4601,7 +4601,7 @@ pub fn addFileError(
     const diags = &self.base.comp.link_diags;
     var err = try diags.addErrorWithNotes(1);
     try err.addMsg(format, args);
-    try err.addNote("while parsing {}", .{self.file(file_index).?.fmtPath()});
+    err.addNote("while parsing {}", .{self.file(file_index).?.fmtPath()});
 }
 
 pub fn failFile(
src/link/MachO.zig
@@ -1572,21 +1572,21 @@ fn reportUndefs(self: *MachO) !void {
         try err.addMsg("undefined symbol: {s}", .{undef_sym.getName(self)});
 
         switch (notes) {
-            .force_undefined => try err.addNote("referenced with linker flag -u", .{}),
-            .entry => try err.addNote("referenced with linker flag -e", .{}),
-            .dyld_stub_binder, .objc_msgsend => try err.addNote("referenced implicitly", .{}),
+            .force_undefined => err.addNote("referenced with linker flag -u", .{}),
+            .entry => err.addNote("referenced with linker flag -e", .{}),
+            .dyld_stub_binder, .objc_msgsend => err.addNote("referenced implicitly", .{}),
             .refs => |refs| {
                 var inote: usize = 0;
                 while (inote < @min(refs.items.len, max_notes)) : (inote += 1) {
                     const ref = refs.items[inote];
                     const file = self.getFile(ref.file).?;
                     const atom = ref.getAtom(self).?;
-                    try err.addNote("referenced by {}:{s}", .{ file.fmtPath(), atom.getName(self) });
+                    err.addNote("referenced by {}:{s}", .{ file.fmtPath(), atom.getName(self) });
                 }
 
                 if (refs.items.len > max_notes) {
                     const remaining = refs.items.len - max_notes;
-                    try err.addNote("referenced {d} more times", .{remaining});
+                    err.addNote("referenced {d} more times", .{remaining});
                 }
             },
         }
@@ -3473,8 +3473,8 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo
             seg_id,
             seg.segName(),
         });
-        try err.addNote("TODO: emit relocations to memory locations in self-hosted backends", .{});
-        try err.addNote("as a workaround, try increasing pre-allocated virtual memory of each segment", .{});
+        err.addNote("TODO: emit relocations to memory locations in self-hosted backends", .{});
+        err.addNote("as a workaround, try increasing pre-allocated virtual memory of each segment", .{});
     }
 
     seg.vmsize = needed_size;
@@ -3776,7 +3776,7 @@ pub fn reportParseError2(
     const diags = &self.base.comp.link_diags;
     var err = try diags.addErrorWithNotes(1);
     try err.addMsg(format, args);
-    try err.addNote("while parsing {}", .{self.getFile(file_index).?.fmtPath()});
+    err.addNote("while parsing {}", .{self.getFile(file_index).?.fmtPath()});
 }
 
 fn reportMissingDependencyError(
@@ -3790,10 +3790,10 @@ fn reportMissingDependencyError(
     const diags = &self.base.comp.link_diags;
     var err = try diags.addErrorWithNotes(2 + checked_paths.len);
     try err.addMsg(format, args);
-    try err.addNote("while resolving {s}", .{path});
-    try err.addNote("a dependency of {}", .{self.getFile(parent).?.fmtPath()});
+    err.addNote("while resolving {s}", .{path});
+    err.addNote("a dependency of {}", .{self.getFile(parent).?.fmtPath()});
     for (checked_paths) |p| {
-        try err.addNote("tried {s}", .{p});
+        err.addNote("tried {s}", .{p});
     }
 }
 
@@ -3807,8 +3807,8 @@ fn reportDependencyError(
     const diags = &self.base.comp.link_diags;
     var err = try diags.addErrorWithNotes(2);
     try err.addMsg(format, args);
-    try err.addNote("while parsing {s}", .{path});
-    try err.addNote("a dependency of {}", .{self.getFile(parent).?.fmtPath()});
+    err.addNote("while parsing {s}", .{path});
+    err.addNote("a dependency of {}", .{self.getFile(parent).?.fmtPath()});
 }
 
 fn reportDuplicates(self: *MachO) error{ HasDuplicates, OutOfMemory }!void {
@@ -3838,17 +3838,17 @@ fn reportDuplicates(self: *MachO) error{ HasDuplicates, OutOfMemory }!void {
 
         var err = try diags.addErrorWithNotes(nnotes + 1);
         try err.addMsg("duplicate symbol definition: {s}", .{sym.getName(self)});
-        try err.addNote("defined by {}", .{sym.getFile(self).?.fmtPath()});
+        err.addNote("defined by {}", .{sym.getFile(self).?.fmtPath()});
 
         var inote: usize = 0;
         while (inote < @min(notes.items.len, max_notes)) : (inote += 1) {
             const file = self.getFile(notes.items[inote]).?;
-            try err.addNote("defined by {}", .{file.fmtPath()});
+            err.addNote("defined by {}", .{file.fmtPath()});
         }
 
         if (notes.items.len > max_notes) {
             const remaining = notes.items.len - max_notes;
-            try err.addNote("defined {d} more times", .{remaining});
+            err.addNote("defined {d} more times", .{remaining});
         }
     }
     return error.HasDuplicates;
src/link/Wasm.zig
@@ -109,7 +109,7 @@ object_tables: std.ArrayListUnmanaged(Table) = .empty,
 /// All memory imports for all objects.
 object_memory_imports: std.ArrayListUnmanaged(MemoryImport) = .empty,
 /// All parsed memory sections for all objects.
-object_memories: std.ArrayListUnmanaged(std.wasm.Memory) = .empty,
+object_memories: std.ArrayListUnmanaged(ObjectMemory) = .empty,
 
 /// All relocations from all objects concatenated. `relocs_start` marks the end
 /// point of object relocations and start point of Zcu relocations.
@@ -119,8 +119,13 @@ object_relocations: std.MultiArrayList(ObjectRelocation) = .empty,
 /// by the (synthetic) __wasm_call_ctors function.
 object_init_funcs: std.ArrayListUnmanaged(InitFunc) = .empty,
 
-/// Non-synthetic section that can essentially be mem-cpy'd into place after performing relocations.
-object_data_segments: std.ArrayListUnmanaged(DataSegment) = .empty,
+/// The data section of an object has many segments. Each segment corresponds
+/// logically to an object file's .data section, or .rodata section. In
+/// the case of `-fdata-sections` there will be one segment per data symbol.
+object_data_segments: std.ArrayListUnmanaged(ObjectDataSegment) = .empty,
+/// Each segment has many data symbols. These correspond logically to global
+/// constants.
+object_datas: std.ArrayListUnmanaged(ObjectData) = .empty,
 /// Non-synthetic section that can essentially be mem-cpy'd into place after performing relocations.
 object_custom_segments: std.AutoArrayHashMapUnmanaged(ObjectSectionIndex, CustomSegment) = .empty,
 
@@ -457,6 +462,21 @@ pub const SourceLocation = enum(u32) {
             .source_location_index => @panic("TODO"),
         }
     }
+
+    pub fn addNote(
+        sl: SourceLocation,
+        wasm: *Wasm,
+        err: *link.Diags.ErrorWithNotes,
+        comptime f: []const u8,
+        args: anytype,
+    ) void {
+        switch (sl.unpack(wasm)) {
+            .none => err.addNote(f, args),
+            .zig_object_nofile => err.addNote("zig compilation unit: " ++ f, args),
+            .object_index => |i| err.addNote("{}: " ++ f, .{i.ptr(wasm).path} ++ args),
+            .source_location_index => @panic("TODO"),
+        }
+    }
 };
 
 /// The lower bits of this ABI-match the flags here:
@@ -687,13 +707,13 @@ pub const UavsExeIndex = enum(u32) {
 
 /// Used when emitting a relocatable object.
 pub const ZcuDataObj = extern struct {
-    code: DataSegment.Payload,
+    code: DataPayload,
     relocs: OutReloc.Slice,
 };
 
 /// Used when not emitting a relocatable object.
 pub const ZcuDataExe = extern struct {
-    code: DataSegment.Payload,
+    code: DataPayload,
     /// Tracks how many references there are for the purposes of sorting data segments.
     count: u32,
 };
@@ -917,6 +937,10 @@ pub const FunctionImport = extern struct {
             return pack(wasm, .{ .zcu_func = @enumFromInt(wasm.zcu_funcs.getIndex(ip_index).?) });
         }
 
+        pub fn fromObjectFunction(wasm: *const Wasm, object_function: ObjectFunctionIndex) Resolution {
+            return pack(wasm, .{ .object_function = object_function });
+        }
+
         pub fn isNavOrUnresolved(r: Resolution, wasm: *const Wasm) bool {
             return switch (r.unpack(wasm)) {
                 .unresolved, .zcu_func => true,
@@ -988,7 +1012,7 @@ pub const Function = extern struct {
     section_index: ObjectSectionIndex,
     source_location: SourceLocation,
 
-    pub const Code = DataSegment.Payload;
+    pub const Code = DataPayload;
 };
 
 pub const GlobalImport = extern struct {
@@ -1283,12 +1307,30 @@ pub const ObjectGlobalIndex = enum(u32) {
     }
 };
 
-/// Index into `Wasm.object_memories`.
-pub const ObjectMemoryIndex = enum(u32) {
-    _,
+pub const ObjectMemory = extern struct {
+    flags: SymbolFlags,
+    name: OptionalString,
+    limits_min: u32,
+    limits_max: u32,
 
-    pub fn ptr(index: ObjectMemoryIndex, wasm: *const Wasm) *std.wasm.Memory {
-        return &wasm.object_memories.items[@intFromEnum(index)];
+    /// Index into `Wasm.object_memories`.
+    pub const Index = enum(u32) {
+        _,
+
+        pub fn ptr(index: Index, wasm: *const Wasm) *ObjectMemory {
+            return &wasm.object_memories.items[@intFromEnum(index)];
+        }
+    };
+
+    pub fn limits(om: *const ObjectMemory) std.wasm.Limits {
+        return .{
+            .flags = .{
+                .has_max = om.limits_has_max,
+                .is_shared = om.limits_is_shared,
+            },
+            .min = om.limits_min,
+            .max = om.limits_max,
+        };
     }
 };
 
@@ -1318,14 +1360,74 @@ pub const OptionalObjectFunctionIndex = enum(u32) {
     }
 };
 
-pub const DataSegment = extern struct {
+pub const ObjectDataSegment = extern struct {
+    /// `none` if segment info custom subsection is missing.
+    name: OptionalString,
+    flags: SymbolFlags,
+    payload: DataPayload,
+
+    /// Index into `Wasm.object_data_segments`.
+    pub const Index = enum(u32) {
+        _,
+
+        pub fn ptr(i: Index, wasm: *const Wasm) *ObjectDataSegment {
+            return &wasm.object_data_segments.items[@intFromEnum(i)];
+        }
+    };
+};
+
+/// A local or exported global const from an object file.
+pub const ObjectData = extern struct {
+    segment: ObjectDataSegment.Index,
+    /// Index into the object segment payload. Must be <= the segment's size.
+    offset: u32,
+    /// May be zero. `offset + size` must be <= the segment's size.
+    size: u32,
     /// `none` if no symbol describes it.
     name: OptionalString,
     flags: SymbolFlags,
-    payload: Payload,
-    /// From the data segment start to the first byte of payload.
-    segment_offset: u32,
-    section_index: ObjectSectionIndex,
+
+    /// Index into `Wasm.object_datas`.
+    pub const Index = enum(u32) {
+        _,
+
+        pub fn ptr(i: Index, wasm: *const Wasm) *ObjectData {
+            return &wasm.object_datas.items[@intFromEnum(i)];
+        }
+    };
+};
+
+pub const DataPayload = extern struct {
+    off: Off,
+    /// The size in bytes of the data representing the segment within the section.
+    len: u32,
+
+    pub const Off = enum(u32) {
+        /// The payload is all zeroes (bss section).
+        none = std.math.maxInt(u32),
+        /// Points into string_bytes. No corresponding string_table entry.
+        _,
+
+        pub fn unwrap(off: Off) ?u32 {
+            return if (off == .none) null else @intFromEnum(off);
+        }
+    };
+
+    pub fn slice(p: DataPayload, wasm: *const Wasm) []const u8 {
+        return wasm.string_bytes.items[p.off.unwrap().?..][0..p.len];
+    }
+};
+
+/// A reference to a local or exported global const.
+pub const DataId = enum(u32) {
+    __zig_error_names,
+    __zig_error_name_table,
+    /// First, an `ObjectDataSegment.Index`.
+    /// Next, index into `uavs_obj` or `uavs_exe` depending on whether emitting an object.
+    /// Next, index into `navs_obj` or `navs_exe` depending on whether emitting an object.
+    _,
+
+    const first_object = @intFromEnum(DataId.__zig_error_name_table) + 1;
 
     pub const Category = enum {
         /// Thread-local variables.
@@ -1337,221 +1439,180 @@ pub const DataSegment = extern struct {
         zero,
     };
 
-    pub const Payload = extern struct {
-        off: Off,
-        /// The size in bytes of the data representing the segment within the section.
-        len: u32,
-
-        pub const Off = enum(u32) {
-            /// The payload is all zeroes (bss section).
-            none = std.math.maxInt(u32),
-            /// Points into string_bytes. No corresponding string_table entry.
-            _,
-
-            pub fn unwrap(off: Off) ?u32 {
-                return if (off == .none) null else @intFromEnum(off);
-            }
-        };
-
-        pub fn slice(p: DataSegment.Payload, wasm: *const Wasm) []const u8 {
-            return wasm.string_bytes.items[p.off.unwrap().?..][0..p.len];
-        }
-    };
-
-    pub const Id = enum(u32) {
+    pub const Unpacked = union(enum) {
         __zig_error_names,
         __zig_error_name_table,
-        /// First, an `ObjectDataSegmentIndex`.
-        /// Next, index into `uavs_obj` or `uavs_exe` depending on whether emitting an object.
-        /// Next, index into `navs_obj` or `navs_exe` depending on whether emitting an object.
-        _,
-
-        const first_object = @intFromEnum(Id.__zig_error_name_table) + 1;
+        object: ObjectDataSegment.Index,
+        uav_exe: UavsExeIndex,
+        uav_obj: UavsObjIndex,
+        nav_exe: NavsExeIndex,
+        nav_obj: NavsObjIndex,
+    };
 
-        pub const Unpacked = union(enum) {
-            __zig_error_names,
-            __zig_error_name_table,
-            object: ObjectDataSegmentIndex,
-            uav_exe: UavsExeIndex,
-            uav_obj: UavsObjIndex,
-            nav_exe: NavsExeIndex,
-            nav_obj: NavsObjIndex,
+    pub fn pack(wasm: *const Wasm, unpacked: Unpacked) DataId {
+        return switch (unpacked) {
+            .__zig_error_names => .__zig_error_names,
+            .__zig_error_name_table => .__zig_error_name_table,
+            .object => |i| @enumFromInt(first_object + @intFromEnum(i)),
+            inline .uav_exe, .uav_obj => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + @intFromEnum(i)),
+            .nav_exe => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + wasm.uavs_exe.entries.len + @intFromEnum(i)),
+            .nav_obj => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + wasm.uavs_obj.entries.len + @intFromEnum(i)),
         };
+    }
 
-        pub fn pack(wasm: *const Wasm, unpacked: Unpacked) Id {
-            return switch (unpacked) {
-                .__zig_error_names => .__zig_error_names,
-                .__zig_error_name_table => .__zig_error_name_table,
-                .object => |i| @enumFromInt(first_object + @intFromEnum(i)),
-                inline .uav_exe, .uav_obj => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + @intFromEnum(i)),
-                .nav_exe => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + wasm.uavs_exe.entries.len + @intFromEnum(i)),
-                .nav_obj => |i| @enumFromInt(first_object + wasm.object_data_segments.items.len + wasm.uavs_obj.entries.len + @intFromEnum(i)),
-            };
-        }
-
-        pub fn unpack(id: Id, wasm: *const Wasm) Unpacked {
-            return switch (id) {
-                .__zig_error_names => .__zig_error_names,
-                .__zig_error_name_table => .__zig_error_name_table,
-                _ => {
-                    const object_index = @intFromEnum(id) - first_object;
-
-                    const uav_index = if (object_index < wasm.object_data_segments.items.len)
-                        return .{ .object = @enumFromInt(object_index) }
+    pub fn unpack(id: DataId, wasm: *const Wasm) Unpacked {
+        return switch (id) {
+            .__zig_error_names => .__zig_error_names,
+            .__zig_error_name_table => .__zig_error_name_table,
+            _ => {
+                const object_index = @intFromEnum(id) - first_object;
+
+                const uav_index = if (object_index < wasm.object_data_segments.items.len)
+                    return .{ .object = @enumFromInt(object_index) }
+                else
+                    object_index - wasm.object_data_segments.items.len;
+
+                const comp = wasm.base.comp;
+                const is_obj = comp.config.output_mode == .Obj;
+                if (is_obj) {
+                    const nav_index = if (uav_index < wasm.uavs_obj.entries.len)
+                        return .{ .uav_obj = @enumFromInt(uav_index) }
                     else
-                        object_index - wasm.object_data_segments.items.len;
-
-                    const comp = wasm.base.comp;
-                    const is_obj = comp.config.output_mode == .Obj;
-                    if (is_obj) {
-                        const nav_index = if (uav_index < wasm.uavs_obj.entries.len)
-                            return .{ .uav_obj = @enumFromInt(uav_index) }
-                        else
-                            uav_index - wasm.uavs_obj.entries.len;
-
-                        return .{ .nav_obj = @enumFromInt(nav_index) };
-                    } else {
-                        const nav_index = if (uav_index < wasm.uavs_exe.entries.len)
-                            return .{ .uav_exe = @enumFromInt(uav_index) }
-                        else
-                            uav_index - wasm.uavs_exe.entries.len;
-
-                        return .{ .nav_exe = @enumFromInt(nav_index) };
-                    }
-                },
-            };
-        }
+                        uav_index - wasm.uavs_obj.entries.len;
 
-        pub fn category(id: Id, wasm: *const Wasm) Category {
-            return switch (unpack(id, wasm)) {
-                .__zig_error_names, .__zig_error_name_table => .data,
-                .object => |i| {
-                    const ptr = i.ptr(wasm);
-                    if (ptr.flags.tls) return .tls;
-                    if (wasm.isBss(ptr.name)) return .zero;
-                    return .data;
-                },
-                inline .uav_exe, .uav_obj => |i| if (i.value(wasm).code.off == .none) .zero else .data,
-                inline .nav_exe, .nav_obj => |i| {
-                    const zcu = wasm.base.comp.zcu.?;
-                    const ip = &zcu.intern_pool;
-                    const nav = ip.getNav(i.key(wasm).*);
-                    if (nav.isThreadLocal(ip)) return .tls;
-                    const code = i.value(wasm).code;
-                    return if (code.off == .none) .zero else .data;
-                },
-            };
-        }
+                    return .{ .nav_obj = @enumFromInt(nav_index) };
+                } else {
+                    const nav_index = if (uav_index < wasm.uavs_exe.entries.len)
+                        return .{ .uav_exe = @enumFromInt(uav_index) }
+                    else
+                        uav_index - wasm.uavs_exe.entries.len;
 
-        pub fn isTls(id: Id, wasm: *const Wasm) bool {
-            return switch (unpack(id, wasm)) {
-                .__zig_error_names, .__zig_error_name_table => false,
-                .object => |i| i.ptr(wasm).flags.tls,
-                .uav_exe, .uav_obj => false,
-                inline .nav_exe, .nav_obj => |i| {
-                    const zcu = wasm.base.comp.zcu.?;
-                    const ip = &zcu.intern_pool;
-                    const nav = ip.getNav(i.key(wasm).*);
-                    return nav.isThreadLocal(ip);
-                },
-            };
-        }
+                    return .{ .nav_exe = @enumFromInt(nav_index) };
+                }
+            },
+        };
+    }
 
-        pub fn isBss(id: Id, wasm: *const Wasm) bool {
-            return id.category(wasm) == .zero;
-        }
+    pub fn category(id: DataId, wasm: *const Wasm) Category {
+        return switch (unpack(id, wasm)) {
+            .__zig_error_names, .__zig_error_name_table => .data,
+            .object => |i| {
+                const ptr = i.ptr(wasm);
+                if (ptr.flags.tls) return .tls;
+                if (wasm.isBss(ptr.name)) return .zero;
+                return .data;
+            },
+            inline .uav_exe, .uav_obj => |i| if (i.value(wasm).code.off == .none) .zero else .data,
+            inline .nav_exe, .nav_obj => |i| {
+                const zcu = wasm.base.comp.zcu.?;
+                const ip = &zcu.intern_pool;
+                const nav = ip.getNav(i.key(wasm).*);
+                if (nav.isThreadLocal(ip)) return .tls;
+                const code = i.value(wasm).code;
+                return if (code.off == .none) .zero else .data;
+            },
+        };
+    }
 
-        pub fn name(id: Id, wasm: *const Wasm) []const u8 {
-            return switch (unpack(id, wasm)) {
-                .__zig_error_names, .__zig_error_name_table, .uav_exe, .uav_obj => ".data",
-                .object => |i| i.ptr(wasm).name.unwrap().?.slice(wasm),
-                inline .nav_exe, .nav_obj => |i| {
-                    const zcu = wasm.base.comp.zcu.?;
-                    const ip = &zcu.intern_pool;
-                    const nav = ip.getNav(i.key(wasm).*);
-                    return nav.status.resolved.@"linksection".toSlice(ip) orelse ".data";
-                },
-            };
-        }
+    pub fn isTls(id: DataId, wasm: *const Wasm) bool {
+        return switch (unpack(id, wasm)) {
+            .__zig_error_names, .__zig_error_name_table => false,
+            .object => |i| i.ptr(wasm).flags.tls,
+            .uav_exe, .uav_obj => false,
+            inline .nav_exe, .nav_obj => |i| {
+                const zcu = wasm.base.comp.zcu.?;
+                const ip = &zcu.intern_pool;
+                const nav = ip.getNav(i.key(wasm).*);
+                return nav.isThreadLocal(ip);
+            },
+        };
+    }
 
-        pub fn alignment(id: Id, wasm: *const Wasm) Alignment {
-            return switch (unpack(id, wasm)) {
-                .__zig_error_names => .@"1",
-                .__zig_error_name_table => wasm.pointerAlignment(),
-                .object => |i| i.ptr(wasm).flags.alignment,
-                inline .uav_exe, .uav_obj => |i| {
-                    const zcu = wasm.base.comp.zcu.?;
-                    const ip = &zcu.intern_pool;
-                    const ip_index = i.key(wasm).*;
-                    const ty: ZcuType = .fromInterned(ip.typeOf(ip_index));
-                    const result = ty.abiAlignment(zcu);
-                    assert(result != .none);
-                    return result;
-                },
-                inline .nav_exe, .nav_obj => |i| {
-                    const zcu = wasm.base.comp.zcu.?;
-                    const ip = &zcu.intern_pool;
-                    const nav = ip.getNav(i.key(wasm).*);
-                    const explicit = nav.status.resolved.alignment;
-                    if (explicit != .none) return explicit;
-                    const ty: ZcuType = .fromInterned(nav.typeOf(ip));
-                    const result = ty.abiAlignment(zcu);
-                    assert(result != .none);
-                    return result;
-                },
-            };
-        }
+    pub fn isBss(id: DataId, wasm: *const Wasm) bool {
+        return id.category(wasm) == .zero;
+    }
 
-        pub fn refCount(id: Id, wasm: *const Wasm) u32 {
-            return switch (unpack(id, wasm)) {
-                .__zig_error_names => @intCast(wasm.error_name_offs.items.len),
-                .__zig_error_name_table => wasm.error_name_table_ref_count,
-                .object, .uav_obj, .nav_obj => 0,
-                inline .uav_exe, .nav_exe => |i| i.value(wasm).count,
-            };
-        }
+    pub fn name(id: DataId, wasm: *const Wasm) []const u8 {
+        return switch (unpack(id, wasm)) {
+            .__zig_error_names, .__zig_error_name_table, .uav_exe, .uav_obj => ".data",
+            .object => |i| i.ptr(wasm).name.unwrap().?.slice(wasm),
+            inline .nav_exe, .nav_obj => |i| {
+                const zcu = wasm.base.comp.zcu.?;
+                const ip = &zcu.intern_pool;
+                const nav = ip.getNav(i.key(wasm).*);
+                return nav.status.resolved.@"linksection".toSlice(ip) orelse ".data";
+            },
+        };
+    }
 
-        pub fn isPassive(id: Id, wasm: *const Wasm) bool {
-            const comp = wasm.base.comp;
-            if (comp.config.import_memory and !id.isBss(wasm)) return true;
-            return switch (unpack(id, wasm)) {
-                .__zig_error_names, .__zig_error_name_table => false,
-                .object => |i| i.ptr(wasm).flags.is_passive,
-                .uav_exe, .uav_obj, .nav_exe, .nav_obj => false,
-            };
-        }
+    pub fn alignment(id: DataId, wasm: *const Wasm) Alignment {
+        return switch (unpack(id, wasm)) {
+            .__zig_error_names => .@"1",
+            .__zig_error_name_table => wasm.pointerAlignment(),
+            .object => |i| i.ptr(wasm).flags.alignment,
+            inline .uav_exe, .uav_obj => |i| {
+                const zcu = wasm.base.comp.zcu.?;
+                const ip = &zcu.intern_pool;
+                const ip_index = i.key(wasm).*;
+                const ty: ZcuType = .fromInterned(ip.typeOf(ip_index));
+                const result = ty.abiAlignment(zcu);
+                assert(result != .none);
+                return result;
+            },
+            inline .nav_exe, .nav_obj => |i| {
+                const zcu = wasm.base.comp.zcu.?;
+                const ip = &zcu.intern_pool;
+                const nav = ip.getNav(i.key(wasm).*);
+                const explicit = nav.status.resolved.alignment;
+                if (explicit != .none) return explicit;
+                const ty: ZcuType = .fromInterned(nav.typeOf(ip));
+                const result = ty.abiAlignment(zcu);
+                assert(result != .none);
+                return result;
+            },
+        };
+    }
 
-        pub fn isEmpty(id: Id, wasm: *const Wasm) bool {
-            return switch (unpack(id, wasm)) {
-                .__zig_error_names, .__zig_error_name_table => false,
-                .object => |i| i.ptr(wasm).payload.off == .none,
-                inline .uav_exe, .uav_obj, .nav_exe, .nav_obj => |i| i.value(wasm).code.off == .none,
-            };
-        }
+    pub fn refCount(id: DataId, wasm: *const Wasm) u32 {
+        return switch (unpack(id, wasm)) {
+            .__zig_error_names => @intCast(wasm.error_name_offs.items.len),
+            .__zig_error_name_table => wasm.error_name_table_ref_count,
+            .object, .uav_obj, .nav_obj => 0,
+            inline .uav_exe, .nav_exe => |i| i.value(wasm).count,
+        };
+    }
 
-        pub fn size(id: Id, wasm: *const Wasm) u32 {
-            return switch (unpack(id, wasm)) {
-                .__zig_error_names => @intCast(wasm.error_name_bytes.items.len),
-                .__zig_error_name_table => {
-                    const comp = wasm.base.comp;
-                    const zcu = comp.zcu.?;
-                    const errors_len = wasm.error_name_offs.items.len;
-                    const elem_size = ZcuType.slice_const_u8_sentinel_0.abiSize(zcu);
-                    return @intCast(errors_len * elem_size);
-                },
-                .object => |i| i.ptr(wasm).payload.len,
-                inline .uav_exe, .uav_obj, .nav_exe, .nav_obj => |i| i.value(wasm).code.len,
-            };
-        }
-    };
-};
+    pub fn isPassive(id: DataId, wasm: *const Wasm) bool {
+        const comp = wasm.base.comp;
+        if (comp.config.import_memory and !id.isBss(wasm)) return true;
+        return switch (unpack(id, wasm)) {
+            .__zig_error_names, .__zig_error_name_table => false,
+            .object => |i| i.ptr(wasm).flags.is_passive,
+            .uav_exe, .uav_obj, .nav_exe, .nav_obj => false,
+        };
+    }
 
-/// Index into `Wasm.object_data_segments`.
-pub const ObjectDataSegmentIndex = enum(u32) {
-    _,
+    pub fn isEmpty(id: DataId, wasm: *const Wasm) bool {
+        return switch (unpack(id, wasm)) {
+            .__zig_error_names, .__zig_error_name_table => false,
+            .object => |i| i.ptr(wasm).payload.off == .none,
+            inline .uav_exe, .uav_obj, .nav_exe, .nav_obj => |i| i.value(wasm).code.off == .none,
+        };
+    }
 
-    pub fn ptr(i: ObjectDataSegmentIndex, wasm: *const Wasm) *DataSegment {
-        return &wasm.object_data_segments.items[@intFromEnum(i)];
+    pub fn size(id: DataId, wasm: *const Wasm) u32 {
+        return switch (unpack(id, wasm)) {
+            .__zig_error_names => @intCast(wasm.error_name_bytes.items.len),
+            .__zig_error_name_table => {
+                const comp = wasm.base.comp;
+                const zcu = comp.zcu.?;
+                const errors_len = wasm.error_name_offs.items.len;
+                const elem_size = ZcuType.slice_const_u8_sentinel_0.abiSize(zcu);
+                return @intCast(errors_len * elem_size);
+            },
+            .object => |i| i.ptr(wasm).payload.len,
+            inline .uav_exe, .uav_obj, .nav_exe, .nav_obj => |i| i.value(wasm).code.len,
+        };
     }
 };
 
@@ -1565,7 +1626,7 @@ pub const CustomSegment = extern struct {
     flags: SymbolFlags,
     section_name: String,
 
-    pub const Payload = DataSegment.Payload;
+    pub const Payload = DataPayload;
 };
 
 /// An index into string_bytes where a wasm expression is found.
@@ -1591,9 +1652,13 @@ pub const FunctionType = extern struct {
     pub const Index = enum(u32) {
         _,
 
-        pub fn ptr(i: FunctionType.Index, wasm: *const Wasm) *FunctionType {
+        pub fn ptr(i: Index, wasm: *const Wasm) *FunctionType {
             return &wasm.func_types.keys()[@intFromEnum(i)];
         }
+
+        pub fn fmt(i: Index, wasm: *const Wasm) Formatter {
+            return i.ptr(wasm).fmt(wasm);
+        }
     };
 
     pub const format = @compileError("can't format without *Wasm reference");
@@ -1601,6 +1666,46 @@ pub const FunctionType = extern struct {
     pub fn eql(a: FunctionType, b: FunctionType) bool {
         return a.params == b.params and a.returns == b.returns;
     }
+
+    pub fn fmt(ft: FunctionType, wasm: *const Wasm) Formatter {
+        return .{ .wasm = wasm, .ft = ft };
+    }
+
+    const Formatter = struct {
+        wasm: *const Wasm,
+        ft: FunctionType,
+
+        pub fn format(
+            self: Formatter,
+            comptime format_string: []const u8,
+            options: std.fmt.FormatOptions,
+            writer: anytype,
+        ) !void {
+            if (format_string.len != 0) std.fmt.invalidFmtError(format_string, self);
+            _ = options;
+            const params = self.ft.params.slice(self.wasm);
+            const returns = self.ft.returns.slice(self.wasm);
+
+            try writer.writeByte('(');
+            for (params, 0..) |param, i| {
+                try writer.print("{s}", .{@tagName(param)});
+                if (i + 1 != params.len) {
+                    try writer.writeAll(", ");
+                }
+            }
+            try writer.writeAll(") -> ");
+            if (returns.len == 0) {
+                try writer.writeAll("nil");
+            } else {
+                for (returns, 0..) |return_ty, i| {
+                    try writer.print("{s}", .{@tagName(return_ty)});
+                    if (i + 1 != returns.len) {
+                        try writer.writeAll(", ");
+                    }
+                }
+            }
+        }
+    };
 };
 
 /// Represents a function entry, holding the index to its type
@@ -1955,7 +2060,7 @@ pub const ObjectRelocation = struct {
         symbol_name: String,
         type_index: FunctionType.Index,
         section: ObjectSectionIndex,
-        data_segment: ObjectDataSegmentIndex,
+        data: ObjectData.Index,
         function: Wasm.ObjectFunctionIndex,
     };
 
@@ -2096,6 +2201,8 @@ pub const Feature = packed struct(u8) {
     /// Type of the feature, must be unique in the sequence of features.
     tag: Tag,
 
+    pub const sentinel: Feature = @bitCast(@as(u8, 0));
+
     /// Stored identically to `String`. The bytes are reinterpreted as `Feature`
     /// elements. Elements must be sorted before string-interning.
     pub const Set = enum(u32) {
@@ -2104,6 +2211,14 @@ pub const Feature = packed struct(u8) {
         pub fn fromString(s: String) Set {
             return @enumFromInt(@intFromEnum(s));
         }
+
+        pub fn string(s: Set) String {
+            return @enumFromInt(@intFromEnum(s));
+        }
+
+        pub fn slice(s: Set, wasm: *const Wasm) [:sentinel]const Feature {
+            return @ptrCast(string(s).slice(wasm));
+        }
     };
 
     /// Unlike `std.Target.wasm.Feature` this also contains linker-features such as shared-mem.
@@ -2129,6 +2244,13 @@ pub const Feature = packed struct(u8) {
             return @enumFromInt(@intFromEnum(feature));
         }
 
+        pub fn toCpuFeature(tag: Tag) ?std.Target.wasm.Feature {
+            return if (@intFromEnum(tag) < @typeInfo(std.Target.wasm.Feature).@"enum".fields.len)
+                @enumFromInt(@intFromEnum(tag))
+            else
+                null;
+        }
+
         pub const format = @compileError("use @tagName instead");
     };
 
@@ -2136,15 +2258,14 @@ pub const Feature = packed struct(u8) {
     pub const Prefix = enum(u2) {
         /// Reserved so that a 0-byte Feature is invalid and therefore can be a sentinel.
         invalid,
-        /// '0x2b': Object uses this feature, and the link fails if feature is
-        /// not in the allowed set.
+        /// Object uses this feature, and the link fails if feature is not in
+        /// the allowed set.
         @"+",
-        /// '0x2d': Object does not use this feature, and the link fails if
-        /// this feature is in the allowed set.
+        /// Object does not use this feature, and the link fails if this
+        /// feature is in the allowed set.
         @"-",
-        /// '0x3d': Object uses this feature, and the link fails if this
-        /// feature is not in the allowed set, or if any object does not use
-        /// this feature.
+        /// Object uses this feature, and the link fails if this feature is not
+        /// in the allowed set, or if any object does not use this feature.
         @"=",
     };
 
@@ -2390,6 +2511,7 @@ pub fn deinit(wasm: *Wasm) void {
     wasm.object_memories.deinit(gpa);
     wasm.object_relocations.deinit(gpa);
     wasm.object_data_segments.deinit(gpa);
+    wasm.object_datas.deinit(gpa);
     wasm.object_custom_segments.deinit(gpa);
     wasm.object_init_funcs.deinit(gpa);
     wasm.object_comdats.deinit(gpa);
@@ -3412,7 +3534,7 @@ pub fn addExpr(wasm: *Wasm, bytes: []const u8) Allocator.Error!Expr {
     return @enumFromInt(wasm.string_bytes.items.len - bytes.len);
 }
 
-pub fn addRelocatableDataPayload(wasm: *Wasm, bytes: []const u8) Allocator.Error!DataSegment.Payload {
+pub fn addRelocatableDataPayload(wasm: *Wasm, bytes: []const u8) Allocator.Error!DataPayload {
     const gpa = wasm.base.comp.gpa;
     try wasm.string_bytes.appendSlice(gpa, bytes);
     return .{
@@ -3546,7 +3668,7 @@ pub fn uavAddr(wasm: *Wasm, uav_index: UavsExeIndex) u32 {
     assert(wasm.flush_buffer.memory_layout_finished);
     const comp = wasm.base.comp;
     assert(comp.config.output_mode != .Obj);
-    const ds_id: DataSegment.Id = .pack(wasm, .{ .uav_exe = uav_index });
+    const ds_id: DataId = .pack(wasm, .{ .uav_exe = uav_index });
     return wasm.flush_buffer.data_segments.get(ds_id).?;
 }
 
@@ -3557,7 +3679,7 @@ pub fn navAddr(wasm: *Wasm, nav_index: InternPool.Nav.Index) u32 {
     assert(comp.config.output_mode != .Obj);
     const navs_exe_index: NavsExeIndex = @enumFromInt(wasm.navs_exe.getIndex(nav_index).?);
     log.debug("navAddr {s} {}", .{ navs_exe_index.name(wasm), nav_index });
-    const ds_id: DataSegment.Id = .pack(wasm, .{ .nav_exe = navs_exe_index });
+    const ds_id: DataId = .pack(wasm, .{ .nav_exe = navs_exe_index });
     return wasm.flush_buffer.data_segments.get(ds_id).?;
 }
 
@@ -3646,14 +3768,14 @@ fn lowerZcuData(wasm: *Wasm, pt: Zcu.PerThread, ip_index: InternPool.Index) !Zcu
     const relocs_len: u32 = @intCast(wasm.out_relocs.len - relocs_start);
     wasm.string_bytes_lock.unlock();
 
-    const naive_code: DataSegment.Payload = .{
+    const naive_code: DataPayload = .{
         .off = @enumFromInt(code_start),
         .len = code_len,
     };
 
     // Only nonzero init values need to take up space in the output.
     const all_zeroes = std.mem.allEqual(u8, naive_code.slice(wasm), 0);
-    const code: DataSegment.Payload = if (!all_zeroes) naive_code else c: {
+    const code: DataPayload = if (!all_zeroes) naive_code else c: {
         wasm.string_bytes.shrinkRetainingCapacity(code_start);
         // Indicate empty by making off and len the same value, however, still
         // transmit the data size by using the size as that value.
src/link.zig
@@ -97,15 +97,12 @@ pub const Diags = struct {
             err_msg.msg = try std.fmt.allocPrint(gpa, format, args);
         }
 
-        pub fn addNote(
-            err: *ErrorWithNotes,
-            comptime format: []const u8,
-            args: anytype,
-        ) error{OutOfMemory}!void {
+        pub fn addNote(err: *ErrorWithNotes, comptime format: []const u8, args: anytype) void {
             const gpa = err.diags.gpa;
+            const msg = std.fmt.allocPrint(gpa, format, args) catch return err.diags.setAllocFailure();
             const err_msg = &err.diags.msgs.items[err.index];
             assert(err.note_slot < err_msg.notes.len);
-            err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) };
+            err_msg.notes[err.note_slot] = .{ .msg = msg };
             err.note_slot += 1;
         }
     };