Commit 17e6e09285

Veikka Tuominen <git@vexu.eu>
2021-01-28 21:38:25
stage2: astgen async
1 parent 9f722f4
src/astgen.zig
@@ -626,10 +626,13 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
         .@"comptime" => return comptimeExpr(mod, scope, rl, node_datas[node].lhs),
         .@"switch", .switch_comma => return switchExpr(mod, scope, rl, node),
 
+        .@"nosuspend" => return nosuspendExpr(mod, scope, rl, node),
+        .@"suspend" => return rvalue(mod, scope, rl, try suspendExpr(mod, scope, node)),
+        .@"await" => return awaitExpr(mod, scope, rl, node),
+        .@"resume" => return rvalue(mod, scope, rl, try resumeExpr(mod, scope, node)),
+
         .@"defer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .defer", .{}),
         .@"errdefer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .errdefer", .{}),
-        .@"await" => return mod.failNode(scope, node, "TODO implement astgen.expr for .await", .{}),
-        .@"resume" => return mod.failNode(scope, node, "TODO implement astgen.expr for .resume", .{}),
         .@"try" => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}),
 
         .array_init_one,
@@ -652,15 +655,12 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
         .struct_init_comma,
         => return mod.failNode(scope, node, "TODO implement astgen.expr for struct literals", .{}),
 
-        .@"suspend" => return mod.failNode(scope, node, "TODO implement astgen.expr for .suspend", .{}),
         .@"anytype" => return mod.failNode(scope, node, "TODO implement astgen.expr for .anytype", .{}),
         .fn_proto_simple,
         .fn_proto_multi,
         .fn_proto_one,
         .fn_proto,
         => return mod.failNode(scope, node, "TODO implement astgen.expr for function prototypes", .{}),
-
-        .@"nosuspend" => return mod.failNode(scope, node, "TODO implement astgen.expr for .nosuspend", .{}),
     }
 }
 
@@ -766,6 +766,8 @@ fn breakExpr(
             },
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+            .gen_suspend => scope = scope.cast(Scope.GenZIR).?.parent,
+            .gen_nosuspend => scope = scope.cast(Scope.Nosuspend).?.parent,
             else => if (break_label != 0) {
                 const label_name = try mod.identifierTokenString(parent_scope, break_label);
                 return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
@@ -819,6 +821,8 @@ fn continueExpr(
             },
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+            .gen_suspend => scope = scope.cast(Scope.GenZIR).?.parent,
+            .gen_nosuspend => scope = scope.cast(Scope.Nosuspend).?.parent,
             else => if (break_label != 0) {
                 const label_name = try mod.identifierTokenString(parent_scope, break_label);
                 return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
@@ -893,6 +897,8 @@ fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIn
             },
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+            .gen_suspend => scope = scope.cast(Scope.GenZIR).?.parent,
+            .gen_nosuspend => scope = scope.cast(Scope.Nosuspend).?.parent,
             else => return,
         }
     }
@@ -1100,6 +1106,8 @@ fn varDecl(
                 s = local_ptr.parent;
             },
             .gen_zir => s = s.cast(Scope.GenZIR).?.parent,
+            .gen_suspend => s = s.cast(Scope.GenZIR).?.parent,
+            .gen_nosuspend => s = s.cast(Scope.Nosuspend).?.parent,
             else => break,
         };
     }
@@ -3021,6 +3029,8 @@ fn identifier(
                 s = local_ptr.parent;
             },
             .gen_zir => s = s.cast(Scope.GenZIR).?.parent,
+            .gen_suspend => s = s.cast(Scope.GenZIR).?.parent,
+            .gen_nosuspend => s = s.cast(Scope.Nosuspend).?.parent,
             else => break,
         };
     }
@@ -3633,14 +3643,109 @@ fn callExpr(
     }
 
     const src = token_starts[call.ast.lparen];
+    var modifier: std.builtin.CallOptions.Modifier = .auto;
+    if (call.async_token) |_| modifier = .async_kw;
+
     const result = try addZIRInst(mod, scope, src, zir.Inst.Call, .{
         .func = lhs,
         .args = args,
+        .modifier = modifier,
     }, .{});
     // TODO function call with result location
     return rvalue(mod, scope, rl, result);
 }
 
