wasm: add atomics opcodes and refactoring

This adds the atomic opcodes for the Threads proposal to the
WebAssembly specification: https://github.com/WebAssembly/threads

PrefixedOpcode has been renamed to MiscOpcode as there's multiple
types of prefixed opcodes. This naming is similar to other tools
such as LLVM. As we now use the 0xFE prefix, we moved the
function_index MIR instruction as it was occupying the same value.
This commit includes renaming all related opcodes.
This commit is contained in:
Luuk de Gram 2023-03-17 07:09:01 +01:00
parent 9fce1df4cd
commit 09d6938df9
No known key found for this signature in database
GPG Key ID: A8CFE58E4DC7D664
5 changed files with 126 additions and 25 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) {

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

@ -2128,8 +2128,8 @@ fn initializeTLSFunction(wasm: *Wasm) !void {
}
// perform the bulk-memory operation to initialize the data segment
try writer.writeByte(std.wasm.opcode(.prefixed));
try leb.writeULEB128(writer, @enumToInt(std.wasm.PrefixedOpcode.memory_init));
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)