Merge pull request #14998 from Luukdegram/shared-mem

wasm-linker: Implement shared-memory
This commit is contained in:
Luuk de Gram 2023-03-19 15:43:06 +01:00 committed by GitHub
commit c26cbd561c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 716 additions and 76 deletions

View File

@ -189,7 +189,9 @@ pub const Opcode = enum(u8) {
i64_extend16_s = 0xC3,
i64_extend32_s = 0xC4,
prefixed = 0xFC,
misc_prefix = 0xFC,
simd_prefix = 0xFD,
atomics_prefix = 0xFE,
_,
};
@ -217,7 +219,7 @@ test "Wasm - opcodes" {
/// Opcodes that require a prefix `0xFC`
/// Each opcode represents a varuint32, meaning
/// they are encoded as leb128 in binary.
pub const PrefixedOpcode = enum(u32) {
pub const MiscOpcode = enum(u32) {
i32_trunc_sat_f32_s = 0x00,
i32_trunc_sat_f32_u = 0x01,
i32_trunc_sat_f64_s = 0x02,
@ -239,6 +241,12 @@ pub const PrefixedOpcode = enum(u32) {
_,
};
/// Returns the integer value of an `MiscOpcode`. Used by the Zig compiler
/// to write instructions to the wasm binary file
pub fn miscOpcode(op: MiscOpcode) u32 {
return @enumToInt(op);
}
/// Simd opcodes that require a prefix `0xFD`.
/// Each opcode represents a varuint32, meaning
/// they are encoded as leb128 in binary.
@ -510,6 +518,86 @@ pub fn simdOpcode(op: SimdOpcode) u32 {
return @enumToInt(op);
}
/// Simd opcodes that require a prefix `0xFE`.
/// Each opcode represents a varuint32, meaning
/// they are encoded as leb128 in binary.
pub const AtomicsOpcode = enum(u32) {
memory_atomic_notify = 0x00,
memory_atomic_wait32 = 0x01,
memory_atomic_wait64 = 0x02,
atomic_fence = 0x03,
i32_atomic_load = 0x10,
i64_atomic_load = 0x11,
i32_atomic_load8_u = 0x12,
i32_atomic_load16_u = 0x13,
i64_atomic_load8_u = 0x14,
i64_atomic_load16_u = 0x15,
i64_atomic_load32_u = 0x16,
i32_atomic_store = 0x17,
i64_atomic_store = 0x18,
i32_atomic_store8 = 0x19,
i32_atomic_store16 = 0x1A,
i64_atomic_store8 = 0x1B,
i64_atomic_store16 = 0x1C,
i64_atomic_store32 = 0x1D,
i32_atomic_rmw_add = 0x1E,
i64_atomic_rmw_add = 0x1F,
i32_atomic_rmw8_add_u = 0x20,
i32_atomic_rmw16_add_u = 0x21,
i64_atomic_rmw8_add_u = 0x22,
i64_atomic_rmw16_add_u = 0x23,
i64_atomic_rmw32_add_u = 0x24,
i32_atomic_rmw_sub = 0x25,
i64_atomic_rmw_sub = 0x26,
i32_atomic_rmw8_sub_u = 0x27A,
i32_atomic_rmw16_sub_u = 0x28A,
i64_atomic_rmw8_sub_u = 0x29A,
i64_atomic_rmw16_sub_u = 0x2A,
i64_atomic_rmw32_sub_u = 0x2B,
i32_atomic_rmw_and = 0x2C,
i64_atomic_rmw_and = 0x2D,
i32_atomic_rmw8_and_u = 0x2E,
i32_atomic_rmw16_and_u = 0x2F,
i64_atomic_rmw8_and_u = 0x30,
i64_atomic_rmw16_and_u = 0x31,
i64_atomic_rmw32_and_u = 0x32,
i32_atomic_rmw_or = 0x33,
i64_atomic_rmw_or = 0x34,
i32_atomic_rmw8_or_u = 0x35,
i32_atomic_rmw16_or_u = 0x36,
i64_atomic_rmw8_or_u = 0x37,
i64_atomic_rmw16_or_u = 0x38,
i64_atomic_rmw32_or_u = 0x39,
i32_atomic_rmw_xor = 0x3A,
i64_atomic_rmw_xor = 0x3B,
i32_atomic_rmw8_xor_u = 0x3C,
i32_atomic_rmw16_xor_u = 0x3D,
i64_atomic_rmw8_xor_u = 0x3E,
i64_atomic_rmw16_xor_u = 0x3F,
i64_atomic_rmw32_xor_u = 0x40,
i32_atomic_rmw_xchg = 0x41,
i64_atomic_rmw_xchg = 0x42,
i32_atomic_rmw8_xchg_u = 0x43,
i32_atomic_rmw16_xchg_u = 0x44,
i64_atomic_rmw8_xchg_u = 0x45,
i64_atomic_rmw16_xchg_u = 0x46,
i64_atomic_rmw32_xchg_u = 0x47,
i32_atomic_rmw_cmpxchg = 0x48,
i64_atomic_rmw_cmpxchg = 0x49,
i32_atomic_rmw8_cmpxchg_u = 0x4A,
i32_atomic_rmw16_cmpxchg_u = 0x4B,
i64_atomic_rmw8_cmpxchg_u = 0x4C,
i64_atomic_rmw16_cmpxchg_u = 0x4D,
i64_atomic_rmw32_cmpxchg_u = 0x4E,
};
/// Returns the integer value of an `AtomicsOpcode`. Used by the Zig compiler
/// to write instructions to the wasm binary file
pub fn atomicsOpcode(op: AtomicsOpcode) u32 {
return @enumToInt(op);
}
/// Enum representing all Wasm value types as per spec:
/// https://webassembly.github.io/spec/core/binary/types.html
pub const Valtype = enum(u8) {
@ -551,8 +639,22 @@ test "Wasm - valtypes" {
/// Limits classify the size range of resizeable storage associated with memory types and table types.
pub const Limits = struct {
flags: u8,
min: u32,
max: ?u32,
max: u32,
pub const Flags = enum(u8) {
WASM_LIMITS_FLAG_HAS_MAX = 0x1,
WASM_LIMITS_FLAG_IS_SHARED = 0x2,
};
pub fn hasFlag(limits: Limits, flag: Flags) bool {
return limits.flags & @enumToInt(flag) != 0;
}
pub fn setFlag(limits: *Limits, flag: Flags) void {
limits.flags |= @enumToInt(flag);
}
};
/// Initialization expressions are used to set the initial value on an object

View File

@ -895,10 +895,10 @@ fn addTag(func: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
try func.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
}
fn addExtended(func: *CodeGen, opcode: wasm.PrefixedOpcode) error{OutOfMemory}!void {
fn addExtended(func: *CodeGen, opcode: wasm.MiscOpcode) error{OutOfMemory}!void {
const extra_index = @intCast(u32, func.mir_extra.items.len);
try func.mir_extra.append(func.gpa, @enumToInt(opcode));
try func.addInst(.{ .tag = .extended, .data = .{ .payload = extra_index } });
try func.addInst(.{ .tag = .misc_prefix, .data = .{ .payload = extra_index } });
}
fn 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 {
try func.mir_extra.ensureUnusedCapacity(func.gpa, 5);
func.mir_extra.appendAssumeCapacity(std.wasm.simdOpcode(.v128_const));
func.mir_extra.appendSliceAssumeCapacity(@alignCast(4, mem.bytesAsSlice(u32, &simd_values)));
try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
}
fn 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
offset + lhs.offset(),
ty.abiAlignment(func.target),
});
return func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
return func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
},
},
.Pointer => {
@ -2420,7 +2420,7 @@ fn load(func: *CodeGen, operand: WValue, ty: Type, offset: u32) InnerError!WValu
offset + operand.offset(),
ty.abiAlignment(func.target),
});
try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
return WValue{ .stack = {} };
}
@ -4477,7 +4477,7 @@ fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
operand.offset(),
elem_ty.abiAlignment(func.target),
});
try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
try func.addLabel(.local_set, result.local.value);
return func.finishAir(inst, result, &.{ty_op.operand});
},
@ -4493,7 +4493,7 @@ fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
try func.emitWValue(operand);
const extra_index = @intCast(u32, func.mir_extra.items.len);
try func.mir_extra.append(func.gpa, opcode);
try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
try func.addInst(.{ .tag = .simd_prefix, .data = .{ .payload = extra_index } });
try func.addLabel(.local_set, result.local.value);
return func.finishAir(inst, result, &.{ty_op.operand});
},