+fn suspendExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
+    const tree = scope.tree();
+    const src = tree.tokens.items(.start)[tree.nodes.items(.main_token)[node]];
+
+    if (scope.getNosuspend()) |some| {
+        const msg = msg: {
+            const msg = try mod.errMsg(scope, src, "suspend in nosuspend block", .{});
+            errdefer msg.destroy(mod.gpa);
+            try mod.errNote(scope, some.src, msg, "nosuspend block here", .{});
+            break :msg msg;
+        };
+        return mod.failWithOwnedErrorMsg(scope, msg);
+    }
+
+    if (scope.getSuspend()) |some| {
+        const msg = msg: {
+            const msg = try mod.errMsg(scope, src, "cannot suspend inside suspend block", .{});
+            errdefer msg.destroy(mod.gpa);
+            try mod.errNote(scope, some.src, msg, "other suspend block here", .{});
+            break :msg msg;
+        };
+        return mod.failWithOwnedErrorMsg(scope, msg);
+    }
+
+    var suspend_scope: Scope.GenZIR = .{
+        .base = .{ .tag = .gen_suspend },
+        .parent = scope,
+        .decl = scope.ownerDecl().?,
+        .arena = scope.arena(),
+        .force_comptime = scope.isComptime(),
+        .instructions = .{},
+    };
+    defer suspend_scope.instructions.deinit(mod.gpa);
+
+    const operand = tree.nodes.items(.data)[node].lhs;
+    if (operand != 0) {
+        const possibly_unused_result = try expr(mod, &suspend_scope.base, .none, operand);
+        if (!possibly_unused_result.tag.isNoReturn()) {
+            _ = try addZIRUnOp(mod, &suspend_scope.base, src, .ensure_result_used, possibly_unused_result);
+        }
+    } else {
+        return addZIRNoOp(mod, scope, src, .@"suspend");
+    }
+
+    const block = try addZIRInstBlock(mod, scope, src, .suspend_block, .{
+        .instructions = try scope.arena().dupe(*zir.Inst, suspend_scope.instructions.items),
+    });
+    return &block.base;
+}
+
+fn nosuspendExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!*zir.Inst {
+    const tree = scope.tree();
+    var child_scope = Scope.Nosuspend{
+        .parent = scope,
+        .gen_zir = scope.getGenZIR(),
+        .src = tree.tokens.items(.start)[tree.nodes.items(.main_token)[node]],
+    };
+
+    return expr(mod, &child_scope.base, rl, tree.nodes.items(.data)[node].lhs);
+}
+
+fn awaitExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!*zir.Inst {
+    const tree = scope.tree();
+    const src = tree.tokens.items(.start)[tree.nodes.items(.main_token)[node]];
+    const is_nosuspend = scope.getNosuspend() != null;
+
+    // TODO some @asyncCall stuff
+
+    if (scope.getSuspend()) |some| {
+        const msg = msg: {
+            const msg = try mod.errMsg(scope, src, "cannot await inside suspend block", .{});
+            errdefer msg.destroy(mod.gpa);
+            try mod.errNote(scope, some.src, msg, "suspend block here", .{});
+            break :msg msg;
+        };
+        return mod.failWithOwnedErrorMsg(scope, msg);
+    }
+
+    const operand = try expr(mod, scope, .ref, tree.nodes.items(.data)[node].lhs);
+    // TODO pass result location
+    return addZIRUnOp(mod, scope, src, if (is_nosuspend) .nosuspend_await else .@"await", operand);
+}
+
+fn resumeExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
+    const tree = scope.tree();
+    const src = tree.tokens.items(.start)[tree.nodes.items(.main_token)[node]];
+
+    const operand = try expr(mod, scope, .ref, tree.nodes.items(.data)[node].lhs);
+    return addZIRUnOp(mod, scope, src, .@"resume", operand);
+}
+
 pub const simple_types = std.ComptimeStringMap(Value.Tag, .{
     .{ "u8", .u8_type },
     .{ "i8", .i8_type },
src/Module.zig
@@ -370,6 +370,8 @@ pub const Scope = struct {
             .gen_zir => return self.cast(GenZIR).?.arena,
             .local_val => return self.cast(LocalVal).?.gen_zir.arena,
             .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena,
+            .gen_suspend => return self.cast(GenZIR).?.arena,
+            .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir.arena,
             .file => unreachable,
             .container => unreachable,
         }
@@ -385,6 +387,8 @@ pub const Scope = struct {
             .gen_zir => self.cast(GenZIR).?.decl,
             .local_val => self.cast(LocalVal).?.gen_zir.decl,
             .local_ptr => self.cast(LocalPtr).?.gen_zir.decl,
+            .gen_suspend => return self.cast(GenZIR).?.decl,
+            .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir.decl,
             .file => null,
             .container => null,
         };
@@ -396,6 +400,8 @@ pub const Scope = struct {
             .gen_zir => self.cast(GenZIR).?.decl,
             .local_val => self.cast(LocalVal).?.gen_zir.decl,
             .local_ptr => self.cast(LocalPtr).?.gen_zir.decl,
+            .gen_suspend => return self.cast(GenZIR).?.decl,
+            .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir.decl,
             .file => null,
             .container => null,
         };
@@ -410,6 +416,8 @@ pub const Scope = struct {
             .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.container,
             .file => return &self.cast(File).?.root_container,
             .container => return self.cast(Container).?,
+            .gen_suspend => return self.cast(GenZIR).?.decl.container,
+            .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir.decl.container,
         }
     }
 
@@ -422,6 +430,8 @@ pub const Scope = struct {
             .gen_zir => unreachable,
             .local_val => unreachable,
             .local_ptr => unreachable,
+            .gen_suspend => unreachable,
+            .gen_nosuspend => unreachable,
             .file => unreachable,
             .container => return self.cast(Container).?.fullyQualifiedNameHash(name),
         }
@@ -436,6 +446,8 @@ pub const Scope = struct {
             .local_val => return &self.cast(LocalVal).?.gen_zir.decl.container.file_scope.tree,
             .local_ptr => return &self.cast(LocalPtr).?.gen_zir.decl.container.file_scope.tree,
             .container => return &self.cast(Container).?.file_scope.tree,
+            .gen_suspend => return &self.cast(GenZIR).?.decl.container.file_scope.tree,
+            .gen_nosuspend => return &self.cast(Nosuspend).?.gen_zir.decl.container.file_scope.tree,
         }
     }
 
