authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-12-31 16:58:49+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-01-02 18:36:17+01:00
log3ca3fe94f454688f05415cf0a39954a0379b4c84
tree42f6c2481c25666992e78bf2e55b0a0d9ac9ae42
parente4869eeac11eccdbb6cf4d8a4e60a272ca179d55
signaturelock-open Commit is signed but in an unrecognized format.

wasm-linker: improve indirect function table

Rather than checking for function pointers during the writing phase, we now create a synethtic symbol when a new link job has started. This means the symbol can correctly be resolved during link time with the indirect function table from other object files, ensuring we are properly performing relocations and our binary writer is now unaware of any of its logic and simply emits the table according to the symbol such as any other symbols.

1 files changed, 107 insertions(+), 87 deletions(-)

src/link/Wasm.zig+107-87
...@@ -335,41 +335,64 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -335,41 +335,64 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
335 wasm_bin.base.file = file;335 wasm_bin.base.file = file;
336 wasm_bin.name = sub_path;336 wasm_bin.name = sub_path;
337337
338 // As sym_index '0' is reserved, we use it for our stack pointer symbol338 // create stack pointer symbol
339 const sym_name = try wasm_bin.string_table.put(allocator, "__stack_pointer");339 {
340 const symbol = try wasm_bin.symbols.addOne(allocator);340 const loc = try wasm_bin.createSyntheticSymbol("__stack_pointer", .global);
341 symbol.* = .{341 const symbol = loc.getSymbol(wasm_bin);
342 .name = sym_name,342 // For object files we will import the stack pointer symbol
343 .tag = .global,343 if (options.output_mode == .Obj) {
344 .flags = 0,344 symbol.setUndefined(true);
345 .index = 0,345 symbol.index = @intCast(u32, wasm_bin.imported_globals_count);
346 };346 wasm_bin.imported_globals_count += 1;
347 const loc: SymbolLoc = .{ .file = null, .index = 0 };347 try wasm_bin.imports.putNoClobber(
348 try wasm_bin.resolved_symbols.putNoClobber(allocator, loc, {});348 allocator,
349 try wasm_bin.globals.putNoClobber(allocator, sym_name, loc);349 loc,
350350 .{
351 // For object files we will import the stack pointer symbol351 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
352 if (options.output_mode == .Obj) {352 .name = symbol.name,
353 symbol.setUndefined(true);353 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
354 try wasm_bin.imports.putNoClobber(354 },
355 allocator,355 );
356 .{ .file = null, .index = 0 },356 } else {
357 .{357 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),358 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
359 .name = sym_name,359 const global = try wasm_bin.wasm_globals.addOne(allocator);
360 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },360 global.* = .{
361 },361 .global_type = .{
362 );362 .valtype = .i32,
363 } else {363 .mutable = true,
364 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);364 },
365 const global = try wasm_bin.wasm_globals.addOne(allocator);365 .init = .{ .i32_const = 0 },
366 global.* = .{366 };
367 .global_type = .{367 }
368 .valtype = .i32,368 }
369 .mutable = true,369
370 },370 // create indirect function pointer symbol
371 .init = .{ .i32_const = 0 },371 {
372 const loc = try wasm_bin.createSyntheticSymbol("__indirect_function_table", .table);
373 const symbol = loc.getSymbol(wasm_bin);
374 const table: std.wasm.Table = .{
375 .limits = .{ .min = 0, .max = null }, // will be overwritten during `mapFunctionTable`
376 .reftype = .funcref,
372 };377 };
378 if (options.output_mode == .Obj or options.import_table) {
379 symbol.setUndefined(true);
380 symbol.index = @intCast(u32, wasm_bin.imported_tables_count);
381 wasm_bin.imported_tables_count += 1;
382 try wasm_bin.imports.put(allocator, loc, .{
383 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
384 .name = symbol.name,
385 .kind = .{ .table = table },
386 });
387 } else {
388 symbol.index = @intCast(u32, wasm_bin.imported_tables_count + wasm_bin.tables.items.len);
389 try wasm_bin.tables.append(allocator, table);
390 if (options.export_table) {
391 symbol.setFlag(.WASM_SYM_EXPORTED);
392 } else {
393 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
394 }
395 }
373 }396 }
374397
375 if (!options.strip and options.module != null) {398 if (!options.strip and options.module != null) {
...@@ -400,6 +423,22 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {...@@ -400,6 +423,22 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
400 return wasm;423 return wasm;
401}424}
402425
426/// For a given name, creates a new global synthetic symbol.
427/// Leaves index undefined and the default flags (0).
428fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !SymbolLoc {
429 const name_offset = try wasm.string_table.put(wasm.base.allocator, name);
430 const sym_index = @intCast(u32, wasm.symbols.items.len);
431 const loc: SymbolLoc = .{ .index = sym_index, .file = null };
432 try wasm.symbols.append(wasm.base.allocator, .{
433 .name = name_offset,
434 .flags = 0,
435 .tag = tag,
436 .index = undefined,
437 });
438 try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {});
439 try wasm.globals.putNoClobber(wasm.base.allocator, name_offset, loc);
440 return loc;
441}
403/// Initializes symbols and atoms for the debug sections442/// Initializes symbols and atoms for the debug sections
404/// Initialization is only done when compiling Zig code.443/// Initialization is only done when compiling Zig code.
405/// When Zig is invoked as a linker instead, the atoms444/// When Zig is invoked as a linker instead, the atoms
...@@ -1249,7 +1288,7 @@ pub fn updateDeclExports(...@@ -1249,7 +1288,7 @@ pub fn updateDeclExports(
1249 const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;1288 const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
12501289
1251 const exp_is_weak = exp.options.linkage == .Internal or exp.options.linkage == .Weak;1290 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 symbol1291 // When both the to-be-exported symbol and the already existing symbol
1253 // are strong symbols, we have a linker error.1292 // are strong symbols, we have a linker error.
1254 // In the other case we replace one with the other.1293 // In the other case we replace one with the other.
1255 if (!exp_is_weak and !existing_sym.isWeak()) {1294 if (!exp_is_weak and !existing_sym.isWeak()) {
...@@ -1361,6 +1400,19 @@ fn mapFunctionTable(wasm: *Wasm) void {...@@ -1361,6 +1400,19 @@ fn mapFunctionTable(wasm: *Wasm) void {
1361 while (it.next()) |value_ptr| : (index += 1) {1400 while (it.next()) |value_ptr| : (index += 1) {
1362 value_ptr.* = index;1401 value_ptr.* = index;
1363 }1402 }
1403
1404 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {
1405 const sym_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;
1406 const import = wasm.imports.getPtr(sym_loc).?;
1407 import.kind.table.limits.min = index - 1; // we start at index 1.
1408 } else if (index > 1) {
1409 log.debug("Appending indirect function table", .{});
1410 const offset = wasm.string_table.getOffset("__indirect_function_table").?;
1411 const sym_with_loc = wasm.globals.get(offset).?;
1412 const symbol = sym_with_loc.getSymbol(wasm);
1413 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
1414 table.limits = .{ .min = index, .max = index };
1415 }
1364}1416}
13651417
1366/// Either creates a new import, or updates one if existing.1418/// Either creates a new import, or updates one if existing.
...@@ -1700,17 +1752,6 @@ fn setupImports(wasm: *Wasm) !void {...@@ -1700,17 +1752,6 @@ fn setupImports(wasm: *Wasm) !void {
1700/// Takes the global, function and table section from each linked object file1752/// Takes the global, function and table section from each linked object file
1701/// and merges it into a single section for each.1753/// and merges it into a single section for each.
1702fn mergeSections(wasm: *Wasm) !void {1754fn 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| {1755 for (wasm.resolved_symbols.keys()) |sym_loc| {
1715 if (sym_loc.file == null) {1756 if (sym_loc.file == null) {
1716 // Zig code-generated symbols are already within the sections and do not1757 // Zig code-generated symbols are already within the sections and do not
...@@ -2613,28 +2654,9 @@ fn writeToFile(...@@ -2613,28 +2654,9 @@ fn writeToFile(
26132654
2614 // Import section2655 // Import section
2615 const import_memory = wasm.base.options.import_memory or is_obj;2656 const import_memory = wasm.base.options.import_memory or is_obj;
2616 const import_table = wasm.base.options.import_table or is_obj;2657 if (wasm.imports.count() != 0 or import_memory) {
2617 if (wasm.imports.count() != 0 or import_memory or import_table) {
2618 const header_offset = try reserveVecSectionHeader(&binary_bytes);2658 const header_offset = try reserveVecSectionHeader(&binary_bytes);
26192659
2620 // import table is always first table so emit that first
2621 if (import_table) {
2622 const table_imp: types.Import = .{
2623 .module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name),
2624 .name = try wasm.string_table.put(wasm.base.allocator, "__indirect_function_table"),
2625 .kind = .{
2626 .table = .{
2627 .limits = .{
2628 .min = @intCast(u32, wasm.function_table.count()),
2629 .max = null,
2630 },
2631 .reftype = .funcref,
2632 },
2633 },
2634 };
2635 try wasm.emitImport(binary_writer, table_imp);
2636 }
2637
2638 var it = wasm.imports.iterator();2660 var it = wasm.imports.iterator();
2639 while (it.next()) |entry| {2661 while (it.next()) |entry| {
2640 assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());2662 assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());
...@@ -2657,7 +2679,7 @@ fn writeToFile(...@@ -2657,7 +2679,7 @@ fn writeToFile(
2657 header_offset,2679 header_offset,
2658 .import,2680 .import,
2659 @intCast(u32, binary_bytes.items.len - header_offset - header_size),2681 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2660 @intCast(u32, wasm.imports.count() + @boolToInt(import_memory) + @boolToInt(import_table)),2682 @intCast(u32, wasm.imports.count() + @boolToInt(import_memory)),
2661 );2683 );
2662 section_count += 1;2684 section_count += 1;
2663 }2685 }
...@@ -2680,22 +2702,20 @@ fn writeToFile(...@@ -2680,22 +2702,20 @@ fn writeToFile(
2680 }2702 }
26812703
2682 // Table section2704 // Table section
2683 const export_table = wasm.base.options.export_table;2705 if (wasm.tables.items.len > 0) {
2684 if (!import_table and wasm.function_table.count() != 0) {
2685 const header_offset = try reserveVecSectionHeader(&binary_bytes);2706 const header_offset = try reserveVecSectionHeader(&binary_bytes);
26862707
2687 try leb.writeULEB128(binary_writer, std.wasm.reftype(.funcref));2708 for (wasm.tables.items) |table| {
2688 try emitLimits(binary_writer, .{2709 try leb.writeULEB128(binary_writer, std.wasm.reftype(table.reftype));
2689 .min = @intCast(u32, wasm.function_table.count()) + 1,2710 try emitLimits(binary_writer, table.limits);
2690 .max = null,2711 }
2691 });
26922712
2693 try writeVecSectionHeader(2713 try writeVecSectionHeader(
2694 binary_bytes.items,2714 binary_bytes.items,
2695 header_offset,2715 header_offset,
2696 .table,2716 .table,
2697 @intCast(u32, binary_bytes.items.len - header_offset - header_size),2717 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2698 @as(u32, 1),2718 @intCast(u32, wasm.tables.items.len),
2699 );2719 );
2700 section_count += 1;2720 section_count += 1;
2701 }2721 }
...@@ -2748,7 +2768,7 @@ fn writeToFile(...@@ -2748,7 +2768,7 @@ fn writeToFile(
2748 }2768 }
27492769
2750 // Export section2770 // Export section
2751 if (wasm.exports.items.len != 0 or export_table or !import_memory) {2771 if (wasm.exports.items.len != 0 or !import_memory) {
2752 const header_offset = try reserveVecSectionHeader(&binary_bytes);2772 const header_offset = try reserveVecSectionHeader(&binary_bytes);
27532773
2754 for (wasm.exports.items) |exp| {2774 for (wasm.exports.items) |exp| {
...@@ -2759,13 +2779,6 @@ fn writeToFile(...@@ -2759,13 +2779,6 @@ fn writeToFile(
2759 try leb.writeULEB128(binary_writer, exp.index);2779 try leb.writeULEB128(binary_writer, exp.index);
2760 }2780 }
27612781
2762 if (export_table) {
2763 try leb.writeULEB128(binary_writer, @intCast(u32, "__indirect_function_table".len));
2764 try binary_writer.writeAll("__indirect_function_table");
2765 try binary_writer.writeByte(std.wasm.externalKind(.table));
2766 try leb.writeULEB128(binary_writer, @as(u32, 0)); // function table is always the first table
2767 }
2768
2769 if (!import_memory) {2782 if (!import_memory) {
2770 try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));2783 try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));
2771 try binary_writer.writeAll("memory");2784 try binary_writer.writeAll("memory");
...@@ -2778,7 +2791,7 @@ fn writeToFile(...@@ -2778,7 +2791,7 @@ fn writeToFile(
2778 header_offset,2791 header_offset,
2779 .@"export",2792 .@"export",
2780 @intCast(u32, binary_bytes.items.len - header_offset - header_size),2793 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2781 @intCast(u32, wasm.exports.items.len) + @boolToInt(export_table) + @boolToInt(!import_memory),2794 @intCast(u32, wasm.exports.items.len) + @boolToInt(!import_memory),
2782 );2795 );
2783 section_count += 1;2796 section_count += 1;
2784 }2797 }
...@@ -2787,11 +2800,18 @@ fn writeToFile(...@@ -2787,11 +2800,18 @@ fn writeToFile(
2787 if (wasm.function_table.count() > 0) {2800 if (wasm.function_table.count() > 0) {
2788 const header_offset = try reserveVecSectionHeader(&binary_bytes);2801 const header_offset = try reserveVecSectionHeader(&binary_bytes);
27892802
2790 var flags: u32 = 0x2; // Yes we have a table2803 const table_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;
2804 const table_sym = table_loc.getSymbol(wasm);
2805
2806 var flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually
2791 try leb.writeULEB128(binary_writer, flags);2807 try leb.writeULEB128(binary_writer, flags);
2792 try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols2808 if (flags == 0x02) {
2809 try leb.writeULEB128(binary_writer, table_sym.index);
2810 }
2793 try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid2811 try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
2794 try leb.writeULEB128(binary_writer, @as(u8, 0));2812 if (flags == 0x02) {
2813 try leb.writeULEB128(binary_writer, @as(u8, 0)); // represents funcref
2814 }
2795 try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));2815 try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));
2796 var symbol_it = wasm.function_table.keyIterator();2816 var symbol_it = wasm.function_table.keyIterator();
2797 while (symbol_it.next()) |symbol_loc_ptr| {2817 while (symbol_it.next()) |symbol_loc_ptr| {