Merge pull request #11048 from Luukdegram/wasm-builtins

stage2: Implement wasm builtins
This commit is contained in:
Andrew Kelley 2022-03-03 20:39:10 -05:00 committed by GitHub
commit c9ee3c1e47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 186 additions and 13 deletions

View File

@ -583,6 +583,17 @@ pub const Inst = struct {
/// Uses the `ty_pl` field.
field_parent_ptr,
/// Implements @wasmMemorySize builtin.
/// Result type is always `u32`,
/// Uses the `pl_op` field, payload represents the index of the target memory.
/// The operand is unused and always set to `Ref.none`.
wasm_memory_size,
/// Implements @wasmMemoryGrow builtin.
/// Result type is always `i32`,
/// Uses the `pl_op` field, payload represents the index of the target memory.
wasm_memory_grow,
pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
return switch (op) {
.lt => .cmp_lt,
@ -618,6 +629,7 @@ pub const Inst = struct {
pub const Data = union {
no_op: void,
un_op: Ref,
bin_op: struct {
lhs: Ref,
rhs: Ref,
@ -945,6 +957,9 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.frame_addr,
=> return Type.initTag(.usize),
.wasm_memory_grow => return Type.i32,
.wasm_memory_size => return Type.u32,
.bool_to_int => return Type.initTag(.u1),
.tag_name, .error_name => return Type.initTag(.const_slice_u8_sentinel_0),

View File

@ -7140,7 +7140,7 @@ fn builtinCall(
// zig fmt: on
.wasm_memory_size => {
const operand = try expr(gz, scope, .{ .ty = .u32_type }, params[0]);
const operand = try comptimeExpr(gz, scope, .{ .coerced_ty = .u32_type }, params[0]);
const result = try gz.addExtendedPayload(.wasm_memory_size, Zir.Inst.UnNode{
.node = gz.nodeIndexToRelative(node),
.operand = operand,
@ -7148,8 +7148,8 @@ fn builtinCall(
return rvalue(gz, rl, result, node);
},
.wasm_memory_grow => {
const index_arg = try expr(gz, scope, .{ .ty = .u32_type }, params[0]);
const delta_arg = try expr(gz, scope, .{ .ty = .u32_type }, params[1]);
const index_arg = try comptimeExpr(gz, scope, .{ .coerced_ty = .u32_type }, params[0]);
const delta_arg = try expr(gz, scope, .{ .coerced_ty = .u32_type }, params[1]);
const result = try gz.addExtendedPayload(.wasm_memory_grow, Zir.Inst.BinNode{
.node = gz.nodeIndexToRelative(node),
.lhs = index_arg,

View File

@ -318,6 +318,7 @@ fn analyzeInst(
.fence,
.ret_addr,
.frame_addr,
.wasm_memory_size,
=> return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
.not,
@ -706,6 +707,10 @@ fn analyzeInst(
return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });
},
.wasm_memory_grow => {
const pl_op = inst_datas[inst].pl_op;
return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none });
},
}
}

View File

@ -14033,8 +14033,22 @@ fn zirWasmMemorySize(
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src: LazySrcLoc = .{ .node_offset = extra.node };
return sema.fail(block, src, "TODO: implement Sema.zirWasmMemorySize", .{});
const index_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
const builtin_src: LazySrcLoc = .{ .node_offset = extra.node };
const target = sema.mod.getTarget();
if (!target.isWasm()) {
return sema.fail(block, builtin_src, "builtin @wasmMemorySize is available when targeting WebAssembly; targeted CPU architecture is {s}", .{@tagName(target.cpu.arch)});
}
const index = @intCast(u32, try sema.resolveInt(block, index_src, extra.operand, Type.u32));
try sema.requireRuntimeBlock(block, builtin_src);
return block.addInst(.{
.tag = .wasm_memory_size,
.data = .{ .pl_op = .{
.operand = .none,
.payload = index,
} },
});
}
fn zirWasmMemoryGrow(
@ -14043,8 +14057,25 @@ fn zirWasmMemoryGrow(
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src: LazySrcLoc = .{ .node_offset = extra.node };
return sema.fail(block, src, "TODO: implement Sema.zirWasmMemoryGrow", .{});
const builtin_src: LazySrcLoc = .{ .node_offset = extra.node };
const index_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
const delta_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
const target = sema.mod.getTarget();
if (!target.isWasm()) {
return sema.fail(block, builtin_src, "builtin @wasmMemoryGrow is available when targeting WebAssembly; targeted CPU architecture is {s}", .{@tagName(target.cpu.arch)});
}
const index = @intCast(u32, try sema.resolveInt(block, index_src, extra.lhs, Type.u32));
const delta = try sema.coerce(block, Type.u32, sema.resolveInst(extra.rhs), delta_src);
try sema.requireRuntimeBlock(block, builtin_src);
return block.addInst(.{
.tag = .wasm_memory_grow,
.data = .{ .pl_op = .{
.operand = delta,
.payload = index,
} },
});
}
fn zirPrefetch(

View File

@ -671,6 +671,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
.wrap_errunion_err => try self.airWrapErrUnionErr(inst),
.wasm_memory_size => unreachable,
.wasm_memory_grow => unreachable,
// zig fmt: on
}

View File

@ -669,6 +669,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
.wrap_errunion_err => try self.airWrapErrUnionErr(inst),
.wasm_memory_size => unreachable,
.wasm_memory_grow => unreachable,
// zig fmt: on
}

View File

@ -642,6 +642,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
.wrap_errunion_err => try self.airWrapErrUnionErr(inst),
.wasm_memory_size => unreachable,
.wasm_memory_grow => unreachable,
// zig fmt: on
}
if (std.debug.runtime_safety) {

View File

@ -1671,6 +1671,9 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
.wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
.wrap_errunion_err => self.airWrapErrUnionErr(inst),
.wasm_memory_size => self.airWasmMemorySize(inst),
.wasm_memory_grow => self.airWasmMemoryGrow(inst),
.add_sat,
.sub_sat,
.mul_sat,
@ -3426,6 +3429,28 @@ fn airPrefetch(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
return WValue{ .none = {} };
}
fn airWasmMemorySize(self: *Self, inst: Air.Inst.Index) !WValue {
if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
const result = try self.allocLocal(self.air.typeOfIndex(inst));
try self.addLabel(.memory_size, pl_op.payload);
try self.addLabel(.local_set, result.local);
return result;
}
fn airWasmMemoryGrow(self: *Self, inst: Air.Inst.Index) !WValue {
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
const operand = try self.resolveInst(pl_op.operand);
const result = try self.allocLocal(self.air.typeOfIndex(inst));
try self.emitWValue(operand);
try self.addLabel(.memory_grow, pl_op.payload);
try self.addLabel(.local_set, result.local);
return result;
}
fn cmpOptionals(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.math.CompareOperator) InnerError!WValue {
assert(operand_ty.hasRuntimeBits());
assert(op == .eq or op == .neq);

View File

@ -89,10 +89,10 @@ pub fn emitMir(emit: *Emit) InnerError!void {
.local_set => try emit.emitLabel(tag, inst),
.local_tee => try emit.emitLabel(tag, inst),
.memory_grow => try emit.emitLabel(tag, inst),
.memory_size => try emit.emitLabel(tag, inst),
// no-ops
.end => try emit.emitTag(tag),
.memory_size => try emit.emitTag(tag),
.@"return" => try emit.emitTag(tag),
.@"unreachable" => try emit.emitTag(tag),

View File

@ -226,9 +226,9 @@ pub const Inst = struct {
i64_store32 = 0x3E,
/// Returns the memory size in amount of pages.
///
/// Uses `nop`
/// Uses `label`
memory_size = 0x3F,
/// Increases the memory at by given number of pages.
/// Increases the memory by given number of pages.
///
/// Uses `label`
memory_grow = 0x40,

View File

@ -759,6 +759,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
.wrap_errunion_err => try self.airWrapErrUnionErr(inst),
.wasm_memory_size => unreachable,
.wasm_memory_grow => unreachable,
// zig fmt: on
}

View File

@ -1758,6 +1758,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.wrap_errunion_payload => try airWrapErrUnionPay(f, inst),
.wrap_errunion_err => try airWrapErrUnionErr(f, inst),
.errunion_payload_ptr_set => try airErrUnionPayloadPtrSet(f, inst),
.wasm_memory_size => try airWasmMemorySize(f, inst),
.wasm_memory_grow => try airWasmMemoryGrow(f, inst),
// zig fmt: on
};
switch (result_value) {
@ -3588,6 +3591,36 @@ fn airPrefetch(f: *Function, inst: Air.Inst.Index) !CValue {
return CValue.none;
}
fn airWasmMemorySize(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst)) return CValue.none;
const pl_op = f.air.instructions.items(.data)[inst].pl_op;
const writer = f.object.writer();
const inst_ty = f.air.typeOfIndex(inst);
const local = try f.allocLocal(inst_ty, .Const);
try writer.writeAll(" = ");
try writer.print("zig_wasm_memory_size({d});\n", .{pl_op.payload});
return local;
}
fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue {
const pl_op = f.air.instructions.items(.data)[inst].pl_op;
const writer = f.object.writer();
const inst_ty = f.air.typeOfIndex(inst);
const operand = try f.resolveInst(pl_op.operand);
const local = try f.allocLocal(inst_ty, .Const);
try writer.writeAll(" = ");
try writer.print("zig_wasm_memory_grow({d}, ", .{pl_op.payload});
try f.writeCValue(writer, operand);
try writer.writeAll(");\n");
return local;
}
fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
return switch (order) {
.Unordered => "memory_order_relaxed",

View File

@ -2314,6 +2314,9 @@ pub const FuncGen = struct {
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
.wrap_errunion_err => try self.airWrapErrUnionErr(inst),
.wasm_memory_size => try self.airWasmMemorySize(inst),
.wasm_memory_grow => try self.airWasmMemoryGrow(inst),
.constant => unreachable,
.const_ty => unreachable,
.unreach => self.airUnreach(inst),
@ -3474,6 +3477,30 @@ pub const FuncGen = struct {
return partial;
}
fn airWasmMemorySize(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
const index = pl_op.payload;
const llvm_u32 = self.context.intType(32);
const llvm_fn = self.getIntrinsic("llvm.wasm.memory.size.i32", &.{llvm_u32});
const args: [1]*const llvm.Value = .{llvm_u32.constInt(index, .False)};
return self.builder.buildCall(llvm_fn, &args, args.len, .Fast, .Auto, "");
}
fn airWasmMemoryGrow(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
const index = pl_op.payload;
const operand = try self.resolveInst(pl_op.operand);
const llvm_u32 = self.context.intType(32);
const llvm_fn = self.getIntrinsic("llvm.wasm.memory.grow.i32", &.{ llvm_u32, llvm_u32 });
const args: [2]*const llvm.Value = .{
llvm_u32.constInt(index, .False),
operand,
};
return self.builder.buildCall(llvm_fn, &args, args.len, .Fast, .Auto, "");
}
fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;

View File

@ -89,6 +89,14 @@
#define zig_prefetch(addr, rw, locality)
#endif
#if defined(__clang__)
#define zig_wasm_memory_size(index) __builtin_wasm_memory_size(index)
#define zig_wasm_memory_grow(index, delta) __builtin_wasm_memory_grow(index, delta)
#else
#define zig_wasm_memory_size(index) zig_unimplemented()
#define zig_wasm_memory_grow(index, delta) zig_unimplemented()
#endif
#if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
#include <stdatomic.h>
#define zig_cmpxchg_strong(obj, expected, desired, succ, fail) atomic_compare_exchange_strong_explicit(obj, &(expected), desired, succ, fail)

View File

@ -250,6 +250,8 @@ const Writer = struct {
.memcpy => try w.writeMemcpy(s, inst),
.memset => try w.writeMemset(s, inst),
.field_parent_ptr => try w.writeFieldParentPtr(s, inst),
.wasm_memory_size => try w.writeWasmMemorySize(s, inst),
.wasm_memory_grow => try w.writeWasmMemoryGrow(s, inst),
.add_with_overflow,
.sub_with_overflow,
@ -623,6 +625,17 @@ const Writer = struct {
try s.writeAll("}");
}
fn writeWasmMemorySize(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
const pl_op = w.air.instructions.items(.data)[inst].pl_op;
try s.print("{d}", .{pl_op.payload});
}
fn writeWasmMemoryGrow(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
const pl_op = w.air.instructions.items(.data)[inst].pl_op;
try s.print("{d}, ", .{pl_op.payload});
try w.writeOperand(s, inst, 0, pl_op.operand);
}
fn writeOperand(
w: *Writer,
s: anytype,

View File

@ -5256,6 +5256,8 @@ pub const Type = extern union {
pub const @"u32" = initTag(.u32);
pub const @"u64" = initTag(.u64);
pub const @"i32" = initTag(.i32);
pub const @"f16" = initTag(.f16);
pub const @"f32" = initTag(.f32);
pub const @"f64" = initTag(.f64);

View File

@ -96,6 +96,10 @@ test {
_ = @import("behavior/void.zig");
_ = @import("behavior/while.zig");
if (builtin.stage2_arch == .wasm32) {
_ = @import("behavior/wasm.zig");
}
if (builtin.zig_backend != .stage1) {
_ = @import("behavior/decltest.zig");
}
@ -168,9 +172,6 @@ test {
_ = @import("behavior/struct_contains_slice_of_itself.zig");
_ = @import("behavior/typename.zig");
_ = @import("behavior/vector.zig");
if (builtin.target.cpu.arch == .wasm32) {
_ = @import("behavior/wasm.zig");
}
}
}
}

View File

@ -1,5 +1,6 @@
const std = @import("std");
const expect = std.testing.expect;
const builtin = @import("builtin");
test "memory size and grow" {
var prev = @wasmMemorySize(0);