authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-01-03 15:47:16+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-01-03 15:47:16+01:00
log79351357672c6e1ed738466d98a580a15c8582c1
tree57c7d7a709a5a08f81e2666d3439965b5ba12839
parent1ec74f1b70607a17829277e846ea19be6aed1fc1
parentb9224c172fea2399623bd707a10e021e776329bc
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14157 from Luukdegram/wasm-linker-misc

wasm-linker: various fixes & improvements

11 files changed, 366 insertions(+), 147 deletions(-)

src/link/Wasm.zig+169-116
......@@ -112,8 +112,6 @@ func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
112112functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.wasm.Func) = .{},
113113/// Output global section
114114wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
115/// Global symbols for exported data symbols
116address_globals: std.ArrayListUnmanaged(SymbolLoc) = .{},
117115/// Memory section
118116memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
119117/// Output table section
......@@ -335,41 +333,64 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
335333 wasm_bin.base.file = file;
336334 wasm_bin.name = sub_path;
337335
338 // As sym_index '0' is reserved, we use it for our stack pointer symbol
339 const sym_name = try wasm_bin.string_table.put(allocator, "__stack_pointer");
340 const symbol = try wasm_bin.symbols.addOne(allocator);
341 symbol.* = .{
342 .name = sym_name,
343 .tag = .global,
344 .flags = 0,
345 .index = 0,
346 };
347 const loc: SymbolLoc = .{ .file = null, .index = 0 };
348 try wasm_bin.resolved_symbols.putNoClobber(allocator, loc, {});
349 try wasm_bin.globals.putNoClobber(allocator, sym_name, loc);
350
351 // For object files we will import the stack pointer symbol
352 if (options.output_mode == .Obj) {
353 symbol.setUndefined(true);
354 try wasm_bin.imports.putNoClobber(
355 allocator,
356 .{ .file = null, .index = 0 },
357 .{
358 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
359 .name = sym_name,
360 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
361 },
362 );
363 } else {
364 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
365 const global = try wasm_bin.wasm_globals.addOne(allocator);
366 global.* = .{
367 .global_type = .{
368 .valtype = .i32,
369 .mutable = true,
370 },
371 .init = .{ .i32_const = 0 },
336 // create stack pointer symbol
337 {
338 const loc = try wasm_bin.createSyntheticSymbol("__stack_pointer", .global);
339 const symbol = loc.getSymbol(wasm_bin);
340 // For object files we will import the stack pointer symbol
341 if (options.output_mode == .Obj) {
342 symbol.setUndefined(true);
343 symbol.index = @intCast(u32, wasm_bin.imported_globals_count);
344 wasm_bin.imported_globals_count += 1;
345 try wasm_bin.imports.putNoClobber(
346 allocator,
347 loc,
348 .{
349 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
350 .name = symbol.name,
351 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
352 },
353 );
354 } else {
355 symbol.index = @intCast(u32, wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
356 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
357 const global = try wasm_bin.wasm_globals.addOne(allocator);
358 global.* = .{
359 .global_type = .{
360 .valtype = .i32,
361 .mutable = true,
362 },
363 .init = .{ .i32_const = 0 },
364 };
365 }
366 }
367
368 // create indirect function pointer symbol
369 {
370 const loc = try wasm_bin.createSyntheticSymbol("__indirect_function_table", .table);
371 const symbol = loc.getSymbol(wasm_bin);
372 const table: std.wasm.Table = .{
373 .limits = .{ .min = 0, .max = null }, // will be overwritten during `mapFunctionTable`
374 .reftype = .funcref,
372375 };
376 if (options.output_mode == .Obj or options.import_table) {
377 symbol.setUndefined(true);
378 symbol.index = @intCast(u32, wasm_bin.imported_tables_count);
379 wasm_bin.imported_tables_count += 1;
380 try wasm_bin.imports.put(allocator, loc, .{
381 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
382 .name = symbol.name,
383 .kind = .{ .table = table },
384 });
385 } else {
386 symbol.index = @intCast(u32, wasm_bin.imported_tables_count + wasm_bin.tables.items.len);
387 try wasm_bin.tables.append(allocator, table);
388 if (options.export_table) {
389 symbol.setFlag(.WASM_SYM_EXPORTED);
390 } else {
391 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
392 }
393 }
373394 }
374395
375396 if (!options.strip and options.module != null) {
......@@ -400,6 +421,22 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
400421 return wasm;
401422}
402423
424/// For a given name, creates a new global synthetic symbol.
425/// Leaves index undefined and the default flags (0).
426fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !SymbolLoc {
427 const name_offset = try wasm.string_table.put(wasm.base.allocator, name);
428 const sym_index = @intCast(u32, wasm.symbols.items.len);
429 const loc: SymbolLoc = .{ .index = sym_index, .file = null };
430 try wasm.symbols.append(wasm.base.allocator, .{
431 .name = name_offset,
432 .flags = 0,
433 .tag = tag,
434 .index = undefined,
435 });
436 try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {});
437 try wasm.globals.putNoClobber(wasm.base.allocator, name_offset, loc);
438 return loc;
439}
403440/// Initializes symbols and atoms for the debug sections
404441/// Initialization is only done when compiling Zig code.
405442/// When Zig is invoked as a linker instead, the atoms
......@@ -766,6 +803,7 @@ fn validateFeatures(
766803
767804fn checkUndefinedSymbols(wasm: *const Wasm) !void {
768805 if (wasm.base.options.output_mode == .Obj) return;
806 if (wasm.base.options.import_symbols) return;
769807
770808 var found_undefined_symbols = false;
771809 for (wasm.undefs.values()) |undef| {
......@@ -775,7 +813,12 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {
775813 const file_name = if (undef.file) |file_index| name: {
776814 break :name wasm.objects.items[file_index].name;
777815 } else wasm.name;
778 log.err("could not resolve undefined symbol '{s}'", .{undef.getName(wasm)});
816 const import_name = if (undef.file) |file_index| name: {
817 const obj = wasm.objects.items[file_index];
818 const name_index = obj.findImport(symbol.tag.externalType(), symbol.index).name;
819 break :name obj.string_table.get(name_index);
820 } else wasm.string_table.get(wasm.imports.get(undef).?.name);
821 log.err("could not resolve undefined symbol '{s}'", .{import_name});
779822 log.err(" defined in '{s}'", .{file_name});
780823 }
781824 }
......@@ -840,7 +883,6 @@ pub fn deinit(wasm: *Wasm) void {
840883 wasm.func_types.deinit(gpa);
841884 wasm.functions.deinit(gpa);
842885 wasm.wasm_globals.deinit(gpa);
843 wasm.address_globals.deinit(gpa);
844886 wasm.function_table.deinit(gpa);
845887 wasm.tables.deinit(gpa);
846888 wasm.exports.deinit(gpa);
......@@ -1249,7 +1291,7 @@ pub fn updateDeclExports(
12491291 const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
12501292
12511293 const exp_is_weak = exp.options.linkage == .Internal or exp.options.linkage == .Weak;
1252 // When both the to-bo-exported symbol and the already existing symbol
1294 // When both the to-be-exported symbol and the already existing symbol
12531295 // are strong symbols, we have a linker error.
12541296 // In the other case we replace one with the other.
12551297 if (!exp_is_weak and !existing_sym.isWeak()) {
......@@ -1361,6 +1403,19 @@ fn mapFunctionTable(wasm: *Wasm) void {
13611403 while (it.next()) |value_ptr| : (index += 1) {
13621404 value_ptr.* = index;
13631405 }
1406
1407 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {
1408 const sym_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;
1409 const import = wasm.imports.getPtr(sym_loc).?;
1410 import.kind.table.limits.min = index - 1; // we start at index 1.
1411 } else if (index > 1) {
1412 log.debug("Appending indirect function table", .{});
1413 const offset = wasm.string_table.getOffset("__indirect_function_table").?;
1414 const sym_with_loc = wasm.globals.get(offset).?;
1415 const symbol = sym_with_loc.getSymbol(wasm);
1416 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
1417 table.limits = .{ .min = index, .max = index };
1418 }
13641419}
13651420
13661421/// Either creates a new import, or updates one if existing.
......@@ -1380,18 +1435,31 @@ pub fn addOrUpdateImport(
13801435 type_index: ?u32,
13811436) !void {
13821437 assert(symbol_index != 0);
1383 // For the import name itwasm, we use the decl's name, rather than the fully qualified name
1384 const decl_name_index = try wasm.string_table.put(wasm.base.allocator, name);
1438 // For the import name, we use the decl's name, rather than the fully qualified name
1439 // Also mangle the name when the lib name is set and not equal to "C" so imports with the same
1440 // name but different module can be resolved correctly.
1441 const mangle_name = lib_name != null and
1442 !std.mem.eql(u8, std.mem.sliceTo(lib_name.?, 0), "c");
1443 const full_name = if (mangle_name) full_name: {
1444 break :full_name try std.fmt.allocPrint(wasm.base.allocator, "{s}|{s}", .{ name, lib_name.? });
1445 } else name;
1446 defer if (mangle_name) wasm.base.allocator.free(full_name);
1447
1448 const decl_name_index = try wasm.string_table.put(wasm.base.allocator, full_name);
13851449 const symbol: *Symbol = &wasm.symbols.items[symbol_index];
13861450 symbol.setUndefined(true);
13871451 symbol.setGlobal(true);
13881452 symbol.name = decl_name_index;
1453 if (mangle_name) {
1454 // we specified a specific name for the symbol that does not match the import name
1455 symbol.setFlag(.WASM_SYM_EXPLICIT_NAME);
1456 }
13891457 const global_gop = try wasm.globals.getOrPut(wasm.base.allocator, decl_name_index);
13901458 if (!global_gop.found_existing) {
13911459 const loc: SymbolLoc = .{ .file = null, .index = symbol_index };
13921460 global_gop.value_ptr.* = loc;
13931461 try wasm.resolved_symbols.put(wasm.base.allocator, loc, {});
1394 try wasm.undefs.putNoClobber(wasm.base.allocator, name, loc);
1462 try wasm.undefs.putNoClobber(wasm.base.allocator, full_name, loc);
13951463 }
13961464
13971465 if (type_index) |ty_index| {
......@@ -1402,7 +1470,7 @@ pub fn addOrUpdateImport(
14021470 if (!gop.found_existing) {
14031471 gop.value_ptr.* = .{
14041472 .module_name = try wasm.string_table.put(wasm.base.allocator, module_name),
1405 .name = decl_name_index,
1473 .name = try wasm.string_table.put(wasm.base.allocator, name),
14061474 .kind = .{ .function = ty_index },
14071475 };
14081476 }
......@@ -1700,17 +1768,6 @@ fn setupImports(wasm: *Wasm) !void {
17001768/// Takes the global, function and table section from each linked object file
17011769/// and merges it into a single section for each.
17021770fn mergeSections(wasm: *Wasm) !void {
1703 // append the indirect function table if initialized
1704 if (wasm.string_table.getOffset("__indirect_function_table")) |offset| {
1705 const sym_loc = wasm.globals.get(offset).?;
1706 const table: std.wasm.Table = .{
1707 .limits = .{ .min = @intCast(u32, wasm.function_table.count()), .max = null },
1708 .reftype = .funcref,
1709 };
1710 sym_loc.getSymbol(wasm).index = @intCast(u32, wasm.tables.items.len) + wasm.imported_tables_count;
1711 try wasm.tables.append(wasm.base.allocator, table);
1712 }
1713
17141771 for (wasm.resolved_symbols.keys()) |sym_loc| {
17151772 if (sym_loc.file == null) {
17161773 // Zig code-generated symbols are already within the sections and do not
......@@ -1800,9 +1857,36 @@ fn setupExports(wasm: *Wasm) !void {
18001857 if (wasm.base.options.output_mode == .Obj) return;
18011858 log.debug("Building exports from symbols", .{});
18021859
1860 const force_exp_names = wasm.base.options.export_symbol_names;
1861 if (force_exp_names.len > 0) {
1862 var failed_exports = try std.ArrayList([]const u8).initCapacity(wasm.base.allocator, force_exp_names.len);
1863 defer failed_exports.deinit();
1864
1865 for (force_exp_names) |exp_name| {
1866 const name_index = wasm.string_table.getOffset(exp_name) orelse {
1867 failed_exports.appendAssumeCapacity(exp_name);
1868 continue;
1869 };
1870 const loc = wasm.globals.get(name_index) orelse {
1871 failed_exports.appendAssumeCapacity(exp_name);
1872 continue;
1873 };
1874
1875 const symbol = loc.getSymbol(wasm);
1876 symbol.setFlag(.WASM_SYM_EXPORTED);
1877 }
1878
1879 if (failed_exports.items.len > 0) {
1880 for (failed_exports.items) |exp_name| {
1881 log.err("could not export '{s}', symbol not found", .{exp_name});
1882 }
1883 return error.MissingSymbol;
1884 }
1885 }
1886
18031887 for (wasm.resolved_symbols.keys()) |sym_loc| {
18041888 const symbol = sym_loc.getSymbol(wasm);
1805 if (!symbol.isExported()) continue;
1889 if (!symbol.isExported(wasm.base.options.rdynamic)) continue;
18061890
18071891 const sym_name = sym_loc.getName(wasm);
18081892 const export_name = if (wasm.export_names.get(sym_loc)) |name| name else blk: {
......@@ -1810,8 +1894,13 @@ fn setupExports(wasm: *Wasm) !void {
18101894 break :blk try wasm.string_table.put(wasm.base.allocator, sym_name);
18111895 };
18121896 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);
1897 const atom = wasm.symbol_atom.get(sym_loc).?;
1898 const va = atom.getVA(wasm, symbol);
1899 const global_index = @intCast(u32, wasm.imported_globals_count + wasm.wasm_globals.items.len);
1900 try wasm.wasm_globals.append(wasm.base.allocator, .{
1901 .global_type = .{ .valtype = .i32, .mutable = false },
1902 .init = .{ .i32_const = @intCast(i32, va) },
1903 });
18151904 break :exp .{
18161905 .name = export_name,
18171906 .kind = .global,
......@@ -2399,6 +2488,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
23992488 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
24002489 try wasm.validateFeatures(&enabled_features, &emit_features_count);
24012490 try wasm.resolveSymbolsInArchives();
2491 try wasm.checkUndefinedSymbols();
24022492
24032493 try wasm.setupStart();
24042494 try wasm.setupImports();
......@@ -2586,28 +2676,9 @@ fn writeToFile(
25862676
25872677 // Import section
25882678 const import_memory = wasm.base.options.import_memory or is_obj;
2589 const import_table = wasm.base.options.import_table or is_obj;
2590 if (wasm.imports.count() != 0 or import_memory or import_table) {
2679 if (wasm.imports.count() != 0 or import_memory) {
25912680 const header_offset = try reserveVecSectionHeader(&binary_bytes);
25922681
2593 // import table is always first table so emit that first
2594 if (import_table) {
2595 const table_imp: types.Import = .{
2596 .module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name),
2597 .name = try wasm.string_table.put(wasm.base.allocator, "__indirect_function_table"),
2598 .kind = .{
2599 .table = .{
2600 .limits = .{
2601 .min = @intCast(u32, wasm.function_table.count()),
2602 .max = null,
2603 },
2604 .reftype = .funcref,
2605 },
2606 },
2607 };
2608 try wasm.emitImport(binary_writer, table_imp);
2609 }
2610
26112682 var it = wasm.imports.iterator();
26122683 while (it.next()) |entry| {
26132684 assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());
......@@ -2630,7 +2701,7 @@ fn writeToFile(
26302701 header_offset,
26312702 .import,
26322703 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2633 @intCast(u32, wasm.imports.count() + @boolToInt(import_memory) + @boolToInt(import_table)),
2704 @intCast(u32, wasm.imports.count() + @boolToInt(import_memory)),
26342705 );
26352706 section_count += 1;
26362707 }
......@@ -2653,22 +2724,20 @@ fn writeToFile(
26532724 }
26542725
26552726 // Table section
2656 const export_table = wasm.base.options.export_table;
2657 if (!import_table and wasm.function_table.count() != 0) {
2727 if (wasm.tables.items.len > 0) {
26582728 const header_offset = try reserveVecSectionHeader(&binary_bytes);
26592729
2660 try leb.writeULEB128(binary_writer, std.wasm.reftype(.funcref));
2661 try emitLimits(binary_writer, .{
2662 .min = @intCast(u32, wasm.function_table.count()) + 1,
2663 .max = null,
2664 });
2730 for (wasm.tables.items) |table| {
2731 try leb.writeULEB128(binary_writer, std.wasm.reftype(table.reftype));
2732 try emitLimits(binary_writer, table.limits);
2733 }
26652734
26662735 try writeVecSectionHeader(
26672736 binary_bytes.items,
26682737 header_offset,
26692738 .table,
26702739 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2671 @as(u32, 1),
2740 @intCast(u32, wasm.tables.items.len),
26722741 );
26732742 section_count += 1;
26742743 }
......@@ -2692,22 +2761,10 @@ fn writeToFile(
26922761 if (wasm.wasm_globals.items.len > 0) {
26932762 const header_offset = try reserveVecSectionHeader(&binary_bytes);
26942763
2695 var global_count: u32 = 0;
26962764 for (wasm.wasm_globals.items) |global| {
26972765 try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));
26982766 try binary_writer.writeByte(@boolToInt(global.global_type.mutable));
26992767 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;
27112768 }
27122769
27132770 try writeVecSectionHeader(
......@@ -2715,13 +2772,13 @@ fn writeToFile(
27152772 header_offset,
27162773 .global,
27172774 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2718 @intCast(u32, global_count),
2775 @intCast(u32, wasm.wasm_globals.items.len),
27192776 );
27202777 section_count += 1;
27212778 }
27222779
27232780 // Export section
2724 if (wasm.exports.items.len != 0 or export_table or !import_memory) {
2781 if (wasm.exports.items.len != 0 or !import_memory) {
27252782 const header_offset = try reserveVecSectionHeader(&binary_bytes);
27262783
27272784 for (wasm.exports.items) |exp| {
......@@ -2732,13 +2789,6 @@ fn writeToFile(
27322789 try leb.writeULEB128(binary_writer, exp.index);
27332790 }
27342791
2735 if (export_table) {
2736 try leb.writeULEB128(binary_writer, @intCast(u32, "__indirect_function_table".len));
2737 try binary_writer.writeAll("__indirect_function_table");
2738 try binary_writer.writeByte(std.wasm.externalKind(.table));
2739 try leb.writeULEB128(binary_writer, @as(u32, 0)); // function table is always the first table
2740 }
2741
27422792 if (!import_memory) {
27432793 try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));
27442794 try binary_writer.writeAll("memory");
......@@ -2751,7 +2801,7 @@ fn writeToFile(
27512801 header_offset,
27522802 .@"export",
27532803 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2754 @intCast(u32, wasm.exports.items.len) + @boolToInt(export_table) + @boolToInt(!import_memory),
2804 @intCast(u32, wasm.exports.items.len) + @boolToInt(!import_memory),
27552805 );
27562806 section_count += 1;
27572807 }
......@@ -2760,11 +2810,18 @@ fn writeToFile(
27602810 if (wasm.function_table.count() > 0) {
27612811 const header_offset = try reserveVecSectionHeader(&binary_bytes);
27622812
2763 var flags: u32 = 0x2; // Yes we have a table
2813 const table_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;
2814 const table_sym = table_loc.getSymbol(wasm);
2815
2816 var flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually
27642817 try leb.writeULEB128(binary_writer, flags);
2765 try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols
2818 if (flags == 0x02) {
2819 try leb.writeULEB128(binary_writer, table_sym.index);
2820 }
27662821 try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
2767 try leb.writeULEB128(binary_writer, @as(u8, 0));
2822 if (flags == 0x02) {
2823 try leb.writeULEB128(binary_writer, @as(u8, 0)); // represents funcref
2824 }
27682825 try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));
27692826 var symbol_it = wasm.function_table.keyIterator();
27702827 while (symbol_it.next()) |symbol_loc_ptr| {
......@@ -3091,11 +3148,7 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
30913148
30923149 for (wasm.resolved_symbols.keys()) |sym_loc| {
30933150 const symbol = sym_loc.getSymbol(wasm).*;
3094 const name = if (symbol.isUndefined()) blk: {
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);
3098 } else sym_loc.getName(wasm);
3151 const name = sym_loc.getName(wasm);
30993152 switch (symbol.tag) {
31003153 .function => {
31013154 const gop = funcs.getOrPutAssumeCapacity(symbol.index);
src/link/Wasm/Atom.zig+18-24
......@@ -90,24 +90,26 @@ pub fn getFirst(atom: *Atom) *Atom {
9090 return tmp;
9191}
9292
93/// Unlike `getFirst` this returns the first `*Atom` that was
94/// produced from Zig code, rather than an object file.
95/// This is useful for debug sections where we want to extend
96/// the bytes, and don't want to overwrite existing Atoms.
97pub fn getFirstZigAtom(atom: *Atom) *Atom {
98 if (atom.file == null) return atom;
99 var tmp = atom;
100 return while (tmp.prev) |prev| {
101 if (prev.file == null) break prev;
102 tmp = prev;
103 } else unreachable; // must allocate an Atom first!
104}
105
10693/// Returns the location of the symbol that represents this `Atom`
10794pub fn symbolLoc(atom: Atom) Wasm.SymbolLoc {
10895 return .{ .file = atom.file, .index = atom.sym_index };
10996}
11097
98/// Returns the virtual address of the `Atom`. This is the address starting
99/// from the first entry within a section.
100pub fn getVA(atom: Atom, wasm: *const Wasm, symbol: *const Symbol) u32 {
101 if (symbol.tag == .function) return atom.offset;
102 std.debug.assert(symbol.tag == .data);
103 const merge_segment = wasm.base.options.output_mode != .Obj;
104 const segment_info = if (atom.file) |object_index| blk: {
105 break :blk wasm.objects.items[object_index].segment_info;
106 } else wasm.segment_info.values();
107 const segment_name = segment_info[symbol.index].outputName(merge_segment);
108 const segment_index = wasm.data_segments.get(segment_name).?;
109 const segment = wasm.segments.items[segment_index];
110 return segment.offset + atom.offset;
111}
112
111113/// Resolves the relocations within the atom, writing the new value
112114/// at the calculated offset.
113115pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
......@@ -159,7 +161,7 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
159161/// The final value must be casted to the correct size.
160162fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 {
161163 const target_loc = (Wasm.SymbolLoc{ .file = atom.file, .index = relocation.index }).finalLoc(wasm_bin);
162 const symbol = target_loc.getSymbol(wasm_bin).*;
164 const symbol = target_loc.getSymbol(wasm_bin);
163165 switch (relocation.relocation_type) {
164166 .R_WASM_FUNCTION_INDEX_LEB => return symbol.index,
165167 .R_WASM_TABLE_NUMBER_LEB => return symbol.index,
......@@ -190,17 +192,9 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
190192 if (symbol.isUndefined()) {
191193 return 0;
192194 }
193
194 const merge_segment = wasm_bin.base.options.output_mode != .Obj;
195195 const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
196 const segment_info = if (target_atom.file) |object_index| blk: {
197 break :blk wasm_bin.objects.items[object_index].segment_info;
198 } else wasm_bin.segment_info.values();
199 const segment_name = segment_info[symbol.index].outputName(merge_segment);
200 const segment_index = wasm_bin.data_segments.get(segment_name).?;
201 const segment = wasm_bin.segments.items[segment_index];
202 const rel_value = @intCast(i32, target_atom.offset + segment.offset) + relocation.addend;
203 return @intCast(u32, rel_value);
196 const va = @intCast(i32, target_atom.getVA(wasm_bin, symbol));
197 return @intCast(u32, va + relocation.addend);
204198 },
205199 .R_WASM_EVENT_INDEX_LEB => return symbol.index,
206200 .R_WASM_SECTION_OFFSET_I32 => {
src/link/Wasm/Symbol.zig+3-5
......@@ -139,12 +139,10 @@ pub fn isNoStrip(symbol: Symbol) bool {
139139 return symbol.flags & @enumToInt(Flag.WASM_SYM_NO_STRIP) != 0;
140140}
141141
142pub fn isExported(symbol: Symbol) bool {
142pub fn isExported(symbol: Symbol, is_dynamic: bool) bool {
143143 if (symbol.isUndefined() or symbol.isLocal()) return false;
144 if (symbol.isHidden()) return false;
145 if (symbol.hasFlag(.WASM_SYM_EXPORTED)) return true;
146 if (symbol.hasFlag(.WASM_SYM_BINDING_WEAK)) return false;
147 return true;
144 if (is_dynamic and symbol.isVisible()) return true;
145 return symbol.hasFlag(.WASM_SYM_EXPORTED);
148146}
149147
150148pub fn isWeak(symbol: Symbol) bool {
test/link.zig+15
......@@ -42,6 +42,16 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {
4242 .requires_stage2 = true,
4343 });
4444
45 cases.addBuildFile("test/link/wasm/export/build.zig", .{
46 .build_modes = true,
47 .requires_stage2 = true,
48 });
49
50 // TODO: Fix open handle in wasm-linker refraining rename from working on Windows.
51 if (builtin.os.tag != .windows) {
52 cases.addBuildFile("test/link/wasm/export-data/build.zig", .{});
53 }
54
4555 cases.addBuildFile("test/link/wasm/extern/build.zig", .{
4656 .build_modes = true,
4757 .requires_stage2 = true,
......@@ -53,6 +63,11 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {
5363 .requires_stage2 = true,
5464 });
5565
66 cases.addBuildFile("test/link/wasm/function-table/build.zig", .{
67 .build_modes = true,
68 .requires_stage2 = true,
69 });
70
5671 cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{
5772 .requires_stage2 = true,
5873 });
test/link/wasm/bss/build.zig+1-2
......@@ -26,8 +26,7 @@ pub fn build(b: *Builder) void {
2626 check_lib.checkNext("name memory"); // as per linker specification
2727
2828 // since we are importing memory, ensure it's not exported
29 check_lib.checkStart("Section export");
30 check_lib.checkNext("entries 1"); // we're exporting function 'foo' so only 1 entry
29 check_lib.checkNotPresent("Section export");
3130
3231 // validate the name of the stack pointer
3332 check_lib.checkStart("Section custom");
test/link/wasm/export-data/build.zig created+39
......@@ -0,0 +1,39 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const test_step = b.step("test", "Test");
6 test_step.dependOn(b.getInstallStep());
7
8 const lib = b.addSharedLibrary("lib", "lib.zig", .unversioned);
9 lib.setBuildMode(.ReleaseSafe); // to make the output deterministic in address positions
10 lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
11 lib.use_lld = false;
12 lib.export_symbol_names = &.{ "foo", "bar" };
13 lib.global_base = 0; // put data section at address 0 to make data symbols easier to parse
14
15 const check_lib = lib.checkObject(.wasm);
16
17 check_lib.checkStart("Section global");
18 check_lib.checkNext("entries 3");
19 check_lib.checkNext("type i32"); // stack pointer so skip other fields
20 check_lib.checkNext("type i32");
21 check_lib.checkNext("mutable false");
22 check_lib.checkNext("i32.const {foo_address}");
23 check_lib.checkNext("type i32");
24 check_lib.checkNext("mutable false");
25 check_lib.checkNext("i32.const {bar_address}");
26 check_lib.checkComputeCompare("foo_address", .{ .op = .eq, .value = .{ .literal = 0 } });
27 check_lib.checkComputeCompare("bar_address", .{ .op = .eq, .value = .{ .literal = 4 } });
28
29 check_lib.checkStart("Section export");
30 check_lib.checkNext("entries 3");
31 check_lib.checkNext("name foo");
32 check_lib.checkNext("kind global");
33 check_lib.checkNext("index 1");
34 check_lib.checkNext("name bar");
35 check_lib.checkNext("kind global");
36 check_lib.checkNext("index 2");
37
38 test_step.dependOn(&check_lib.step);
39}
test/link/wasm/export-data/lib.zig created+2
......@@ -0,0 +1,2 @@
1export const foo: u32 = 0xbbbbbbbb;
2export const bar: u32 = 0xbbbbbbbb;
test/link/wasm/export/build.zig created+48
......@@ -0,0 +1,48 @@
1const std = @import("std");
2
3pub fn build(b: *std.build.Builder) void {
4 const mode = b.standardReleaseOptions();
5
6 const no_export = b.addSharedLibrary("no-export", "main.zig", .unversioned);
7 no_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
8 no_export.setBuildMode(mode);
9 no_export.use_llvm = false;
10 no_export.use_lld = false;
11
12 const dynamic_export = b.addSharedLibrary("dynamic", "main.zig", .unversioned);
13 dynamic_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
14 dynamic_export.setBuildMode(mode);
15 dynamic_export.rdynamic = true;
16 dynamic_export.use_llvm = false;
17 dynamic_export.use_lld = false;
18
19 const force_export = b.addSharedLibrary("force", "main.zig", .unversioned);
20 force_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
21 force_export.setBuildMode(mode);
22 force_export.export_symbol_names = &.{"foo"};
23 force_export.use_llvm = false;
24 force_export.use_lld = false;
25
26 const check_no_export = no_export.checkObject(.wasm);
27 check_no_export.checkStart("Section export");
28 check_no_export.checkNext("entries 1");
29 check_no_export.checkNext("name memory");
30 check_no_export.checkNext("kind memory");
31
32 const check_dynamic_export = dynamic_export.checkObject(.wasm);
33 check_dynamic_export.checkStart("Section export");
34 check_dynamic_export.checkNext("entries 2");
35 check_dynamic_export.checkNext("name foo");
36 check_dynamic_export.checkNext("kind function");
37
38 const check_force_export = force_export.checkObject(.wasm);
39 check_force_export.checkStart("Section export");
40 check_force_export.checkNext("entries 2");
41 check_force_export.checkNext("name foo");
42 check_force_export.checkNext("kind function");
43
44 const test_step = b.step("test", "Run linker test");
45 test_step.dependOn(&check_no_export.step);
46 test_step.dependOn(&check_dynamic_export.step);
47 test_step.dependOn(&check_force_export.step);
48}
test/link/wasm/export/main.zig created+1
......@@ -0,0 +1 @@
1export fn foo() void {}
test/link/wasm/function-table/build.zig created+63
......@@ -0,0 +1,63 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9
10 const import_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
11 import_table.setBuildMode(mode);
12 import_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
13 import_table.use_llvm = false;
14 import_table.use_lld = false;
15 import_table.import_table = true;
16
17 const export_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
18 export_table.setBuildMode(mode);
19 export_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
20 export_table.use_llvm = false;
21 export_table.use_lld = false;
22 export_table.export_table = true;
23
24 const regular_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
25 regular_table.setBuildMode(mode);
26 regular_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
27 regular_table.use_llvm = false;
28 regular_table.use_lld = false;
29
30 const check_import = import_table.checkObject(.wasm);
31 const check_export = export_table.checkObject(.wasm);
32 const check_regular = regular_table.checkObject(.wasm);
33
34 check_import.checkStart("Section import");
35 check_import.checkNext("entries 1");
36 check_import.checkNext("module env");
37 check_import.checkNext("name __indirect_function_table");
38 check_import.checkNext("kind table");
39 check_import.checkNext("type funcref");
40 check_import.checkNext("min 1"); // 1 function pointer
41 check_import.checkNotPresent("max"); // when importing, we do not provide a max
42 check_import.checkNotPresent("Section table"); // we're importing it
43
44 check_export.checkStart("Section export");
45 check_export.checkNext("entries 2");
46 check_export.checkNext("name __indirect_function_table"); // as per linker specification
47 check_export.checkNext("kind table");
48
49 check_regular.checkStart("Section table");
50 check_regular.checkNext("entries 1");
51 check_regular.checkNext("type funcref");
52 check_regular.checkNext("min 2"); // index starts at 1 & 1 function pointer = 2.
53 check_regular.checkNext("max 2");
54 check_regular.checkStart("Section element");
55 check_regular.checkNext("entries 1");
56 check_regular.checkNext("table index 0");
57 check_regular.checkNext("i32.const 1"); // we want to start function indexes at 1
58 check_regular.checkNext("indexes 1"); // 1 function pointer
59
60 test_step.dependOn(&check_import.step);
61 test_step.dependOn(&check_export.step);
62 test_step.dependOn(&check_regular.step);
63}
test/link/wasm/function-table/lib.zig created+7
......@@ -0,0 +1,7 @@
1var func: *const fn () void = &bar;
2
3export fn foo() void {
4 func();
5}
6
7fn bar() void {}