Commit 943dac3e85

Andrew Kelley <andrew@ziglang.org>
2024-12-05 08:22:09
compiler: add type safety for export indices
1 parent 9bf715d
src/arch/wasm/CodeGen.zig
@@ -1,7 +1,6 @@
 const std = @import("std");
 const builtin = @import("builtin");
 const Allocator = std.mem.Allocator;
-const ArrayList = std.ArrayList;
 const assert = std.debug.assert;
 const testing = std.testing;
 const leb = std.leb;
@@ -631,7 +630,7 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
 /// Maps `loop` instructions to their label. `br` to here repeats the loop.
 loops: std.AutoHashMapUnmanaged(Air.Inst.Index, u32) = .empty,
 /// `bytes` contains the wasm bytecode belonging to the 'code' section.
-code: *ArrayList(u8),
+code: *std.ArrayListUnmanaged(u8),
 /// The index the next local generated will have
 /// NOTE: arguments share the index with locals therefore the first variable
 /// will have the index that comes after the last argument's index
@@ -639,8 +638,6 @@ local_index: u32 = 0,
 /// The index of the current argument.
 /// Used to track which argument is being referenced in `airArg`.
 arg_index: u32 = 0,
-/// If codegen fails, an error messages will be allocated and saved in `err_msg`
-err_msg: *Zcu.ErrorMsg,
 /// List of all locals' types generated throughout this declaration
 /// used to emit locals count at start of 'code' section.
 locals: std.ArrayListUnmanaged(u8),
@@ -732,10 +729,9 @@ pub fn deinit(func: *CodeGen) void {
     func.* = undefined;
 }
 
