Commit b5261599d7

Andrew Kelley <andrew@ziglang.org>
2025-01-12 07:12:43
wasm linker: implement `@tagName` functions when tags are autoassigned
1 parent d0d0847
Changed files (3)
src/link/Wasm/Flush.zig
@@ -109,6 +109,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
     const entry_name = if (wasm.entry_resolution.isNavOrUnresolved(wasm)) wasm.entry_name else .none;
 
     // Detect any intrinsics that were called; they need to have dependencies on the symbols marked.
+    // Likewise detect `@tagName` calls so those functions can be included in the output and synthesized.
     for (wasm.mir_instructions.items(.tag), wasm.mir_instructions.items(.data)) |tag, *data| switch (tag) {
         .call_intrinsic => {
             const symbol_name = try wasm.internString(@tagName(data.intrinsic));
@@ -119,6 +120,28 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
             });
             try wasm.markFunctionImport(symbol_name, i.value(wasm), i);
         },
+        .call_tag_name => {
+            const zcu = comp.zcu.?;
+            const ip = &zcu.intern_pool;
+            assert(ip.indexToKey(data.ip_index) == .enum_type);
+            const gop = try wasm.zcu_funcs.getOrPut(gpa, data.ip_index);
+            if (!gop.found_existing) {
+                wasm.tag_name_table_ref_count += 1;
+                const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu);
+                gop.value_ptr.* = .{ .tag_name = .{
+                    .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{@intFromEnum(data.ip_index)}),
+                    .type_index = try wasm.internFunctionType(.Unspecified, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, target),
+                    .table_index = @intCast(wasm.tag_name_offs.items.len),
+                } };
+                try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
+                const tag_names = ip.loadEnumType(data.ip_index).names;
+                for (tag_names.get(ip)) |tag_name| {
+                    const slice = tag_name.toSlice(ip);
+                    try wasm.tag_name_offs.append(gpa, @intCast(wasm.tag_name_bytes.items.len));
+                    try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]);
+                }
+            }
+        },
         else => continue,
     };
 
@@ -222,7 +245,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
     // unused segments can be omitted.
     try f.data_segments.ensureUnusedCapacity(gpa, wasm.data_segments.entries.len +
         wasm.uavs_obj.entries.len + wasm.navs_obj.entries.len +
-        wasm.uavs_exe.entries.len + wasm.navs_exe.entries.len + 2);
+        wasm.uavs_exe.entries.len + wasm.navs_exe.entries.len + 4);
     if (is_obj) assert(wasm.uavs_exe.entries.len == 0);
     if (is_obj) assert(wasm.navs_exe.entries.len == 0);
     if (!is_obj) assert(wasm.uavs_obj.entries.len == 0);
@@ -243,6 +266,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
         f.data_segments.putAssumeCapacity(.__zig_error_names, @as(u32, undefined));
         f.data_segments.putAssumeCapacity(.__zig_error_name_table, @as(u32, undefined));
     }
+    if (wasm.tag_name_table_ref_count > 0) {
+        f.data_segments.putAssumeCapacity(.__zig_tag_names, @as(u32, undefined));
+        f.data_segments.putAssumeCapacity(.__zig_tag_name_table, @as(u32, undefined));
+    }
     for (wasm.data_segments.keys()) |data_id| f.data_segments.putAssumeCapacity(data_id, @as(u32, undefined));
 
     try wasm.functions.ensureUnusedCapacity(gpa, 3);
@@ -751,7 +778,14 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
 
                 log.debug("lowering function code for '{s}'", .{resolution.name(wasm).?});
 
-                try i.value(wasm).function.lower(wasm, binary_bytes);
+                const zcu = comp.zcu.?;
+                const ip = &zcu.intern_pool;
+                switch (ip.indexToKey(i.key(wasm).*)) {
+                    .enum_type => {
+                        try emitTagNameFunction(gpa, binary_bytes, f.data_segments.get(.__zig_tag_name_table).?, i.value(wasm).tag_name.table_index);
+                    },
+                    else => try i.value(wasm).function.lower(wasm, binary_bytes),
+                }
             },
         };
 
