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

wasm-linker: various fixes & improvements
This commit is contained in:
Luuk de Gram 2023-01-03 15:47:16 +01:00 committed by GitHub
commit 7935135767
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 365 additions and 146 deletions

View File

@ -112,8 +112,6 @@ func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.wasm.Func) = .{},
/// Output global section
wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
/// Global symbols for exported data symbols
address_globals: std.ArrayListUnmanaged(SymbolLoc) = .{},
/// Memory section
memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
/// Output table section
@ -335,41 +333,64 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
wasm_bin.base.file = file;
wasm_bin.name = sub_path;
// As sym_index '0' is reserved, we use it for our stack pointer symbol
const sym_name = try wasm_bin.string_table.put(allocator, "__stack_pointer");
const symbol = try wasm_bin.symbols.addOne(allocator);
symbol.* = .{
.name = sym_name,
.tag = .global,
.flags = 0,
.index = 0,
};
const loc: SymbolLoc = .{ .file = null, .index = 0 };
try wasm_bin.resolved_symbols.putNoClobber(allocator, loc, {});
try wasm_bin.globals.putNoClobber(allocator, sym_name, loc);
// create stack pointer symbol
{
const loc = try wasm_bin.createSyntheticSymbol("__stack_pointer", .global);
const symbol = loc.getSymbol(wasm_bin);
// For object files we will import the stack pointer symbol
if (options.output_mode == .Obj) {
symbol.setUndefined(true);
symbol.index = @intCast(u32, wasm_bin.imported_globals_count);
wasm_bin.imported_globals_count += 1;
try wasm_bin.imports.putNoClobber(
allocator,
loc,
.{
.module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
.name = symbol.name,
.kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
},
);
} else {
symbol.index = @intCast(u32, wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
const global = try wasm_bin.wasm_globals.addOne(allocator);
global.* = .{
.global_type = .{
.valtype = .i32,
.mutable = true,
},
.init = .{ .i32_const = 0 },
};
}
}
// For object files we will import the stack pointer symbol
if (options.output_mode == .Obj) {
symbol.setUndefined(true);
try wasm_bin.imports.putNoClobber(
allocator,
.{ .file = null, .index = 0 },
.{
.module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
.name = sym_name,
.kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
},
);
} else {
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
const global = try wasm_bin.wasm_globals.addOne(allocator);
global.* = .{
.global_type = .{
.valtype = .i32,
.mutable = true,
},
.init = .{ .i32_const = 0 },
// create indirect function pointer symbol
{
const loc = try wasm_bin.createSyntheticSymbol("__indirect_function_table", .table);
const symbol = loc.getSymbol(wasm_bin);
const table: std.wasm.Table = .{
.limits = .{ .min = 0, .max = null }, // will be overwritten during `mapFunctionTable`
.reftype = .funcref,
};
if (options.output_mode == .Obj or options.import_table) {
symbol.setUndefined(true);
symbol.index = @intCast(u32, wasm_bin.imported_tables_count);
wasm_bin.imported_tables_count += 1;
try wasm_bin.imports.put(allocator, loc, .{
.module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
.name = symbol.name,
.kind = .{ .table = table },
});
} else {
symbol.index = @intCast(u32, wasm_bin.imported_tables_count + wasm_bin.tables.items.len);
try wasm_bin.tables.append(allocator, table);
if (options.export_table) {
symbol.setFlag(.WASM_SYM_EXPORTED);
} else {
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
}
}
}
if (!options.strip and options.module != null) {
@ -400,6 +421,22 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
return wasm;
}
/// For a given name, creates a new global synthetic symbol.
/// Leaves index undefined and the default flags (0).
fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !SymbolLoc {
const name_offset = try wasm.string_table.put(wasm.base.allocator, name);
const sym_index = @intCast(u32, wasm.symbols.items.len);
const loc: SymbolLoc = .{ .index = sym_index, .file = null };
try wasm.symbols.append(wasm.base.allocator, .{
.name = name_offset,
.flags = 0,
.tag = tag,
.index = undefined,
});
try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {});
try wasm.globals.putNoClobber(wasm.base.allocator, name_offset, loc);
return loc;
}
/// Initializes symbols and atoms for the debug sections
/// Initialization is only done when compiling Zig code.
/// When Zig is invoked as a linker instead, the atoms
@ -766,6 +803,7 @@ fn validateFeatures(
fn checkUndefinedSymbols(wasm: *const Wasm) !void {
if (wasm.base.options.output_mode == .Obj) return;
if (wasm.base.options.import_symbols) return;
var found_undefined_symbols = false;
for (wasm.undefs.values()) |undef| {
@ -775,7 +813,12 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {
const file_name = if (undef.file) |file_index| name: {
break :name wasm.objects.items[file_index].name;
} else wasm.name;
log.err("could not resolve undefined symbol '{s}'", .{undef.getName(wasm)});
const import_name = if (undef.file) |file_index| name: {
const obj = wasm.objects.items[file_index];
const name_index = obj.findImport(symbol.tag.externalType(), symbol.index).name;
break :name obj.string_table.get(name_index);
} else wasm.string_table.get(wasm.imports.get(undef).?.name);
log.err("could not resolve undefined symbol '{s}'", .{import_name});
log.err(" defined in '{s}'", .{file_name});
}
}
@ -840,7 +883,6 @@ pub fn deinit(wasm: *Wasm) void {
wasm.func_types.deinit(gpa);
wasm.functions.deinit(gpa);
wasm.wasm_globals.deinit(gpa);
wasm.address_globals.deinit(gpa);
wasm.function_table.deinit(gpa);
wasm.tables.deinit(gpa);
wasm.exports.deinit(gpa);
@ -1249,7 +1291,7 @@ pub fn updateDeclExports(
const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
const exp_is_weak = exp.options.linkage == .Internal or exp.options.linkage == .Weak;
// When both the to-bo-exported symbol and the already existing symbol
// When both the to-be-exported symbol and the already existing symbol
// are strong symbols, we have a linker error.
// In the other case we replace one with the other.
if (!exp_is_weak and !existing_sym.isWeak()) {
@ -1361,6 +1403,19 @@ fn mapFunctionTable(wasm: *Wasm) void {
while (it.next()) |value_ptr| : (index += 1) {
value_ptr.* = index;
}
if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {
const sym_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;
const import = wasm.imports.getPtr(sym_loc).?;
import.kind.table.limits.min = index - 1; // we start at index 1.
} else if (index > 1) {
log.debug("Appending indirect function table", .{});
const offset = wasm.string_table.getOffset("__indirect_function_table").?;
const sym_with_loc = wasm.globals.get(offset).?;
const symbol = sym_with_loc.getSymbol(wasm);
const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
table.limits = .{ .min = index, .max = index };
}
}
/// Either creates a new import, or updates one if existing.
@ -1380,18 +1435,31 @@ pub fn addOrUpdateImport(
type_index: ?u32,
) !void {
assert(symbol_index != 0);
// For the import name itwasm, we use the decl's name, rather than the fully qualified name
const decl_name_index = try wasm.string_table.put(wasm.base.allocator, name);
// For the import name, we use the decl's name, rather than the fully qualified name
// Also mangle the name when the lib name is set and not equal to "C" so imports with the same
// name but different module can be resolved correctly.
const mangle_name = lib_name != null and
!std.mem.eql(u8, std.mem.sliceTo(lib_name.?, 0), "c");
const full_name = if (mangle_name) full_name: {
break :full_name try std.fmt.allocPrint(wasm.base.allocator, "{s}|{s}", .{ name, lib_name.? });
} else name;
defer if (mangle_name) wasm.base.allocator.free(full_name);
const decl_name_index = try wasm.string_table.put(wasm.base.allocator, full_name);
const symbol: *Symbol = &wasm.symbols.items[symbol_index];
symbol.setUndefined(true);
symbol.setGlobal(true);
symbol.name = decl_name_index;
if (mangle_name) {
// we specified a specific name for the symbol that does not match the import name
symbol.setFlag(.WASM_SYM_EXPLICIT_NAME);
}
const global_gop = try wasm.globals.getOrPut(wasm.base.allocator, decl_name_index);
if (!global_gop.found_existing) {
const loc: SymbolLoc = .{ .file = null, .index = symbol_index };
global_gop.value_ptr.* = loc;
try wasm.resolved_symbols.put(wasm.base.allocator, loc, {});
try wasm.undefs.putNoClobber(wasm.base.allocator, name, loc);
try wasm.undefs.putNoClobber(wasm.base.allocator, full_name, loc);
}
if (type_index) |ty_index| {
@ -1402,7 +1470,7 @@ pub fn addOrUpdateImport(
if (!gop.found_existing) {
gop.value_ptr.* = .{
.module_name = try wasm.string_table.put(wasm.base.allocator, module_name),
.name = decl_name_index,
.name = try wasm.string_table.put(wasm.base.allocator, name),
.kind = .{ .function = ty_index },
};
}
@ -1700,17 +1768,6 @@ fn setupImports(wasm: *Wasm) !void {
/// Takes the global, function and table section from each linked object file
/// and merges it into a single section for each.
fn mergeSections(wasm: *Wasm) !void {
// append the indirect function table if initialized
if (wasm.string_table.getOffset("__indirect_function_table")) |offset| {
const sym_loc = wasm.globals.get(offset).?;
const table: std.wasm.Table = .{
.limits = .{ .min = @intCast(u32, wasm.function_table.count()), .max = null },
.reftype = .funcref,
};
sym_loc.getSymbol(wasm).index = @intCast(u32, wasm.tables.items.len) + wasm.imported_tables_count;
try wasm.tables.append(wasm.base.allocator, table);
}
for (wasm.resolved_symbols.keys()) |sym_loc| {
if (sym_loc.file == null) {
// Zig code-generated symbols are already within the sections and do not
@ -1800,9 +1857,36 @@ fn setupExports(wasm: *Wasm) !void {
if (wasm.base.options.output_mode == .Obj) return;
log.debug("Building exports from symbols", .{});
const force_exp_names = wasm.base.options.export_symbol_names;
if (force_exp_names.len > 0) {
var failed_exports = try std.ArrayList([]const u8).initCapacity(wasm.base.allocator, force_exp_names.len);
defer failed_exports.deinit();
for (force_exp_names) |exp_name| {
const name_index = wasm.string_table.getOffset(exp_name) orelse {
failed_exports.appendAssumeCapacity(exp_name);
continue;
};
const loc = wasm.globals.get(name_index) orelse {
failed_exports.appendAssumeCapacity(exp_name);
continue;
};
const symbol = loc.getSymbol(wasm);
symbol.setFlag(.WASM_SYM_EXPORTED);
}
if (failed_exports.items.len > 0) {
for (failed_exports.items) |exp_name| {
log.err("could not export '{s}', symbol not found", .{exp_name});
}
return error.MissingSymbol;
}
}
for (wasm.resolved_symbols.keys()) |sym_loc| {
const symbol = sym_loc.getSymbol(wasm);
if (!symbol.isExported()) continue;
if (!symbol.isExported(wasm.base.options.rdynamic)) continue;
const sym_name = sym_loc.getName(wasm);
const export_name = if (wasm.export_names.get(sym_loc)) |name| name else blk: {
@ -1810,8 +1894,13 @@ fn setupExports(wasm: *Wasm) !void {
break :blk try wasm.string_table.put(wasm.base.allocator, sym_name);
};
const exp: types.Export = if (symbol.tag == .data) exp: {
const global_index = @intCast(u32, wasm.wasm_globals.items.len + wasm.address_globals.items.len);
try wasm.address_globals.append(wasm.base.allocator, sym_loc);
const atom = wasm.symbol_atom.get(sym_loc).?;
const va = atom.getVA(wasm, symbol);
const global_index = @intCast(u32, wasm.imported_globals_count + wasm.wasm_globals.items.len);
try wasm.wasm_globals.append(wasm.base.allocator, .{
.global_type = .{ .valtype = .i32, .mutable = false },
.init = .{ .i32_const = @intCast(i32, va) },
});
break :exp .{
.name = export_name,
.kind = .global,
@ -2399,6 +2488,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
try wasm.validateFeatures(&enabled_features, &emit_features_count);
try wasm.resolveSymbolsInArchives();
try wasm.checkUndefinedSymbols();
try wasm.setupStart();
try wasm.setupImports();
@ -2586,28 +2676,9 @@ fn writeToFile(
// Import section
const import_memory = wasm.base.options.import_memory or is_obj;
const import_table = wasm.base.options.import_table or is_obj;
if (wasm.imports.count() != 0 or import_memory or import_table) {
if (wasm.imports.count() != 0 or import_memory) {
const header_offset = try reserveVecSectionHeader(&binary_bytes);
// import table is always first table so emit that first
if (import_table) {
const table_imp: types.Import = .{
.module_name = try wasm.string_table.put(wasm.base.allocator, wasm.host_name),
.name = try wasm.string_table.put(wasm.base.allocator, "__indirect_function_table"),
.kind = .{
.table = .{
.limits = .{
.min = @intCast(u32, wasm.function_table.count()),
.max = null,
},
.reftype = .funcref,
},
},
};
try wasm.emitImport(binary_writer, table_imp);
}
var it = wasm.imports.iterator();
while (it.next()) |entry| {
assert(entry.key_ptr.*.getSymbol(wasm).isUndefined());
@ -2630,7 +2701,7 @@ fn writeToFile(
header_offset,
.import,
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
@intCast(u32, wasm.imports.count() + @boolToInt(import_memory) + @boolToInt(import_table)),
@intCast(u32, wasm.imports.count() + @boolToInt(import_memory)),
);
section_count += 1;
}
@ -2653,22 +2724,20 @@ fn writeToFile(
}
// Table section
const export_table = wasm.base.options.export_table;
if (!import_table and wasm.function_table.count() != 0) {
if (wasm.tables.items.len > 0) {
const header_offset = try reserveVecSectionHeader(&binary_bytes);
try leb.writeULEB128(binary_writer, std.wasm.reftype(.funcref));
try emitLimits(binary_writer, .{
.min = @intCast(u32, wasm.function_table.count()) + 1,
.max = null,
});
for (wasm.tables.items) |table| {
try leb.writeULEB128(binary_writer, std.wasm.reftype(table.reftype));
try emitLimits(binary_writer, table.limits);
}
try writeVecSectionHeader(
binary_bytes.items,
header_offset,
.table,
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
@as(u32, 1),
@intCast(u32, wasm.tables.items.len),
);
section_count += 1;
}
@ -2692,22 +2761,10 @@ fn writeToFile(
if (wasm.wasm_globals.items.len > 0) {
const header_offset = try reserveVecSectionHeader(&binary_bytes);
var global_count: u32 = 0;
for (wasm.wasm_globals.items) |global| {
try binary_writer.writeByte(std.wasm.valtype(global.global_type.valtype));
try binary_writer.writeByte(@boolToInt(global.global_type.mutable));
try emitInit(binary_writer, global.init);
global_count += 1;
}
for (wasm.address_globals.items) |sym_loc| {
const atom = wasm.symbol_atom.get(sym_loc).?;
try binary_writer.writeByte(std.wasm.valtype(.i32));
try binary_writer.writeByte(0); // immutable
try emitInit(binary_writer, .{
.i32_const = @bitCast(i32, atom.offset),
});
global_count += 1;
}
try writeVecSectionHeader(
@ -2715,13 +2772,13 @@ fn writeToFile(
header_offset,
.global,
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
@intCast(u32, global_count),
@intCast(u32, wasm.wasm_globals.items.len),
);
section_count += 1;
}
// Export section
if (wasm.exports.items.len != 0 or export_table or !import_memory) {
if (wasm.exports.items.len != 0 or !import_memory) {
const header_offset = try reserveVecSectionHeader(&binary_bytes);
for (wasm.exports.items) |exp| {
@ -2732,13 +2789,6 @@ fn writeToFile(
try leb.writeULEB128(binary_writer, exp.index);
}
if (export_table) {
try leb.writeULEB128(binary_writer, @intCast(u32, "__indirect_function_table".len));
try binary_writer.writeAll("__indirect_function_table");
try binary_writer.writeByte(std.wasm.externalKind(.table));
try leb.writeULEB128(binary_writer, @as(u32, 0)); // function table is always the first table
}
if (!import_memory) {
try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));
try binary_writer.writeAll("memory");
@ -2751,7 +2801,7 @@ fn writeToFile(
header_offset,
.@"export",
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
@intCast(u32, wasm.exports.items.len) + @boolToInt(export_table) + @boolToInt(!import_memory),
@intCast(u32, wasm.exports.items.len) + @boolToInt(!import_memory),
);
section_count += 1;
}
@ -2760,11 +2810,18 @@ fn writeToFile(
if (wasm.function_table.count() > 0) {
const header_offset = try reserveVecSectionHeader(&binary_bytes);
var flags: u32 = 0x2; // Yes we have a table
const table_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;
const table_sym = table_loc.getSymbol(wasm);
var flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually
try leb.writeULEB128(binary_writer, flags);
try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols
if (flags == 0x02) {
try leb.writeULEB128(binary_writer, table_sym.index);
}
try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
try leb.writeULEB128(binary_writer, @as(u8, 0));
if (flags == 0x02) {
try leb.writeULEB128(binary_writer, @as(u8, 0)); // represents funcref
}
try leb.writeULEB128(binary_writer, @intCast(u32, wasm.function_table.count()));
var symbol_it = wasm.function_table.keyIterator();
while (symbol_it.next()) |symbol_loc_ptr| {
@ -3091,11 +3148,7 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
for (wasm.resolved_symbols.keys()) |sym_loc| {
const symbol = sym_loc.getSymbol(wasm).*;
const name = if (symbol.isUndefined()) blk: {
if (symbol.tag == .data) continue;
const imp = wasm.imports.get(sym_loc) orelse continue;
break :blk wasm.string_table.get(imp.name);
} else sym_loc.getName(wasm);
const name = sym_loc.getName(wasm);
switch (symbol.tag) {
.function => {
const gop = funcs.getOrPutAssumeCapacity(symbol.index);

View File

@ -90,24 +90,26 @@ pub fn getFirst(atom: *Atom) *Atom {
return tmp;
}
/// Unlike `getFirst` this returns the first `*Atom` that was
/// produced from Zig code, rather than an object file.
/// This is useful for debug sections where we want to extend
/// the bytes, and don't want to overwrite existing Atoms.
pub fn getFirstZigAtom(atom: *Atom) *Atom {
if (atom.file == null) return atom;
var tmp = atom;
return while (tmp.prev) |prev| {
if (prev.file == null) break prev;
tmp = prev;
} else unreachable; // must allocate an Atom first!
}
/// Returns the location of the symbol that represents this `Atom`
pub fn symbolLoc(atom: Atom) Wasm.SymbolLoc {
return .{ .file = atom.file, .index = atom.sym_index };
}
/// Returns the virtual address of the `Atom`. This is the address starting
/// from the first entry within a section.
pub fn getVA(atom: Atom, wasm: *const Wasm, symbol: *const Symbol) u32 {
if (symbol.tag == .function) return atom.offset;
std.debug.assert(symbol.tag == .data);
const merge_segment = wasm.base.options.output_mode != .Obj;
const segment_info = if (atom.file) |object_index| blk: {
break :blk wasm.objects.items[object_index].segment_info;
} else wasm.segment_info.values();
const segment_name = segment_info[symbol.index].outputName(merge_segment);
const segment_index = wasm.data_segments.get(segment_name).?;
const segment = wasm.segments.items[segment_index];
return segment.offset + atom.offset;
}
/// Resolves the relocations within the atom, writing the new value
/// at the calculated offset.
pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
@ -159,7 +161,7 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
/// The final value must be casted to the correct size.
fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 {
const target_loc = (Wasm.SymbolLoc{ .file = atom.file, .index = relocation.index }).finalLoc(wasm_bin);
const symbol = target_loc.getSymbol(wasm_bin).*;
const symbol = target_loc.getSymbol(wasm_bin);
switch (relocation.relocation_type) {
.R_WASM_FUNCTION_INDEX_LEB => return symbol.index,
.R_WASM_TABLE_NUMBER_LEB => return symbol.index,
@ -190,17 +192,9 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
if (symbol.isUndefined()) {
return 0;
}
const merge_segment = wasm_bin.base.options.output_mode != .Obj;
const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
const segment_info = if (target_atom.file) |object_index| blk: {
break :blk wasm_bin.objects.items[object_index].segment_info;
} else wasm_bin.segment_info.values();
const segment_name = segment_info[symbol.index].outputName(merge_segment);
const segment_index = wasm_bin.data_segments.get(segment_name).?;
const segment = wasm_bin.segments.items[segment_index];
const rel_value = @intCast(i32, target_atom.offset + segment.offset) + relocation.addend;
return @intCast(u32, rel_value);
const va = @intCast(i32, target_atom.getVA(wasm_bin, symbol));
return @intCast(u32, va + relocation.addend);
},
.R_WASM_EVENT_INDEX_LEB => return symbol.index,
.R_WASM_SECTION_OFFSET_I32 => {

View File

@ -139,12 +139,10 @@ pub fn isNoStrip(symbol: Symbol) bool {
return symbol.flags & @enumToInt(Flag.WASM_SYM_NO_STRIP) != 0;
}
pub fn isExported(symbol: Symbol) bool {
pub fn isExported(symbol: Symbol, is_dynamic: bool) bool {
if (symbol.isUndefined() or symbol.isLocal()) return false;
if (symbol.isHidden()) return false;
if (symbol.hasFlag(.WASM_SYM_EXPORTED)) return true;
if (symbol.hasFlag(.WASM_SYM_BINDING_WEAK)) return false;
return true;
if (is_dynamic and symbol.isVisible()) return true;
return symbol.hasFlag(.WASM_SYM_EXPORTED);
}
pub fn isWeak(symbol: Symbol) bool {

View File

@ -42,6 +42,16 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {
.requires_stage2 = true,
});
cases.addBuildFile("test/link/wasm/export/build.zig", .{
.build_modes = true,
.requires_stage2 = true,
});
// TODO: Fix open handle in wasm-linker refraining rename from working on Windows.
if (builtin.os.tag != .windows) {
cases.addBuildFile("test/link/wasm/export-data/build.zig", .{});
}
cases.addBuildFile("test/link/wasm/extern/build.zig", .{
.build_modes = true,
.requires_stage2 = true,
@ -53,6 +63,11 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {
.requires_stage2 = true,
});
cases.addBuildFile("test/link/wasm/function-table/build.zig", .{
.build_modes = true,
.requires_stage2 = true,
});
cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{
.requires_stage2 = true,
});

View File

@ -26,8 +26,7 @@ pub fn build(b: *Builder) void {
check_lib.checkNext("name memory"); // as per linker specification
// since we are importing memory, ensure it's not exported
check_lib.checkStart("Section export");
check_lib.checkNext("entries 1"); // we're exporting function 'foo' so only 1 entry
check_lib.checkNotPresent("Section export");
// validate the name of the stack pointer
check_lib.checkStart("Section custom");

View File

@ -0,0 +1,39 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const test_step = b.step("test", "Test");
test_step.dependOn(b.getInstallStep());
const lib = b.addSharedLibrary("lib", "lib.zig", .unversioned);
lib.setBuildMode(.ReleaseSafe); // to make the output deterministic in address positions
lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
lib.use_lld = false;
lib.export_symbol_names = &.{ "foo", "bar" };
lib.global_base = 0; // put data section at address 0 to make data symbols easier to parse
const check_lib = lib.checkObject(.wasm);
check_lib.checkStart("Section global");
check_lib.checkNext("entries 3");
check_lib.checkNext("type i32"); // stack pointer so skip other fields
check_lib.checkNext("type i32");
check_lib.checkNext("mutable false");
check_lib.checkNext("i32.const {foo_address}");
check_lib.checkNext("type i32");
check_lib.checkNext("mutable false");
check_lib.checkNext("i32.const {bar_address}");
check_lib.checkComputeCompare("foo_address", .{ .op = .eq, .value = .{ .literal = 0 } });
check_lib.checkComputeCompare("bar_address", .{ .op = .eq, .value = .{ .literal = 4 } });
check_lib.checkStart("Section export");
check_lib.checkNext("entries 3");
check_lib.checkNext("name foo");
check_lib.checkNext("kind global");
check_lib.checkNext("index 1");
check_lib.checkNext("name bar");
check_lib.checkNext("kind global");
check_lib.checkNext("index 2");
test_step.dependOn(&check_lib.step);
}

View File

@ -0,0 +1,2 @@
export const foo: u32 = 0xbbbbbbbb;
export const bar: u32 = 0xbbbbbbbb;

View File

@ -0,0 +1,48 @@
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
const mode = b.standardReleaseOptions();
const no_export = b.addSharedLibrary("no-export", "main.zig", .unversioned);
no_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
no_export.setBuildMode(mode);
no_export.use_llvm = false;
no_export.use_lld = false;
const dynamic_export = b.addSharedLibrary("dynamic", "main.zig", .unversioned);
dynamic_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
dynamic_export.setBuildMode(mode);
dynamic_export.rdynamic = true;
dynamic_export.use_llvm = false;
dynamic_export.use_lld = false;
const force_export = b.addSharedLibrary("force", "main.zig", .unversioned);
force_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
force_export.setBuildMode(mode);
force_export.export_symbol_names = &.{"foo"};
force_export.use_llvm = false;
force_export.use_lld = false;
const check_no_export = no_export.checkObject(.wasm);
check_no_export.checkStart("Section export");
check_no_export.checkNext("entries 1");
check_no_export.checkNext("name memory");
check_no_export.checkNext("kind memory");
const check_dynamic_export = dynamic_export.checkObject(.wasm);
check_dynamic_export.checkStart("Section export");
check_dynamic_export.checkNext("entries 2");
check_dynamic_export.checkNext("name foo");
check_dynamic_export.checkNext("kind function");
const check_force_export = force_export.checkObject(.wasm);
check_force_export.checkStart("Section export");
check_force_export.checkNext("entries 2");
check_force_export.checkNext("name foo");
check_force_export.checkNext("kind function");
const test_step = b.step("test", "Run linker test");
test_step.dependOn(&check_no_export.step);
test_step.dependOn(&check_dynamic_export.step);
test_step.dependOn(&check_force_export.step);
}

View File

@ -0,0 +1 @@
export fn foo() void {}

View File

@ -0,0 +1,63 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const test_step = b.step("test", "Test");
test_step.dependOn(b.getInstallStep());
const import_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
import_table.setBuildMode(mode);
import_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
import_table.use_llvm = false;
import_table.use_lld = false;
import_table.import_table = true;
const export_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
export_table.setBuildMode(mode);
export_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
export_table.use_llvm = false;
export_table.use_lld = false;
export_table.export_table = true;
const regular_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
regular_table.setBuildMode(mode);
regular_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
regular_table.use_llvm = false;
regular_table.use_lld = false;
const check_import = import_table.checkObject(.wasm);
const check_export = export_table.checkObject(.wasm);
const check_regular = regular_table.checkObject(.wasm);
check_import.checkStart("Section import");
check_import.checkNext("entries 1");
check_import.checkNext("module env");
check_import.checkNext("name __indirect_function_table");
check_import.checkNext("kind table");
check_import.checkNext("type funcref");
check_import.checkNext("min 1"); // 1 function pointer
check_import.checkNotPresent("max"); // when importing, we do not provide a max
check_import.checkNotPresent("Section table"); // we're importing it
check_export.checkStart("Section export");
check_export.checkNext("entries 2");
check_export.checkNext("name __indirect_function_table"); // as per linker specification
check_export.checkNext("kind table");
check_regular.checkStart("Section table");
check_regular.checkNext("entries 1");
check_regular.checkNext("type funcref");
check_regular.checkNext("min 2"); // index starts at 1 & 1 function pointer = 2.
check_regular.checkNext("max 2");
check_regular.checkStart("Section element");
check_regular.checkNext("entries 1");
check_regular.checkNext("table index 0");
check_regular.checkNext("i32.const 1"); // we want to start function indexes at 1
check_regular.checkNext("indexes 1"); // 1 function pointer
test_step.dependOn(&check_import.step);
test_step.dependOn(&check_export.step);
test_step.dependOn(&check_regular.step);
}

View File

@ -0,0 +1,7 @@
var func: *const fn () void = &bar;
export fn foo() void {
func();
}
fn bar() void {}