diff options
Diffstat (limited to 'src/link')
| -rw-r--r-- | src/link/Wasm.zig | 760 | ||||
| -rw-r--r-- | src/link/Wasm/Archive.zig | 74 | ||||
| -rw-r--r-- | src/link/Wasm/Object.zig | 44 | ||||
| -rw-r--r-- | src/link/Wasm/Symbol.zig | 5 | ||||
| -rw-r--r-- | src/link/Wasm/ZigObject.zig | 82 |
5 files changed, 462 insertions, 503 deletions
diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index b2780a9f4e..a1cd642fcc 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -36,11 +36,16 @@ const Value = @import("../Value.zig"); const ZigObject = @import("Wasm/ZigObject.zig"); base: link.File, +/// Null-terminated strings, indexes have type String and string_table provides +/// lookup. +string_bytes: std.ArrayListUnmanaged(u8), +/// Omitted when serializing linker state. +string_table: String.Table, /// Symbol name of the entry function to export -entry_name: ?[]const u8, +entry_name: OptionalString, /// When true, will allow undefined symbols import_symbols: bool, -/// List of *global* symbol names to export to the host environment. +/// Set of *global* symbol names to export to the host environment. export_symbol_names: []const []const u8, /// When defined, sets the start of the data section. global_base: ?u64, @@ -63,32 +68,14 @@ objects: std.ArrayListUnmanaged(Object) = .{}, /// LLVM uses "env" by default when none is given. This would be a good default for Zig /// to support existing code. /// TODO: Allow setting this through a flag? -host_name: []const u8 = "env", +host_name: String, /// List of symbols generated by the linker. synthetic_symbols: std.ArrayListUnmanaged(Symbol) = .empty, /// Maps atoms to their segment index -atoms: std.AutoHashMapUnmanaged(u32, Atom.Index) = .empty, +atoms: std.AutoHashMapUnmanaged(Segment.Index, Atom.Index) = .empty, /// List of all atoms. managed_atoms: std.ArrayListUnmanaged(Atom) = .empty, -/// Represents the index into `segments` where the 'code' section -/// lives. -code_section_index: ?u32 = null, -/// The index of the segment representing the custom '.debug_info' section. -debug_info_index: ?u32 = null, -/// The index of the segment representing the custom '.debug_line' section. -debug_line_index: ?u32 = null, -/// The index of the segment representing the custom '.debug_loc' section. -debug_loc_index: ?u32 = null, -/// The index of the segment representing the custom '.debug_ranges' section. -debug_ranges_index: ?u32 = null, -/// The index of the segment representing the custom '.debug_pubnames' section. -debug_pubnames_index: ?u32 = null, -/// The index of the segment representing the custom '.debug_pubtypes' section. -debug_pubtypes_index: ?u32 = null, -/// The index of the segment representing the custom '.debug_pubtypes' section. -debug_str_index: ?u32 = null, -/// The index of the segment representing the custom '.debug_pubtypes' section. -debug_abbrev_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, @@ -104,13 +91,11 @@ imports: std.AutoHashMapUnmanaged(SymbolLoc, Import) = .empty, /// Used for code, data and custom sections. segments: std.ArrayListUnmanaged(Segment) = .empty, /// Maps a data segment key (such as .rodata) to the index into `segments`. -data_segments: std.StringArrayHashMapUnmanaged(u32) = .empty, +data_segments: std.StringArrayHashMapUnmanaged(Segment.Index) = .empty, /// A table of `NamedSegment` which provide meta data /// about a data symbol such as its name where the key is /// the segment index, which can be found from `data_segments` -segment_info: std.AutoArrayHashMapUnmanaged(u32, NamedSegment) = .empty, -/// Deduplicated string table for strings used by symbols, imports and exports. -string_table: StringTable = .{}, +segment_info: std.AutoArrayHashMapUnmanaged(Segment.Index, NamedSegment) = .empty, // Output sections /// Output type section @@ -158,8 +143,8 @@ function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .empty, /// e.g. when an undefined symbol references a symbol from the archive. lazy_archives: std.ArrayListUnmanaged(LazyArchive) = .empty, -/// A map of global names (read: offset into string table) to their symbol location -globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .empty, +/// A map of global names to their symbol location +globals: std.AutoArrayHashMapUnmanaged(String, SymbolLoc) = .empty, /// The list of GOT symbols and their location got_symbols: std.ArrayListUnmanaged(SymbolLoc) = .empty, /// Maps discarded symbols and their positions to the location of the symbol @@ -169,8 +154,7 @@ discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .empty, /// into the final binary. resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .empty, /// Symbols that remain undefined after symbol resolution. -/// Note: The key represents an offset into the string table, rather than the actual string. -undefs: std.AutoArrayHashMapUnmanaged(u32, SymbolLoc) = .empty, +undefs: std.AutoArrayHashMapUnmanaged(String, SymbolLoc) = .empty, /// Maps a symbol's location to an atom. This can be used to find meta /// data of a symbol, such as its size, or its offset to perform a relocation. /// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped. @@ -178,8 +162,103 @@ symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .empty, /// `--verbose-link` output. /// Initialized on creation, appended to as inputs are added, printed during `flush`. +/// String data is allocated into Compilation arena. dump_argv_list: std.ArrayListUnmanaged([]const u8), +/// Represents the index into `segments` where the 'code' section lives. +code_section_index: Segment.OptionalIndex = .none, +custom_sections: CustomSections, +preloaded_strings: PreloadedStrings, + +/// Type reflection is used on the field names to autopopulate each field +/// during initialization. +const PreloadedStrings = struct { + __heap_base: String, + __heap_end: String, + __indirect_function_table: String, + __linear_memory: String, + __stack_pointer: String, + __tls_align: String, + __tls_base: String, + __tls_size: String, + __wasm_apply_global_tls_relocs: String, + __wasm_call_ctors: String, + __wasm_init_memory: String, + __wasm_init_memory_flag: String, + __wasm_init_tls: String, + __zig_err_name_table: String, + __zig_err_names: String, + __zig_errors_len: String, + _initialize: String, + _start: String, + memory: String, +}; + +/// Type reflection is used on the field names to autopopulate each inner `name` field. +const CustomSections = struct { + @".debug_info": CustomSection, + @".debug_pubtypes": CustomSection, + @".debug_abbrev": CustomSection, + @".debug_line": CustomSection, + @".debug_str": CustomSection, + @".debug_pubnames": CustomSection, + @".debug_loc": CustomSection, + @".debug_ranges": CustomSection, +}; + +const CustomSection = struct { + name: String, + index: Segment.OptionalIndex, +}; + +/// Index into string_bytes +pub const String = enum(u32) { + _, + + const Table = std.HashMapUnmanaged(String, void, TableContext, std.hash_map.default_max_load_percentage); + + const TableContext = struct { + bytes: []const u8, + + pub fn eql(_: @This(), a: String, b: String) bool { + return a == b; + } + + pub fn hash(ctx: @This(), key: String) u64 { + return std.hash_map.hashString(mem.sliceTo(ctx.bytes[@intFromEnum(key)..], 0)); + } + }; + + const TableIndexAdapter = struct { + bytes: []const u8, + + pub fn eql(ctx: @This(), a: []const u8, b: String) bool { + return mem.eql(u8, a, mem.sliceTo(ctx.bytes[@intFromEnum(b)..], 0)); + } + + pub fn hash(_: @This(), adapted_key: []const u8) u64 { + assert(mem.indexOfScalar(u8, adapted_key, 0) == null); + return std.hash_map.hashString(adapted_key); + } + }; + + pub fn toOptional(i: String) OptionalString { + const result: OptionalString = @enumFromInt(@intFromEnum(i)); + assert(result != .none); + return result; + } +}; + +pub const OptionalString = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn unwrap(i: OptionalString) ?String { + if (i == .none) return null; + return @enumFromInt(@intFromEnum(i)); + } +}; + /// Index into objects array or the zig object. pub const ObjectId = enum(u16) { zig_object = std.math.maxInt(u16) - 1, @@ -222,6 +301,26 @@ pub const Segment = struct { offset: u32, flags: u32, + const Index = enum(u32) { + _, + + pub fn toOptional(i: Index) OptionalIndex { + const result: OptionalIndex = @enumFromInt(@intFromEnum(i)); + assert(result != .none); + return result; + } + }; + + const OptionalIndex = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn unwrap(i: OptionalIndex) ?Index { + if (i == .none) return null; + return @enumFromInt(@intFromEnum(i)); + } + }; + pub const Flag = enum(u32) { WASM_DATA_SEGMENT_IS_PASSIVE = 0x01, WASM_DATA_SEGMENT_HAS_MEMINDEX = 0x02, @@ -260,26 +359,8 @@ pub fn symbolLocSymbol(wasm: *const Wasm, loc: SymbolLoc) *Symbol { } /// From a given location, returns the name of the symbol. -pub fn symbolLocName(wasm: *const Wasm, loc: SymbolLoc) []const u8 { - if (wasm.discarded.get(loc)) |new_loc| { - return wasm.symbolLocName(new_loc); - } - switch (loc.file) { - .none => { - const sym = wasm.synthetic_symbols.items[@intFromEnum(loc.index)]; - return wasm.string_table.get(sym.name); - }, - .zig_object => { - const zo = wasm.zig_object.?; - const sym = zo.symbols.items[@intFromEnum(loc.index)]; - return zo.string_table.get(sym.name).?; - }, - _ => { - const obj = &wasm.objects.items[@intFromEnum(loc.file)]; - const sym = obj.symtable[@intFromEnum(loc.index)]; - return obj.string_table.get(sym.name); - }, - } +pub fn symbolLocName(wasm: *const Wasm, loc: SymbolLoc) [:0]const u8 { + return wasm.stringSlice(wasm.symbolLocSymbol(loc).name); } /// From a given symbol location, returns the final location. @@ -325,75 +406,6 @@ pub const InitFuncLoc = struct { return lhs.priority < rhs.priority; } }; -/// Generic string table that duplicates strings -/// and converts them into offsets instead. -pub const StringTable = struct { - /// Table that maps string offsets, which is used to de-duplicate strings. - /// Rather than having the offset map to the data, the `StringContext` holds all bytes of the string. - /// The strings are stored as a contigious array where each string is zero-terminated. - string_table: std.HashMapUnmanaged( - u32, - void, - std.hash_map.StringIndexContext, - std.hash_map.default_max_load_percentage, - ) = .{}, - /// Holds the actual data of the string table. - string_data: std.ArrayListUnmanaged(u8) = .empty, - - /// Accepts a string and searches for a corresponding string. - /// When found, de-duplicates the string and returns the existing offset instead. - /// When the string is not found in the `string_table`, a new entry will be inserted - /// and the new offset to its data will be returned. - pub fn put(table: *StringTable, allocator: Allocator, string: []const u8) !u32 { - const gop = try table.string_table.getOrPutContextAdapted( - allocator, - string, - std.hash_map.StringIndexAdapter{ .bytes = &table.string_data }, - .{ .bytes = &table.string_data }, - ); - if (gop.found_existing) { - const off = gop.key_ptr.*; - log.debug("reusing string '{s}' at offset 0x{x}", .{ string, off }); - return off; - } - - try table.string_data.ensureUnusedCapacity(allocator, string.len + 1); - const offset: u32 = @intCast(table.string_data.items.len); - - log.debug("writing new string '{s}' at offset 0x{x}", .{ string, offset }); - - table.string_data.appendSliceAssumeCapacity(string); - table.string_data.appendAssumeCapacity(0); - - gop.key_ptr.* = offset; - - return offset; - } - - /// From a given offset, returns its corresponding string value. - /// Asserts offset does not exceed bounds. - pub fn get(table: StringTable, off: u32) []const u8 { - assert(off < table.string_data.items.len); - return mem.sliceTo(@as([*:0]const u8, @ptrCast(table.string_data.items.ptr + off)), 0); - } - - /// Returns the offset of a given string when it exists. - /// Will return null if the given string does not yet exist within the string table. - pub fn getOffset(table: *StringTable, string: []const u8) ?u32 { - return table.string_table.getKeyAdapted( - string, - std.hash_map.StringIndexAdapter{ .bytes = &table.string_data }, - ); - } - - /// Frees all resources of the string table. Any references pointing - /// to the strings will be invalid. - pub fn deinit(table: *StringTable, allocator: Allocator) void { - table.string_data.deinit(allocator); - table.string_table.deinit(allocator); - table.* = undefined; - } -}; pub fn open( arena: Allocator, @@ -451,6 +463,8 @@ pub fn createEmpty( .build_id = options.build_id, }, .name = undefined, + .string_table = .empty, + .string_bytes = .empty, .import_table = options.import_table, .export_table = options.export_table, .import_symbols = options.import_symbols, @@ -459,20 +473,38 @@ pub fn createEmpty( .initial_memory = options.initial_memory, .max_memory = options.max_memory, - .entry_name = switch (options.entry) { - .disabled => null, - .default => if (output_mode != .Exe) null else defaultEntrySymbolName(wasi_exec_model), - .enabled => defaultEntrySymbolName(wasi_exec_model), - .named => |name| name, - }, + .entry_name = undefined, .zig_object = null, .dump_argv_list = .empty, + .host_name = undefined, + .custom_sections = undefined, + .preloaded_strings = undefined, }; if (use_llvm and comp.config.have_zcu) { wasm.llvm_object = try LlvmObject.create(arena, comp); } errdefer wasm.base.destroy(); + wasm.host_name = try wasm.internString("env"); + + inline for (@typeInfo(CustomSections).@"struct".fields) |field| { + @field(wasm.custom_sections, field.name) = .{ + .index = .none, + .name = try wasm.internString(field.name), + }; + } + + inline for (@typeInfo(PreloadedStrings).@"struct".fields) |field| { + @field(wasm.preloaded_strings, field.name) = try wasm.internString(field.name); + } + + wasm.entry_name = switch (options.entry) { + .disabled => .none, + .default => if (output_mode != .Exe) .none else defaultEntrySymbolName(&wasm.preloaded_strings, wasi_exec_model).toOptional(), + .enabled => defaultEntrySymbolName(&wasm.preloaded_strings, wasi_exec_model).toOptional(), + .named => |name| (try wasm.internString(name)).toOptional(), + }; + if (use_lld and (use_llvm or !comp.config.have_zcu)) { // LLVM emits the object file (if any); LLD links it into the final product. return wasm; @@ -498,22 +530,18 @@ pub fn createEmpty( // create stack pointer symbol { - const loc = try wasm.createSyntheticSymbol("__stack_pointer", .global); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__stack_pointer, .global); const symbol = wasm.symbolLocSymbol(loc); // For object files we will import the stack pointer symbol if (output_mode == .Obj) { symbol.setUndefined(true); symbol.index = @intCast(wasm.imported_globals_count); wasm.imported_globals_count += 1; - try wasm.imports.putNoClobber( - gpa, - loc, - .{ - .module_name = try wasm.string_table.put(gpa, wasm.host_name), - .name = symbol.name, - .kind = .{ .global = .{ .valtype = .i32, .mutable = true } }, - }, - ); + try wasm.imports.putNoClobber(gpa, loc, .{ + .module_name = wasm.host_name, + .name = symbol.name, + .kind = .{ .global = .{ .valtype = .i32, .mutable = true } }, + }); } else { symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); @@ -530,7 +558,7 @@ pub fn createEmpty( // create indirect function pointer symbol { - const loc = try wasm.createSyntheticSymbol("__indirect_function_table", .table); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__indirect_function_table, .table); const symbol = wasm.symbolLocSymbol(loc); const table: std.wasm.Table = .{ .limits = .{ .flags = 0, .min = 0, .max = undefined }, // will be overwritten during `mapFunctionTable` @@ -541,7 +569,7 @@ pub fn createEmpty( symbol.index = @intCast(wasm.imported_tables_count); wasm.imported_tables_count += 1; try wasm.imports.put(gpa, loc, .{ - .module_name = try wasm.string_table.put(gpa, wasm.host_name), + .module_name = wasm.host_name, .name = symbol.name, .kind = .{ .table = table }, }); @@ -558,7 +586,7 @@ pub fn createEmpty( // create __wasm_call_ctors { - const loc = try wasm.createSyntheticSymbol("__wasm_call_ctors", .function); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_call_ctors, .function); const symbol = wasm.symbolLocSymbol(loc); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); // we do not know the function index until after we merged all sections. @@ -569,7 +597,7 @@ pub fn createEmpty( // shared-memory symbols for TLS support if (shared_memory) { { - const loc = try wasm.createSyntheticSymbol("__tls_base", .global); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_base, .global); const symbol = wasm.symbolLocSymbol(loc); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); @@ -580,7 +608,7 @@ pub fn createEmpty( }); } { - const loc = try wasm.createSyntheticSymbol("__tls_size", .global); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_size, .global); const symbol = wasm.symbolLocSymbol(loc); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); @@ -591,7 +619,7 @@ pub fn createEmpty( }); } { - const loc = try wasm.createSyntheticSymbol("__tls_align", .global); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__tls_align, .global); const symbol = wasm.symbolLocSymbol(loc); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len); @@ -602,7 +630,7 @@ pub fn createEmpty( }); } { - const loc = try wasm.createSyntheticSymbol("__wasm_init_tls", .function); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_tls, .function); const symbol = wasm.symbolLocSymbol(loc); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); } @@ -655,13 +683,11 @@ pub fn addOrUpdateImport( /// For a given name, creates a new global synthetic symbol. /// Leaves index undefined and the default flags (0). -fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !SymbolLoc { - const gpa = wasm.base.comp.gpa; - const name_offset = try wasm.string_table.put(gpa, name); - return wasm.createSyntheticSymbolOffset(name_offset, tag); +fn createSyntheticSymbol(wasm: *Wasm, name: String, tag: Symbol.Tag) !SymbolLoc { + return wasm.createSyntheticSymbolOffset(name, tag); } -fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: u32, tag: Symbol.Tag) !SymbolLoc { +fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: String, tag: Symbol.Tag) !SymbolLoc { const sym_index: Symbol.Index = @enumFromInt(wasm.synthetic_symbols.items.len); const loc: SymbolLoc = .{ .index = sym_index, .file = .none }; const gpa = wasm.base.comp.gpa; @@ -803,16 +829,6 @@ fn objectSymbol(wasm: *const Wasm, object_id: ObjectId, index: Symbol.Index) *Sy return &obj.symtable[@intFromEnum(index)]; } -fn objectSymbolName(wasm: *const Wasm, object_id: ObjectId, index: Symbol.Index) []const u8 { - const obj = wasm.objectById(object_id) orelse { - const zo = wasm.zig_object.?; - const sym = zo.symbols.items[@intFromEnum(index)]; - return zo.string_table.get(sym.name).?; - }; - const sym = obj.symtable[@intFromEnum(index)]; - return obj.string_table.get(sym.name); -} - fn objectFunction(wasm: *const Wasm, object_id: ObjectId, sym_index: Symbol.Index) std.wasm.Func { const obj = wasm.objectById(object_id) orelse { const zo = wasm.zig_object.?; @@ -850,13 +866,6 @@ fn objectImport(wasm: *const Wasm, object_id: ObjectId, symbol_index: Symbol.Ind return obj.findImport(obj.symtable[@intFromEnum(symbol_index)]); } -/// For a given offset, returns its string value. -/// Asserts string exists in the object string table. -fn objectString(wasm: *const Wasm, object_id: ObjectId, offset: u32) []const u8 { - const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.string_table.get(offset).?; - return obj.string_table.get(offset); -} - /// Returns the object element pointer, or null if it is the ZigObject. fn objectById(wasm: *const Wasm, object_id: ObjectId) ?*Object { if (object_id == .zig_object) return null; @@ -876,27 +885,25 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { .file = object_id.toOptional(), .index = sym_index, }; - const sym_name = objectString(wasm, object_id, symbol.name); - if (mem.eql(u8, sym_name, "__indirect_function_table")) { - continue; - } - const sym_name_index = try wasm.string_table.put(gpa, sym_name); + if (symbol.name == wasm.preloaded_strings.__indirect_function_table) continue; if (symbol.isLocal()) { if (symbol.isUndefined()) { - diags.addParseError(obj_path, "local symbol '{s}' references import", .{sym_name}); + diags.addParseError(obj_path, "local symbol '{s}' references import", .{ + wasm.stringSlice(symbol.name), + }); } try wasm.resolved_symbols.putNoClobber(gpa, location, {}); continue; } - const maybe_existing = try wasm.globals.getOrPut(gpa, sym_name_index); + const maybe_existing = try wasm.globals.getOrPut(gpa, symbol.name); if (!maybe_existing.found_existing) { maybe_existing.value_ptr.* = location; try wasm.resolved_symbols.putNoClobber(gpa, location, {}); if (symbol.isUndefined()) { - try wasm.undefs.putNoClobber(gpa, sym_name_index, location); + try wasm.undefs.putNoClobber(gpa, symbol.name, location); } continue; } @@ -918,7 +925,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { } // both are defined and weak, we have a symbol collision. var err = try diags.addErrorWithNotes(2); - try err.addMsg("symbol '{s}' defined multiple times", .{sym_name}); + try err.addMsg("symbol '{s}' defined multiple times", .{wasm.stringSlice(symbol.name)}); try err.addNote("first definition in '{'}'", .{existing_file_path}); try err.addNote("next definition in '{'}'", .{obj_path}); } @@ -929,7 +936,9 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { if (symbol.tag != existing_sym.tag) { var err = try diags.addErrorWithNotes(2); - try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) }); + try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{ + wasm.stringSlice(symbol.name), @tagName(symbol.tag), @tagName(existing_sym.tag), + }); try err.addNote("first definition in '{'}'", .{existing_file_path}); try err.addNote("next definition in '{'}'", .{obj_path}); } @@ -937,22 +946,18 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { if (existing_sym.isUndefined() and symbol.isUndefined()) { // only verify module/import name for function symbols if (symbol.tag == .function) { - const existing_name = if (existing_loc.file.unwrap()) |existing_obj_id| blk: { - const imp = objectImport(wasm, existing_obj_id, existing_loc.index); - break :blk objectString(wasm, existing_obj_id, imp.module_name); - } else blk: { - const name_index = wasm.imports.get(existing_loc).?.module_name; - break :blk wasm.string_table.get(name_index); - }; + const existing_name = if (existing_loc.file.unwrap()) |existing_obj_id| + objectImport(wasm, existing_obj_id, existing_loc.index).module_name + else + wasm.imports.get(existing_loc).?.module_name; - const imp = objectImport(wasm, object_id, sym_index); - const module_name = objectString(wasm, object_id, imp.module_name); - if (!mem.eql(u8, existing_name, module_name)) { + const module_name = objectImport(wasm, object_id, sym_index).module_name; + if (existing_name != module_name) { var err = try diags.addErrorWithNotes(2); try err.addMsg("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{ - sym_name, - existing_name, - module_name, + wasm.stringSlice(symbol.name), + wasm.stringSlice(existing_name), + wasm.stringSlice(module_name), }); try err.addNote("first definition in '{'}'", .{existing_file_path}); try err.addNote("next definition in '{'}'", .{obj_path}); @@ -969,7 +974,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { const new_ty = wasm.getGlobalType(location); if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) { var err = try diags.addErrorWithNotes(2); - try err.addMsg("symbol '{s}' mismatching global types", .{sym_name}); + try err.addMsg("symbol '{s}' mismatching global types", .{wasm.stringSlice(symbol.name)}); try err.addNote("first definition in '{'}'", .{existing_file_path}); try err.addNote("next definition in '{'}'", .{obj_path}); } @@ -980,7 +985,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { const new_ty = wasm.getFunctionSignature(location); if (!existing_ty.eql(new_ty)) { var err = try diags.addErrorWithNotes(3); - try err.addMsg("symbol '{s}' mismatching function signatures.", .{sym_name}); + try err.addMsg("symbol '{s}' mismatching function signatures.", .{wasm.stringSlice(symbol.name)}); try err.addNote("expected signature {}, but found signature {}", .{ existing_ty, new_ty }); try err.addNote("first definition in '{'}'", .{existing_file_path}); try err.addNote("next definition in '{'}'", .{obj_path}); @@ -996,16 +1001,16 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { } // simply overwrite with the new symbol - log.debug("Overwriting symbol '{s}'", .{sym_name}); + log.debug("Overwriting symbol '{s}'", .{wasm.stringSlice(symbol.name)}); log.debug(" old definition in '{'}'", .{existing_file_path}); log.debug(" new definition in '{'}'", .{obj_path}); try wasm.discarded.putNoClobber(gpa, existing_loc, location); maybe_existing.value_ptr.* = location; - try wasm.globals.put(gpa, sym_name_index, location); + try wasm.globals.put(gpa, symbol.name, location); try wasm.resolved_symbols.put(gpa, location, {}); assert(wasm.resolved_symbols.swapRemove(existing_loc)); if (existing_sym.isUndefined()) { - _ = wasm.undefs.swapRemove(sym_name_index); + _ = wasm.undefs.swapRemove(symbol.name); } } } @@ -1021,7 +1026,7 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void { const sym_name_index = wasm.undefs.keys()[index]; for (wasm.lazy_archives.items) |lazy_archive| { - const sym_name = wasm.string_table.get(sym_name_index); + const sym_name = wasm.stringSlice(sym_name_index); log.debug("Detected symbol '{s}' in archive '{'}', parsing objects..", .{ sym_name, lazy_archive.path, }); @@ -1066,13 +1071,13 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void { if (!wasm.hasPassiveInitializationSegments()) { return; } - const sym_loc = try wasm.createSyntheticSymbol("__wasm_init_memory", .function); + const sym_loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_memory, .function); wasm.symbolLocSymbol(sym_loc).mark(); const flag_address: u32 = if (shared_memory) address: { // when we have passive initialization segments and shared memory // `setupMemory` will create this symbol and set its virtual address. - const loc = wasm.findGlobalSymbol("__wasm_init_memory_flag").?; + const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_init_memory_flag).?; break :address wasm.symbolLocSymbol(loc).virtual_address; } else 0; @@ -1113,31 +1118,30 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void { try writer.writeByte(std.wasm.opcode(.end)); } - var it = wasm.data_segments.iterator(); - var segment_index: u32 = 0; - while (it.next()) |entry| : (segment_index += 1) { - const segment: Segment = wasm.segments.items[entry.value_ptr.*]; - if (segment.needsPassiveInitialization(import_memory, entry.key_ptr.*)) { + for (wasm.data_segments.keys(), wasm.data_segments.values(), 0..) |key, value, segment_index_usize| { + const segment_index: u32 = @intCast(segment_index_usize); + const segment = wasm.segmentPtr(value); + if (segment.needsPassiveInitialization(import_memory, key)) { // For passive BSS segments we can simple issue a memory.fill(0). // For non-BSS segments we do a memory.init. Both these // instructions take as their first argument the destination // address. try writeI32Const(writer, segment.offset); - if (shared_memory and std.mem.eql(u8, entry.key_ptr.*, ".tdata")) { + if (shared_memory and std.mem.eql(u8, key, ".tdata")) { // When we initialize the TLS segment we also set the `__tls_base` // global. This allows the runtime to use this static copy of the // TLS data for the first/main thread. try writeI32Const(writer, segment.offset); try writer.writeByte(std.wasm.opcode(.global_set)); - const loc = wasm.findGlobalSymbol("__tls_base").?; + const loc = wasm.globals.get(wasm.preloaded_strings.__tls_base).?; try leb.writeUleb128(writer, wasm.symbolLocSymbol(loc).index); } try writeI32Const(writer, 0); try writeI32Const(writer, segment.size); try writer.writeByte(std.wasm.opcode(.misc_prefix)); - if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) { + if (std.mem.eql(u8, key, ".bss")) { // fill bss segment with zeroes try leb.writeUleb128(writer, std.wasm.miscOpcode(.memory_fill)); } else { @@ -1187,11 +1191,9 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void { try writer.writeByte(std.wasm.opcode(.end)); // end $drop } - it.reset(); - segment_index = 0; - while (it.next()) |entry| : (segment_index += 1) { - const name = entry.key_ptr.*; - const segment: Segment = wasm.segments.items[entry.value_ptr.*]; + for (wasm.data_segments.keys(), wasm.data_segments.values(), 0..) |name, value, segment_index_usize| { + const segment_index: u32 = @intCast(segment_index_usize); + const segment = wasm.segmentPtr(value); if (segment.needsPassiveInitialization(import_memory, name) and !std.mem.eql(u8, name, ".bss")) { @@ -1211,7 +1213,7 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void { try writer.writeByte(std.wasm.opcode(.end)); try wasm.createSyntheticFunction( - "__wasm_init_memory", + wasm.preloaded_strings.__wasm_init_memory, std.wasm.Type{ .params = &.{}, .returns = &.{} }, &function_body, ); @@ -1230,7 +1232,7 @@ fn setupTLSRelocationsFunction(wasm: *Wasm) !void { return; } - const loc = try wasm.createSyntheticSymbol("__wasm_apply_global_tls_relocs", .function); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_apply_global_tls_relocs, .function); wasm.symbolLocSymbol(loc).mark(); var function_body = std.ArrayList(u8).init(gpa); defer function_body.deinit(); @@ -1244,7 +1246,7 @@ fn setupTLSRelocationsFunction(wasm: *Wasm) !void { if (sym.tag == .data and sym.isDefined()) { // get __tls_base try writer.writeByte(std.wasm.opcode(.global_get)); - try leb.writeUleb128(writer, wasm.symbolLocSymbol(wasm.findGlobalSymbol("__tls_base").?).index); + try leb.writeUleb128(writer, wasm.symbolLocSymbol(wasm.globals.get(wasm.preloaded_strings.__tls_base).?).index); // add the virtual address of the symbol try writer.writeByte(std.wasm.opcode(.i32_const)); @@ -1260,7 +1262,7 @@ fn setupTLSRelocationsFunction(wasm: *Wasm) !void { try writer.writeByte(std.wasm.opcode(.end)); try wasm.createSyntheticFunction( - "__wasm_apply_global_tls_relocs", + wasm.preloaded_strings.__wasm_apply_global_tls_relocs, std.wasm.Type{ .params = &.{}, .returns = &.{} }, &function_body, ); @@ -1422,7 +1424,7 @@ fn resolveLazySymbols(wasm: *Wasm) !void { const gpa = comp.gpa; const shared_memory = comp.config.shared_memory; - if (wasm.string_table.getOffset("__heap_base")) |name_offset| { + if (wasm.getExistingString("__heap_base")) |name_offset| { if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data); try wasm.discarded.putNoClobber(gpa, kv.value, loc); @@ -1430,7 +1432,7 @@ fn resolveLazySymbols(wasm: *Wasm) !void { } } - if (wasm.string_table.getOffset("__heap_end")) |name_offset| { + if (wasm.getExistingString("__heap_end")) |name_offset| { if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { const loc = try wasm.createSyntheticSymbolOffset(name_offset, .data); try wasm.discarded.putNoClobber(gpa, kv.value, loc); @@ -1439,7 +1441,7 @@ fn resolveLazySymbols(wasm: *Wasm) !void { } if (!shared_memory) { - if (wasm.string_table.getOffset("__tls_base")) |name_offset| { + if (wasm.getExistingString("__tls_base")) |name_offset| { if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| { const loc = try wasm.createSyntheticSymbolOffset(name_offset, .global); try wasm.discarded.putNoClobber(gpa, kv.value, loc); @@ -1456,11 +1458,9 @@ fn resolveLazySymbols(wasm: *Wasm) !void { } } -// Tries to find a global symbol by its name. Returns null when not found, -/// and its location when it is found. -pub fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc { - const offset = wasm.string_table.getOffset(name) orelse return null; - return wasm.globals.get(offset); +pub fn findGlobalSymbol(wasm: *const Wasm, name: []const u8) ?SymbolLoc { + const name_index = wasm.getExistingString(name) orelse return null; + return wasm.globals.get(name_index); } fn checkUndefinedSymbols(wasm: *const Wasm) !void { @@ -1516,7 +1516,7 @@ pub fn deinit(wasm: *Wasm) void { for (wasm.lazy_archives.items) |*lazy_archive| lazy_archive.deinit(gpa); wasm.lazy_archives.deinit(gpa); - if (wasm.findGlobalSymbol("__wasm_init_tls")) |loc| { + if (wasm.globals.get(wasm.preloaded_strings.__wasm_init_tls)) |loc| { const atom = wasm.symbol_atom.get(loc).?; wasm.getAtomPtr(atom).deinit(gpa); } @@ -1544,6 +1544,7 @@ pub fn deinit(wasm: *Wasm) void { wasm.init_funcs.deinit(gpa); wasm.exports.deinit(gpa); + wasm.string_bytes.deinit(gpa); wasm.string_table.deinit(gpa); wasm.dump_argv_list.deinit(gpa); } @@ -1649,7 +1650,8 @@ fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type { /// and then returns the index to it. pub fn getGlobalSymbol(wasm: *Wasm, name: []const u8, lib_name: ?[]const u8) !Symbol.Index { _ = lib_name; - return wasm.zig_object.?.getGlobalSymbol(wasm.base.comp.gpa, name); + const name_index = try wasm.internString(name); + return wasm.zig_object.?.getGlobalSymbol(wasm.base.comp.gpa, name_index); } /// For a given `Nav`, find the given symbol index's atom, and create a relocation for the type. @@ -1721,12 +1723,12 @@ fn mapFunctionTable(wasm: *Wasm) void { } if (wasm.import_table or wasm.base.comp.config.output_mode == .Obj) { - const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?; + const sym_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?; const import = wasm.imports.getPtr(sym_loc).?; import.kind.table.limits.min = index - 1; // we start at index 1. } else if (index > 1) { log.debug("Appending indirect function table", .{}); - const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?; + const sym_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?; const symbol = wasm.symbolLocSymbol(sym_loc); const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count]; table.limits = .{ .min = index, .max = index, .flags = 0x1 }; @@ -1735,7 +1737,7 @@ fn mapFunctionTable(wasm: *Wasm) void { /// From a given index, append the given `Atom` at the back of the linked list. /// Simply inserts it into the map of atoms when it doesn't exist yet. -pub fn appendAtomAtIndex(wasm: *Wasm, index: u32, atom_index: Atom.Index) !void { +pub fn appendAtomAtIndex(wasm: *Wasm, index: Segment.Index, atom_index: Atom.Index) !void { const gpa = wasm.base.comp.gpa; const atom = wasm.getAtomPtr(atom_index); if (wasm.atoms.getPtr(index)) |last_index_ptr| { @@ -1752,9 +1754,9 @@ fn allocateAtoms(wasm: *Wasm) !void { var it = wasm.atoms.iterator(); while (it.next()) |entry| { - const segment = &wasm.segments.items[entry.key_ptr.*]; + const segment = wasm.segmentPtr(entry.key_ptr.*); var atom_index = entry.value_ptr.*; - if (entry.key_ptr.* == wasm.code_section_index) { + if (entry.key_ptr.toOptional() == wasm.code_section_index) { // Code section is allocated upon writing as they are required to be ordered // to synchronise with the function section. continue; @@ -1825,7 +1827,7 @@ fn allocateVirtualAddresses(wasm: *Wasm) void { }; const segment_name = segment_info[symbol.index].outputName(merge_segment); const segment_index = wasm.data_segments.get(segment_name).?; - const segment = wasm.segments.items[segment_index]; + const segment = wasm.segmentPtr(segment_index); // TLS symbols have their virtual address set relative to their own TLS segment, // rather than the entire Data section. @@ -1839,7 +1841,7 @@ fn allocateVirtualAddresses(wasm: *Wasm) void { fn sortDataSegments(wasm: *Wasm) !void { const gpa = wasm.base.comp.gpa; - var new_mapping: std.StringArrayHashMapUnmanaged(u32) = .empty; + var new_mapping: std.StringArrayHashMapUnmanaged(Segment.Index) = .empty; try new_mapping.ensureUnusedCapacity(gpa, wasm.data_segments.count()); errdefer new_mapping.deinit(gpa); @@ -1894,9 +1896,9 @@ fn setupInitFunctions(wasm: *Wasm) !void { }; if (ty.params.len != 0) { var err = try diags.addErrorWithNotes(0); - try err.addMsg("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)}); + try err.addMsg("constructor functions cannot take arguments: '{s}'", .{wasm.stringSlice(symbol.name)}); } - log.debug("appended init func '{s}'\n", .{object.string_table.get(symbol.name)}); + log.debug("appended init func '{s}'\n", .{wasm.stringSlice(symbol.name)}); wasm.init_funcs.appendAssumeCapacity(.{ .index = @enumFromInt(init_func.symbol_index), .file = @enumFromInt(object_index), @@ -1913,7 +1915,7 @@ fn setupInitFunctions(wasm: *Wasm) !void { mem.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan); if (wasm.init_funcs.items.len > 0) { - const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?; + const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_call_ctors).?; try wasm.mark(loc); } } @@ -1927,10 +1929,10 @@ fn setupInitFunctions(wasm: *Wasm) !void { fn initializeCallCtorsFunction(wasm: *Wasm) !void { const gpa = wasm.base.comp.gpa; // No code to emit, so also no ctors to call - if (wasm.code_section_index == null) { + if (wasm.code_section_index == .none) { // Make sure to remove it from the resolved symbols so we do not emit // it within any section. TODO: Remove this once we implement garbage collection. - const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?; + const loc = wasm.globals.get(wasm.preloaded_strings.__wasm_call_ctors).?; assert(wasm.resolved_symbols.swapRemove(loc)); return; } @@ -1965,7 +1967,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void { } try wasm.createSyntheticFunction( - "__wasm_call_ctors", + wasm.preloaded_strings.__wasm_call_ctors, std.wasm.Type{ .params = &.{}, .returns = &.{} }, &function_body, ); @@ -1973,12 +1975,12 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void { fn createSyntheticFunction( wasm: *Wasm, - symbol_name: []const u8, + symbol_name: String, func_ty: std.wasm.Type, function_body: *std.ArrayList(u8), ) !void { const gpa = wasm.base.comp.gpa; - const loc = wasm.findGlobalSymbol(symbol_name).?; // forgot to create symbol? + const loc = wasm.globals.get(symbol_name).?; const symbol = wasm.symbolLocSymbol(loc); if (symbol.isDead()) { return; @@ -1998,7 +2000,7 @@ fn createSyntheticFunction( const atom = wasm.getAtomPtr(atom_index); atom.size = @intCast(function_body.items.len); atom.code = function_body.moveToUnmanaged(); - try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom_index); + try wasm.appendAtomAtIndex(wasm.code_section_index.unwrap().?, atom_index); } /// Unlike `createSyntheticFunction` this function is to be called by @@ -2016,7 +2018,7 @@ pub fn createFunction( /// If required, sets the function index in the `start` section. fn setupStartSection(wasm: *Wasm) !void { - if (wasm.findGlobalSymbol("__wasm_init_memory")) |loc| { + if (wasm.globals.get(wasm.preloaded_strings.__wasm_init_memory)) |loc| { wasm.entry = wasm.symbolLocSymbol(loc).index; } } @@ -2029,7 +2031,7 @@ fn initializeTLSFunction(wasm: *Wasm) !void { if (!shared_memory) return; // ensure function is marked as we must emit it - wasm.symbolLocSymbol(wasm.findGlobalSymbol("__wasm_init_tls").?).mark(); + wasm.symbolLocSymbol(wasm.globals.get(wasm.preloaded_strings.__wasm_init_tls).?).mark(); var function_body = std.ArrayList(u8).init(gpa); defer function_body.deinit(); @@ -2041,14 +2043,14 @@ fn initializeTLSFunction(wasm: *Wasm) !void { // If there's a TLS segment, initialize it during runtime using the bulk-memory feature if (wasm.data_segments.getIndex(".tdata")) |data_index| { const segment_index = wasm.data_segments.entries.items(.value)[data_index]; - const segment = wasm.segments.items[segment_index]; + const segment = wasm.segmentPtr(segment_index); const param_local: u32 = 0; try writer.writeByte(std.wasm.opcode(.local_get)); try leb.writeUleb128(writer, param_local); - const tls_base_loc = wasm.findGlobalSymbol("__tls_base").?; + const tls_base_loc = wasm.globals.get(wasm.preloaded_strings.__tls_base).?; try writer.writeByte(std.wasm.opcode(.global_set)); try leb.writeUleb128(writer, wasm.symbolLocSymbol(tls_base_loc).index); @@ -2076,7 +2078,7 @@ fn initializeTLSFunction(wasm: *Wasm) !void { // If we have to perform any TLS relocations, call the corresponding function // which performs all runtime TLS relocations. This is a synthetic function, // generated by the linker. - if (wasm.findGlobalSymbol("__wasm_apply_global_tls_relocs")) |loc| { + if (wasm.globals.get(wasm.preloaded_strings.__wasm_apply_global_tls_relocs)) |loc| { try writer.writeByte(std.wasm.opcode(.call)); try leb.writeUleb128(writer, wasm.symbolLocSymbol(loc).index); wasm.symbolLocSymbol(loc).mark(); @@ -2085,7 +2087,7 @@ fn initializeTLSFunction(wasm: *Wasm) !void { try writer.writeByte(std.wasm.opcode(.end)); try wasm.createSyntheticFunction( - "__wasm_init_tls", + wasm.preloaded_strings.__wasm_init_tls, std.wasm.Type{ .params = &.{.i32}, .returns = &.{} }, &function_body, ); @@ -2101,21 +2103,18 @@ fn setupImports(wasm: *Wasm) !void { }; const symbol = wasm.symbolLocSymbol(symbol_loc); - if (symbol.isDead() or - !symbol.requiresImport() or - std.mem.eql(u8, wasm.symbolLocName(symbol_loc), "__indirect_function_table")) - { - continue; - } + if (symbol.isDead()) continue; + if (!symbol.requiresImport()) continue; + if (symbol.name == wasm.preloaded_strings.__indirect_function_table) continue; - log.debug("Symbol '{s}' will be imported from the host", .{wasm.symbolLocName(symbol_loc)}); + log.debug("Symbol '{s}' will be imported from the host", .{wasm.stringSlice(symbol.name)}); const import = objectImport(wasm, object_id, symbol_loc.index); // We copy the import to a new import to ensure the names contain references // to the internal string table, rather than of the object file. const new_imp: Import = .{ - .module_name = try wasm.string_table.put(gpa, objectString(wasm, object_id, import.module_name)), - .name = try wasm.string_table.put(gpa, objectString(wasm, object_id, import.name)), + .module_name = import.module_name, + .name = import.name, .kind = import.kind, }; // TODO: De-duplicate imports when they contain the same names and type @@ -2283,7 +2282,8 @@ fn checkExportNames(wasm: *Wasm) !void { var failed_exports = false; for (force_exp_names) |exp_name| { - const loc = wasm.findGlobalSymbol(exp_name) orelse { + const exp_name_interned = try wasm.internString(exp_name); + const loc = wasm.globals.get(exp_name_interned) orelse { var err = try diags.addErrorWithNotes(0); try err.addMsg("could not export '{s}', symbol not found", .{exp_name}); failed_exports = true; @@ -2310,11 +2310,6 @@ fn setupExports(wasm: *Wasm) !void { const symbol = wasm.symbolLocSymbol(sym_loc); if (!symbol.isExported(comp.config.rdynamic)) continue; - const sym_name = wasm.symbolLocName(sym_loc); - const export_name = if (sym_loc.file == .none) - symbol.name - else - try wasm.string_table.put(gpa, sym_name); const exp: Export = if (symbol.tag == .data) exp: { const global_index = @as(u32, @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len)); try wasm.wasm_globals.append(gpa, .{ @@ -2322,18 +2317,18 @@ fn setupExports(wasm: *Wasm) !void { .init = .{ .i32_const = @as(i32, @intCast(symbol.virtual_address)) }, }); break :exp .{ - .name = export_name, + .name = symbol.name, .kind = .global, .index = global_index, }; } else .{ - .name = export_name, + .name = symbol.name, .kind = symbol.tag.externalType(), .index = symbol.index, }; log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{ - sym_name, - wasm.string_table.get(exp.name), + wasm.stringSlice(symbol.name), + wasm.stringSlice(exp.name), exp.index, }); try wasm.exports.append(gpa, exp); @@ -2346,20 +2341,18 @@ fn setupStart(wasm: *Wasm) !void { const comp = wasm.base.comp; const diags = &wasm.base.comp.link_diags; // do not export entry point if user set none or no default was set. - const entry_name = wasm.entry_name orelse return; + const entry_name = wasm.entry_name.unwrap() orelse return; - const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name}); - return error.FlushFailure; + const symbol_loc = wasm.globals.get(entry_name) orelse { + var err = try diags.addErrorWithNotes(1); + try err.addMsg("entry symbol '{s}' missing", .{wasm.stringSlice(entry_name)}); + try err.addNote("'-fno-entry' suppresses this error", .{}); + return error.LinkFailure; }; const symbol = wasm.symbolLocSymbol(symbol_loc); - if (symbol.tag != .function) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg("Entry symbol '{s}' is not a function", .{entry_name}); - return error.FlushFailure; - } + if (symbol.tag != .function) + return diags.fail("entry symbol '{s}' is not a function", .{wasm.stringSlice(entry_name)}); // Ensure the symbol is exported so host environment can access it if (comp.config.output_mode != .Obj) { @@ -2387,7 +2380,7 @@ fn setupMemory(wasm: *Wasm) !void { const is_obj = comp.config.output_mode == .Obj; - const stack_ptr = if (wasm.findGlobalSymbol("__stack_pointer")) |loc| index: { + const stack_ptr = if (wasm.globals.get(wasm.preloaded_strings.__stack_pointer)) |loc| index: { const sym = wasm.symbolLocSymbol(loc); break :index sym.index - wasm.imported_globals_count; } else null; @@ -2404,20 +2397,20 @@ fn setupMemory(wasm: *Wasm) !void { var offset: u32 = @as(u32, @intCast(memory_ptr)); var data_seg_it = wasm.data_segments.iterator(); while (data_seg_it.next()) |entry| { - const segment = &wasm.segments.items[entry.value_ptr.*]; + const segment = wasm.segmentPtr(entry.value_ptr.*); memory_ptr = segment.alignment.forward(memory_ptr); // set TLS-related symbols if (mem.eql(u8, entry.key_ptr.*, ".tdata")) { - if (wasm.findGlobalSymbol("__tls_size")) |loc| { + if (wasm.globals.get(wasm.preloaded_strings.__tls_size)) |loc| { const sym = wasm.symbolLocSymbol(loc); wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.size); } - if (wasm.findGlobalSymbol("__tls_align")) |loc| { + if (wasm.globals.get(wasm.preloaded_strings.__tls_align)) |loc| { const sym = wasm.symbolLocSymbol(loc); wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.alignment.toByteUnits().?); } - if (wasm.findGlobalSymbol("__tls_base")) |loc| { + if (wasm.globals.get(wasm.preloaded_strings.__tls_base)) |loc| { const sym = wasm.symbolLocSymbol(loc); wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = if (shared_memory) @as(i32, 0) @@ -2435,7 +2428,7 @@ fn setupMemory(wasm: *Wasm) !void { if (shared_memory and wasm.hasPassiveInitializationSegments()) { // align to pointer size memory_ptr = mem.alignForward(u64, memory_ptr, 4); - const loc = try wasm.createSyntheticSymbol("__wasm_init_memory_flag", .data); + const loc = try wasm.createSyntheticSymbol(wasm.preloaded_strings.__wasm_init_memory_flag, .data); const sym = wasm.symbolLocSymbol(loc); sym.mark(); sym.virtual_address = @as(u32, @intCast(memory_ptr)); @@ -2452,7 +2445,7 @@ fn setupMemory(wasm: *Wasm) !void { // One of the linked object files has a reference to the __heap_base symbol. // We must set its virtual address so it can be used in relocations. - if (wasm.findGlobalSymbol("__heap_base")) |loc| { + if (wasm.globals.get(wasm.preloaded_strings.__heap_base)) |loc| { const symbol = wasm.symbolLocSymbol(loc); symbol.virtual_address = @intCast(heap_alignment.forward(memory_ptr)); } @@ -2482,7 +2475,7 @@ fn setupMemory(wasm: *Wasm) !void { wasm.memories.limits.min = @as(u32, @intCast(memory_ptr / page_size)); log.debug("Total memory pages: {d}", .{wasm.memories.limits.min}); - if (wasm.findGlobalSymbol("__heap_end")) |loc| { + if (wasm.globals.get(wasm.preloaded_strings.__heap_end)) |loc| { const symbol = wasm.symbolLocSymbol(loc); symbol.virtual_address = @as(u32, @intCast(memory_ptr)); } @@ -2512,12 +2505,12 @@ fn setupMemory(wasm: *Wasm) !void { /// From a given object's index and the index of the segment, returns the corresponding /// index of the segment within the final data section. When the segment does not yet /// exist, a new one will be initialized and appended. The new index will be returned in that case. -pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol.Index) !u32 { +pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol.Index) !Segment.Index { const comp = wasm.base.comp; const gpa = comp.gpa; const diags = &wasm.base.comp.link_diags; const symbol = objectSymbols(wasm, object_id)[@intFromEnum(symbol_index)]; - const index: u32 = @intCast(wasm.segments.items.len); + const index: Segment.Index = @enumFromInt(wasm.segments.items.len); const shared_memory = comp.config.shared_memory; switch (symbol.tag) { @@ -2545,66 +2538,27 @@ pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol return index; } else return result.value_ptr.*; }, - .function => return wasm.code_section_index orelse blk: { - wasm.code_section_index = index; + .function => return wasm.code_section_index.unwrap() orelse blk: { + wasm.code_section_index = index.toOptional(); try wasm.appendDummySegment(); break :blk index; }, .section => { - const section_name = objectSymbolName(wasm, object_id, symbol_index); - if (mem.eql(u8, section_name, ".debug_info")) { - return wasm.debug_info_index orelse blk: { - wasm.debug_info_index = index; - try wasm.appendDummySegment(); - break :blk index; - }; - } else if (mem.eql(u8, section_name, ".debug_line")) { - return wasm.debug_line_index orelse blk: { - wasm.debug_line_index = index; - try wasm.appendDummySegment(); - break :blk index; - }; - } else if (mem.eql(u8, section_name, ".debug_loc")) { - return wasm.debug_loc_index orelse blk: { - wasm.debug_loc_index = index; - try wasm.appendDummySegment(); - break :blk index; - }; - } else if (mem.eql(u8, section_name, ".debug_ranges")) { - return wasm.debug_ranges_index orelse blk: { - wasm.debug_ranges_index = index; - try wasm.appendDummySegment(); - break :blk index; - }; - } else if (mem.eql(u8, section_name, ".debug_pubnames")) { - return wasm.debug_pubnames_index orelse blk: { - wasm.debug_pubnames_index = index; - try wasm.appendDummySegment(); - break :blk index; - }; - } else if (mem.eql(u8, section_name, ".debug_pubtypes")) { - return wasm.debug_pubtypes_index orelse blk: { - wasm.debug_pubtypes_index = index; - try wasm.appendDummySegment(); - break :blk index; - }; - } else if (mem.eql(u8, section_name, ".debug_abbrev")) { - return wasm.debug_abbrev_index orelse blk: { - wasm.debug_abbrev_index = index; - try wasm.appendDummySegment(); - break :blk index; - }; - } else if (mem.eql(u8, section_name, ".debug_str")) { - return wasm.debug_str_index orelse blk: { - wasm.debug_str_index = index; - try wasm.appendDummySegment(); - break :blk index; - }; + const section_name = wasm.objectSymbol(object_id, symbol_index).name; + + inline for (@typeInfo(CustomSections).@"struct".fields) |field| { + if (@field(wasm.custom_sections, field.name).name == section_name) { + const field_ptr = &@field(wasm.custom_sections, field.name).index; + return field_ptr.unwrap() orelse { + field_ptr.* = index.toOptional(); + try wasm.appendDummySegment(); + return index; + }; + } } else { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("found unknown section '{s}'", .{section_name}); - try err.addNote("defined in '{'}'", .{objectPath(wasm, object_id)}); - return error.UnexpectedValue; + return diags.failParse(objectPath(wasm, object_id), "unknown section: {s}", .{ + wasm.stringSlice(section_name), + }); } }, else => unreachable, @@ -2803,10 +2757,9 @@ fn writeToFile( } if (import_memory) { - const mem_name = if (is_obj) "__linear_memory" else "memory"; const mem_imp: Import = .{ - .module_name = try wasm.string_table.put(gpa, wasm.host_name), - .name = try wasm.string_table.put(gpa, mem_name), + .module_name = wasm.host_name, + .name = if (is_obj) wasm.preloaded_strings.__linear_memory else wasm.preloaded_strings.memory, .kind = .{ .memory = wasm.memories.limits }, }; try wasm.emitImport(binary_writer, mem_imp); @@ -2898,7 +2851,7 @@ fn writeToFile( const header_offset = try reserveVecSectionHeader(&binary_bytes); for (wasm.exports.items) |exp| { - const name = wasm.string_table.get(exp.name); + const name = wasm.stringSlice(exp.name); try leb.writeUleb128(binary_writer, @as(u32, @intCast(name.len))); try binary_writer.writeAll(name); try leb.writeUleb128(binary_writer, @intFromEnum(exp.kind)); @@ -2937,7 +2890,7 @@ fn writeToFile( if (wasm.function_table.count() > 0) { const header_offset = try reserveVecSectionHeader(&binary_bytes); - const table_loc = wasm.findGlobalSymbol("__indirect_function_table").?; + const table_loc = wasm.globals.get(wasm.preloaded_strings.__indirect_function_table).?; const table_sym = wasm.symbolLocSymbol(table_loc); const flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually @@ -2982,7 +2935,7 @@ fn writeToFile( } // Code section - if (wasm.code_section_index != null) { + if (wasm.code_section_index != .none) { const header_offset = try reserveVecSectionHeader(&binary_bytes); const start_offset = binary_bytes.items.len - 5; // minus 5 so start offset is 5 to include entry count @@ -3022,7 +2975,7 @@ fn writeToFile( // want to guarantee the data is zero initialized if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue; const segment_index = entry.value_ptr.*; - const segment = wasm.segments.items[segment_index]; + const segment = wasm.segmentPtr(segment_index); if (segment.size == 0) continue; // do not emit empty segments segment_count += 1; var atom_index = wasm.atoms.get(segment_index).?; @@ -3133,24 +3086,8 @@ fn writeToFile( var debug_bytes = std.ArrayList(u8).init(gpa); defer debug_bytes.deinit(); - const DebugSection = struct { - name: []const u8, - index: ?u32, - }; - - const debug_sections: []const DebugSection = &.{ - .{ .name = ".debug_info", .index = wasm.debug_info_index }, - .{ .name = ".debug_pubtypes", .index = wasm.debug_pubtypes_index }, - .{ .name = ".debug_abbrev", .index = wasm.debug_abbrev_index }, - .{ .name = ".debug_line", .index = wasm.debug_line_index }, - .{ .name = ".debug_str", .index = wasm.debug_str_index }, - .{ .name = ".debug_pubnames", .index = wasm.debug_pubnames_index }, - .{ .name = ".debug_loc", .index = wasm.debug_loc_index }, - .{ .name = ".debug_ranges", .index = wasm.debug_ranges_index }, - }; - - for (debug_sections) |item| { - if (item.index) |index| { + inline for (@typeInfo(CustomSections).@"struct".fields) |field| { + if (@field(wasm.custom_sections, field.name).index.unwrap()) |index| { var atom = wasm.getAtomPtr(wasm.atoms.get(index).?); while (true) { atom.resolveRelocs(wasm); @@ -3158,7 +3095,7 @@ fn writeToFile( if (atom.prev == .null) break; atom = wasm.getAtomPtr(atom.prev); } - try emitDebugSection(&binary_bytes, debug_bytes.items, item.name); + try emitDebugSection(&binary_bytes, debug_bytes.items, field.name); debug_bytes.clearRetainingCapacity(); } } @@ -3430,11 +3367,11 @@ fn emitInit(writer: anytype, init_expr: std.wasm.InitExpression) !void { } fn emitImport(wasm: *Wasm, writer: anytype, import: Import) !void { - const module_name = wasm.string_table.get(import.module_name); + const module_name = wasm.stringSlice(import.module_name); try leb.writeUleb128(writer, @as(u32, @intCast(module_name.len))); try writer.writeAll(module_name); - const name = wasm.string_table.get(import.name); + const name = wasm.stringSlice(import.name); try leb.writeUleb128(writer, @as(u32, @intCast(name.len))); try writer.writeAll(name); @@ -3515,7 +3452,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: } try man.addOptionalFile(module_obj_path); try man.addOptionalFilePath(compiler_rt_path); - man.hash.addOptionalBytes(wasm.entry_name); + man.hash.addOptionalBytes(wasm.optionalStringSlice(wasm.entry_name)); man.hash.add(wasm.base.stack_size); man.hash.add(wasm.base.build_id); man.hash.add(import_memory); @@ -3664,7 +3601,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: try argv.append("--export-dynamic"); } - if (wasm.entry_name) |entry_name| { + if (wasm.optionalStringSlice(wasm.entry_name)) |entry_name| { try argv.appendSlice(&.{ "--entry", entry_name }); } else { try argv.append("--no-entry"); @@ -4009,7 +3946,7 @@ fn emitCodeRelocations( section_index: u32, symbol_table: std.AutoArrayHashMap(SymbolLoc, u32), ) !void { - const code_index = wasm.code_section_index orelse return; + const code_index = wasm.code_section_index.unwrap() orelse return; const writer = binary_bytes.writer(); const header_offset = try reserveCustomSectionHeader(binary_bytes); @@ -4106,7 +4043,7 @@ fn hasPassiveInitializationSegments(wasm: *const Wasm) bool { var it = wasm.data_segments.iterator(); while (it.next()) |entry| { - const segment: Segment = wasm.segments.items[entry.value_ptr.*]; + const segment = wasm.segmentPtr(entry.value_ptr.*); if (segment.needsPassiveInitialization(import_memory, entry.key_ptr.*)) { return true; } @@ -4213,10 +4150,13 @@ fn mark(wasm: *Wasm, loc: SymbolLoc) !void { } } -fn defaultEntrySymbolName(wasi_exec_model: std.builtin.WasiExecModel) []const u8 { +fn defaultEntrySymbolName( + preloaded_strings: *const PreloadedStrings, + wasi_exec_model: std.builtin.WasiExecModel, +) String { return switch (wasi_exec_model) { - .reactor => "_initialize", - .command => "_start", + .reactor => preloaded_strings._initialize, + .command => preloaded_strings._start, }; } @@ -4352,7 +4292,7 @@ pub const Atom = struct { symbol.tag != .section and symbol.isDead()) { - const val = atom.thombstone(wasm) orelse relocation.addend; + const val = atom.tombstone(wasm) orelse relocation.addend; return @bitCast(val); } switch (relocation.relocation_type) { @@ -4394,7 +4334,7 @@ pub const Atom = struct { }, .R_WASM_FUNCTION_OFFSET_I32 => { if (symbol.isUndefined()) { - const val = atom.thombstone(wasm) orelse relocation.addend; + const val = atom.tombstone(wasm) orelse relocation.addend; return @bitCast(val); } const target_atom_index = wasm.symbol_atom.get(target_loc).?; @@ -4411,16 +4351,19 @@ pub const Atom = struct { } } - // For a given `Atom` returns whether it has a thombstone value or not. + // For a given `Atom` returns whether it has a tombstone value or not. /// This defines whether we want a specific value when a section is dead. - fn thombstone(atom: Atom, wasm: *const Wasm) ?i64 { - const atom_name = wasm.symbolLocName(atom.symbolLoc()); - if (std.mem.eql(u8, atom_name, ".debug_ranges") or std.mem.eql(u8, atom_name, ".debug_loc")) { + fn tombstone(atom: Atom, wasm: *const Wasm) ?i64 { + const atom_name = wasm.symbolLocSymbol(atom.symbolLoc()).name; + if (atom_name == wasm.custom_sections.@".debug_ranges".name or + atom_name == wasm.custom_sections.@".debug_loc".name) + { return -2; - } else if (std.mem.startsWith(u8, atom_name, ".debug_")) { + } else if (std.mem.startsWith(u8, wasm.stringSlice(atom_name), ".debug_")) { return -1; + } else { + return null; } - return null; } }; @@ -4509,8 +4452,8 @@ pub const Relocation = struct { /// of the import using offsets into a string table, rather than the slices itself. /// This saves us (potentially) 24 bytes per import on 64bit machines. pub const Import = struct { - module_name: u32, - name: u32, + module_name: String, + name: String, kind: std.wasm.Import.Kind, }; @@ -4519,7 +4462,7 @@ pub const Import = struct { /// of the export using offsets into a string table, rather than the slice itself. /// This saves us (potentially) 12 bytes per export on 64bit machines. pub const Export = struct { - name: u32, + name: String, index: u32, kind: std.wasm.ExternalKind, }; @@ -4719,7 +4662,7 @@ fn parseSymbolIntoAtom(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol.In atom.code = std.ArrayListUnmanaged(u8).fromOwnedSlice(relocatable_data.data[0..relocatable_data.size]); atom.original_offset = relocatable_data.offset; - const segment: *Wasm.Segment = &wasm.segments.items[final_index]; + const segment = wasm.segmentPtr(final_index); if (relocatable_data.type == .data) { //code section and custom sections are 1-byte aligned segment.alignment = segment.alignment.max(atom.alignment); } @@ -4782,3 +4725,48 @@ fn searchRelocEnd(relocs: []const Wasm.Relocation, address: u32) usize { } return relocs.len; } + +pub fn internString(wasm: *Wasm, bytes: []const u8) error{OutOfMemory}!String { + const gpa = wasm.base.comp.gpa; + const gop = try wasm.string_table.getOrPutContextAdapted( + gpa, + @as([]const u8, bytes), + @as(String.TableIndexAdapter, .{ .bytes = wasm.string_bytes.items }), + @as(String.TableContext, .{ .bytes = wasm.string_bytes.items }), + ); + if (gop.found_existing) return gop.key_ptr.*; + + try wasm.string_bytes.ensureUnusedCapacity(gpa, bytes.len + 1); + const new_off: String = @enumFromInt(wasm.string_bytes.items.len); + + wasm.string_bytes.appendSliceAssumeCapacity(bytes); + wasm.string_bytes.appendAssumeCapacity(0); + + gop.key_ptr.* = new_off; + + return new_off; +} + +pub fn getExistingString(wasm: *const Wasm, bytes: []const u8) ?String { + return wasm.string_table.getKeyAdapted(bytes, @as(String.TableIndexAdapter, .{ + .bytes = wasm.string_bytes.items, + })); +} + +pub fn stringSlice(wasm: *const Wasm, index: String) [:0]const u8 { + const slice = wasm.string_bytes.items[@intFromEnum(index)..]; + return slice[0..mem.indexOfScalar(u8, slice, 0).? :0]; +} + +pub fn optionalStringSlice(wasm: *const Wasm, index: OptionalString) ?[:0]const u8 { + return stringSlice(wasm, index.unwrap() orelse return null); +} + +pub fn castToString(wasm: *const Wasm, index: u32) String { + assert(index == 0 or wasm.string_bytes.items[index - 1] == 0); + return @enumFromInt(index); +} + +fn segmentPtr(wasm: *const Wasm, index: Segment.Index) *Segment { + return &wasm.segments.items[@intFromEnum(index)]; +} diff --git a/src/link/Wasm/Archive.zig b/src/link/Wasm/Archive.zig index bdbdec6f9a..1ff36c5af8 100644 --- a/src/link/Wasm/Archive.zig +++ b/src/link/Wasm/Archive.zig @@ -1,5 +1,3 @@ -header: ar_hdr, - /// A list of long file names, delimited by a LF character (0x0a). /// This is stored as a single slice of bytes, as the header-names /// point to the character index of a file name, rather than the index @@ -14,65 +12,54 @@ toc: Toc, const Toc = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)); // Archive files start with the ARMAG identifying string. Then follows a -// `struct ar_hdr', and as many bytes of member file data as its `ar_size' +// `struct Header', and as many bytes of member file data as its `size' // member indicates, for each member file. /// String that begins an archive file. const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; /// Size of that string. const SARMAG: u4 = 8; -/// String in ar_fmag at the end of each header. +/// String in fmag at the end of each header. const ARFMAG: *const [2:0]u8 = "`\n"; -const ar_hdr = extern struct { +const Header = extern struct { /// Member file name, sometimes / terminated. - ar_name: [16]u8, - + name: [16]u8, /// File date, decimal seconds since Epoch. - ar_date: [12]u8, - + date: [12]u8, /// User ID, in ASCII format. - ar_uid: [6]u8, - + uid: [6]u8, /// Group ID, in ASCII format. - ar_gid: [6]u8, - + gid: [6]u8, /// File mode, in ASCII octal. - ar_mode: [8]u8, - + mode: [8]u8, /// File size, in ASCII decimal. - ar_size: [10]u8, - + size: [10]u8, /// Always contains ARFMAG. - ar_fmag: [2]u8, + fmag: [2]u8, const NameOrIndex = union(enum) { name: []const u8, index: u32, }; - fn nameOrIndex(archive: ar_hdr) !NameOrIndex { - const value = getValue(&archive.ar_name); + fn nameOrIndex(archive: Header) !NameOrIndex { + const value = getValue(&archive.name); const slash_index = mem.indexOfScalar(u8, value, '/') orelse return error.MalformedArchive; const len = value.len; if (slash_index == len - 1) { // Name stored directly - return NameOrIndex{ .name = value }; + return .{ .name = value }; } else { // Name follows the header directly and its length is encoded in // the name field. const index = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10); - return NameOrIndex{ .index = index }; + return .{ .index = index }; } } - fn date(archive: ar_hdr) !u64 { - const value = getValue(&archive.ar_date); - return std.fmt.parseInt(u64, value, 10); - } - - fn size(archive: ar_hdr) !u32 { - const value = getValue(&archive.ar_size); + fn parsedSize(archive: Header) !u32 { + const value = getValue(&archive.size); return std.fmt.parseInt(u32, value, 10); } @@ -100,8 +87,8 @@ pub fn parse(gpa: Allocator, file_contents: []const u8) !Archive { const magic = try reader.readBytesNoEof(SARMAG); if (!mem.eql(u8, &magic, ARMAG)) return error.BadArchiveMagic; - const header = try reader.readStruct(ar_hdr); - if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) return error.BadHeaderDelimiter; + const header = try reader.readStruct(Header); + if (!mem.eql(u8, &header.fmag, ARFMAG)) return error.BadHeaderDelimiter; var toc = try parseTableOfContents(gpa, header, reader); errdefer deinitToc(gpa, &toc); @@ -110,13 +97,12 @@ pub fn parse(gpa: Allocator, file_contents: []const u8) !Archive { errdefer gpa.free(long_file_names); return .{ - .header = header, .toc = toc, .long_file_names = long_file_names, }; } -fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 { +fn parseName(archive: *const Archive, header: Header) ![]const u8 { const name_or_index = try header.nameOrIndex(); switch (name_or_index) { .name => |name| return name, @@ -127,10 +113,10 @@ fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 { } } -fn parseTableOfContents(gpa: Allocator, header: ar_hdr, reader: anytype) !Toc { +fn parseTableOfContents(gpa: Allocator, header: Header, reader: anytype) !Toc { // size field can have extra spaces padded in front as well as the end, // so we trim those first before parsing the ASCII value. - const size_trimmed = mem.trim(u8, &header.ar_size, " "); + const size_trimmed = mem.trim(u8, &header.size, " "); const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10); const num_symbols = try reader.readInt(u32, .big); @@ -170,14 +156,14 @@ fn parseTableOfContents(gpa: Allocator, header: ar_hdr, reader: anytype) !Toc { } fn parseNameTable(gpa: Allocator, reader: anytype) ![]const u8 { - const header: ar_hdr = try reader.readStruct(ar_hdr); - if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) { + const header: Header = try reader.readStruct(Header); + if (!mem.eql(u8, &header.fmag, ARFMAG)) { return error.InvalidHeaderDelimiter; } - if (!mem.eql(u8, header.ar_name[0..2], "//")) { + if (!mem.eql(u8, header.name[0..2], "//")) { return error.MissingTableName; } - const table_size = try header.size(); + const table_size = try header.parsedSize(); const long_file_names = try gpa.alloc(u8, table_size); errdefer gpa.free(long_file_names); try reader.readNoEof(long_file_names); @@ -187,16 +173,16 @@ fn parseNameTable(gpa: Allocator, reader: anytype) ![]const u8 { /// From a given file offset, starts reading for a file header. /// When found, parses the object file into an `Object` and returns it. -pub fn parseObject(archive: Archive, wasm: *const Wasm, file_contents: []const u8, path: Path) !Object { +pub fn parseObject(archive: Archive, wasm: *Wasm, file_contents: []const u8, path: Path) !Object { var fbs = std.io.fixedBufferStream(file_contents); - const header = try fbs.reader().readStruct(ar_hdr); + const header = try fbs.reader().readStruct(Header); - if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) return error.BadArchiveHeaderDelimiter; + if (!mem.eql(u8, &header.fmag, ARFMAG)) return error.BadArchiveHeaderDelimiter; const object_name = try archive.parseName(header); - const object_file_size = try header.size(); + const object_file_size = try header.parsedSize(); - return Object.create(wasm, file_contents[@sizeOf(ar_hdr)..][0..object_file_size], path, object_name); + return Object.create(wasm, file_contents[@sizeOf(Header)..][0..object_file_size], path, object_name); } const std = @import("std"); diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index 8b0a847cc9..fc11f91876 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -63,10 +63,6 @@ comdat_info: []const Wasm.Comdat = &.{}, /// Represents non-synthetic sections that can essentially be mem-cpy'd into place /// after performing relocations. relocatable_data: std.AutoHashMapUnmanaged(RelocatableData.Tag, []RelocatableData) = .empty, -/// String table for all strings required by the object file, such as symbol names, -/// import name, module name and export names. Each string will be deduplicated -/// and returns an offset into the table. -string_table: Wasm.StringTable = .{}, /// Amount of functions in the `import` sections. imported_functions_count: u32 = 0, /// Amount of globals in the `import` section. @@ -126,7 +122,7 @@ pub const RelocatableData = struct { /// When a max size is given, will only parse up to the given size, /// else will read until the end of the file. pub fn create( - wasm: *const Wasm, + wasm: *Wasm, file_contents: []const u8, path: Path, archive_member_name: ?[]const u8, @@ -187,7 +183,6 @@ pub fn deinit(object: *Object, gpa: Allocator) void { } } object.relocatable_data.deinit(gpa); - object.string_table.deinit(gpa); object.* = undefined; } @@ -242,9 +237,9 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm: *const Wasm) !?Symbol } } else unreachable; - if (!std.mem.eql(u8, object.string_table.get(table_import.name), "__indirect_function_table")) { + if (table_import.name != wasm.preloaded_strings.__indirect_function_table) { return diags.failParse(object.path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{ - object.string_table.get(table_import.name), + wasm.stringSlice(table_import.name), }); } @@ -264,10 +259,12 @@ const Parser = struct { reader: std.io.FixedBufferStream([]const u8), /// Object file we're building object: *Object, - /// Read-only reference to the WebAssembly linker - wasm: *const Wasm, + /// Mutable so that the string table can be modified. + wasm: *Wasm, fn parseObject(parser: *Parser, gpa: Allocator) anyerror!void { + const wasm = parser.wasm; + { var magic_bytes: [4]u8 = undefined; try parser.reader.reader().readNoEof(&magic_bytes); @@ -316,7 +313,7 @@ const Parser = struct { .type = .custom, .data = debug_content.ptr, .size = debug_size, - .index = try parser.object.string_table.put(gpa, name), + .index = @intFromEnum(try wasm.internString(name)), .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset .section_index = section_index, }); @@ -375,8 +372,8 @@ const Parser = struct { }; import.* = .{ - .module_name = try parser.object.string_table.put(gpa, module_name), - .name = try parser.object.string_table.put(gpa, name), + .module_name = try wasm.internString(module_name), + .name = try wasm.internString(name), .kind = kind_value, }; } @@ -422,7 +419,7 @@ const Parser = struct { defer gpa.free(name); try reader.readNoEof(name); exp.* = .{ - .name = try parser.object.string_table.put(gpa, name), + .name = try wasm.internString(name), .kind = try readEnum(std.wasm.ExternalKind, reader), .index = try readLeb(u32, reader), }; @@ -587,6 +584,7 @@ const Parser = struct { /// `parser` is used to provide access to other sections that may be needed, /// such as access to the `import` section to find the name of a symbol. fn parseSubsection(parser: *Parser, gpa: Allocator, reader: anytype) !void { + const wasm = parser.wasm; const sub_type = try leb.readUleb128(u8, reader); log.debug("Found subsection: {s}", .{@tagName(@as(Wasm.SubsectionType, @enumFromInt(sub_type)))}); const payload_len = try leb.readUleb128(u32, reader); @@ -680,7 +678,7 @@ const Parser = struct { symbol.* = try parser.parseSymbol(gpa, reader); log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{ @tagName(symbol.tag), - parser.object.string_table.get(symbol.name), + wasm.stringSlice(symbol.name), symbol.flags, }); } @@ -697,15 +695,18 @@ const Parser = struct { if (parser.object.relocatable_data.get(.custom)) |custom_sections| { for (custom_sections) |*data| { if (!data.represented) { + const name = wasm.castToString(data.index); try symbols.append(.{ - .name = data.index, + .name = name, .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .tag = .section, .virtual_address = 0, .index = data.section_index, }); data.represented = true; - log.debug("Created synthetic custom section symbol for '{s}'", .{parser.object.string_table.get(data.index)}); + log.debug("Created synthetic custom section symbol for '{s}'", .{ + wasm.stringSlice(name), + }); } } } @@ -719,7 +720,8 @@ const Parser = struct { /// requires access to `Object` to find the name of a symbol when it's /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set. fn parseSymbol(parser: *Parser, gpa: Allocator, reader: anytype) !Symbol { - const tag = @as(Symbol.Tag, @enumFromInt(try leb.readUleb128(u8, reader))); + const wasm = parser.wasm; + const tag: Symbol.Tag = @enumFromInt(try leb.readUleb128(u8, reader)); const flags = try leb.readUleb128(u32, reader); var symbol: Symbol = .{ .flags = flags, @@ -735,7 +737,7 @@ const Parser = struct { const name = try gpa.alloc(u8, name_len); defer gpa.free(name); try reader.readNoEof(name); - symbol.name = try parser.object.string_table.put(gpa, name); + symbol.name = try wasm.internString(name); // Data symbols only have the following fields if the symbol is defined if (symbol.isDefined()) { @@ -750,7 +752,7 @@ const Parser = struct { const section_data = parser.object.relocatable_data.get(.custom).?; for (section_data) |*data| { if (data.section_index == symbol.index) { - symbol.name = data.index; + symbol.name = wasm.castToString(data.index); data.represented = true; break; } @@ -765,7 +767,7 @@ const Parser = struct { const name = try gpa.alloc(u8, name_len); defer gpa.free(name); try reader.readNoEof(name); - break :name try parser.object.string_table.put(gpa, name); + break :name try wasm.internString(name); } else parser.object.findImport(symbol).name; }, } diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index 1383234782..b60b73c46f 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -8,8 +8,8 @@ /// Can contain any of the flags defined in `Flag` flags: u32, /// Symbol name, when the symbol is undefined the name will be taken from the import. -/// Note: This is an index into the string table. -name: u32, +/// Note: This is an index into the wasm string table. +name: wasm.String, /// Index into the list of objects based on set `tag` /// NOTE: This will be set to `undefined` when `tag` is `data` /// and the symbol is undefined. @@ -207,3 +207,4 @@ pub fn format(symbol: Symbol, comptime fmt: []const u8, options: std.fmt.FormatO const std = @import("std"); const Symbol = @This(); +const wasm = @import("../Wasm.zig"); diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig index 437c6479b3..d82329dda6 100644 --- a/src/link/Wasm/ZigObject.zig +++ b/src/link/Wasm/ZigObject.zig @@ -23,16 +23,14 @@ globals: std.ArrayListUnmanaged(std.wasm.Global) = .empty, atom_types: std.AutoHashMapUnmanaged(Atom.Index, u32) = .empty, /// List of all symbols generated by Zig code. symbols: std.ArrayListUnmanaged(Symbol) = .empty, -/// Map from symbol name offset to their index into the `symbols` list. -global_syms: std.AutoHashMapUnmanaged(u32, Symbol.Index) = .empty, +/// Map from symbol name to their index into the `symbols` list. +global_syms: std.AutoHashMapUnmanaged(Wasm.String, Symbol.Index) = .empty, /// List of symbol indexes which are free to be used. symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .empty, /// Extra metadata about the linking section, such as alignment of segments and their name. segment_info: std.ArrayListUnmanaged(Wasm.NamedSegment) = .empty, /// List of indexes which contain a free slot in the `segment_info` list. segment_free_list: std.ArrayListUnmanaged(u32) = .empty, -/// File encapsulated string table, used to deduplicate strings within the generated file. -string_table: StringTable = .{}, /// Map for storing anonymous declarations. Each anonymous decl maps to its Atom's index. uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Atom.Index) = .empty, /// List of atom indexes of functions that are generated by the backend. @@ -88,13 +86,9 @@ const NavInfo = struct { atom: Atom.Index = .null, exports: std.ArrayListUnmanaged(Symbol.Index) = .empty, - fn @"export"(ni: NavInfo, zig_object: *const ZigObject, name: []const u8) ?Symbol.Index { + fn @"export"(ni: NavInfo, zo: *const ZigObject, name: Wasm.String) ?Symbol.Index { for (ni.exports.items) |sym_index| { - const sym_name_index = zig_object.symbol(sym_index).name; - const sym_name = zig_object.string_table.getAssumeExists(sym_name_index); - if (std.mem.eql(u8, name, sym_name)) { - return sym_index; - } + if (zo.symbol(sym_index).name == name) return sym_index; } return null; } @@ -126,14 +120,14 @@ pub fn init(zig_object: *ZigObject, wasm: *Wasm) !void { fn createStackPointer(zig_object: *ZigObject, wasm: *Wasm) !void { const gpa = wasm.base.comp.gpa; - const sym_index = try zig_object.getGlobalSymbol(gpa, "__stack_pointer"); + const sym_index = try zig_object.getGlobalSymbol(gpa, wasm.preloaded_strings.__stack_pointer); const sym = zig_object.symbol(sym_index); sym.index = zig_object.imported_globals_count; sym.tag = .global; const is_wasm32 = wasm.base.comp.root_mod.resolved_target.result.cpu.arch == .wasm32; try zig_object.imports.putNoClobber(gpa, sym_index, .{ .name = sym.name, - .module_name = try zig_object.string_table.insert(gpa, wasm.host_name), + .module_name = wasm.host_name, .kind = .{ .global = .{ .valtype = if (is_wasm32) .i32 else .i64, .mutable = true } }, }); zig_object.imported_globals_count += 1; @@ -174,7 +168,7 @@ pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void { atom.deinit(gpa); } } - if (zig_object.findGlobalSymbol("__zig_errors_len")) |sym_index| { + if (zig_object.global_syms.get(wasm.preloaded_strings.__zig_errors_len)) |sym_index| { const atom_index = wasm.symbol_atom.get(.{ .file = .zig_object, .index = sym_index }).?; wasm.getAtomPtr(atom_index).deinit(gpa); } @@ -206,7 +200,6 @@ pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void { zig_object.segment_info.deinit(gpa); zig_object.segment_free_list.deinit(gpa); - zig_object.string_table.deinit(gpa); if (zig_object.dwarf) |*dwarf| { dwarf.deinit(); } @@ -219,7 +212,7 @@ pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void { pub fn allocateSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator) !Symbol.Index { try zig_object.symbols.ensureUnusedCapacity(gpa, 1); const sym: Symbol = .{ - .name = std.math.maxInt(u32), // will be set after updateDecl as well as during atom creation for decls + .name = undefined, // will be set after updateDecl as well as during atom creation for decls .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .tag = .undefined, // will be set after updateDecl .index = std.math.maxInt(u32), // will be set during atom parsing @@ -345,7 +338,7 @@ fn finishUpdateNav( const atom_index = nav_info.atom; const atom = wasm.getAtomPtr(atom_index); const sym = zig_object.symbol(atom.sym_index); - sym.name = try zig_object.string_table.insert(gpa, nav.fqn.toSlice(ip)); + sym.name = try wasm.internString(nav.fqn.toSlice(ip)); try atom.code.appendSlice(gpa, code); atom.size = @intCast(code.len); @@ -432,7 +425,7 @@ pub fn getOrCreateAtomForNav( gop.value_ptr.* = .{ .atom = try wasm.createAtom(sym_index, .zig_object) }; const nav = ip.getNav(nav_index); const sym = zig_object.symbol(sym_index); - sym.name = try zig_object.string_table.insert(gpa, nav.fqn.toSlice(ip)); + sym.name = try wasm.internString(nav.fqn.toSlice(ip)); } return gop.value_ptr.atom; } @@ -500,7 +493,7 @@ fn lowerConst( const segment_name = try std.mem.concat(gpa, u8, &.{ ".rodata.", name }); errdefer gpa.free(segment_name); zig_object.symbol(sym_index).* = .{ - .name = try zig_object.string_table.insert(gpa, name), + .name = try wasm.internString(name), .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .tag = .data, .index = try zig_object.createDataSegment( @@ -551,11 +544,10 @@ pub fn getErrorTableSymbol(zig_object: *ZigObject, wasm: *Wasm, pt: Zcu.PerThrea const slice_ty = Type.slice_const_u8_sentinel_0; atom.alignment = slice_ty.abiAlignment(pt.zcu); - const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_name_table"); const segment_name = try gpa.dupe(u8, ".rodata.__zig_err_name_table"); const sym = zig_object.symbol(sym_index); sym.* = .{ - .name = sym_name, + .name = wasm.preloaded_strings.__zig_err_name_table, .tag = .data, .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .index = try zig_object.createDataSegment(gpa, segment_name, atom.alignment), @@ -583,11 +575,10 @@ fn populateErrorNameTable(zig_object: *ZigObject, wasm: *Wasm, tid: Zcu.PerThrea const names_atom_index = try wasm.createAtom(names_sym_index, .zig_object); const names_atom = wasm.getAtomPtr(names_atom_index); names_atom.alignment = .@"1"; - const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_names"); const segment_name = try gpa.dupe(u8, ".rodata.__zig_err_names"); const names_symbol = zig_object.symbol(names_sym_index); names_symbol.* = .{ - .name = sym_name, + .name = wasm.preloaded_strings.__zig_err_names, .tag = .data, .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .index = try zig_object.createDataSegment(gpa, segment_name, names_atom.alignment), @@ -661,14 +652,14 @@ pub fn addOrUpdateImport( // For the import name, we use the decl's name, rather than the fully qualified name // Also mangle the name when the lib name is set and not equal to "C" so imports with the same // name but different module can be resolved correctly. - const mangle_name = lib_name != null and - !std.mem.eql(u8, lib_name.?, "c"); - const full_name = if (mangle_name) full_name: { - break :full_name try std.fmt.allocPrint(gpa, "{s}|{s}", .{ name, lib_name.? }); - } else name; + const mangle_name = if (lib_name) |n| !std.mem.eql(u8, n, "c") else false; + const full_name = if (mangle_name) + try std.fmt.allocPrint(gpa, "{s}|{s}", .{ name, lib_name.? }) + else + name; defer if (mangle_name) gpa.free(full_name); - const decl_name_index = try zig_object.string_table.insert(gpa, full_name); + const decl_name_index = try wasm.internString(full_name); const sym: *Symbol = &zig_object.symbols.items[@intFromEnum(symbol_index)]; sym.setUndefined(true); sym.setGlobal(true); @@ -680,13 +671,11 @@ pub fn addOrUpdateImport( if (type_index) |ty_index| { const gop = try zig_object.imports.getOrPut(gpa, symbol_index); - const module_name = if (lib_name) |l_name| l_name else wasm.host_name; - if (!gop.found_existing) { - zig_object.imported_functions_count += 1; - } + const module_name = if (lib_name) |n| try wasm.internString(n) else wasm.host_name; + if (!gop.found_existing) zig_object.imported_functions_count += 1; gop.value_ptr.* = .{ - .module_name = try zig_object.string_table.insert(gpa, module_name), - .name = try zig_object.string_table.insert(gpa, name), + .module_name = module_name, + .name = try wasm.internString(name), .kind = .{ .function = ty_index }, }; sym.tag = .function; @@ -699,8 +688,7 @@ pub fn addOrUpdateImport( /// such as an exported or imported symbol. /// If the symbol does not yet exist, creates a new one symbol instead /// and then returns the index to it. -pub fn getGlobalSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator, name: []const u8) !Symbol.Index { - const name_index = try zig_object.string_table.insert(gpa, name); +pub fn getGlobalSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator, name_index: Wasm.String) !Symbol.Index { const gop = try zig_object.global_syms.getOrPut(gpa, name_index); if (gop.found_existing) { return gop.value_ptr.*; @@ -840,7 +828,8 @@ pub fn deleteExport( .uav => @panic("TODO: implement Wasm linker code for exporting a constant value"), }; const nav_info = zig_object.navs.getPtr(nav_index) orelse return; - if (nav_info.@"export"(zig_object, name.toSlice(&zcu.intern_pool))) |sym_index| { + const name_interned = wasm.getExistingString(name.toSlice(&zcu.intern_pool)).?; + if (nav_info.@"export"(zig_object, name_interned)) |sym_index| { const sym = zig_object.symbol(sym_index); nav_info.deleteExport(sym_index); std.debug.assert(zig_object.global_syms.remove(sym.name)); @@ -886,14 +875,13 @@ pub fn updateExports( continue; } - const export_string = exp.opts.name.toSlice(ip); - const sym_index = if (nav_info.@"export"(zig_object, export_string)) |idx| idx else index: { + const export_name = try wasm.internString(exp.opts.name.toSlice(ip)); + const sym_index = if (nav_info.@"export"(zig_object, export_name)) |idx| idx else index: { const sym_index = try zig_object.allocateSymbol(gpa); try nav_info.appendExport(gpa, sym_index); break :index sym_index; }; - const export_name = try zig_object.string_table.insert(gpa, export_string); const sym = zig_object.symbol(sym_index); sym.setGlobal(true); sym.setUndefined(false); @@ -922,7 +910,7 @@ pub fn updateExports( if (exp.opts.visibility == .hidden) { sym.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); } - log.debug(" with name '{s}' - {}", .{ export_string, sym }); + log.debug(" with name '{s}' - {}", .{ wasm.stringSlice(export_name), sym }); try zig_object.global_syms.put(gpa, export_name, sym_index); try wasm.symbol_atom.put(gpa, .{ .file = .zig_object, .index = sym_index }, atom_index); } @@ -1014,7 +1002,7 @@ pub fn putOrGetFuncType(zig_object: *ZigObject, gpa: std.mem.Allocator, func_typ /// This will only be generated if the symbol exists. fn setupErrorsLen(zig_object: *ZigObject, wasm: *Wasm) !void { const gpa = wasm.base.comp.gpa; - const sym_index = zig_object.findGlobalSymbol("__zig_errors_len") orelse return; + const sym_index = zig_object.global_syms.get(wasm.preloaded_strings.__zig_errors_len) orelse return; const errors_len = 1 + wasm.base.comp.zcu.?.intern_pool.global_error_set.getNamesFromMainThread().len; // overwrite existing atom if it already exists (maybe the error set has increased) @@ -1045,11 +1033,6 @@ fn setupErrorsLen(zig_object: *ZigObject, wasm: *Wasm) !void { try atom.code.writer(gpa).writeInt(u16, @intCast(errors_len), .little); } -fn findGlobalSymbol(zig_object: *ZigObject, name: []const u8) ?Symbol.Index { - const offset = zig_object.string_table.getOffset(name) orelse return null; - return zig_object.global_syms.get(offset); -} - /// Initializes symbols and atoms for the debug sections /// Initialization is only done when compiling Zig code. /// When Zig is invoked as a linker instead, the atoms @@ -1082,7 +1065,7 @@ pub fn createDebugSectionForIndex(zig_object: *ZigObject, wasm: *Wasm, index: *? const atom = wasm.getAtomPtr(atom_index); zig_object.symbols.items[sym_index] = .{ .tag = .section, - .name = try zig_object.string_table.put(gpa, name), + .name = try wasm.internString(name), .index = 0, .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), }; @@ -1197,7 +1180,7 @@ pub fn createFunction( const sym_index = try zig_object.allocateSymbol(gpa); const sym = zig_object.symbol(sym_index); sym.tag = .function; - sym.name = try zig_object.string_table.insert(gpa, symbol_name); + sym.name = try wasm.internString(symbol_name); const type_index = try zig_object.putOrGetFuncType(gpa, func_ty); sym.index = try zig_object.appendFunction(gpa, .{ .type_index = type_index }); @@ -1244,7 +1227,6 @@ const Dwarf = @import("../Dwarf.zig"); const InternPool = @import("../../InternPool.zig"); const Liveness = @import("../../Liveness.zig"); const Zcu = @import("../../Zcu.zig"); -const StringTable = @import("../StringTable.zig"); const Symbol = @import("Symbol.zig"); const Type = @import("../../Type.zig"); const Value = @import("../../Value.zig"); |
