authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-12-18 16:41:57+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-12-18 16:41:57+01:00
log8eac2e30c995ab5d36bd06765f94c2c432bbd96b
tree0d3a0f136985d2b80a62fa37e041f9a774705776
parentdd850929822abb7f81a0c4fdfa97ecf37d4bc16c
signature Commit is signed but in an unrecognized format.

wasm-linker: Add caching + more into zld path


1 files changed, 166 insertions(+), 67 deletions(-)

src/link/Wasm.zig+166-67
...@@ -1718,7 +1718,7 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -1718,7 +1718,7 @@ fn mergeSections(wasm: *Wasm) !void {
1718 continue;1718 continue;
1719 }1719 }
17201720
1721 const object = wasm.objects.items[sym_loc.file.?];1721 const object = &wasm.objects.items[sym_loc.file.?];
1722 const symbol = &object.symtable[sym_loc.index];1722 const symbol = &object.symtable[sym_loc.index];
1723 if (symbol.isUndefined() or (symbol.tag != .function and symbol.tag != .global and symbol.tag != .table)) {1723 if (symbol.isUndefined() or (symbol.tag != .function and symbol.tag != .global and symbol.tag != .table)) {
1724 // Skip undefined symbols as they go in the `import` section1724 // Skip undefined symbols as they go in the `import` section
...@@ -1730,13 +1730,12 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -1730,13 +1730,12 @@ fn mergeSections(wasm: *Wasm) !void {
1730 const index = symbol.index - offset;1730 const index = symbol.index - offset;
1731 switch (symbol.tag) {1731 switch (symbol.tag) {
1732 .function => {1732 .function => {
1733 const original_func = object.functions[index];
1734 const gop = try wasm.functions.getOrPut(1733 const gop = try wasm.functions.getOrPut(
1735 wasm.base.allocator,1734 wasm.base.allocator,
1736 .{ .file = sym_loc.file, .index = symbol.index },1735 .{ .file = sym_loc.file, .index = symbol.index },
1737 );1736 );
1738 if (!gop.found_existing) {1737 if (!gop.found_existing) {
1739 gop.value_ptr.* = original_func;1738 gop.value_ptr.* = object.functions[index];
1740 }1739 }
1741 symbol.index = @intCast(u32, gop.index) + wasm.imported_functions_count;1740 symbol.index = @intCast(u32, gop.index) + wasm.imported_functions_count;
1742 },1741 },
...@@ -1784,7 +1783,7 @@ fn mergeTypes(wasm: *Wasm) !void {...@@ -1784,7 +1783,7 @@ fn mergeTypes(wasm: *Wasm) !void {
17841783
1785 if (symbol.isUndefined()) {1784 if (symbol.isUndefined()) {
1786 log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(wasm)});1785 log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(wasm)});
1787 const import: *types.Import = wasm.imports.getPtr(sym_loc).?;1786 const import: *types.Import = wasm.imports.getPtr(sym_loc) orelse continue;
1788 const original_type = object.func_types[import.kind.function];1787 const original_type = object.func_types[import.kind.function];
1789 import.kind.function = try wasm.putOrGetFuncType(original_type);1788 import.kind.function = try wasm.putOrGetFuncType(original_type);
1790 } else if (!dirty.contains(symbol.index)) {1789 } else if (!dirty.contains(symbol.index)) {
...@@ -2235,22 +2234,112 @@ pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) lin...@@ -2235,22 +2234,112 @@ pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) lin
2235 }2234 }
2236}2235}
22372236
2237/// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary.
2238fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {2238fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
2239 const tracy = trace(@src());2239 const tracy = trace(@src());
2240 defer tracy.end();2240 defer tracy.end();
22412241
2242 if (build_options.have_llvm) {2242 const gpa = wasm.base.allocator;
2243 if (wasm.llvm_object) |llvm_object| {2243 const options = wasm.base.options;
2244 return try llvm_object.flushModule(comp, prog_node);2244
2245 // Used for all temporary memory allocated during flushin
2246 var arena_instance = std.heap.ArenaAllocator.init(gpa);
2247 defer arena_instance.deinit();
2248 const arena = arena_instance.allocator();
2249
2250 const directory = options.emit.?.directory; // Just an alias to make it shorter to type.
2251 const full_out_path = try directory.join(arena, &[_][]const u8{options.emit.?.sub_path});
2252
2253 // If there is no Zig code to compile, then we should skip flushing the output file because it
2254 // will not be part of the linker line anyway.
2255 const module_obj_path: ?[]const u8 = if (options.module != null) blk: {
2256 assert(options.use_llvm); // `linkWithZld` should never be called when the Wasm backend is used
2257 try wasm.flushModule(comp, prog_node);
2258
2259 if (fs.path.dirname(full_out_path)) |dirname| {
2260 break :blk try fs.path.join(arena, &.{ dirname, wasm.base.intermediary_basename.? });
2261 } else {
2262 break :blk wasm.base.intermediary_basename.?;
2245 }2263 }
2246 }2264 } else null;
22472265
2248 var sub_prog_node = prog_node.start("Wasm Flush", 0);2266 var sub_prog_node = prog_node.start("Wasm Flush", 0);
2249 sub_prog_node.activate();2267 sub_prog_node.activate();
2250 defer sub_prog_node.end();2268 defer sub_prog_node.end();
22512269
2252 // ensure the error names table is populated when an error name is referenced2270 const is_obj = options.output_mode == .Obj;
2253 try wasm.populateErrorNameTable();2271 const compiler_rt_path: ?[]const u8 = if (options.include_compiler_rt and !is_obj)
2272 comp.compiler_rt_lib.?.full_object_path
2273 else
2274 null;
2275 const id_symlink_basename = "zld.id";
2276
2277 var man: Cache.Manifest = undefined;
2278 defer if (!options.disable_lld_caching) man.deinit();
2279 var digest: [Cache.hex_digest_len]u8 = undefined;
2280
2281 // NOTE: The following section must be maintained to be equal
2282 // as the section defined in `linkWithLLD`
2283 if (!options.disable_lld_caching) {
2284 man = comp.cache_parent.obtain();
2285
2286 // We are about to obtain this lock, so here we give other processes a chance first.
2287 wasm.base.releaseLock();
2288
2289 comptime assert(Compilation.link_hash_implementation_version == 7);
2290
2291 for (options.objects) |obj| {
2292 _ = try man.addFile(obj.path, null);
2293 man.hash.add(obj.must_link);
2294 }
2295 for (comp.c_object_table.keys()) |key| {
2296 _ = try man.addFile(key.status.success.object_path, null);
2297 }
2298 try man.addOptionalFile(module_obj_path);
2299 try man.addOptionalFile(compiler_rt_path);
2300 man.hash.addOptionalBytes(options.entry);
2301 man.hash.addOptional(options.stack_size_override);
2302 man.hash.add(options.import_memory);
2303 man.hash.add(options.import_table);
2304 man.hash.add(options.export_table);
2305 man.hash.addOptional(options.initial_memory);
2306 man.hash.addOptional(options.max_memory);
2307 man.hash.add(options.shared_memory);
2308 man.hash.addOptional(options.global_base);
2309 man.hash.add(options.export_symbol_names.len);
2310 // strip does not need to go into the linker hash because it is part of the hash namespace
2311 for (options.export_symbol_names) |symbol_name| {
2312 man.hash.addBytes(symbol_name);
2313 }
2314
2315 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
2316 _ = try man.hit();
2317 digest = man.final();
2318
2319 var prev_digest_buf: [digest.len]u8 = undefined;
2320 const prev_digest: []u8 = Cache.readSmallFile(
2321 directory.handle,
2322 id_symlink_basename,
2323 &prev_digest_buf,
2324 ) catch |err| blk: {
2325 log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
2326 // Handle this as a cache miss.
2327 break :blk prev_digest_buf[0..0];
2328 };
2329 if (mem.eql(u8, prev_digest, &digest)) {
2330 log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
2331 // Hot diggity dog! The output binary is already there.
2332 wasm.base.lock = man.toOwnedLock();
2333 return;
2334 }
2335 log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) });
2336
2337 // We are about to change the output file to be different, so we invalidate the build hash now.
2338 directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
2339 error.FileNotFound => {},
2340 else => |e| return e,
2341 };
2342 }
22542343
2255 // The amount of sections that will be written2344 // The amount of sections that will be written
2256 var section_count: u32 = 0;2345 var section_count: u32 = 0;
...@@ -2259,17 +2348,44 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2259,17 +2348,44 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2259 // Index of the data section. Used to tell relocation table where the section lives.2348 // Index of the data section. Used to tell relocation table where the section lives.
2260 var data_section_index: ?u32 = null;2349 var data_section_index: ?u32 = null;
22612350
2262 // Used for all temporary memory allocated during flushin
2263 var arena_instance = std.heap.ArenaAllocator.init(wasm.base.allocator);
2264 defer arena_instance.deinit();
2265 const arena = arena_instance.allocator();
2266
2267 // Positional arguments to the linker such as object files and static archives.2351 // Positional arguments to the linker such as object files and static archives.
2268 var positionals = std.ArrayList([]const u8).init(arena);2352 var positionals = std.ArrayList([]const u8).init(arena);
2269 try positionals.ensureUnusedCapacity(wasm.base.options.objects.len);2353 try positionals.ensureUnusedCapacity(options.objects.len);
2354
2355 // When the target os is WASI, we allow linking with WASI-LIBC
2356 if (options.target.os.tag == .wasi) {
2357 const is_exe_or_dyn_lib = wasm.base.options.output_mode == .Exe or
2358 (wasm.base.options.output_mode == .Lib and wasm.base.options.link_mode == .Dynamic);
2359 if (is_exe_or_dyn_lib) {
2360 const wasi_emulated_libs = wasm.base.options.wasi_emulated_libs;
2361 for (wasi_emulated_libs) |crt_file| {
2362 try positionals.append(try comp.get_libc_crt_file(
2363 arena,
2364 wasi_libc.emulatedLibCRFileLibName(crt_file),
2365 ));
2366 }
22702367
2271 for (wasm.base.options.objects) |object| {2368 if (wasm.base.options.link_libc) {
2272 positionals.appendAssumeCapacity(object.path);2369 try positionals.append(try comp.get_libc_crt_file(
2370 arena,
2371 wasi_libc.execModelCrtFileFullName(wasm.base.options.wasi_exec_model),
2372 ));
2373 try positionals.append(try comp.get_libc_crt_file(arena, "libc.a"));
2374 }
2375
2376 if (wasm.base.options.link_libcpp) {
2377 try positionals.append(comp.libcxx_static_lib.?.full_object_path);
2378 try positionals.append(comp.libcxxabi_static_lib.?.full_object_path);
2379 }
2380 }
2381 }
2382
2383 if (module_obj_path) |path| {
2384 try positionals.append(path);
2385 }
2386
2387 for (options.objects) |object| {
2388 try positionals.append(object.path);
2273 }2389 }
22742390
2275 for (comp.c_object_table.keys()) |c_object| {2391 for (comp.c_object_table.keys()) |c_object| {
...@@ -2290,46 +2406,13 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2290,46 +2406,13 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2290 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;2406 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
2291 try wasm.validateFeatures(&enabled_features, &emit_features_count);2407 try wasm.validateFeatures(&enabled_features, &emit_features_count);
2292 try wasm.resolveSymbolsInArchives();2408 try wasm.resolveSymbolsInArchives();
2293 try wasm.checkUndefinedSymbols();2409 // try wasm.checkUndefinedSymbols();
22942410
2295 // When we finish/error we reset the state of the linker
2296 // So we can rebuild the binary file on each incremental update
2297 defer wasm.resetState();
2298 try wasm.setupStart();2411 try wasm.setupStart();
2299 try wasm.setupImports();2412 try wasm.setupImports();
2300 if (wasm.base.options.module) |mod| {
2301 var decl_it = wasm.decls.keyIterator();
2302 while (decl_it.next()) |decl_index_ptr| {
2303 const decl = mod.declPtr(decl_index_ptr.*);
2304 if (decl.isExtern()) continue;
2305 const atom = &decl.*.link.wasm;
2306 if (decl.ty.zigTypeTag() == .Fn) {
2307 try wasm.parseAtom(atom, .{ .function = decl.fn_link.wasm });
2308 } else if (decl.getVariable()) |variable| {
2309 if (!variable.is_mutable) {
2310 try wasm.parseAtom(atom, .{ .data = .read_only });
2311 } else if (variable.init.isUndefDeep()) {
2312 try wasm.parseAtom(atom, .{ .data = .uninitialized });
2313 } else {
2314 try wasm.parseAtom(atom, .{ .data = .initialized });
2315 }
2316 } else {
2317 try wasm.parseAtom(atom, .{ .data = .read_only });
2318 }
2319
2320 // also parse atoms for a decl's locals
2321 for (atom.locals.items) |*local_atom| {
2322 try wasm.parseAtom(local_atom, .{ .data = .read_only });
2323 }
2324 }
2325
2326 if (wasm.dwarf) |*dwarf| {
2327 try dwarf.flushModule(wasm.base.options.module.?);
2328 }
2329 }
23302413
2331 for (wasm.objects.items) |*object, object_index| {2414 for (wasm.objects.items) |*object, object_index| {
2332 try object.parseIntoAtoms(wasm.base.allocator, @intCast(u16, object_index), wasm);2415 try object.parseIntoAtoms(gpa, @intCast(u16, object_index), wasm);
2333 }2416 }
23342417
2335 try wasm.allocateAtoms();2418 try wasm.allocateAtoms();
...@@ -2340,9 +2423,8 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2340,9 +2423,8 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2340 try wasm.setupExports();2423 try wasm.setupExports();
23412424
2342 const header_size = 5 + 1;2425 const header_size = 5 + 1;
2343 const is_obj = wasm.base.options.output_mode == .Obj;
23442426
2345 var binary_bytes = std.ArrayList(u8).init(wasm.base.allocator);2427 var binary_bytes = std.ArrayList(u8).init(gpa);
2346 defer binary_bytes.deinit();2428 defer binary_bytes.deinit();
2347 const binary_writer = binary_bytes.writer();2429 const binary_writer = binary_bytes.writer();
23482430
...@@ -2380,16 +2462,16 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2380,16 +2462,16 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2380 }2462 }
23812463
2382 // Import section2464 // Import section
2383 const import_memory = wasm.base.options.import_memory or is_obj;2465 const import_memory = options.import_memory or is_obj;
2384 const import_table = wasm.base.options.import_table or is_obj;2466 const import_table = options.import_table or is_obj;
2385 if (wasm.imports.count() != 0 or import_memory or import_table) {2467 if (wasm.imports.count() != 0 or import_memory or import_table) {
2386 const header_offset = try reserveVecSectionHeader(&binary_bytes);2468 const header_offset = try reserveVecSectionHeader(&binary_bytes);
23872469
2388 // import table is always first table so emit that first2470 // import table is always first table so emit that first
2389 if (import_table) {2471 if (import_table) {
2390 const table_imp: types.Import = .{2472 const table_imp: types.Import = .{
2391 .module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name),2473 .module_name = try wasm.string_table.put(gpa, wasm.host_name),
2392 .name = try wasm.string_table.put(wasm.base.allocator, "__indirect_function_table"),2474 .name = try wasm.string_table.put(gpa, "__indirect_function_table"),
2393 .kind = .{2475 .kind = .{
2394 .table = .{2476 .table = .{
2395 .limits = .{2477 .limits = .{
...@@ -2413,8 +2495,8 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2413,8 +2495,8 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2413 if (import_memory) {2495 if (import_memory) {
2414 const mem_name = if (is_obj) "__linear_memory" else "memory";2496 const mem_name = if (is_obj) "__linear_memory" else "memory";
2415 const mem_imp: types.Import = .{2497 const mem_imp: types.Import = .{
2416 .module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name),2498 .module_name = try wasm.string_table.put(gpa, wasm.host_name),
2417 .name = try wasm.string_table.put(wasm.base.allocator, mem_name),2499 .name = try wasm.string_table.put(gpa, mem_name),
2418 .kind = .{ .memory = wasm.memories.limits },2500 .kind = .{ .memory = wasm.memories.limits },
2419 };2501 };
2420 try wasm.emitImport(binary_writer, mem_imp);2502 try wasm.emitImport(binary_writer, mem_imp);
...@@ -2448,7 +2530,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2448,7 +2530,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2448 }2530 }
24492531
2450 // Table section2532 // Table section
2451 const export_table = wasm.base.options.export_table;2533 const export_table = options.export_table;
2452 if (!import_table and wasm.function_table.count() != 0) {2534 if (!import_table and wasm.function_table.count() != 0) {
2453 const header_offset = try reserveVecSectionHeader(&binary_bytes);2535 const header_offset = try reserveVecSectionHeader(&binary_bytes);
24542536
...@@ -2704,13 +2786,13 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2704,13 +2786,13 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2704 if (data_section_index) |data_index| {2786 if (data_section_index) |data_index| {
2705 try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table);2787 try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table);
2706 }2788 }
2707 } else if (!wasm.base.options.strip) {2789 } else if (!options.strip) {
2708 try wasm.emitNameSection(&binary_bytes, arena);2790 try wasm.emitNameSection(&binary_bytes, arena);
2709 }2791 }
27102792
2711 if (!wasm.base.options.strip) {2793 if (!options.strip) {
2712 if (wasm.dwarf) |*dwarf| {2794 if (wasm.dwarf) |*dwarf| {
2713 const mod = wasm.base.options.module.?;2795 const mod = options.module.?;
2714 try dwarf.writeDbgAbbrev();2796 try dwarf.writeDbgAbbrev();
2715 // for debug info and ranges, the address is always 0,2797 // for debug info and ranges, the address is always 0,
2716 // as locations are always offsets relative to 'code' section.2798 // 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...@@ -2719,7 +2801,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2719 try dwarf.writeDbgLineHeader();2801 try dwarf.writeDbgLineHeader();
2720 }2802 }
27212803
2722 var debug_bytes = std.ArrayList(u8).init(wasm.base.allocator);2804 var debug_bytes = std.ArrayList(u8).init(gpa);
2723 defer debug_bytes.deinit();2805 defer debug_bytes.deinit();
27242806
2725 const DebugSection = struct {2807 const DebugSection = struct {
...@@ -2768,6 +2850,21 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2768,6 +2850,21 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2768 .iov_len = binary_bytes.items.len,2850 .iov_len = binary_bytes.items.len,
2769 }};2851 }};
2770 try wasm.base.file.?.writevAll(&iovec);2852 try wasm.base.file.?.writevAll(&iovec);
2853
2854 if (!wasm.base.options.disable_lld_caching) {
2855 // Update the file with the digest. If it fails we can continue; it only
2856 // means that the next invocation will have an unnecessary cache miss.
2857 Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
2858 log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)});
2859 };
2860 // Again failure here only means an unnecessary cache miss.
2861 man.writeManifest() catch |err| {
2862 log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
2863 };
2864 // We hang on to this lock so that the output file path can be used without
2865 // other processes clobbering it.
2866 wasm.base.lock = man.toOwnedLock();
2867 }
2771}2868}
27722869
2773pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {2870pub 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...@@ -3422,7 +3519,9 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
3422 for (wasm.resolved_symbols.keys()) |sym_loc| {3519 for (wasm.resolved_symbols.keys()) |sym_loc| {
3423 const symbol = sym_loc.getSymbol(wasm).*;3520 const symbol = sym_loc.getSymbol(wasm).*;
3424 const name = if (symbol.isUndefined()) blk: {3521 const name = if (symbol.isUndefined()) blk: {
3425 break :blk wasm.string_table.get(wasm.imports.get(sym_loc).?.name);3522 if (symbol.tag == .data) continue;
3523 const imp = wasm.imports.get(sym_loc) orelse continue;
3524 break :blk wasm.string_table.get(imp.name);
3426 } else sym_loc.getName(wasm);3525 } else sym_loc.getName(wasm);
3427 switch (symbol.tag) {3526 switch (symbol.tag) {
3428 .function => {3527 .function => {