@@ -443,9 +455,10 @@ pub const Scope = struct {
     pub fn getGenZIR(self: *Scope) *GenZIR {
         return switch (self.tag) {
             .block => unreachable,
-            .gen_zir => self.cast(GenZIR).?,
+            .gen_zir, .gen_suspend => self.cast(GenZIR).?,
             .local_val => return self.cast(LocalVal).?.gen_zir,
             .local_ptr => return self.cast(LocalPtr).?.gen_zir,
+            .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir,
             .file => unreachable,
             .container => unreachable,
         };
@@ -461,6 +474,8 @@ pub const Scope = struct {
             .gen_zir => unreachable,
             .local_val => unreachable,
             .local_ptr => unreachable,
+            .gen_suspend => unreachable,
+            .gen_nosuspend => unreachable,
         }
     }
 
@@ -472,6 +487,8 @@ pub const Scope = struct {
             .local_val => unreachable,
             .local_ptr => unreachable,
             .block => unreachable,
+            .gen_suspend => unreachable,
+            .gen_nosuspend => unreachable,
         }
     }
 
@@ -486,6 +503,36 @@ pub const Scope = struct {
                 .local_val => @fieldParentPtr(LocalVal, "base", cur).parent,
                 .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent,
                 .block => return @fieldParentPtr(Block, "base", cur).src_decl.container.file_scope,
+                .gen_suspend => @fieldParentPtr(GenZIR, "base", cur).parent,
+                .gen_nosuspend => @fieldParentPtr(Nosuspend, "base", cur).parent,
+            };
+        }
+    }
+
+    pub fn getSuspend(base: *Scope) ?*Scope.GenZIR {
+        var cur = base;
+        while (true) {
+            cur = switch (cur.tag) {
+                .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent,
+                .local_val => @fieldParentPtr(LocalVal, "base", cur).parent,
+                .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent,
+                .gen_nosuspend => @fieldParentPtr(Nosuspend, "base", cur).parent,
+                .gen_suspend => return @fieldParentPtr(GenZIR, "base", cur),
+                else => return null,
+            };
+        }
+    }
+
+    pub fn getNosuspend(base: *Scope) ?*Scope.Nosuspend {
+        var cur = base;
+        while (true) {
+            cur = switch (cur.tag) {
+                .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent,
+                .local_val => @fieldParentPtr(LocalVal, "base", cur).parent,
+                .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent,
+                .gen_suspend => @fieldParentPtr(GenZIR, "base", cur).parent,
+                .gen_nosuspend => return @fieldParentPtr(Nosuspend, "base", cur),
+                else => return null,
             };
         }
     }
