stage2: cleanups to wasm memory intrinsics

* AIR: use pl_op instead of ty_pl for wasm_memory_size. No need to
   store the type because the type is always `u32`.
 * AstGen: use coerced_ty for `@wasmMemorySize` and `@wasmMemoryGrow`
   and do the coercions in Sema.
 * Sema: use more accurate source locations for errors.
 * Provide more information in the compiler error message.
 * Codegen: use liveness data to avoid lowering unused
   `@wasmMemorySize`.
 * LLVM backend: add implementation
   - I wasn't able to test it because we are hitting a linker error for
     `-target wasm32-wasi -fLLVM`.
 * C backend: use `zig_unimplemented()` instead of silently doing wrong
   behavior for these builtins.
 * behavior tests: branch only on stage2_arch for inclusion of the
   wasm.zig file. We would change it to `builtin.cpu.arch` but that is
   causing a compiler crash on some backends.
This commit is contained in:
Andrew Kelley 2022-03-03 18:31:55 -07:00
parent 7fd32de018
commit e532b0c0b5
11 changed files with 62 additions and 44 deletions

View file

@ -584,10 +584,13 @@ pub const Inst = struct {
field_parent_ptr, field_parent_ptr,
/// Implements @wasmMemorySize builtin. /// Implements @wasmMemorySize builtin.
/// Uses the `ty_pl` field, payload represents the index of the target memory. /// 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, wasm_memory_size,
/// Implements @wasmMemoryGrow builtin. /// Implements @wasmMemoryGrow builtin.
/// Result type is always `i32`,
/// Uses the `pl_op` field, payload represents the index of the target memory. /// Uses the `pl_op` field, payload represents the index of the target memory.
wasm_memory_grow, wasm_memory_grow,
@ -626,6 +629,7 @@ pub const Inst = struct {
pub const Data = union { pub const Data = union {
no_op: void, no_op: void,
un_op: Ref, un_op: Ref,
bin_op: struct { bin_op: struct {
lhs: Ref, lhs: Ref,
rhs: Ref, rhs: Ref,
@ -885,7 +889,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.aggregate_init, .aggregate_init,
.union_init, .union_init,
.field_parent_ptr, .field_parent_ptr,
.wasm_memory_size,
=> return air.getRefType(datas[inst].ty_pl.ty), => return air.getRefType(datas[inst].ty_pl.ty),
.not, .not,
@ -954,7 +957,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.frame_addr, .frame_addr,
=> return Type.initTag(.usize), => return Type.initTag(.usize),
.wasm_memory_grow => return Type.initTag(.i32), .wasm_memory_grow => return Type.i32,
.wasm_memory_size => return Type.u32,
.bool_to_int => return Type.initTag(.u1), .bool_to_int => return Type.initTag(.u1),

View file

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

View file

@ -14033,18 +14033,19 @@ fn zirWasmMemorySize(
extended: Zir.Inst.Extended.InstData, extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref { ) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src: LazySrcLoc = .{ .node_offset = extra.node }; const index_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
if (!sema.mod.getTarget().isWasm() and sema.mod.comp.bin_file.options.object_format != .c) { const builtin_src: LazySrcLoc = .{ .node_offset = extra.node };
return sema.fail(block, src, "builtin '@wasmMemorySize' is a wasm feature only", .{}); 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 operand = try sema.resolveInt(block, src, extra.operand, Type.u32); const index = @intCast(u32, try sema.resolveInt(block, index_src, extra.operand, Type.u32));
const index = @intCast(u32, operand); try sema.requireRuntimeBlock(block, builtin_src);
try sema.requireRuntimeBlock(block, src);
return block.addInst(.{ return block.addInst(.{
.tag = .wasm_memory_size, .tag = .wasm_memory_size,
.data = .{ .ty_pl = .{ .data = .{ .pl_op = .{
.ty = try sema.addType(Type.u32), .operand = .none,
.payload = index, .payload = index,
} }, } },
}); });
@ -14056,20 +14057,22 @@ fn zirWasmMemoryGrow(
extended: Zir.Inst.Extended.InstData, extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref { ) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data; const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src: LazySrcLoc = .{ .node_offset = extra.node }; const builtin_src: LazySrcLoc = .{ .node_offset = extra.node };
if (!sema.mod.getTarget().isWasm() and sema.mod.comp.bin_file.options.object_format != .c) { const index_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
return sema.fail(block, src, "builtin '@wasmMemoryGrow' is a wasm feature only", .{}); 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_arg = try sema.resolveInt(block, src, extra.lhs, Type.u32); const index = @intCast(u32, try sema.resolveInt(block, index_src, extra.lhs, Type.u32));
const index = @intCast(u32, index_arg); const delta = try sema.coerce(block, Type.u32, sema.resolveInst(extra.rhs), delta_src);
const delta_arg = sema.resolveInst(extra.rhs);
try sema.requireRuntimeBlock(block, src); try sema.requireRuntimeBlock(block, builtin_src);
return block.addInst(.{ return block.addInst(.{
.tag = .wasm_memory_grow, .tag = .wasm_memory_grow,
.data = .{ .pl_op = .{ .data = .{ .pl_op = .{
.operand = delta_arg, .operand = delta,
.payload = index, .payload = index,
} }, } },
}); });

View file

@ -3430,10 +3430,12 @@ fn airPrefetch(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
} }
fn airWasmMemorySize(self: *Self, inst: Air.Inst.Index) !WValue { fn airWasmMemorySize(self: *Self, inst: Air.Inst.Index) !WValue {
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; 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)); const result = try self.allocLocal(self.air.typeOfIndex(inst));
try self.addLabel(.memory_size, ty_pl.payload); try self.addLabel(.memory_size, pl_op.payload);
try self.addLabel(.local_set, result.local); try self.addLabel(.local_set, result.local);
return result; return result;
} }

View file

@ -3592,14 +3592,16 @@ fn airPrefetch(f: *Function, inst: Air.Inst.Index) !CValue {
} }
fn airWasmMemorySize(f: *Function, inst: Air.Inst.Index) !CValue { fn airWasmMemorySize(f: *Function, inst: Air.Inst.Index) !CValue {
const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; 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 writer = f.object.writer();
const inst_ty = f.air.typeOfIndex(inst); const inst_ty = f.air.typeOfIndex(inst);
const local = try f.allocLocal(inst_ty, .Const); const local = try f.allocLocal(inst_ty, .Const);
try writer.writeAll(" = "); try writer.writeAll(" = ");
try writer.print("zig_wasm_memory_size({d});\n", .{ty_pl.payload}); try writer.print("zig_wasm_memory_size({d});\n", .{pl_op.payload});
return local; return local;
} }

View file

@ -3478,13 +3478,27 @@ pub const FuncGen = struct {
} }
fn airWasmMemorySize(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { fn airWasmMemorySize(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
_ = inst; if (self.liveness.isUnused(inst)) return null;
return self.todo("implement builtin `@wasmMemorySize()`", .{});
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 { fn airWasmMemoryGrow(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
_ = inst; const pl_op = self.air.instructions.items(.data)[inst].pl_op;
return self.todo("implement builtin `@wasmMemoryGrow()`", .{}); 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 { fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {

View file

@ -91,14 +91,10 @@
#if defined(__clang__) #if defined(__clang__)
#define zig_wasm_memory_size(index) __builtin_wasm_memory_size(index) #define zig_wasm_memory_size(index) __builtin_wasm_memory_size(index)
#else
#define zig_wasm_memory_size(index) 0
#endif
#if defined(__clang__)
#define zig_wasm_memory_grow(index, delta) __builtin_wasm_memory_grow(index, delta) #define zig_wasm_memory_grow(index, delta) __builtin_wasm_memory_grow(index, delta)
#else #else
#define zig_wasm_memory_grow(index, delta) 0 #define zig_wasm_memory_size(index) zig_unimplemented()
#define zig_wasm_memory_grow(index, delta) zig_unimplemented()
#endif #endif
#if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) #if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)

View file

@ -626,8 +626,8 @@ const Writer = struct {
} }
fn writeWasmMemorySize(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { fn writeWasmMemorySize(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const pl_op = w.air.instructions.items(.data)[inst].pl_op;
try s.print("{d}", .{ty_pl.payload}); try s.print("{d}", .{pl_op.payload});
} }
fn writeWasmMemoryGrow(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { fn writeWasmMemoryGrow(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {

View file

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

View file

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

View file

@ -3,8 +3,6 @@ const expect = std.testing.expect;
const builtin = @import("builtin"); const builtin = @import("builtin");
test "memory size and grow" { test "memory size and grow" {
if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
var prev = @wasmMemorySize(0); var prev = @wasmMemorySize(0);
try expect(prev == @wasmMemoryGrow(0, 1)); try expect(prev == @wasmMemoryGrow(0, 1));
try expect(prev + 1 == @wasmMemorySize(0)); try expect(prev + 1 == @wasmMemorySize(0));