@@ -849,9 +883,23 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
                         if (is_obj) @panic("TODO error name table reloc");
                         const base = f.data_segments.get(.__zig_error_names).?;
                         if (!is64) {
-                            try emitErrorNameTable(gpa, binary_bytes, wasm.error_name_offs.items, wasm.error_name_bytes.items, base, u32);
+                            try emitTagNameTable(gpa, binary_bytes, wasm.error_name_offs.items, wasm.error_name_bytes.items, base, u32);
                         } else {
-                            try emitErrorNameTable(gpa, binary_bytes, wasm.error_name_offs.items, wasm.error_name_bytes.items, base, u64);
+                            try emitTagNameTable(gpa, binary_bytes, wasm.error_name_offs.items, wasm.error_name_bytes.items, base, u64);
+                        }
+                        break :append;
+                    },
+                    .__zig_tag_names => {
+                        try binary_bytes.appendSlice(gpa, wasm.tag_name_bytes.items);
+                        break :append;
+                    },
+                    .__zig_tag_name_table => {
+                        if (is_obj) @panic("TODO tag name table reloc");
+                        const base = f.data_segments.get(.__zig_tag_names).?;
+                        if (!is64) {
+                            try emitTagNameTable(gpa, binary_bytes, wasm.tag_name_offs.items, wasm.tag_name_bytes.items, base, u32);
+                        } else {
+                            try emitTagNameTable(gpa, binary_bytes, wasm.tag_name_offs.items, wasm.tag_name_bytes.items, base, u64);
                         }
                         break :append;
                     },
@@ -1497,18 +1545,18 @@ fn uleb128size(x: u32) u32 {
     return size;
 }
 
-fn emitErrorNameTable(
+fn emitTagNameTable(
     gpa: Allocator,
     code: *std.ArrayListUnmanaged(u8),
-    error_name_offs: []const u32,
-    error_name_bytes: []const u8,
+    tag_name_offs: []const u32,
+    tag_name_bytes: []const u8,
     base: u32,
     comptime Int: type,
 ) error{OutOfMemory}!void {
     const ptr_size_bytes = @divExact(@bitSizeOf(Int), 8);
-    try code.ensureUnusedCapacity(gpa, ptr_size_bytes * 2 * error_name_offs.len);
-    for (error_name_offs) |off| {
-        const name_len: u32 = @intCast(mem.indexOfScalar(u8, error_name_bytes[off..], 0).?);
+    try code.ensureUnusedCapacity(gpa, ptr_size_bytes * 2 * tag_name_offs.len);
+    for (tag_name_offs) |off| {
+        const name_len: u32 = @intCast(mem.indexOfScalar(u8, tag_name_bytes[off..], 0).?);
         mem.writeInt(Int, code.addManyAsArrayAssumeCapacity(ptr_size_bytes), base + off, .little);
         mem.writeInt(Int, code.addManyAsArrayAssumeCapacity(ptr_size_bytes), name_len, .little);
     }
@@ -1849,6 +1897,42 @@ fn emitInitMemoryFunction(
     binary_bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
 }
 
+fn emitTagNameFunction(
+    gpa: Allocator,
+    code: *std.ArrayListUnmanaged(u8),
+    table_base_addr: u32,
+    table_index: u32,
+) Allocator.Error!void {
+    try code.ensureUnusedCapacity(gpa, 7 * 5 + 6 + 1 * 6);
+    appendReservedUleb32(code, 0); // no locals
+
+    const slice_abi_size = 8;
+    const encoded_alignment = @ctz(@as(u32, 4));
+    const all_tag_values_autoassigned = true;
+    if (all_tag_values_autoassigned) {
+        // Then it's a direct table lookup.
+        code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
+        appendReservedUleb32(code, 0);
+
+        code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
+        appendReservedUleb32(code, 1);
+
+        appendReservedI32Const(code, slice_abi_size);
+        code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_mul));
+
+        code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load));
+        appendReservedUleb32(code, encoded_alignment);
+        appendReservedUleb32(code, table_base_addr + table_index * 8);
+
+        code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_store));
+        appendReservedUleb32(code, encoded_alignment);
+        appendReservedUleb32(code, 0);
+    }
+
+    // End of the function body
+    code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
+}
+
 /// Writes an unsigned 32-bit integer as a LEB128-encoded 'i32.const' value.
 fn appendReservedI32Const(bytes: *std.ArrayListUnmanaged(u8), val: u32) void {
     bytes.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_const));