-/// Sets `err_msg` on `CodeGen` and returns `error.CodegenFail` which is caught in link/Wasm.zig
-fn fail(func: *CodeGen, comptime fmt: []const u8, args: anytype) InnerError {
-    func.err_msg = try Zcu.ErrorMsg.create(func.gpa, func.src_loc, fmt, args);
-    return error.CodegenFail;
+fn fail(func: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
+    const msg = try Zcu.ErrorMsg.create(func.gpa, func.src_loc, fmt, args);
+    return func.pt.zcu.codegenFailMsg(func.owner_nav, msg);
 }
 
 /// Resolves the `WValue` for the given instruction `inst`
@@ -1173,9 +1169,9 @@ pub fn generate(
     func_index: InternPool.Index,
     air: Air,
     liveness: Liveness,
-    code: *std.ArrayList(u8),
+    code: *std.ArrayListUnmanaged(u8),
     debug_output: link.File.DebugInfoOutput,
-) codegen.CodeGenError!codegen.Result {
+) codegen.CodeGenError!void {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const func = zcu.funcInfo(func_index);
@@ -1189,7 +1185,6 @@ pub fn generate(
         .code = code,
         .owner_nav = func.owner_nav,
         .src_loc = src_loc,
-        .err_msg = undefined,
         .locals = .{},
         .target = target,
         .bin_file = bin_file.cast(.wasm).?,
@@ -1199,11 +1194,9 @@ pub fn generate(
     defer code_gen.deinit();
 
     genFunc(&code_gen) catch |err| switch (err) {
-        error.CodegenFail => return codegen.Result{ .fail = code_gen.err_msg },
-        else => |e| return e,
+        error.CodegenFail => return error.CodegenFail,
+        else => |e| return code_gen.fail("failed to generate function: {s}", .{@errorName(e)}),
     };
-
-    return codegen.Result.ok;
 }
 
 fn genFunc(func: *CodeGen) InnerError!void {
src/arch/wasm/Mir.zig
@@ -80,15 +80,15 @@ pub const Inst = struct {
         ///
         /// Uses `nop`
         @"return" = 0x0F,
-        /// Calls a function using `nav_index`.
-        call_nav,
-        /// Calls a function using `func_index`.
-        call_func,
         /// Calls a function pointer by its function signature
         /// and index into the function table.
         ///
         /// Uses `label`
         call_indirect = 0x11,
+        /// Calls a function using `nav_index`.
+        call_nav,
+        /// Calls a function using `func_index`.
+        call_func,
         /// Calls a function by its index.
         ///
         /// The function is the auto-generated tag name function for the type
src/codegen/c.zig
@@ -3052,12 +3052,12 @@ pub fn genDeclValue(
     try w.writeAll(";\n");
 }
 
-pub fn genExports(dg: *DeclGen, exported: Zcu.Exported, export_indices: []const u32) !void {
+pub fn genExports(dg: *DeclGen, exported: Zcu.Exported, export_indices: []const Zcu.Export.Index) !void {
     const zcu = dg.pt.zcu;
     const ip = &zcu.intern_pool;
     const fwd = dg.fwdDeclWriter();
 
-    const main_name = zcu.all_exports.items[export_indices[0]].opts.name;
+    const main_name = export_indices[0].ptr(zcu).opts.name;
     try fwd.writeAll("#define ");
     switch (exported) {
         .nav => |nav| try dg.renderNavName(fwd, nav),
@@ -3069,7 +3069,7 @@ pub fn genExports(dg: *DeclGen, exported: Zcu.Exported, export_indices: []const
 
     const exported_val = exported.getValue(zcu);
     if (ip.isFunctionType(exported_val.typeOf(zcu).toIntern())) return for (export_indices) |export_index| {
-        const @"export" = &zcu.all_exports.items[export_index];
+        const @"export" = export_index.ptr(zcu);
         try fwd.writeAll("zig_extern ");
         if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage_fn ");
         try dg.renderFunctionSignature(
@@ -3091,7 +3091,7 @@ pub fn genExports(dg: *DeclGen, exported: Zcu.Exported, export_indices: []const
         else => true,
     };
     for (export_indices) |export_index| {
-        const @"export" = &zcu.all_exports.items[export_index];
+        const @"export" = export_index.ptr(zcu);
         try fwd.writeAll("zig_extern ");
         if (@"export".opts.linkage == .weak) try fwd.writeAll("zig_weak_linkage ");
         const extern_name = @"export".opts.name.toSlice(ip);
src/codegen/llvm.zig
@@ -1810,7 +1810,7 @@ pub const Object = struct {
         self: *Object,
         pt: Zcu.PerThread,
         exported: Zcu.Exported,
-        export_indices: []const u32,
+        export_indices: []const Zcu.Export.Index,
     ) link.File.UpdateExportsError!void {
         assert(std.meta.eql(pt, self.pt));
         const zcu = pt.zcu;
src/link/Elf/ZigObject.zig
@@ -1745,7 +1745,7 @@ pub fn updateExports(
     elf_file: *Elf,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) link.File.UpdateExportsError!void {
     const tracy = trace(@src());
     defer tracy.end();
src/link/MachO/ZigObject.zig
@@ -1246,7 +1246,7 @@ pub fn updateExports(
     macho_file: *MachO,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) link.File.UpdateExportsError!void {
     const tracy = trace(@src());
     defer tracy.end();
src/link/Wasm/Flush.zig
@@ -137,13 +137,12 @@ pub fn finish(f: *Flush, wasm: *Wasm, arena: Allocator) anyerror!void {
 
     // Merge and order the data segments. Depends on garbage collection so that
     // unused segments can be omitted.
-    try f.ensureUnusedCapacity(gpa, wasm.object_data_segments.items.len);
+    try f.data_segments.ensureUnusedCapacity(gpa, wasm.object_data_segments.items.len);
     for (wasm.object_data_segments.items, 0..) |*ds, i| {
         if (!ds.flags.alive) continue;
+        const data_segment_index: Wasm.DataSegment.Index = @enumFromInt(i);
         any_passive_inits = any_passive_inits or ds.flags.is_passive or (import_memory and !isBss(wasm, ds.name));
-        f.data_segments.putAssumeCapacityNoClobber(@intCast(i), .{
-            .offset = undefined,
-        });
+        f.data_segments.putAssumeCapacityNoClobber(data_segment_index, .{ .offset = undefined });
     }
 
     try wasm.functions.ensureUnusedCapacity(gpa, 3);
@@ -1082,8 +1081,8 @@ fn emitProducerSection(gpa: Allocator, binary_bytes: *std.ArrayListUnmanaged(u8)
 //    try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
 //}
 
-fn isBss(wasm: *Wasm, name: String) bool {
-    const s = name.slice(wasm);
+fn isBss(wasm: *Wasm, optional_name: Wasm.OptionalString) bool {
+    const s = optional_name.slice(wasm) orelse return false;
     return mem.eql(u8, s, ".bss") or mem.startsWith(u8, s, ".bss.");
 }
 
src/link/C.zig
@@ -840,7 +840,7 @@ pub fn updateExports(
     self: *C,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) !void {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
src/link/Elf.zig
@@ -2393,7 +2393,7 @@ pub fn updateExports(
     self: *Elf,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) link.File.UpdateExportsError!void {
     if (build_options.skip_non_native and builtin.object_format != .elf) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
src/link/MachO.zig
@@ -3056,7 +3056,7 @@ pub fn updateExports(
     self: *MachO,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) link.File.UpdateExportsError!void {
     if (build_options.skip_non_native and builtin.object_format != .macho) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
src/link/NvPtx.zig
@@ -100,7 +100,7 @@ pub fn updateExports(
     self: *NvPtx,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) !void {
     if (build_options.skip_non_native and builtin.object_format != .nvptx)
         @panic("Attempted to compile for object format that was disabled by build configuration");
src/link/Plan9.zig
@@ -60,7 +60,7 @@ fn_nav_table: std.AutoArrayHashMapUnmanaged(
 data_nav_table: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, []u8) = .empty,
 /// When `updateExports` is called, we store the export indices here, to be used
 /// during flush.
-nav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, []u32) = .empty,
+nav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, []Zcu.Export.Index) = .empty,
 
 lazy_syms: LazySymbolTable = .{},
 
@@ -1007,7 +1007,7 @@ pub fn updateExports(
     self: *Plan9,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) !void {
     const gpa = self.base.comp.gpa;
     switch (exported) {
@@ -1018,7 +1018,7 @@ pub fn updateExports(
                 gpa.free(kv.value);
             }
             try self.nav_exports.ensureUnusedCapacity(gpa, 1);
-            const duped_indices = try gpa.dupe(u32, export_indices);
+            const duped_indices = try gpa.dupe(Zcu.Export.Index, export_indices);
             self.nav_exports.putAssumeCapacityNoClobber(nav, duped_indices);
         },
     }
src/link/SpirV.zig
@@ -155,7 +155,7 @@ pub fn updateExports(
     self: *SpirV,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) !void {
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
@@ -190,7 +190,7 @@ pub fn updateExports(
         };
 
         for (export_indices) |export_idx| {
-            const exp = zcu.all_exports.items[export_idx];
+            const exp = export_idx.ptr(zcu);
             try self.object.spv.declareEntryPoint(
                 spv_decl_index,
                 exp.opts.name.toSlice(ip),
src/link/Wasm.zig
@@ -56,6 +56,10 @@ base: link.File,
 /// string_table entries for them. Alternately those sites could be moved to
 /// use a different byte array for this purpose.
 string_bytes: std.ArrayListUnmanaged(u8),
+/// Sometimes we have logic that wants to borrow string bytes to store
+/// arbitrary things in there. In this case it is not allowed to intern new
+/// strings during this time. This safety lock is used to detect misuses.
+string_bytes_lock: std.debug.SafetyLock = .{},
 /// Omitted when serializing linker state.
 string_table: String.Table,
 /// Symbol name of the entry function to export
@@ -202,6 +206,10 @@ any_exports_updated: bool = true,
 /// Index into `objects`.
 pub const ObjectIndex = enum(u32) {
     _,
+
+    pub fn ptr(index: ObjectIndex, wasm: *const Wasm) *Object {
+        return &wasm.objects.items[@intFromEnum(index)];
+    }
 };
 
 /// Index into `functions`.
@@ -269,12 +277,26 @@ pub const SourceLocation = enum(u32) {
         };
     }
 
+    pub fn unpack(sl: SourceLocation, wasm: *const Wasm) Unpacked {
+        return switch (sl) {
+            .zig_object_nofile => .zig_object_nofile,
+            .none => .none,
+            _ => {
+                const i = @intFromEnum(sl);
+                if (i < wasm.objects.items.len) return .{ .object_index = @enumFromInt(i) };
+                const sl_index = i - wasm.objects.items.len;
+                _ = sl_index;
+                @panic("TODO");
+            },
+        };
+    }
+
     pub fn addError(sl: SourceLocation, wasm: *Wasm, comptime f: []const u8, args: anytype) void {
         const diags = &wasm.base.comp.link_diags;
         switch (sl.unpack(wasm)) {
             .none => unreachable,
             .zig_object_nofile => diags.addError("zig compilation unit: " ++ f, args),
-            .object_index => |i| diags.addError("{}: " ++ f, .{wasm.objects.items[i].path} ++ args),
+            .object_index => |i| diags.addError("{}: " ++ f, .{i.ptr(wasm).path} ++ args),
             .source_location_index => @panic("TODO"),
         }
     }
@@ -520,6 +542,10 @@ pub const FunctionImport = extern struct {
     /// Index into `object_function_imports`.
     pub const Index = enum(u32) {
         _,
+
+        pub fn ptr(index: FunctionImport.Index, wasm: *const Wasm) *FunctionImport {
+            return &wasm.object_function_imports.items[@intFromEnum(index)];
+        }
     };
 };
 
@@ -543,7 +569,8 @@ pub const GlobalImport = extern struct {
     source_location: SourceLocation,
     resolution: Resolution,
 
-    /// Represents a synthetic global, or a global from an object.
+    /// Represents a synthetic global, a global from an object, or a global
+    /// from the Zcu.
     pub const Resolution = enum(u32) {
         unresolved,
         __heap_base,
@@ -556,6 +583,68 @@ pub const GlobalImport = extern struct {
         // Next, index into `object_globals`.
         // Next, index into `navs`.
         _,
+
+        const first_object_global = @intFromEnum(Resolution.__zig_error_name_table) + 1;
+
+        pub const Unpacked = union(enum) {
+            unresolved,
+            __heap_base,
+            __heap_end,
+            __stack_pointer,
+            __tls_align,
+            __tls_base,
+            __tls_size,
+            __zig_error_name_table,
+            object_global: ObjectGlobalIndex,
+            nav: Nav.Index,
+        };
+
+        pub fn unpack(r: Resolution, wasm: *const Wasm) Unpacked {
+            return switch (r) {
+                .unresolved => .unresolved,
+                .__wasm_apply_global_tls_relocs => .__wasm_apply_global_tls_relocs,
+                .__wasm_call_ctors => .__wasm_call_ctors,
+                .__wasm_init_memory => .__wasm_init_memory,
+                .__wasm_init_tls => .__wasm_init_tls,
+                .__zig_error_names => .__zig_error_names,
+                _ => {
+                    const i: u32 = @intFromEnum(r);
+                    const object_global_index = i - first_object_global;
+                    if (object_global_index < wasm.object_globals.items.len)
+                        return .{ .object_global = @enumFromInt(object_global_index) };
+                    const nav_index = object_global_index - wasm.object_globals.items.len;
+                    return .{ .nav = @enumFromInt(nav_index) };
+                },
+            };
+        }
+
+        pub fn pack(wasm: *const Wasm, unpacked: Unpacked) Resolution {
+            return switch (unpacked) {
+                .unresolved => .unresolved,
+                .__heap_base => .__heap_base,
+                .__heap_end => .__heap_end,
+                .__stack_pointer => .__stack_pointer,
+                .__tls_align => .__tls_align,
+                .__tls_base => .__tls_base,
+                .__tls_size => .__tls_size,
+                .__zig_error_name_table => .__zig_error_name_table,
+                .object_global => |i| @enumFromInt(first_object_global + @intFromEnum(i)),
+                .nav => |i| @enumFromInt(first_object_global + wasm.object_globals.items.len + @intFromEnum(i)),
+            };
+        }
+
+        pub fn fromIpNav(wasm: *const Wasm, ip_nav: InternPool.Nav.Index) Resolution {
+            return pack(wasm, .{ .nav = @enumFromInt(wasm.navs.getIndex(ip_nav).?) });
+        }
+    };
+
+    /// Index into `object_global_imports`.
+    pub const Index = enum(u32) {
+        _,
+
+        pub fn ptr(index: Index, wasm: *const Wasm) *GlobalImport {
+            return &wasm.object_global_imports.items[@intFromEnum(index)];
+        }
     };
 };
 
@@ -634,20 +723,6 @@ pub const ObjectSectionIndex = enum(u32) {
     _,
 };
 
-/// Index into `object_function_imports`.
-pub const ObjectFunctionImportIndex = enum(u32) {
-    _,
-
-    pub fn ptr(index: ObjectFunctionImportIndex, wasm: *const Wasm) *FunctionImport {
-        return &wasm.object_function_imports.items[@intFromEnum(index)];
-    }
-};
-
-/// Index into `object_global_imports`.
-pub const ObjectGlobalImportIndex = enum(u32) {
-    _,
-};
-
 /// Index into `object_table_imports`.
 pub const ObjectTableImportIndex = enum(u32) {
     _,
@@ -861,11 +936,35 @@ pub const ValtypeList = enum(u32) {
     }
 };
 
+/// Index into `imports`.
+pub const ZcuImportIndex = enum(u32) {
+    _,
+};
+
 /// 0. Index into `object_function_imports`.
 /// 1. Index into `imports`.
 pub const FunctionImportId = enum(u32) {
     _,
 
+    pub const Unpacked = union(enum) {
+        object_function_import: FunctionImport.Index,
+        zcu_import: ZcuImportIndex,
+    };
+
+    pub fn pack(unpacked: Unpacked, wasm: *const Wasm) FunctionImportId {
+        return switch (unpacked) {
+            .object_function_import => |i| @enumFromInt(@intFromEnum(i)),
+            .zcu_import => |i| @enumFromInt(@intFromEnum(i) - wasm.object_function_imports.entries.len),
+        };
+    }
+
+    pub fn unpack(id: FunctionImportId, wasm: *const Wasm) Unpacked {
+        const i = @intFromEnum(id);
+        if (i < wasm.object_function_imports.entries.len) return .{ .object_function_import = @enumFromInt(i) };
+        const zcu_import_i = i - wasm.object_function_imports.entries.len;
+        return .{ .zcu_import = @enumFromInt(zcu_import_i) };
+    }
+
     /// This function is allowed O(N) lookup because it is only called during
     /// diagnostic generation.
     pub fn sourceLocation(id: FunctionImportId, wasm: *const Wasm) SourceLocation {
@@ -873,10 +972,10 @@ pub const FunctionImportId = enum(u32) {
             .object_function_import => |obj_func_index| {
                 // TODO binary search
                 for (wasm.objects.items, 0..) |o, i| {
-                    if (o.function_imports.off <= obj_func_index and
-                        o.function_imports.off + o.function_imports.len > obj_func_index)
+                    if (o.function_imports.off <= @intFromEnum(obj_func_index) and
+                        o.function_imports.off + o.function_imports.len > @intFromEnum(obj_func_index))
                     {
-                        return .pack(wasm, .{ .object_index = @enumFromInt(i) });
+                        return .pack(.{ .object_index = @enumFromInt(i) }, wasm);
                     }
                 } else unreachable;
             },
@@ -890,17 +989,36 @@ pub const FunctionImportId = enum(u32) {
 pub const GlobalImportId = enum(u32) {
     _,
 
+    pub const Unpacked = union(enum) {
+        object_global_import: GlobalImport.Index,
+        zcu_import: ZcuImportIndex,
+    };
+
+    pub fn pack(unpacked: Unpacked, wasm: *const Wasm) GlobalImportId {
+        return switch (unpacked) {
+            .object_global_import => |i| @enumFromInt(@intFromEnum(i)),
+            .zcu_import => |i| @enumFromInt(@intFromEnum(i) - wasm.object_global_imports.entries.len),
+        };
+    }
+
+    pub fn unpack(id: GlobalImportId, wasm: *const Wasm) Unpacked {
+        const i = @intFromEnum(id);
+        if (i < wasm.object_global_imports.entries.len) return .{ .object_global_import = @enumFromInt(i) };
+        const zcu_import_i = i - wasm.object_global_imports.entries.len;
+        return .{ .zcu_import = @enumFromInt(zcu_import_i) };
+    }
+
     /// This function is allowed O(N) lookup because it is only called during
     /// diagnostic generation.
     pub fn sourceLocation(id: GlobalImportId, wasm: *const Wasm) SourceLocation {
         switch (id.unpack(wasm)) {
-            .object_global_import => |obj_func_index| {
+            .object_global_import => |obj_global_index| {
                 // TODO binary search
                 for (wasm.objects.items, 0..) |o, i| {
-                    if (o.global_imports.off <= obj_func_index and
-                        o.global_imports.off + o.global_imports.len > obj_func_index)
+                    if (o.global_imports.off <= @intFromEnum(obj_global_index) and
+                        o.global_imports.off + o.global_imports.len > @intFromEnum(obj_global_index))
                     {
-                        return .pack(wasm, .{ .object_index = @enumFromInt(i) });
+                        return .pack(.{ .object_index = @enumFromInt(i) }, wasm);
                     }
                 } else unreachable;
             },
@@ -1330,23 +1448,13 @@ pub fn deinit(wasm: *Wasm) void {
     wasm.object_memories.deinit(gpa);
 
     wasm.object_data_segments.deinit(gpa);
-    wasm.object_relocatable_codes.deinit(gpa);
     wasm.object_custom_segments.deinit(gpa);
-    wasm.object_symbols.deinit(gpa);
-    wasm.object_named_segments.deinit(gpa);
     wasm.object_init_funcs.deinit(gpa);
     wasm.object_comdats.deinit(gpa);
-    wasm.object_relocations.deinit(gpa);
     wasm.object_relocations_table.deinit(gpa);
     wasm.object_comdat_symbols.deinit(gpa);
     wasm.objects.deinit(gpa);
 
-    wasm.synthetic_symbols.deinit(gpa);
-    wasm.undefs.deinit(gpa);
-    wasm.discarded.deinit(gpa);
-    wasm.segments.deinit(gpa);
-    wasm.segment_info.deinit(gpa);
-
     wasm.func_types.deinit(gpa);
     wasm.function_exports.deinit(gpa);
     wasm.function_imports.deinit(gpa);
@@ -1354,8 +1462,6 @@ pub fn deinit(wasm: *Wasm) void {
     wasm.globals.deinit(gpa);
     wasm.global_imports.deinit(gpa);
     wasm.table_imports.deinit(gpa);
-    wasm.output_globals.deinit(gpa);
-    wasm.exports.deinit(gpa);
 
     wasm.string_bytes.deinit(gpa);
     wasm.string_table.deinit(gpa);
@@ -1374,12 +1480,11 @@ pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index,
     const nav_index = func.owner_nav;
 
     const code_start: u32 = @intCast(wasm.string_bytes.items.len);
-    const relocs_start: u32 = @intCast(wasm.relocations.items.len);
+    const relocs_start: u32 = @intCast(wasm.relocations.len);
     wasm.string_bytes_lock.lock();
 
-    const wasm_codegen = @import("../../arch/wasm/CodeGen.zig");
     dev.check(.wasm_backend);
-    const result = try wasm_codegen.generate(
+    try CodeGen.generate(
         &wasm.base,
         pt,
         zcu.navSrcLoc(nav_index),
@@ -1391,18 +1496,12 @@ pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index,
     );
 
     const code_len: u32 = @intCast(wasm.string_bytes.items.len - code_start);
-    const relocs_len: u32 = @intCast(wasm.relocations.items.len - relocs_start);
+    const relocs_len: u32 = @intCast(wasm.relocations.len - relocs_start);
     wasm.string_bytes_lock.unlock();
 
-    const code: Nav.Code = switch (result) {
-        .ok => .{
-            .off = code_start,
-            .len = code_len,
-        },
-        .fail => |em| {
-            try pt.zcu.failed_codegen.put(gpa, nav_index, em);
-            return;
-        },
+    const code: Nav.Code = .{
+        .off = code_start,
+        .len = code_len,
     };
 
     const gop = try wasm.navs.getOrPut(gpa, nav_index);
@@ -1445,24 +1544,22 @@ pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index
 
     if (!nav_init.typeOf(zcu).hasRuntimeBits(zcu)) {
         _ = wasm.imports.swapRemove(nav_index);
-        if (wasm.navs.swapRemove(nav_index)) |old| {
-            _ = old;
+        if (wasm.navs.swapRemove(nav_index)) {
             @panic("TODO reclaim resources");
         }
         return;
     }
 
     if (is_extern) {
-        try wasm.imports.put(nav_index, {});
-        if (wasm.navs.swapRemove(nav_index)) |old| {
-            _ = old;
+        try wasm.imports.put(gpa, nav_index, {});
+        if (wasm.navs.swapRemove(nav_index)) {
             @panic("TODO reclaim resources");
         }
         return;
     }
 
     const code_start: u32 = @intCast(wasm.string_bytes.items.len);
-    const relocs_start: u32 = @intCast(wasm.relocations.items.len);
+    const relocs_start: u32 = @intCast(wasm.relocations.len);
     wasm.string_bytes_lock.lock();
 
     const res = try codegen.generateSymbol(
@@ -1475,7 +1572,7 @@ pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index
     );
 
     const code_len: u32 = @intCast(wasm.string_bytes.items.len - code_start);
-    const relocs_len: u32 = @intCast(wasm.relocations.items.len - relocs_start);
+    const relocs_len: u32 = @intCast(wasm.relocations.len - relocs_start);
     wasm.string_bytes_lock.unlock();
 
     const code: Nav.Code = switch (res) {
@@ -1531,7 +1628,7 @@ pub fn updateExports(
     wasm: *Wasm,
     pt: Zcu.PerThread,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) !void {
     if (build_options.skip_non_native and builtin.object_format != .wasm) {
         @panic("Attempted to compile for object format that was disabled by build configuration");
@@ -1668,7 +1765,7 @@ fn markFunction(
     wasm: *Wasm,
     name: String,
     import: *FunctionImport,
-    func_index: ObjectFunctionImportIndex,
+    func_index: FunctionImport.Index,
 ) error{OutOfMemory}!void {
     if (import.flags.alive) return;
     import.flags.alive = true;
@@ -1712,7 +1809,7 @@ fn markGlobal(
     wasm: *Wasm,
     name: String,
     import: *GlobalImport,
-    global_index: ObjectGlobalImportIndex,
+    global_index: GlobalImport.Index,
 ) !void {
     if (import.flags.alive) return;
     import.flags.alive = true;
src/Zcu/PerThread.zig
@@ -2815,8 +2815,8 @@ pub fn processExports(pt: Zcu.PerThread) !void {
     const gpa = zcu.gpa;
 
     // First, construct a mapping of every exported value and Nav to the indices of all its different exports.
-    var nav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, std.ArrayListUnmanaged(u32)) = .empty;
-    var uav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, std.ArrayListUnmanaged(u32)) = .empty;
+    var nav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, std.ArrayListUnmanaged(Zcu.Export.Index)) = .empty;
+    var uav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, std.ArrayListUnmanaged(Zcu.Export.Index)) = .empty;
     defer {
         for (nav_exports.values()) |*exports| {
             exports.deinit(gpa);
@@ -2835,7 +2835,7 @@ pub fn processExports(pt: Zcu.PerThread) !void {
     try nav_exports.ensureTotalCapacity(gpa, zcu.single_exports.count() + zcu.multi_exports.count());
 
     for (zcu.single_exports.values()) |export_idx| {
-        const exp = zcu.all_exports.items[export_idx];
+        const exp = export_idx.ptr(zcu);
         const value_ptr, const found_existing = switch (exp.exported) {
             .nav => |nav| gop: {
                 const gop = try nav_exports.getOrPut(gpa, nav);
@@ -2863,7 +2863,7 @@ pub fn processExports(pt: Zcu.PerThread) !void {
                 },
             };
             if (!found_existing) value_ptr.* = .{};
-            try value_ptr.append(gpa, @intCast(export_idx));
+            try value_ptr.append(gpa, @enumFromInt(export_idx));
         }
     }
 
@@ -2882,20 +2882,20 @@ pub fn processExports(pt: Zcu.PerThread) !void {
     }
 }
 
-const SymbolExports = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, u32);
+const SymbolExports = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, Zcu.Export.Index);
 
 fn processExportsInner(
     pt: Zcu.PerThread,
     symbol_exports: *SymbolExports,
     exported: Zcu.Exported,
-    export_indices: []const u32,
+    export_indices: []const Zcu.Export.Index,
 ) error{OutOfMemory}!void {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
 
     for (export_indices) |export_idx| {
-        const new_export = &zcu.all_exports.items[export_idx];
+        const new_export = export_idx.ptr(zcu);
         const gop = try symbol_exports.getOrPut(gpa, new_export.opts.name);
         if (gop.found_existing) {
             new_export.status = .failed_retryable;
@@ -2904,7 +2904,7 @@ fn processExportsInner(
                 new_export.opts.name.fmt(ip),
             });
             errdefer msg.destroy(gpa);
-            const other_export = zcu.all_exports.items[gop.value_ptr.*];
+            const other_export = gop.value_ptr.ptr(zcu);
             try zcu.errNote(other_export.src, msg, "other symbol here", .{});
             zcu.failed_exports.putAssumeCapacityNoClobber(export_idx, msg);
             new_export.status = .failed;
src/link.zig
@@ -817,7 +817,7 @@ pub const File = struct {
         base: *File,
         pt: Zcu.PerThread,
         exported: Zcu.Exported,
-        export_indices: []const u32,
+        export_indices: []const Zcu.Export.Index,
     ) UpdateExportsError!void {
         switch (base.tag) {
             inline else => |tag| {
src/Sema.zig
@@ -38298,7 +38298,7 @@ pub fn flushExports(sema: *Sema) !void {
     // So, pick up and delete any existing exports. This strategy performs
     // redundant work, but that's okay, because this case is exceedingly rare.
     if (zcu.single_exports.get(sema.owner)) |export_idx| {
-        try sema.exports.append(gpa, zcu.all_exports.items[export_idx]);
+        try sema.exports.append(gpa, export_idx.ptr(zcu).*);
     } else if (zcu.multi_exports.get(sema.owner)) |info| {
         try sema.exports.appendSlice(gpa, zcu.all_exports.items[info.index..][0..info.len]);
     }
@@ -38307,12 +38307,12 @@ pub fn flushExports(sema: *Sema) !void {
     // `sema.exports` is completed; store the data into the `Zcu`.
     if (sema.exports.items.len == 1) {
         try zcu.single_exports.ensureUnusedCapacity(gpa, 1);
-        const export_idx = zcu.free_exports.popOrNull() orelse idx: {
+        const export_idx: Zcu.Export.Index = zcu.free_exports.popOrNull() orelse idx: {
             _ = try zcu.all_exports.addOne(gpa);
-            break :idx zcu.all_exports.items.len - 1;
+            break :idx @enumFromInt(zcu.all_exports.items.len - 1);
         };
-        zcu.all_exports.items[export_idx] = sema.exports.items[0];
-        zcu.single_exports.putAssumeCapacityNoClobber(sema.owner, @intCast(export_idx));
+        export_idx.ptr(zcu).* = sema.exports.items[0];
+        zcu.single_exports.putAssumeCapacityNoClobber(sema.owner, export_idx);
     } else {
         try zcu.multi_exports.ensureUnusedCapacity(gpa, 1);
         const exports_base = zcu.all_exports.items.len;
src/Zcu.zig
@@ -79,11 +79,11 @@ local_zir_cache: Compilation.Directory,
 all_exports: std.ArrayListUnmanaged(Export) = .empty,
 /// This is a list of free indices in `all_exports`. These indices may be reused by exports from
 /// future semantic analysis.
-free_exports: std.ArrayListUnmanaged(u32) = .empty,
+free_exports: std.ArrayListUnmanaged(Export.Index) = .empty,
 /// Maps from an `AnalUnit` which performs a single export, to the index into `all_exports` of
 /// the export it performs. Note that the key is not the `Decl` being exported, but the `AnalUnit`
 /// whose analysis triggered the export.
-single_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,
+single_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, Export.Index) = .empty,
 /// Like `single_exports`, but for `AnalUnit`s which perform multiple exports.
 /// The exports are `all_exports.items[index..][0..len]`.
 multi_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {
@@ -145,8 +145,7 @@ compile_log_sources: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {
 failed_files: std.AutoArrayHashMapUnmanaged(*File, ?*ErrorMsg) = .empty,
 /// The ErrorMsg memory is owned by the `EmbedFile`, using Module's general purpose allocator.
 failed_embed_files: std.AutoArrayHashMapUnmanaged(*EmbedFile, *ErrorMsg) = .empty,
-/// Key is index into `all_exports`.
-failed_exports: std.AutoArrayHashMapUnmanaged(u32, *ErrorMsg) = .empty,
+failed_exports: std.AutoArrayHashMapUnmanaged(Export.Index, *ErrorMsg) = .empty,
 /// If analysis failed due to a cimport error, the corresponding Clang errors
 /// are stored here.
 cimport_errors: std.AutoArrayHashMapUnmanaged(AnalUnit, std.zig.ErrorBundle) = .empty,
@@ -3101,7 +3100,7 @@ pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void {
     const gpa = zcu.gpa;
 
     const exports_base, const exports_len = if (zcu.single_exports.fetchSwapRemove(anal_unit)) |kv|
-        .{ kv.value, 1 }
+        .{ @intFromEnum(kv.value), 1 }
     else if (zcu.multi_exports.fetchSwapRemove(anal_unit)) |info|
         .{ info.value.index, info.value.len }
     else
@@ -3115,11 +3114,12 @@ pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void {
     // This case is needed because in some rare edge cases, `Sema` wants to add and delete exports
     // within a single update.
     if (dev.env.supports(.incremental)) {
-        for (exports, exports_base..) |exp, export_idx| {
+        for (exports, exports_base..) |exp, export_index_usize| {
+            const export_idx: Export.Index = @enumFromInt(export_index_usize);
             if (zcu.comp.bin_file) |lf| {
                 lf.deleteExport(exp.exported, exp.opts.name);
             }
-            if (zcu.failed_exports.fetchSwapRemove(@intCast(export_idx))) |failed_kv| {
+            if (zcu.failed_exports.fetchSwapRemove(export_idx)) |failed_kv| {
                 failed_kv.value.destroy(gpa);
             }
         }
@@ -3131,7 +3131,7 @@ pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void {
         return;
     };
     for (exports_base..exports_base + exports_len) |export_idx| {
-        zcu.free_exports.appendAssumeCapacity(@intCast(export_idx));
+        zcu.free_exports.appendAssumeCapacity(@enumFromInt(export_idx));
     }
 }
 
@@ -3277,7 +3277,7 @@ fn lockAndClearFileCompileError(zcu: *Zcu, file: *File) void {
 
 pub fn handleUpdateExports(
     zcu: *Zcu,
-    export_indices: []const u32,
+    export_indices: []const Export.Index,
     result: link.File.UpdateExportsError!void,
 ) Allocator.Error!void {
     const gpa = zcu.gpa;
@@ -3285,12 +3285,10 @@ pub fn handleUpdateExports(
         error.OutOfMemory => return error.OutOfMemory,
         error.AnalysisFail => {
             const export_idx = export_indices[0];
-            const new_export = &zcu.all_exports.items[export_idx];
+            const new_export = export_idx.ptr(zcu);
             new_export.status = .failed_retryable;
             try zcu.failed_exports.ensureUnusedCapacity(gpa, 1);
-            const msg = try ErrorMsg.create(gpa, new_export.src, "unable to export: {s}", .{
-                @errorName(err),
-            });
+            const msg = try ErrorMsg.create(gpa, new_export.src, "unable to export: {s}", .{@errorName(err)});
             zcu.failed_exports.putAssumeCapacityNoClobber(export_idx, msg);
         },
     };