diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 9beb40e418..d62d5adb25 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -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); diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index eb3a31b8a0..de9cefebdc 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -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 => { diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index 365f4a2c0b..089eee289e 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -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 { diff --git a/test/link.zig b/test/link.zig index ff18d65be5..5e26ae728d 100644 --- a/test/link.zig +++ b/test/link.zig @@ -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, }); diff --git a/test/link/wasm/bss/build.zig b/test/link/wasm/bss/build.zig index c9bc1aa106..e234a3f402 100644 --- a/test/link/wasm/bss/build.zig +++ b/test/link/wasm/bss/build.zig @@ -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"); diff --git a/test/link/wasm/export-data/build.zig b/test/link/wasm/export-data/build.zig new file mode 100644 index 0000000000..283566dab3 --- /dev/null +++ b/test/link/wasm/export-data/build.zig @@ -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); +} diff --git a/test/link/wasm/export-data/lib.zig b/test/link/wasm/export-data/lib.zig new file mode 100644 index 0000000000..dffce185fa --- /dev/null +++ b/test/link/wasm/export-data/lib.zig @@ -0,0 +1,2 @@ +export const foo: u32 = 0xbbbbbbbb; +export const bar: u32 = 0xbbbbbbbb; diff --git a/test/link/wasm/export/build.zig b/test/link/wasm/export/build.zig new file mode 100644 index 0000000000..181e77e296 --- /dev/null +++ b/test/link/wasm/export/build.zig @@ -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); +} diff --git a/test/link/wasm/export/main.zig b/test/link/wasm/export/main.zig new file mode 100644 index 0000000000..0e416dbf18 --- /dev/null +++ b/test/link/wasm/export/main.zig @@ -0,0 +1 @@ +export fn foo() void {} diff --git a/test/link/wasm/function-table/build.zig b/test/link/wasm/function-table/build.zig new file mode 100644 index 0000000000..f7572bd6b1 --- /dev/null +++ b/test/link/wasm/function-table/build.zig @@ -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); +} diff --git a/test/link/wasm/function-table/lib.zig b/test/link/wasm/function-table/lib.zig new file mode 100644 index 0000000000..ed7a85b2db --- /dev/null +++ b/test/link/wasm/function-table/lib.zig @@ -0,0 +1,7 @@ +var func: *const fn () void = &bar; + +export fn foo() void { + func(); +} + +fn bar() void {}