src/link/Wasm.zig
@@ -46,7 +46,6 @@ const lldMain = @import("../main.zig").lldMain;
 const trace = @import("../tracy.zig").trace;
 const wasi_libc = @import("../wasi_libc.zig");
 const Value = @import("../Value.zig");
-const ZcuType = @import("../Type.zig");
 
 base: link.File,
 /// Null-terminated strings, indexes have type String and string_table provides
@@ -190,6 +189,7 @@ navs_exe: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, ZcuDataExe) = .emp
 uavs_obj: std.AutoArrayHashMapUnmanaged(InternPool.Index, ZcuDataObj) = .empty,
 /// Tracks ref count to optimize LEB encodings for UAV references.
 uavs_exe: std.AutoArrayHashMapUnmanaged(InternPool.Index, ZcuDataExe) = .empty,
+/// When the key is an enum type, this represents a `@tagName` function.
 zcu_funcs: std.AutoArrayHashMapUnmanaged(InternPool.Index, ZcuFunc) = .empty,
 nav_exports: std.AutoArrayHashMapUnmanaged(NavExport, Zcu.Export.Index) = .empty,
 uav_exports: std.AutoArrayHashMapUnmanaged(UavExport, Zcu.Export.Index) = .empty,
@@ -269,6 +269,7 @@ object_indirect_function_import_set: std.AutoArrayHashMapUnmanaged(String, void)
 object_indirect_function_set: std.AutoArrayHashMapUnmanaged(ObjectFunctionIndex, void) = .empty,
 
 error_name_table_ref_count: u32 = 0,
+tag_name_table_ref_count: u32 = 0,
 
 /// Set to true if any `GLOBAL_INDEX` relocation is encountered with
 /// `SymbolFlags.tls` set to true. This is for objects only; final
@@ -294,6 +295,14 @@ error_name_bytes: std.ArrayListUnmanaged(u8) = .empty,
 /// is stored. No need to serialize; trivially reconstructed.
 error_name_offs: std.ArrayListUnmanaged(u32) = .empty,
 
+tag_name_bytes: std.ArrayListUnmanaged(u8) = .empty,
+tag_name_offs: std.ArrayListUnmanaged(u32) = .empty,
+
+pub const TagNameOff = extern struct {
+    off: u32,
+    len: u32,
+};
+
 /// Index into `Wasm.zcu_indirect_function_set`.
 pub const ZcuIndirectFunctionSetIndex = enum(u32) {
     _,
@@ -857,8 +866,16 @@ const ZcuDataStarts = struct {
     }
 };
 
-pub const ZcuFunc = extern struct {
+pub const ZcuFunc = union {
     function: CodeGen.Function,
+    tag_name: TagName,
+
+    pub const TagName = extern struct {
+        symbol_name: String,
+        type_index: FunctionType.Index,
+        /// Index into `Wasm.tag_name_offs`.
+        table_index: u32,
+    };
 
     /// Index into `Wasm.zcu_funcs`.
     /// Note that swapRemove is sometimes performed on `zcu_funcs`.
@@ -876,20 +893,35 @@ pub const ZcuFunc = extern struct {
         pub fn name(i: @This(), wasm: *const Wasm) [:0]const u8 {
             const zcu = wasm.base.comp.zcu.?;
             const ip = &zcu.intern_pool;
-            const func = ip.toFunc(i.key(wasm).*);
-            const nav = ip.getNav(func.owner_nav);
-            return nav.fqn.toSlice(ip);
+            const ip_index = i.key(wasm).*;
+            switch (ip.indexToKey(ip_index)) {
+                .func => |func| {
+                    const nav = ip.getNav(func.owner_nav);
+                    return nav.fqn.toSlice(ip);
+                },
+                .enum_type => {
+                    return i.value(wasm).tag_name.symbol_name.slice(wasm);
+                },
+                else => unreachable,
+            }
         }
 
-        pub fn typeIndex(i: @This(), wasm: *Wasm) ?FunctionType.Index {
+        pub fn typeIndex(i: @This(), wasm: *Wasm) FunctionType.Index {
             const comp = wasm.base.comp;
             const zcu = comp.zcu.?;
             const target = &comp.root_mod.resolved_target.result;
             const ip = &zcu.intern_pool;
-            const func = ip.toFunc(i.key(wasm).*);
-            const fn_ty = zcu.navValue(func.owner_nav).typeOf(zcu);
-            const fn_info = zcu.typeToFunc(fn_ty).?;
-            return wasm.getExistingFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target);
+            switch (ip.indexToKey(i.key(wasm).*)) {
+                .func => |func| {
+                    const fn_ty = zcu.navValue(func.owner_nav).typeOf(zcu);
+                    const fn_info = zcu.typeToFunc(fn_ty).?;
+                    return wasm.getExistingFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target).?;
+                },
+                .enum_type => {
+                    return i.value(wasm).tag_name.type_index;
+                },
+                else => unreachable,
+            }
         }
     };
 };
