Commit 43cb19ea4d

Luuk de Gram <luuk@degram.dev>
2022-03-03 19:10:58
wasm: Implement `@wasmMemoryGrow` builtin
Similarly to the other wasm builtin, this implements the grow variation where the memory index is a comptime known value. The operand as well as the result are runtime values. This also verifies during semantic analysis the target we're building for is wasm, or else emits a compilation error. This means that other backends do not have to handle this AIR instruction, other than the wasm and LLVM backends.
1 parent ec4c30a
src/arch/aarch64/CodeGen.zig
@@ -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
         }
 
src/arch/arm/CodeGen.zig
@@ -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
         }
 
src/arch/riscv64/CodeGen.zig
@@ -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) {
src/arch/wasm/CodeGen.zig
@@ -1672,6 +1672,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
         .wrap_errunion_err => self.airWrapErrUnionErr(inst),
 
         .wasm_memory_size => self.airWasmMemorySize(inst),
+        .wasm_memory_grow => self.airWasmMemoryGrow(inst),
 
         .add_sat,
         .sub_sat,
@@ -3438,6 +3439,18 @@ fn airWasmMemorySize(self: *Self, inst: Air.Inst.Index) !WValue {
     return result;
 }
 
+fn airWasmMemoryGrow(self: *Self, inst: Air.Inst.Index) !WValue {
+    const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+    const extra = self.air.extraData(Air.WasmMemoryIndex, pl_op.payload).data;
+    const operand = try self.resolveInst(pl_op.operand);
+
+    const result = try self.allocLocal(Type.usize);
+    try self.emitWValue(operand);
+    try self.addLabel(.memory_grow, extra.index);
+    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);
src/arch/x86_64/CodeGen.zig
@@ -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
         }
 
src/codegen/c.zig
@@ -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 => unreachable,
+            .wasm_memory_grow => unreachable,
             // zig fmt: on
         };
         switch (result_value) {
src/codegen/llvm.zig
@@ -3478,7 +3478,12 @@ pub const FuncGen = struct {
 
     fn airWasmMemorySize(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
         _ = inst;
-        return self.todo("`@wasmMemorySize()`", .{});
+        return self.todo("implement builtin `@wasmMemorySize()`", .{});
+    }
+
+    fn airWasmMemoryGrow(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        _ = inst;
+        return self.todo("implement builtin `@wasmMemoryGrow()`", .{});
     }
 
     fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
src/Air.zig
@@ -587,6 +587,10 @@ pub const Inst = struct {
         /// Uses the `ty_pl` field, payload is `WasmMemoryIndex`.
         wasm_memory_size,
 
+        /// Implements @wasmMemoryGrow builtin.
+        /// Uses the `pl_op` field, payload is `WasmMemoryIndex`.
+        wasm_memory_grow,
+
         pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
             return switch (op) {
                 .lt => .cmp_lt,
@@ -956,6 +960,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .frame_addr,
         => return Type.initTag(.usize),
 
+        .wasm_memory_grow => return Type.initTag(.i32),
+
         .bool_to_int => return Type.initTag(.u1),
 
         .tag_name, .error_name => return Type.initTag(.const_slice_u8_sentinel_0),
src/AstGen.zig
@@ -7148,7 +7148,7 @@ fn builtinCall(
             return rvalue(gz, rl, result, node);
         },
         .wasm_memory_grow => {
-            const index_arg = try expr(gz, scope, .{ .ty = .u32_type }, params[0]);
+            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 result = try gz.addExtendedPayload(.wasm_memory_grow, Zir.Inst.BinNode{
                 .node = gz.nodeIndexToRelative(node),
src/Liveness.zig
@@ -707,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 });
+        },
     }
 }
 
src/print_air.zig
@@ -251,6 +251,7 @@ const Writer = struct {
             .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,
@@ -625,10 +626,18 @@ const Writer = struct {
     }
 
     fn writeWasmMemorySize(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
-        const pl_op = w.air.instructions.items(.data)[inst].ty_pl;
+        const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
+        const extra = w.air.extraData(Air.WasmMemoryIndex, ty_pl.payload).data;
+
+        try s.print("{d}", .{extra.index});
+    }
+
+    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;
         const extra = w.air.extraData(Air.WasmMemoryIndex, pl_op.payload).data;
 
-        try s.print(", {d}", .{extra.index});
+        try s.print("{d}, ", .{extra.index});
+        try w.writeOperand(s, inst, 0, pl_op.operand);
     }
 
     fn writeOperand(
src/Sema.zig
@@ -14034,6 +14034,9 @@ fn zirWasmMemorySize(
 ) 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()) {
+        return sema.fail(block, src, "builtin '@wasmMemorySize' is a wasm feature only", .{});
+    }
 
     const operand = try sema.resolveInt(block, src, extra.operand, Type.u32);
     const index = @intCast(u32, operand);
@@ -14054,7 +14057,22 @@ fn zirWasmMemoryGrow(
 ) 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", .{});
+    if (!sema.mod.getTarget().isWasm()) {
+        return sema.fail(block, src, "builtin '@wasmMemoryGrow' is a wasm feature only", .{});
+    }
+
+    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);
+
+    try sema.requireRuntimeBlock(block, src);
+    return block.addInst(.{
+        .tag = .wasm_memory_grow,
+        .data = .{ .pl_op = .{
+            .operand = delta_arg,
+            .payload = try sema.addExtra(Air.WasmMemoryIndex{ .index = index }),
+        } },
+    });
 }
 
 fn zirPrefetch(