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) = .{},...@@ -112,8 +112,6 @@ 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) = .{},
117/// Memory section115/// Memory section
118memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },116memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
119/// Output table section117/// Output table section
...@@ -335,41 +333,64 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -335,41 +333,64 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
335 wasm_bin.base.file = file;333 wasm_bin.base.file = file;
336 wasm_bin.name = sub_path;334 wasm_bin.name = sub_path;
337335
338 // As sym_index '0' is reserved, we use it for our stack pointer symbol336 // create stack pointer symbol
339 const sym_name = try wasm_bin.string_table.put(allocator, "__stack_pointer");337 {
340 const symbol = try wasm_bin.symbols.addOne(allocator);338 const loc = try wasm_bin.createSyntheticSymbol("__stack_pointer", .global);
341 symbol.* = .{339 const symbol = loc.getSymbol(wasm_bin);
342 .name = sym_name,340 // For object files we will import the stack pointer symbol
343 .tag = .global,341 if (options.output_mode == .Obj) {
344 .flags = 0,342 symbol.setUndefined(true);
345 .index = 0,343 symbol.index = @intCast(u32, wasm_bin.imported_globals_count);
346 };344 wasm_bin.imported_globals_count += 1;
347 const loc: SymbolLoc = .{ .file = null, .index = 0 };345 try wasm_bin.imports.putNoClobber(
348 try wasm_bin.resolved_symbols.putNoClobber(allocator, loc, {});346 allocator,
349 try wasm_bin.globals.putNoClobber(allocator, sym_name, loc);347 loc,
350348 .{
351 // For object files we will import the stack pointer symbol349 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
352 if (options.output_mode == .Obj) {350 .name = symbol.name,
353 symbol.setUndefined(true);351 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
354 try wasm_bin.imports.putNoClobber(352 },
355 allocator,353 );
356 .{ .file = null, .index = 0 },354 } else {
357 .{355 symbol.index = @intCast(u32, wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
358 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),356 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
359 .name = sym_name,357 const global = try wasm_bin.wasm_globals.addOne(allocator);
360 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },358 global.* = .{
361 },359 .global_type = .{
362 );360 .valtype = .i32,
363 } else {361 .mutable = true,
364 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);362 },
365 const global = try wasm_bin.wasm_globals.addOne(allocator);363 .init = .{ .i32_const = 0 },
366 global.* = .{364 };
367 .global_type = .{365 }
368 .valtype = .i32,366 }
369 .mutable = true,367
370 },368 // create indirect function pointer symbol
371 .init = .{ .i32_const = 0 },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,
372 };375 };
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 }
373 }394 }
374395
375 if (!options.strip and options.module != null) {396 if (!options.strip and options.module != null) {
...@@ -400,6 +421,22 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {...@@ -400,6 +421,22 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
400 return wasm;421 return wasm;
401}422}
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}
403/// Initializes symbols and atoms for the debug sections440/// Initializes symbols and atoms for the debug sections
404/// Initialization is only done when compiling Zig code.441/// Initialization is only done when compiling Zig code.
405/// When Zig is invoked as a linker instead, the atoms442/// When Zig is invoked as a linker instead, the atoms
...@@ -766,6 +803,7 @@ fn validateFeatures(...@@ -766,6 +803,7 @@ fn validateFeatures(
766803
767fn checkUndefinedSymbols(wasm: *const Wasm) !void {804fn checkUndefinedSymbols(wasm: *const Wasm) !void {
768 if (wasm.base.options.output_mode == .Obj) return;805 if (wasm.base.options.output_mode == .Obj) return;
806 if (wasm.base.options.import_symbols) return;
769807
770 var found_undefined_symbols = false;808 var found_undefined_symbols = false;
771 for (wasm.undefs.values()) |undef| {809 for (wasm.undefs.values()) |undef| {
...@@ -775,7 +813,12 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {...@@ -775,7 +813,12 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {
775 const file_name = if (undef.file) |file_index| name: {813 const file_name = if (undef.file) |file_index| name: {
776 break :name wasm.objects.items[file_index].name;814 break :name wasm.objects.items[file_index].name;
777 } else wasm.name;815 } 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});
779 log.err(" defined in '{s}'", .{file_name});822 log.err(" defined in '{s}'", .{file_name});
780 }823 }
781 }824 }
...@@ -840,7 +883,6 @@ pub fn deinit(wasm: *Wasm) void {...@@ -840,7 +883,6 @@ pub fn deinit(wasm: *Wasm) void {
840 wasm.func_types.deinit(gpa);883 wasm.func_types.deinit(gpa);
841 wasm.functions.deinit(gpa);884 wasm.functions.deinit(gpa);
842 wasm.wasm_globals.deinit(gpa);885 wasm.wasm_globals.deinit(gpa);
843 wasm.address_globals.deinit(gpa);
844 wasm.function_table.deinit(gpa);886 wasm.function_table.deinit(gpa);
845 wasm.tables.deinit(gpa);887 wasm.tables.deinit(gpa);
846 wasm.exports.deinit(gpa);888 wasm.exports.deinit(gpa);
...@@ -1249,7 +1291,7 @@ pub fn updateDeclExports(...@@ -1249,7 +1291,7 @@ pub fn updateDeclExports(
1249 const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;1291 const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
12501292
1251 const exp_is_weak = exp.options.linkage == .Internal or exp.options.linkage == .Weak;1293 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 symbol1294 // When both the to-be-exported symbol and the already existing symbol
1253 // are strong symbols, we have a linker error.1295 // are strong symbols, we have a linker error.
1254 // In the other case we replace one with the other.1296 // In the other case we replace one with the other.
1255 if (!exp_is_weak and !existing_sym.isWeak()) {1297 if (!exp_is_weak and !existing_sym.isWeak()) {
...@@ -1361,6 +1403,19 @@ fn mapFunctionTable(wasm: *Wasm) void {...@@ -1361,6 +1403,19 @@ fn mapFunctionTable(wasm: *Wasm) void {
1361 while (it.next()) |value_ptr| : (index += 1) {1403 while (it.next()) |value_ptr| : (index += 1) {
1362 value_ptr.* = index;1404 value_ptr.* = index;
1363 }1405 }
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 }
1364}1419}
13651420
1366/// Either creates a new import, or updates one if existing.1421/// Either creates a new import, or updates one if existing.
...@@ -1380,18 +1435,31 @@ pub fn addOrUpdateImport(...@@ -1380,18 +1435,31 @@ pub fn addOrUpdateImport(
1380 type_index: ?u32,1435 type_index: ?u32,
1381) !void {1436) !void {
1382 assert(symbol_index != 0);1437 assert(symbol_index != 0);
1383 // For the import name itwasm, we use the decl's name, rather than the fully qualified name1438 // For the import name, 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);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);
1385 const symbol: *Symbol = &wasm.symbols.items[symbol_index];1449 const symbol: *Symbol = &wasm.symbols.items[symbol_index];
1386 symbol.setUndefined(true);1450 symbol.setUndefined(true);
1387 symbol.setGlobal(true);1451 symbol.setGlobal(true);
1388 symbol.name = decl_name_index;1452 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 }
1389 const global_gop = try wasm.globals.getOrPut(wasm.base.allocator, decl_name_index);1457 const global_gop = try wasm.globals.getOrPut(wasm.base.allocator, decl_name_index);
1390 if (!global_gop.found_existing) {1458 if (!global_gop.found_existing) {
1391 const loc: SymbolLoc = .{ .file = null, .index = symbol_index };1459 const loc: SymbolLoc = .{ .file = null, .index = symbol_index };
1392 global_gop.value_ptr.* = loc;1460 global_gop.value_ptr.* = loc;
1393 try wasm.resolved_symbols.put(wasm.base.allocator, loc, {});1461 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);
1395 }1463 }
13961464
1397 if (type_index) |ty_index| {1465 if (type_index) |ty_index| {
...@@ -1402,7 +1470,7 @@ pub fn addOrUpdateImport(...@@ -1402,7 +1470,7 @@ pub fn addOrUpdateImport(
1402 if (!gop.found_existing) {1470 if (!gop.found_existing) {
1403 gop.value_ptr.* = .{1471 gop.value_ptr.* = .{
1404 .module_name = try wasm.string_table.put(wasm.base.allocator, module_name),1472 .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),
1406 .kind = .{ .function = ty_index },1474 .kind = .{ .function = ty_index },
1407 };1475 };
1408 }1476 }
...@@ -1700,17 +1768,6 @@ fn setupImports(wasm: *Wasm) !void {...@@ -1700,17 +1768,6 @@ fn setupImports(wasm: *Wasm) !void {
1700/// Takes the global, function and table section from each linked object file1768/// Takes the global, function and table section from each linked object file
1701/// and merges it into a single section for each.1769/// and merges it into a single section for each.
1702fn mergeSections(wasm: *Wasm) !void {1770fn 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
1714 for (wasm.resolved_symbols.keys()) |sym_loc| {1771 for (wasm.resolved_symbols.keys()) |sym_loc| {
1715 if (sym_loc.file == null) {1772 if (sym_loc.file == null) {
1716 // Zig code-generated symbols are already within the sections and do not1773 // Zig code-generated symbols are already within the sections and do not
...@@ -1800,9 +1857,36 @@ fn setupExports(wasm: *Wasm) !void {...@@ -1800,9 +1857,36 @@ fn setupExports(wasm: *Wasm) !void {
1800 if (wasm.base.options.output_mode == .Obj) return;1857 if (wasm.base.options.output_mode == .Obj) return;
1801 log.debug("Building exports from symbols", .{});1858 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
1803 for (wasm.resolved_symbols.keys()) |sym_loc| {1887 for (wasm.resolved_symbols.keys()) |sym_loc| {
1804 const symbol = sym_loc.getSymbol(wasm);1888 const symbol = sym_loc.getSymbol(wasm);
1805 if (!symbol.isExported()) continue;1889 if (!symbol.isExported(wasm.base.options.rdynamic)) continue;
18061890
1807 const sym_name = sym_loc.getName(wasm);1891 const sym_name = sym_loc.getName(wasm);
1808 const export_name = if (wasm.export_names.get(sym_loc)) |name| name else blk: {1892 const export_name = if (wasm.export_names.get(sym_loc)) |name| name else blk: {
...@@ -1810,8 +1894,13 @@ fn setupExports(wasm: *Wasm) !void {...@@ -1810,8 +1894,13 @@ fn setupExports(wasm: *Wasm) !void {
1810 break :blk try wasm.string_table.put(wasm.base.allocator, sym_name);1894 break :blk try wasm.string_table.put(wasm.base.allocator, sym_name);
1811 };1895 };
1812 const exp: types.Export = if (symbol.tag == .data) exp: {1896 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);1897 const atom = wasm.symbol_atom.get(sym_loc).?;
1814 try wasm.address_globals.append(wasm.base.allocator, 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 });
1815 break :exp .{1904 break :exp .{
1816 .name = export_name,1905 .name = export_name,
1817 .kind = .global,1906 .kind = .global,
...@@ -2399,6 +2488,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2399,6 +2488,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2399 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;2488 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
2400 try wasm.validateFeatures(&enabled_features, &emit_features_count);2489 try wasm.validateFeatures(&enabled_features, &emit_features_count);
2401 try wasm.resolveSymbolsInArchives();2490 try wasm.resolveSymbolsInArchives();
2491 try wasm.checkUndefinedSymbols();
24022492
2403 try wasm.setupStart();2493 try wasm.setupStart();
2404 try wasm.setupImports();2494 try wasm.setupImports();
...@@ -2586,28 +2676,9 @@ fn writeToFile(...@@ -2586,28 +2676,9 @@ fn writeToFile(
25862676
2587 // Import section2677 // Import section
2588 const import_memory = wasm.base.options.import_memory or is_obj;2678 const import_memory = wasm.base.options.import_memory or is_obj;
2589 const import_table = wasm.base.options.import_table or is_obj;2679 if (wasm.imports.count() != 0 or import_memory) {
2590 if (wasm.imports.count() != 0 or import_memory or import_table) {
2591 const header_offset = try reserveVecSectionHeader(&binary_bytes);2680 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
2611 var it = wasm.imports.iterator();2682 var it = wasm.imports.iterator();
2612 while (it.next()) |entry| {2683 while (it.next()) |entry| {
2613 assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());2684 assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());
...@@ -2630,7 +2701,7 @@ fn writeToFile(...@@ -2630,7 +2701,7 @@ fn writeToFile(
2630 header_offset,2701 header_offset,
2631 .import,2702 .import,
2632 @intCast(u32, binary_bytes.items.len - header_offset - header_size),2703 @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)),
2634 );2705 );
2635 section_count += 1;2706 section_count += 1;
2636 }2707 }
...@@ -2653,22 +2724,20 @@ fn writeToFile(...@@ -2653,22 +2724,20 @@ fn writeToFile(
2653 }2724 }
26542725
2655 // Table section2726 // Table section
2656 const export_table = wasm.base.options.export_table;2727 if (wasm.tables.items.len > 0) {
2657 if (!import_table and wasm.function_table.count() != 0) {
2658 const header_offset = try reserveVecSectionHeader(&binary_bytes);2728 const header_offset = try reserveVecSectionHeader(&binary_bytes);
26592729
2660 try leb.writeULEB128(binary_writer, std.wasm.reftype(.funcref));2730 for (wasm.tables.items) |table| {
2661 try emitLimits(binary_writer, .{2731 try leb.writeULEB128(binary_writer, std.wasm.reftype(table.reftype));
2662 .min = @intCast(u32, wasm.function_table.count()) + 1,2732 try emitLimits(binary_writer, table.limits);
2663 .max = null,2733 }
2664 });
26652734
2666 try writeVecSectionHeader(2735 try writeVecSectionHeader(
2667 binary_bytes.items,2736 binary_bytes.items,
2668 header_offset,2737 header_offset,
2669 .table,2738 .table,
2670 @intCast(u32, binary_bytes.items.len - header_offset - header_size),2739 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2671 @as(u32, 1),2740 @intCast(u32, wasm.tables.items.len),
2672 );2741 );
2673 section_count += 1;2742 section_count += 1;
2674 }2743 }
...@@ -2692,22 +2761,10 @@ fn writeToFile(...@@ -2692,22 +2761,10 @@ fn writeToFile(
2692 if (wasm.wasm_globals.items.len > 0) {2761 if (wasm.wasm_globals.items.len > 0) {
2693 const header_offset = try reserveVecSectionHeader(&binary_bytes);2762 const header_offset = try reserveVecSectionHeader(&binary_bytes);
26942763
2695 var global_count: u32 = 0;
2696 for (wasm.wasm_globals.items) |global| {2764 for (wasm.wasm_globals.items) |global| {
2697 try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));2765 try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));
2698 try binary_writer.writeByte(@boolToInt(global.global_type.mutable));2766 try binary_writer.writeByte(@boolToInt(global.global_type.mutable));
2699 try emitInit(binary_writer, global.init);2767 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;
2711 }2768 }
27122769
2713 try writeVecSectionHeader(2770 try writeVecSectionHeader(
...@@ -2715,13 +2772,13 @@ fn writeToFile(...@@ -2715,13 +2772,13 @@ fn writeToFile(
2715 header_offset,2772 header_offset,
2716 .global,2773 .global,
2717 @intCast(u32, binary_bytes.items.len - header_offset - header_size),2774 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2718 @intCast(u32, global_count),2775 @intCast(u32, wasm.wasm_globals.items.len),
2719 );2776 );
2720 section_count += 1;2777 section_count += 1;
2721 }2778 }
27222779
2723 // Export section2780 // 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) {
2725 const header_offset = try reserveVecSectionHeader(&binary_bytes);2782 const header_offset = try reserveVecSectionHeader(&binary_bytes);
27262783
2727 for (wasm.exports.items) |exp| {2784 for (wasm.exports.items) |exp| {
...@@ -2732,13 +2789,6 @@ fn writeToFile(...@@ -2732,13 +2789,6 @@ fn writeToFile(
2732 try leb.writeULEB128(binary_writer, exp.index);2789 try leb.writeULEB128(binary_writer, exp.index);
2733 }2790 }
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
2742 if (!import_memory) {2792 if (!import_memory) {
2743 try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));2793 try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));
2744 try binary_writer.writeAll("memory");2794 try binary_writer.writeAll("memory");
...@@ -2751,7 +2801,7 @@ fn writeToFile(...@@ -2751,7 +2801,7 @@ fn writeToFile(
2751 header_offset,2801 header_offset,
2752 .@"export",2802 .@"export",
2753 @intCast(u32, binary_bytes.items.len - header_offset - header_size),2803 @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),
2755 );2805 );
2756 section_count += 1;2806 section_count += 1;
2757 }2807 }
...@@ -2760,11 +2810,18 @@ fn writeToFile(...@@ -2760,11 +2810,18 @@ fn writeToFile(
2760 if (wasm.function_table.count() > 0) {2810 if (wasm.function_table.count() > 0) {
2761 const header_offset = try reserveVecSectionHeader(&binary_bytes);2811 const header_offset = try reserveVecSectionHeader(&binary_bytes);
27622812
2763 var flags: u32 = 0x2; // Yes we have a table2813 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
2764 try leb.writeULEB128(binary_writer, flags);2817 try leb.writeULEB128(binary_writer, flags);
2765 try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols2818 if (flags == 0x02) {
2819 try leb.writeULEB128(binary_writer, table_sym.index);
2820 }
2766 try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid2821 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 }
2768 try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));2825 try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));
2769 var symbol_it = wasm.function_table.keyIterator();2826 var symbol_it = wasm.function_table.keyIterator();
2770 while (symbol_it.next()) |symbol_loc_ptr| {2827 while (symbol_it.next()) |symbol_loc_ptr| {
...@@ -3091,11 +3148,7 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem...@@ -3091,11 +3148,7 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
30913148
3092 for (wasm.resolved_symbols.keys()) |sym_loc| {3149 for (wasm.resolved_symbols.keys()) |sym_loc| {
3093 const symbol = sym_loc.getSymbol(wasm).*;3150 const symbol = sym_loc.getSymbol(wasm).*;
3094 const name = if (symbol.isUndefined()) blk: {3151 const name = sym_loc.getName(wasm);
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);
3099 switch (symbol.tag) {3152 switch (symbol.tag) {
3100 .function => {3153 .function => {
3101 const gop = funcs.getOrPutAssumeCapacity(symbol.index);3154 const gop = funcs.getOrPutAssumeCapacity(symbol.index);
src/link/Wasm/Atom.zig+18-24
...@@ -90,24 +90,26 @@ pub fn getFirst(atom: *Atom) *Atom {...@@ -90,24 +90,26 @@ pub fn getFirst(atom: *Atom) *Atom {
90 return tmp;90 return tmp;
91}91}
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
106/// Returns the location of the symbol that represents this `Atom`93/// Returns the location of the symbol that represents this `Atom`
107pub fn symbolLoc(atom: Atom) Wasm.SymbolLoc {94pub fn symbolLoc(atom: Atom) Wasm.SymbolLoc {
108 return .{ .file = atom.file, .index = atom.sym_index };95 return .{ .file = atom.file, .index = atom.sym_index };
109}96}
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
111/// Resolves the relocations within the atom, writing the new value113/// Resolves the relocations within the atom, writing the new value
112/// at the calculated offset.114/// at the calculated offset.
113pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {115pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
...@@ -159,7 +161,7 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {...@@ -159,7 +161,7 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
159/// The final value must be casted to the correct size.161/// The final value must be casted to the correct size.
160fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 {162fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 {
161 const target_loc = (Wasm.SymbolLoc{ .file = atom.file, .index = relocation.index }).finalLoc(wasm_bin);163 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);
163 switch (relocation.relocation_type) {165 switch (relocation.relocation_type) {
164 .R_WASM_FUNCTION_INDEX_LEB => return symbol.index,166 .R_WASM_FUNCTION_INDEX_LEB => return symbol.index,
165 .R_WASM_TABLE_NUMBER_LEB => return symbol.index,167 .R_WASM_TABLE_NUMBER_LEB => return symbol.index,
...@@ -190,17 +192,9 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa...@@ -190,17 +192,9 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
190 if (symbol.isUndefined()) {192 if (symbol.isUndefined()) {
191 return 0;193 return 0;
192 }194 }
193
194 const merge_segment = wasm_bin.base.options.output_mode != .Obj;
195 const target_atom = wasm_bin.symbol_atom.get(target_loc).?;195 const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
196 const segment_info = if (target_atom.file) |object_index| blk: {196 const va = @intCast(i32, target_atom.getVA(wasm_bin, symbol));
197 break :blk wasm_bin.objects.items[object_index].segment_info;197 return @intCast(u32, va + relocation.addend);
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);
204 },198 },
205 .R_WASM_EVENT_INDEX_LEB => return symbol.index,199 .R_WASM_EVENT_INDEX_LEB => return symbol.index,
206 .R_WASM_SECTION_OFFSET_I32 => {200 .R_WASM_SECTION_OFFSET_I32 => {
src/link/Wasm/Symbol.zig+3-5
...@@ -139,12 +139,10 @@ pub fn isNoStrip(symbol: Symbol) bool {...@@ -139,12 +139,10 @@ pub fn isNoStrip(symbol: Symbol) bool {
139 return symbol.flags & @enumToInt(Flag.WASM_SYM_NO_STRIP) != 0;139 return symbol.flags & @enumToInt(Flag.WASM_SYM_NO_STRIP) != 0;
140}140}
141141
142pub fn isExported(symbol: Symbol) bool {142pub fn isExported(symbol: Symbol, is_dynamic: bool) bool {
143 if (symbol.isUndefined() or symbol.isLocal()) return false;143 if (symbol.isUndefined() or symbol.isLocal()) return false;
144 if (symbol.isHidden()) return false;144 if (is_dynamic and symbol.isVisible()) return true;
145 if (symbol.hasFlag(.WASM_SYM_EXPORTED)) return true;145 return symbol.hasFlag(.WASM_SYM_EXPORTED);
146 if (symbol.hasFlag(.WASM_SYM_BINDING_WEAK)) return false;
147 return true;
148}146}
149147
150pub fn isWeak(symbol: Symbol) bool {148pub fn isWeak(symbol: Symbol) bool {
test/link.zig+15
...@@ -42,6 +42,16 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {...@@ -42,6 +42,16 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {
42 .requires_stage2 = true,42 .requires_stage2 = true,
43 });43 });
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
45 cases.addBuildFile("test/link/wasm/extern/build.zig", .{55 cases.addBuildFile("test/link/wasm/extern/build.zig", .{
46 .build_modes = true,56 .build_modes = true,
47 .requires_stage2 = true,57 .requires_stage2 = true,
...@@ -53,6 +63,11 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {...@@ -53,6 +63,11 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {
53 .requires_stage2 = true,63 .requires_stage2 = true,
54 });64 });
5565
66 cases.addBuildFile("test/link/wasm/function-table/build.zig", .{
67 .build_modes = true,
68 .requires_stage2 = true,
69 });
70
56 cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{71 cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{
57 .requires_stage2 = true,72 .requires_stage2 = true,
58 });73 });
test/link/wasm/bss/build.zig+1-2
...@@ -26,8 +26,7 @@ pub fn build(b: *Builder) void {...@@ -26,8 +26,7 @@ pub fn build(b: *Builder) void {
26 check_lib.checkNext("name memory"); // as per linker specification26 check_lib.checkNext("name memory"); // as per linker specification
2727
28 // since we are importing memory, ensure it's not exported28 // since we are importing memory, ensure it's not exported
29 check_lib.checkStart("Section export");29 check_lib.checkNotPresent("Section export");
30 check_lib.checkNext("entries 1"); // we're exporting function 'foo' so only 1 entry
3130
32 // validate the name of the stack pointer31 // validate the name of the stack pointer
33 check_lib.checkStart("Section custom");32 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 {}