diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index dbb9ab801e1f4f0b620a64cfbc138c043250e46b..a06d650b39dae829b44fa267b475099b9be359e3 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -45,20 +45,29 @@ host_name: []const u8 = "env", /// This is ment for bookkeeping so we can safely cleanup all codegen memory /// when calling `deinit` decls: std.AutoHashMapUnmanaged(*Module.Decl, void) = .{}, -/// List of all symbols. +/// List of all symbols generated by Zig code. symbols: std.ArrayListUnmanaged(Symbol) = .{}, /// List of symbol indexes which are free to be used. symbols_free_list: std.ArrayListUnmanaged(u32) = .{}, /// Maps atoms to their segment index atoms: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, +/// Atoms managed and created by the linker. This contains atoms +/// from object files, and not Atoms generated by a Decl. +managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, /// Represents the index into `segments` where the 'code' section /// lives. code_section_index: ?u32 = null, /// The count of imported functions. This number will be appended /// to the function indexes as their index starts at the lowest non-extern function. imported_functions_count: u32 = 0, -/// Map of symbol indexes, represented by its `wasm.Import` -imports: std.AutoHashMapUnmanaged(u32, wasm.Import) = .{}, +/// The count of imported wasm globals. This number will be appended +/// to the global indexes when sections are merged. +imported_globals_count: u32 = 0, +/// The count of imported tables. This number will be appended +/// to the table indexes when sections are merged. +imported_tables_count: u32 = 0, +/// Map of symbol locations, represented by its `wasm.Import` +imports: std.AutoHashMapUnmanaged(SymbolLoc, wasm.Import) = .{}, /// Represents non-synthetic section entries. /// Used for code, data and custom sections. segments: std.ArrayListUnmanaged(Segment) = .{}, @@ -77,6 +86,10 @@ functions: std.ArrayListUnmanaged(wasm.Func) = .{}, wasm_globals: std.ArrayListUnmanaged(wasm.Global) = .{}, /// Memory section memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } }, +/// Output table section +tables: std.ArrayListUnmanaged(wasm.Table) = .{}, +/// Output export section +exports: std.ArrayListUnmanaged(wasm.Export) = .{}, /// Indirect function table, used to call function pointers /// When this is non-zero, we must emit a table entry, @@ -87,14 +100,14 @@ function_table: std.AutoHashMapUnmanaged(u32, u32) = .{}, /// All object files and their data which are linked into the final binary objects: std.ArrayListUnmanaged(Object) = .{}, +/// A map of global names to their symbol location +globals: std.StringHashMapUnmanaged(SymbolLoc) = .{}, /// Maps discarded symbols and their positions to the location of the symbol /// it was resolved to discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{}, -/// Mapping between symbol names and their respective location. -/// This map contains all symbols that will be written into the final binary -/// and were either defined, or resolved. -/// TODO: Use string interning and make the key an index, rather than a unique string. -symbol_resolver: std.StringArrayHashMapUnmanaged(SymbolLoc) = .{}, +/// List of all symbol locations which have been resolved by the linker and will be emit +/// into the final binary. +resolved_symbols: std.ArrayListUnmanaged(SymbolLoc) = .{}, pub const Segment = struct { alignment: u32, @@ -116,6 +129,18 @@ pub const SymbolLoc = struct { /// The index of the object file where the symbol resides. /// When this is `null` the symbol comes from a non-object file. file: ?u16, + + /// From a given location, returns the corresponding symbol in the wasm binary + pub fn getSymbol(self: SymbolLoc, wasm_bin: *const Wasm) *Symbol { + if (self.file) |object_index| { + if (wasm_bin.discarded.get(self)) |old_loc| { + return old_loc.getSymbol(wasm_bin); + } + const object = wasm_bin.objects.items[object_index]; + return &object.symtable[self.index]; + } + return &wasm_bin.symbols.items[self.index]; + } }; pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Wasm { @@ -186,49 +211,118 @@ fn parseObjectFile(self: *Wasm, path: []const u8) !bool { const file = try fs.cwd().openFile(path, .{}); errdefer file.close(); - var object = Object.init(self.base.allocator, file, path) catch |err| { - if (err == error.InvalidMagicByte) { + var object = Object.create(self.base.allocator, file, path) catch |err| switch (err) { + error.InvalidMagicByte, error.NotObjectFile => { log.warn("Self hosted linker does not support non-object file parsing", .{}); return false; - } else return err; + }, + else => |e| return e, }; errdefer object.deinit(self.base.allocator); try self.objects.append(self.base.allocator, object); return true; } +fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { + const object: Object = self.objects.items[object_index]; + log.debug("Resolving symbols in object: '{s}'", .{object.name}); + + for (object.symtable) |symbol, i| { + const sym_index = @intCast(u32, i); + const location: SymbolLoc = .{ + .file = object_index, + .index = sym_index, + }; + + if (symbol.isLocal()) { + if (symbol.isUndefined()) { + log.err("Local symbols are not allowed to reference imports", .{}); + log.err(" symbol '{s}' defined in '{s}'", .{ symbol.name, object.name }); + return error.undefinedLocal; + } + try self.resolved_symbols.append(self.base.allocator, location); + continue; + } + + // TODO: locals are allowed to have duplicate symbol names + // TODO: Store undefined symbols so we can verify at the end if they've all been found + // if not, emit an error (unless --allow-undefined is enabled). + const maybe_existing = try self.globals.getOrPut(self.base.allocator, std.mem.sliceTo(symbol.name, 0)); + if (!maybe_existing.found_existing) { + maybe_existing.value_ptr.* = location; + + try self.globals.putNoClobber(self.base.allocator, std.mem.sliceTo(symbol.name, 0), location); + continue; + } + + const existing_loc = maybe_existing.value_ptr.*; + const existing_sym: *Symbol = existing_loc.getSymbol(self); + + if (!existing_sym.isUndefined()) { + if (!symbol.isUndefined()) { + log.err("symbol '{s}' defined multiple times", .{existing_sym.name}); + log.err(" first definition in '{s}'", .{self.objects.items[existing_loc.file.?].name}); + log.err(" next definition in '{s}'", .{object.name}); + return error.SymbolCollision; + } + + continue; // Do not overwrite defined symbols with undefined symbols + } + + // simply overwrite with the new symbol + log.info("Overwriting symbol '{s}'", .{symbol.name}); + log.info(" first definition in '{s}'", .{self.objects.items[existing_loc.file.?].name}); + log.info(" next definition in '{s}'", .{object.name}); + try self.discarded.putNoClobber(self.base.allocator, maybe_existing.value_ptr.*, location); + maybe_existing.value_ptr.* = location; + try self.globals.putNoClobber(self.base.allocator, std.mem.sliceTo(symbol.name, 0), location); + } +} + pub fn deinit(self: *Wasm) void { + const gpa = self.base.allocator; if (build_options.have_llvm) { - if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); + if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); } var decl_it = self.decls.keyIterator(); while (decl_it.next()) |decl_ptr| { const decl = decl_ptr.*; - decl.link.wasm.deinit(self.base.allocator); + decl.link.wasm.deinit(gpa); } for (self.func_types.items) |*func_type| { - func_type.deinit(self.base.allocator); + func_type.deinit(gpa); } for (self.segment_info.items) |segment_info| { - self.base.allocator.free(segment_info.name); + gpa.free(segment_info.name); + } + for (self.objects.items) |*object| { + object.file.?.close(); + object.deinit(gpa); } - self.decls.deinit(self.base.allocator); - self.symbols.deinit(self.base.allocator); - self.symbols_free_list.deinit(self.base.allocator); - self.atoms.deinit(self.base.allocator); - self.segments.deinit(self.base.allocator); - self.data_segments.deinit(self.base.allocator); - self.segment_info.deinit(self.base.allocator); + self.decls.deinit(gpa); + self.symbols.deinit(gpa); + self.symbols_free_list.deinit(gpa); + self.globals.deinit(gpa); + self.resolved_symbols.deinit(gpa); + self.discarded.deinit(gpa); + self.atoms.deinit(gpa); + self.managed_atoms.deinit(gpa); + self.segments.deinit(gpa); + self.data_segments.deinit(gpa); + self.segment_info.deinit(gpa); + self.objects.deinit(gpa); // free output sections - self.imports.deinit(self.base.allocator); - self.func_types.deinit(self.base.allocator); - self.functions.deinit(self.base.allocator); - self.wasm_globals.deinit(self.base.allocator); - self.function_table.deinit(self.base.allocator); + self.imports.deinit(gpa); + self.func_types.deinit(gpa); + self.functions.deinit(gpa); + self.wasm_globals.deinit(gpa); + self.function_table.deinit(gpa); + self.tables.deinit(gpa); + self.exports.deinit(gpa); } pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void { @@ -453,7 +547,7 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { } if (decl.isExtern()) { - const import = self.imports.fetchRemove(atom.sym_index).?.value; + const import = self.imports.fetchRemove(.{ .file = null, .index = atom.sym_index }).?.value; switch (import.kind) { .function => self.imported_functions_count -= 1, else => unreachable, @@ -469,6 +563,9 @@ pub fn addTableFunction(self: *Wasm, symbol_index: u32) !void { try self.function_table.put(self.base.allocator, symbol_index, index); } +/// Assigns indexes to all indirect functions. +/// Starts at offset 1, where the value `0` represents an unresolved function pointer +/// or null-pointer fn mapFunctionTable(self: *Wasm) void { var it = self.function_table.valueIterator(); var index: u32 = 1; @@ -484,7 +581,7 @@ fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void { symbol.setUndefined(true); switch (decl.ty.zigTypeTag()) { .Fn => { - const gop = try self.imports.getOrPut(self.base.allocator, symbol_index); + const gop = try self.imports.getOrPut(self.base.allocator, .{ .index = symbol_index, .file = null }); const module_name = if (decl.getExternFn().?.lib_name) |lib_name| blk: { break :blk std.mem.sliceTo(lib_name, 0); } else self.host_name; @@ -599,21 +696,176 @@ fn allocateAtoms(self: *Wasm) !void { } fn setupImports(self: *Wasm) void { + for (self.resolved_symbols.items) |symbol_loc| { + if (symbol_loc.file == null) { + // imports generated by Zig code are already in the `import` section + continue; + } + + const symbol = symbol_loc.getSymbol(self); + if (symbol.tag == .data or !symbol.requiresImport()) { + continue; + } + + log.debug("Symbol '{s}' will be imported from the host", .{symbol.name}); + const import = self.objects.items[symbol_loc.file.?].findImport(symbol.externalType(), symbol.index); + // TODO: De-duplicate imports + try self.imports.putNoClobber(self.base.allocator, symbol_loc, import); + } + + // Assign all indexes of the imports to their representing symbols var function_index: u32 = 0; + var global_index: u32 = 0; + var table_index: u32 = 0; var it = self.imports.iterator(); while (it.next()) |entry| { - const symbol = &self.symbols.items[entry.key_ptr.*]; + const symbol = entry.key_ptr.*.getSymbol(self); const import: wasm.Import = entry.value_ptr.*; switch (import.kind) { .function => { symbol.index = function_index; function_index += 1; }, + .global => { + symbol.index = global_index; + global_index += 1; + }, + .table => { + symbol.index = table_index; + table_index += 1; + }, else => unreachable, } } } +/// Takes the global, function and table section from each linked object file +/// and merges it into a single section for each. +fn mergeSections(self: *Wasm) !void { + // append the indirect function table if initialized + if (self.globals.get("__indirect_function_table")) |sym_loc| { + const table: wasm.Table = .{ + .limits = .{ .min = @intCast(u32, self.function_table.count()), .max = null }, + .reftype = .funcref, + }; + sym_loc.getSymbol(self).index = @intCast(u32, self.tables.items.len) + self.imported_tables_count; + try self.tables.append(self.base.allocator, table); + } + + for (self.resolved_symbols.items) |sym_loc| { + if (sym_loc.file == null) { + // Zig code-generated symbols are already within the sections and do not + // require to be merged + continue; + } + + const object = self.objects.items[sym_loc.file.?]; + const symbol = &object.symtable[sym_loc.index]; + if (symbol.isUndefined()) { + // Skip undefined symbols as they go in the `import` section + continue; + } + + const offset = object.importedCountByKind(symbol.externalType()); + const index = symbol.index - offset; + switch (symbol.tag) { + .function => { + const original_func = object.functions[index]; + symbol.index = @intCast(u32, self.functions.items.len) + self.imported_functions_count; + try self.functions.append(self.base.allocator, original_func); + }, + .global => { + const original_global = object.globals[index]; + symbol.index = @intCast(u32, self.globals.items.len) + self.imported_globals_count; + try self.wasm_globals.append(self.base.allocator, original_global); + }, + .table => { + const original_table = object.tables[index]; + symbol.index = @intCast(u32, self.tables.items.len) + self.imported_tables_count; + try self.tables.append(self.base.allocator, original_table); + }, + else => {}, + } + } + + log.debug("Merged ({d}) functions", .{self.functions.items.len}); + log.debug("Merged ({d}) globals", .{self.wasm_globals.items.len}); + log.debug("Merged ({d}) tables", .{self.tables.tems.len}); +} + +/// Merges function types of all object files into the final +/// 'types' section, while assigning the type index to the representing +/// section (import, export, function). +fn mergeTypes(self: *Wasm) !void { + for (self.resolved_symbols.items) |sym_loc| { + if (sym_loc.file == null) { + // zig code-generated symbols are already present in final type section + continue; + } + const object = self.objects.items[sym_loc.file.?]; + const symbol = object.symtable[sym_loc.index]; + if (symbol.tag != .function) { + // Only functions have types + continue; + } + + if (symbol.isUndefined()) { + log.debug("Adding type from extern function '{s}'", .{symbol.name}); + const import: *wasm.Import = self.imports.getPtr(sym_loc); + const original_type = object.types[import.kind.function]; + import.kind.function = try self.putOrGetFuncType(original_type); + } else { + log.debug("Adding type from function '{s}'", .{symbol.name}); + const func = &self.functions.items[symbol.index - self.imported_functions_count]; + func.type_index = try self.putOrGetFuncType(object.types[func.type_index]); + } + } + log.debug("Completed merging and deduplicating types. Total count: ({d})", .{self.func_types.items.len}); +} + +fn setupExports(self: *Wasm) !void { + log.debug("Building exports from symbols", .{}); + + // When importing memory option if false, we export it instead + if (!self.base.options.import_memory) { + try self.exports.append(self.base.allocator, .{ .name = "memory", .kind = .memory, .index = 0 }); + } + + for (self.resolved_symbols.items) |sym_loc| { + const symbol = sym_loc.getSymbol(self); + if (!symbol.isExported()) continue; + + const exp: wasm.Export = .{ .name = symbol.name, .kind = symbol.externalType(), .index = symbol.index }; + log.debug("Appending export for symbol '{s}' at index: ({d})", .{ exp.name, exp.index }); + try self.exports.append(self.base.allocator, exp); + } + + log.debug("Completed building exports. Total count: ({d})", .{self.exports.items.len}); +} + +fn setupStart(self: *Wasm) !void { + const entry_name = self.base.options.entry orelse "_start"; + + const symbol_loc = self.globals.get(entry_name) orelse { + if (self.base.options.output_mode == .Exe) { + if (self.base.options.wasi_exec_model == .reactor) return; // Not required for reactors + } else { + return; // No entry point needed for non-executable wasm files + } + log.err("Entry symbol '{s}' missing", .{entry_name}); + return error.MissingSymbol; + }; + + const symbol = symbol_loc.getSymbol(self); + if (symbol.tag != .function) { + log.err("Entry symbol '{s}' is not a function", .{entry_name}); + return error.InvalidEntryKind; + } + + // Ensure the symbol is exported so host environment can access it + symbol.setFlag(.WASM_SYM_EXPORTED); +} + /// Sets up the memory section of the wasm module, as well as the stack. fn setupMemory(self: *Wasm) !void { log.debug("Setting up memory layout", .{}); @@ -754,6 +1006,12 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // TODO: Also link with other objects such as compiler-rt try self.parseInputFiles(positionals.items); + var object_index: u16 = 0; + while (object_index < self.objects.items.len) : (object_index += 1) { + try self.resolveSymbolsInObject(object_index); + try self.objects.items[object_index].parseIntoAtoms(self.base.allocator, object_index, self); + } + // When we finish/error we reset the state of the linker // So we can rebuild the binary file on each incremental update defer self.resetState(); @@ -777,6 +1035,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { try self.setupMemory(); try self.allocateAtoms(); self.mapFunctionTable(); + try self.mergeSections(); + try self.mergeTypes(); + try self.setupExports(); const file = self.base.file.?; const header_size = 5 + 1; diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index fc4effd7145838fd580f231613455d80637ec140..ab725104251bbb6b83a1c536effa1ecf13335291 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -50,7 +50,7 @@ pub fn deinit(self: *Atom, gpa: Allocator) void { self.relocs.deinit(gpa); self.code.deinit(gpa); - while (self.locals.items) |*local| { + for (self.locals.items) |*local| { local.deinit(gpa); } self.locals.deinit(gpa); @@ -93,7 +93,7 @@ pub fn symbolAtom(self: *Atom, symbol_index: u32) *Atom { /// Resolves the relocations within the atom, writing the new value /// at the calculated offset. pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void { - const symbol: Symbol = wasm_bin.symbols.items[self.sym_index]; + const symbol: Symbol = wasm_bin.managed_symbols.items[self.sym_index]; log.debug("Resolving relocs in atom '{s}' count({d})", .{ symbol.name, self.relocs.items.len, @@ -102,7 +102,7 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void { for (self.relocs.items) |reloc| { const value = try relocationValue(reloc, wasm_bin); log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{ - wasm_bin.symbols.items[reloc.index].name, + wasm_bin.managed_symbols.items[reloc.index].name, symbol.name, reloc.offset, value, @@ -139,7 +139,7 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void { /// All values will be represented as a `u64` as all values can fit within it. /// The final value must be casted to the correct size. fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 { - const symbol: Symbol = wasm_bin.symbols.items[relocation.index]; + const symbol: Symbol = wasm_bin.managed_symbols.items[relocation.index]; return switch (relocation.relocation_type) { .R_WASM_FUNCTION_INDEX_LEB => symbol.index, .R_WASM_TABLE_NUMBER_LEB => symbol.index, diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index df98ea4d37a09d6f693b0d9ece8f5125973eceec..3dc4a91a63571dfa11f30c9335776c7fa47d0bee 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -6,14 +6,14 @@ const Object = @This(); const Atom = @import("Atom.zig"); const types = @import("types.zig"); const std = @import("std"); -const Wasm = @import("Wasm.zig"); +const Wasm = @import("../Wasm.zig"); const Symbol = @import("Symbol.zig"); const Allocator = std.mem.Allocator; const leb = std.leb; const meta = std.meta; -const log = std.log.scoped(.zwld); +const log = std.log.scoped(.link); /// Wasm spec version used for this `Object` version: u32 = 0, @@ -26,7 +26,7 @@ file: ?std.fs.File = null, /// Name (read path) of the object file. name: []const u8, /// Parsed type section -types: []const std.wasm.Type = &.{}, +func_types: []const std.wasm.Type = &.{}, /// A list of all imports for this module imports: []std.wasm.Import = &.{}, /// Parsed function section @@ -104,7 +104,8 @@ const RelocatableData = struct { pub const InitError = error{NotObjectFile} || ParseError || std.fs.File.ReadError; /// Initializes a new `Object` from a wasm object file. -pub fn init(gpa: Allocator, file: std.fs.File, path: []const u8) InitError!Object { +/// This also parses and verifies the object file. +pub fn create(gpa: Allocator, file: std.fs.File, path: []const u8) InitError!Object { var object: Object = .{ .file = file, .name = path, @@ -161,7 +162,7 @@ pub fn getTable(self: *const Object, id: u32) *std.wasm.Table { /// we initialize a new table symbol that corresponds to that import and return that symbol. /// /// When the object file is *NOT* MVP, we return `null`. -fn checkLegacyIndirectFunctionTable(self: *Object) !?Symbol { +fn checkLegacyIndirectFunctionTable(self: *Object, gpa: Allocator) !?Symbol { var table_count: usize = 0; for (self.symtable) |sym| { if (sym.tag == .table) table_count += 1; @@ -204,7 +205,7 @@ fn checkLegacyIndirectFunctionTable(self: *Object) !?Symbol { var table_symbol: Symbol = .{ .flags = 0, - .name = table_import.name, + .name = try gpa.dupeZ(u8, table_import.name), .tag = .table, .index = 0, }; @@ -310,7 +311,7 @@ fn Parser(comptime ReaderType: type) type { } }, .type => { - for (try readVec(&self.object.types, reader, gpa)) |*type_val| { + for (try readVec(&self.object.func_types, reader, gpa)) |*type_val| { if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType; for (try readVec(&type_val.params, reader, gpa)) |*param| { @@ -636,7 +637,7 @@ fn Parser(comptime ReaderType: type) type { // we found all symbols, check for indirect function table // in case of an MVP object file - if (try self.object.checkLegacyIndirectFunctionTable()) |symbol| { + if (try self.object.checkLegacyIndirectFunctionTable(gpa)) |symbol| { try symbols.append(symbol); log.debug("Found legacy indirect function table. Created symbol", .{}); } @@ -662,7 +663,7 @@ fn Parser(comptime ReaderType: type) type { switch (tag) { .data => { const name_len = try leb.readULEB128(u32, reader); - const name = try gpa.alloc(u8, name_len); + const name = try gpa.allocSentinel(u8, name_len, 0); try reader.readNoEof(name); symbol.name = name; @@ -684,16 +685,16 @@ fn Parser(comptime ReaderType: type) type { const is_undefined = symbol.isUndefined(); if (is_undefined) { - maybe_import = self.object.findImport(symbol.externalType(), symbol.index); + maybe_import = self.object.findImport(symbol.tag.externalType(), symbol.index); } const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME); if (!(is_undefined and !explicit_name)) { const name_len = try leb.readULEB128(u32, reader); - const name = try gpa.alloc(u8, name_len); + const name = try gpa.allocSentinel(u8, name_len, 0); try reader.readNoEof(name); symbol.name = name; } else { - symbol.name = maybe_import.?.name; + symbol.name = try gpa.dupeZ(u8, maybe_import.?.name); } }, } @@ -818,7 +819,6 @@ pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin } } - // TODO: Replace `atom.code` from an existing slice to a pointer to the data try atom.code.appendSlice(gpa, relocatable_data.data[0..relocatable_data.size]); const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index]; diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index 53d96d6382ac463a8b01ddf15339eb30080e9d72..55e9851263e84a0083ed4bad9c7ac05219e11933 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -38,6 +38,7 @@ pub const Tag = enum { .data => .memory, .section => unreachable, // Not an external type .event => unreachable, // Not an external type + .dead => unreachable, // Dead symbols should not be referenced .table => .table, }; }