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
335335 wasm_bin.base.file = file;
336336 wasm_bin.name = sub_path;
337337
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 },
338 // create stack pointer symbol
339 {
340 const loc = try wasm_bin.createSyntheticSymbol("__stack_pointer", .global);
341 const symbol = loc.getSymbol(wasm_bin);
342 // For object files we will import the stack pointer symbol
343 if (options.output_mode == .Obj) {
344 symbol.setUndefined(true);
345 symbol.index = @intCast(u32, wasm_bin.imported_globals_count);
346 wasm_bin.imported_globals_count += 1;
347 try wasm_bin.imports.putNoClobber(
348 allocator,
349 loc,
350 .{
351 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
352 .name = symbol.name,
353 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
354 },
355 );
356 } else {
357 symbol.index = @intCast(u32, wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
358 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
359 const global = try wasm_bin.wasm_globals.addOne(allocator);
360 global.* = .{
361 .global_type = .{
362 .valtype = .i32,
363 .mutable = true,
364 },
365 .init = .{ .i32_const = 0 },
366 };
367 }
368 }
369
370 // create indirect function pointer symbol
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,
372377 };
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 }
373396 }
374397
375398 if (!options.strip and options.module != null) {
......@@ -400,6 +423,22 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
400423 return wasm;
401424}
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}
403442/// Initializes symbols and atoms for the debug sections
404443/// Initialization is only done when compiling Zig code.
405444/// When Zig is invoked as a linker instead, the atoms
......@@ -1249,7 +1288,7 @@ pub fn updateDeclExports(
12491288 const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
12501289
12511290 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
1291 // When both the to-be-exported symbol and the already existing symbol
12531292 // are strong symbols, we have a linker error.
12541293 // In the other case we replace one with the other.
12551294 if (!exp_is_weak and !existing_sym.isWeak()) {
......@@ -1361,6 +1400,19 @@ fn mapFunctionTable(wasm: *Wasm) void {
13611400 while (it.next()) |value_ptr| : (index += 1) {
13621401 value_ptr.* = index;
13631402 }
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 }
13641416}
13651417
13661418/// Either creates a new import, or updates one if existing.
......@@ -1700,17 +1752,6 @@ fn setupImports(wasm: *Wasm) !void {
17001752/// Takes the global, function and table section from each linked object file
17011753/// and merges it into a single section for each.
17021754fn 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
17141755 for (wasm.resolved_symbols.keys()) |sym_loc| {
17151756 if (sym_loc.file == null) {
17161757 // Zig code-generated symbols are already within the sections and do not
......@@ -2613,28 +2654,9 @@ fn writeToFile(
26132654
26142655 // Import section
26152656 const import_memory = wasm.base.options.import_memory or is_obj;
2616 const import_table = wasm.base.options.import_table or is_obj;
2617 if (wasm.imports.count() != 0 or import_memory or import_table) {
2657 if (wasm.imports.count() != 0 or import_memory) {
26182658 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
26382660 var it = wasm.imports.iterator();
26392661 while (it.next()) |entry| {
26402662 assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());
......@@ -2657,7 +2679,7 @@ fn writeToFile(
26572679 header_offset,
26582680 .import,
26592681 @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)),
26612683 );
26622684 section_count += 1;
26632685 }
......@@ -2680,22 +2702,20 @@ fn writeToFile(
26802702 }
26812703
26822704 // Table section
2683 const export_table = wasm.base.options.export_table;
2684 if (!import_table and wasm.function_table.count() != 0) {
2705 if (wasm.tables.items.len > 0) {
26852706 const header_offset = try reserveVecSectionHeader(&binary_bytes);
26862707
2687 try leb.writeULEB128(binary_writer, std.wasm.reftype(.funcref));
2688 try emitLimits(binary_writer, .{
2689 .min = @intCast(u32, wasm.function_table.count()) + 1,
2690 .max = null,
2691 });
2708 for (wasm.tables.items) |table| {
2709 try leb.writeULEB128(binary_writer, std.wasm.reftype(table.reftype));
2710 try emitLimits(binary_writer, table.limits);
2711 }
26922712
26932713 try writeVecSectionHeader(
26942714 binary_bytes.items,
26952715 header_offset,
26962716 .table,
26972717 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
2698 @as(u32, 1),
2718 @intCast(u32, wasm.tables.items.len),
26992719 );
27002720 section_count += 1;
27012721 }
......@@ -2748,7 +2768,7 @@ fn writeToFile(
27482768 }
27492769
27502770 // 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) {
27522772 const header_offset = try reserveVecSectionHeader(&binary_bytes);
27532773
27542774 for (wasm.exports.items) |exp| {
......@@ -2759,13 +2779,6 @@ fn writeToFile(
27592779 try leb.writeULEB128(binary_writer, exp.index);
27602780 }
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
27692782 if (!import_memory) {
27702783 try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));
27712784 try binary_writer.writeAll("memory");
......@@ -2778,7 +2791,7 @@ fn writeToFile(
27782791 header_offset,
27792792 .@"export",
27802793 @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),
27822795 );
27832796 section_count += 1;
27842797 }
......@@ -2787,11 +2800,18 @@ fn writeToFile(
27872800 if (wasm.function_table.count() > 0) {
27882801 const header_offset = try reserveVecSectionHeader(&binary_bytes);
27892802
2790 var flags: u32 = 0x2; // Yes we have a table
2803 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
27912807 try leb.writeULEB128(binary_writer, flags);
2792 try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols
2808 if (flags == 0x02) {
2809 try leb.writeULEB128(binary_writer, table_sym.index);
2810 }
27932811 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 }
27952815 try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));
27962816 var symbol_it = wasm.function_table.keyIterator();
27972817 while (symbol_it.next()) |symbol_loc_ptr| {