View File

@ -239,8 +239,9 @@ pub fn emitMir(emit: *Emit) InnerError!void {
.i64_clz => try emit.emitTag(tag),
.i64_ctz => try emit.emitTag(tag),
.extended => try emit.emitExtended(inst),
.simd => try emit.emitSimd(inst),
.misc_prefix => try emit.emitExtended(inst),
.simd_prefix => try emit.emitSimd(inst),
.atomics_prefix => try emit.emitAtomic(inst),
}
}
}
@ -433,9 +434,9 @@ fn emitExtended(emit: *Emit, inst: Mir.Inst.Index) !void {
const extra_index = emit.mir.instructions.items(.data)[inst].payload;
const opcode = emit.mir.extra[extra_index];
const writer = emit.code.writer();
try emit.code.append(0xFC);
try emit.code.append(std.wasm.opcode(.misc_prefix));
try leb128.writeULEB128(writer, opcode);
switch (@intToEnum(std.wasm.PrefixedOpcode, opcode)) {
switch (@intToEnum(std.wasm.MiscOpcode, opcode)) {
// bulk-memory opcodes
.data_drop => {
const segment = emit.mir.extra[extra_index + 1];
@ -472,7 +473,7 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {
const extra_index = emit.mir.instructions.items(.data)[inst].payload;
const opcode = emit.mir.extra[extra_index];
const writer = emit.code.writer();
try emit.code.append(0xFD);
try emit.code.append(std.wasm.opcode(.simd_prefix));
try leb128.writeULEB128(writer, opcode);
switch (@intToEnum(std.wasm.SimdOpcode, opcode)) {
.v128_store,
@ -496,10 +497,15 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {
.f32x4_splat,
.f64x2_splat,
=> {}, // opcode already written
else => |tag| return emit.fail("TODO: Implement simd instruction: {s}\n", .{@tagName(tag)}),
else => |tag| return emit.fail("TODO: Implement simd instruction: {s}", .{@tagName(tag)}),
}
}
fn emitAtomic(emit: *Emit, inst: Mir.Inst.Index) !void {
_ = inst;
return emit.fail("TODO: Implement atomics instructions", .{});
}
fn emitMemFill(emit: *Emit) !void {
try emit.code.append(0xFC);
try emit.code.append(0x0B);

View File

@ -87,6 +87,13 @@ pub const Inst = struct {
///
/// Uses `label`
call_indirect = 0x11,
/// Contains a symbol to a function pointer
/// uses `label`
///
/// Note: This uses `0x16` as value which is reserved by the WebAssembly
/// specification but unused, meaning we must update this if the specification were to
/// use this value.
function_index = 0x16,
/// Pops three values from the stack and pushes
/// the first or second value dependent on the third value.
/// Uses `tag`
@ -510,24 +517,24 @@ pub const Inst = struct {
i64_extend16_s = 0xC3,
/// Uses `tag`
i64_extend32_s = 0xC4,
/// The instruction consists of an extension opcode.
/// The instruction consists of a prefixed opcode.
/// The prefixed opcode can be found at payload's index.
///
/// The `data` field depends on the extension instruction and
/// may contain additional data.
extended = 0xFC,
misc_prefix = 0xFC,
/// The instruction consists of a simd opcode.
/// The actual simd-opcode is found at payload's index.
///
/// The `data` field depends on the simd instruction and
/// may contain additional data.
simd = 0xFD,
/// Contains a symbol to a function pointer
/// uses `label`
simd_prefix = 0xFD,
/// The instruction consists of an atomics opcode.
/// The actual atomics-opcode is found at payload's index.
///
/// Note: This uses `0xFE` as value as it is unused and not reserved
/// by the wasm specification, making it safe to use.
function_index = 0xFE,
/// The `data` field depends on the atomics instruction and
/// may contain additional data.
atomics_prefix = 0xFE,
/// Contains a symbol to a memory address
/// Uses `label`
///

View File

@ -111,7 +111,11 @@ functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.
/// Output global section
wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
/// Memory section
memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
memories: std.wasm.Memory = .{ .limits = .{
.min = 0,
.max = undefined,
.flags = 0,
} },
/// Output table section
tables: std.ArrayListUnmanaged(std.wasm.Table) = .{},
/// Output export section
@ -135,6 +139,8 @@ archives: std.ArrayListUnmanaged(Archive) = .{},
/// A map of global names (read: offset into string table) to their symbol location
globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .{},
/// The list of GOT symbols and their location
got_symbols: std.ArrayListUnmanaged(SymbolLoc) = .{},
/// Maps discarded symbols and their positions to the location of the symbol
/// it was resolved to
discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{},
@ -176,6 +182,24 @@ pub const Segment = struct {
alignment: u32,
size: u32,
offset: u32,
flags: u32,
pub const Flag = enum(u32) {
WASM_DATA_SEGMENT_IS_PASSIVE = 0x01,
WASM_DATA_SEGMENT_HAS_MEMINDEX = 0x02,
};
pub fn isPassive(segment: Segment) bool {
return segment.flags & @enumToInt(Flag.WASM_DATA_SEGMENT_IS_PASSIVE) != 0;
}
/// For a given segment, determines if it needs passive initialization
fn needsPassiveInitialization(segment: Segment, import_mem: bool, name: []const u8) bool {
if (import_mem and !std.mem.eql(u8, name, ".bss")) {
return true;
}
return segment.isPassive();
}
};
pub const Export = struct {
@ -396,7 +420,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
const loc = try wasm_bin.createSyntheticSymbol("__indirect_function_table", .table);
const symbol = loc.getSymbol(wasm_bin);
const table: std.wasm.Table = .{
.limits = .{ .min = 0, .max = null }, // will be overwritten during `mapFunctionTable`
.limits = .{ .flags = 0, .min = 0, .max = undefined }, // will be overwritten during `mapFunctionTable`
.reftype = .funcref,
};
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
// at the end during `initializeCallCtorsFunction`.
}
// shared-memory symbols for TLS support
if (wasm_bin.base.options.shared_memory) {
{
const loc = try wasm_bin.createSyntheticSymbol("__tls_base", .global);
const symbol = loc.getSymbol(wasm_bin);
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
}
{
const loc = try wasm_bin.createSyntheticSymbol("__tls_size", .global);
const symbol = loc.getSymbol(wasm_bin);
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
}
{
const loc = try wasm_bin.createSyntheticSymbol("__tls_align", .global);
const symbol = loc.getSymbol(wasm_bin);
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
}
{
const loc = try wasm_bin.createSyntheticSymbol("__wasm_tls_init", .function);
const symbol = loc.getSymbol(wasm_bin);
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
}
}
// if (!options.strip and options.module != null) {
// wasm_bin.dwarf = Dwarf.init(allocator, &wasm_bin.base, options.target);
// try wasm_bin.initDebugSections();
@ -597,6 +645,15 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
return true;
}
fn requiresTLSReloc(wasm: *const Wasm) bool {
for (wasm.got_symbols.items) |loc| {
if (loc.getSymbol(wasm).isTLS()) {
return true;
}
}
return false;
}
fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {
const object: Object = wasm.objects.items[object_index];
log.debug("Resolving symbols in object: '{s}'", .{object.name});
@ -775,6 +832,220 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
}
}
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
// dropped in __wasm_init_memory, which is registered as the start function
// We also initialize bss segments (using memory.fill) as part of this
// function.
if (!wasm.hasPassiveInitializationSegments()) {
return;
}
const flag_address: u32 = if (wasm.base.options.shared_memory) address: {
// when we have passive initialization segments and shared memory
// `setupMemory` will create this symbol and set its virtual address.
const loc = wasm.findGlobalSymbol("__wasm_init_memory_flag").?;
break :address loc.getSymbol(wasm).virtual_address;
} else 0;
var function_body = std.ArrayList(u8).init(wasm.base.allocator);
defer function_body.deinit();
const writer = function_body.writer();
// we have 0 locals
try leb.writeULEB128(writer, @as(u32, 0));
if (wasm.base.options.shared_memory) {
// destination blocks
// based on values we jump to corresponding label
try writer.writeByte(std.wasm.opcode(.block)); // $drop
try writer.writeByte(std.wasm.block_empty); // block type
try writer.writeByte(std.wasm.opcode(.block)); // $wait
try writer.writeByte(std.wasm.block_empty); // block type
try writer.writeByte(std.wasm.opcode(.block)); // $init
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 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
try leb.writeULEB128(writer, @as(u32, 0)); // offset
// based on the value from the atomic check, jump to the label.
try writer.writeByte(std.wasm.opcode(.br_table));
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).
try leb.writeULEB128(writer, @as(u32, 0)); // $init
try leb.writeULEB128(writer, @as(u32, 1)); // $wait
try leb.writeULEB128(writer, @as(u32, 2)); // $drop
try writer.writeByte(std.wasm.opcode(.end));
}
var it = wasm.data_segments.iterator();
var segment_index: u32 = 0;
while (it.next()) |entry| : (segment_index += 1) {
const segment: Segment = wasm.segments.items[entry.value_ptr.*];
if (segment.needsPassiveInitialization(wasm.base.options.import_memory, entry.key_ptr.*)) {
// For passive BSS segments we can simple issue a memory.fill(0).
// 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);
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 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 writer.writeByte(std.wasm.opcode(.misc_prefix));
if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) {
// fill bss segment with zeroes
try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_fill));
} else {
// initialize the segment
try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_init));
try leb.writeULEB128(writer, segment_index);
}
try writer.writeByte(0); // memory index immediate
}
}
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 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 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));
try leb.writeULEB128(writer, std.wasm.atomicsOpcode(.memory_atomic_notify));
try leb.writeULEB128(writer, @as(u32, 2)); // alignment
try leb.writeULEB128(writer, @as(u32, 0)); // offset
try writer.writeByte(std.wasm.opcode(.drop));
// branch and drop segments
try writer.writeByte(std.wasm.opcode(.br));
try leb.writeULEB128(writer, @as(u32, 1));
// 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 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
try leb.writeULEB128(writer, @as(u32, 0)); // offset
try writer.writeByte(std.wasm.opcode(.drop));
try writer.writeByte(std.wasm.opcode(.end)); // end $drop
}
it.reset();
segment_index = 0;
while (it.next()) |entry| : (segment_index += 1) {
const name = entry.key_ptr.*;
const segment: Segment = wasm.segments.items[entry.value_ptr.*];
if (segment.needsPassiveInitialization(wasm.base.options.import_memory, name) and
!std.mem.eql(u8, name, ".bss"))
{
// The TLS region should not be dropped since its is needed
// during the initialization of each thread (__wasm_init_tls).
if (wasm.base.options.shared_memory and std.mem.eql(u8, name, ".tdata")) {
continue;
}
try writer.writeByte(std.wasm.opcode(.misc_prefix));
try leb.writeULEB128(writer, std.wasm.miscOpcode(.data_drop));
try leb.writeULEB128(writer, segment_index);
}
}
// End of the function body
try writer.writeByte(std.wasm.opcode(.end));
try wasm.createSyntheticFunction(
"__wasm_init_memory",
std.wasm.Type{ .params = &.{}, .returns = &.{} },
&function_body,
);
}
/// Constructs a synthetic function that performs runtime relocations for
/// TLS symbols. This function is called by `__wasm_init_tls`.
fn setupTLSRelocationsFunction(wasm: *Wasm) !void {
// When we have TLS GOT entries and shared memory is enabled,
// we must perform runtime relocations or else we don't create the function.
if (!wasm.base.options.shared_memory or !wasm.requiresTLSReloc()) {
return;
}
// const loc = try wasm.createSyntheticSymbol("__wasm_apply_global_tls_relocs");
var function_body = std.ArrayList(u8).init(wasm.base.allocator);
defer function_body.deinit();
const writer = function_body.writer();
// locals (we have none)
try writer.writeByte(0);
for (wasm.got_symbols.items, 0..) |got_loc, got_index| {
const sym: *Symbol = got_loc.getSymbol(wasm);
if (!sym.isTLS()) continue; // only relocate TLS symbols
if (sym.tag == .data and sym.isDefined()) {
// get __tls_base
try writer.writeByte(std.wasm.opcode(.global_get));
try leb.writeULEB128(writer, wasm.findGlobalSymbol("__tls_base").?.getSymbol(wasm).index);
// add the virtual address of the symbol
try writer.writeByte(std.wasm.opcode(.i32_const));
try leb.writeULEB128(writer, sym.virtual_address);
} else if (sym.tag == .function) {
@panic("TODO: relocate GOT entry of function");
} else continue;
try writer.writeByte(std.wasm.opcode(.i32_add));
try writer.writeByte(std.wasm.opcode(.global_set));
try leb.writeULEB128(writer, wasm.imported_globals_count + @intCast(u32, wasm.wasm_globals.items.len + got_index));
}
try writer.writeByte(std.wasm.opcode(.end));
try wasm.createSyntheticFunction(
"__wasm_apply_global_tls_relocs",
std.wasm.Type{ .params = &.{}, .returns = &.{} },
&function_body,
);
}
fn validateFeatures(
wasm: *const Wasm,
to_emit: *[@typeInfo(types.Feature.Tag).Enum.fields.len]bool,
@ -791,6 +1062,8 @@ fn validateFeatures(
// when false, we fail linking. We only verify this after a loop to catch all invalid features.
var valid_feature_set = true;
// will be set to true when there's any TLS segment found in any of the object files
var has_tls = false;
// When the user has given an explicit list of features to enable,
// we extract them and insert each into the 'allowed' list.
@ -821,6 +1094,12 @@ fn validateFeatures(
},
}
}
for (object.segment_info) |segment| {
if (segment.isTLS()) {
has_tls = true;
}
}
}
// when we infer the features, we allow each feature found in the 'used' set
@ -832,7 +1111,7 @@ fn validateFeatures(
allowed[used_index] = is_enabled;
emit_features_count.* += @boolToInt(is_enabled);
} else if (is_enabled and !allowed[used_index]) {
log.err("feature '{s}' not allowed, but used by linked object", .{(@intToEnum(types.Feature.Tag, used_index)).toString()});
log.err("feature '{}' not allowed, but used by linked object", .{@intToEnum(types.Feature.Tag, used_index)});
log.err(" defined in '{s}'", .{wasm.objects.items[used_set >> 1].name});
valid_feature_set = false;
}
@ -842,6 +1121,30 @@ fn validateFeatures(
return error.InvalidFeatureSet;
}
if (wasm.base.options.shared_memory) {
const disallowed_feature = disallowed[@enumToInt(types.Feature.Tag.shared_mem)];
if (@truncate(u1, disallowed_feature) != 0) {
log.err(
"shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled",
.{wasm.objects.items[disallowed_feature >> 1].name},
);
valid_feature_set = false;
}
for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| {
if (!allowed[@enumToInt(feature)]) {
log.err("feature '{}' is not used but is required for shared-memory", .{feature});
}
}
}
if (has_tls) {
for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| {
if (!allowed[@enumToInt(feature)]) {
log.err("feature '{}' is not used but is required for thread-local storage", .{feature});
}
}
}
// For each linked object, validate the required and disallowed features
for (wasm.objects.items) |object| {
var object_used_features = [_]bool{false} ** known_features_count;
@ -850,7 +1153,7 @@ fn validateFeatures(
// from here a feature is always used
const disallowed_feature = disallowed[@enumToInt(feature.tag)];
if (@truncate(u1, disallowed_feature) != 0) {
log.err("feature '{s}' is disallowed, but used by linked object", .{feature.tag.toString()});
log.err("feature '{}' is disallowed, but used by linked object", .{feature.tag});
log.err(" disallowed by '{s}'", .{wasm.objects.items[disallowed_feature >> 1].name});
log.err(" used in '{s}'", .{object.name});
valid_feature_set = false;
@ -863,7 +1166,7 @@ fn validateFeatures(
for (required, 0..) |required_feature, feature_index| {
const is_required = @truncate(u1, required_feature) != 0;
if (is_required and !object_used_features[feature_index]) {
log.err("feature '{s}' is required but not used in linked object", .{(@intToEnum(types.Feature.Tag, feature_index)).toString()});
log.err("feature '{}' is required but not used in linked object", .{@intToEnum(types.Feature.Tag, feature_index)});
log.err(" required by '{s}'", .{wasm.objects.items[required_feature >> 1].name});
log.err(" missing in '{s}'", .{object.name});
valid_feature_set = false;
@ -894,6 +1197,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,
@ -1517,7 +1827,7 @@ fn mapFunctionTable(wasm: *Wasm) void {
const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
const symbol = sym_loc.getSymbol(wasm);
const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
table.limits = .{ .min = index, .max = index };
table.limits = .{ .min = index, .max = index, .flags = 0x1 };
}
}
@ -1630,6 +1940,7 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
.alignment = atom.alignment,
.size = atom.size,
.offset = 0,
.flags = 0,
});
}
@ -1668,10 +1979,15 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
break :result index;
} else {
const index = @intCast(u32, wasm.segments.items.len);
var flags: u32 = 0;
if (wasm.base.options.shared_memory) {
flags |= @enumToInt(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
}
try wasm.segments.append(wasm.base.allocator, .{
.alignment = atom.alignment,
.size = 0,
.offset = 0,
.flags = flags,
});
gop.value_ptr.* = index;
@ -1907,10 +2223,23 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
try writer.writeByte(std.wasm.opcode(.end));
}
const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
try wasm.createSyntheticFunction(
"__wasm_call_ctors",
std.wasm.Type{ .params = &.{}, .returns = &.{} },
&function_body,
);
}
fn createSyntheticFunction(
wasm: *Wasm,
symbol_name: []const u8,
func_ty: std.wasm.Type,
function_body: *std.ArrayList(u8),
) !void {
const loc = wasm.findGlobalSymbol(symbol_name) orelse
try wasm.createSyntheticSymbol(symbol_name, .function);
const symbol = loc.getSymbol(wasm);
// create type (() -> nil) as we do not have any parameters or return value.
const ty_index = try wasm.putOrGetFuncType(.{ .params = &[_]std.wasm.Valtype{}, .returns = &[_]std.wasm.Valtype{} });
const ty_index = try wasm.putOrGetFuncType(func_ty);
// create function with above type
const func_index = wasm.imported_functions_count + @intCast(u32, wasm.functions.count());
try wasm.functions.putNoClobber(
@ -1942,6 +2271,68 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
atom.offset = prev_atom.offset + prev_atom.size;
}
fn initializeTLSFunction(wasm: *Wasm) !void {
if (!wasm.base.options.shared_memory) return;
var function_body = std.ArrayList(u8).init(wasm.base.allocator);
defer function_body.deinit();
const writer = function_body.writer();
// locals
try writer.writeByte(0);
// If there's a TLS segment, initialize it during runtime using the bulk-memory feature
if (wasm.data_segments.getIndex(".tdata")) |data_index| {
const segment_index = wasm.data_segments.entries.items(.value)[data_index];
const segment = wasm.segments.items[segment_index];
const param_local: u32 = 0;
try writer.writeByte(std.wasm.opcode(.local_get));
try leb.writeULEB128(writer, param_local);
const tls_base_loc = wasm.findGlobalSymbol("__tls_base").?;
try writer.writeByte(std.wasm.opcode(.global_get));
try leb.writeULEB128(writer, tls_base_loc.getSymbol(wasm).index);
// load stack values for the bulk-memory operation
{
try writer.writeByte(std.wasm.opcode(.local_get));
try leb.writeULEB128(writer, param_local);
try writer.writeByte(std.wasm.opcode(.i32_const));
try leb.writeULEB128(writer, @as(u32, 0)); //segment offset
try writer.writeByte(std.wasm.opcode(.i32_const));
try leb.writeULEB128(writer, @as(u32, segment.size)); //segment offset
}
// perform the bulk-memory operation to initialize the data segment
try writer.writeByte(std.wasm.opcode(.misc_prefix));
try leb.writeULEB128(writer, std.wasm.miscOpcode(.memory_init));
// segment immediate
try leb.writeULEB128(writer, @intCast(u32, data_index));
// memory index immediate (always 0)
try leb.writeULEB128(writer, @as(u32, 0));
}
// If we have to perform any TLS relocations, call the corresponding function
// which performs all runtime TLS relocations. This is a synthetic function,
// generated by the linker.
if (wasm.findGlobalSymbol("__wasm_apply_global_tls_relocs")) |loc| {
try writer.writeByte(std.wasm.opcode(.call));
try leb.writeULEB128(writer, loc.getSymbol(wasm).index);
}
try writer.writeByte(std.wasm.opcode(.end));
try wasm.createSyntheticFunction(
"__wasm_init_tls",
std.wasm.Type{ .params = &.{.i32}, .returns = &.{} },
&function_body,
);
}
fn setupImports(wasm: *Wasm) !void {
log.debug("Merging imports", .{});
var discarded_it = wasm.discarded.keyIterator();
@ -2224,11 +2615,50 @@ 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_size")) |loc| {
const sym = loc.getSymbol(wasm);
sym.index = @intCast(u32, 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 = @intCast(i32, segment.size) },
});
}
if (wasm.findGlobalSymbol("__tls_align")) |loc| {
const sym = loc.getSymbol(wasm);
sym.index = @intCast(u32, 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 = @intCast(i32, segment.alignment) },
});
}
if (wasm.findGlobalSymbol("__tls_base")) |loc| {
const sym = loc.getSymbol(wasm);
sym.index = @intCast(u32, 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 @intCast(i32, memory_ptr) },
});
}
}
memory_ptr += segment.size;
segment.offset = offset;
offset += segment.size;
}
// create the memory init flag which is used by the init memory function
if (wasm.base.options.shared_memory and wasm.hasPassiveInitializationSegments()) {
// align to pointer size
memory_ptr = mem.alignForwardGeneric(u64, memory_ptr, 4);
const loc = try wasm.createSyntheticSymbol("__wasm_init_memory_flag", .data);
const sym = loc.getSymbol(wasm);
sym.virtual_address = @intCast(u32, memory_ptr);
memory_ptr += 4;
}
if (!place_stack_first and !is_obj) {
memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, stack_alignment);
memory_ptr += stack_size;
@ -2286,6 +2716,10 @@ fn setupMemory(wasm: *Wasm) !void {
return error.MemoryTooBig;
}
wasm.memories.limits.max = @intCast(u32, max_memory / page_size);
wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_HAS_MAX);
if (wasm.base.options.shared_memory) {
wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_IS_SHARED);
}
log.debug("Maximum memory pages: {?d}", .{wasm.memories.limits.max});
}
}
@ -2305,7 +2739,16 @@ pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32
const result = try wasm.data_segments.getOrPut(wasm.base.allocator, segment_info.outputName(merge_segment));
if (!result.found_existing) {
result.value_ptr.* = index;
try wasm.appendDummySegment();
var flags: u32 = 0;
if (wasm.base.options.shared_memory) {
flags |= @enumToInt(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
}
try wasm.segments.append(wasm.base.allocator, .{
.alignment = 1,
.size = 0,
.offset = 0,
.flags = flags,
});
return index;
} else return result.value_ptr.*;
},
@ -2379,6 +2822,7 @@ fn appendDummySegment(wasm: *Wasm) !void {
.alignment = 1,
.size = 0,
.offset = 0,
.flags = 0,
});
}
@ -2746,6 +3190,9 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
try wasm.mergeSections();
try wasm.mergeTypes();
try wasm.initializeCallCtorsFunction();
try wasm.setupInitMemoryFunction();
try wasm.setupTLSRelocationsFunction();
try wasm.initializeTLSFunction();
try wasm.setupExports();
try wasm.writeToFile(enabled_features, emit_features_count, arena);
@ -2874,6 +3321,9 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
try wasm.mergeSections();
try wasm.mergeTypes();
try wasm.initializeCallCtorsFunction();
try wasm.setupInitMemoryFunction();
try wasm.setupTLSRelocationsFunction();
try wasm.initializeTLSFunction();
try wasm.setupExports();
try wasm.writeToFile(enabled_features, emit_features_count, arena);
}
@ -3096,6 +3546,19 @@ fn writeToFile(
section_count += 1;
}
// When the shared-memory option is enabled, we *must* emit the 'data count' section.
const data_segments_count = wasm.data_segments.count() - @boolToInt(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(
binary_bytes.items,
header_offset,
.data_count,
@intCast(u32, binary_bytes.items.len - header_offset - header_size),
@intCast(u32, data_segments_count),
);
}
// Code section
var code_section_size: u32 = 0;
if (wasm.code_section_index) |code_index| {
@ -3146,7 +3609,7 @@ fn writeToFile(
}
// Data section
if (wasm.data_segments.count() != 0) {
if (data_segments_count != 0) {
const header_offset = try reserveVecSectionHeader(&binary_bytes);
var it = wasm.data_segments.iterator();
@ -3161,10 +3624,15 @@ fn writeToFile(
segment_count += 1;
var atom_index = wasm.atoms.get(segment_index).?;
// flag and index to memory section (currently, there can only be 1 memory section in wasm)
try leb.writeULEB128(binary_writer, @as(u32, 0));
try leb.writeULEB128(binary_writer, segment.flags);
if (segment.flags & @enumToInt(Wasm.Segment.Flag.WASM_DATA_SEGMENT_HAS_MEMINDEX) != 0) {
try leb.writeULEB128(binary_writer, @as(u32, 0)); // memory is always index 0 as we only have 1 memory entry
}
// when a segment is passive, it's initialized during runtime.
if (!segment.isPassive()) {
try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) });
}
// offset into data section
try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) });
try leb.writeULEB128(binary_writer, segment.size);
// fill in the offset table and the data segments
@ -3413,7 +3881,8 @@ fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []con
if (enabled) {
const feature: types.Feature = .{ .prefix = .used, .tag = @intToEnum(types.Feature.Tag, feature_index) };
try leb.writeULEB128(writer, @enumToInt(feature.prefix));
const string = feature.tag.toString();
var buf: [100]u8 = undefined;
const string = try std.fmt.bufPrint(&buf, "{}", .{feature.tag});
try leb.writeULEB128(writer, @intCast(u32, string.len));
try writer.writeAll(string);
}
@ -3507,10 +3976,10 @@ fn emitNameSubsection(wasm: *Wasm, section_id: std.wasm.NameSubsection, names: a
}
fn emitLimits(writer: anytype, limits: std.wasm.Limits) !void {
try leb.writeULEB128(writer, @boolToInt(limits.max != null));
try writer.writeByte(limits.flags);
try leb.writeULEB128(writer, limits.min);
if (limits.max) |max| {
try leb.writeULEB128(writer, max);
if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) {
try leb.writeULEB128(writer, limits.max);
}
}
@ -4205,6 +4674,17 @@ fn emitDataRelocations(
try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
}
fn hasPassiveInitializationSegments(wasm: *const Wasm) bool {
var it = wasm.data_segments.iterator();
while (it.next()) |entry| {
const segment: Segment = wasm.segments.items[entry.value_ptr.*];
if (segment.needsPassiveInitialization(wasm.base.options.import_memory, entry.key_ptr.*)) {
return true;
}
}
return false;
}
pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {
var index: u32 = 0;
while (index < wasm.func_types.items.len) : (index += 1) {

View File

@ -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");
},
}
}

