diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e998a8d50e..287f880d92 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -894,6 +894,13 @@ fn resolveLazySymbols(wasm: *Wasm) !void { try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc); _ = wasm.resolved_symbols.swapRemove(loc); } + + if (!wasm.base.options.shared_memory) { + if (wasm.undefs.fetchSwapRemove("__tls_base")) |kv| { + const loc = try wasm.createSyntheticSymbol("__tls_base", .global); + try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc); + } + } } // Tries to find a global symbol by its name. Returns null when not found, @@ -2224,6 +2231,18 @@ fn setupMemory(wasm: *Wasm) !void { while (data_seg_it.next()) |entry| { const segment = &wasm.segments.items[entry.value_ptr.*]; memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment); + + // set TLS-related symbols + if (mem.eql(u8, entry.key_ptr.*, ".tdata")) { + if (wasm.findGlobalSymbol("__tls_base")) |loc| { + const sym = loc.getSymbol(wasm); + sym.index = try wasm.globals.append(wasm.base.allocator, wasm.imports.globalCount, .{ + .global_type = .{ .valtype = .i32_const, .mutable = false }, + .init = .{ .i32_const = @intCast(i32, memory_ptr) }, + }); + } + } + memory_ptr += segment.size; segment.offset = offset; offset += segment.size; diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index 0c9d761f05..7d2f5a6696 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -126,10 +126,12 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void { .R_WASM_TABLE_INDEX_SLEB, .R_WASM_TABLE_NUMBER_LEB, .R_WASM_TYPE_INDEX_LEB, + .R_WASM_MEMORY_ADDR_TLS_SLEB, => leb.writeUnsignedFixed(5, atom.code.items[reloc.offset..][0..5], @intCast(u32, value)), .R_WASM_MEMORY_ADDR_LEB64, .R_WASM_MEMORY_ADDR_SLEB64, .R_WASM_TABLE_INDEX_SLEB64, + .R_WASM_MEMORY_ADDR_TLS_SLEB64, => leb.writeUnsignedFixed(10, atom.code.items[reloc.offset..][0..10], value), } } @@ -190,5 +192,10 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa const rel_value = @intCast(i32, target_atom.offset + offset) + relocation.addend; return @intCast(u32, rel_value); }, + .R_WASM_MEMORY_ADDR_TLS_SLEB, + .R_WASM_MEMORY_ADDR_TLS_SLEB64, + => { + @panic("TODO: Implement TLS relocations"); + }, } } diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index 45c9464ec8..4b918064c1 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -674,6 +674,12 @@ fn Parser(comptime ReaderType: type) type { segment.alignment, segment.flags, }); + + // support legacy object files that specified being TLS by the name instead of the TLS flag. + if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) { + // set the flag so we can simply check for the flag in the rest of the linker. + segment.flags |= @enumToInt(types.Segment.Flags.WASM_SEG_FLAG_TLS); + } } parser.object.segment_info = segments; }, diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index 156b507a32..8a1c4c5fdb 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -90,6 +90,10 @@ pub fn requiresImport(symbol: Symbol) bool { return true; } +pub fn isTLS(symbol: Symbol) bool { + return symbol.flags & @enumToInt(Flag.WASM_SYM_TLS) != 0; +} + pub fn hasFlag(symbol: Symbol, flag: Flag) bool { return symbol.flags & @enumToInt(flag) != 0; } diff --git a/src/link/Wasm/types.zig b/src/link/Wasm/types.zig index 964ba04ba0..a65e352c46 100644 --- a/src/link/Wasm/types.zig +++ b/src/link/Wasm/types.zig @@ -38,6 +38,8 @@ pub const Relocation = struct { R_WASM_TABLE_INDEX_SLEB64 = 18, R_WASM_TABLE_INDEX_I64 = 19, R_WASM_TABLE_NUMBER_LEB = 20, + R_WASM_MEMORY_ADDR_TLS_SLEB = 21, + R_WASM_MEMORY_ADDR_TLS_SLEB64 = 25, /// Returns true for relocation types where the `addend` field is present. pub fn addendIsPresent(self: RelocationType) bool { @@ -125,23 +127,34 @@ pub const Segment = struct { /// Bitfield containing flags for a segment flags: u32, + pub fn isTLS(segment: Segment) bool { + return segment.flags & @enumToInt(Flags.WASM_SEG_FLAG_TLS) != 0; + } + /// Returns the name as how it will be output into the final object /// file or binary. When `merge_segments` is true, this will return the /// short name. i.e. ".rodata". When false, it returns the entire name instead. - pub fn outputName(self: Segment, merge_segments: bool) []const u8 { - if (std.mem.startsWith(u8, self.name, ".synthetic")) return ".synthetic"; // always merge - if (!merge_segments) return self.name; - if (std.mem.startsWith(u8, self.name, ".rodata.")) { + pub fn outputName(segment: Segment, merge_segments: bool) []const u8 { + if (segment.isTLS()) { + return ".tdata"; + } else if (!merge_segments) { + return segment.name; + } else if (std.mem.startsWith(u8, segment.name, ".rodata.")) { return ".rodata"; - } else if (std.mem.startsWith(u8, self.name, ".text.")) { + } else if (std.mem.startsWith(u8, segment.name, ".text.")) { return ".text"; - } else if (std.mem.startsWith(u8, self.name, ".data.")) { + } else if (std.mem.startsWith(u8, segment.name, ".data.")) { return ".data"; - } else if (std.mem.startsWith(u8, self.name, ".bss.")) { + } else if (std.mem.startsWith(u8, segment.name, ".bss.")) { return ".bss"; } - return self.name; + return segment.name; } + + pub const Flags = enum(u32) { + WASM_SEG_FLAG_STRINGS = 0x1, + WASM_SEG_FLAG_TLS = 0x2, + }; }; pub const InitFunc = struct {