authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-12-20 18:42:14+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-12-20 18:42:14+01:00
logdc22c3b9a5ef382ed9c7fb4ff503c7d8be187cee
treee93ff39a7bedae4c8c637bd8493f2545cb900d89
parent4aa8462cc936820bc2c81ea1f3cfc613ff07a9f7
parent6f44e2d1d3dc1ac7942082dc98947f7beaa1f205
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14006 from Luukdegram/wasm-no-lld

wasm-linker: allow usage of the in-house WebAssembly linker

5 files changed, 309 insertions(+), 60 deletions(-)

src/link/Wasm.zig+281-28
...@@ -112,6 +112,8 @@ func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},...@@ -112,6 +112,8 @@ func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
112functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.wasm.Func) = .{},112functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.wasm.Func) = .{},
113/// Output global section113/// Output global section
114wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},114wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
115/// Global symbols for exported data symbols
116address_globals: std.ArrayListUnmanaged(SymbolLoc) = .{},
115/// Memory section117/// Memory section
116memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },118memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
117/// Output table section119/// Output table section
...@@ -311,13 +313,23 @@ pub const StringTable = struct {...@@ -311,13 +313,23 @@ pub const StringTable = struct {
311pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Wasm {313pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
312 assert(options.target.ofmt == .wasm);314 assert(options.target.ofmt == .wasm);
313315
314 if (build_options.have_llvm and options.use_llvm) {316 if (build_options.have_llvm and options.use_llvm and options.use_lld) {
315 return createEmpty(allocator, options);317 return createEmpty(allocator, options);
316 }318 }
317319
318 const wasm_bin = try createEmpty(allocator, options);320 const wasm_bin = try createEmpty(allocator, options);
319 errdefer wasm_bin.base.destroy();321 errdefer wasm_bin.base.destroy();
320322
323 // We are not using LLD at this point, so ensure we set the intermediary basename
324 if (build_options.have_llvm and options.use_llvm and options.module != null) {
325 // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`,
326 // we also want to put the intermediary object file in the cache while the
327 // main emit directory is the cwd.
328 wasm_bin.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{
329 options.emit.?.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch),
330 });
331 }
332
321 // TODO: read the file and keep valid parts instead of truncating333 // TODO: read the file and keep valid parts instead of truncating
322 const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });334 const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
323 wasm_bin.base.file = file;335 wasm_bin.base.file = file;
...@@ -613,7 +625,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {...@@ -613,7 +625,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {
613 try wasm.resolved_symbols.put(wasm.base.allocator, location, {});625 try wasm.resolved_symbols.put(wasm.base.allocator, location, {});
614 assert(wasm.resolved_symbols.swapRemove(existing_loc));626 assert(wasm.resolved_symbols.swapRemove(existing_loc));
615 if (existing_sym.isUndefined()) {627 if (existing_sym.isUndefined()) {
616 assert(wasm.undefs.swapRemove(sym_name));628 _ = wasm.undefs.swapRemove(sym_name);
617 }629 }
618 }630 }
619}631}
...@@ -624,8 +636,7 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {...@@ -624,8 +636,7 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
624 log.debug("Resolving symbols in archives", .{});636 log.debug("Resolving symbols in archives", .{});
625 var index: u32 = 0;637 var index: u32 = 0;
626 undef_loop: while (index < wasm.undefs.count()) {638 undef_loop: while (index < wasm.undefs.count()) {
627 const undef_sym_loc = wasm.undefs.values()[index];639 const sym_name = wasm.undefs.keys()[index];
628 const sym_name = undef_sym_loc.getName(wasm);
629640
630 for (wasm.archives.items) |archive| {641 for (wasm.archives.items) |archive| {
631 const offset = archive.toc.get(sym_name) orelse {642 const offset = archive.toc.get(sym_name) orelse {
...@@ -829,6 +840,7 @@ pub fn deinit(wasm: *Wasm) void {...@@ -829,6 +840,7 @@ pub fn deinit(wasm: *Wasm) void {
829 wasm.func_types.deinit(gpa);840 wasm.func_types.deinit(gpa);
830 wasm.functions.deinit(gpa);841 wasm.functions.deinit(gpa);
831 wasm.wasm_globals.deinit(gpa);842 wasm.wasm_globals.deinit(gpa);
843 wasm.address_globals.deinit(gpa);
832 wasm.function_table.deinit(gpa);844 wasm.function_table.deinit(gpa);
833 wasm.tables.deinit(gpa);845 wasm.tables.deinit(gpa);
834 wasm.exports.deinit(gpa);846 wasm.exports.deinit(gpa);
...@@ -1556,9 +1568,13 @@ fn allocateAtoms(wasm: *Wasm) !void {...@@ -1556,9 +1568,13 @@ fn allocateAtoms(wasm: *Wasm) !void {
1556 var atom: *Atom = entry.value_ptr.*.getFirst();1568 var atom: *Atom = entry.value_ptr.*.getFirst();
1557 var offset: u32 = 0;1569 var offset: u32 = 0;
1558 while (true) {1570 while (true) {
1571 const symbol_loc = atom.symbolLoc();
1572 if (!wasm.resolved_symbols.contains(symbol_loc)) {
1573 atom = atom.next orelse break;
1574 continue;
1575 }
1559 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);1576 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);
1560 atom.offset = offset;1577 atom.offset = offset;
1561 const symbol_loc = atom.symbolLoc();
1562 log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{1578 log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{
1563 symbol_loc.getName(wasm),1579 symbol_loc.getName(wasm),
1564 offset,1580 offset,
...@@ -1566,7 +1582,7 @@ fn allocateAtoms(wasm: *Wasm) !void {...@@ -1566,7 +1582,7 @@ fn allocateAtoms(wasm: *Wasm) !void {
1566 atom.size,1582 atom.size,
1567 });1583 });
1568 offset += atom.size;1584 offset += atom.size;
1569 try wasm.symbol_atom.put(wasm.base.allocator, atom.symbolLoc(), atom); // Update atom pointers1585 try wasm.symbol_atom.put(wasm.base.allocator, symbol_loc, atom); // Update atom pointers
1570 atom = atom.next orelse break;1586 atom = atom.next orelse break;
1571 }1587 }
1572 segment.size = std.mem.alignForwardGeneric(u32, offset, segment.alignment);1588 segment.size = std.mem.alignForwardGeneric(u32, offset, segment.alignment);
...@@ -1702,7 +1718,7 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -1702,7 +1718,7 @@ fn mergeSections(wasm: *Wasm) !void {
1702 continue;1718 continue;
1703 }1719 }
17041720
1705 const object = wasm.objects.items[sym_loc.file.?];1721 const object = &wasm.objects.items[sym_loc.file.?];
1706 const symbol = &object.symtable[sym_loc.index];1722 const symbol = &object.symtable[sym_loc.index];
1707 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)) {
1708 // Skip undefined symbols as they go in the `import` section1724 // Skip undefined symbols as they go in the `import` section
...@@ -1714,13 +1730,12 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -1714,13 +1730,12 @@ fn mergeSections(wasm: *Wasm) !void {
1714 const index = symbol.index - offset;1730 const index = symbol.index - offset;
1715 switch (symbol.tag) {1731 switch (symbol.tag) {
1716 .function => {1732 .function => {
1717 const original_func = object.functions[index];
1718 const gop = try wasm.functions.getOrPut(1733 const gop = try wasm.functions.getOrPut(
1719 wasm.base.allocator,1734 wasm.base.allocator,
1720 .{ .file = sym_loc.file, .index = symbol.index },1735 .{ .file = sym_loc.file, .index = symbol.index },
1721 );1736 );
1722 if (!gop.found_existing) {1737 if (!gop.found_existing) {
1723 gop.value_ptr.* = original_func;1738 gop.value_ptr.* = object.functions[index];
1724 }1739 }
1725 symbol.index = @intCast(u32, gop.index) + wasm.imported_functions_count;1740 symbol.index = @intCast(u32, gop.index) + wasm.imported_functions_count;
1726 },1741 },
...@@ -1768,7 +1783,7 @@ fn mergeTypes(wasm: *Wasm) !void {...@@ -1768,7 +1783,7 @@ fn mergeTypes(wasm: *Wasm) !void {
17681783
1769 if (symbol.isUndefined()) {1784 if (symbol.isUndefined()) {
1770 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)});
1771 const import: *types.Import = wasm.imports.getPtr(sym_loc).?;1786 const import: *types.Import = wasm.imports.getPtr(sym_loc) orelse continue;
1772 const original_type = object.func_types[import.kind.function];1787 const original_type = object.func_types[import.kind.function];
1773 import.kind.function = try wasm.putOrGetFuncType(original_type);1788 import.kind.function = try wasm.putOrGetFuncType(original_type);
1774 } else if (!dirty.contains(symbol.index)) {1789 } else if (!dirty.contains(symbol.index)) {
...@@ -1794,7 +1809,15 @@ fn setupExports(wasm: *Wasm) !void {...@@ -1794,7 +1809,15 @@ fn setupExports(wasm: *Wasm) !void {
1794 if (sym_loc.file == null) break :blk symbol.name;1809 if (sym_loc.file == null) break :blk symbol.name;
1795 break :blk try wasm.string_table.put(wasm.base.allocator, sym_name);1810 break :blk try wasm.string_table.put(wasm.base.allocator, sym_name);
1796 };1811 };
1797 const exp: types.Export = .{1812 const exp: types.Export = if (symbol.tag == .data) exp: {
1813 const global_index = @intCast(u32, wasm.wasm_globals.items.len + wasm.address_globals.items.len);
1814 try wasm.address_globals.append(wasm.base.allocator, sym_loc);
1815 break :exp .{
1816 .name = export_name,
1817 .kind = .global,
1818 .index = global_index,
1819 };
1820 } else .{
1798 .name = export_name,1821 .name = export_name,
1799 .kind = symbol.tag.externalType(),1822 .kind = symbol.tag.externalType(),
1800 .index = symbol.index,1823 .index = symbol.index,
...@@ -2134,7 +2157,6 @@ pub fn createDebugSectionForIndex(wasm: *Wasm, index: *?u32, name: []const u8) !...@@ -2134,7 +2157,6 @@ pub fn createDebugSectionForIndex(wasm: *Wasm, index: *?u32, name: []const u8) !
2134 const new_index = @intCast(u32, wasm.segments.items.len);2157 const new_index = @intCast(u32, wasm.segments.items.len);
2135 index.* = new_index;2158 index.* = new_index;
2136 try wasm.appendDummySegment();2159 try wasm.appendDummySegment();
2137 // _ = index;
21382160
2139 const sym_index = wasm.symbols_free_list.popOrNull() orelse idx: {2161 const sym_index = wasm.symbols_free_list.popOrNull() orelse idx: {
2140 const tmp_index = @intCast(u32, wasm.symbols.items.len);2162 const tmp_index = @intCast(u32, wasm.symbols.items.len);
...@@ -2202,13 +2224,213 @@ pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) lin...@@ -2202,13 +2224,213 @@ pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) lin
2202 }2224 }
2203 return;2225 return;
2204 }2226 }
2227
2205 if (build_options.have_llvm and wasm.base.options.use_lld) {2228 if (build_options.have_llvm and wasm.base.options.use_lld) {
2206 return wasm.linkWithLLD(comp, prog_node);2229 return wasm.linkWithLLD(comp, prog_node);
2230 } else if (build_options.have_llvm and wasm.base.options.use_llvm and !wasm.base.options.use_lld) {
2231 return wasm.linkWithZld(comp, prog_node);
2207 } else {2232 } else {
2208 return wasm.flushModule(comp, prog_node);2233 return wasm.flushModule(comp, prog_node);
2209 }2234 }
2210}2235}
22112236
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 {
2239 const tracy = trace(@src());
2240 defer tracy.end();
2241
2242 const gpa = wasm.base.allocator;
2243 const options = wasm.base.options;
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.?;
2263 }
2264 } else null;
2265
2266 var sub_prog_node = prog_node.start("Wasm Flush", 0);
2267 sub_prog_node.activate();
2268 defer sub_prog_node.end();
2269
2270 const is_obj = options.output_mode == .Obj;
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 }
2343
2344 // Positional arguments to the linker such as object files and static archives.
2345 var positionals = std.ArrayList([]const u8).init(arena);
2346 try positionals.ensureUnusedCapacity(options.objects.len);
2347
2348 // When the target os is WASI, we allow linking with WASI-LIBC
2349 if (options.target.os.tag == .wasi) {
2350 const is_exe_or_dyn_lib = wasm.base.options.output_mode == .Exe or
2351 (wasm.base.options.output_mode == .Lib and wasm.base.options.link_mode == .Dynamic);
2352 if (is_exe_or_dyn_lib) {
2353 const wasi_emulated_libs = wasm.base.options.wasi_emulated_libs;
2354 for (wasi_emulated_libs) |crt_file| {
2355 try positionals.append(try comp.get_libc_crt_file(
2356 arena,
2357 wasi_libc.emulatedLibCRFileLibName(crt_file),
2358 ));
2359 }
2360
2361 if (wasm.base.options.link_libc) {
2362 try positionals.append(try comp.get_libc_crt_file(
2363 arena,
2364 wasi_libc.execModelCrtFileFullName(wasm.base.options.wasi_exec_model),
2365 ));
2366 try positionals.append(try comp.get_libc_crt_file(arena, "libc.a"));
2367 }
2368
2369 if (wasm.base.options.link_libcpp) {
2370 try positionals.append(comp.libcxx_static_lib.?.full_object_path);
2371 try positionals.append(comp.libcxxabi_static_lib.?.full_object_path);
2372 }
2373 }
2374 }
2375
2376 if (module_obj_path) |path| {
2377 try positionals.append(path);
2378 }
2379
2380 for (options.objects) |object| {
2381 try positionals.append(object.path);
2382 }
2383
2384 for (comp.c_object_table.keys()) |c_object| {
2385 try positionals.append(c_object.status.success.object_path);
2386 }
2387
2388 if (comp.compiler_rt_lib) |lib| {
2389 try positionals.append(lib.full_object_path);
2390 }
2391
2392 try wasm.parseInputFiles(positionals.items);
2393
2394 for (wasm.objects.items) |_, object_index| {
2395 try wasm.resolveSymbolsInObject(@intCast(u16, object_index));
2396 }
2397
2398 var emit_features_count: u32 = 0;
2399 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
2400 try wasm.validateFeatures(&enabled_features, &emit_features_count);
2401 try wasm.resolveSymbolsInArchives();
2402
2403 try wasm.setupStart();
2404 try wasm.setupImports();
2405
2406 for (wasm.objects.items) |*object, object_index| {
2407 try object.parseIntoAtoms(gpa, @intCast(u16, object_index), wasm);
2408 }
2409
2410 try wasm.allocateAtoms();
2411 try wasm.setupMemory();
2412 wasm.mapFunctionTable();
2413 try wasm.mergeSections();
2414 try wasm.mergeTypes();
2415 try wasm.setupExports();
2416 try wasm.writeToFile(enabled_features, emit_features_count, arena);
2417
2418 if (!wasm.base.options.disable_lld_caching) {
2419 // Update the file with the digest. If it fails we can continue; it only
2420 // means that the next invocation will have an unnecessary cache miss.
2421 Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
2422 log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)});
2423 };
2424 // Again failure here only means an unnecessary cache miss.
2425 man.writeManifest() catch |err| {
2426 log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
2427 };
2428 // We hang on to this lock so that the output file path can be used without
2429 // other processes clobbering it.
2430 wasm.base.lock = man.toOwnedLock();
2431 }
2432}
2433
2212pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {2434pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
2213 const tracy = trace(@src());2435 const tracy = trace(@src());
2214 defer tracy.end();2436 defer tracy.end();
...@@ -2219,20 +2441,13 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2219,20 +2441,13 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2219 }2441 }
2220 }2442 }
22212443
2222 var sub_prog_node = prog_node.start("WASM Flush", 0);2444 var sub_prog_node = prog_node.start("Wasm Flush", 0);
2223 sub_prog_node.activate();2445 sub_prog_node.activate();
2224 defer sub_prog_node.end();2446 defer sub_prog_node.end();
22252447
2226 // ensure the error names table is populated when an error name is referenced2448 // ensure the error names table is populated when an error name is referenced
2227 try wasm.populateErrorNameTable();2449 try wasm.populateErrorNameTable();
22282450
2229 // The amount of sections that will be written
2230 var section_count: u32 = 0;
2231 // Index of the code section. Used to tell relocation table where the section lives.
2232 var code_section_index: ?u32 = null;
2233 // Index of the data section. Used to tell relocation table where the section lives.
2234 var data_section_index: ?u32 = null;
2235
2236 // Used for all temporary memory allocated during flushin2451 // Used for all temporary memory allocated during flushin
2237 var arena_instance = std.heap.ArenaAllocator.init(wasm.base.allocator);2452 var arena_instance = std.heap.ArenaAllocator.init(wasm.base.allocator);
2238 defer arena_instance.deinit();2453 defer arena_instance.deinit();
...@@ -2312,9 +2527,25 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2312,9 +2527,25 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2312 try wasm.mergeSections();2527 try wasm.mergeSections();
2313 try wasm.mergeTypes();2528 try wasm.mergeTypes();
2314 try wasm.setupExports();2529 try wasm.setupExports();
2530 try wasm.writeToFile(enabled_features, emit_features_count, arena);
2531}
23152532
2533/// Writes the WebAssembly in-memory module to the file
2534fn writeToFile(
2535 wasm: *Wasm,
2536 enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool,
2537 feature_count: u32,
2538 arena: Allocator,
2539) !void {
2540 // Size of each section header
2316 const header_size = 5 + 1;2541 const header_size = 5 + 1;
2317 const is_obj = wasm.base.options.output_mode == .Obj;2542 // The amount of sections that will be written
2543 var section_count: u32 = 0;
2544 // Index of the code section. Used to tell relocation table where the section lives.
2545 var code_section_index: ?u32 = null;
2546 // Index of the data section. Used to tell relocation table where the section lives.
2547 var data_section_index: ?u32 = null;
2548 const is_obj = wasm.base.options.output_mode == .Obj or (!wasm.base.options.use_llvm and wasm.base.options.use_lld);
23182549
2319 var binary_bytes = std.ArrayList(u8).init(wasm.base.allocator);2550 var binary_bytes = std.ArrayList(u8).init(wasm.base.allocator);
2320 defer binary_bytes.deinit();2551 defer binary_bytes.deinit();
...@@ -2461,10 +2692,22 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2461,10 +2692,22 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2461 if (wasm.wasm_globals.items.len > 0) {2692 if (wasm.wasm_globals.items.len > 0) {
2462 const header_offset = try reserveVecSectionHeader(&binary_bytes);2693 const header_offset = try reserveVecSectionHeader(&binary_bytes);
24632694
2695 var global_count: u32 = 0;
2464 for (wasm.wasm_globals.items) |global| {2696 for (wasm.wasm_globals.items) |global| {
2465 try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));2697 try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));
2466 try binary_writer.writeByte(@boolToInt(global.global_type.mutable));2698 try binary_writer.writeByte(@boolToInt(global.global_type.mutable));
2467 try emitInit(binary_writer, global.init);2699 try emitInit(binary_writer, global.init);
2700 global_count += 1;
2701 }
2702
2703 for (wasm.address_globals.items) |sym_loc| {
2704 const atom = wasm.symbol_atom.get(sym_loc).?;
2705 try binary_writer.writeByte(std.wasm.valtype(.i32));
2706 try binary_writer.writeByte(0); // immutable
2707 try emitInit(binary_writer, .{
2708 .i32_const = @bitCast(i32, atom.offset),
2709 });
2710 global_count += 1;
2468 }2711 }
24692712
2470 try writeVecSectionHeader(2713 try writeVecSectionHeader(
...@@ -2472,7 +2715,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2472,7 +2715,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2472 header_offset,2715 header_offset,
2473 .global,2716 .global,
2474 @intCast(u32, binary_bytes.items.len - header_offset - header_size),2717 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2475 @intCast(u32, wasm.wasm_globals.items.len),2718 @intCast(u32, global_count),
2476 );2719 );
2477 section_count += 1;2720 section_count += 1;
2478 }2721 }
...@@ -2708,8 +2951,8 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2708,8 +2951,8 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2708 }2951 }
27092952
2710 try emitProducerSection(&binary_bytes);2953 try emitProducerSection(&binary_bytes);
2711 if (emit_features_count > 0) {2954 if (feature_count > 0) {
2712 try emitFeaturesSection(&binary_bytes, &enabled_features, emit_features_count);2955 try emitFeaturesSection(&binary_bytes, &enabled_features, feature_count);
2713 }2956 }
2714 }2957 }
27152958
...@@ -2849,7 +3092,9 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem...@@ -2849,7 +3092,9 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
2849 for (wasm.resolved_symbols.keys()) |sym_loc| {3092 for (wasm.resolved_symbols.keys()) |sym_loc| {
2850 const symbol = sym_loc.getSymbol(wasm).*;3093 const symbol = sym_loc.getSymbol(wasm).*;
2851 const name = if (symbol.isUndefined()) blk: {3094 const name = if (symbol.isUndefined()) blk: {
2852 break :blk wasm.string_table.get(wasm.imports.get(sym_loc).?.name);3095 if (symbol.tag == .data) continue;
3096 const imp = wasm.imports.get(sym_loc) orelse continue;
3097 break :blk wasm.string_table.get(imp.name);
2853 } else sym_loc.getName(wasm);3098 } else sym_loc.getName(wasm);
2854 switch (symbol.tag) {3099 switch (symbol.tag) {
2855 .function => {3100 .function => {
...@@ -3607,15 +3852,23 @@ fn emitDataRelocations(...@@ -3607,15 +3852,23 @@ fn emitDataRelocations(
3607 try writeCustomSectionHeader(binary_bytes.items, header_offset, size);3852 try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
3608}3853}
36093854
3610/// Searches for an a matching function signature, when not found3855pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {
3611/// a new entry will be made. The index of the existing/new signature will be returned.
3612pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 {
3613 var index: u32 = 0;3856 var index: u32 = 0;
3614 while (index < wasm.func_types.items.len) : (index += 1) {3857 while (index < wasm.func_types.items.len) : (index += 1) {
3615 if (wasm.func_types.items[index].eql(func_type)) return index;3858 if (wasm.func_types.items[index].eql(func_type)) return index;
3616 }3859 }
3860 return null;
3861}
3862
3863/// Searches for an a matching function signature, when not found
3864/// a new entry will be made. The index of the existing/new signature will be returned.
3865pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 {
3866 if (wasm.getTypeIndex(func_type)) |index| {
3867 return index;
3868 }
36173869
3618 // functype does not exist.3870 // functype does not exist.
3871 const index = @intCast(u32, wasm.func_types.items.len);
3619 const params = try wasm.base.allocator.dupe(std.wasm.Valtype, func_type.params);3872 const params = try wasm.base.allocator.dupe(std.wasm.Valtype, func_type.params);
3620 errdefer wasm.base.allocator.free(params);3873 errdefer wasm.base.allocator.free(params);
3621 const returns = try wasm.base.allocator.dupe(std.wasm.Valtype, func_type.returns);3874 const returns = try wasm.base.allocator.dupe(std.wasm.Valtype, func_type.returns);
src/link/Wasm/Archive.zig+7-8
...@@ -157,13 +157,12 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype...@@ -157,13 +157,12 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype
157 };157 };
158158
159 var i: usize = 0;159 var i: usize = 0;
160 while (i < sym_tab.len) {160 var pos: usize = 0;
161 const string = mem.sliceTo(sym_tab[i..], 0);161 while (i < num_symbols) : (i += 1) {
162 if (string.len == 0) {162 const string = mem.sliceTo(sym_tab[pos..], 0);
163 i += 1;163 pos += string.len + 1;
164 continue;164 if (string.len == 0) continue;
165 }165
166 i += string.len;
167 const name = try allocator.dupe(u8, string);166 const name = try allocator.dupe(u8, string);
168 errdefer allocator.free(name);167 errdefer allocator.free(name);
169 const gop = try archive.toc.getOrPut(allocator, name);168 const gop = try archive.toc.getOrPut(allocator, name);
...@@ -172,7 +171,7 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype...@@ -172,7 +171,7 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype
172 } else {171 } else {
173 gop.value_ptr.* = .{};172 gop.value_ptr.* = .{};
174 }173 }
175 try gop.value_ptr.append(allocator, symbol_positions[gop.index]);174 try gop.value_ptr.append(allocator, symbol_positions[i]);
176 }175 }
177}176}
178177
src/link/Wasm/Atom.zig+16-19
...@@ -168,12 +168,13 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa...@@ -168,12 +168,13 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
168 .R_WASM_TABLE_INDEX_SLEB,168 .R_WASM_TABLE_INDEX_SLEB,
169 .R_WASM_TABLE_INDEX_SLEB64,169 .R_WASM_TABLE_INDEX_SLEB64,
170 => return wasm_bin.function_table.get(target_loc) orelse 0,170 => return wasm_bin.function_table.get(target_loc) orelse 0,
171 .R_WASM_TYPE_INDEX_LEB => return blk: {171 .R_WASM_TYPE_INDEX_LEB => {
172 if (symbol.isUndefined()) {172 const file_index = atom.file orelse {
173 const imp = wasm_bin.imports.get(target_loc).?;173 return relocation.index;
174 break :blk imp.kind.function;174 };
175 }175
176 break :blk wasm_bin.functions.values()[symbol.index - wasm_bin.imported_functions_count].type_index;176 const original_type = wasm_bin.objects.items[file_index].func_types[relocation.index];
177 return wasm_bin.getTypeIndex(original_type).?;
177 },178 },
178 .R_WASM_GLOBAL_INDEX_I32,179 .R_WASM_GLOBAL_INDEX_I32,
179 .R_WASM_GLOBAL_INDEX_LEB,180 .R_WASM_GLOBAL_INDEX_LEB,
...@@ -185,7 +186,11 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa...@@ -185,7 +186,11 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
185 .R_WASM_MEMORY_ADDR_SLEB,186 .R_WASM_MEMORY_ADDR_SLEB,
186 .R_WASM_MEMORY_ADDR_SLEB64,187 .R_WASM_MEMORY_ADDR_SLEB64,
187 => {188 => {
188 std.debug.assert(symbol.tag == .data and !symbol.isUndefined());189 std.debug.assert(symbol.tag == .data);
190 if (symbol.isUndefined()) {
191 return 0;
192 }
193
189 const merge_segment = wasm_bin.base.options.output_mode != .Obj;194 const merge_segment = wasm_bin.base.options.output_mode != .Obj;
190 const target_atom = wasm_bin.symbol_atom.get(target_loc).?;195 const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
191 const segment_info = if (target_atom.file) |object_index| blk: {196 const segment_info = if (target_atom.file) |object_index| blk: {
...@@ -204,18 +209,10 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa...@@ -204,18 +209,10 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
204 return @intCast(u32, rel_value);209 return @intCast(u32, rel_value);
205 },210 },
206 .R_WASM_FUNCTION_OFFSET_I32 => {211 .R_WASM_FUNCTION_OFFSET_I32 => {
207 const target_atom = wasm_bin.symbol_atom.get(target_loc).?;212 const target_atom = wasm_bin.symbol_atom.get(target_loc) orelse {
208 var current_atom = target_atom.getFirst();213 return @bitCast(u32, @as(i32, -1));
209 var offset: u32 = 0;214 };
210 // TODO: Calculate this during atom allocation, rather than215 const offset: u32 = 11 + Wasm.getULEB128Size(target_atom.size); // Header (11 bytes fixed-size) + body size (leb-encoded)
211 // this linear calculation. For now it's done here as atoms
212 // are being sorted after atom allocation, as functions aren't
213 // merged until later.
214 while (true) {
215 offset += 5; // each atom uses 5 bytes to store its body's size
216 if (current_atom == target_atom) break;
217 current_atom = current_atom.next.?;
218 }
219 const rel_value = @intCast(i32, target_atom.offset + offset) + relocation.addend;216 const rel_value = @intCast(i32, target_atom.offset + offset) + relocation.addend;
220 return @intCast(u32, rel_value);217 return @intCast(u32, rel_value);
221 },218 },
src/link/Wasm/Object.zig+4-4
...@@ -923,7 +923,7 @@ pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_b...@@ -923,7 +923,7 @@ pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_b
923 try atom.relocs.append(gpa, reloc);923 try atom.relocs.append(gpa, reloc);
924924
925 if (relocation.isTableIndex()) {925 if (relocation.isTableIndex()) {
926 try wasm_bin.function_table.putNoClobber(gpa, .{926 try wasm_bin.function_table.put(gpa, .{
927 .file = object_index,927 .file = object_index,
928 .index = relocation.index,928 .index = relocation.index,
929 }, 0);929 }, 0);
...@@ -938,17 +938,17 @@ pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_b...@@ -938,17 +938,17 @@ pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_b
938 .index = relocatable_data.getIndex(),938 .index = relocatable_data.getIndex(),
939 })) |symbols| {939 })) |symbols| {
940 atom.sym_index = symbols.pop();940 atom.sym_index = symbols.pop();
941 try wasm_bin.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), atom);
941942
942 // symbols referencing the same atom will be added as alias943 // symbols referencing the same atom will be added as alias
943 // or as 'parent' when they are global.944 // or as 'parent' when they are global.
944 while (symbols.popOrNull()) |idx| {945 while (symbols.popOrNull()) |idx| {
946 try wasm_bin.symbol_atom.putNoClobber(gpa, .{ .file = atom.file, .index = idx }, atom);
945 const alias_symbol = object.symtable[idx];947 const alias_symbol = object.symtable[idx];
946 const symbol = object.symtable[atom.sym_index];948 if (alias_symbol.isGlobal()) {
947 if (alias_symbol.isGlobal() and symbol.isLocal()) {
948 atom.sym_index = idx;949 atom.sym_index = idx;
949 }950 }
950 }951 }
951 try wasm_bin.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), atom);
952 }952 }
953953
954 const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index];954 const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index];
src/link/Wasm/Symbol.zig+1-1
...@@ -38,7 +38,7 @@ pub const Tag = enum {...@@ -38,7 +38,7 @@ pub const Tag = enum {
38 return switch (tag) {38 return switch (tag) {
39 .function => .function,39 .function => .function,
40 .global => .global,40 .global => .global,
41 .data => .memory,41 .data => unreachable, // Data symbols will generate a global
42 .section => unreachable, // Not an external type42 .section => unreachable, // Not an external type
43 .event => unreachable, // Not an external type43 .event => unreachable, // Not an external type
44 .dead => unreachable, // Dead symbols should not be referenced44 .dead => unreachable, // Dead symbols should not be referenced