@@ -507,6 +554,8 @@ pub const Scope = struct {
         gen_zir,
         local_val,
         local_ptr,
+        gen_suspend,
+        gen_nosuspend,
     };
 
     pub const Container = struct {
@@ -740,6 +789,8 @@ pub const Scope = struct {
         /// so they can possibly be elided later if the labeled block ends up not needing
         /// a result location pointer.
         labeled_store_to_block_ptr_list: std.ArrayListUnmanaged(*zir.Inst.BinOp) = .{},
+        /// for suspend error notes
+        src: usize = 0,
 
         pub const Label = struct {
             token: ast.TokenIndex,
@@ -773,6 +824,16 @@ pub const Scope = struct {
         name: []const u8,
         ptr: *zir.Inst,
     };
+
+    pub const Nosuspend = struct {
+        pub const base_tag: Tag = .gen_nosuspend;
+
+        base: Scope = Scope{ .tag = base_tag },
+        /// Parents can be: `LocalVal`, `LocalPtr`, `GenZIR`.
+        parent: *Scope,
+        gen_zir: *GenZIR,
+        src: usize,
+    };
 };
 
 /// This struct holds data necessary to construct API-facing `AllErrors.Message`.
@@ -3586,7 +3647,7 @@ pub fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, err_msg: *ErrorMsg) I
             }
             self.failed_decls.putAssumeCapacityNoClobber(block.owner_decl, err_msg);
         },
-        .gen_zir => {
+        .gen_zir, .gen_suspend => {
             const gen_zir = scope.cast(Scope.GenZIR).?;
             gen_zir.decl.analysis = .sema_failure;
             gen_zir.decl.generation = self.generation;
@@ -3604,6 +3665,12 @@ pub fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, err_msg: *ErrorMsg) I
             gen_zir.decl.generation = self.generation;
             self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg);
         },
+        .gen_nosuspend => {
+            const gen_zir = scope.cast(Scope.Nosuspend).?.gen_zir;
+            gen_zir.decl.analysis = .sema_failure;
+            gen_zir.decl.generation = self.generation;
+            self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg);
+        },
         .file => unreachable,
         .container => unreachable,
     }
