diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index a06d650b39..66e49e7ad5 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -34,6 +34,8 @@ pub const base_tag = link.File.Tag.wasm; pub const DeclBlock = Atom; base: link.File, +/// Output name of the file +name: []const u8, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. llvm_object: ?*LlvmObject = null, /// When importing objects from the host environment, a name must be supplied. @@ -156,6 +158,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option // TODO: read the file and keep valid parts instead of truncating const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true }); wasm_bin.base.file = file; + wasm_bin.name = sub_path; try file.writeAll(&(wasm.magic ++ wasm.version)); @@ -170,7 +173,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }; const symbol = try wasm_bin.symbols.addOne(allocator); symbol.* = .{ - .name = "__stack_pointer", + .name = try allocator.dupeZ(u8, "__stack_pointer"), .tag = .global, .flags = 0, .index = 0, @@ -188,6 +191,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm { .file = null, .allocator = gpa, }, + .name = undefined, }; const use_llvm = build_options.have_llvm and options.use_llvm; const use_stage1 = build_options.is_stage1 and options.use_stage1; @@ -233,6 +237,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { .file = object_index, .index = sym_index, }; + const sym_name = std.mem.sliceTo(symbol.name, 0); if (symbol.isLocal()) { if (symbol.isUndefined()) { @@ -247,21 +252,23 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { // 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)); + const maybe_existing = try self.globals.getOrPut(self.base.allocator, sym_name); 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); + const existing_file_path = if (existing_loc.file) |file| blk: { + break :blk self.objects.items[file].name; + } else self.name; + 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(" first definition in '{s}'", .{existing_file_path}); log.err(" next definition in '{s}'", .{object.name}); return error.SymbolCollision; } @@ -271,11 +278,11 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { // 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}); + log.info(" old definition in '{s}'", .{existing_file_path}); + log.info(" new 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); + try self.globals.put(self.base.allocator, sym_name, location); } } @@ -302,6 +309,12 @@ pub fn deinit(self: *Wasm) void { object.deinit(gpa); } + for (self.symbols.items) |symbol| { + if (symbol.tag != .dead) { + gpa.free(mem.sliceTo(symbol.name, 0)); + } + } + self.decls.deinit(gpa); self.symbols.deinit(gpa); self.symbols_free_list.deinit(gpa); @@ -441,7 +454,7 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void { const atom: *Atom = &decl.link.wasm; atom.size = @intCast(u32, code.len); atom.alignment = decl.ty.abiAlignment(self.base.options.target); - self.symbols.items[atom.sym_index].name = decl.name; + self.symbols.items[atom.sym_index].name = try self.base.allocator.dupeZ(u8, std.mem.sliceTo(decl.name, 0)); try atom.code.appendSlice(self.base.allocator, code); } @@ -449,8 +462,10 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void { /// and then append it as a 'contained' atom onto the Decl. pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type) !u32 { assert(ty.zigTypeTag() != .Fn); // cannot create local symbols for functions + const local_index = decl.link.wasm.locals.items.len; + const name = try std.fmt.allocPrintZ(self.base.allocator, "__unnamed_{s}_{d}", .{ decl.name, local_index }); var symbol: Symbol = .{ - .name = "unnamed_local", + .name = name, .flags = 0, .tag = .data, .index = undefined, @@ -494,7 +509,7 @@ pub fn getDeclVAddr( const atom = decl.link.wasm.symbolAtom(symbol_index); const is_wasm32 = self.base.options.target.cpu.arch == .wasm32; if (ty.zigTypeTag() == .Fn) { - std.debug.assert(addend == 0); // addend not allowed for function relocations + assert(addend == 0); // addend not allowed for function relocations // We found a function pointer, so add it to our table, // as function pointers are not allowed to be stored inside the data section. // They are instead stored in a function table which are called by index. @@ -543,15 +558,13 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { self.symbols.items[atom.sym_index].tag = .dead; // to ensure it does not end in the names section for (atom.locals.items) |local_atom| { self.symbols.items[local_atom.sym_index].tag = .dead; // also for any local symbol + // self.base.allocator.free(mem.sliceTo(self.symbols.items[local_atom.sym_index].name, 0)); self.symbols_free_list.append(self.base.allocator, local_atom.sym_index) catch {}; } + // self.base.allocator.free(mem.sliceTo(self.symbols.items[atom.sym_index].name, 0)); if (decl.isExtern()) { - const import = self.imports.fetchRemove(.{ .file = null, .index = atom.sym_index }).?.value; - switch (import.kind) { - .function => self.imported_functions_count -= 1, - else => unreachable, - } + assert(self.imports.remove(.{ .file = null, .index = atom.sym_index })); } atom.deinit(self.base.allocator); @@ -577,16 +590,18 @@ fn mapFunctionTable(self: *Wasm) void { fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void { const symbol_index = decl.link.wasm.sym_index; const symbol: *Symbol = &self.symbols.items[symbol_index]; - symbol.name = decl.name; + const decl_name = mem.sliceTo(decl.name, 0); + symbol.name = try self.base.allocator.dupeZ(u8, decl_name); symbol.setUndefined(true); + // also add it as a global so it can be resolved + try self.globals.put(self.base.allocator, decl_name, .{ .file = null, .index = symbol_index }); switch (decl.ty.zigTypeTag()) { .Fn => { 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); + break :blk mem.sliceTo(lib_name, 0); } else self.host_name; if (!gop.found_existing) { - self.imported_functions_count += 1; gop.value_ptr.* = .{ .module_name = module_name, .name = std.mem.span(symbol.name), @@ -608,9 +623,8 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void { const symbol: *Symbol = &self.symbols.items[atom.sym_index]; const final_index: u32 = switch (kind) { .function => |fn_data| result: { - const type_index = fn_data.type_index; const index = @intCast(u32, self.functions.items.len + self.imported_functions_count); - try self.functions.append(self.base.allocator, .{ .type_index = type_index }); + try self.functions.append(self.base.allocator, .{ .type_index = fn_data.type_index }); symbol.tag = .function; symbol.index = index; @@ -641,6 +655,7 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void { break :blk index; }; const info_index = @intCast(u32, self.segment_info.items.len); + // TODO: Add mutables global decls to .bss section instead const segment_name = try std.mem.concat(self.base.allocator, u8, &.{ ".rodata.", std.mem.span(symbol.name), @@ -684,7 +699,7 @@ fn allocateAtoms(self: *Wasm) !void { offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment); atom.offset = offset; log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{ - self.symbols.items[atom.sym_index].name, + (SymbolLoc{ .file = atom.file, .index = atom.sym_index }).getSymbol(self).name, offset, offset + atom.size, atom.size, @@ -695,7 +710,7 @@ fn allocateAtoms(self: *Wasm) !void { } } -fn setupImports(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 @@ -708,7 +723,7 @@ fn setupImports(self: *Wasm) void { } 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); + const import = self.objects.items[symbol_loc.file.?].findImport(symbol.tag.externalType(), symbol.index); // TODO: De-duplicate imports try self.imports.putNoClobber(self.base.allocator, symbol_loc, import); } @@ -737,6 +752,9 @@ fn setupImports(self: *Wasm) void { else => unreachable, } } + self.imported_functions_count = function_index; + self.imported_globals_count = global_index; + self.imported_tables_count = table_index; } /// Takes the global, function and table section from each linked object file @@ -761,12 +779,13 @@ fn mergeSections(self: *Wasm) !void { const object = self.objects.items[sym_loc.file.?]; const symbol = &object.symtable[sym_loc.index]; - if (symbol.isUndefined()) { + if (symbol.isUndefined() or (symbol.tag != .function and symbol.tag != .global and symbol.tag != .table)) { // Skip undefined symbols as they go in the `import` section + // Also skip symbols that do not need to have a section merged. continue; } - const offset = object.importedCountByKind(symbol.externalType()); + const offset = object.importedCountByKind(symbol.tag.externalType()); const index = symbol.index - offset; switch (symbol.tag) { .function => { @@ -776,7 +795,7 @@ fn mergeSections(self: *Wasm) !void { }, .global => { const original_global = object.globals[index]; - symbol.index = @intCast(u32, self.globals.items.len) + self.imported_globals_count; + symbol.index = @intCast(u32, self.wasm_globals.items.len) + self.imported_globals_count; try self.wasm_globals.append(self.base.allocator, original_global); }, .table => { @@ -790,7 +809,7 @@ fn mergeSections(self: *Wasm) !void { 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}); + log.debug("Merged ({d}) tables", .{self.tables.items.len}); } /// Merges function types of all object files into the final @@ -811,13 +830,13 @@ fn mergeTypes(self: *Wasm) !void { 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]; + const import: *wasm.Import = self.imports.getPtr(sym_loc).?; + const original_type = object.func_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]); + func.type_index = try self.putOrGetFuncType(object.func_types[func.type_index]); } } log.debug("Completed merging and deduplicating types. Total count: ({d})", .{self.func_types.items.len}); @@ -835,7 +854,11 @@ fn setupExports(self: *Wasm) !void { const symbol = sym_loc.getSymbol(self); if (!symbol.isExported()) continue; - const exp: wasm.Export = .{ .name = symbol.name, .kind = symbol.externalType(), .index = symbol.index }; + const exp: wasm.Export = .{ + .name = mem.sliceTo(symbol.name, 0), + .kind = symbol.tag.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); } @@ -948,6 +971,41 @@ fn setupMemory(self: *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(self: *Wasm, object_index: u16, relocatable_index: u32) !u32 { + const object: Object = self.objects.items[object_index]; + const relocatable_data = object.relocatable_data[relocatable_index]; + const index = @intCast(u32, self.segments.items.len); + + switch (relocatable_data.type) { + .data => { + const segment_info = object.segment_info[relocatable_data.index]; + const result = try self.data_segments.getOrPut(self.base.allocator, segment_info.outputName()); + if (!result.found_existing) { + result.value_ptr.* = index; + try self.segments.append(self.base.allocator, .{ + .alignment = 1, + .size = 0, + .offset = 0, + }); + return index; + } else return result.value_ptr.*; + }, + .code => return self.code_section_index orelse blk: { + self.code_section_index = index; + try self.segments.append(self.base.allocator, .{ + .alignment = 1, + .size = 0, + .offset = 0, + }); + break :blk index; + }, + .custom => return error.@"TODO: Custom section relocations for wasm", + } +} + fn resetState(self: *Wasm) void { for (self.segment_info.items) |*segment_info| { self.base.allocator.free(segment_info.name); @@ -1015,7 +1073,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // 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(); - self.setupImports(); + try self.setupImports(); var decl_it = self.decls.keyIterator(); while (decl_it.next()) |decl| { if (decl.*.isExtern()) continue; @@ -1050,7 +1108,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - + log.debug("Writing type section. Count: ({d})", .{self.func_types.items.len}); for (self.func_types.items) |func_type| { try leb.writeULEB128(writer, wasm.function_type); try leb.writeULEB128(writer, @intCast(u32, func_type.params.len)); @@ -1095,8 +1153,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { var it = self.imports.iterator(); while (it.next()) |entry| { - const import_symbol = self.symbols.items[entry.key_ptr.*]; - std.debug.assert(import_symbol.isUndefined()); + assert(entry.key_ptr.*.getSymbol(self).isUndefined()); const import = entry.value_ptr.*; try emitImport(writer, import); } @@ -1207,7 +1264,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { .Fn => { const target = exprt.exported_decl.link.wasm.sym_index; const target_symbol = self.symbols.items[target]; - std.debug.assert(target_symbol.tag == .function); + assert(target_symbol.tag == .function); // Type of the export try writer.writeByte(wasm.externalKind(.function)); // Exported function index @@ -1323,8 +1380,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { try writer.writeByteNTimes(0, diff); current_offset += diff; } - std.debug.assert(current_offset == atom.offset); - std.debug.assert(atom.code.items.len == atom.size); + assert(current_offset == atom.offset); + assert(atom.code.items.len == atom.size); try writer.writeAll(atom.code.items); current_offset += atom.size; @@ -1335,10 +1392,12 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // segments are aligned. if (current_offset != segment.size) { try writer.writeByteNTimes(0, segment.size - current_offset); + current_offset += segment.size - current_offset; } break; } } + assert(current_offset == segment.size); } try writeVecSectionHeader( @@ -1371,8 +1430,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { for (self.symbols.items) |symbol| { switch (symbol.tag) { - .function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = std.mem.sliceTo(symbol.name, 0) }), - .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = std.mem.sliceTo(symbol.name, 0) }), + .function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = mem.sliceTo(symbol.name, 0) }), + .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = mem.sliceTo(symbol.name, 0) }), else => {}, } } diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index ab72510425..ec8b5f5b49 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -23,6 +23,9 @@ alignment: u32, /// Offset into the section where the atom lives, this already accounts /// for alignment. offset: u32, +/// Represents the index of the file this atom was generated from. +/// This is 'null' when the atom was generated by a Decl from Zig code. +file: ?u16, /// Next atom in relation to this atom. /// When null, this atom is the last atom @@ -38,6 +41,7 @@ locals: std.ArrayListUnmanaged(Atom) = .{}, /// Represents a default empty wasm `Atom` pub const empty: Atom = .{ .alignment = 0, + .file = null, .next = null, .offset = 0, .prev = null, @@ -93,16 +97,17 @@ 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.managed_symbols.items[self.sym_index]; + const loc: Wasm.SymbolLoc = .{ .file = self.file, .index = self.sym_index }; + const symbol = loc.getSymbol(wasm_bin).*; log.debug("Resolving relocs in atom '{s}' count({d})", .{ symbol.name, self.relocs.items.len, }); for (self.relocs.items) |reloc| { - const value = try relocationValue(reloc, wasm_bin); + const value = try self.relocationValue(reloc, wasm_bin); log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{ - wasm_bin.managed_symbols.items[reloc.index].name, + (Wasm.SymbolLoc{ .file = self.file, .index = reloc.index }).getSymbol(wasm_bin).name, symbol.name, reloc.offset, value, @@ -138,8 +143,9 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void { /// From a given `relocation` will return the new value to be written. /// 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.managed_symbols.items[relocation.index]; +fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wasm) !u64 { + const target_loc: Wasm.SymbolLoc = .{ .file = self.file, .index = relocation.index }; + const symbol = target_loc.getSymbol(wasm_bin).*; 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 3dc4a91a63..fdc80e608c 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -131,9 +131,9 @@ pub fn deinit(self: *Object, gpa: Allocator) void { /// Finds the import within the list of imports from a given kind and index of that kind. /// Asserts the import exists -pub fn findImport(self: *const Object, import_kind: std.wasm.ExternalKind, index: u32) *std.wasm.Import { +pub fn findImport(self: *const Object, import_kind: std.wasm.ExternalKind, index: u32) std.wasm.Import { var i: u32 = 0; - return for (self.imports) |*import| { + return for (self.imports) |import| { if (std.meta.activeTag(import.kind) == import_kind) { if (i == index) return import; i += 1; @@ -681,7 +681,7 @@ fn Parser(comptime ReaderType: type) type { }, else => { symbol.index = try leb.readULEB128(u32, reader); - var maybe_import: ?*std.wasm.Import = null; + var maybe_import: ?std.wasm.Import = null; const is_undefined = symbol.isUndefined(); if (is_undefined) { @@ -791,10 +791,14 @@ pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin .kind = relocatable_data.getSymbolKind(), .index = @intCast(u32, relocatable_data.index), }) orelse continue; // encountered a segment we do not create an atom for - const final_index = try wasm_bin.getMatchingSegment(gpa, object_index, @intCast(u32, index)); + const final_index = try wasm_bin.getMatchingSegment(object_index, @intCast(u32, index)); - const atom = try Atom.create(gpa); - errdefer atom.deinit(gpa); + const atom = try gpa.create(Atom); + atom.* = Atom.empty; + errdefer { + atom.deinit(gpa); + gpa.destroy(atom); + } try wasm_bin.managed_atoms.append(gpa, atom); atom.file = object_index; @@ -803,19 +807,23 @@ pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin atom.sym_index = sym_index; const relocations: []types.Relocation = self.relocations.get(relocatable_data.section_index) orelse &.{}; - for (relocations) |*relocation| { + for (relocations) |relocation| { if (isInbetween(relocatable_data.offset, atom.size, relocation.offset)) { // set the offset relative to the offset of the segment itself, // rather than within the entire section. - relocation.offset -= relocatable_data.offset; - try atom.relocs.append(gpa, relocation.*); + var reloc = relocation; + reloc.offset -= relocatable_data.offset; + try atom.relocs.append(gpa, reloc); - if (relocation.isTableIndex()) { - try wasm_bin.elements.appendSymbol(gpa, .{ - .file = object_index, - .sym_index = relocation.index, - }); - } + // TODO: Automatically append the target symbol to the indirect + // function table when the relocation is a table index. + // + // if (relocation.isTableIndex()) { + // try wasm_bin.elements.appendSymbol(gpa, .{ + // .file = object_index, + // .sym_index = relocation.index, + // }); + // } } } diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index 55e9851263..d89c5fb19f 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -78,7 +78,7 @@ pub const Flag = enum(u32) { pub fn requiresImport(self: Symbol) bool { if (!self.isUndefined()) return false; if (self.isWeak()) return false; - if (self.kind == .data) return false; + if (self.tag == .data) return false; // if (self.isDefined() and self.isWeak()) return true; //TODO: Only when building shared lib return true;