mirror of
https://github.com/ziglang/zig.git
synced 2026-02-21 16:54:52 +00:00
Merge pull request #14998 from Luukdegram/shared-mem
wasm-linker: Implement shared-memory
This commit is contained in:
commit
c26cbd561c
108
lib/std/wasm.zig
108
lib/std/wasm.zig
@ -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
|
||||
|
||||
@ -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});
|
||||
},
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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`
|
||||
///
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user