authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-12-08 21:18:11+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-12-12 17:42:00+01:00
logc6d654f73bbe80ed3653be6a31ddcaa4772a4fe2
tree04c166bceaea1df0044582297f0332291e23119e
parentdb06eed7a3e741a319182b2e4edc889b83787962
signaturelock-open Commit is signed but in an unrecognized format.

wasm: implement the 'splat' instruction part 1

This implements `airSplat` for the native WebAssembly backend when the features 'simd128' or 'relaxed-simd' are enabled. The commit supports splat where the value lives in the linear memory segment, as well as on the stack. This saves a lot of instruction cost. When it detects the element type is not 8, 16, 32 or 64 bits, the backend will instead use the same strategy as if the features where disabled.

3 files changed, 65 insertions(+), 5 deletions(-)

lib/std/wasm.zig+2-2
...@@ -256,13 +256,13 @@ pub const SimdOpcode = enum(u32) {...@@ -256,13 +256,13 @@ pub const SimdOpcode = enum(u32) {
256 v128_const = 0x0C,256 v128_const = 0x0C,
257 i8x16_shuffle = 0x0D,257 i8x16_shuffle = 0x0D,
258 i8x16_swizzle = 0x0E,258 i8x16_swizzle = 0x0E,
259 @"8x16_splat" = 0x0F,259 i8x16_splat = 0x0F,
260 i16x8_splat = 0x10,260 i16x8_splat = 0x10,
261 i32x4_splat = 0x11,261 i32x4_splat = 0x11,
262 i64x2_splat = 0x12,262 i64x2_splat = 0x12,
263 f32x4_splat = 0x13,263 f32x4_splat = 0x13,
264 f64x2_splat = 0x14,264 f64x2_splat = 0x14,
265 @"8x16_extract_lane_s" = 0x15,265 i8x16_extract_lane_s = 0x15,
266 i8x16_extract_lane_u = 0x16,266 i8x16_extract_lane_u = 0x16,
267 i8x16_replace_lane = 0x17,267 i8x16_replace_lane = 0x17,
268 i16x8_extract_lane_s = 0x18,268 i16x8_extract_lane_s = 0x18,
src/arch/wasm/CodeGen.zig+49-2
...@@ -4430,9 +4430,56 @@ fn airIntToFloat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4430,9 +4430,56 @@ fn airIntToFloat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4430fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {4430fn airSplat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4431 const ty_op = func.air.instructions.items(.data)[inst].ty_op;4431 const ty_op = func.air.instructions.items(.data)[inst].ty_op;
4432 const operand = try func.resolveInst(ty_op.operand);4432 const operand = try func.resolveInst(ty_op.operand);
4433 const ty = func.air.typeOfIndex(inst);
4434 const elem_ty = ty.childType();
44334435
4434 _ = operand;4436 const result = try func.allocLocal(ty);
4435 return func.fail("TODO: Implement wasm airSplat", .{});4437 if (determineSimdStoreStrategy(ty, func.target) == .direct) blk: {
4438 switch (operand) {
4439 // when the operand lives in the linear memory section, we can directly
4440 // load and splat the value at once. Meaning we do not first have to load
4441 // the scalar value onto the stack.
4442 .stack_offset, .memory, .memory_offset => {
4443 const opcode = switch (elem_ty.bitSize(func.target)) {
4444 8 => std.wasm.simdOpcode(.v128_load8_splat),
4445 16 => std.wasm.simdOpcode(.v128_load16_splat),
4446 32 => std.wasm.simdOpcode(.v128_load32_splat),
4447 64 => std.wasm.simdOpcode(.v128_load64_splat),
4448 else => break :blk, // Cannot make use of simd-instructions
4449 };
4450 try func.emitWValue(operand);
4451 // TODO: Add helper functions for simd opcodes
4452 const extra_index = @intCast(u32, func.mir_extra.items.len);
4453 // stores as := opcode, offset, alignment (opcode::memarg)
4454 try func.mir_extra.appendSlice(func.gpa, &[_]u32{
4455 opcode,
4456 operand.offset(),
4457 elem_ty.abiAlignment(func.target),
4458 });
4459 try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
4460 try func.addLabel(.local_set, result.local.value);
4461 return func.finishAir(inst, result, &.{ty_op.operand});
4462 },
4463 .local => {
4464 const opcode = switch (elem_ty.bitSize(func.target)) {
4465 8 => std.wasm.simdOpcode(.i8x16_splat),
4466 16 => std.wasm.simdOpcode(.i16x8_splat),
4467 32 => if (elem_ty.isInt()) std.wasm.simdOpcode(.i32x4_splat) else std.wasm.simdOpcode(.f32x4_splat),
4468 64 => if (elem_ty.isInt()) std.wasm.simdOpcode(.i64x2_splat) else std.wasm.simdOpcode(.f64x2_splat),
4469 else => break :blk, // Cannot make use of simd-instructions
4470 };
4471 try func.emitWValue(operand);
4472 const extra_index = @intCast(u32, func.mir_extra.items.len);
4473 try func.mir_extra.append(func.gpa, opcode);
4474 try func.addInst(.{ .tag = .simd, .data = .{ .payload = extra_index } });
4475 try func.addLabel(.local_set, result.local.value);
4476 return func.finishAir(inst, result, &.{ty_op.operand});
4477 },
4478 else => unreachable,
4479 }
4480 }
4481
4482 return func.fail("TODO: Implement wasm airSplat unrolled", .{});
4436}4483}
44374484
4438fn airSelect(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {4485fn airSelect(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
src/arch/wasm/Emit.zig+14-1
...@@ -437,7 +437,13 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {...@@ -437,7 +437,13 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {
437 try emit.code.append(0xFD);437 try emit.code.append(0xFD);
438 try leb128.writeULEB128(writer, opcode);438 try leb128.writeULEB128(writer, opcode);
439 switch (@intToEnum(std.wasm.SimdOpcode, opcode)) {439 switch (@intToEnum(std.wasm.SimdOpcode, opcode)) {
440 .v128_store, .v128_load => {440 .v128_store,
441 .v128_load,
442 .v128_load8_splat,
443 .v128_load16_splat,
444 .v128_load32_splat,
445 .v128_load64_splat,
446 => {
441 const mem_arg = emit.mir.extraData(Mir.MemArg, extra_index + 1).data;447 const mem_arg = emit.mir.extraData(Mir.MemArg, extra_index + 1).data;
442 try encodeMemArg(mem_arg, writer);448 try encodeMemArg(mem_arg, writer);
443 },449 },
...@@ -445,6 +451,13 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {...@@ -445,6 +451,13 @@ fn emitSimd(emit: *Emit, inst: Mir.Inst.Index) !void {
445 const simd_value = emit.mir.extra[extra_index + 1 ..][0..4];451 const simd_value = emit.mir.extra[extra_index + 1 ..][0..4];
446 try writer.writeAll(std.mem.asBytes(simd_value));452 try writer.writeAll(std.mem.asBytes(simd_value));
447 },453 },
454 .i8x16_splat,
455 .i16x8_splat,
456 .i32x4_splat,
457 .i64x2_splat,
458 .f32x4_splat,
459 .f64x2_splat,
460 => {}, // opcode already written
448 else => |tag| return emit.fail("TODO: Implement simd instruction: {s}\n", .{@tagName(tag)}),461 else => |tag| return emit.fail("TODO: Implement simd instruction: {s}\n", .{@tagName(tag)}),
449 }462 }
450}463}