@@ -988,8 +1020,12 @@ pub const FunctionImport = extern struct {
             return fromIpIndex(wasm, ip.getNav(nav_index).status.fully_resolved.val);
         }
 
+        pub fn fromZcuFunc(wasm: *const Wasm, i: ZcuFunc.Index) Resolution {
+            return pack(wasm, .{ .zcu_func = i });
+        }
+
         pub fn fromIpIndex(wasm: *const Wasm, ip_index: InternPool.Index) Resolution {
-            return pack(wasm, .{ .zcu_func = @enumFromInt(wasm.zcu_funcs.getIndex(ip_index).?) });
+            return fromZcuFunc(wasm, @enumFromInt(wasm.zcu_funcs.getIndex(ip_index).?));
         }
 
         pub fn fromObjectFunction(wasm: *const Wasm, object_function: ObjectFunctionIndex) Resolution {
@@ -1012,7 +1048,7 @@ pub const FunctionImport = extern struct {
                 => getExistingFuncType2(wasm, &.{}, &.{}),
                 .__wasm_init_tls => getExistingFuncType2(wasm, &.{.i32}, &.{}),
                 .object_function => |i| i.ptr(wasm).type_index,
-                .zcu_func => |i| i.typeIndex(wasm).?,
+                .zcu_func => |i| i.typeIndex(wasm),
             };
         }
 
