Commit e532b0c0b5

Andrew Kelley <andrew@ziglang.org>
2022-03-04 02:31:55
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.
1 parent 7fd32de
src/arch/wasm/CodeGen.zig
@@ -3430,10 +3430,12 @@ fn airPrefetch(self: *Self, inst: Air.Inst.Index) InnerError!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));
-    try self.addLabel(.memory_size, ty_pl.payload);
+    try self.addLabel(.memory_size, pl_op.payload);
     try self.addLabel(.local_set, result.local);
     return result;
 }
src/codegen/c.zig
@@ -3592,14 +3592,16 @@ fn airPrefetch(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 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", .{ty_pl.payload});
+    try writer.print("zig_wasm_memory_size({d});\n", .{pl_op.payload});
 
     return local;
 }
src/codegen/llvm.zig
@@ -3478,13 +3478,27 @@ pub const FuncGen = struct {
     }
 
     fn airWasmMemorySize(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
-        _ = inst;
-        return self.todo("implement builtin `@wasmMemorySize()`", .{});
+        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 {
-        _ = inst;
-        return self.todo("implement builtin `@wasmMemoryGrow()`", .{});
+        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 {
src/link/C/zig.h
@@ -91,14 +91,10 @@
 
 #if defined(__clang__)
 #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)
 #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
 
 #if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
src/Air.zig
@@ -584,10 +584,13 @@ pub const Inst = struct {
         field_parent_ptr,
 
         /// 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,
 
         /// Implements @wasmMemoryGrow builtin.
+        /// Result type is always `i32`,
         /// Uses the `pl_op` field, payload represents the index of the target memory.
         wasm_memory_grow,
 
@@ -626,6 +629,7 @@ pub const Inst = struct {
     pub const Data = union {
         no_op: void,
         un_op: Ref,
+
         bin_op: struct {
             lhs: Ref,
             rhs: Ref,
@@ -885,7 +889,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .aggregate_init,
         .union_init,
         .field_parent_ptr,
-        .wasm_memory_size,
         => return air.getRefType(datas[inst].ty_pl.ty),
 
         .not,
@@ -954,7 +957,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .frame_addr,
         => 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),
 
src/AstGen.zig
@@ -7140,7 +7140,7 @@ fn builtinCall(
         // zig fmt: on
 
         .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{
                 .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 comptimeExpr(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,
src/print_air.zig
@@ -626,8 +626,8 @@ const Writer = struct {
     }
 
     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;
-        try s.print("{d}", .{ty_pl.payload});
+        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 {
src/Sema.zig
@@ -14033,18 +14033,19 @@ 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 };
-    if (!sema.mod.getTarget().isWasm() and sema.mod.comp.bin_file.options.object_format != .c) {
-        return sema.fail(block, src, "builtin '@wasmMemorySize' is a wasm feature only", .{});
+    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 operand = try sema.resolveInt(block, src, extra.operand, Type.u32);
-    const index = @intCast(u32, operand);
-    try sema.requireRuntimeBlock(block, src);
+    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 = .{ .ty_pl = .{
-            .ty = try sema.addType(Type.u32),
+        .data = .{ .pl_op = .{
+            .operand = .none,
             .payload = index,
         } },
     });
@@ -14056,20 +14057,22 @@ 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 };
-    if (!sema.mod.getTarget().isWasm() and sema.mod.comp.bin_file.options.object_format != .c) {
-        return sema.fail(block, src, "builtin '@wasmMemoryGrow' is a wasm feature only", .{});
+    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_arg = try sema.resolveInt(block, src, extra.lhs, Type.u32);
-    const index = @intCast(u32, index_arg);
-    const delta_arg = sema.resolveInst(extra.rhs);
+    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, src);
+    try sema.requireRuntimeBlock(block, builtin_src);
     return block.addInst(.{
         .tag = .wasm_memory_grow,
         .data = .{ .pl_op = .{
-            .operand = delta_arg,
+            .operand = delta,
             .payload = index,
         } },
     });
src/type.zig
@@ -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);
test/behavior/wasm.zig
@@ -3,8 +3,6 @@ const expect = std.testing.expect;
 const builtin = @import("builtin");
 
 test "memory size and grow" {
-    if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
-
     var prev = @wasmMemorySize(0);
     try expect(prev == @wasmMemoryGrow(0, 1));
     try expect(prev + 1 == @wasmMemorySize(0));
test/behavior.zig
@@ -96,7 +96,7 @@ test {
     _ = @import("behavior/void.zig");
     _ = @import("behavior/while.zig");
 
-    if (builtin.zig_backend == .stage2_wasm) {
+    if (builtin.stage2_arch == .wasm32) {
         _ = @import("behavior/wasm.zig");
     }
 
@@ -172,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");
-                }
             }
         }
     }