authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-03-19 15:43:06+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-03-19 15:43:06+01:00
logc26cbd561c812a8915bc3c3480358b0f2be10de0
tree93bebc0c00d501352668451471e0d2bd41d530b0
parent322ace70f9fb5c3e8833255d1df033208c448349
parent4e0d7154b1a701b906f3d9c5401dc0109253971f
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14998 from Luukdegram/shared-mem

wasm-linker: Implement shared-memory

9 files changed, 716 insertions(+), 76 deletions(-)

lib/std/wasm.zig+105-3
......@@ -189,7 +189,9 @@ pub const Opcode = enum(u8) {
189189 i64_extend16_s = 0xC3,
190190 i64_extend32_s = 0xC4,
191191
192 prefixed = 0xFC,
192 misc_prefix = 0xFC,
193 simd_prefix = 0xFD,
194 atomics_prefix = 0xFE,
193195 _,
194196};
195197
......@@ -217,7 +219,7 @@ test "Wasm - opcodes" {
217219/// Opcodes that require a prefix `0xFC`
218220/// Each opcode represents a varuint32, meaning
219221/// they are encoded as leb128 in binary.
220pub const PrefixedOpcode = enum(u32) {
222pub const MiscOpcode = enum(u32) {
221223 i32_trunc_sat_f32_s = 0x00,
222224 i32_trunc_sat_f32_u = 0x01,
223225 i32_trunc_sat_f64_s = 0x02,
......@@ -239,6 +241,12 @@ pub const PrefixedOpcode = enum(u32) {
239241 _,
240242};
241243
244/// Returns the integer value of an `MiscOpcode`. Used by the Zig compiler
245/// to write instructions to the wasm binary file
246pub fn miscOpcode(op: MiscOpcode) u32 {
247 return @enumToInt(op);
248}
249
242250/// Simd opcodes that require a prefix `0xFD`.
243251/// Each opcode represents a varuint32, meaning
244252/// they are encoded as leb128 in binary.
......@@ -510,6 +518,86 @@ pub fn simdOpcode(op: SimdOpcode) u32 {
510518 return @enumToInt(op);
511519}
512520
521/// Simd opcodes that require a prefix `0xFE`.
522/// Each opcode represents a varuint32, meaning
523/// they are encoded as leb128 in binary.
524pub const AtomicsOpcode = enum(u32) {
525 memory_atomic_notify = 0x00,
526 memory_atomic_wait32 = 0x01,
527 memory_atomic_wait64 = 0x02,
528 atomic_fence = 0x03,
529 i32_atomic_load = 0x10,
530 i64_atomic_load = 0x11,
531 i32_atomic_load8_u = 0x12,
532 i32_atomic_load16_u = 0x13,
533 i64_atomic_load8_u = 0x14,
534 i64_atomic_load16_u = 0x15,
535 i64_atomic_load32_u = 0x16,
536 i32_atomic_store = 0x17,
537 i64_atomic_store = 0x18,
538 i32_atomic_store8 = 0x19,
539 i32_atomic_store16 = 0x1A,
540 i64_atomic_store8 = 0x1B,
541 i64_atomic_store16 = 0x1C,
542 i64_atomic_store32 = 0x1D,
543 i32_atomic_rmw_add = 0x1E,
544 i64_atomic_rmw_add = 0x1F,
545 i32_atomic_rmw8_add_u = 0x20,
546 i32_atomic_rmw16_add_u = 0x21,
547 i64_atomic_rmw8_add_u = 0x22,
548 i64_atomic_rmw16_add_u = 0x23,
549 i64_atomic_rmw32_add_u = 0x24,
550 i32_atomic_rmw_sub = 0x25,
551 i64_atomic_rmw_sub = 0x26,
552 i32_atomic_rmw8_sub_u = 0x27A,
553 i32_atomic_rmw16_sub_u = 0x28A,
554 i64_atomic_rmw8_sub_u = 0x29A,
555 i64_atomic_rmw16_sub_u = 0x2A,
556 i64_atomic_rmw32_sub_u = 0x2B,
557 i32_atomic_rmw_and = 0x2C,
558 i64_atomic_rmw_and = 0x2D,
559 i32_atomic_rmw8_and_u = 0x2E,
560 i32_atomic_rmw16_and_u = 0x2F,
561 i64_atomic_rmw8_and_u = 0x30,
562 i64_atomic_rmw16_and_u = 0x31,
563 i64_atomic_rmw32_and_u = 0x32,
564 i32_atomic_rmw_or = 0x33,
565 i64_atomic_rmw_or = 0x34,
566 i32_atomic_rmw8_or_u = 0x35,
567 i32_atomic_rmw16_or_u = 0x36,
568 i64_atomic_rmw8_or_u = 0x37,
569 i64_atomic_rmw16_or_u = 0x38,
570 i64_atomic_rmw32_or_u = 0x39,
571 i32_atomic_rmw_xor = 0x3A,
572 i64_atomic_rmw_xor = 0x3B,
573 i32_atomic_rmw8_xor_u = 0x3C,
574 i32_atomic_rmw16_xor_u = 0x3D,
575 i64_atomic_rmw8_xor_u = 0x3E,
576 i64_atomic_rmw16_xor_u = 0x3F,
577 i64_atomic_rmw32_xor_u = 0x40,
578 i32_atomic_rmw_xchg = 0x41,
579 i64_atomic_rmw_xchg = 0x42,
580 i32_atomic_rmw8_xchg_u = 0x43,
581 i32_atomic_rmw16_xchg_u = 0x44,
582 i64_atomic_rmw8_xchg_u = 0x45,
583 i64_atomic_rmw16_xchg_u = 0x46,
584 i64_atomic_rmw32_xchg_u = 0x47,
585
586 i32_atomic_rmw_cmpxchg = 0x48,
587 i64_atomic_rmw_cmpxchg = 0x49,
588 i32_atomic_rmw8_cmpxchg_u = 0x4A,
589 i32_atomic_rmw16_cmpxchg_u = 0x4B,
590 i64_atomic_rmw8_cmpxchg_u = 0x4C,
591 i64_atomic_rmw16_cmpxchg_u = 0x4D,
592 i64_atomic_rmw32_cmpxchg_u = 0x4E,
593};
594
595/// Returns the integer value of an `AtomicsOpcode`. Used by the Zig compiler
596/// to write instructions to the wasm binary file
597pub fn atomicsOpcode(op: AtomicsOpcode) u32 {
598 return @enumToInt(op);
599}
600
513601/// Enum representing all Wasm value types as per spec:
514602/// https://webassembly.github.io/spec/core/binary/types.html
515603pub const Valtype = enum(u8) {
......@@ -551,8 +639,22 @@ test "Wasm - valtypes" {
551639
552640/// Limits classify the size range of resizeable storage associated with memory types and table types.
553641pub const Limits = struct {
642 flags: u8,
554643 min: u32,
555 max: ?u32,
644 max: u32,
645
646 pub const Flags = enum(u8) {
647 WASM_LIMITS_FLAG_HAS_MAX = 0x1,
648 WASM_LIMITS_FLAG_IS_SHARED = 0x2,
649 };
650
651 pub fn hasFlag(limits: Limits, flag: Flags) bool {
652 return limits.flags & @enumToInt(flag) != 0;
653 }
654
655 pub fn setFlag(limits: *Limits, flag: Flags) void {
656 limits.flags |= @enumToInt(flag);
657 }
556658};
557659
558660/// Initialization expressions are used to set the initial value on an object
src/arch/wasm/CodeGen.zig+7-7
......@@ -895,10 +895,10 @@ fn addTag(func: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
895895 try func.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
896896}
897897
898fn addExtended(func: *CodeGen, opcode: wasm.PrefixedOpcode) error{OutOfMemory}!void {
898fn addExtended(func: *CodeGen, opcode: wasm.MiscOpcode) error{OutOfMemory}!void {
899899 const extra_index = @intCast(u32, func.mir_extra.items.len);
900900 try func.mir_extra.append(func.gpa, @enumToInt(opcode));
901 try func.addInst(.{ .tag = .extended, .data = .{ .payload = extra_index } });
901 try func.addInst(.{ .tag = .misc_prefix, .data = .{ .payload = extra_index } });
902902}
903903
904904fn addLabel(func: *CodeGen, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {
......@@ -925,7 +925,7 @@ fn addImm128(func: *CodeGen, index: u32) error{OutOfMemory}!void {
925925 try func.mir_extra.ensureUnusedCapacity(func.gpa, 5);
926926 func.mir_extra.appendAssumeCapacity(std.wasm.simdOpcode(.v128_const));
927927 func.mir_extra.appendSliceAssumeCapacity(@alignCast(4, mem.bytesAsSlice(u32, &simd_values)));
928 try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
928 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
929929}
930930
931931fn addFloat64(func: *CodeGen, float: f64) error{OutOfMemory}!void {
......@@ -2310,7 +2310,7 @@ fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerE
23102310 offset + lhs.offset(),
23112311 ty.abiAlignment(func.target),
23122312 });
2313 return func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
2313 return func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
23142314 },
23152315 },
23162316 .Pointer => {
......@@ -2420,7 +2420,7 @@ fn load(func: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValu
24202420 offset + operand.offset(),
24212421 ty.abiAlignment(func.target),
24222422 });
2423 try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
2423 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
24242424 return WValue{ .stack = {} };
24252425 }
24262426
......@@ -4477,7 +4477,7 @@ fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
44774477 operand.offset(),
44784478 elem_ty.abiAlignment(func.target),
44794479 });
4480 try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
4480 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
44814481 try func.addLabel(.local_set, result.local.value);
44824482 return func.finishAir(inst, result, &.{ty_op.operand});
44834483 },
......@@ -4493,7 +4493,7 @@ fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
44934493 try func.emitWValue(operand);
44944494 const extra_index = @intCast(u32, func.mir_extra.items.len);
44954495 try func.mir_extra.append(func.gpa, opcode);
4496 try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
4496 try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
44974497 try func.addLabel(.local_set, result.local.value);
44984498 return func.finishAir(inst, result, &.{ty_op.operand});
44994499 },
src/arch/wasm/Emit.zig+12-6
......@@ -239,8 +239,9 @@ pub fn emitMir(emit: *Emit) InnerError!void {
239239 .i64_clz => try emit.emitTag(tag),
240240 .i64_ctz => try emit.emitTag(tag),
241241
242 .extended => try emit.emitExtended(inst),
243 .simd => try emit.emitSimd(inst),
242 .misc_prefix => try emit.emitExtended(inst),
243 .simd_prefix => try emit.emitSimd(inst),
244 .atomics_prefix => try emit.emitAtomic(inst),
244245 }
245246 }
246247}
......@@ -433,9 +434,9 @@ fn emitExtended(emit: *Emit, inst: Mir.Inst.Index) !void {
433434 const extra_index = emit.mir.instructions.items(.data)[inst].payload;
434435 const opcode = emit.mir.extra[extra_index];
435436 const writer = emit.code.writer();
436 try emit.code.append(0xFC);
437 try emit.code.append(std.wasm.opcode(.misc_prefix));
437438 try leb128.writeULEB128(writer, opcode);
438 switch (@intToEnum(std.wasm.PrefixedOpcode, opcode)) {
439 switch (@intToEnum(std.wasm.MiscOpcode, opcode)) {
439440 // bulk-memory opcodes
440441 .data_drop => {
441442 const segment = emit.mir.extra[extra_index + 1];
......@@ -472,7 +473,7 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {
472473 const extra_index = emit.mir.instructions.items(.data)[inst].payload;
473474 const opcode = emit.mir.extra[extra_index];
474475 const writer = emit.code.writer();
475 try emit.code.append(0xFD);
476 try emit.code.append(std.wasm.opcode(.simd_prefix));
476477 try leb128.writeULEB128(writer, opcode);
477478 switch (@intToEnum(std.wasm.SimdOpcode, opcode)) {
478479 .v128_store,
......@@ -496,10 +497,15 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {
496497 .f32x4_splat,
497498 .f64x2_splat,
498499 => {}, // opcode already written
499 else => |tag| return emit.fail("TODO: Implement simd instruction: {s}\n", .{@tagName(tag)}),
500 else => |tag| return emit.fail("TODO: Implement simd instruction: {s}", .{@tagName(tag)}),
500501 }
501502}
502503
504fn emitAtomic(emit: *Emit, inst: Mir.Inst.Index) !void {
505 _ = inst;
506 return emit.fail("TODO: Implement atomics instructions", .{});
507}
508
503509fn emitMemFill(emit: *Emit) !void {
504510 try emit.code.append(0xFC);
505511 try emit.code.append(0x0B);
src/arch/wasm/Mir.zig+15-8
......@@ -87,6 +87,13 @@ pub const Inst = struct {
8787 ///
8888 /// Uses `label`
8989 call_indirect = 0x11,
90 /// Contains a symbol to a function pointer
91 /// uses `label`
92 ///
93 /// Note: This uses `0x16` as value which is reserved by the WebAssembly
94 /// specification but unused, meaning we must update this if the specification were to
95 /// use this value.
96 function_index = 0x16,
9097 /// Pops three values from the stack and pushes
9198 /// the first or second value dependent on the third value.
9299 /// Uses `tag`
......@@ -510,24 +517,24 @@ pub const Inst = struct {
510517 i64_extend16_s = 0xC3,
511518 /// Uses `tag`
512519 i64_extend32_s = 0xC4,
513 /// The instruction consists of an extension opcode.
520 /// The instruction consists of a prefixed opcode.
514521 /// The prefixed opcode can be found at payload's index.
515522 ///
516523 /// The `data` field depends on the extension instruction and
517524 /// may contain additional data.
518 extended = 0xFC,
525 misc_prefix = 0xFC,
519526 /// The instruction consists of a simd opcode.
520527 /// The actual simd-opcode is found at payload's index.
521528 ///
522529 /// The `data` field depends on the simd instruction and
523530 /// may contain additional data.
524 simd = 0xFD,
525 /// Contains a symbol to a function pointer
526 /// uses `label`
531 simd_prefix = 0xFD,
532 /// The instruction consists of an atomics opcode.
533 /// The actual atomics-opcode is found at payload's index.
527534 ///
528 /// Note: This uses `0xFE` as value as it is unused and not reserved
529 /// by the wasm specification, making it safe to use.
530 function_index = 0xFE,
535 /// The `data` field depends on the atomics instruction and
536 /// may contain additional data.
537 atomics_prefix = 0xFE,
531538 /// Contains a symbol to a memory address
532539 /// Uses `label`
533540 ///
src/link/Wasm.zig+498-18
......@@ -111,7 +111,11 @@ functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.
111111/// Output global section
112112wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
113113/// Memory section
114memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
114memories: std.wasm.Memory = .{ .limits = .{
115 .min = 0,
116 .max = undefined,
117 .flags = 0,
118} },
115119/// Output table section
116120tables: std.ArrayListUnmanaged(std.wasm.Table) = .{},
117121/// Output export section
......@@ -135,6 +139,8 @@ archives: std.ArrayListUnmanaged(Archive) = .{},
135139
136140/// A map of global names (read: offset into string table) to their symbol location
137141globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .{},
142/// The list of GOT symbols and their location
143got_symbols: std.ArrayListUnmanaged(SymbolLoc) = .{},
138144/// Maps discarded symbols and their positions to the location of the symbol
139145/// it was resolved to
140146discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{},
......@@ -176,6 +182,24 @@ pub const Segment = struct {
176182 alignment: u32,
177183 size: u32,
178184 offset: u32,
185 flags: u32,
186
187 pub const Flag = enum(u32) {
188 WASM_DATA_SEGMENT_IS_PASSIVE = 0x01,
189 WASM_DATA_SEGMENT_HAS_MEMINDEX = 0x02,
190 };
191
192 pub fn isPassive(segment: Segment) bool {
193 return segment.flags & @enumToInt(Flag.WASM_DATA_SEGMENT_IS_PASSIVE) != 0;
194 }
195
196 /// For a given segment, determines if it needs passive initialization
197 fn needsPassiveInitialization(segment: Segment, import_mem: bool, name: []const u8) bool {
198 if (import_mem and !std.mem.eql(u8, name, ".bss")) {
199 return true;
200 }
201 return segment.isPassive();
202 }
179203};
180204
181205pub const Export = struct {
......@@ -396,7 +420,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
396420 const loc = try wasm_bin.createSyntheticSymbol("__indirect_function_table", .table);
397421 const symbol = loc.getSymbol(wasm_bin);
398422 const table: std.wasm.Table = .{
399 .limits = .{ .min = 0, .max = null }, // will be overwritten during `mapFunctionTable`
423 .limits = .{ .flags = 0, .min = 0, .max = undefined }, // will be overwritten during `mapFunctionTable`
400424 .reftype = .funcref,
401425 };
402426 if (options.output_mode == .Obj or options.import_table) {
......@@ -429,6 +453,30 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
429453 // at the end during `initializeCallCtorsFunction`.
430454 }
431455
456 // shared-memory symbols for TLS support
457 if (wasm_bin.base.options.shared_memory) {
458 {
459 const loc = try wasm_bin.createSyntheticSymbol("__tls_base", .global);
460 const symbol = loc.getSymbol(wasm_bin);
461 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
462 }
463 {
464 const loc = try wasm_bin.createSyntheticSymbol("__tls_size", .global);
465 const symbol = loc.getSymbol(wasm_bin);
466 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
467 }
468 {
469 const loc = try wasm_bin.createSyntheticSymbol("__tls_align", .global);
470 const symbol = loc.getSymbol(wasm_bin);
471 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
472 }
473 {
474 const loc = try wasm_bin.createSyntheticSymbol("__wasm_tls_init", .function);
475 const symbol = loc.getSymbol(wasm_bin);
476 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
477 }
478 }
479
432480 // if (!options.strip and options.module != null) {
433481 // wasm_bin.dwarf = Dwarf.init(allocator, &wasm_bin.base, options.target);
434482 // try wasm_bin.initDebugSections();
......@@ -597,6 +645,15 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
597645 return true;
598646}
599647
648fn requiresTLSReloc(wasm: *const Wasm) bool {
649 for (wasm.got_symbols.items) |loc| {
650 if (loc.getSymbol(wasm).isTLS()) {
651 return true;
652 }
653 }
654 return false;
655}
656
600657fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {
601658 const object: Object = wasm.objects.items[object_index];
602659 log.debug("Resolving symbols in object: '{s}'", .{object.name});
......@@ -775,6 +832,220 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
775832 }
776833}
777834
835fn setupInitMemoryFunction(wasm: *Wasm) !void {
836 // Passive segments are used to avoid memory being reinitialized on each
837 // thread's instantiation. These passive segments are initialized and
838 // dropped in __wasm_init_memory, which is registered as the start function
839 // We also initialize bss segments (using memory.fill) as part of this
840 // function.
841 if (!wasm.hasPassiveInitializationSegments()) {
842 return;
843 }
844
845 const flag_address: u32 = if (wasm.base.options.shared_memory) address: {
846 // when we have passive initialization segments and shared memory
847 // `setupMemory` will create this symbol and set its virtual address.
848 const loc = wasm.findGlobalSymbol("__wasm_init_memory_flag").?;
849 break :address loc.getSymbol(wasm).virtual_address;
850 } else 0;
851
852 var function_body = std.ArrayList(u8).init(wasm.base.allocator);
853 defer function_body.deinit();
854 const writer = function_body.writer();
855
856 // we have 0 locals
857 try leb.writeULEB128(writer, @as(u32, 0));
858
859 if (wasm.base.options.shared_memory) {
860 // destination blocks
861 // based on values we jump to corresponding label
862 try writer.writeByte(std.wasm.opcode(.block)); // $drop
863 try writer.writeByte(std.wasm.block_empty); // block type
864
865 try writer.writeByte(std.wasm.opcode(.block)); // $wait
866 try writer.writeByte(std.wasm.block_empty); // block type
867
868 try writer.writeByte(std.wasm.opcode(.block)); // $init
869 try writer.writeByte(std.wasm.block_empty); // block type
870
871 // atomically check
872 try writer.writeByte(std.wasm.opcode(.i32_const));
873 try leb.writeULEB128(writer, flag_address);
874 try writer.writeByte(std.wasm.opcode(.i32_const));
875 try leb.writeULEB128(writer, @as(u32, 0));
876 try writer.writeByte(std.wasm.opcode(.i32_const));
877 try leb.writeULEB128(writer, @as(u32, 1));
878 try writer.writeByte(std.wasm.opcode(.atomics_prefix));
879 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_rmw_cmpxchg));
880 try leb.writeULEB128(writer, @as(u32, 2)); // alignment
881 try leb.writeULEB128(writer, @as(u32, 0)); // offset
882
883 // based on the value from the atomic check, jump to the label.
884 try writer.writeByte(std.wasm.opcode(.br_table));
885 try leb.writeULEB128(writer, @as(u32, 2)); // length of the table (we have 3 blocks but because of the mandatory default the length is 2).
886 try leb.writeULEB128(writer, @as(u32, 0)); // $init
887 try leb.writeULEB128(writer, @as(u32, 1)); // $wait
888 try leb.writeULEB128(writer, @as(u32, 2)); // $drop
889 try writer.writeByte(std.wasm.opcode(.end));
890 }
891
892 var it = wasm.data_segments.iterator();
893 var segment_index: u32 = 0;
894 while (it.next()) |entry| : (segment_index += 1) {
895 const segment: Segment = wasm.segments.items[entry.value_ptr.*];
896 if (segment.needsPassiveInitialization(wasm.base.options.import_memory, entry.key_ptr.*)) {
897 // For passive BSS segments we can simple issue a memory.fill(0).
898 // For non-BSS segments we do a memory.init. Both these
899 // instructions take as their first argument the destination
900 // address.
901 try writer.writeByte(std.wasm.opcode(.i32_const));
902 try leb.writeULEB128(writer, segment.offset);
903
904 if (wasm.base.options.shared_memory and std.mem.eql(u8, entry.key_ptr.*, ".tdata")) {
905 // When we initialize the TLS segment we also set the `__tls_base`
906 // global. This allows the runtime to use this static copy of the
907 // TLS data for the first/main thread.
908 try writer.writeByte(std.wasm.opcode(.i32_const));
909 try leb.writeULEB128(writer, segment.offset);
910 try writer.writeByte(std.wasm.opcode(.global_set));
911 const loc = wasm.findGlobalSymbol("__tls_base").?;
912 try leb.writeULEB128(writer, loc.getSymbol(wasm).index);
913 }
914
915 try writer.writeByte(std.wasm.opcode(.i32_const));
916 try leb.writeULEB128(writer, @as(u32, 0));
917 try writer.writeByte(std.wasm.opcode(.i32_const));
918 try leb.writeULEB128(writer, segment.size);
919 try writer.writeByte(std.wasm.opcode(.misc_prefix));
920 if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) {
921 // fill bss segment with zeroes
922 try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_fill));
923 } else {
924 // initialize the segment
925 try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_init));
926 try leb.writeULEB128(writer, segment_index);
927 }
928 try writer.writeByte(0); // memory index immediate
929 }
930 }
931
932 if (wasm.base.options.shared_memory) {
933 // we set the init memory flag to value '2'
934 try writer.writeByte(std.wasm.opcode(.i32_const));
935 try leb.writeULEB128(writer, flag_address);
936 try writer.writeByte(std.wasm.opcode(.i32_const));
937 try leb.writeULEB128(writer, @as(u32, 2));
938 try writer.writeByte(std.wasm.opcode(.atomics_prefix));
939 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.i32_atomic_store));
940 try leb.writeULEB128(writer, @as(u32, 2)); // alignment
941 try leb.writeULEB128(writer, @as(u32, 0)); // offset
942
943 // notify any waiters for segment initialization completion
944 try writer.writeByte(std.wasm.opcode(.i32_const));
945 try leb.writeULEB128(writer, flag_address);
946 try writer.writeByte(std.wasm.opcode(.i32_const));
947 try leb.writeILEB128(writer, @as(i32, -1)); // number of waiters
948 try writer.writeByte(std.wasm.opcode(.atomics_prefix));
949 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.memory_atomic_notify));
950 try leb.writeULEB128(writer, @as(u32, 2)); // alignment
951 try leb.writeULEB128(writer, @as(u32, 0)); // offset
952 try writer.writeByte(std.wasm.opcode(.drop));
953
954 // branch and drop segments
955 try writer.writeByte(std.wasm.opcode(.br));
956 try leb.writeULEB128(writer, @as(u32, 1));
957
958 // wait for thread to initialize memory segments
959 try writer.writeByte(std.wasm.opcode(.end)); // end $wait
960 try writer.writeByte(std.wasm.opcode(.i32_const));
961 try leb.writeULEB128(writer, flag_address);
962 try writer.writeByte(std.wasm.opcode(.i32_const));
963 try leb.writeULEB128(writer, @as(u32, 1)); // expected flag value
964 try writer.writeByte(std.wasm.opcode(.i32_const));
965 try leb.writeILEB128(writer, @as(i32, -1)); // timeout
966 try writer.writeByte(std.wasm.opcode(.atomics_prefix));
967 try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.memory_atomic_wait32));
968 try leb.writeULEB128(writer, @as(u32, 2)); // alignment
969 try leb.writeULEB128(writer, @as(u32, 0)); // offset
970 try writer.writeByte(std.wasm.opcode(.drop));
971
972 try writer.writeByte(std.wasm.opcode(.end)); // end $drop
973 }
974
975 it.reset();
976 segment_index = 0;
977 while (it.next()) |entry| : (segment_index += 1) {
978 const name = entry.key_ptr.*;
979 const segment: Segment = wasm.segments.items[entry.value_ptr.*];
980 if (segment.needsPassiveInitialization(wasm.base.options.import_memory, name) and
981 !std.mem.eql(u8, name, ".bss"))
982 {
983 // The TLS region should not be dropped since its is needed
984 // during the initialization of each thread (__wasm_init_tls).
985 if (wasm.base.options.shared_memory and std.mem.eql(u8, name, ".tdata")) {
986 continue;
987 }
988
989 try writer.writeByte(std.wasm.opcode(.misc_prefix));
990 try leb.writeULEB128(writer, std.wasm.miscOpcode(.data_drop));
991 try leb.writeULEB128(writer, segment_index);
992 }
993 }
994
995 // End of the function body
996 try writer.writeByte(std.wasm.opcode(.end));
997
998 try wasm.createSyntheticFunction(
999 "__wasm_init_memory",
1000 std.wasm.Type{ .params = &.{}, .returns = &.{} },
1001 &function_body,
1002 );
1003}
1004
1005/// Constructs a synthetic function that performs runtime relocations for
1006/// TLS symbols. This function is called by `__wasm_init_tls`.
1007fn setupTLSRelocationsFunction(wasm: *Wasm) !void {
1008 // When we have TLS GOT entries and shared memory is enabled,
1009 // we must perform runtime relocations or else we don't create the function.
1010 if (!wasm.base.options.shared_memory or !wasm.requiresTLSReloc()) {
1011 return;
1012 }
1013
1014 // const loc = try wasm.createSyntheticSymbol("__wasm_apply_global_tls_relocs");
1015 var function_body = std.ArrayList(u8).init(wasm.base.allocator);
1016 defer function_body.deinit();
1017 const writer = function_body.writer();
1018
1019 // locals (we have none)
1020 try writer.writeByte(0);
1021 for (wasm.got_symbols.items, 0..) |got_loc, got_index| {
1022 const sym: *Symbol = got_loc.getSymbol(wasm);
1023 if (!sym.isTLS()) continue; // only relocate TLS symbols
1024 if (sym.tag == .data and sym.isDefined()) {
1025 // get __tls_base
1026 try writer.writeByte(std.wasm.opcode(.global_get));
1027 try leb.writeULEB128(writer, wasm.findGlobalSymbol("__tls_base").?.getSymbol(wasm).index);
1028
1029 // add the virtual address of the symbol
1030 try writer.writeByte(std.wasm.opcode(.i32_const));
1031 try leb.writeULEB128(writer, sym.virtual_address);
1032 } else if (sym.tag == .function) {
1033 @panic("TODO: relocate GOT entry of function");
1034 } else continue;
1035
1036 try writer.writeByte(std.wasm.opcode(.i32_add));
1037 try writer.writeByte(std.wasm.opcode(.global_set));
1038 try leb.writeULEB128(writer, wasm.imported_globals_count + @intCast(u32, wasm.wasm_globals.items.len + got_index));
1039 }
1040 try writer.writeByte(std.wasm.opcode(.end));
1041
1042 try wasm.createSyntheticFunction(
1043 "__wasm_apply_global_tls_relocs",
1044 std.wasm.Type{ .params = &.{}, .returns = &.{} },
1045 &function_body,
1046 );
1047}
1048
7781049fn validateFeatures(
7791050 wasm: *const Wasm,
7801051 to_emit: *[@typeInfo(types.Feature.Tag).Enum.fields.len]bool,
......@@ -791,6 +1062,8 @@ fn validateFeatures(
7911062
7921063 // when false, we fail linking. We only verify this after a loop to catch all invalid features.
7931064 var valid_feature_set = true;
1065 // will be set to true when there's any TLS segment found in any of the object files
1066 var has_tls = false;
7941067
7951068 // When the user has given an explicit list of features to enable,
7961069 // we extract them and insert each into the 'allowed' list.
......@@ -821,6 +1094,12 @@ fn validateFeatures(
8211094 },
8221095 }
8231096 }
1097
1098 for (object.segment_info) |segment| {
1099 if (segment.isTLS()) {
1100 has_tls = true;
1101 }
1102 }
8241103 }
8251104
8261105 // when we infer the features, we allow each feature found in the 'used' set
......@@ -832,7 +1111,7 @@ fn validateFeatures(
8321111 allowed[used_index] = is_enabled;
8331112 emit_features_count.* += @boolToInt(is_enabled);
8341113 } else if (is_enabled and !allowed[used_index]) {
835 log.err("feature '{s}' not allowed, but used by linked object", .{(@intToEnum(types.Feature.Tag, used_index)).toString()});
1114 log.err("feature '{}' not allowed, but used by linked object", .{@intToEnum(types.Feature.Tag, used_index)});
8361115 log.err(" defined in '{s}'", .{wasm.objects.items[used_set >> 1].name});
8371116 valid_feature_set = false;
8381117 }
......@@ -842,6 +1121,30 @@ fn validateFeatures(
8421121 return error.InvalidFeatureSet;
8431122 }
8441123
1124 if (wasm.base.options.shared_memory) {
1125 const disallowed_feature = disallowed[@enumToInt(types.Feature.Tag.shared_mem)];
1126 if (@truncate(u1, disallowed_feature) != 0) {
1127 log.err(
1128 "shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled",
1129 .{wasm.objects.items[disallowed_feature >> 1].name},
1130 );
1131 valid_feature_set = false;
1132 }
1133
1134 for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| {
1135 if (!allowed[@enumToInt(feature)]) {
1136 log.err("feature '{}' is not used but is required for shared-memory", .{feature});
1137 }
1138 }
1139 }
1140
1141 if (has_tls) {
1142 for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| {
1143 if (!allowed[@enumToInt(feature)]) {
1144 log.err("feature '{}' is not used but is required for thread-local storage", .{feature});
1145 }
1146 }
1147 }
8451148 // For each linked object, validate the required and disallowed features
8461149 for (wasm.objects.items) |object| {
8471150 var object_used_features = [_]bool{false} ** known_features_count;
......@@ -850,7 +1153,7 @@ fn validateFeatures(
8501153 // from here a feature is always used
8511154 const disallowed_feature = disallowed[@enumToInt(feature.tag)];
8521155 if (@truncate(u1, disallowed_feature) != 0) {
853 log.err("feature '{s}' is disallowed, but used by linked object", .{feature.tag.toString()});
1156 log.err("feature '{}' is disallowed, but used by linked object", .{feature.tag});
8541157 log.err(" disallowed by '{s}'", .{wasm.objects.items[disallowed_feature >> 1].name});
8551158 log.err(" used in '{s}'", .{object.name});
8561159 valid_feature_set = false;
......@@ -863,7 +1166,7 @@ fn validateFeatures(
8631166 for (required, 0..) |required_feature, feature_index| {
8641167 const is_required = @truncate(u1, required_feature) != 0;
8651168 if (is_required and !object_used_features[feature_index]) {
866 log.err("feature '{s}' is required but not used in linked object", .{(@intToEnum(types.Feature.Tag, feature_index)).toString()});
1169 log.err("feature '{}' is required but not used in linked object", .{@intToEnum(types.Feature.Tag, feature_index)});
8671170 log.err(" required by '{s}'", .{wasm.objects.items[required_feature >> 1].name});
8681171 log.err(" missing in '{s}'", .{object.name});
8691172 valid_feature_set = false;
......@@ -894,6 +1197,13 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
8941197 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
8951198 _ = wasm.resolved_symbols.swapRemove(loc);
8961199 }
1200
1201 if (!wasm.base.options.shared_memory) {
1202 if (wasm.undefs.fetchSwapRemove("__tls_base")) |kv| {
1203 const loc = try wasm.createSyntheticSymbol("__tls_base", .global);
1204 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
1205 }
1206 }
8971207}
8981208
8991209// Tries to find a global symbol by its name. Returns null when not found,
......@@ -1517,7 +1827,7 @@ fn mapFunctionTable(wasm: *Wasm) void {
15171827 const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
15181828 const symbol = sym_loc.getSymbol(wasm);
15191829 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
1520 table.limits = .{ .min = index, .max = index };
1830 table.limits = .{ .min = index, .max = index, .flags = 0x1 };
15211831 }
15221832}
15231833
......@@ -1630,6 +1940,7 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
16301940 .alignment = atom.alignment,
16311941 .size = atom.size,
16321942 .offset = 0,
1943 .flags = 0,
16331944 });
16341945 }
16351946
......@@ -1668,10 +1979,15 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
16681979 break :result index;
16691980 } else {
16701981 const index = @intCast(u32, wasm.segments.items.len);
1982 var flags: u32 = 0;
1983 if (wasm.base.options.shared_memory) {
1984 flags |= @enumToInt(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
1985 }
16711986 try wasm.segments.append(wasm.base.allocator, .{
16721987 .alignment = atom.alignment,
16731988 .size = 0,
16741989 .offset = 0,
1990 .flags = flags,
16751991 });
16761992 gop.value_ptr.* = index;
16771993
......@@ -1907,10 +2223,23 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
19072223 try writer.writeByte(std.wasm.opcode(.end));
19082224 }
19092225
1910 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
2226 try wasm.createSyntheticFunction(
2227 "__wasm_call_ctors",
2228 std.wasm.Type{ .params = &.{}, .returns = &.{} },
2229 &function_body,
2230 );
2231}
2232
2233fn createSyntheticFunction(
2234 wasm: *Wasm,
2235 symbol_name: []const u8,
2236 func_ty: std.wasm.Type,
2237 function_body: *std.ArrayList(u8),
2238) !void {
2239 const loc = wasm.findGlobalSymbol(symbol_name) orelse
2240 try wasm.createSyntheticSymbol(symbol_name, .function);
19112241 const symbol = loc.getSymbol(wasm);
1912 // create type (() -> nil) as we do not have any parameters or return value.
1913 const ty_index = try wasm.putOrGetFuncType(.{ .params = &[_]std.wasm.Valtype{}, .returns = &[_]std.wasm.Valtype{} });
2242 const ty_index = try wasm.putOrGetFuncType(func_ty);
19142243 // create function with above type
19152244 const func_index = wasm.imported_functions_count + @intCast(u32, wasm.functions.count());
19162245 try wasm.functions.putNoClobber(
......@@ -1942,6 +2271,68 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
19422271 atom.offset = prev_atom.offset + prev_atom.size;
19432272}
19442273
2274fn initializeTLSFunction(wasm: *Wasm) !void {
2275 if (!wasm.base.options.shared_memory) return;
2276
2277 var function_body = std.ArrayList(u8).init(wasm.base.allocator);
2278 defer function_body.deinit();
2279 const writer = function_body.writer();
2280
2281 // locals
2282 try writer.writeByte(0);
2283
2284 // If there's a TLS segment, initialize it during runtime using the bulk-memory feature
2285 if (wasm.data_segments.getIndex(".tdata")) |data_index| {
2286 const segment_index = wasm.data_segments.entries.items(.value)[data_index];
2287 const segment = wasm.segments.items[segment_index];
2288
2289 const param_local: u32 = 0;
2290
2291 try writer.writeByte(std.wasm.opcode(.local_get));
2292 try leb.writeULEB128(writer, param_local);
2293
2294 const tls_base_loc = wasm.findGlobalSymbol("__tls_base").?;
2295 try writer.writeByte(std.wasm.opcode(.global_get));
2296 try leb.writeULEB128(writer, tls_base_loc.getSymbol(wasm).index);
2297
2298 // load stack values for the bulk-memory operation
2299 {
2300 try writer.writeByte(std.wasm.opcode(.local_get));
2301 try leb.writeULEB128(writer, param_local);
2302
2303 try writer.writeByte(std.wasm.opcode(.i32_const));
2304 try leb.writeULEB128(writer, @as(u32, 0)); //segment offset
2305
2306 try writer.writeByte(std.wasm.opcode(.i32_const));
2307 try leb.writeULEB128(writer, @as(u32, segment.size)); //segment offset
2308 }
2309
2310 // perform the bulk-memory operation to initialize the data segment
2311 try writer.writeByte(std.wasm.opcode(.misc_prefix));
2312 try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_init));
2313 // segment immediate
2314 try leb.writeULEB128(writer, @intCast(u32, data_index));
2315 // memory index immediate (always 0)
2316 try leb.writeULEB128(writer, @as(u32, 0));
2317 }
2318
2319 // If we have to perform any TLS relocations, call the corresponding function
2320 // which performs all runtime TLS relocations. This is a synthetic function,
2321 // generated by the linker.
2322 if (wasm.findGlobalSymbol("__wasm_apply_global_tls_relocs")) |loc| {
2323 try writer.writeByte(std.wasm.opcode(.call));
2324 try leb.writeULEB128(writer, loc.getSymbol(wasm).index);
2325 }
2326
2327 try writer.writeByte(std.wasm.opcode(.end));
2328
2329 try wasm.createSyntheticFunction(
2330 "__wasm_init_tls",
2331 std.wasm.Type{ .params = &.{.i32}, .returns = &.{} },
2332 &function_body,
2333 );
2334}
2335
19452336fn setupImports(wasm: *Wasm) !void {
19462337 log.debug("Merging imports", .{});
19472338 var discarded_it = wasm.discarded.keyIterator();
......@@ -2224,11 +2615,50 @@ fn setupMemory(wasm: *Wasm) !void {
22242615 while (data_seg_it.next()) |entry| {
22252616 const segment = &wasm.segments.items[entry.value_ptr.*];
22262617 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);
2618
2619 // set TLS-related symbols
2620 if (mem.eql(u8, entry.key_ptr.*, ".tdata")) {
2621 if (wasm.findGlobalSymbol("__tls_size")) |loc| {
2622 const sym = loc.getSymbol(wasm);
2623 sym.index = @intCast(u32, wasm.wasm_globals.items.len) + wasm.imported_globals_count;
2624 try wasm.wasm_globals.append(wasm.base.allocator, .{
2625 .global_type = .{ .valtype = .i32, .mutable = false },
2626 .init = .{ .i32_const = @intCast(i32, segment.size) },
2627 });
2628 }
2629 if (wasm.findGlobalSymbol("__tls_align")) |loc| {
2630 const sym = loc.getSymbol(wasm);
2631 sym.index = @intCast(u32, wasm.wasm_globals.items.len) + wasm.imported_globals_count;
2632 try wasm.wasm_globals.append(wasm.base.allocator, .{
2633 .global_type = .{ .valtype = .i32, .mutable = false },
2634 .init = .{ .i32_const = @intCast(i32, segment.alignment) },
2635 });
2636 }
2637 if (wasm.findGlobalSymbol("__tls_base")) |loc| {
2638 const sym = loc.getSymbol(wasm);
2639 sym.index = @intCast(u32, wasm.wasm_globals.items.len) + wasm.imported_globals_count;
2640 try wasm.wasm_globals.append(wasm.base.allocator, .{
2641 .global_type = .{ .valtype = .i32, .mutable = wasm.base.options.shared_memory },
2642 .init = .{ .i32_const = if (wasm.base.options.shared_memory) @as(u32, 0) else @intCast(i32, memory_ptr) },
2643 });
2644 }
2645 }
2646
22272647 memory_ptr += segment.size;
22282648 segment.offset = offset;
22292649 offset += segment.size;
22302650 }
22312651
2652 // create the memory init flag which is used by the init memory function
2653 if (wasm.base.options.shared_memory and wasm.hasPassiveInitializationSegments()) {
2654 // align to pointer size
2655 memory_ptr = mem.alignForwardGeneric(u64, memory_ptr, 4);
2656 const loc = try wasm.createSyntheticSymbol("__wasm_init_memory_flag", .data);
2657 const sym = loc.getSymbol(wasm);
2658 sym.virtual_address = @intCast(u32, memory_ptr);
2659 memory_ptr += 4;
2660 }
2661
22322662 if (!place_stack_first and !is_obj) {
22332663 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, stack_alignment);
22342664 memory_ptr += stack_size;
......@@ -2286,6 +2716,10 @@ fn setupMemory(wasm: *Wasm) !void {
22862716 return error.MemoryTooBig;
22872717 }
22882718 wasm.memories.limits.max = @intCast(u32, max_memory / page_size);
2719 wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_HAS_MAX);
2720 if (wasm.base.options.shared_memory) {
2721 wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_IS_SHARED);
2722 }
22892723 log.debug("Maximum memory pages: {?d}", .{wasm.memories.limits.max});
22902724 }
22912725}
......@@ -2305,7 +2739,16 @@ pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32
23052739 const result = try wasm.data_segments.getOrPut(wasm.base.allocator, segment_info.outputName(merge_segment));
23062740 if (!result.found_existing) {
23072741 result.value_ptr.* = index;
2308 try wasm.appendDummySegment();
2742 var flags: u32 = 0;
2743 if (wasm.base.options.shared_memory) {
2744 flags |= @enumToInt(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
2745 }
2746 try wasm.segments.append(wasm.base.allocator, .{
2747 .alignment = 1,
2748 .size = 0,
2749 .offset = 0,
2750 .flags = flags,
2751 });
23092752 return index;
23102753 } else return result.value_ptr.*;
23112754 },
......@@ -2379,6 +2822,7 @@ fn appendDummySegment(wasm: *Wasm) !void {
23792822 .alignment = 1,
23802823 .size = 0,
23812824 .offset = 0,
2825 .flags = 0,
23822826 });
23832827}
23842828
......@@ -2746,6 +3190,9 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
27463190 try wasm.mergeSections();
27473191 try wasm.mergeTypes();
27483192 try wasm.initializeCallCtorsFunction();
3193 try wasm.setupInitMemoryFunction();
3194 try wasm.setupTLSRelocationsFunction();
3195 try wasm.initializeTLSFunction();
27493196 try wasm.setupExports();
27503197 try wasm.writeToFile(enabled_features, emit_features_count, arena);
27513198
......@@ -2874,6 +3321,9 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
28743321 try wasm.mergeSections();
28753322 try wasm.mergeTypes();
28763323 try wasm.initializeCallCtorsFunction();
3324 try wasm.setupInitMemoryFunction();
3325 try wasm.setupTLSRelocationsFunction();
3326 try wasm.initializeTLSFunction();
28773327 try wasm.setupExports();
28783328 try wasm.writeToFile(enabled_features, emit_features_count, arena);
28793329}
......@@ -3096,6 +3546,19 @@ fn writeToFile(
30963546 section_count += 1;
30973547 }
30983548
3549 // When the shared-memory option is enabled, we *must* emit the 'data count' section.
3550 const data_segments_count = wasm.data_segments.count() - @boolToInt(wasm.data_segments.contains(".bss") and import_memory);
3551 if (data_segments_count != 0 and wasm.base.options.shared_memory) {
3552 const header_offset = try reserveVecSectionHeader(&binary_bytes);
3553 try writeVecSectionHeader(
3554 binary_bytes.items,
3555 header_offset,
3556 .data_count,
3557 @intCast(u32, binary_bytes.items.len - header_offset - header_size),
3558 @intCast(u32, data_segments_count),
3559 );
3560 }
3561
30993562 // Code section
31003563 var code_section_size: u32 = 0;
31013564 if (wasm.code_section_index) |code_index| {
......@@ -3146,7 +3609,7 @@ fn writeToFile(
31463609 }
31473610
31483611 // Data section
3149 if (wasm.data_segments.count() != 0) {
3612 if (data_segments_count != 0) {
31503613 const header_offset = try reserveVecSectionHeader(&binary_bytes);
31513614
31523615 var it = wasm.data_segments.iterator();
......@@ -3161,10 +3624,15 @@ fn writeToFile(
31613624 segment_count += 1;
31623625 var atom_index = wasm.atoms.get(segment_index).?;
31633626
3164 // flag and index to memory section (currently, there can only be 1 memory section in wasm)
3165 try leb.writeULEB128(binary_writer, @as(u32, 0));
3627 try leb.writeULEB128(binary_writer, segment.flags);
3628 if (segment.flags & @enumToInt(Wasm.Segment.Flag.WASM_DATA_SEGMENT_HAS_MEMINDEX) != 0) {
3629 try leb.writeULEB128(binary_writer, @as(u32, 0)); // memory is always index 0 as we only have 1 memory entry
3630 }
3631 // when a segment is passive, it's initialized during runtime.
3632 if (!segment.isPassive()) {
3633 try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) });
3634 }
31663635 // offset into data section
3167 try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) });
31683636 try leb.writeULEB128(binary_writer, segment.size);
31693637
31703638 // fill in the offset table and the data segments
......@@ -3413,7 +3881,8 @@ fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []con
34133881 if (enabled) {
34143882 const feature: types.Feature = .{ .prefix = .used, .tag = @intToEnum(types.Feature.Tag, feature_index) };
34153883 try leb.writeULEB128(writer, @enumToInt(feature.prefix));
3416 const string = feature.tag.toString();
3884 var buf: [100]u8 = undefined;
3885 const string = try std.fmt.bufPrint(&buf, "{}", .{feature.tag});
34173886 try leb.writeULEB128(writer, @intCast(u32, string.len));
34183887 try writer.writeAll(string);
34193888 }
......@@ -3507,10 +3976,10 @@ fn emitNameSubsection(wasm: *Wasm, section_id: std.wasm.NameSubsection, names: a
35073976}
35083977
35093978fn emitLimits(writer: anytype, limits: std.wasm.Limits) !void {
3510 try leb.writeULEB128(writer, @boolToInt(limits.max != null));
3979 try writer.writeByte(limits.flags);
35113980 try leb.writeULEB128(writer, limits.min);
3512 if (limits.max) |max| {
3513 try leb.writeULEB128(writer, max);
3981 if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) {
3982 try leb.writeULEB128(writer, limits.max);
35143983 }
35153984}
35163985
......@@ -4205,6 +4674,17 @@ fn emitDataRelocations(
42054674 try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
42064675}
42074676
4677fn hasPassiveInitializationSegments(wasm: *const Wasm) bool {
4678 var it = wasm.data_segments.iterator();
4679 while (it.next()) |entry| {
4680 const segment: Segment = wasm.segments.items[entry.value_ptr.*];
4681 if (segment.needsPassiveInitialization(wasm.base.options.import_memory, entry.key_ptr.*)) {
4682 return true;
4683 }
4684 }
4685 return false;
4686}
4687
42084688pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {
42094689 var index: u32 = 0;
42104690 while (index < wasm.func_types.items.len) : (index += 1) {
src/link/Wasm/Atom.zig+7
......@@ -126,10 +126,12 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
126126 .R_WASM_TABLE_INDEX_SLEB,
127127 .R_WASM_TABLE_NUMBER_LEB,
128128 .R_WASM_TYPE_INDEX_LEB,
129 .R_WASM_MEMORY_ADDR_TLS_SLEB,
129130 => leb.writeUnsignedFixed(5, atom.code.items[reloc.offset..][0..5], @intCast(u32, value)),
130131 .R_WASM_MEMORY_ADDR_LEB64,
131132 .R_WASM_MEMORY_ADDR_SLEB64,
132133 .R_WASM_TABLE_INDEX_SLEB64,
134 .R_WASM_MEMORY_ADDR_TLS_SLEB64,
133135 => leb.writeUnsignedFixed(10, atom.code.items[reloc.offset..][0..10], value),
134136 }
135137 }
......@@ -190,5 +192,10 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
190192 const rel_value = @intCast(i32, target_atom.offset + offset) + relocation.addend;
191193 return @intCast(u32, rel_value);
192194 },
195 .R_WASM_MEMORY_ADDR_TLS_SLEB,
196 .R_WASM_MEMORY_ADDR_TLS_SLEB64,
197 => {
198 @panic("TODO: Implement TLS relocations");
199 },
193200 }
194201}
src/link/Wasm/Object.zig+39-10
......@@ -601,8 +601,8 @@ fn Parser(comptime ReaderType: type) type {
601601 });
602602
603603 for (relocations) |*relocation| {
604 const rel_type = try leb.readULEB128(u8, reader);
605 const rel_type_enum = @intToEnum(types.Relocation.RelocationType, rel_type);
604 const rel_type = try reader.readByte();
605 const rel_type_enum = std.meta.intToEnum(types.Relocation.RelocationType, rel_type) catch return error.MalformedSection;
606606 relocation.* = .{
607607 .relocation_type = rel_type_enum,
608608 .offset = try leb.readULEB128(u32, reader),
......@@ -674,6 +674,12 @@ fn Parser(comptime ReaderType: type) type {
674674 segment.alignment,
675675 segment.flags,
676676 });
677
678 // support legacy object files that specified being TLS by the name instead of the TLS flag.
679 if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) {
680 // set the flag so we can simply check for the flag in the rest of the linker.
681 segment.flags |= @enumToInt(types.Segment.Flags.WASM_SEG_FLAG_TLS);
682 }
677683 }
678684 parser.object.segment_info = segments;
679685 },
......@@ -846,12 +852,17 @@ fn readEnum(comptime T: type, reader: anytype) !T {
846852}
847853
848854fn readLimits(reader: anytype) !std.wasm.Limits {
849 const flags = try readLeb(u1, reader);
855 const flags = try reader.readByte();
850856 const min = try readLeb(u32, reader);
851 return std.wasm.Limits{
857 var limits: std.wasm.Limits = .{
858 .flags = flags,
852859 .min = min,
853 .max = if (flags == 0) null else try readLeb(u32, reader),
860 .max = undefined,
854861 };
862 if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) {
863 limits.max = try readLeb(u32, reader);
864 }
865 return limits;
855866}
856867
857868fn readInit(reader: anytype) !std.wasm.InitExpression {
......@@ -919,11 +930,29 @@ pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_b
919930 reloc.offset -= relocatable_data.offset;
920931 try atom.relocs.append(gpa, reloc);
921932
922 if (relocation.isTableIndex()) {
923 try wasm_bin.function_table.put(gpa, .{
924 .file = object_index,
925 .index = relocation.index,
926 }, 0);
933 switch (relocation.relocation_type) {
934 .R_WASM_TABLE_INDEX_I32,
935 .R_WASM_TABLE_INDEX_I64,
936 .R_WASM_TABLE_INDEX_SLEB,
937 .R_WASM_TABLE_INDEX_SLEB64,
938 => {
939 try wasm_bin.function_table.put(gpa, .{
940 .file = object_index,
941 .index = relocation.index,
942 }, 0);
943 },
944 .R_WASM_GLOBAL_INDEX_I32,
945 .R_WASM_GLOBAL_INDEX_LEB,
946 => {
947 const sym = object.symtable[relocation.index];
948 if (sym.tag != .global) {
949 try wasm_bin.got_symbols.append(
950 wasm_bin.base.allocator,
951 .{ .file = object_index, .index = relocation.index },
952 );
953 }
954 },
955 else => {},
927956 }
928957 }
929958 }
src/link/Wasm/Symbol.zig+4
......@@ -90,6 +90,10 @@ pub fn requiresImport(symbol: Symbol) bool {
9090 return true;
9191}
9292
93pub fn isTLS(symbol: Symbol) bool {
94 return symbol.flags & @enumToInt(Flag.WASM_SYM_TLS) != 0;
95}
96
9397pub fn hasFlag(symbol: Symbol, flag: Flag) bool {
9498 return symbol.flags & @enumToInt(flag) != 0;
9599}
src/link/Wasm/types.zig+29-24
......@@ -38,6 +38,8 @@ pub const Relocation = struct {
3838 R_WASM_TABLE_INDEX_SLEB64 = 18,
3939 R_WASM_TABLE_INDEX_I64 = 19,
4040 R_WASM_TABLE_NUMBER_LEB = 20,
41 R_WASM_MEMORY_ADDR_TLS_SLEB = 21,
42 R_WASM_MEMORY_ADDR_TLS_SLEB64 = 25,
4143
4244 /// Returns true for relocation types where the `addend` field is present.
4345 pub fn addendIsPresent(self: RelocationType) bool {
......@@ -48,6 +50,8 @@ pub const Relocation = struct {
4850 .R_WASM_MEMORY_ADDR_LEB64,
4951 .R_WASM_MEMORY_ADDR_SLEB64,
5052 .R_WASM_MEMORY_ADDR_I64,
53 .R_WASM_MEMORY_ADDR_TLS_SLEB,
54 .R_WASM_MEMORY_ADDR_TLS_SLEB64,
5155 .R_WASM_FUNCTION_OFFSET_I32,
5256 .R_WASM_SECTION_OFFSET_I32,
5357 => true,
......@@ -67,18 +71,6 @@ pub const Relocation = struct {
6771 };
6872 }
6973
70 /// Returns true when the relocation represents a table index relocatable
71 pub fn isTableIndex(self: Relocation) bool {
72 return switch (self.relocation_type) {
73 .R_WASM_TABLE_INDEX_I32,
74 .R_WASM_TABLE_INDEX_I64,
75 .R_WASM_TABLE_INDEX_SLEB,
76 .R_WASM_TABLE_INDEX_SLEB64,
77 => true,
78 else => false,
79 };
80 }
81
8274 pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
8375 _ = fmt;
8476 _ = options;
......@@ -125,23 +117,34 @@ pub const Segment = struct {
125117 /// Bitfield containing flags for a segment
126118 flags: u32,
127119
120 pub fn isTLS(segment: Segment) bool {
121 return segment.flags & @enumToInt(Flags.WASM_SEG_FLAG_TLS) != 0;
122 }
123
128124 /// Returns the name as how it will be output into the final object
129125 /// file or binary. When `merge_segments` is true, this will return the
130126 /// short name. i.e. ".rodata". When false, it returns the entire name instead.
131 pub fn outputName(self: Segment, merge_segments: bool) []const u8 {
132 if (std.mem.startsWith(u8, self.name, ".synthetic")) return ".synthetic"; // always merge
133 if (!merge_segments) return self.name;
134 if (std.mem.startsWith(u8, self.name, ".rodata.")) {
127 pub fn outputName(segment: Segment, merge_segments: bool) []const u8 {
128 if (segment.isTLS()) {
129 return ".tdata";
130 } else if (!merge_segments) {
131 return segment.name;
132 } else if (std.mem.startsWith(u8, segment.name, ".rodata.")) {
135133 return ".rodata";
136 } else if (std.mem.startsWith(u8, self.name, ".text.")) {
134 } else if (std.mem.startsWith(u8, segment.name, ".text.")) {
137135 return ".text";
138 } else if (std.mem.startsWith(u8, self.name, ".data.")) {
136 } else if (std.mem.startsWith(u8, segment.name, ".data.")) {
139137 return ".data";
140 } else if (std.mem.startsWith(u8, self.name, ".bss.")) {
138 } else if (std.mem.startsWith(u8, segment.name, ".bss.")) {
141139 return ".bss";
142140 }
143 return self.name;
141 return segment.name;
144142 }
143
144 pub const Flags = enum(u32) {
145 WASM_SEG_FLAG_STRINGS = 0x1,
146 WASM_SEG_FLAG_TLS = 0x2,
147 };
145148};
146149
147150pub const InitFunc = struct {
......@@ -205,8 +208,10 @@ pub const Feature = struct {
205208 return @intToEnum(Tag, @enumToInt(feature));
206209 }
207210
208 pub fn toString(tag: Tag) []const u8 {
209 return switch (tag) {
211 pub fn format(tag: Tag, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
212 _ = fmt;
213 _ = opt;
214 try writer.writeAll(switch (tag) {
210215 .atomics => "atomics",
211216 .bulk_memory => "bulk-memory",
212217 .exception_handling => "exception-handling",
......@@ -220,7 +225,7 @@ pub const Feature = struct {
220225 .simd128 => "simd128",
221226 .tail_call => "tail-call",
222227 .shared_mem => "shared-mem",
223 };
228 });
224229 }
225230 };
226231
......@@ -233,7 +238,7 @@ pub const Feature = struct {
233238 pub fn format(feature: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
234239 _ = opt;
235240 _ = fmt;
236 try writer.print("{c} {s}", .{ feature.prefix, feature.tag.toString() });
241 try writer.print("{c} {}", .{ feature.prefix, feature.tag });
237242 }
238243};
239244