From 41199bba4bae4783634ee52ecd8d111f8f82fd4c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 13 Dec 2022 19:19:05 +0100 Subject: [PATCH 1/8] wasm-linker: Create separate path for one-shot When invoking the self-hosted linker using `-fno-LLD` while using the LLVM backend or invoking it as a linker, we create a seperate path. This path will link the object file generated by LLVM and the supplied object files just once, allowing to simplify the implementation between incremental and regular linking. --- src/link/Wasm.zig | 537 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 533 insertions(+), 4 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 74a303b8c7..e5bce8bda6 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -311,13 +311,23 @@ pub const StringTable = struct { pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Wasm { assert(options.target.ofmt == .wasm); - if (build_options.have_llvm and options.use_llvm) { + if (build_options.have_llvm and options.use_llvm and options.use_lld) { return createEmpty(allocator, options); } const wasm_bin = try createEmpty(allocator, options); errdefer wasm_bin.base.destroy(); + // We are not using LLD at this point, so ensure we set the intermediary basename + if (build_options.have_llvm and options.use_llvm and options.module != null) { + // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`, + // we also want to put the intermediary object file in the cache while the + // main emit directory is the cwd. + wasm_bin.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{ + options.emit.?.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), + }); + } + // 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; @@ -2134,7 +2144,6 @@ pub fn createDebugSectionForIndex(wasm: *Wasm, index: *?u32, name: []const u8) ! const new_index = @intCast(u32, wasm.segments.items.len); index.* = new_index; try wasm.appendDummySegment(); - // _ = index; const sym_index = wasm.symbols_free_list.popOrNull() orelse idx: { const tmp_index = @intCast(u32, wasm.symbols.items.len); @@ -2202,14 +2211,17 @@ pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) lin } return; } + if (build_options.have_llvm and wasm.base.options.use_lld) { return wasm.linkWithLLD(comp, prog_node); + } else if (build_options.have_llvm and wasm.base.options.use_llvm and !wasm.base.options.use_lld) { + return wasm.linkWithZld(comp, prog_node); } else { return wasm.flushModule(comp, prog_node); } } -pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { +fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -2219,7 +2231,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod } } - var sub_prog_node = prog_node.start("WASM Flush", 0); + var sub_prog_node = prog_node.start("Wasm Flush", 0); sub_prog_node.activate(); defer sub_prog_node.end(); @@ -2726,6 +2738,523 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod try wasm.base.file.?.writevAll(&iovec); } +pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { + const tracy = trace(@src()); + defer tracy.end(); + + if (build_options.have_llvm) { + if (wasm.llvm_object) |llvm_object| { + return try llvm_object.flushModule(comp, prog_node); + } + } + + var sub_prog_node = prog_node.start("Wasm Flush", 0); + sub_prog_node.activate(); + defer sub_prog_node.end(); + + // ensure the error names table is populated when an error name is referenced + try wasm.populateErrorNameTable(); + + // The amount of sections that will be written + var section_count: u32 = 0; + // Index of the code section. Used to tell relocation table where the section lives. + var code_section_index: ?u32 = null; + // Index of the data section. Used to tell relocation table where the section lives. + var data_section_index: ?u32 = null; + + // Used for all temporary memory allocated during flushin + var arena_instance = std.heap.ArenaAllocator.init(wasm.base.allocator); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + // Positional arguments to the linker such as object files and static archives. + var positionals = std.ArrayList([]const u8).init(arena); + try positionals.ensureUnusedCapacity(wasm.base.options.objects.len); + + for (wasm.base.options.objects) |object| { + positionals.appendAssumeCapacity(object.path); + } + + for (comp.c_object_table.keys()) |c_object| { + try positionals.append(c_object.status.success.object_path); + } + + if (comp.compiler_rt_lib) |lib| { + try positionals.append(lib.full_object_path); + } + + try wasm.parseInputFiles(positionals.items); + + for (wasm.objects.items) |_, object_index| { + try wasm.resolveSymbolsInObject(@intCast(u16, object_index)); + } + + var emit_features_count: u32 = 0; + var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; + try wasm.validateFeatures(&enabled_features, &emit_features_count); + try wasm.resolveSymbolsInArchives(); + try wasm.checkUndefinedSymbols(); + + // When we finish/error we reset the state of the linker + // So we can rebuild the binary file on each incremental update + defer wasm.resetState(); + try wasm.setupStart(); + try wasm.setupImports(); + if (wasm.base.options.module) |mod| { + var decl_it = wasm.decls.keyIterator(); + while (decl_it.next()) |decl_index_ptr| { + const decl = mod.declPtr(decl_index_ptr.*); + if (decl.isExtern()) continue; + const atom = &decl.*.link.wasm; + if (decl.ty.zigTypeTag() == .Fn) { + try wasm.parseAtom(atom, .{ .function = decl.fn_link.wasm }); + } else if (decl.getVariable()) |variable| { + if (!variable.is_mutable) { + try wasm.parseAtom(atom, .{ .data = .read_only }); + } else if (variable.init.isUndefDeep()) { + try wasm.parseAtom(atom, .{ .data = .uninitialized }); + } else { + try wasm.parseAtom(atom, .{ .data = .initialized }); + } + } else { + try wasm.parseAtom(atom, .{ .data = .read_only }); + } + + // also parse atoms for a decl's locals + for (atom.locals.items) |*local_atom| { + try wasm.parseAtom(local_atom, .{ .data = .read_only }); + } + } + + if (wasm.dwarf) |*dwarf| { + try dwarf.flushModule(wasm.base.options.module.?); + } + } + + for (wasm.objects.items) |*object, object_index| { + try object.parseIntoAtoms(wasm.base.allocator, @intCast(u16, object_index), wasm); + } + + try wasm.allocateAtoms(); + try wasm.setupMemory(); + wasm.mapFunctionTable(); + try wasm.mergeSections(); + try wasm.mergeTypes(); + try wasm.setupExports(); + + const header_size = 5 + 1; + const is_obj = wasm.base.options.output_mode == .Obj or (!wasm.base.options.use_llvm and wasm.base.options.use_lld); + + var binary_bytes = std.ArrayList(u8).init(wasm.base.allocator); + defer binary_bytes.deinit(); + const binary_writer = binary_bytes.writer(); + + // We write the magic bytes at the end so they will only be written + // if everything succeeded as expected. So populate with 0's for now. + try binary_writer.writeAll(&[_]u8{0} ** 8); + // (Re)set file pointer to 0 + try wasm.base.file.?.setEndPos(0); + try wasm.base.file.?.seekTo(0); + + // Type section + if (wasm.func_types.items.len != 0) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len}); + for (wasm.func_types.items) |func_type| { + try leb.writeULEB128(binary_writer, std.wasm.function_type); + try leb.writeULEB128(binary_writer, @intCast(u32, func_type.params.len)); + for (func_type.params) |param_ty| { + try leb.writeULEB128(binary_writer, std.wasm.valtype(param_ty)); + } + try leb.writeULEB128(binary_writer, @intCast(u32, func_type.returns.len)); + for (func_type.returns) |ret_ty| { + try leb.writeULEB128(binary_writer, std.wasm.valtype(ret_ty)); + } + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .type, + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @intCast(u32, wasm.func_types.items.len), + ); + section_count += 1; + } + + // Import section + const import_memory = wasm.base.options.import_memory or is_obj; + const import_table = wasm.base.options.import_table or is_obj; + if (wasm.imports.count() != 0 or import_memory or import_table) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + + // import table is always first table so emit that first + if (import_table) { + const table_imp: types.Import = .{ + .module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name), + .name = try wasm.string_table.put(wasm.base.allocator, "__indirect_function_table"), + .kind = .{ + .table = .{ + .limits = .{ + .min = @intCast(u32, wasm.function_table.count()), + .max = null, + }, + .reftype = .funcref, + }, + }, + }; + try wasm.emitImport(binary_writer, table_imp); + } + + var it = wasm.imports.iterator(); + while (it.next()) |entry| { + assert(entry.key_ptr.*.getSymbol(wasm).isUndefined()); + const import = entry.value_ptr.*; + try wasm.emitImport(binary_writer, import); + } + + if (import_memory) { + const mem_name = if (is_obj) "__linear_memory" else "memory"; + const mem_imp: types.Import = .{ + .module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name), + .name = try wasm.string_table.put(wasm.base.allocator, mem_name), + .kind = .{ .memory = wasm.memories.limits }, + }; + try wasm.emitImport(binary_writer, mem_imp); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .import, + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @intCast(u32, wasm.imports.count() + @boolToInt(import_memory) + @boolToInt(import_table)), + ); + section_count += 1; + } + + // Function section + if (wasm.functions.count() != 0) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + for (wasm.functions.values()) |function| { + try leb.writeULEB128(binary_writer, function.type_index); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .function, + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @intCast(u32, wasm.functions.count()), + ); + section_count += 1; + } + + // Table section + const export_table = wasm.base.options.export_table; + if (!import_table and wasm.function_table.count() != 0) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + + try leb.writeULEB128(binary_writer, std.wasm.reftype(.funcref)); + try emitLimits(binary_writer, .{ + .min = @intCast(u32, wasm.function_table.count()) + 1, + .max = null, + }); + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .table, + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @as(u32, 1), + ); + section_count += 1; + } + + // Memory section + if (!import_memory) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + + try emitLimits(binary_writer, wasm.memories.limits); + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .memory, + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @as(u32, 1), // wasm currently only supports 1 linear memory segment + ); + section_count += 1; + } + + // Global section (used to emit stack pointer) + if (wasm.wasm_globals.items.len > 0) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + + for (wasm.wasm_globals.items) |global| { + try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype)); + try binary_writer.writeByte(@boolToInt(global.global_type.mutable)); + try emitInit(binary_writer, global.init); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .global, + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @intCast(u32, wasm.wasm_globals.items.len), + ); + section_count += 1; + } + + // Export section + if (wasm.exports.items.len != 0 or export_table or !import_memory) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + + for (wasm.exports.items) |exp| { + const name = wasm.string_table.get(exp.name); + try leb.writeULEB128(binary_writer, @intCast(u32, name.len)); + try binary_writer.writeAll(name); + try leb.writeULEB128(binary_writer, @enumToInt(exp.kind)); + try leb.writeULEB128(binary_writer, exp.index); + } + + if (export_table) { + try leb.writeULEB128(binary_writer, @intCast(u32, "__indirect_function_table".len)); + try binary_writer.writeAll("__indirect_function_table"); + try binary_writer.writeByte(std.wasm.externalKind(.table)); + try leb.writeULEB128(binary_writer, @as(u32, 0)); // function table is always the first table + } + + if (!import_memory) { + try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len)); + try binary_writer.writeAll("memory"); + try binary_writer.writeByte(std.wasm.externalKind(.memory)); + try leb.writeULEB128(binary_writer, @as(u32, 0)); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .@"export", + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @intCast(u32, wasm.exports.items.len) + @boolToInt(export_table) + @boolToInt(!import_memory), + ); + section_count += 1; + } + + // element section (function table) + if (wasm.function_table.count() > 0) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + + var flags: u32 = 0x2; // Yes we have a table + try leb.writeULEB128(binary_writer, flags); + try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols + try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid + try leb.writeULEB128(binary_writer, @as(u8, 0)); + try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count())); + var symbol_it = wasm.function_table.keyIterator(); + while (symbol_it.next()) |symbol_loc_ptr| { + try leb.writeULEB128(binary_writer, symbol_loc_ptr.*.getSymbol(wasm).index); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .element, + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @as(u32, 1), + ); + section_count += 1; + } + + // Code section + var code_section_size: u32 = 0; + if (wasm.code_section_index) |code_index| { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + var atom: *Atom = wasm.atoms.get(code_index).?.getFirst(); + + // The code section must be sorted in line with the function order. + var sorted_atoms = try std.ArrayList(*Atom).initCapacity(wasm.base.allocator, wasm.functions.count()); + defer sorted_atoms.deinit(); + + while (true) { + if (!is_obj) { + atom.resolveRelocs(wasm); + } + sorted_atoms.appendAssumeCapacity(atom); + atom = atom.next orelse break; + } + + const atom_sort_fn = struct { + fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool { + const lhs_sym = lhs.symbolLoc().getSymbol(ctx); + const rhs_sym = rhs.symbolLoc().getSymbol(ctx); + return lhs_sym.index < rhs_sym.index; + } + }.sort; + + std.sort.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn); + + for (sorted_atoms.items) |sorted_atom| { + try leb.writeULEB128(binary_writer, sorted_atom.size); + try binary_writer.writeAll(sorted_atom.code.items); + } + + code_section_size = @intCast(u32, binary_bytes.items.len - header_offset - header_size); + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .code, + code_section_size, + @intCast(u32, wasm.functions.count()), + ); + code_section_index = section_count; + section_count += 1; + } + + // Data section + if (wasm.data_segments.count() != 0) { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + + var it = wasm.data_segments.iterator(); + var segment_count: u32 = 0; + while (it.next()) |entry| { + // do not output 'bss' section unless we import memory and therefore + // want to guarantee the data is zero initialized + if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue; + segment_count += 1; + const atom_index = entry.value_ptr.*; + var atom: *Atom = wasm.atoms.getPtr(atom_index).?.*.getFirst(); + const segment = wasm.segments.items[atom_index]; + + // flag and index to memory section (currently, there can only be 1 memory section in wasm) + try leb.writeULEB128(binary_writer, @as(u32, 0)); + // offset into data section + try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) }); + try leb.writeULEB128(binary_writer, segment.size); + + // fill in the offset table and the data segments + var current_offset: u32 = 0; + while (true) { + if (!is_obj) { + atom.resolveRelocs(wasm); + } + + // Pad with zeroes to ensure all segments are aligned + if (current_offset != atom.offset) { + const diff = atom.offset - current_offset; + try binary_writer.writeByteNTimes(0, diff); + current_offset += diff; + } + assert(current_offset == atom.offset); + assert(atom.code.items.len == atom.size); + try binary_writer.writeAll(atom.code.items); + + current_offset += atom.size; + if (atom.next) |next| { + atom = next; + } else { + // also pad with zeroes when last atom to ensure + // segments are aligned. + if (current_offset != segment.size) { + try binary_writer.writeByteNTimes(0, segment.size - current_offset); + current_offset += segment.size - current_offset; + } + break; + } + } + assert(current_offset == segment.size); + } + + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .data, + @intCast(u32, binary_bytes.items.len - header_offset - header_size), + @intCast(u32, segment_count), + ); + data_section_index = section_count; + section_count += 1; + } + + if (is_obj) { + // relocations need to point to the index of a symbol in the final symbol table. To save memory, + // we never store all symbols in a single table, but store a location reference instead. + // This means that for a relocatable object file, we need to generate one and provide it to the relocation sections. + var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena); + try wasm.emitLinkSection(&binary_bytes, &symbol_table); + if (code_section_index) |code_index| { + try wasm.emitCodeRelocations(&binary_bytes, code_index, symbol_table); + } + if (data_section_index) |data_index| { + try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table); + } + } else if (!wasm.base.options.strip) { + try wasm.emitNameSection(&binary_bytes, arena); + } + + if (!wasm.base.options.strip) { + if (wasm.dwarf) |*dwarf| { + const mod = wasm.base.options.module.?; + try dwarf.writeDbgAbbrev(); + // for debug info and ranges, the address is always 0, + // as locations are always offsets relative to 'code' section. + try dwarf.writeDbgInfoHeader(mod, 0, code_section_size); + try dwarf.writeDbgAranges(0, code_section_size); + try dwarf.writeDbgLineHeader(); + } + + var debug_bytes = std.ArrayList(u8).init(wasm.base.allocator); + 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| { + var atom = wasm.atoms.get(index).?.getFirst(); + while (true) { + atom.resolveRelocs(wasm); + try debug_bytes.appendSlice(atom.code.items); + atom = atom.next orelse break; + } + try emitDebugSection(&binary_bytes, debug_bytes.items, item.name); + debug_bytes.clearRetainingCapacity(); + } + } + + try emitProducerSection(&binary_bytes); + if (emit_features_count > 0) { + try emitFeaturesSection(&binary_bytes, &enabled_features, emit_features_count); + } + } + + // Only when writing all sections executed properly we write the magic + // bytes. This allows us to easily detect what went wrong while generating + // the final binary. + mem.copy(u8, binary_bytes.items, &(std.wasm.magic ++ std.wasm.version)); + + // finally, write the entire binary into the file. + var iovec = [_]std.os.iovec_const{.{ + .iov_base = binary_bytes.items.ptr, + .iov_len = binary_bytes.items.len, + }}; + try wasm.base.file.?.writevAll(&iovec); +} + fn emitDebugSection(binary_bytes: *std.ArrayList(u8), data: []const u8, name: []const u8) !void { if (data.len == 0) return; const header_offset = try reserveCustomSectionHeader(binary_bytes); From f95549ddc71f75f3bf15f54f8d03a1a367d0db37 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 14 Dec 2022 16:55:23 +0100 Subject: [PATCH 2/8] wasm-linker: Export data symbols as global When a data symbol is required to be exported, we instead generate a global that will be exported. This global is immutable and contains the address of the data symbol. --- src/link/Wasm.zig | 41 +++++++++++++++++++++++++++++++++++++--- src/link/Wasm/Symbol.zig | 2 +- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e5bce8bda6..e89422d0ae 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -112,6 +112,8 @@ func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{}, functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.wasm.Func) = .{}, /// Output global section wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{}, +/// Global symbols for exported data symbols +address_globals: std.ArrayListUnmanaged(SymbolLoc) = .{}, /// Memory section memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } }, /// Output table section @@ -839,6 +841,7 @@ pub fn deinit(wasm: *Wasm) void { wasm.func_types.deinit(gpa); wasm.functions.deinit(gpa); wasm.wasm_globals.deinit(gpa); + wasm.address_globals.deinit(gpa); wasm.function_table.deinit(gpa); wasm.tables.deinit(gpa); wasm.exports.deinit(gpa); @@ -1804,7 +1807,15 @@ fn setupExports(wasm: *Wasm) !void { if (sym_loc.file == null) break :blk symbol.name; break :blk try wasm.string_table.put(wasm.base.allocator, sym_name); }; - const exp: types.Export = .{ + const exp: types.Export = if (symbol.tag == .data) exp: { + const global_index = @intCast(u32, wasm.wasm_globals.items.len + wasm.address_globals.items.len); + try wasm.address_globals.append(wasm.base.allocator, sym_loc); + break :exp .{ + .name = export_name, + .kind = .global, + .index = global_index, + }; + } else .{ .name = export_name, .kind = symbol.tag.externalType(), .index = symbol.index, @@ -2473,10 +2484,22 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l if (wasm.wasm_globals.items.len > 0) { const header_offset = try reserveVecSectionHeader(&binary_bytes); + var global_count: u32 = 0; for (wasm.wasm_globals.items) |global| { try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype)); try binary_writer.writeByte(@boolToInt(global.global_type.mutable)); try emitInit(binary_writer, global.init); + global_count += 1; + } + + for (wasm.address_globals.items) |sym_loc| { + const atom = wasm.symbol_atom.get(sym_loc).?; + try binary_writer.writeByte(std.wasm.valtype(.i32)); + try binary_writer.writeByte(0); // immutable + try emitInit(binary_writer, .{ + .i32_const = @bitCast(i32, atom.offset), + }); + global_count += 1; } try writeVecSectionHeader( @@ -2484,7 +2507,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l header_offset, .global, @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @intCast(u32, wasm.wasm_globals.items.len), + @intCast(u32, global_count), ); section_count += 1; } @@ -2990,10 +3013,22 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod if (wasm.wasm_globals.items.len > 0) { const header_offset = try reserveVecSectionHeader(&binary_bytes); + var global_count: u32 = 0; for (wasm.wasm_globals.items) |global| { try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype)); try binary_writer.writeByte(@boolToInt(global.global_type.mutable)); try emitInit(binary_writer, global.init); + global_count += 1; + } + + for (wasm.address_globals.items) |sym_loc| { + const atom = wasm.symbol_atom.get(sym_loc).?; + try binary_writer.writeByte(std.wasm.valtype(.i32)); + try binary_writer.writeByte(0); // immutable + try emitInit(binary_writer, .{ + .i32_const = @bitCast(i32, atom.offset), + }); + global_count += 1; } try writeVecSectionHeader( @@ -3001,7 +3036,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod header_offset, .global, @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @intCast(u32, wasm.wasm_globals.items.len), + @intCast(u32, global_count), ); section_count += 1; } diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index d857e6de44..365f4a2c0b 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -38,7 +38,7 @@ pub const Tag = enum { return switch (tag) { .function => .function, .global => .global, - .data => .memory, + .data => unreachable, // Data symbols will generate a global .section => unreachable, // Not an external type .event => unreachable, // Not an external type .dead => unreachable, // Dead symbols should not be referenced From ae106db8897ce3afd38dbfc1c2c811cd2f2de357 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Thu, 15 Dec 2022 20:28:30 +0100 Subject: [PATCH 3/8] wasm-linker: Fix debug info relocations --- src/link/Wasm/Atom.zig | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index 4c1da180bd..4f16a1c2fb 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -204,18 +204,10 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa return @intCast(u32, rel_value); }, .R_WASM_FUNCTION_OFFSET_I32 => { - const target_atom = wasm_bin.symbol_atom.get(target_loc).?; - var current_atom = target_atom.getFirst(); - var offset: u32 = 0; - // TODO: Calculate this during atom allocation, rather than - // this linear calculation. For now it's done here as atoms - // are being sorted after atom allocation, as functions aren't - // merged until later. - while (true) { - offset += 5; // each atom uses 5 bytes to store its body's size - if (current_atom == target_atom) break; - current_atom = current_atom.next.?; - } + const target_atom = wasm_bin.symbol_atom.get(target_loc) orelse { + return @bitCast(u32, @as(i32, -1)); + }; + const offset: u32 = 11 + Wasm.getULEB128Size(target_atom.size); // Header (11 bytes fixed-size) + body size (leb-encoded) const rel_value = @intCast(i32, target_atom.offset + offset) + relocation.addend; return @intCast(u32, rel_value); }, From 476202eec03a2196daab7f9998c556796cc42eca Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 16 Dec 2022 18:31:24 +0100 Subject: [PATCH 4/8] wasm-linker: Fix archive symbols parsing When parsing the table of contents containing the symbols and their positions we initially used the index within the map to retrieve the offset. However, during resizing of the underlaying array this would invalidate those indexes which meant incorrect offsets were being stored for symbols. We now use the current symbol index to also get the index into the symbol position instead. --- src/link/Wasm.zig | 5 ++--- src/link/Wasm/Archive.zig | 15 +++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e89422d0ae..d8aab58ef1 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -625,7 +625,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void { try wasm.resolved_symbols.put(wasm.base.allocator, location, {}); assert(wasm.resolved_symbols.swapRemove(existing_loc)); if (existing_sym.isUndefined()) { - assert(wasm.undefs.swapRemove(sym_name)); + _ = wasm.undefs.swapRemove(sym_name); } } } @@ -636,8 +636,7 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void { log.debug("Resolving symbols in archives", .{}); var index: u32 = 0; undef_loop: while (index < wasm.undefs.count()) { - const undef_sym_loc = wasm.undefs.values()[index]; - const sym_name = undef_sym_loc.getName(wasm); + const sym_name = wasm.undefs.keys()[index]; for (wasm.archives.items) |archive| { const offset = archive.toc.get(sym_name) orelse { diff --git a/src/link/Wasm/Archive.zig b/src/link/Wasm/Archive.zig index 2fa1e07915..c4fb9b8291 100644 --- a/src/link/Wasm/Archive.zig +++ b/src/link/Wasm/Archive.zig @@ -157,13 +157,12 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype }; var i: usize = 0; - while (i < sym_tab.len) { - const string = mem.sliceTo(sym_tab[i..], 0); - if (string.len == 0) { - i += 1; - continue; - } - i += string.len; + var pos: usize = 0; + while (i < num_symbols) : (i += 1) { + const string = mem.sliceTo(sym_tab[pos..], 0); + pos += string.len + 1; + if (string.len == 0) continue; + const name = try allocator.dupe(u8, string); errdefer allocator.free(name); const gop = try archive.toc.getOrPut(allocator, name); @@ -172,7 +171,7 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype } else { gop.value_ptr.* = .{}; } - try gop.value_ptr.append(allocator, symbol_positions[gop.index]); + try gop.value_ptr.append(allocator, symbol_positions[i]); } } From 2a62dbda0bb5e5c8a1c92a058b684309bd7efeeb Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sat, 17 Dec 2022 17:17:34 +0100 Subject: [PATCH 5/8] wasm-linker: fix type index relocations Previously we used the relocation index to find the corresponding symbol that represents the type. However, the index actually represents the index into the list of types. We solved this by first retrieving the original type, and then finding its location in the new list of types. When the atom file is 'null', it means the type originates from a Zig function pointer or a synthetic function. In both cases, the final type index was already resolved and therefore equals to relocation's index value. --- src/link/Wasm/Atom.zig | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index 4f16a1c2fb..f716cd56ee 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -168,12 +168,13 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa .R_WASM_TABLE_INDEX_SLEB, .R_WASM_TABLE_INDEX_SLEB64, => return wasm_bin.function_table.get(target_loc) orelse 0, - .R_WASM_TYPE_INDEX_LEB => return blk: { - if (symbol.isUndefined()) { - const imp = wasm_bin.imports.get(target_loc).?; - break :blk imp.kind.function; - } - break :blk wasm_bin.functions.values()[symbol.index - wasm_bin.imported_functions_count].type_index; + .R_WASM_TYPE_INDEX_LEB => { + const file_index = atom.file orelse { + return relocation.index; + }; + + const original_type = wasm_bin.objects.items[file_index].func_types[relocation.index]; + return wasm_bin.getTypeIndex(original_type).?; }, .R_WASM_GLOBAL_INDEX_I32, .R_WASM_GLOBAL_INDEX_LEB, From dd850929822abb7f81a0c4fdfa97ecf37d4bc16c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 18 Dec 2022 16:37:00 +0100 Subject: [PATCH 6/8] wasm-linker: Fix relocations for alias'd atoms When an atom has one or multiple aliasses, we we could not find the target atom from the alias'd symbol. This is solved by ensuring that we also insert each alias symbol in the symbol-atom map. --- src/link/Wasm.zig | 36 +++++++++++++++++++++++++++--------- src/link/Wasm/Atom.zig | 6 +++++- src/link/Wasm/Object.zig | 8 ++++---- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index d8aab58ef1..6444b431be 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1568,9 +1568,13 @@ fn allocateAtoms(wasm: *Wasm) !void { var atom: *Atom = entry.value_ptr.*.getFirst(); var offset: u32 = 0; while (true) { + const symbol_loc = atom.symbolLoc(); + if (!wasm.resolved_symbols.contains(symbol_loc)) { + atom = atom.next orelse break; + continue; + } offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment); atom.offset = offset; - const symbol_loc = atom.symbolLoc(); log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{ symbol_loc.getName(wasm), offset, @@ -1578,7 +1582,7 @@ fn allocateAtoms(wasm: *Wasm) !void { atom.size, }); offset += atom.size; - try wasm.symbol_atom.put(wasm.base.allocator, atom.symbolLoc(), atom); // Update atom pointers + try wasm.symbol_atom.put(wasm.base.allocator, symbol_loc, atom); // Update atom pointers atom = atom.next orelse break; } segment.size = std.mem.alignForwardGeneric(u32, offset, segment.alignment); @@ -2579,14 +2583,16 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l var atom: *Atom = wasm.atoms.get(code_index).?.getFirst(); // The code section must be sorted in line with the function order. - var sorted_atoms = try std.ArrayList(*Atom).initCapacity(wasm.base.allocator, wasm.functions.count()); + var sorted_atoms = try std.ArrayList(*Atom).initCapacity(gpa, wasm.functions.count()); defer sorted_atoms.deinit(); while (true) { - if (!is_obj) { - atom.resolveRelocs(wasm); + if (wasm.resolved_symbols.contains(atom.symbolLoc())) { + if (!is_obj) { + atom.resolveRelocs(wasm); + } + sorted_atoms.appendAssumeCapacity(atom); } - sorted_atoms.appendAssumeCapacity(atom); atom = atom.next orelse break; } @@ -2641,6 +2647,10 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l // fill in the offset table and the data segments var current_offset: u32 = 0; while (true) { + if (!wasm.resolved_symbols.contains(atom.symbolLoc())) { + atom = atom.next orelse break; + continue; + } if (!is_obj) { atom.resolveRelocs(wasm); } @@ -4170,15 +4180,23 @@ fn emitDataRelocations( try writeCustomSectionHeader(binary_bytes.items, header_offset, size); } -/// Searches for an a matching function signature, when not found -/// a new entry will be made. The index of the existing/new signature will be returned. -pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 { +pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 { var index: u32 = 0; while (index < wasm.func_types.items.len) : (index += 1) { if (wasm.func_types.items[index].eql(func_type)) return index; } + return null; +} + +/// Searches for an a matching function signature, when not found +/// a new entry will be made. The index of the existing/new signature will be returned. +pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 { + if (wasm.getTypeIndex(func_type)) |index| { + return index; + } // functype does not exist. + const index = @intCast(u32, wasm.func_types.items.len); const params = try wasm.base.allocator.dupe(std.wasm.Valtype, func_type.params); errdefer wasm.base.allocator.free(params); const returns = try wasm.base.allocator.dupe(std.wasm.Valtype, func_type.returns); diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index f716cd56ee..eb3a31b8a0 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -186,7 +186,11 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa .R_WASM_MEMORY_ADDR_SLEB, .R_WASM_MEMORY_ADDR_SLEB64, => { - std.debug.assert(symbol.tag == .data and !symbol.isUndefined()); + std.debug.assert(symbol.tag == .data); + if (symbol.isUndefined()) { + return 0; + } + const merge_segment = wasm_bin.base.options.output_mode != .Obj; const target_atom = wasm_bin.symbol_atom.get(target_loc).?; const segment_info = if (target_atom.file) |object_index| blk: { diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index e5947228a5..8f49d68712 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -923,7 +923,7 @@ pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_b try atom.relocs.append(gpa, reloc); if (relocation.isTableIndex()) { - try wasm_bin.function_table.putNoClobber(gpa, .{ + try wasm_bin.function_table.put(gpa, .{ .file = object_index, .index = relocation.index, }, 0); @@ -938,17 +938,17 @@ pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_b .index = relocatable_data.getIndex(), })) |symbols| { atom.sym_index = symbols.pop(); + try wasm_bin.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), atom); // symbols referencing the same atom will be added as alias // or as 'parent' when they are global. while (symbols.popOrNull()) |idx| { + try wasm_bin.symbol_atom.putNoClobber(gpa, .{ .file = atom.file, .index = idx }, atom); const alias_symbol = object.symtable[idx]; - const symbol = object.symtable[atom.sym_index]; - if (alias_symbol.isGlobal() and symbol.isLocal()) { + if (alias_symbol.isGlobal()) { atom.sym_index = idx; } } - try wasm_bin.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), atom); } const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index]; From 8eac2e30c995ab5d36bd06765f94c2c432bbd96b Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 18 Dec 2022 16:41:57 +0100 Subject: [PATCH 7/8] wasm-linker: Add caching + more into zld path --- src/link/Wasm.zig | 233 +++++++++++++++++++++++++++++++++------------- 1 file changed, 166 insertions(+), 67 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 6444b431be..33df0eb7ea 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1718,7 +1718,7 @@ fn mergeSections(wasm: *Wasm) !void { continue; } - const object = wasm.objects.items[sym_loc.file.?]; + const object = &wasm.objects.items[sym_loc.file.?]; const symbol = &object.symtable[sym_loc.index]; 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 @@ -1730,13 +1730,12 @@ fn mergeSections(wasm: *Wasm) !void { const index = symbol.index - offset; switch (symbol.tag) { .function => { - const original_func = object.functions[index]; const gop = try wasm.functions.getOrPut( wasm.base.allocator, .{ .file = sym_loc.file, .index = symbol.index }, ); if (!gop.found_existing) { - gop.value_ptr.* = original_func; + gop.value_ptr.* = object.functions[index]; } symbol.index = @intCast(u32, gop.index) + wasm.imported_functions_count; }, @@ -1784,7 +1783,7 @@ fn mergeTypes(wasm: *Wasm) !void { if (symbol.isUndefined()) { log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(wasm)}); - const import: *types.Import = wasm.imports.getPtr(sym_loc).?; + const import: *types.Import = wasm.imports.getPtr(sym_loc) orelse continue; const original_type = object.func_types[import.kind.function]; import.kind.function = try wasm.putOrGetFuncType(original_type); } else if (!dirty.contains(symbol.index)) { @@ -2235,22 +2234,112 @@ pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) lin } } +/// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary. fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); - if (build_options.have_llvm) { - if (wasm.llvm_object) |llvm_object| { - return try llvm_object.flushModule(comp, prog_node); + const gpa = wasm.base.allocator; + const options = wasm.base.options; + + // Used for all temporary memory allocated during flushin + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + const directory = options.emit.?.directory; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{options.emit.?.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (options.module != null) blk: { + assert(options.use_llvm); // `linkWithZld` should never be called when the Wasm backend is used + try wasm.flushModule(comp, prog_node); + + if (fs.path.dirname(full_out_path)) |dirname| { + break :blk try fs.path.join(arena, &.{ dirname, wasm.base.intermediary_basename.? }); + } else { + break :blk wasm.base.intermediary_basename.?; } - } + } else null; var sub_prog_node = prog_node.start("Wasm Flush", 0); sub_prog_node.activate(); defer sub_prog_node.end(); - // ensure the error names table is populated when an error name is referenced - try wasm.populateErrorNameTable(); + const is_obj = options.output_mode == .Obj; + const compiler_rt_path: ?[]const u8 = if (options.include_compiler_rt and !is_obj) + comp.compiler_rt_lib.?.full_object_path + else + null; + const id_symlink_basename = "zld.id"; + + var man: Cache.Manifest = undefined; + defer if (!options.disable_lld_caching) man.deinit(); + var digest: [Cache.hex_digest_len]u8 = undefined; + + // NOTE: The following section must be maintained to be equal + // as the section defined in `linkWithLLD` + if (!options.disable_lld_caching) { + man = comp.cache_parent.obtain(); + + // We are about to obtain this lock, so here we give other processes a chance first. + wasm.base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 7); + + for (options.objects) |obj| { + _ = try man.addFile(obj.path, null); + man.hash.add(obj.must_link); + } + for (comp.c_object_table.keys()) |key| { + _ = try man.addFile(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + try man.addOptionalFile(compiler_rt_path); + man.hash.addOptionalBytes(options.entry); + man.hash.addOptional(options.stack_size_override); + man.hash.add(options.import_memory); + man.hash.add(options.import_table); + man.hash.add(options.export_table); + man.hash.addOptional(options.initial_memory); + man.hash.addOptional(options.max_memory); + man.hash.add(options.shared_memory); + man.hash.addOptional(options.global_base); + man.hash.add(options.export_symbol_names.len); + // strip does not need to go into the linker hash because it is part of the hash namespace + for (options.export_symbol_names) |symbol_name| { + man.hash.addBytes(symbol_name); + } + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + wasm.base.lock = man.toOwnedLock(); + return; + } + log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } // The amount of sections that will be written var section_count: u32 = 0; @@ -2259,17 +2348,44 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l // Index of the data section. Used to tell relocation table where the section lives. var data_section_index: ?u32 = null; - // Used for all temporary memory allocated during flushin - var arena_instance = std.heap.ArenaAllocator.init(wasm.base.allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - // Positional arguments to the linker such as object files and static archives. var positionals = std.ArrayList([]const u8).init(arena); - try positionals.ensureUnusedCapacity(wasm.base.options.objects.len); + try positionals.ensureUnusedCapacity(options.objects.len); - for (wasm.base.options.objects) |object| { - positionals.appendAssumeCapacity(object.path); + // When the target os is WASI, we allow linking with WASI-LIBC + if (options.target.os.tag == .wasi) { + const is_exe_or_dyn_lib = wasm.base.options.output_mode == .Exe or + (wasm.base.options.output_mode == .Lib and wasm.base.options.link_mode == .Dynamic); + if (is_exe_or_dyn_lib) { + const wasi_emulated_libs = wasm.base.options.wasi_emulated_libs; + for (wasi_emulated_libs) |crt_file| { + try positionals.append(try comp.get_libc_crt_file( + arena, + wasi_libc.emulatedLibCRFileLibName(crt_file), + )); + } + + if (wasm.base.options.link_libc) { + try positionals.append(try comp.get_libc_crt_file( + arena, + wasi_libc.execModelCrtFileFullName(wasm.base.options.wasi_exec_model), + )); + try positionals.append(try comp.get_libc_crt_file(arena, "libc.a")); + } + + if (wasm.base.options.link_libcpp) { + try positionals.append(comp.libcxx_static_lib.?.full_object_path); + try positionals.append(comp.libcxxabi_static_lib.?.full_object_path); + } + } + } + + if (module_obj_path) |path| { + try positionals.append(path); + } + + for (options.objects) |object| { + try positionals.append(object.path); } for (comp.c_object_table.keys()) |c_object| { @@ -2290,46 +2406,13 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; try wasm.validateFeatures(&enabled_features, &emit_features_count); try wasm.resolveSymbolsInArchives(); - try wasm.checkUndefinedSymbols(); + // try wasm.checkUndefinedSymbols(); - // When we finish/error we reset the state of the linker - // So we can rebuild the binary file on each incremental update - defer wasm.resetState(); try wasm.setupStart(); try wasm.setupImports(); - if (wasm.base.options.module) |mod| { - var decl_it = wasm.decls.keyIterator(); - while (decl_it.next()) |decl_index_ptr| { - const decl = mod.declPtr(decl_index_ptr.*); - if (decl.isExtern()) continue; - const atom = &decl.*.link.wasm; - if (decl.ty.zigTypeTag() == .Fn) { - try wasm.parseAtom(atom, .{ .function = decl.fn_link.wasm }); - } else if (decl.getVariable()) |variable| { - if (!variable.is_mutable) { - try wasm.parseAtom(atom, .{ .data = .read_only }); - } else if (variable.init.isUndefDeep()) { - try wasm.parseAtom(atom, .{ .data = .uninitialized }); - } else { - try wasm.parseAtom(atom, .{ .data = .initialized }); - } - } else { - try wasm.parseAtom(atom, .{ .data = .read_only }); - } - - // also parse atoms for a decl's locals - for (atom.locals.items) |*local_atom| { - try wasm.parseAtom(local_atom, .{ .data = .read_only }); - } - } - - if (wasm.dwarf) |*dwarf| { - try dwarf.flushModule(wasm.base.options.module.?); - } - } for (wasm.objects.items) |*object, object_index| { - try object.parseIntoAtoms(wasm.base.allocator, @intCast(u16, object_index), wasm); + try object.parseIntoAtoms(gpa, @intCast(u16, object_index), wasm); } try wasm.allocateAtoms(); @@ -2340,9 +2423,8 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l try wasm.setupExports(); const header_size = 5 + 1; - const is_obj = wasm.base.options.output_mode == .Obj; - var binary_bytes = std.ArrayList(u8).init(wasm.base.allocator); + var binary_bytes = std.ArrayList(u8).init(gpa); defer binary_bytes.deinit(); const binary_writer = binary_bytes.writer(); @@ -2380,16 +2462,16 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l } // Import section - const import_memory = wasm.base.options.import_memory or is_obj; - const import_table = wasm.base.options.import_table or is_obj; + const import_memory = options.import_memory or is_obj; + const import_table = options.import_table or is_obj; if (wasm.imports.count() != 0 or import_memory or import_table) { const header_offset = try reserveVecSectionHeader(&binary_bytes); // import table is always first table so emit that first if (import_table) { const table_imp: types.Import = .{ - .module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name), - .name = try wasm.string_table.put(wasm.base.allocator, "__indirect_function_table"), + .module_name = try wasm.string_table.put(gpa, wasm.host_name), + .name = try wasm.string_table.put(gpa, "__indirect_function_table"), .kind = .{ .table = .{ .limits = .{ @@ -2413,8 +2495,8 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l if (import_memory) { const mem_name = if (is_obj) "__linear_memory" else "memory"; const mem_imp: types.Import = .{ - .module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name), - .name = try wasm.string_table.put(wasm.base.allocator, mem_name), + .module_name = try wasm.string_table.put(gpa, wasm.host_name), + .name = try wasm.string_table.put(gpa, mem_name), .kind = .{ .memory = wasm.memories.limits }, }; try wasm.emitImport(binary_writer, mem_imp); @@ -2448,7 +2530,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l } // Table section - const export_table = wasm.base.options.export_table; + const export_table = options.export_table; if (!import_table and wasm.function_table.count() != 0) { const header_offset = try reserveVecSectionHeader(&binary_bytes); @@ -2704,13 +2786,13 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l if (data_section_index) |data_index| { try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table); } - } else if (!wasm.base.options.strip) { + } else if (!options.strip) { try wasm.emitNameSection(&binary_bytes, arena); } - if (!wasm.base.options.strip) { + if (!options.strip) { if (wasm.dwarf) |*dwarf| { - const mod = wasm.base.options.module.?; + const mod = options.module.?; try dwarf.writeDbgAbbrev(); // for debug info and ranges, the address is always 0, // as locations are always offsets relative to 'code' section. @@ -2719,7 +2801,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l try dwarf.writeDbgLineHeader(); } - var debug_bytes = std.ArrayList(u8).init(wasm.base.allocator); + var debug_bytes = std.ArrayList(u8).init(gpa); defer debug_bytes.deinit(); const DebugSection = struct { @@ -2768,6 +2850,21 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l .iov_len = binary_bytes.items.len, }}; try wasm.base.file.?.writevAll(&iovec); + + if (!wasm.base.options.disable_lld_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + wasm.base.lock = man.toOwnedLock(); + } } pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { @@ -3422,7 +3519,9 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem for (wasm.resolved_symbols.keys()) |sym_loc| { const symbol = sym_loc.getSymbol(wasm).*; const name = if (symbol.isUndefined()) blk: { - break :blk wasm.string_table.get(wasm.imports.get(sym_loc).?.name); + if (symbol.tag == .data) continue; + const imp = wasm.imports.get(sym_loc) orelse continue; + break :blk wasm.string_table.get(imp.name); } else sym_loc.getName(wasm); switch (symbol.tag) { .function => { From 6f44e2d1d3dc1ac7942082dc98947f7beaa1f205 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Mon, 19 Dec 2022 16:50:25 +0100 Subject: [PATCH 8/8] wasm-linker: consolidate writing to file This merges the paths from flushModule and linkWithZld to a single function that will write the entire WebAssembly module to the file. This reduces the chance of mistakes as we do not have to duplicate the logic. A similar action may be needed later for linkWithLLD. --- src/link/Wasm.zig | 465 ++-------------------------------------------- 1 file changed, 19 insertions(+), 446 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 33df0eb7ea..346c92ebbe 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2341,13 +2341,6 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l }; } - // The amount of sections that will be written - var section_count: u32 = 0; - // Index of the code section. Used to tell relocation table where the section lives. - var code_section_index: ?u32 = null; - // Index of the data section. Used to tell relocation table where the section lives. - var data_section_index: ?u32 = null; - // Positional arguments to the linker such as object files and static archives. var positionals = std.ArrayList([]const u8).init(arena); try positionals.ensureUnusedCapacity(options.objects.len); @@ -2406,7 +2399,6 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; try wasm.validateFeatures(&enabled_features, &emit_features_count); try wasm.resolveSymbolsInArchives(); - // try wasm.checkUndefinedSymbols(); try wasm.setupStart(); try wasm.setupImports(); @@ -2421,435 +2413,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l try wasm.mergeSections(); try wasm.mergeTypes(); try wasm.setupExports(); - - const header_size = 5 + 1; - - var binary_bytes = std.ArrayList(u8).init(gpa); - defer binary_bytes.deinit(); - const binary_writer = binary_bytes.writer(); - - // We write the magic bytes at the end so they will only be written - // if everything succeeded as expected. So populate with 0's for now. - try binary_writer.writeAll(&[_]u8{0} ** 8); - // (Re)set file pointer to 0 - try wasm.base.file.?.setEndPos(0); - try wasm.base.file.?.seekTo(0); - - // Type section - if (wasm.func_types.items.len != 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - log.debug("Writing type section. Count: ({d})", .{wasm.func_types.items.len}); - for (wasm.func_types.items) |func_type| { - try leb.writeULEB128(binary_writer, std.wasm.function_type); - try leb.writeULEB128(binary_writer, @intCast(u32, func_type.params.len)); - for (func_type.params) |param_ty| { - try leb.writeULEB128(binary_writer, std.wasm.valtype(param_ty)); - } - try leb.writeULEB128(binary_writer, @intCast(u32, func_type.returns.len)); - for (func_type.returns) |ret_ty| { - try leb.writeULEB128(binary_writer, std.wasm.valtype(ret_ty)); - } - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .type, - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @intCast(u32, wasm.func_types.items.len), - ); - section_count += 1; - } - - // Import section - const import_memory = options.import_memory or is_obj; - const import_table = options.import_table or is_obj; - if (wasm.imports.count() != 0 or import_memory or import_table) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - // import table is always first table so emit that first - if (import_table) { - const table_imp: types.Import = .{ - .module_name = try wasm.string_table.put(gpa, wasm.host_name), - .name = try wasm.string_table.put(gpa, "__indirect_function_table"), - .kind = .{ - .table = .{ - .limits = .{ - .min = @intCast(u32, wasm.function_table.count()), - .max = null, - }, - .reftype = .funcref, - }, - }, - }; - try wasm.emitImport(binary_writer, table_imp); - } - - var it = wasm.imports.iterator(); - while (it.next()) |entry| { - assert(entry.key_ptr.*.getSymbol(wasm).isUndefined()); - const import = entry.value_ptr.*; - try wasm.emitImport(binary_writer, import); - } - - if (import_memory) { - const mem_name = if (is_obj) "__linear_memory" else "memory"; - const mem_imp: types.Import = .{ - .module_name = try wasm.string_table.put(gpa, wasm.host_name), - .name = try wasm.string_table.put(gpa, mem_name), - .kind = .{ .memory = wasm.memories.limits }, - }; - try wasm.emitImport(binary_writer, mem_imp); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .import, - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @intCast(u32, wasm.imports.count() + @boolToInt(import_memory) + @boolToInt(import_table)), - ); - section_count += 1; - } - - // Function section - if (wasm.functions.count() != 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - for (wasm.functions.values()) |function| { - try leb.writeULEB128(binary_writer, function.type_index); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .function, - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @intCast(u32, wasm.functions.count()), - ); - section_count += 1; - } - - // Table section - const export_table = options.export_table; - if (!import_table and wasm.function_table.count() != 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - try leb.writeULEB128(binary_writer, std.wasm.reftype(.funcref)); - try emitLimits(binary_writer, .{ - .min = @intCast(u32, wasm.function_table.count()) + 1, - .max = null, - }); - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .table, - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @as(u32, 1), - ); - section_count += 1; - } - - // Memory section - if (!import_memory) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - try emitLimits(binary_writer, wasm.memories.limits); - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .memory, - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @as(u32, 1), // wasm currently only supports 1 linear memory segment - ); - section_count += 1; - } - - // Global section (used to emit stack pointer) - if (wasm.wasm_globals.items.len > 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - var global_count: u32 = 0; - for (wasm.wasm_globals.items) |global| { - try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype)); - try binary_writer.writeByte(@boolToInt(global.global_type.mutable)); - try emitInit(binary_writer, global.init); - global_count += 1; - } - - for (wasm.address_globals.items) |sym_loc| { - const atom = wasm.symbol_atom.get(sym_loc).?; - try binary_writer.writeByte(std.wasm.valtype(.i32)); - try binary_writer.writeByte(0); // immutable - try emitInit(binary_writer, .{ - .i32_const = @bitCast(i32, atom.offset), - }); - global_count += 1; - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .global, - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @intCast(u32, global_count), - ); - section_count += 1; - } - - // Export section - if (wasm.exports.items.len != 0 or export_table or !import_memory) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - for (wasm.exports.items) |exp| { - const name = wasm.string_table.get(exp.name); - try leb.writeULEB128(binary_writer, @intCast(u32, name.len)); - try binary_writer.writeAll(name); - try leb.writeULEB128(binary_writer, @enumToInt(exp.kind)); - try leb.writeULEB128(binary_writer, exp.index); - } - - if (export_table) { - try leb.writeULEB128(binary_writer, @intCast(u32, "__indirect_function_table".len)); - try binary_writer.writeAll("__indirect_function_table"); - try binary_writer.writeByte(std.wasm.externalKind(.table)); - try leb.writeULEB128(binary_writer, @as(u32, 0)); // function table is always the first table - } - - if (!import_memory) { - try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len)); - try binary_writer.writeAll("memory"); - try binary_writer.writeByte(std.wasm.externalKind(.memory)); - try leb.writeULEB128(binary_writer, @as(u32, 0)); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .@"export", - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @intCast(u32, wasm.exports.items.len) + @boolToInt(export_table) + @boolToInt(!import_memory), - ); - section_count += 1; - } - - // element section (function table) - if (wasm.function_table.count() > 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - var flags: u32 = 0x2; // Yes we have a table - try leb.writeULEB128(binary_writer, flags); - try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols - try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid - try leb.writeULEB128(binary_writer, @as(u8, 0)); - try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count())); - var symbol_it = wasm.function_table.keyIterator(); - while (symbol_it.next()) |symbol_loc_ptr| { - try leb.writeULEB128(binary_writer, symbol_loc_ptr.*.getSymbol(wasm).index); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .element, - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @as(u32, 1), - ); - section_count += 1; - } - - // Code section - var code_section_size: u32 = 0; - if (wasm.code_section_index) |code_index| { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - var atom: *Atom = wasm.atoms.get(code_index).?.getFirst(); - - // The code section must be sorted in line with the function order. - var sorted_atoms = try std.ArrayList(*Atom).initCapacity(gpa, wasm.functions.count()); - defer sorted_atoms.deinit(); - - while (true) { - if (wasm.resolved_symbols.contains(atom.symbolLoc())) { - if (!is_obj) { - atom.resolveRelocs(wasm); - } - sorted_atoms.appendAssumeCapacity(atom); - } - atom = atom.next orelse break; - } - - const atom_sort_fn = struct { - fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool { - const lhs_sym = lhs.symbolLoc().getSymbol(ctx); - const rhs_sym = rhs.symbolLoc().getSymbol(ctx); - return lhs_sym.index < rhs_sym.index; - } - }.sort; - - std.sort.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn); - - for (sorted_atoms.items) |sorted_atom| { - try leb.writeULEB128(binary_writer, sorted_atom.size); - try binary_writer.writeAll(sorted_atom.code.items); - } - - code_section_size = @intCast(u32, binary_bytes.items.len - header_offset - header_size); - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .code, - code_section_size, - @intCast(u32, wasm.functions.count()), - ); - code_section_index = section_count; - section_count += 1; - } - - // Data section - if (wasm.data_segments.count() != 0) { - const header_offset = try reserveVecSectionHeader(&binary_bytes); - - var it = wasm.data_segments.iterator(); - var segment_count: u32 = 0; - while (it.next()) |entry| { - // do not output 'bss' section unless we import memory and therefore - // want to guarantee the data is zero initialized - if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue; - segment_count += 1; - const atom_index = entry.value_ptr.*; - var atom: *Atom = wasm.atoms.getPtr(atom_index).?.*.getFirst(); - const segment = wasm.segments.items[atom_index]; - - // flag and index to memory section (currently, there can only be 1 memory section in wasm) - try leb.writeULEB128(binary_writer, @as(u32, 0)); - // offset into data section - try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) }); - try leb.writeULEB128(binary_writer, segment.size); - - // fill in the offset table and the data segments - var current_offset: u32 = 0; - while (true) { - if (!wasm.resolved_symbols.contains(atom.symbolLoc())) { - atom = atom.next orelse break; - continue; - } - if (!is_obj) { - atom.resolveRelocs(wasm); - } - - // Pad with zeroes to ensure all segments are aligned - if (current_offset != atom.offset) { - const diff = atom.offset - current_offset; - try binary_writer.writeByteNTimes(0, diff); - current_offset += diff; - } - assert(current_offset == atom.offset); - assert(atom.code.items.len == atom.size); - try binary_writer.writeAll(atom.code.items); - - current_offset += atom.size; - if (atom.next) |next| { - atom = next; - } else { - // also pad with zeroes when last atom to ensure - // segments are aligned. - if (current_offset != segment.size) { - try binary_writer.writeByteNTimes(0, segment.size - current_offset); - current_offset += segment.size - current_offset; - } - break; - } - } - assert(current_offset == segment.size); - } - - try writeVecSectionHeader( - binary_bytes.items, - header_offset, - .data, - @intCast(u32, binary_bytes.items.len - header_offset - header_size), - @intCast(u32, segment_count), - ); - data_section_index = section_count; - section_count += 1; - } - - if (is_obj) { - // relocations need to point to the index of a symbol in the final symbol table. To save memory, - // we never store all symbols in a single table, but store a location reference instead. - // This means that for a relocatable object file, we need to generate one and provide it to the relocation sections. - var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena); - try wasm.emitLinkSection(&binary_bytes, &symbol_table); - if (code_section_index) |code_index| { - try wasm.emitCodeRelocations(&binary_bytes, code_index, symbol_table); - } - if (data_section_index) |data_index| { - try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table); - } - } else if (!options.strip) { - try wasm.emitNameSection(&binary_bytes, arena); - } - - if (!options.strip) { - if (wasm.dwarf) |*dwarf| { - const mod = options.module.?; - try dwarf.writeDbgAbbrev(); - // for debug info and ranges, the address is always 0, - // as locations are always offsets relative to 'code' section. - try dwarf.writeDbgInfoHeader(mod, 0, code_section_size); - try dwarf.writeDbgAranges(0, code_section_size); - try dwarf.writeDbgLineHeader(); - } - - 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| { - var atom = wasm.atoms.get(index).?.getFirst(); - while (true) { - atom.resolveRelocs(wasm); - try debug_bytes.appendSlice(atom.code.items); - atom = atom.next orelse break; - } - try emitDebugSection(&binary_bytes, debug_bytes.items, item.name); - debug_bytes.clearRetainingCapacity(); - } - } - - try emitProducerSection(&binary_bytes); - if (emit_features_count > 0) { - try emitFeaturesSection(&binary_bytes, &enabled_features, emit_features_count); - } - } - - // Only when writing all sections executed properly we write the magic - // bytes. This allows us to easily detect what went wrong while generating - // the final binary. - mem.copy(u8, binary_bytes.items, &(std.wasm.magic ++ std.wasm.version)); - - // finally, write the entire binary into the file. - var iovec = [_]std.os.iovec_const{.{ - .iov_base = binary_bytes.items.ptr, - .iov_len = binary_bytes.items.len, - }}; - try wasm.base.file.?.writevAll(&iovec); + try wasm.writeToFile(enabled_features, emit_features_count, arena); if (!wasm.base.options.disable_lld_caching) { // Update the file with the digest. If it fails we can continue; it only @@ -2884,13 +2448,6 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod // ensure the error names table is populated when an error name is referenced try wasm.populateErrorNameTable(); - // The amount of sections that will be written - var section_count: u32 = 0; - // Index of the code section. Used to tell relocation table where the section lives. - var code_section_index: ?u32 = null; - // Index of the data section. Used to tell relocation table where the section lives. - var data_section_index: ?u32 = null; - // Used for all temporary memory allocated during flushin var arena_instance = std.heap.ArenaAllocator.init(wasm.base.allocator); defer arena_instance.deinit(); @@ -2970,8 +2527,24 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod try wasm.mergeSections(); try wasm.mergeTypes(); try wasm.setupExports(); + try wasm.writeToFile(enabled_features, emit_features_count, arena); +} +/// Writes the WebAssembly in-memory module to the file +fn writeToFile( + wasm: *Wasm, + enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool, + feature_count: u32, + arena: Allocator, +) !void { + // Size of each section header const header_size = 5 + 1; + // The amount of sections that will be written + var section_count: u32 = 0; + // Index of the code section. Used to tell relocation table where the section lives. + var code_section_index: ?u32 = null; + // Index of the data section. Used to tell relocation table where the section lives. + var data_section_index: ?u32 = null; const is_obj = wasm.base.options.output_mode == .Obj or (!wasm.base.options.use_llvm and wasm.base.options.use_lld); var binary_bytes = std.ArrayList(u8).init(wasm.base.allocator); @@ -3378,8 +2951,8 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod } try emitProducerSection(&binary_bytes); - if (emit_features_count > 0) { - try emitFeaturesSection(&binary_bytes, &enabled_features, emit_features_count); + if (feature_count > 0) { + try emitFeaturesSection(&binary_bytes, &enabled_features, feature_count); } }