From 388589987c104354c072d04128327e9823c87497 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 16 Jul 2023 16:07:20 +0200 Subject: [PATCH 1/6] wasm-linker: allow explicit memory exports Rather than verifying if importing memory is false, we now rely on the option that was passed to the CLI (where export is defaulted to `true` unless only import-memory is given). --- src/link/Wasm.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 429fc12f14..3b019e166f 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3519,6 +3519,7 @@ fn writeToFile( // Import section const import_memory = wasm.base.options.import_memory or is_obj; + const export_memory = wasm.base.options.export_memory; if (wasm.imports.count() != 0 or import_memory) { const header_offset = try reserveVecSectionHeader(&binary_bytes); @@ -3621,7 +3622,7 @@ fn writeToFile( } // Export section - if (wasm.exports.items.len != 0 or !import_memory) { + if (wasm.exports.items.len != 0 or export_memory) { const header_offset = try reserveVecSectionHeader(&binary_bytes); for (wasm.exports.items) |exp| { @@ -3632,7 +3633,7 @@ fn writeToFile( try leb.writeULEB128(binary_writer, exp.index); } - if (!import_memory) { + if (export_memory) { try leb.writeULEB128(binary_writer, @as(u32, @intCast("memory".len))); try binary_writer.writeAll("memory"); try binary_writer.writeByte(std.wasm.externalKind(.memory)); @@ -3644,7 +3645,7 @@ fn writeToFile( header_offset, .@"export", @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)), - @as(u32, @intCast(wasm.exports.items.len)) + @intFromBool(!import_memory), + @as(u32, @intCast(wasm.exports.items.len)) + @intFromBool(export_memory), ); section_count += 1; } From 376e1b4603e70f1407862fde0180cfe45ca56e73 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 16 Jul 2023 16:31:07 +0200 Subject: [PATCH 2/6] wasm-linker: implement TLS relocations --- src/link/Wasm.zig | 9 ++++++++- src/link/Wasm/Atom.zig | 13 +++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 3b019e166f..f53c7bb287 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2153,7 +2153,14 @@ fn allocateVirtualAddresses(wasm: *Wasm) void { 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]; - symbol.virtual_address = atom.offset + segment.offset; + + // TLS symbols have their virtual address set relative to their own TLS segment, + // rather than the entire Data section. + if (symbol.hasFlag(.WASM_SYM_TLS)) { + symbol.virtual_address = atom.offset; + } else { + symbol.virtual_address = atom.offset + segment.offset; + } } } diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index 64e9ebaaa1..8987893ed7 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -174,14 +174,14 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa return 0; } const va = @as(i64, @intCast(symbol.virtual_address)); - return @as(u32, @intCast(va + relocation.addend)); + return @intCast(va + relocation.addend); }, .R_WASM_EVENT_INDEX_LEB => return symbol.index, .R_WASM_SECTION_OFFSET_I32 => { const target_atom_index = wasm_bin.symbol_atom.get(target_loc).?; const target_atom = wasm_bin.getAtom(target_atom_index); - const rel_value = @as(i32, @intCast(target_atom.offset)) + relocation.addend; - return @as(u32, @intCast(rel_value)); + const rel_value: i32 = @intCast(target_atom.offset); + return @intCast(rel_value + relocation.addend); }, .R_WASM_FUNCTION_OFFSET_I32 => { const target_atom_index = wasm_bin.symbol_atom.get(target_loc) orelse { @@ -189,13 +189,14 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa }; const target_atom = wasm_bin.getAtom(target_atom_index); const offset: u32 = 11 + Wasm.getULEB128Size(target_atom.size); // Header (11 bytes fixed-size) + body size (leb-encoded) - const rel_value = @as(i32, @intCast(target_atom.offset + offset)) + relocation.addend; - return @as(u32, @intCast(rel_value)); + const rel_value: i32 = @intCast(target_atom.offset + offset); + return @intCast(rel_value + relocation.addend); }, .R_WASM_MEMORY_ADDR_TLS_SLEB, .R_WASM_MEMORY_ADDR_TLS_SLEB64, => { - @panic("TODO: Implement TLS relocations"); + const va: i32 = @intCast(symbol.virtual_address); + return @intCast(va + relocation.addend); }, } } From 2672f7d9e8bcfc62b16a7073bddf330df4701762 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 16 Jul 2023 16:58:47 +0200 Subject: [PATCH 3/6] wasm-linker: shared-memory fixes Implements the `start` section which will execute a given function at startup of the program. After function execution, the _start function will be called by the runtime. In the case of shared-memory we set this section to the function `__wasm_init_memory` which will initialize all memory on startup. This also fixes the above mentioned function to ensure we correctly lower the i32 values. Lastly, this fixes a typo where we would retrieve a global, instead of setting its value. --- src/link/Wasm.zig | 76 ++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index f53c7bb287..e07f1d74bc 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -124,6 +124,8 @@ exports: std.ArrayListUnmanaged(types.Export) = .{}, /// List of initialization functions. These must be called in order of priority /// by the (synthetic) __wasm_call_ctors function. init_funcs: std.ArrayListUnmanaged(InitFuncLoc) = .{}, +/// Index to a function defining the entry of the wasm file +entry: ?u32 = null, /// Indirect function table, used to call function pointers /// When this is non-zero, we must emit a table entry, @@ -477,7 +479,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); } { - const loc = try wasm_bin.createSyntheticSymbol("__wasm_tls_init", .function); + const loc = try wasm_bin.createSyntheticSymbol("__wasm_init_tls", .function); const symbol = loc.getSymbol(wasm_bin); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); } @@ -843,6 +845,12 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void { } } +/// Writes an unsigned 32-bit integer as a LEB128-encoded 'i32.const' value. +fn writeI32Const(writer: anytype, val: u32) !void { + try writer.writeByte(std.wasm.opcode(.i32_const)); + try leb.writeILEB128(writer, @as(i32, @bitCast(val))); +} + fn setupInitMemoryFunction(wasm: *Wasm) !void { // Passive segments are used to avoid memory being reinitialized on each // thread's instantiation. These passive segments are initialized and @@ -880,12 +888,9 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void { try writer.writeByte(std.wasm.block_empty); // block type // atomically check - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, flag_address); - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, @as(u32, 0)); - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, @as(u32, 1)); + try writeI32Const(writer, flag_address); + try writeI32Const(writer, 0); + try writeI32Const(writer, 1); try writer.writeByte(std.wasm.opcode(.atomics_prefix)); try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_rmw_cmpxchg)); try leb.writeULEB128(writer, @as(u32, 2)); // alignment @@ -909,24 +914,20 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void { // For non-BSS segments we do a memory.init. Both these // instructions take as their first argument the destination // address. - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, segment.offset); + try writeI32Const(writer, segment.offset); if (wasm.base.options.shared_memory and std.mem.eql(u8, entry.key_ptr.*, ".tdata")) { // When we initialize the TLS segment we also set the `__tls_base` // global. This allows the runtime to use this static copy of the // TLS data for the first/main thread. - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, segment.offset); + try writeI32Const(writer, segment.offset); try writer.writeByte(std.wasm.opcode(.global_set)); const loc = wasm.findGlobalSymbol("__tls_base").?; try leb.writeULEB128(writer, loc.getSymbol(wasm).index); } - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, @as(u32, 0)); - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, segment.size); + try writeI32Const(writer, 0); + try writeI32Const(writer, segment.size); try writer.writeByte(std.wasm.opcode(.misc_prefix)); if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) { // fill bss segment with zeroes @@ -942,18 +943,15 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void { if (wasm.base.options.shared_memory) { // we set the init memory flag to value '2' - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, flag_address); - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, @as(u32, 2)); + try writeI32Const(writer, flag_address); + try writeI32Const(writer, 2); try writer.writeByte(std.wasm.opcode(.atomics_prefix)); try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_store)); try leb.writeULEB128(writer, @as(u32, 2)); // alignment try leb.writeULEB128(writer, @as(u32, 0)); // offset // notify any waiters for segment initialization completion - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, flag_address); + try writeI32Const(writer, flag_address); try writer.writeByte(std.wasm.opcode(.i32_const)); try leb.writeILEB128(writer, @as(i32, -1)); // number of waiters try writer.writeByte(std.wasm.opcode(.atomics_prefix)); @@ -968,12 +966,10 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void { // wait for thread to initialize memory segments try writer.writeByte(std.wasm.opcode(.end)); // end $wait - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, flag_address); - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, @as(u32, 1)); // expected flag value - try writer.writeByte(std.wasm.opcode(.i32_const)); - try leb.writeILEB128(writer, @as(i32, -1)); // timeout + try writeI32Const(writer, flag_address); + try writeI32Const(writer, 1); // expected flag value + try writer.writeByte(std.wasm.opcode(.i64_const)); + try leb.writeILEB128(writer, @as(i64, -1)); // timeout try writer.writeByte(std.wasm.opcode(.atomics_prefix)); try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.memory_atomic_wait32)); try leb.writeULEB128(writer, @as(u32, 2)); // alignment @@ -2174,7 +2170,7 @@ fn sortDataSegments(wasm: *Wasm) !void { const SortContext = struct { fn sort(_: void, lhs: []const u8, rhs: []const u8) bool { - return order(lhs) <= order(rhs); + return order(lhs) < order(rhs); } fn order(name: []const u8) u8 { @@ -2405,6 +2401,13 @@ pub fn createFunction( return loc.index; } +/// If required, sets the function index in the `start` section. +fn setupStartSection(wasm: *Wasm) !void { + if (wasm.findGlobalSymbol("__wasm_init_memory")) |loc| { + wasm.entry = loc.getSymbol(wasm).index; + } +} + fn initializeTLSFunction(wasm: *Wasm) !void { if (!wasm.base.options.shared_memory) return; @@ -2426,7 +2429,7 @@ fn initializeTLSFunction(wasm: *Wasm) !void { try leb.writeULEB128(writer, param_local); const tls_base_loc = wasm.findGlobalSymbol("__tls_base").?; - try writer.writeByte(std.wasm.opcode(.global_get)); + try writer.writeByte(std.wasm.opcode(.global_set)); try leb.writeULEB128(writer, tls_base_loc.getSymbol(wasm).index); // load stack values for the bulk-memory operation @@ -3329,6 +3332,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l try wasm.setupInitMemoryFunction(); try wasm.setupTLSRelocationsFunction(); try wasm.initializeTLSFunction(); + try wasm.setupStartSection(); try wasm.setupExports(); try wasm.writeToFile(enabled_features, emit_features_count, arena); @@ -3466,6 +3470,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod try wasm.setupInitMemoryFunction(); try wasm.setupTLSRelocationsFunction(); try wasm.initializeTLSFunction(); + try wasm.setupStartSection(); try wasm.setupExports(); try wasm.writeToFile(enabled_features, emit_features_count, arena); } @@ -3657,6 +3662,17 @@ fn writeToFile( section_count += 1; } + if (wasm.entry) |entry_index| { + const header_offset = try reserveVecSectionHeader(&binary_bytes); + try writeVecSectionHeader( + binary_bytes.items, + header_offset, + .start, + @intCast(binary_bytes.items.len - header_offset - header_size), + entry_index, + ); + } + // element section (function table) if (wasm.function_table.count() > 0) { const header_offset = try reserveVecSectionHeader(&binary_bytes); @@ -3690,7 +3706,7 @@ fn writeToFile( } // When the shared-memory option is enabled, we *must* emit the 'data count' section. - const data_segments_count = wasm.data_segments.count() - @intFromBool(wasm.data_segments.contains(".bss") and import_memory); + const data_segments_count = wasm.data_segments.count() - @intFromBool(wasm.data_segments.contains(".bss") and !import_memory); if (data_segments_count != 0 and wasm.base.options.shared_memory) { const header_offset = try reserveVecSectionHeader(&binary_bytes); try writeVecSectionHeader( From 3fd6e93f4f6f34658d5e198064f54e1dad09e241 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Mon, 17 Jul 2023 18:38:02 +0200 Subject: [PATCH 4/6] wasm-linker: prevent double-free on parse failure --- src/link/Wasm/Object.zig | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index 8e4df417ae..6feec26aea 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -353,9 +353,14 @@ fn Parser(comptime ReaderType: type) type { var debug_names = std.ArrayList(u8).init(gpa); errdefer { - while (relocatable_data.popOrNull()) |rel_data| { - gpa.free(rel_data.data[0..rel_data.size]); - } else relocatable_data.deinit(); + // only free the inner contents of relocatable_data if we didn't + // assign it to the object yet. + if (parser.object.relocatable_data.len == 0) { + for (relocatable_data.items) |rel_data| { + gpa.free(rel_data.data[0..rel_data.size]); + } + relocatable_data.deinit(); + } gpa.free(debug_names.items); debug_names.deinit(); } From 1a3304ed236b60cd31c790bae929a78c58c9d33e Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Mon, 17 Jul 2023 20:22:25 +0200 Subject: [PATCH 5/6] test/link: add shared-memory test for WebAssembly --- lib/std/Build/Step/CheckObject.zig | 17 +++++- lib/std/Build/Step/Compile.zig | 4 ++ test/link/wasm/shared-memory/build.zig | 77 ++++++++++++++++++++++++++ test/link/wasm/shared-memory/lib.zig | 5 ++ 4 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 test/link/wasm/shared-memory/build.zig create mode 100644 test/link/wasm/shared-memory/lib.zig diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index 5c24d53722..0c74a4e40e 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -1012,6 +1012,10 @@ const WasmDumper = struct { const start = try std.leb.readULEB128(u32, reader); try writer.print("\nstart {d}\n", .{start}); }, + .data_count => { + const count = try std.leb.readULEB128(u32, reader); + try writer.print("\ncount {d}\n", .{count}); + }, else => {}, // skip unknown sections } } @@ -1143,9 +1147,16 @@ const WasmDumper = struct { .data => { var i: u32 = 0; while (i < entries) : (i += 1) { - const index = try std.leb.readULEB128(u32, reader); + const flags = try std.leb.readULEB128(u32, reader); + const index = if (flags & 0x02 != 0) + try std.leb.readULEB128(u32, reader) + else + 0; try writer.print("memory index 0x{x}\n", .{index}); - try parseDumpInit(step, reader, writer); + if (flags == 0) { + try parseDumpInit(step, reader, writer); + } + const size = try std.leb.readULEB128(u32, reader); try writer.print("size {d}\n", .{size}); try reader.skipBytes(size, .{}); // we do not care about the content of the segments @@ -1174,7 +1185,7 @@ const WasmDumper = struct { } fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void { - const byte = try std.leb.readULEB128(u8, reader); + const byte = try reader.readByte(); const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch { return step.fail("invalid wasm opcode '{d}'", .{byte}); }; diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index c83f72328c..22e88a0d71 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -65,6 +65,7 @@ sanitize_thread: bool, rdynamic: bool, dwarf_format: ?std.dwarf.Format = null, import_memory: bool = false, +export_memory: bool = false, /// For WebAssembly targets, this will allow for undefined symbols to /// be imported from the host environment. import_symbols: bool = false, @@ -1662,6 +1663,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.import_memory) { try zig_args.append("--import-memory"); } + if (self.export_memory) { + try zig_args.append("--export-memory"); + } if (self.import_symbols) { try zig_args.append("--import-symbols"); } diff --git a/test/link/wasm/shared-memory/build.zig b/test/link/wasm/shared-memory/build.zig new file mode 100644 index 0000000000..7107ad1c4e --- /dev/null +++ b/test/link/wasm/shared-memory/build.zig @@ -0,0 +1,77 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test"); + b.default_step = test_step; + + add(b, test_step, .Debug); + + // Enable the following build modes once garbage-collection is implemented properly. + // add(b, test_step, .ReleaseFast); + // add(b, test_step, .ReleaseSmall); + // add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.OptimizeMode) void { + { + const lib = b.addSharedLibrary(.{ + .name = "lib", + .root_source_file = .{ .path = "lib.zig" }, + .target = .{ + .cpu_arch = .wasm32, + .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp }, + .cpu_features_add = std.Target.wasm.featureSet(&.{ .atomics, .bulk_memory }), + .os_tag = .freestanding, + }, + .optimize = optimize_mode, + }); + lib.use_lld = false; + lib.strip = false; + lib.import_memory = true; + lib.export_memory = true; + lib.shared_memory = true; + lib.max_memory = 67108864; + lib.single_threaded = false; + lib.export_symbol_names = &.{"foo"}; + + const check_lib = lib.checkObject(); + + check_lib.checkStart("Section import"); + check_lib.checkNext("entries 1"); + check_lib.checkNext("module env"); + check_lib.checkNext("name memory"); // ensure we are importing memory + + check_lib.checkStart("Section export"); + check_lib.checkNext("entries 2"); + check_lib.checkNext("name memory"); // ensure we also export memory again + + // This section *must* be emit as the start function is set to the index + // of __wasm_init_memory + check_lib.checkStart("Section start"); + + // This section is only and *must* be emit when shared-memory is enabled + check_lib.checkStart("Section data_count"); + check_lib.checkNext("count 3"); + + check_lib.checkStart("Section custom"); + check_lib.checkNext("name name"); + check_lib.checkNext("type function"); + check_lib.checkNext("name __wasm_init_memory"); + check_lib.checkNext("name __wasm_init_tls"); + check_lib.checkNext("type global"); + check_lib.checkNext("name __tls_size"); + check_lib.checkNext("name __tls_align"); + check_lib.checkNext("name __tls_base"); + + check_lib.checkNext("type data_segment"); + check_lib.checkNext("names 3"); + check_lib.checkNext("index 0"); + check_lib.checkNext("name .rodata"); + check_lib.checkNext("index 1"); + check_lib.checkNext("name .bss"); + check_lib.checkNext("index 2"); + check_lib.checkNext("name .tdata"); + + test_step.dependOn(&check_lib.step); + } +} diff --git a/test/link/wasm/shared-memory/lib.zig b/test/link/wasm/shared-memory/lib.zig new file mode 100644 index 0000000000..714babb16c --- /dev/null +++ b/test/link/wasm/shared-memory/lib.zig @@ -0,0 +1,5 @@ +threadlocal var some_tls_global: u32 = 1; + +export fn foo() void { + some_tls_global = 2; +} From 142dbc7b82c741692dd17f8c0455203826342bba Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 18 Jul 2023 19:55:20 +0200 Subject: [PATCH 6/6] wasm-linker: create TLS Wasm globals correctly Previously, they were only created when we had any TLS segment. This meant that while the symbol existed, the global itself wouldn't. The result of this was a crash during symbol names writing as it would attempt to write the symbol name of a global that didn't exist. Now we always create them, and instead update its `init` value during `setupMemory`. In the future, the entire symbol (and global) will be removed by the garbage collector. --- src/link/Wasm.zig | 40 +++++++++-------- test/link/wasm/shared-memory/build.zig | 59 +++++++++++++++++--------- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e07f1d74bc..134194068e 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -410,7 +410,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }, ); } else { - symbol.index = @as(u32, @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len)); + symbol.index = @intCast(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.* = .{ @@ -433,7 +433,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }; if (options.output_mode == .Obj or options.import_table) { symbol.setUndefined(true); - symbol.index = @as(u32, @intCast(wasm_bin.imported_tables_count)); + symbol.index = @intCast(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), @@ -467,16 +467,31 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option const loc = try wasm_bin.createSyntheticSymbol("__tls_base", .global); const symbol = loc.getSymbol(wasm_bin); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); + symbol.index = @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len); + try wasm_bin.wasm_globals.append(wasm_bin.base.allocator, .{ + .global_type = .{ .valtype = .i32, .mutable = true }, + .init = .{ .i32_const = undefined }, + }); } { const loc = try wasm_bin.createSyntheticSymbol("__tls_size", .global); const symbol = loc.getSymbol(wasm_bin); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); + symbol.index = @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len); + try wasm_bin.wasm_globals.append(wasm_bin.base.allocator, .{ + .global_type = .{ .valtype = .i32, .mutable = false }, + .init = .{ .i32_const = undefined }, + }); } { const loc = try wasm_bin.createSyntheticSymbol("__tls_align", .global); const symbol = loc.getSymbol(wasm_bin); symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); + symbol.index = @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len); + try wasm_bin.wasm_globals.append(wasm_bin.base.allocator, .{ + .global_type = .{ .valtype = .i32, .mutable = false }, + .init = .{ .i32_const = undefined }, + }); } { const loc = try wasm_bin.createSyntheticSymbol("__wasm_init_tls", .function); @@ -2757,27 +2772,18 @@ fn setupMemory(wasm: *Wasm) !void { if (mem.eql(u8, entry.key_ptr.*, ".tdata")) { if (wasm.findGlobalSymbol("__tls_size")) |loc| { const sym = loc.getSymbol(wasm); - sym.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count; - try wasm.wasm_globals.append(wasm.base.allocator, .{ - .global_type = .{ .valtype = .i32, .mutable = false }, - .init = .{ .i32_const = @as(i32, @intCast(segment.size)) }, - }); + wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.size); } if (wasm.findGlobalSymbol("__tls_align")) |loc| { const sym = loc.getSymbol(wasm); - sym.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count; - try wasm.wasm_globals.append(wasm.base.allocator, .{ - .global_type = .{ .valtype = .i32, .mutable = false }, - .init = .{ .i32_const = @as(i32, @intCast(segment.alignment)) }, - }); + wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.alignment); } if (wasm.findGlobalSymbol("__tls_base")) |loc| { const sym = loc.getSymbol(wasm); - sym.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count; - try wasm.wasm_globals.append(wasm.base.allocator, .{ - .global_type = .{ .valtype = .i32, .mutable = wasm.base.options.shared_memory }, - .init = .{ .i32_const = if (wasm.base.options.shared_memory) @as(u32, 0) else @as(i32, @intCast(memory_ptr)) }, - }); + wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = if (wasm.base.options.shared_memory) + @as(i32, 0) + else + @as(i32, @intCast(memory_ptr)); } } diff --git a/test/link/wasm/shared-memory/build.zig b/test/link/wasm/shared-memory/build.zig index 7107ad1c4e..cf84ad7528 100644 --- a/test/link/wasm/shared-memory/build.zig +++ b/test/link/wasm/shared-memory/build.zig @@ -5,11 +5,9 @@ pub fn build(b: *std.Build) void { b.default_step = test_step; add(b, test_step, .Debug); - - // Enable the following build modes once garbage-collection is implemented properly. - // add(b, test_step, .ReleaseFast); - // add(b, test_step, .ReleaseSmall); - // add(b, test_step, .ReleaseSafe); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); } fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.OptimizeMode) void { @@ -47,30 +45,53 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt // This section *must* be emit as the start function is set to the index // of __wasm_init_memory - check_lib.checkStart("Section start"); + // release modes will have the TLS segment optimized out in our test-case. + // This means we won't have __wasm_init_memory in such case, and therefore + // should also not have a section "start" + if (optimize_mode == .Debug) { + check_lib.checkStart("Section start"); + } // This section is only and *must* be emit when shared-memory is enabled - check_lib.checkStart("Section data_count"); - check_lib.checkNext("count 3"); + // release modes will have the TLS segment optimized out in our test-case. + if (optimize_mode == .Debug) { + check_lib.checkStart("Section data_count"); + check_lib.checkNext("count 3"); + } check_lib.checkStart("Section custom"); check_lib.checkNext("name name"); check_lib.checkNext("type function"); - check_lib.checkNext("name __wasm_init_memory"); + if (optimize_mode == .Debug) { + check_lib.checkNext("name __wasm_init_memory"); + } check_lib.checkNext("name __wasm_init_tls"); check_lib.checkNext("type global"); - check_lib.checkNext("name __tls_size"); - check_lib.checkNext("name __tls_align"); - check_lib.checkNext("name __tls_base"); + + // In debug mode the symbol __tls_base is resolved to an undefined symbol + // from the object file, hence its placement differs than in release modes + // where the entire tls segment is optimized away, and tls_base will have + // its original position. + if (optimize_mode == .Debug) { + check_lib.checkNext("name __tls_size"); + check_lib.checkNext("name __tls_align"); + check_lib.checkNext("name __tls_base"); + } else { + check_lib.checkNext("name __tls_base"); + check_lib.checkNext("name __tls_size"); + check_lib.checkNext("name __tls_align"); + } check_lib.checkNext("type data_segment"); - check_lib.checkNext("names 3"); - check_lib.checkNext("index 0"); - check_lib.checkNext("name .rodata"); - check_lib.checkNext("index 1"); - check_lib.checkNext("name .bss"); - check_lib.checkNext("index 2"); - check_lib.checkNext("name .tdata"); + if (optimize_mode == .Debug) { + check_lib.checkNext("names 3"); + check_lib.checkNext("index 0"); + check_lib.checkNext("name .rodata"); + check_lib.checkNext("index 1"); + check_lib.checkNext("name .bss"); + check_lib.checkNext("index 2"); + check_lib.checkNext("name .tdata"); + } test_step.dependOn(&check_lib.step); }