@@ -1717,6 +1753,10 @@ pub const DataPayload = extern struct {
 pub const DataSegmentId = enum(u32) {
     __zig_error_names,
     __zig_error_name_table,
+    /// All name string bytes for all `@tagName` implementations, concatenated together.
+    __zig_tag_names,
+    /// All tag name slices for all `@tagName` implementations, concatenated together.
+    __zig_tag_name_table,
     /// This and `__heap_end` are better retrieved via a global, but there is
     /// some suboptimal code out there (wasi libc) that additionally needs them
     /// as data symbols.
@@ -1742,6 +1782,8 @@ pub const DataSegmentId = enum(u32) {
     pub const Unpacked = union(enum) {
         __zig_error_names,
         __zig_error_name_table,
+        __zig_tag_names,
+        __zig_tag_name_table,
         __heap_base,
         __heap_end,
         object: ObjectDataSegment.Index,
@@ -1755,6 +1797,8 @@ pub const DataSegmentId = enum(u32) {
         return switch (unpacked) {
             .__zig_error_names => .__zig_error_names,
             .__zig_error_name_table => .__zig_error_name_table,
+            .__zig_tag_names => .__zig_tag_names,
+            .__zig_tag_name_table => .__zig_tag_name_table,
             .__heap_base => .__heap_base,
             .__heap_end => .__heap_end,
             .object => |i| @enumFromInt(first_object + @intFromEnum(i)),
@@ -1768,6 +1812,8 @@ pub const DataSegmentId = enum(u32) {
         return switch (id) {
             .__zig_error_names => .__zig_error_names,
             .__zig_error_name_table => .__zig_error_name_table,
+            .__zig_tag_names => .__zig_tag_names,
+            .__zig_tag_name_table => .__zig_tag_name_table,
             .__heap_base => .__heap_base,
             .__heap_end => .__heap_end,
             _ => {
@@ -1815,7 +1861,14 @@ pub const DataSegmentId = enum(u32) {
 
     pub fn category(id: DataSegmentId, wasm: *const Wasm) Category {
         return switch (unpack(id, wasm)) {
-            .__zig_error_names, .__zig_error_name_table, .__heap_base, .__heap_end => .data,
+            .__zig_error_names,
+            .__zig_error_name_table,
+            .__zig_tag_names,
+            .__zig_tag_name_table,
+            .__heap_base,
+            .__heap_end,
+            => .data,
+
             .object => |i| {
                 const ptr = i.ptr(wasm);
                 if (ptr.flags.tls) return .tls;
@@ -1836,7 +1889,14 @@ pub const DataSegmentId = enum(u32) {
 
     pub fn isTls(id: DataSegmentId, wasm: *const Wasm) bool {
         return switch (unpack(id, wasm)) {
-            .__zig_error_names, .__zig_error_name_table, .__heap_base, .__heap_end => false,
+            .__zig_error_names,
+            .__zig_error_name_table,
+            .__zig_tag_names,
+            .__zig_tag_name_table,
+            .__heap_base,
+            .__heap_end,
+            => false,
+
             .object => |i| i.ptr(wasm).flags.tls,
             .uav_exe, .uav_obj => false,
             inline .nav_exe, .nav_obj => |i| {
@@ -1854,7 +1914,16 @@ pub const DataSegmentId = enum(u32) {
 
     pub fn name(id: DataSegmentId, wasm: *const Wasm) []const u8 {
         return switch (unpack(id, wasm)) {
-            .__zig_error_names, .__zig_error_name_table, .uav_exe, .uav_obj, .__heap_base, .__heap_end => ".data",
+            .__zig_error_names,
+            .__zig_error_name_table,
+            .__zig_tag_names,
+            .__zig_tag_name_table,
+            .uav_exe,
+            .uav_obj,
+            .__heap_base,
+            .__heap_end,
+            => ".data",
+
             .object => |i| i.ptr(wasm).name.unwrap().?.slice(wasm),
             inline .nav_exe, .nav_obj => |i| {
                 const zcu = wasm.base.comp.zcu.?;
@@ -1867,14 +1936,14 @@ pub const DataSegmentId = enum(u32) {
 
     pub fn alignment(id: DataSegmentId, wasm: *const Wasm) Alignment {
         return switch (unpack(id, wasm)) {
-            .__zig_error_names => .@"1",
-            .__zig_error_name_table, .__heap_base, .__heap_end => wasm.pointerAlignment(),
+            .__zig_error_names, .__zig_tag_names => .@"1",
+            .__zig_error_name_table, .__zig_tag_name_table, .__heap_base, .__heap_end => 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 ty: Zcu.Type = .fromInterned(ip.typeOf(ip_index));
                 const result = ty.abiAlignment(zcu);
                 assert(result != .none);
                 return result;
@@ -1885,7 +1954,7 @@ pub const DataSegmentId = enum(u32) {
                 const nav = ip.getNav(i.key(wasm).*);
                 const explicit = nav.getAlignment();
                 if (explicit != .none) return explicit;
-                const ty: ZcuType = .fromInterned(nav.typeOf(ip));
+                const ty: Zcu.Type = .fromInterned(nav.typeOf(ip));
                 const result = ty.abiAlignment(zcu);
                 assert(result != .none);
                 return result;
@@ -1897,6 +1966,8 @@ pub const DataSegmentId = enum(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,
+            .__zig_tag_names => @intCast(wasm.tag_name_offs.items.len),
+            .__zig_tag_name_table => wasm.tag_name_table_ref_count,
             .object, .uav_obj, .nav_obj, .__heap_base, .__heap_end => 0,
             inline .uav_exe, .nav_exe => |i| i.value(wasm).count,
         };
@@ -1906,7 +1977,14 @@ pub const DataSegmentId = enum(u32) {
         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, .__heap_base, .__heap_end => false,
+            .__zig_error_names,
+            .__zig_error_name_table,
+            .__zig_tag_names,
+            .__zig_tag_name_table,
+            .__heap_base,
+            .__heap_end,
+            => false,
+
             .object => |i| i.ptr(wasm).flags.is_passive,
             .uav_exe, .uav_obj, .nav_exe, .nav_obj => false,
         };
@@ -1914,7 +1992,14 @@ pub const DataSegmentId = enum(u32) {
 
     pub fn isEmpty(id: DataSegmentId, wasm: *const Wasm) bool {
         return switch (unpack(id, wasm)) {
-            .__zig_error_names, .__zig_error_name_table, .__heap_base, .__heap_end => false,
+            .__zig_error_names,
+            .__zig_error_name_table,
+            .__zig_tag_names,
+            .__zig_tag_name_table,
+            .__heap_base,
+            .__heap_end,
+            => 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,
         };
@@ -1927,9 +2012,17 @@ pub const DataSegmentId = enum(u32) {
                 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);
+                const elem_size = Zcu.Type.slice_const_u8_sentinel_0.abiSize(zcu);
                 return @intCast(errors_len * elem_size);
             },
+            .__zig_tag_names => @intCast(wasm.tag_name_bytes.items.len),
+            .__zig_tag_name_table => {
+                const comp = wasm.base.comp;
+                const zcu = comp.zcu.?;
+                const table_len = wasm.tag_name_offs.items.len;
+                const elem_size = Zcu.Type.slice_const_u8_sentinel_0.abiSize(zcu);
+                return @intCast(table_len * elem_size);
+            },
             .__heap_base, .__heap_end => wasm.pointerSize(),
             .object => |i| i.ptr(wasm).payload.len,
             inline .uav_exe, .uav_obj, .nav_exe, .nav_obj => |i| i.value(wasm).code.len,
@@ -3052,6 +3145,8 @@ pub fn deinit(wasm: *Wasm) void {
 
     wasm.error_name_bytes.deinit(gpa);
     wasm.error_name_offs.deinit(gpa);
+    wasm.tag_name_bytes.deinit(gpa);
+    wasm.tag_name_offs.deinit(gpa);
 
     wasm.missing_exports.deinit(gpa);
 }
@@ -4197,7 +4292,7 @@ pub fn internFunctionType(
     wasm: *Wasm,
     cc: std.builtin.CallingConvention,
     params: []const InternPool.Index,
-    return_type: ZcuType,
+    return_type: Zcu.Type,
     target: *const std.Target,
 ) Allocator.Error!FunctionType.Index {
     try convertZcuFnType(wasm.base.comp, cc, params, return_type, target, &wasm.params_scratch, &wasm.returns_scratch);
@@ -4211,7 +4306,7 @@ pub fn getExistingFunctionType(
     wasm: *Wasm,
     cc: std.builtin.CallingConvention,
     params: []const InternPool.Index,
-    return_type: ZcuType,
+    return_type: Zcu.Type,
     target: *const std.Target,
 ) ?FunctionType.Index {
     convertZcuFnType(wasm.base.comp, cc, params, return_type, target, &wasm.params_scratch, &wasm.returns_scratch) catch |err| switch (err) {
@@ -4395,7 +4490,7 @@ fn convertZcuFnType(
     comp: *Compilation,
     cc: std.builtin.CallingConvention,
     params: []const InternPool.Index,
-    return_type: ZcuType,
+    return_type: Zcu.Type,
     target: *const std.Target,
     params_buffer: *std.ArrayListUnmanaged(std.wasm.Valtype),
     returns_buffer: *std.ArrayListUnmanaged(std.wasm.Valtype),
@@ -4423,7 +4518,7 @@ fn convertZcuFnType(
 
     // param types
     for (params) |param_type_ip| {
-        const param_type = ZcuType.fromInterned(param_type_ip);
+        const param_type = Zcu.Type.fromInterned(param_type_ip);
         if (!param_type.hasRuntimeBitsIgnoreComptime(zcu)) continue;
 
         switch (cc) {
src/Zcu.zig
@@ -19,8 +19,8 @@ const Ast = std.zig.Ast;
 const Zcu = @This();
 const Compilation = @import("Compilation.zig");
 const Cache = std.Build.Cache;
-const Value = @import("Value.zig");
-const Type = @import("Type.zig");
+pub const Value = @import("Value.zig");
+pub const Type = @import("Type.zig");
 const Package = @import("Package.zig");
 const link = @import("link.zig");
 const Air = @import("Air.zig");