Skip to content

Commit 3ca3fe9

Browse files
committed
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 parent e4869ee commit 3ca3fe9

1 file changed

Lines changed: 107 additions & 87 deletions

File tree

src/link/Wasm.zig

Lines changed: 107 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -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).
428+
fn 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.
17021754
fn 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| {

0 commit comments

Comments
 (0)