authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-07-19 21:55:30+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-07-19 21:55:30+02:00
logcec1e973b626ae6bd8ce1891537926e59c814463
treec0cfb76b2b4c835645dbd405eeb4d83debcd85e7
parent0aacb6369fdf3762b7ab6069955684abb63f1459
parent142dbc7b82c741692dd17f8c0455203826342bba
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #16439 from Luukdegram/wasm-linker

wasm-linker: finish shared-memory & TLS implementation

7 files changed, 217 insertions(+), 63 deletions(-)

lib/std/Build/Step/CheckObject.zig+14-3
...@@ -1012,6 +1012,10 @@ const WasmDumper = struct {...@@ -1012,6 +1012,10 @@ const WasmDumper = struct {
1012 const start = try std.leb.readULEB128(u32, reader);1012 const start = try std.leb.readULEB128(u32, reader);
1013 try writer.print("\nstart {d}\n", .{start});1013 try writer.print("\nstart {d}\n", .{start});
1014 },1014 },
1015 .data_count => {
1016 const count = try std.leb.readULEB128(u32, reader);
1017 try writer.print("\ncount {d}\n", .{count});
1018 },
1015 else => {}, // skip unknown sections1019 else => {}, // skip unknown sections
1016 }1020 }
1017 }1021 }
...@@ -1143,9 +1147,16 @@ const WasmDumper = struct {...@@ -1143,9 +1147,16 @@ const WasmDumper = struct {
1143 .data => {1147 .data => {
1144 var i: u32 = 0;1148 var i: u32 = 0;
1145 while (i < entries) : (i += 1) {1149 while (i < entries) : (i += 1) {
1146 const index = try std.leb.readULEB128(u32, reader);1150 const flags = try std.leb.readULEB128(u32, reader);
1151 const index = if (flags & 0x02 != 0)
1152 try std.leb.readULEB128(u32, reader)
1153 else
1154 0;
1147 try writer.print("memory index 0x{x}\n", .{index});1155 try writer.print("memory index 0x{x}\n", .{index});
1148 try parseDumpInit(step, reader, writer);1156 if (flags == 0) {
1157 try parseDumpInit(step, reader, writer);
1158 }
1159
1149 const size = try std.leb.readULEB128(u32, reader);1160 const size = try std.leb.readULEB128(u32, reader);
1150 try writer.print("size {d}\n", .{size});1161 try writer.print("size {d}\n", .{size});
1151 try reader.skipBytes(size, .{}); // we do not care about the content of the segments1162 try reader.skipBytes(size, .{}); // we do not care about the content of the segments
...@@ -1174,7 +1185,7 @@ const WasmDumper = struct {...@@ -1174,7 +1185,7 @@ const WasmDumper = struct {
1174 }1185 }
11751186
1176 fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void {1187 fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void {
1177 const byte = try std.leb.readULEB128(u8, reader);1188 const byte = try reader.readByte();
1178 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch {1189 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch {
1179 return step.fail("invalid wasm opcode '{d}'", .{byte});1190 return step.fail("invalid wasm opcode '{d}'", .{byte});
1180 };1191 };
lib/std/Build/Step/Compile.zig+4
...@@ -65,6 +65,7 @@ sanitize_thread: bool,...@@ -65,6 +65,7 @@ sanitize_thread: bool,
65rdynamic: bool,65rdynamic: bool,
66dwarf_format: ?std.dwarf.Format = null,66dwarf_format: ?std.dwarf.Format = null,
67import_memory: bool = false,67import_memory: bool = false,
68export_memory: bool = false,
68/// For WebAssembly targets, this will allow for undefined symbols to69/// For WebAssembly targets, this will allow for undefined symbols to
69/// be imported from the host environment.70/// be imported from the host environment.
70import_symbols: bool = false,71import_symbols: bool = false,
...@@ -1662,6 +1663,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -1662,6 +1663,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
1662 if (self.import_memory) {1663 if (self.import_memory) {
1663 try zig_args.append("--import-memory");1664 try zig_args.append("--import-memory");
1664 }1665 }
1666 if (self.export_memory) {
1667 try zig_args.append("--export-memory");
1668 }
1665 if (self.import_symbols) {1669 if (self.import_symbols) {
1666 try zig_args.append("--import-symbols");1670 try zig_args.append("--import-symbols");
1667 }1671 }
src/link/Wasm.zig+81-51
...@@ -125,6 +125,8 @@ exports: std.ArrayListUnmanaged(types.Export) = .{},...@@ -125,6 +125,8 @@ exports: std.ArrayListUnmanaged(types.Export) = .{},
125/// List of initialization functions. These must be called in order of priority125/// List of initialization functions. These must be called in order of priority
126/// by the (synthetic) __wasm_call_ctors function.126/// by the (synthetic) __wasm_call_ctors function.
127init_funcs: std.ArrayListUnmanaged(InitFuncLoc) = .{},127init_funcs: std.ArrayListUnmanaged(InitFuncLoc) = .{},
128/// Index to a function defining the entry of the wasm file
129entry: ?u32 = null,
128130
129/// Indirect function table, used to call function pointers131/// Indirect function table, used to call function pointers
130/// When this is non-zero, we must emit a table entry,132/// When this is non-zero, we must emit a table entry,
...@@ -409,7 +411,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -409,7 +411,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
409 },411 },
410 );412 );
411 } else {413 } else {
412 symbol.index = @as(u32, @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len));414 symbol.index = @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
413 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);415 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
414 const global = try wasm_bin.wasm_globals.addOne(allocator);416 const global = try wasm_bin.wasm_globals.addOne(allocator);
415 global.* = .{417 global.* = .{
...@@ -432,7 +434,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -432,7 +434,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
432 };434 };
433 if (options.output_mode == .Obj or options.import_table) {435 if (options.output_mode == .Obj or options.import_table) {
434 symbol.setUndefined(true);436 symbol.setUndefined(true);
435 symbol.index = @as(u32, @intCast(wasm_bin.imported_tables_count));437 symbol.index = @intCast(wasm_bin.imported_tables_count);
436 wasm_bin.imported_tables_count += 1;438 wasm_bin.imported_tables_count += 1;
437 try wasm_bin.imports.put(allocator, loc, .{439 try wasm_bin.imports.put(allocator, loc, .{
438 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),440 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
...@@ -466,19 +468,34 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -466,19 +468,34 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
466 const loc = try wasm_bin.createSyntheticSymbol("__tls_base", .global);468 const loc = try wasm_bin.createSyntheticSymbol("__tls_base", .global);
467 const symbol = loc.getSymbol(wasm_bin);469 const symbol = loc.getSymbol(wasm_bin);
468 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);470 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
471 symbol.index = @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
472 try wasm_bin.wasm_globals.append(wasm_bin.base.allocator, .{
473 .global_type = .{ .valtype = .i32, .mutable = true },
474 .init = .{ .i32_const = undefined },
475 });
469 }476 }
470 {477 {
471 const loc = try wasm_bin.createSyntheticSymbol("__tls_size", .global);478 const loc = try wasm_bin.createSyntheticSymbol("__tls_size", .global);
472 const symbol = loc.getSymbol(wasm_bin);479 const symbol = loc.getSymbol(wasm_bin);
473 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);480 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
481 symbol.index = @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
482 try wasm_bin.wasm_globals.append(wasm_bin.base.allocator, .{
483 .global_type = .{ .valtype = .i32, .mutable = false },
484 .init = .{ .i32_const = undefined },
485 });
474 }486 }
475 {487 {
476 const loc = try wasm_bin.createSyntheticSymbol("__tls_align", .global);488 const loc = try wasm_bin.createSyntheticSymbol("__tls_align", .global);
477 const symbol = loc.getSymbol(wasm_bin);489 const symbol = loc.getSymbol(wasm_bin);
478 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);490 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
491 symbol.index = @intCast(wasm_bin.imported_globals_count + wasm_bin.wasm_globals.items.len);
492 try wasm_bin.wasm_globals.append(wasm_bin.base.allocator, .{
493 .global_type = .{ .valtype = .i32, .mutable = false },
494 .init = .{ .i32_const = undefined },
495 });
479 }496 }
480 {497 {
481 const loc = try wasm_bin.createSyntheticSymbol("__wasm_tls_init", .function);498 const loc = try wasm_bin.createSyntheticSymbol("__wasm_init_tls", .function);
482 const symbol = loc.getSymbol(wasm_bin);499 const symbol = loc.getSymbol(wasm_bin);
483 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);500 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
484 }501 }
...@@ -844,6 +861,12 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {...@@ -844,6 +861,12 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
844 }861 }
845}862}
846863
864/// Writes an unsigned 32-bit integer as a LEB128-encoded 'i32.const' value.
865fn writeI32Const(writer: anytype, val: u32) !void {
866 try writer.writeByte(std.wasm.opcode(.i32_const));
867 try leb.writeILEB128(writer, @as(i32, @bitCast(val)));
868}
869
847fn setupInitMemoryFunction(wasm: *Wasm) !void {870fn setupInitMemoryFunction(wasm: *Wasm) !void {
848 // Passive segments are used to avoid memory being reinitialized on each871 // Passive segments are used to avoid memory being reinitialized on each
849 // thread's instantiation. These passive segments are initialized and872 // thread's instantiation. These passive segments are initialized and
...@@ -881,12 +904,9 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void {...@@ -881,12 +904,9 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void {
881 try writer.writeByte(std.wasm.block_empty); // block type904 try writer.writeByte(std.wasm.block_empty); // block type
882905
883 // atomically check906 // atomically check
884 try writer.writeByte(std.wasm.opcode(.i32_const));907 try writeI32Const(writer, flag_address);
885 try leb.writeULEB128(writer, flag_address);908 try writeI32Const(writer, 0);
886 try writer.writeByte(std.wasm.opcode(.i32_const));909 try writeI32Const(writer, 1);
887 try leb.writeULEB128(writer, @as(u32, 0));
888 try writer.writeByte(std.wasm.opcode(.i32_const));
889 try leb.writeULEB128(writer, @as(u32, 1));
890 try writer.writeByte(std.wasm.opcode(.atomics_prefix));910 try writer.writeByte(std.wasm.opcode(.atomics_prefix));
891 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_rmw_cmpxchg));911 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_rmw_cmpxchg));
892 try leb.writeULEB128(writer, @as(u32, 2)); // alignment912 try leb.writeULEB128(writer, @as(u32, 2)); // alignment
...@@ -910,24 +930,20 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void {...@@ -910,24 +930,20 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void {
910 // For non-BSS segments we do a memory.init. Both these930 // For non-BSS segments we do a memory.init. Both these
911 // instructions take as their first argument the destination931 // instructions take as their first argument the destination
912 // address.932 // address.
913 try writer.writeByte(std.wasm.opcode(.i32_const));933 try writeI32Const(writer, segment.offset);
914 try leb.writeULEB128(writer, segment.offset);
915934
916 if (wasm.base.options.shared_memory and std.mem.eql(u8, entry.key_ptr.*, ".tdata")) {935 if (wasm.base.options.shared_memory and std.mem.eql(u8, entry.key_ptr.*, ".tdata")) {
917 // When we initialize the TLS segment we also set the `__tls_base`936 // When we initialize the TLS segment we also set the `__tls_base`
918 // global. This allows the runtime to use this static copy of the937 // global. This allows the runtime to use this static copy of the
919 // TLS data for the first/main thread.938 // TLS data for the first/main thread.
920 try writer.writeByte(std.wasm.opcode(.i32_const));939 try writeI32Const(writer, segment.offset);
921 try leb.writeULEB128(writer, segment.offset);
922 try writer.writeByte(std.wasm.opcode(.global_set));940 try writer.writeByte(std.wasm.opcode(.global_set));
923 const loc = wasm.findGlobalSymbol("__tls_base").?;941 const loc = wasm.findGlobalSymbol("__tls_base").?;
924 try leb.writeULEB128(writer, loc.getSymbol(wasm).index);942 try leb.writeULEB128(writer, loc.getSymbol(wasm).index);
925 }943 }
926944
927 try writer.writeByte(std.wasm.opcode(.i32_const));945 try writeI32Const(writer, 0);
928 try leb.writeULEB128(writer, @as(u32, 0));946 try writeI32Const(writer, segment.size);
929 try writer.writeByte(std.wasm.opcode(.i32_const));
930 try leb.writeULEB128(writer, segment.size);
931 try writer.writeByte(std.wasm.opcode(.misc_prefix));947 try writer.writeByte(std.wasm.opcode(.misc_prefix));
932 if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) {948 if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) {
933 // fill bss segment with zeroes949 // fill bss segment with zeroes
...@@ -943,18 +959,15 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void {...@@ -943,18 +959,15 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void {
943959
944 if (wasm.base.options.shared_memory) {960 if (wasm.base.options.shared_memory) {
945 // we set the init memory flag to value '2'961 // we set the init memory flag to value '2'
946 try writer.writeByte(std.wasm.opcode(.i32_const));962 try writeI32Const(writer, flag_address);
947 try leb.writeULEB128(writer, flag_address);963 try writeI32Const(writer, 2);
948 try writer.writeByte(std.wasm.opcode(.i32_const));
949 try leb.writeULEB128(writer, @as(u32, 2));
950 try writer.writeByte(std.wasm.opcode(.atomics_prefix));964 try writer.writeByte(std.wasm.opcode(.atomics_prefix));
951 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_store));965 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_store));
952 try leb.writeULEB128(writer, @as(u32, 2)); // alignment966 try leb.writeULEB128(writer, @as(u32, 2)); // alignment
953 try leb.writeULEB128(writer, @as(u32, 0)); // offset967 try leb.writeULEB128(writer, @as(u32, 0)); // offset
954968
955 // notify any waiters for segment initialization completion969 // notify any waiters for segment initialization completion
956 try writer.writeByte(std.wasm.opcode(.i32_const));970 try writeI32Const(writer, flag_address);
957 try leb.writeULEB128(writer, flag_address);
958 try writer.writeByte(std.wasm.opcode(.i32_const));971 try writer.writeByte(std.wasm.opcode(.i32_const));
959 try leb.writeILEB128(writer, @as(i32, -1)); // number of waiters972 try leb.writeILEB128(writer, @as(i32, -1)); // number of waiters
960 try writer.writeByte(std.wasm.opcode(.atomics_prefix));973 try writer.writeByte(std.wasm.opcode(.atomics_prefix));
...@@ -969,12 +982,10 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void {...@@ -969,12 +982,10 @@ fn setupInitMemoryFunction(wasm: *Wasm) !void {
969982
970 // wait for thread to initialize memory segments983 // wait for thread to initialize memory segments
971 try writer.writeByte(std.wasm.opcode(.end)); // end $wait984 try writer.writeByte(std.wasm.opcode(.end)); // end $wait
972 try writer.writeByte(std.wasm.opcode(.i32_const));985 try writeI32Const(writer, flag_address);
973 try leb.writeULEB128(writer, flag_address);986 try writeI32Const(writer, 1); // expected flag value
974 try writer.writeByte(std.wasm.opcode(.i32_const));987 try writer.writeByte(std.wasm.opcode(.i64_const));
975 try leb.writeULEB128(writer, @as(u32, 1)); // expected flag value988 try leb.writeILEB128(writer, @as(i64, -1)); // timeout
976 try writer.writeByte(std.wasm.opcode(.i32_const));
977 try leb.writeILEB128(writer, @as(i32, -1)); // timeout
978 try writer.writeByte(std.wasm.opcode(.atomics_prefix));989 try writer.writeByte(std.wasm.opcode(.atomics_prefix));
979 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.memory_atomic_wait32));990 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.memory_atomic_wait32));
980 try leb.writeULEB128(writer, @as(u32, 2)); // alignment991 try leb.writeULEB128(writer, @as(u32, 2)); // alignment
...@@ -2154,7 +2165,14 @@ fn allocateVirtualAddresses(wasm: *Wasm) void {...@@ -2154,7 +2165,14 @@ fn allocateVirtualAddresses(wasm: *Wasm) void {
2154 const segment_name = segment_info[symbol.index].outputName(merge_segment);2165 const segment_name = segment_info[symbol.index].outputName(merge_segment);
2155 const segment_index = wasm.data_segments.get(segment_name).?;2166 const segment_index = wasm.data_segments.get(segment_name).?;
2156 const segment = wasm.segments.items[segment_index];2167 const segment = wasm.segments.items[segment_index];
2157 symbol.virtual_address = atom.offset + segment.offset;2168
2169 // TLS symbols have their virtual address set relative to their own TLS segment,
2170 // rather than the entire Data section.
2171 if (symbol.hasFlag(.WASM_SYM_TLS)) {
2172 symbol.virtual_address = atom.offset;
2173 } else {
2174 symbol.virtual_address = atom.offset + segment.offset;
2175 }
2158 }2176 }
2159}2177}
21602178
...@@ -2168,7 +2186,7 @@ fn sortDataSegments(wasm: *Wasm) !void {...@@ -2168,7 +2186,7 @@ fn sortDataSegments(wasm: *Wasm) !void {
21682186
2169 const SortContext = struct {2187 const SortContext = struct {
2170 fn sort(_: void, lhs: []const u8, rhs: []const u8) bool {2188 fn sort(_: void, lhs: []const u8, rhs: []const u8) bool {
2171 return order(lhs) <= order(rhs);2189 return order(lhs) < order(rhs);
2172 }2190 }
21732191
2174 fn order(name: []const u8) u8 {2192 fn order(name: []const u8) u8 {
...@@ -2399,6 +2417,13 @@ pub fn createFunction(...@@ -2399,6 +2417,13 @@ pub fn createFunction(
2399 return loc.index;2417 return loc.index;
2400}2418}
24012419
2420/// If required, sets the function index in the `start` section.
2421fn setupStartSection(wasm: *Wasm) !void {
2422 if (wasm.findGlobalSymbol("__wasm_init_memory")) |loc| {
2423 wasm.entry = loc.getSymbol(wasm).index;
2424 }
2425}
2426
2402fn initializeTLSFunction(wasm: *Wasm) !void {2427fn initializeTLSFunction(wasm: *Wasm) !void {
2403 if (!wasm.base.options.shared_memory) return;2428 if (!wasm.base.options.shared_memory) return;
24042429
...@@ -2420,7 +2445,7 @@ fn initializeTLSFunction(wasm: *Wasm) !void {...@@ -2420,7 +2445,7 @@ fn initializeTLSFunction(wasm: *Wasm) !void {
2420 try leb.writeULEB128(writer, param_local);2445 try leb.writeULEB128(writer, param_local);
24212446
2422 const tls_base_loc = wasm.findGlobalSymbol("__tls_base").?;2447 const tls_base_loc = wasm.findGlobalSymbol("__tls_base").?;
2423 try writer.writeByte(std.wasm.opcode(.global_get));2448 try writer.writeByte(std.wasm.opcode(.global_set));
2424 try leb.writeULEB128(writer, tls_base_loc.getSymbol(wasm).index);2449 try leb.writeULEB128(writer, tls_base_loc.getSymbol(wasm).index);
24252450
2426 // load stack values for the bulk-memory operation2451 // load stack values for the bulk-memory operation
...@@ -2748,27 +2773,18 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -2748,27 +2773,18 @@ fn setupMemory(wasm: *Wasm) !void {
2748 if (mem.eql(u8, entry.key_ptr.*, ".tdata")) {2773 if (mem.eql(u8, entry.key_ptr.*, ".tdata")) {
2749 if (wasm.findGlobalSymbol("__tls_size")) |loc| {2774 if (wasm.findGlobalSymbol("__tls_size")) |loc| {
2750 const sym = loc.getSymbol(wasm);2775 const sym = loc.getSymbol(wasm);
2751 sym.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count;2776 wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.size);
2752 try wasm.wasm_globals.append(wasm.base.allocator, .{
2753 .global_type = .{ .valtype = .i32, .mutable = false },
2754 .init = .{ .i32_const = @as(i32, @intCast(segment.size)) },
2755 });
2756 }2777 }
2757 if (wasm.findGlobalSymbol("__tls_align")) |loc| {2778 if (wasm.findGlobalSymbol("__tls_align")) |loc| {
2758 const sym = loc.getSymbol(wasm);2779 const sym = loc.getSymbol(wasm);
2759 sym.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count;2780 wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = @intCast(segment.alignment);
2760 try wasm.wasm_globals.append(wasm.base.allocator, .{
2761 .global_type = .{ .valtype = .i32, .mutable = false },
2762 .init = .{ .i32_const = @as(i32, @intCast(segment.alignment)) },
2763 });
2764 }2781 }
2765 if (wasm.findGlobalSymbol("__tls_base")) |loc| {2782 if (wasm.findGlobalSymbol("__tls_base")) |loc| {
2766 const sym = loc.getSymbol(wasm);2783 const sym = loc.getSymbol(wasm);
2767 sym.index = @as(u32, @intCast(wasm.wasm_globals.items.len)) + wasm.imported_globals_count;2784 wasm.wasm_globals.items[sym.index - wasm.imported_globals_count].init.i32_const = if (wasm.base.options.shared_memory)
2768 try wasm.wasm_globals.append(wasm.base.allocator, .{2785 @as(i32, 0)
2769 .global_type = .{ .valtype = .i32, .mutable = wasm.base.options.shared_memory },2786 else
2770 .init = .{ .i32_const = if (wasm.base.options.shared_memory) @as(u32, 0) else @as(i32, @intCast(memory_ptr)) },2787 @as(i32, @intCast(memory_ptr));
2771 });
2772 }2788 }
2773 }2789 }
27742790
...@@ -3323,6 +3339,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -3323,6 +3339,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
3323 try wasm.setupInitMemoryFunction();3339 try wasm.setupInitMemoryFunction();
3324 try wasm.setupTLSRelocationsFunction();3340 try wasm.setupTLSRelocationsFunction();
3325 try wasm.initializeTLSFunction();3341 try wasm.initializeTLSFunction();
3342 try wasm.setupStartSection();
3326 try wasm.setupExports();3343 try wasm.setupExports();
3327 try wasm.writeToFile(enabled_features, emit_features_count, arena);3344 try wasm.writeToFile(enabled_features, emit_features_count, arena);
33283345
...@@ -3460,6 +3477,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -3460,6 +3477,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
3460 try wasm.setupInitMemoryFunction();3477 try wasm.setupInitMemoryFunction();
3461 try wasm.setupTLSRelocationsFunction();3478 try wasm.setupTLSRelocationsFunction();
3462 try wasm.initializeTLSFunction();3479 try wasm.initializeTLSFunction();
3480 try wasm.setupStartSection();
3463 try wasm.setupExports();3481 try wasm.setupExports();
3464 try wasm.writeToFile(enabled_features, emit_features_count, arena);3482 try wasm.writeToFile(enabled_features, emit_features_count, arena);
3465}3483}
...@@ -3520,6 +3538,7 @@ fn writeToFile(...@@ -3520,6 +3538,7 @@ fn writeToFile(
35203538
3521 // Import section3539 // Import section
3522 const import_memory = wasm.base.options.import_memory or is_obj;3540 const import_memory = wasm.base.options.import_memory or is_obj;
3541 const export_memory = wasm.base.options.export_memory;
3523 if (wasm.imports.count() != 0 or import_memory) {3542 if (wasm.imports.count() != 0 or import_memory) {
3524 const header_offset = try reserveVecSectionHeader(&binary_bytes);3543 const header_offset = try reserveVecSectionHeader(&binary_bytes);
35253544
...@@ -3622,7 +3641,7 @@ fn writeToFile(...@@ -3622,7 +3641,7 @@ fn writeToFile(
3622 }3641 }
36233642
3624 // Export section3643 // Export section
3625 if (wasm.exports.items.len != 0 or !import_memory) {3644 if (wasm.exports.items.len != 0 or export_memory) {
3626 const header_offset = try reserveVecSectionHeader(&binary_bytes);3645 const header_offset = try reserveVecSectionHeader(&binary_bytes);
36273646
3628 for (wasm.exports.items) |exp| {3647 for (wasm.exports.items) |exp| {
...@@ -3633,7 +3652,7 @@ fn writeToFile(...@@ -3633,7 +3652,7 @@ fn writeToFile(
3633 try leb.writeULEB128(binary_writer, exp.index);3652 try leb.writeULEB128(binary_writer, exp.index);
3634 }3653 }
36353654
3636 if (!import_memory) {3655 if (export_memory) {
3637 try leb.writeULEB128(binary_writer, @as(u32, @intCast("memory".len)));3656 try leb.writeULEB128(binary_writer, @as(u32, @intCast("memory".len)));
3638 try binary_writer.writeAll("memory");3657 try binary_writer.writeAll("memory");
3639 try binary_writer.writeByte(std.wasm.externalKind(.memory));3658 try binary_writer.writeByte(std.wasm.externalKind(.memory));
...@@ -3645,11 +3664,22 @@ fn writeToFile(...@@ -3645,11 +3664,22 @@ fn writeToFile(
3645 header_offset,3664 header_offset,
3646 .@"export",3665 .@"export",
3647 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3666 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3648 @as(u32, @intCast(wasm.exports.items.len)) + @intFromBool(!import_memory),3667 @as(u32, @intCast(wasm.exports.items.len)) + @intFromBool(export_memory),
3649 );3668 );
3650 section_count += 1;3669 section_count += 1;
3651 }3670 }
36523671
3672 if (wasm.entry) |entry_index| {
3673 const header_offset = try reserveVecSectionHeader(&binary_bytes);
3674 try writeVecSectionHeader(
3675 binary_bytes.items,
3676 header_offset,
3677 .start,
3678 @intCast(binary_bytes.items.len - header_offset - header_size),
3679 entry_index,
3680 );
3681 }
3682
3653 // element section (function table)3683 // element section (function table)
3654 if (wasm.function_table.count() > 0) {3684 if (wasm.function_table.count() > 0) {
3655 const header_offset = try reserveVecSectionHeader(&binary_bytes);3685 const header_offset = try reserveVecSectionHeader(&binary_bytes);
...@@ -3683,7 +3713,7 @@ fn writeToFile(...@@ -3683,7 +3713,7 @@ fn writeToFile(
3683 }3713 }
36843714
3685 // When the shared-memory option is enabled, we *must* emit the 'data count' section.3715 // When the shared-memory option is enabled, we *must* emit the 'data count' section.
3686 const data_segments_count = wasm.data_segments.count() - @intFromBool(wasm.data_segments.contains(".bss") and import_memory);3716 const data_segments_count = wasm.data_segments.count() - @intFromBool(wasm.data_segments.contains(".bss") and !import_memory);
3687 if (data_segments_count != 0 and wasm.base.options.shared_memory) {3717 if (data_segments_count != 0 and wasm.base.options.shared_memory) {
3688 const header_offset = try reserveVecSectionHeader(&binary_bytes);3718 const header_offset = try reserveVecSectionHeader(&binary_bytes);
3689 try writeVecSectionHeader(3719 try writeVecSectionHeader(
src/link/Wasm/Atom.zig+7-6
...@@ -174,14 +174,14 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa...@@ -174,14 +174,14 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
174 return 0;174 return 0;
175 }175 }
176 const va = @as(i64, @intCast(symbol.virtual_address));176 const va = @as(i64, @intCast(symbol.virtual_address));
177 return @as(u32, @intCast(va + relocation.addend));177 return @intCast(va + relocation.addend);
178 },178 },
179 .R_WASM_EVENT_INDEX_LEB => return symbol.index,179 .R_WASM_EVENT_INDEX_LEB => return symbol.index,
180 .R_WASM_SECTION_OFFSET_I32 => {180 .R_WASM_SECTION_OFFSET_I32 => {
181 const target_atom_index = wasm_bin.symbol_atom.get(target_loc).?;181 const target_atom_index = wasm_bin.symbol_atom.get(target_loc).?;
182 const target_atom = wasm_bin.getAtom(target_atom_index);182 const target_atom = wasm_bin.getAtom(target_atom_index);
183 const rel_value = @as(i32, @intCast(target_atom.offset)) + relocation.addend;183 const rel_value: i32 = @intCast(target_atom.offset);
184 return @as(u32, @intCast(rel_value));184 return @intCast(rel_value + relocation.addend);
185 },185 },
186 .R_WASM_FUNCTION_OFFSET_I32 => {186 .R_WASM_FUNCTION_OFFSET_I32 => {
187 const target_atom_index = wasm_bin.symbol_atom.get(target_loc) orelse {187 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...@@ -189,13 +189,14 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
189 };189 };
190 const target_atom = wasm_bin.getAtom(target_atom_index);190 const target_atom = wasm_bin.getAtom(target_atom_index);
191 const offset: u32 = 11 + Wasm.getULEB128Size(target_atom.size); // Header (11 bytes fixed-size) + body size (leb-encoded)191 const offset: u32 = 11 + Wasm.getULEB128Size(target_atom.size); // Header (11 bytes fixed-size) + body size (leb-encoded)
192 const rel_value = @as(i32, @intCast(target_atom.offset + offset)) + relocation.addend;192 const rel_value: i32 = @intCast(target_atom.offset + offset);
193 return @as(u32, @intCast(rel_value));193 return @intCast(rel_value + relocation.addend);
194 },194 },
195 .R_WASM_MEMORY_ADDR_TLS_SLEB,195 .R_WASM_MEMORY_ADDR_TLS_SLEB,
196 .R_WASM_MEMORY_ADDR_TLS_SLEB64,196 .R_WASM_MEMORY_ADDR_TLS_SLEB64,
197 => {197 => {
198 @panic("TODO: Implement TLS relocations");198 const va: i32 = @intCast(symbol.virtual_address);
199 return @intCast(va + relocation.addend);
199 },200 },
200 }201 }
201}202}
src/link/Wasm/Object.zig+8-3
...@@ -353,9 +353,14 @@ fn Parser(comptime ReaderType: type) type {...@@ -353,9 +353,14 @@ fn Parser(comptime ReaderType: type) type {
353 var debug_names = std.ArrayList(u8).init(gpa);353 var debug_names = std.ArrayList(u8).init(gpa);
354354
355 errdefer {355 errdefer {
356 while (relocatable_data.popOrNull()) |rel_data| {356 // only free the inner contents of relocatable_data if we didn't
357 gpa.free(rel_data.data[0..rel_data.size]);357 // assign it to the object yet.
358 } else relocatable_data.deinit();358 if (parser.object.relocatable_data.len == 0) {
359 for (relocatable_data.items) |rel_data| {
360 gpa.free(rel_data.data[0..rel_data.size]);
361 }
362 relocatable_data.deinit();
363 }
359 gpa.free(debug_names.items);364 gpa.free(debug_names.items);
360 debug_names.deinit();365 debug_names.deinit();
361 }366 }
test/link/wasm/shared-memory/build.zig created+98
...@@ -0,0 +1,98 @@
1const std = @import("std");
2
3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");
5 b.default_step = test_step;
6
7 add(b, test_step, .Debug);
8 add(b, test_step, .ReleaseFast);
9 add(b, test_step, .ReleaseSmall);
10 add(b, test_step, .ReleaseSafe);
11}
12
13fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.OptimizeMode) void {
14 {
15 const lib = b.addSharedLibrary(.{
16 .name = "lib",
17 .root_source_file = .{ .path = "lib.zig" },
18 .target = .{
19 .cpu_arch = .wasm32,
20 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
21 .cpu_features_add = std.Target.wasm.featureSet(&.{ .atomics, .bulk_memory }),
22 .os_tag = .freestanding,
23 },
24 .optimize = optimize_mode,
25 });
26 lib.use_lld = false;
27 lib.strip = false;
28 lib.import_memory = true;
29 lib.export_memory = true;
30 lib.shared_memory = true;
31 lib.max_memory = 67108864;
32 lib.single_threaded = false;
33 lib.export_symbol_names = &.{"foo"};
34
35 const check_lib = lib.checkObject();
36
37 check_lib.checkStart("Section import");
38 check_lib.checkNext("entries 1");
39 check_lib.checkNext("module env");
40 check_lib.checkNext("name memory"); // ensure we are importing memory
41
42 check_lib.checkStart("Section export");
43 check_lib.checkNext("entries 2");
44 check_lib.checkNext("name memory"); // ensure we also export memory again
45
46 // This section *must* be emit as the start function is set to the index
47 // of __wasm_init_memory
48 // release modes will have the TLS segment optimized out in our test-case.
49 // This means we won't have __wasm_init_memory in such case, and therefore
50 // should also not have a section "start"
51 if (optimize_mode == .Debug) {
52 check_lib.checkStart("Section start");
53 }
54
55 // This section is only and *must* be emit when shared-memory is enabled
56 // release modes will have the TLS segment optimized out in our test-case.
57 if (optimize_mode == .Debug) {
58 check_lib.checkStart("Section data_count");
59 check_lib.checkNext("count 3");
60 }
61
62 check_lib.checkStart("Section custom");
63 check_lib.checkNext("name name");
64 check_lib.checkNext("type function");
65 if (optimize_mode == .Debug) {
66 check_lib.checkNext("name __wasm_init_memory");
67 }
68 check_lib.checkNext("name __wasm_init_tls");
69 check_lib.checkNext("type global");
70
71 // In debug mode the symbol __tls_base is resolved to an undefined symbol
72 // from the object file, hence its placement differs than in release modes
73 // where the entire tls segment is optimized away, and tls_base will have
74 // its original position.
75 if (optimize_mode == .Debug) {
76 check_lib.checkNext("name __tls_size");
77 check_lib.checkNext("name __tls_align");
78 check_lib.checkNext("name __tls_base");
79 } else {
80 check_lib.checkNext("name __tls_base");
81 check_lib.checkNext("name __tls_size");
82 check_lib.checkNext("name __tls_align");
83 }
84
85 check_lib.checkNext("type data_segment");
86 if (optimize_mode == .Debug) {
87 check_lib.checkNext("names 3");
88 check_lib.checkNext("index 0");
89 check_lib.checkNext("name .rodata");
90 check_lib.checkNext("index 1");
91 check_lib.checkNext("name .bss");
92 check_lib.checkNext("index 2");
93 check_lib.checkNext("name .tdata");
94 }
95
96 test_step.dependOn(&check_lib.step);
97 }
98}
test/link/wasm/shared-memory/lib.zig created+5
...@@ -0,0 +1,5 @@
1threadlocal var some_tls_global: u32 = 1;
2
3export fn foo() void {
4 some_tls_global = 2;
5}