src/zir.zig
@@ -61,6 +61,8 @@ pub const Inst = struct {
         as,
         /// Inline assembly.
         @"asm",
+        /// Await an async function.
+        @"await",
         /// Bitwise AND. `&`
         bit_and,
         /// TODO delete this instruction, it has no purpose.
@@ -212,6 +214,8 @@ pub const Inst = struct {
         mul,
         /// Twos complement wrapping integer multiplication.
         mulwrap,
+        /// An await inside a nosuspend scope.
+        nosuspend_await,
         /// Given a reference to a function and a parameter index, returns the
         /// type of the parameter. TODO what happens when the parameter is `anytype`?
         param_type,
@@ -226,6 +230,8 @@ pub const Inst = struct {
         /// the memory location is in the stack frame, local to the scope containing the
         /// instruction.
         ref,
+        /// Resume an async function.
+        @"resume",
         /// Obtains a pointer to the return value.
         ret_ptr,
         /// Obtains the return type of the in-scope function.
@@ -348,6 +354,11 @@ pub const Inst = struct {
         enum_type,
         /// Does nothing; returns a void value.
         void_value,
+        /// Suspend an async function.
+        @"suspend",
+        /// Suspend an async function.
+        /// Same as .suspend but with a block.
+        suspend_block,
         /// A switch expression.
         switchbr,
         /// Same as `switchbr` but the target is a pointer to the value being switched on.
@@ -369,6 +380,7 @@ pub const Inst = struct {
                 .unreachable_unsafe,
                 .unreachable_safe,
                 .void_value,
+                .@"suspend",
                 => NoOp,
 
                 .alloc,
@@ -417,6 +429,9 @@ pub const Inst = struct {
                 .import,
                 .set_eval_branch_quota,
                 .indexable_ptr_len,
+                .@"resume",
+                .@"await",
+                .nosuspend_await,
                 => UnOp,
 
                 .add,
@@ -461,6 +476,7 @@ pub const Inst = struct {
                 .block_flat,
                 .block_comptime,
                 .block_comptime_flat,
+                .suspend_block,
                 => Block,
 
                 .switchbr, .switchbr_ref => SwitchBr,
@@ -633,6 +649,9 @@ pub const Inst = struct {
                 .struct_type,
                 .void_value,
                 .switch_range,
+                .@"resume",
+                .@"await",
+                .nosuspend_await,
                 => false,
 
                 .@"break",
@@ -649,6 +668,8 @@ pub const Inst = struct {
                 .container_field,
                 .switchbr,
                 .switchbr_ref,
+                .@"suspend",
+                .suspend_block,
                 => true,
             };
         }
src/zir_sema.zig
@@ -160,6 +160,11 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
         .switchbr => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr).?, false),
         .switchbr_ref => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr_ref).?, true),
         .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?),
+        .@"await" => return zirAwait(mod, scope, old_inst.castTag(.@"await").?),
+        .nosuspend_await => return zirAwait(mod, scope, old_inst.castTag(.nosuspend_await).?),
+        .@"resume" => return zirResume(mod, scope, old_inst.castTag(.@"resume").?),
+        .@"suspend" => return zirSuspend(mod, scope, old_inst.castTag(.@"suspend").?),
+        .suspend_block => return zirSuspendBlock(mod, scope, old_inst.castTag(.suspend_block).?),
 
         .container_field_named,
         .container_field_typed,
@@ -1080,6 +1085,22 @@ fn zirFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst {
     });
 }
 
+fn zirAwait(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+    return mod.fail(scope, inst.base.src, "TODO implement await", .{});
+}
+
+fn zirResume(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+    return mod.fail(scope, inst.base.src, "TODO implement resume", .{});
+}
+
+fn zirSuspend(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
+    return mod.fail(scope, inst.base.src, "TODO implement suspend", .{});
+}
+
+fn zirSuspendBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst {
+    return mod.fail(scope, inst.base.src, "TODO implement suspend", .{});
+}
+
 fn zirIntType(mod: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
@@ -2046,7 +2067,7 @@ fn zirBitwise(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*In
                 rhs.ty.arrayLen(),
             });
         }
-        return mod.fail(scope, inst.base.src, "TODO implement support for vectors in analyzeInstBitwise", .{});
+        return mod.fail(scope, inst.base.src, "TODO implement support for vectors in zirBitwise", .{});
     } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) {
         return mod.fail(scope, inst.base.src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
             lhs.ty,
@@ -2127,7 +2148,7 @@ fn zirArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!
                 rhs.ty.arrayLen(),
             });
         }
-        return mod.fail(scope, inst.base.src, "TODO implement support for vectors in analyzeInstBinOp", .{});
+        return mod.fail(scope, inst.base.src, "TODO implement support for vectors in zirBinOp", .{});
     } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) {
         return mod.fail(scope, inst.base.src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
             lhs.ty,