View File

@ -601,8 +601,8 @@ fn Parser(comptime ReaderType: type) type {
});
for (relocations) |*relocation| {
const rel_type = try leb.readULEB128(u8, reader);
const rel_type_enum = @intToEnum(types.Relocation.RelocationType, rel_type);
const rel_type = try reader.readByte();
const rel_type_enum = std.meta.intToEnum(types.Relocation.RelocationType, rel_type) catch return error.MalformedSection;
relocation.* = .{
.relocation_type = rel_type_enum,
.offset = try leb.readULEB128(u32, reader),
@ -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;
},
@ -846,12 +852,17 @@ fn readEnum(comptime T: type, reader: anytype) !T {
}
fn readLimits(reader: anytype) !std.wasm.Limits {
const flags = try readLeb(u1, reader);
const flags = try reader.readByte();
const min = try readLeb(u32, reader);
return std.wasm.Limits{
var limits: std.wasm.Limits = .{
.flags = flags,
.min = min,
.max = if (flags == 0) null else try readLeb(u32, reader),
.max = undefined,
};
if (limits.hasFlag(.WASM_LIMITS_FLAG_HAS_MAX)) {
limits.max = try readLeb(u32, reader);
}
return limits;
}
fn readInit(reader: anytype) !std.wasm.InitExpression {
@ -919,11 +930,29 @@ pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_b
reloc.offset -= relocatable_data.offset;
try atom.relocs.append(gpa, reloc);
if (relocation.isTableIndex()) {
try wasm_bin.function_table.put(gpa, .{
.file = object_index,
.index = relocation.index,
}, 0);
switch (relocation.relocation_type) {
.R_WASM_TABLE_INDEX_I32,
.R_WASM_TABLE_INDEX_I64,
.R_WASM_TABLE_INDEX_SLEB,
.R_WASM_TABLE_INDEX_SLEB64,
=> {
try wasm_bin.function_table.put(gpa, .{
.file = object_index,
.index = relocation.index,
}, 0);
},
.R_WASM_GLOBAL_INDEX_I32,
.R_WASM_GLOBAL_INDEX_LEB,
=> {
const sym = object.symtable[relocation.index];
if (sym.tag != .global) {
try wasm_bin.got_symbols.append(
wasm_bin.base.allocator,
.{ .file = object_index, .index = relocation.index },
);
}
},
else => {},
}
}
}

View File

@ -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;
}

View File

@ -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 {
@ -48,6 +50,8 @@ pub const Relocation = struct {
.R_WASM_MEMORY_ADDR_LEB64,
.R_WASM_MEMORY_ADDR_SLEB64,
.R_WASM_MEMORY_ADDR_I64,
.R_WASM_MEMORY_ADDR_TLS_SLEB,
.R_WASM_MEMORY_ADDR_TLS_SLEB64,
.R_WASM_FUNCTION_OFFSET_I32,
.R_WASM_SECTION_OFFSET_I32,
=> true,
@ -67,18 +71,6 @@ pub const Relocation = struct {
};
}
/// Returns true when the relocation represents a table index relocatable
pub fn isTableIndex(self: Relocation) bool {
return switch (self.relocation_type) {
.R_WASM_TABLE_INDEX_I32,
.R_WASM_TABLE_INDEX_I64,
.R_WASM_TABLE_INDEX_SLEB,
.R_WASM_TABLE_INDEX_SLEB64,
=> true,
else => false,
};
}
pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = options;
@ -125,23 +117,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 {
@ -205,8 +208,10 @@ pub const Feature = struct {
return @intToEnum(Tag, @enumToInt(feature));
}
pub fn toString(tag: Tag) []const u8 {
return switch (tag) {
pub fn format(tag: Tag, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = opt;
try writer.writeAll(switch (tag) {
.atomics => "atomics",
.bulk_memory => "bulk-memory",
.exception_handling => "exception-handling",
@ -220,7 +225,7 @@ pub const Feature = struct {
.simd128 => "simd128",
.tail_call => "tail-call",
.shared_mem => "shared-mem",
};
});
}
};
@ -233,7 +238,7 @@ pub const Feature = struct {
pub fn format(feature: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
_ = opt;
_ = fmt;
try writer.print("{c} {s}", .{ feature.prefix, feature.tag.toString() });
try writer.print("{c} {}", .{ feature.prefix, feature.tag });
}
};