Commit bd1c55d2c2

Andrew Kelley <superjoe30@gmail.com>
2018-07-18 23:40:59
self-hosted: compile errors for return in wrong place
* outside fn definition * inside defer expression
1 parent aa3b412
Changed files (4)
src-self-hosted/compilation.zig
@@ -207,6 +207,8 @@ pub const Compilation = struct {
 
     destroy_handle: promise,
 
+    have_err_ret_tracing: bool,
+
     const CompileErrList = std.ArrayList(*errmsg.Msg);
 
     // TODO handle some of these earlier and report them in a way other than error codes
@@ -379,6 +381,7 @@ pub const Compilation = struct {
 
             .override_libc = null,
             .destroy_handle = undefined,
+            .have_err_ret_tracing = false,
         });
         errdefer {
             comp.arena_allocator.deinit();
@@ -660,7 +663,11 @@ pub const Compilation = struct {
         while (it.next()) |decl_ptr| {
             const decl = decl_ptr.*;
             switch (decl.id) {
-                ast.Node.Id.Comptime => @panic("TODO"),
+                ast.Node.Id.Comptime => {
+                    const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl);
+
+                    try decl_group.call(addCompTimeBlock, self, parsed_file, &decls.base, comptime_node);
+                },
                 ast.Node.Id.VarDecl => @panic("TODO"),
                 ast.Node.Id.FnProto => {
                     const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
@@ -709,6 +716,69 @@ pub const Compilation = struct {
         }
     }
 
+    /// caller takes ownership of resulting Code
+    async fn genAndAnalyzeCode(
+        comp: *Compilation,
+        parsed_file: *ParsedFile,
+        scope: *Scope,
+        node: *ast.Node,
+        expected_type: ?*Type,
+    ) !?*ir.Code {
+        const unanalyzed_code = (await (async ir.gen(
+            comp,
+            node,
+            scope,
+            parsed_file,
+        ) catch unreachable)) catch |err| switch (err) {
+            // This poison value should not cause the errdefers to run. It simply means
+            // that self.compile_errors is populated.
+            // TODO https://github.com/ziglang/zig/issues/769
+            error.SemanticAnalysisFailed => return null,
+            else => return err,
+        };
+        defer unanalyzed_code.destroy(comp.gpa());
+
+        if (comp.verbose_ir) {
+            std.debug.warn("unanalyzed:\n");
+            unanalyzed_code.dump();
+        }
+
+        const analyzed_code = (await (async ir.analyze(
+            comp,
+            parsed_file,
+            unanalyzed_code,
+            expected_type,
+        ) catch unreachable)) catch |err| switch (err) {
+            // This poison value should not cause the errdefers to run. It simply means
+            // that self.compile_errors is populated.
+            // TODO https://github.com/ziglang/zig/issues/769
+            error.SemanticAnalysisFailed => return null,
+            else => return err,
+        };
+        errdefer analyzed_code.destroy(comp.gpa());
+
+        return analyzed_code;
+    }
+
+    async fn addCompTimeBlock(
+        comp: *Compilation,
+        parsed_file: *ParsedFile,
+        scope: *Scope,
+        comptime_node: *ast.Node.Comptime,
+    ) !void {
+        const void_type = Type.Void.get(comp);
+        defer void_type.base.base.deref(comp);
+
+        const analyzed_code = (try await (async genAndAnalyzeCode(
+            comp,
+            parsed_file,
+            scope,
+            comptime_node.expr,
+            &void_type.base,
+        ) catch unreachable)) orelse return;
+        analyzed_code.destroy(comp.gpa());
+    }
+
     async fn addTopLevelDecl(self: *Compilation, decl: *Decl) !void {
         const is_export = decl.isExported(&decl.parsed_file.tree);
 
@@ -931,38 +1001,12 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
     const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name);
     fn_decl.value = Decl.Fn.Val{ .Ok = fn_val };
 
-    const unanalyzed_code = (await (async ir.gen(
-        comp,
-        body_node,
-        &fndef_scope.base,
-        Span.token(body_node.lastToken()),
+    const analyzed_code = (try await (async comp.genAndAnalyzeCode(
         fn_decl.base.parsed_file,
-    ) catch unreachable)) catch |err| switch (err) {
-        // This poison value should not cause the errdefers to run. It simply means
-        // that self.compile_errors is populated.
-        // TODO https://github.com/ziglang/zig/issues/769
-        error.SemanticAnalysisFailed => return {},
-        else => return err,
-    };
-    defer unanalyzed_code.destroy(comp.gpa());
-
-    if (comp.verbose_ir) {
-        std.debug.warn("unanalyzed:\n");
-        unanalyzed_code.dump();
-    }
-
-    const analyzed_code = (await (async ir.analyze(
-        comp,
-        fn_decl.base.parsed_file,
-        unanalyzed_code,
+        &fndef_scope.base,
+        body_node,
         null,
-    ) catch unreachable)) catch |err| switch (err) {
-        // This poison value should not cause the errdefers to run. It simply means
-        // that self.compile_errors is populated.
-        // TODO https://github.com/ziglang/zig/issues/769
-        error.SemanticAnalysisFailed => return {},
-        else => return err,
-    };
+    ) catch unreachable)) orelse return;
     errdefer analyzed_code.destroy(comp.gpa());
 
     if (comp.verbose_ir) {
src-self-hosted/ir.zig
@@ -46,7 +46,7 @@ pub const IrVal = union(enum) {
     }
 };
 
-pub const Instruction = struct {
+pub const Inst = struct {
     id: Id,
     scope: *Scope,
     debug_id: usize,
@@ -59,15 +59,15 @@ pub const Instruction = struct {
     is_generated: bool,
 
     /// the instruction that is derived from this one in analysis
-    child: ?*Instruction,
+    child: ?*Inst,
 
     /// the instruction that this one derives from in analysis
-    parent: ?*Instruction,
+    parent: ?*Inst,
 
     /// populated durign codegen
     llvm_value: ?llvm.ValueRef,
 
-    pub fn cast(base: *Instruction, comptime T: type) ?*T {
+    pub fn cast(base: *Inst, comptime T: type) ?*T {
         if (base.id == comptime typeToId(T)) {
             return @fieldParentPtr(T, "base", base);
         }
@@ -77,18 +77,18 @@ pub const Instruction = struct {
     pub fn typeToId(comptime T: type) Id {
         comptime var i = 0;
         inline while (i < @memberCount(Id)) : (i += 1) {
-            if (T == @field(Instruction, @memberName(Id, i))) {
+            if (T == @field(Inst, @memberName(Id, i))) {
                 return @field(Id, @memberName(Id, i));
             }
         }
         unreachable;
     }
 
-    pub fn dump(base: *const Instruction) void {
+    pub fn dump(base: *const Inst) void {
         comptime var i = 0;
         inline while (i < @memberCount(Id)) : (i += 1) {
             if (base.id == @field(Id, @memberName(Id, i))) {
-                const T = @field(Instruction, @memberName(Id, i));
+                const T = @field(Inst, @memberName(Id, i));
                 std.debug.warn("#{} = {}(", base.debug_id, @tagName(base.id));
                 @fieldParentPtr(T, "base", base).dump();
                 std.debug.warn(")");
@@ -98,29 +98,29 @@ pub const Instruction = struct {
         unreachable;
     }
 
-    pub fn hasSideEffects(base: *const Instruction) bool {
+    pub fn hasSideEffects(base: *const Inst) bool {
         comptime var i = 0;
         inline while (i < @memberCount(Id)) : (i += 1) {
             if (base.id == @field(Id, @memberName(Id, i))) {
-                const T = @field(Instruction, @memberName(Id, i));
+                const T = @field(Inst, @memberName(Id, i));
                 return @fieldParentPtr(T, "base", base).hasSideEffects();
             }
         }
         unreachable;
     }
 
-    pub fn analyze(base: *Instruction, ira: *Analyze) Analyze.Error!*Instruction {
+    pub fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst {
         comptime var i = 0;
         inline while (i < @memberCount(Id)) : (i += 1) {
             if (base.id == @field(Id, @memberName(Id, i))) {
-                const T = @field(Instruction, @memberName(Id, i));
+                const T = @field(Inst, @memberName(Id, i));
                 return @fieldParentPtr(T, "base", base).analyze(ira);
             }
         }
         unreachable;
     }
 
-    pub fn render(base: *Instruction, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) {
+    pub fn render(base: *Inst, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) {
         switch (base.id) {
             Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val),
             Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val),
@@ -133,14 +133,14 @@ pub const Instruction = struct {
         }
     }
 
-    fn ref(base: *Instruction, builder: *Builder) void {
+    fn ref(base: *Inst, builder: *Builder) void {
         base.ref_count += 1;
         if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) {
             base.owner_bb.ref();
         }
     }
 
-    fn getAsParam(param: *Instruction) !*Instruction {
+    fn getAsParam(param: *Inst) !*Inst {
         const child = param.child orelse return error.SemanticAnalysisFailed;
         switch (child.val) {
             IrVal.Unknown => return error.SemanticAnalysisFailed,
@@ -149,7 +149,7 @@ pub const Instruction = struct {
     }
 
     /// asserts that the type is known
-    fn getKnownType(self: *Instruction) *Type {
+    fn getKnownType(self: *Inst) *Type {
         switch (self.val) {
             IrVal.KnownType => |typeof| return typeof,
             IrVal.KnownValue => |value| return value.typeof,
@@ -157,11 +157,11 @@ pub const Instruction = struct {
         }
     }
 
-    pub fn setGenerated(base: *Instruction) void {
+    pub fn setGenerated(base: *Inst) void {
         base.is_generated = true;
     }
 
-    pub fn isNoReturn(base: *const Instruction) bool {
+    pub fn isNoReturn(base: *const Inst) bool {
         switch (base.val) {
             IrVal.Unknown => return false,
             IrVal.KnownValue => |x| return x.typeof.id == Type.Id.NoReturn,
@@ -169,11 +169,11 @@ pub const Instruction = struct {
         }
     }
 
-    pub fn isCompTime(base: *const Instruction) bool {
+    pub fn isCompTime(base: *const Inst) bool {
         return base.val == IrVal.KnownValue;
     }
 
-    pub fn linkToParent(self: *Instruction, parent: *Instruction) void {
+    pub fn linkToParent(self: *Inst, parent: *Inst) void {
         assert(self.parent == null);
         assert(parent.child == null);
         self.parent = parent;
@@ -192,7 +192,7 @@ pub const Instruction = struct {
     };
 
     pub const Const = struct {
-        base: Instruction,
+        base: Inst,
         params: Params,
 
         const Params = struct {};
@@ -209,7 +209,7 @@ pub const Instruction = struct {
             return false;
         }
 
-        pub fn analyze(self: *const Const, ira: *Analyze) !*Instruction {
+        pub fn analyze(self: *const Const, ira: *Analyze) !*Inst {
             const new_inst = try ira.irb.build(Const, self.base.scope, self.base.span, Params{});
             new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() };
             return new_inst;
@@ -221,11 +221,11 @@ pub const Instruction = struct {
     };
 
     pub const Return = struct {
-        base: Instruction,
+        base: Inst,
         params: Params,
 
         const Params = struct {
-            return_value: *Instruction,
+            return_value: *Inst,
         };
 
         const ir_val_init = IrVal.Init.NoReturn;
@@ -238,7 +238,7 @@ pub const Instruction = struct {
             return true;
         }
 
-        pub fn analyze(self: *const Return, ira: *Analyze) !*Instruction {
+        pub fn analyze(self: *const Return, ira: *Analyze) !*Inst {
             const value = try self.params.return_value.getAsParam();
             const casted_value = try ira.implicitCast(value, ira.explicit_return_type);
 
@@ -261,11 +261,11 @@ pub const Instruction = struct {
     };
 
     pub const Ref = struct {
-        base: Instruction,
+        base: Inst,
         params: Params,
 
         const Params = struct {
-            target: *Instruction,
+            target: *Inst,
             mut: Type.Pointer.Mut,
             volatility: Type.Pointer.Vol,
         };
@@ -278,7 +278,7 @@ pub const Instruction = struct {
             return false;
         }
 
-        pub fn analyze(self: *const Ref, ira: *Analyze) !*Instruction {
+        pub fn analyze(self: *const Ref, ira: *Analyze) !*Inst {
             const target = try self.params.target.getAsParam();
 
             if (ira.getCompTimeValOrNullUndefOk(target)) |val| {
@@ -314,7 +314,7 @@ pub const Instruction = struct {
     };
 
     pub const DeclVar = struct {
-        base: Instruction,
+        base: Inst,
         params: Params,
 
         const Params = struct {
@@ -329,17 +329,17 @@ pub const Instruction = struct {
             return true;
         }
 
-        pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Instruction {
+        pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Inst {
             return error.Unimplemented; // TODO
         }
     };
 
     pub const CheckVoidStmt = struct {
-        base: Instruction,
+        base: Inst,
         params: Params,
 
         const Params = struct {
-            target: *Instruction,
+            target: *Inst,
         };
 
         const ir_val_init = IrVal.Init.Unknown;
@@ -350,18 +350,18 @@ pub const Instruction = struct {
             return true;
         }
 
-        pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Instruction {
+        pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Inst {
             return error.Unimplemented; // TODO
         }
     };
 
     pub const Phi = struct {
-        base: Instruction,
+        base: Inst,
         params: Params,
 
         const Params = struct {
             incoming_blocks: []*BasicBlock,
-            incoming_values: []*Instruction,
+            incoming_values: []*Inst,
         };
 
         const ir_val_init = IrVal.Init.Unknown;
@@ -372,18 +372,18 @@ pub const Instruction = struct {
             return false;
         }
 
-        pub fn analyze(self: *const Phi, ira: *Analyze) !*Instruction {
+        pub fn analyze(self: *const Phi, ira: *Analyze) !*Inst {
             return error.Unimplemented; // TODO
         }
     };
 
     pub const Br = struct {
-        base: Instruction,
+        base: Inst,
         params: Params,
 
         const Params = struct {
             dest_block: *BasicBlock,
-            is_comptime: *Instruction,
+            is_comptime: *Inst,
         };
 
         const ir_val_init = IrVal.Init.NoReturn;
@@ -394,17 +394,41 @@ pub const Instruction = struct {
             return true;
         }
 
-        pub fn analyze(self: *const Br, ira: *Analyze) !*Instruction {
+        pub fn analyze(self: *const Br, ira: *Analyze) !*Inst {
+            return error.Unimplemented; // TODO
+        }
+    };
+
+    pub const CondBr = struct {
+        base: Inst,
+        params: Params,
+
+        const Params = struct {
+            condition: *Inst,
+            then_block: *BasicBlock,
+            else_block: *BasicBlock,
+            is_comptime: *Inst,
+        };
+
+        const ir_val_init = IrVal.Init.NoReturn;
+
+        pub fn dump(inst: *const CondBr) void {}
+
+        pub fn hasSideEffects(inst: *const CondBr) bool {
+            return true;
+        }
+
+        pub fn analyze(self: *const CondBr, ira: *Analyze) !*Inst {
             return error.Unimplemented; // TODO
         }
     };
 
     pub const AddImplicitReturnType = struct {
-        base: Instruction,
+        base: Inst,
         params: Params,
 
         pub const Params = struct {
-            target: *Instruction,
+            target: *Inst,
         };
 
         const ir_val_init = IrVal.Init.Unknown;
@@ -417,12 +441,117 @@ pub const Instruction = struct {
             return true;
         }
 
-        pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Instruction {
+        pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Inst {
             const target = try self.params.target.getAsParam();
             try ira.src_implicit_return_type_list.append(target);
             return ira.irb.buildConstVoid(self.base.scope, self.base.span, true);
         }
     };
+
+    pub const TestErr = struct {
+        base: Inst,
+        params: Params,
+
+        pub const Params = struct {
+            target: *Inst,
+        };
+
+        const ir_val_init = IrVal.Init.Unknown;
+
+        pub fn dump(inst: *const TestErr) void {
+            std.debug.warn("#{}", inst.params.target.debug_id);
+        }
+
+        pub fn hasSideEffects(inst: *const TestErr) bool {
+            return false;
+        }
+
+        pub fn analyze(self: *const TestErr, ira: *Analyze) !*Inst {
+            const target = try self.params.target.getAsParam();
+            const target_type = target.getKnownType();
+            switch (target_type.id) {
+                Type.Id.ErrorUnion => {
+                    return error.Unimplemented;
+                    //    if (instr_is_comptime(value)) {
+                    //        ConstExprValue *err_union_val = ir_resolve_const(ira, value, UndefBad);
+                    //        if (!err_union_val)
+                    //            return ira->codegen->builtin_types.entry_invalid;
+
+                    //        if (err_union_val->special != ConstValSpecialRuntime) {
+                    //            ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
+                    //            out_val->data.x_bool = (err_union_val->data.x_err_union.err != nullptr);
+                    //            return ira->codegen->builtin_types.entry_bool;
+                    //        }
+                    //    }
+
+                    //    TypeTableEntry *err_set_type = type_entry->data.error_union.err_set_type;
+                    //    if (!resolve_inferred_error_set(ira->codegen, err_set_type, instruction->base.source_node)) {
+                    //        return ira->codegen->builtin_types.entry_invalid;
+                    //    }
+                    //    if (!type_is_global_error_set(err_set_type) &&
+                    //        err_set_type->data.error_set.err_count == 0)
+                    //    {
+                    //        assert(err_set_type->data.error_set.infer_fn == nullptr);
+                    //        ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
+                    //        out_val->data.x_bool = false;
+                    //        return ira->codegen->builtin_types.entry_bool;
+                    //    }
+
+                    //    ir_build_test_err_from(&ira->new_irb, &instruction->base, value);
+                    //    return ira->codegen->builtin_types.entry_bool;
+                },
+                Type.Id.ErrorSet => {
+                    return ira.irb.buildConstBool(self.base.scope, self.base.span, true);
+                },
+                else => {
+                    return ira.irb.buildConstBool(self.base.scope, self.base.span, false);
+                },
+            }
+        }
+    };
+
+    pub const TestCompTime = struct {
+        base: Inst,
+        params: Params,
+
+        pub const Params = struct {
+            target: *Inst,
+        };
+
+        const ir_val_init = IrVal.Init.Unknown;
+
+        pub fn dump(inst: *const TestCompTime) void {
+            std.debug.warn("#{}", inst.params.target.debug_id);
+        }
+
+        pub fn hasSideEffects(inst: *const TestCompTime) bool {
+            return false;
+        }
+
+        pub fn analyze(self: *const TestCompTime, ira: *Analyze) !*Inst {
+            const target = try self.params.target.getAsParam();
+            return ira.irb.buildConstBool(self.base.scope, self.base.span, target.isCompTime());
+        }
+    };
+
+    pub const SaveErrRetAddr = struct {
+        base: Inst,
+        params: Params,
+
+        const Params = struct {};
+
+        const ir_val_init = IrVal.Init.Unknown;
+
+        pub fn dump(inst: *const SaveErrRetAddr) void {}
+
+        pub fn hasSideEffects(inst: *const SaveErrRetAddr) bool {
+            return true;
+        }
+
+        pub fn analyze(self: *const SaveErrRetAddr, ira: *Analyze) !*Inst {
+            return ira.irb.build(Inst.SaveErrRetAddr, self.base.scope, self.base.span, Params{});
+        }
+    };
 };
 
 pub const Variable = struct {
@@ -434,8 +563,8 @@ pub const BasicBlock = struct {
     name_hint: [*]const u8, // must be a C string literal
     debug_id: usize,
     scope: *Scope,
-    instruction_list: std.ArrayList(*Instruction),
-    ref_instruction: ?*Instruction,
+    instruction_list: std.ArrayList(*Inst),
+    ref_instruction: ?*Inst,
 
     /// for codegen
     llvm_block: llvm.BasicBlockRef,
@@ -491,10 +620,12 @@ pub const Builder = struct {
     next_debug_id: usize,
     parsed_file: *ParsedFile,
     is_comptime: bool,
+    is_async: bool,
+    begin_scope: ?*Scope,
 
     pub const Error = Analyze.Error;
 
-    pub fn init(comp: *Compilation, parsed_file: *ParsedFile) !Builder {
+    pub fn init(comp: *Compilation, parsed_file: *ParsedFile, begin_scope: ?*Scope) !Builder {
         const code = try comp.gpa().create(Code{
             .basic_block_list = undefined,
             .arena = std.heap.ArenaAllocator.init(comp.gpa()),
@@ -510,6 +641,8 @@ pub const Builder = struct {
             .code = code,
             .next_debug_id = 0,
             .is_comptime = false,
+            .is_async = false,
+            .begin_scope = begin_scope,
         };
     }
 
@@ -529,7 +662,7 @@ pub const Builder = struct {
             .name_hint = name_hint,
             .debug_id = self.next_debug_id,
             .scope = scope,
-            .instruction_list = std.ArrayList(*Instruction).init(self.arena()),
+            .instruction_list = std.ArrayList(*Inst).init(self.arena()),
             .child = null,
             .parent = null,
             .ref_instruction = null,
@@ -549,66 +682,69 @@ pub const Builder = struct {
         self.current_basic_block = basic_block;
     }
 
-    pub fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Instruction {
+    pub fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst {
         switch (node.id) {
             ast.Node.Id.Root => unreachable,
             ast.Node.Id.Use => unreachable,
             ast.Node.Id.TestDecl => unreachable,
-            ast.Node.Id.VarDecl => @panic("TODO"),
-            ast.Node.Id.Defer => @panic("TODO"),
-            ast.Node.Id.InfixOp => @panic("TODO"),
-            ast.Node.Id.PrefixOp => @panic("TODO"),
-            ast.Node.Id.SuffixOp => @panic("TODO"),
-            ast.Node.Id.Switch => @panic("TODO"),
-            ast.Node.Id.While => @panic("TODO"),
-            ast.Node.Id.For => @panic("TODO"),
-            ast.Node.Id.If => @panic("TODO"),
-            ast.Node.Id.ControlFlowExpression => return error.Unimplemented,
-            ast.Node.Id.Suspend => @panic("TODO"),
-            ast.Node.Id.VarType => @panic("TODO"),
-            ast.Node.Id.ErrorType => @panic("TODO"),
-            ast.Node.Id.FnProto => @panic("TODO"),
-            ast.Node.Id.PromiseType => @panic("TODO"),
-            ast.Node.Id.IntegerLiteral => @panic("TODO"),
-            ast.Node.Id.FloatLiteral => @panic("TODO"),
-            ast.Node.Id.StringLiteral => @panic("TODO"),
-            ast.Node.Id.MultilineStringLiteral => @panic("TODO"),
-            ast.Node.Id.CharLiteral => @panic("TODO"),
-            ast.Node.Id.BoolLiteral => @panic("TODO"),
-            ast.Node.Id.NullLiteral => @panic("TODO"),
-            ast.Node.Id.UndefinedLiteral => @panic("TODO"),
-            ast.Node.Id.ThisLiteral => @panic("TODO"),
-            ast.Node.Id.Unreachable => @panic("TODO"),
-            ast.Node.Id.Identifier => @panic("TODO"),
+            ast.Node.Id.VarDecl => return error.Unimplemented,
+            ast.Node.Id.Defer => return error.Unimplemented,
+            ast.Node.Id.InfixOp => return error.Unimplemented,
+            ast.Node.Id.PrefixOp => return error.Unimplemented,
+            ast.Node.Id.SuffixOp => return error.Unimplemented,
+            ast.Node.Id.Switch => return error.Unimplemented,
+            ast.Node.Id.While => return error.Unimplemented,
+            ast.Node.Id.For => return error.Unimplemented,
+            ast.Node.Id.If => return error.Unimplemented,
+            ast.Node.Id.ControlFlowExpression => {
+                const control_flow_expr = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", node);
+                return irb.genControlFlowExpr(control_flow_expr, scope, lval);
+            },
+            ast.Node.Id.Suspend => return error.Unimplemented,
+            ast.Node.Id.VarType => return error.Unimplemented,
+            ast.Node.Id.ErrorType => return error.Unimplemented,
+            ast.Node.Id.FnProto => return error.Unimplemented,
+            ast.Node.Id.PromiseType => return error.Unimplemented,
+            ast.Node.Id.IntegerLiteral => return error.Unimplemented,
+            ast.Node.Id.FloatLiteral => return error.Unimplemented,
+            ast.Node.Id.StringLiteral => return error.Unimplemented,
+            ast.Node.Id.MultilineStringLiteral => return error.Unimplemented,
+            ast.Node.Id.CharLiteral => return error.Unimplemented,
+            ast.Node.Id.BoolLiteral => return error.Unimplemented,
+            ast.Node.Id.NullLiteral => return error.Unimplemented,
+            ast.Node.Id.UndefinedLiteral => return error.Unimplemented,
+            ast.Node.Id.ThisLiteral => return error.Unimplemented,
+            ast.Node.Id.Unreachable => return error.Unimplemented,
+            ast.Node.Id.Identifier => return error.Unimplemented,
             ast.Node.Id.GroupedExpression => {
                 const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node);
                 return irb.genNode(grouped_expr.expr, scope, lval);
             },
-            ast.Node.Id.BuiltinCall => @panic("TODO"),
-            ast.Node.Id.ErrorSetDecl => @panic("TODO"),
-            ast.Node.Id.ContainerDecl => @panic("TODO"),
-            ast.Node.Id.Asm => @panic("TODO"),
-            ast.Node.Id.Comptime => @panic("TODO"),
+            ast.Node.Id.BuiltinCall => return error.Unimplemented,
+            ast.Node.Id.ErrorSetDecl => return error.Unimplemented,
+            ast.Node.Id.ContainerDecl => return error.Unimplemented,
+            ast.Node.Id.Asm => return error.Unimplemented,
+            ast.Node.Id.Comptime => return error.Unimplemented,
             ast.Node.Id.Block => {
                 const block = @fieldParentPtr(ast.Node.Block, "base", node);
                 return irb.lvalWrap(scope, try irb.genBlock(block, scope), lval);
             },
-            ast.Node.Id.DocComment => @panic("TODO"),
-            ast.Node.Id.SwitchCase => @panic("TODO"),
-            ast.Node.Id.SwitchElse => @panic("TODO"),
-            ast.Node.Id.Else => @panic("TODO"),
-            ast.Node.Id.Payload => @panic("TODO"),
-            ast.Node.Id.PointerPayload => @panic("TODO"),
-            ast.Node.Id.PointerIndexPayload => @panic("TODO"),
-            ast.Node.Id.StructField => @panic("TODO"),
-            ast.Node.Id.UnionTag => @panic("TODO"),
-            ast.Node.Id.EnumTag => @panic("TODO"),
-            ast.Node.Id.ErrorTag => @panic("TODO"),
-            ast.Node.Id.AsmInput => @panic("TODO"),
-            ast.Node.Id.AsmOutput => @panic("TODO"),
-            ast.Node.Id.AsyncAttribute => @panic("TODO"),
-            ast.Node.Id.ParamDecl => @panic("TODO"),
-            ast.Node.Id.FieldInitializer => @panic("TODO"),
+            ast.Node.Id.DocComment => return error.Unimplemented,
+            ast.Node.Id.SwitchCase => return error.Unimplemented,
+            ast.Node.Id.SwitchElse => return error.Unimplemented,
+            ast.Node.Id.Else => return error.Unimplemented,
+            ast.Node.Id.Payload => return error.Unimplemented,
+            ast.Node.Id.PointerPayload => return error.Unimplemented,
+            ast.Node.Id.PointerIndexPayload => return error.Unimplemented,
+            ast.Node.Id.StructField => return error.Unimplemented,
+            ast.Node.Id.UnionTag => return error.Unimplemented,
+            ast.Node.Id.EnumTag => return error.Unimplemented,
+            ast.Node.Id.ErrorTag => return error.Unimplemented,
+            ast.Node.Id.AsmInput => return error.Unimplemented,
+            ast.Node.Id.AsmOutput => return error.Unimplemented,
+            ast.Node.Id.AsyncAttribute => return error.Unimplemented,
+            ast.Node.Id.ParamDecl => return error.Unimplemented,
+            ast.Node.Id.FieldInitializer => return error.Unimplemented,
         }
     }
 
@@ -630,7 +766,7 @@ pub const Builder = struct {
         }
     }
 
-    pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Instruction {
+    pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst {
         const block_scope = try Scope.Block.create(irb.comp, parent_scope);
 
         const outer_block_scope = &block_scope.base;
@@ -648,7 +784,7 @@ pub const Builder = struct {
         }
 
         if (block.label) |label| {
-            block_scope.incoming_values = std.ArrayList(*Instruction).init(irb.arena());
+            block_scope.incoming_values = std.ArrayList(*Inst).init(irb.arena());
             block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena());
             block_scope.end_block = try irb.createBasicBlock(parent_scope, c"BlockEnd");
             block_scope.is_comptime = try irb.buildConstBool(
@@ -659,7 +795,7 @@ pub const Builder = struct {
         }
 
         var is_continuation_unreachable = false;
-        var noreturn_return_value: ?*Instruction = null;
+        var noreturn_return_value: ?*Inst = null;
 
         var stmt_it = block.statements.iterator(0);
         while (stmt_it.next()) |statement_node_ptr| {
@@ -686,16 +822,16 @@ pub const Builder = struct {
                 noreturn_return_value = statement_value;
             }
 
-            if (statement_value.cast(Instruction.DeclVar)) |decl_var| {
+            if (statement_value.cast(Inst.DeclVar)) |decl_var| {
                 // variable declarations start a new scope
                 child_scope = decl_var.params.variable.child_scope;
             } else if (!is_continuation_unreachable) {
                 // this statement's value must be void
                 _ = irb.build(
-                    Instruction.CheckVoidStmt,
+                    Inst.CheckVoidStmt,
                     child_scope,
                     statement_value.span,
-                    Instruction.CheckVoidStmt.Params{ .target = statement_value },
+                    Inst.CheckVoidStmt.Params{ .target = statement_value },
                 );
             }
         }
@@ -707,7 +843,7 @@ pub const Builder = struct {
             }
 
             try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
-            return irb.build(Instruction.Phi, parent_scope, Span.token(block.rbrace), Instruction.Phi.Params{
+            return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{
                 .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(),
                 .incoming_values = block_scope.incoming_values.toOwnedSlice(),
             });
@@ -720,14 +856,14 @@ pub const Builder = struct {
             );
             _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
 
-            _ = try irb.buildGen(Instruction.Br, parent_scope, Span.token(block.rbrace), Instruction.Br.Params{
+            _ = try irb.buildGen(Inst.Br, parent_scope, Span.token(block.rbrace), Inst.Br.Params{
                 .dest_block = block_scope.end_block,
                 .is_comptime = block_scope.is_comptime,
             });
 
             try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
 
-            return irb.build(Instruction.Phi, parent_scope, Span.token(block.rbrace), Instruction.Phi.Params{
+            return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{
                 .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(),
                 .incoming_values = block_scope.incoming_values.toOwnedSlice(),
             });
@@ -737,6 +873,135 @@ pub const Builder = struct {
         return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true);
     }
 
+    pub fn genControlFlowExpr(
+        irb: *Builder,
+        control_flow_expr: *ast.Node.ControlFlowExpression,
+        scope: *Scope,
+        lval: LVal,
+    ) !*Inst {
+        switch (control_flow_expr.kind) {
+            ast.Node.ControlFlowExpression.Kind.Break => |arg| return error.Unimplemented,
+            ast.Node.ControlFlowExpression.Kind.Continue => |arg| return error.Unimplemented,
+            ast.Node.ControlFlowExpression.Kind.Return => {
+                const src_span = Span.token(control_flow_expr.ltoken);
+                if (scope.findFnDef() == null) {
+                    try irb.comp.addCompileError(
+                        irb.parsed_file,
+                        src_span,
+                        "return expression outside function definition",
+                    );
+                    return error.SemanticAnalysisFailed;
+                }
+
+                if (scope.findDeferExpr()) |scope_defer_expr| {
+                    if (!scope_defer_expr.reported_err) {
+                        try irb.comp.addCompileError(
+                            irb.parsed_file,
+                            src_span,
+                            "cannot return from defer expression",
+                        );
+                        scope_defer_expr.reported_err = true;
+                    }
+                    return error.SemanticAnalysisFailed;
+                }
+
+                const outer_scope = irb.begin_scope.?;
+                const return_value = if (control_flow_expr.rhs) |rhs| blk: {
+                    break :blk try irb.genNode(rhs, scope, LVal.None);
+                } else blk: {
+                    break :blk try irb.buildConstVoid(scope, src_span, true);
+                };
+
+                const defer_counts = irb.countDefers(scope, outer_scope);
+                const have_err_defers = defer_counts.error_exit != 0;
+                if (have_err_defers or irb.comp.have_err_ret_tracing) {
+                    const err_block = try irb.createBasicBlock(scope, c"ErrRetErr");
+                    const ok_block = try irb.createBasicBlock(scope, c"ErrRetOk");
+                    if (!have_err_defers) {
+                        _ = try irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit);
+                    }
+
+                    const is_err = try irb.build(
+                        Inst.TestErr,
+                        scope,
+                        src_span,
+                        Inst.TestErr.Params{ .target = return_value },
+                    );
+
+                    const err_is_comptime = try irb.buildTestCompTime(scope, src_span, is_err);
+
+                    _ = try irb.buildGen(Inst.CondBr, scope, src_span, Inst.CondBr.Params{
+                        .condition = is_err,
+                        .then_block = err_block,
+                        .else_block = ok_block,
+                        .is_comptime = err_is_comptime,
+                    });
+
+                    const ret_stmt_block = try irb.createBasicBlock(scope, c"RetStmt");
+
+                    try irb.setCursorAtEndAndAppendBlock(err_block);
+                    if (have_err_defers) {
+                        _ = try irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ErrorExit);
+                    }
+                    if (irb.comp.have_err_ret_tracing and !irb.isCompTime(scope)) {
+                        _ = try irb.build(Inst.SaveErrRetAddr, scope, src_span, Inst.SaveErrRetAddr.Params{});
+                    }
+                    _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{
+                        .dest_block = ret_stmt_block,
+                        .is_comptime = err_is_comptime,
+                    });
+
+                    try irb.setCursorAtEndAndAppendBlock(ok_block);
+                    if (have_err_defers) {
+                        _ = try irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit);
+                    }
+                    _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{
+                        .dest_block = ret_stmt_block,
+                        .is_comptime = err_is_comptime,
+                    });
+
+                    try irb.setCursorAtEndAndAppendBlock(ret_stmt_block);
+                    return irb.genAsyncReturn(scope, src_span, return_value, false);
+                } else {
+                    _ = try irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit);
+                    return irb.genAsyncReturn(scope, src_span, return_value, false);
+                }
+            },
+        }
+    }
+
+    const DeferCounts = struct {
+        scope_exit: usize,
+        error_exit: usize,
+    };
+
+    fn countDefers(irb: *Builder, inner_scope: *Scope, outer_scope: *Scope) DeferCounts {
+        var result = DeferCounts{ .scope_exit = 0, .error_exit = 0 };
+
+        var scope = inner_scope;
+        while (scope != outer_scope) {
+            switch (scope.id) {
+                Scope.Id.Defer => {
+                    const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope);
+                    switch (defer_scope.kind) {
+                        Scope.Defer.Kind.ScopeExit => result.scope_exit += 1,
+                        Scope.Defer.Kind.ErrorExit => result.error_exit += 1,
+                    }
+                    scope = scope.parent orelse break;
+                },
+                Scope.Id.FnDef => break,
+
+                Scope.Id.CompTime,
+                Scope.Id.Block,
+                => scope = scope.parent orelse break,
+
+                Scope.Id.DeferExpr => unreachable,
+                Scope.Id.Decls => unreachable,
+            }
+        }
+        return result;
+    }
+
     fn genDefersForBlock(
         irb: *Builder,
         inner_scope: *Scope,
@@ -764,10 +1029,10 @@ pub const Builder = struct {
                             is_noreturn = true;
                         } else {
                             _ = try irb.build(
-                                Instruction.CheckVoidStmt,
+                                Inst.CheckVoidStmt,
                                 &defer_expr_scope.base,
                                 Span.token(defer_expr_scope.expr_node.lastToken()),
-                                Instruction.CheckVoidStmt.Params{ .target = instruction },
+                                Inst.CheckVoidStmt.Params{ .target = instruction },
                             );
                         }
                     }
@@ -785,13 +1050,13 @@ pub const Builder = struct {
         }
     }
 
-    pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Instruction, lval: LVal) !*Instruction {
+    pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Inst, lval: LVal) !*Inst {
         switch (lval) {
             LVal.None => return instruction,
             LVal.Ptr => {
                 // We needed a pointer to a value, but we got a value. So we create
                 // an instruction which just makes a const pointer of it.
-                return irb.build(Instruction.Ref, scope, instruction.span, Instruction.Ref.Params{
+                return irb.build(Inst.Ref, scope, instruction.span, Inst.Ref.Params{
                     .target = instruction,
                     .mut = Type.Pointer.Mut.Const,
                     .volatility = Type.Pointer.Vol.Non,
@@ -811,10 +1076,10 @@ pub const Builder = struct {
         span: Span,
         params: I.Params,
         is_generated: bool,
-    ) !*Instruction {
+    ) !*Inst {
         const inst = try self.arena().create(I{
-            .base = Instruction{
-                .id = Instruction.typeToId(I),
+            .base = Inst{
+                .id = Inst.typeToId(I),
                 .is_generated = is_generated,
                 .scope = scope,
                 .debug_id = self.next_debug_id,
@@ -838,8 +1103,8 @@ pub const Builder = struct {
         inline while (i < @memberCount(I.Params)) : (i += 1) {
             const FieldType = comptime @typeOf(@field(I.Params(undefined), @memberName(I.Params, i)));
             switch (FieldType) {
-                *Instruction => @field(inst.params, @memberName(I.Params, i)).ref(self),
-                ?*Instruction => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref(self),
+                *Inst => @field(inst.params, @memberName(I.Params, i)).ref(self),
+                ?*Inst => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref(self),
                 else => {},
             }
         }
@@ -855,7 +1120,7 @@ pub const Builder = struct {
         scope: *Scope,
         span: Span,
         params: I.Params,
-    ) !*Instruction {
+    ) !*Inst {
         return self.buildExtra(I, scope, span, params, false);
     }
 
@@ -865,21 +1130,71 @@ pub const Builder = struct {
         scope: *Scope,
         span: Span,
         params: I.Params,
-    ) !*Instruction {
+    ) !*Inst {
         return self.buildExtra(I, scope, span, params, true);
     }
 
-    fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Instruction {
-        const inst = try self.build(Instruction.Const, scope, span, Instruction.Const.Params{});
+    fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Inst {
+        const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{});
         inst.val = IrVal{ .KnownValue = &Value.Bool.get(self.comp, x).base };
         return inst;
     }
 
-    fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Instruction {
-        const inst = try self.buildExtra(Instruction.Const, scope, span, Instruction.Const.Params{}, is_generated);
+    fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Inst {
+        const inst = try self.buildExtra(Inst.Const, scope, span, Inst.Const.Params{}, is_generated);
         inst.val = IrVal{ .KnownValue = &Value.Void.get(self.comp).base };
         return inst;
     }
+
+    /// If the code is explicitly set to be comptime, then builds a const bool,
+    /// otherwise builds a TestCompTime instruction.
+    fn buildTestCompTime(self: *Builder, scope: *Scope, span: Span, target: *Inst) !*Inst {
+        if (self.isCompTime(scope)) {
+            return self.buildConstBool(scope, span, true);
+        } else {
+            return self.build(
+                Inst.TestCompTime,
+                scope,
+                span,
+                Inst.TestCompTime.Params{ .target = target },
+            );
+        }
+    }
+
+    fn genAsyncReturn(irb: *Builder, scope: *Scope, span: Span, result: *Inst, is_gen: bool) !*Inst {
+        _ = irb.buildGen(
+            Inst.AddImplicitReturnType,
+            scope,
+            span,
+            Inst.AddImplicitReturnType.Params{ .target = result },
+        );
+
+        if (!irb.is_async) {
+            return irb.buildExtra(
+                Inst.Return,
+                scope,
+                span,
+                Inst.Return.Params{ .return_value = result },
+                is_gen,
+            );
+        }
+        return error.Unimplemented;
+
+        //ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_field_ptr, return_value);
+        //IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node,
+        //        get_optional_type(irb->codegen, irb->codegen->builtin_types.entry_promise));
+        //// TODO replace replacement_value with @intToPtr(?promise, 0x1) when it doesn't crash zig
+        //IrInstruction *replacement_value = irb->exec->coro_handle;
+        //IrInstruction *maybe_await_handle = ir_build_atomic_rmw(irb, scope, node,
+        //        promise_type_val, irb->exec->coro_awaiter_field_ptr, nullptr, replacement_value, nullptr,
+        //        AtomicRmwOp_xchg, AtomicOrderSeqCst);
+        //ir_build_store_ptr(irb, scope, node, irb->exec->await_handle_var_ptr, maybe_await_handle);
+        //IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node, maybe_await_handle);
+        //IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
+        //return ir_build_cond_br(irb, scope, node, is_non_null, irb->exec->coro_normal_final, irb->exec->coro_early_final,
+        //        is_comptime);
+        //// the above blocks are rendered by ir_gen after the rest of codegen
+    }
 };
 
 const Analyze = struct {
@@ -888,7 +1203,7 @@ const Analyze = struct {
     const_predecessor_bb: ?*BasicBlock,
     parent_basic_block: *BasicBlock,
     instruction_index: usize,
-    src_implicit_return_type_list: std.ArrayList(*Instruction),
+    src_implicit_return_type_list: std.ArrayList(*Inst),
     explicit_return_type: ?*Type,
 
     pub const Error = error{
@@ -903,7 +1218,7 @@ const Analyze = struct {
     };
 
     pub fn init(comp: *Compilation, parsed_file: *ParsedFile, explicit_return_type: ?*Type) !Analyze {
-        var irb = try Builder.init(comp, parsed_file);
+        var irb = try Builder.init(comp, parsed_file, null);
         errdefer irb.abort();
 
         return Analyze{
@@ -912,7 +1227,7 @@ const Analyze = struct {
             .const_predecessor_bb = null,
             .parent_basic_block = undefined, // initialized with startBasicBlock
             .instruction_index = undefined, // initialized with startBasicBlock
-            .src_implicit_return_type_list = std.ArrayList(*Instruction).init(irb.arena()),
+            .src_implicit_return_type_list = std.ArrayList(*Inst).init(irb.arena()),
             .explicit_return_type = explicit_return_type,
         };
     }
@@ -921,7 +1236,7 @@ const Analyze = struct {
         self.irb.abort();
     }
 
-    pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Instruction) !*BasicBlock {
+    pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Inst) !*BasicBlock {
         if (old_bb.child) |child| {
             if (ref_old_instruction == null or child.ref_instruction != ref_old_instruction)
                 return child;
@@ -984,18 +1299,18 @@ const Analyze = struct {
         return self.irb.comp.addCompileError(self.irb.parsed_file, span, fmt, args);
     }
 
-    fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Instruction) Analyze.Error!*Type {
+    fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Inst) Analyze.Error!*Type {
         // TODO actual implementation
         return &Type.Void.get(self.irb.comp).base;
     }
 
-    fn implicitCast(self: *Analyze, target: *Instruction, optional_dest_type: ?*Type) Analyze.Error!*Instruction {
+    fn implicitCast(self: *Analyze, target: *Inst, optional_dest_type: ?*Type) Analyze.Error!*Inst {
         const dest_type = optional_dest_type orelse return target;
-        @panic("TODO implicitCast");
+        return error.Unimplemented;
     }
 
-    fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Instruction) ?*Value {
-        @panic("TODO getCompTimeValOrNullUndefOk");
+    fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Inst) ?*Value {
+        @panic("TODO");
     }
 
     fn getCompTimeRef(
@@ -1005,8 +1320,8 @@ const Analyze = struct {
         mut: Type.Pointer.Mut,
         volatility: Type.Pointer.Vol,
         ptr_align: u32,
-    ) Analyze.Error!*Instruction {
-        @panic("TODO getCompTimeRef");
+    ) Analyze.Error!*Inst {
+        return error.Unimplemented;
     }
 };
 
@@ -1014,10 +1329,9 @@ pub async fn gen(
     comp: *Compilation,
     body_node: *ast.Node,
     scope: *Scope,
-    end_span: Span,
     parsed_file: *ParsedFile,
 ) !*Code {
-    var irb = try Builder.init(comp, parsed_file);
+    var irb = try Builder.init(comp, parsed_file, scope);
     errdefer irb.abort();
 
     const entry_block = try irb.createBasicBlock(scope, c"Entry");
@@ -1026,18 +1340,8 @@ pub async fn gen(
 
     const result = try irb.genNode(body_node, scope, LVal.None);
     if (!result.isNoReturn()) {
-        _ = irb.buildGen(
-            Instruction.AddImplicitReturnType,
-            scope,
-            end_span,
-            Instruction.AddImplicitReturnType.Params{ .target = result },
-        );
-        _ = irb.buildGen(
-            Instruction.Return,
-            scope,
-            end_span,
-            Instruction.Return.Params{ .return_value = result },
-        );
+        // no need for save_err_ret_addr because this cannot return error
+        _ = try irb.genAsyncReturn(scope, Span.token(body_node.lastToken()), result, true);
     }
 
     return irb.finish();
src-self-hosted/scope.zig
@@ -49,6 +49,24 @@ pub const Scope = struct {
         }
     }
 
+    pub fn findDeferExpr(base: *Scope) ?*DeferExpr {
+        var scope = base;
+        while (true) {
+            switch (scope.id) {
+                Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", base),
+
+                Id.FnDef,
+                Id.Decls,
+                => return null,
+
+                Id.Block,
+                Id.Defer,
+                Id.CompTime,
+                => scope = scope.parent orelse return null,
+            }
+        }
+    }
+
     pub const Id = enum {
         Decls,
         Block,
@@ -90,10 +108,10 @@ pub const Scope = struct {
 
     pub const Block = struct {
         base: Scope,
-        incoming_values: std.ArrayList(*ir.Instruction),
+        incoming_values: std.ArrayList(*ir.Inst),
         incoming_blocks: std.ArrayList(*ir.BasicBlock),
         end_block: *ir.BasicBlock,
-        is_comptime: *ir.Instruction,
+        is_comptime: *ir.Inst,
 
         safety: Safety,
 
@@ -242,6 +260,7 @@ pub const Scope = struct {
     pub const DeferExpr = struct {
         base: Scope,
         expr_node: *ast.Node,
+        reported_err: bool,
 
         /// Creates a DeferExpr scope with 1 reference
         pub fn create(comp: *Compilation, parent: ?*Scope, expr_node: *ast.Node) !*DeferExpr {
@@ -252,6 +271,7 @@ pub const Scope = struct {
                     .ref_count = 1,
                 },
                 .expr_node = expr_node,
+                .reported_err = false,
             });
             errdefer comp.gpa().destroy(self);
 
test/stage2/compile_errors.zig
@@ -9,4 +9,16 @@ pub fn addCases(ctx: *TestContext) !void {
     try ctx.testCompileError(
         \\fn() void {}
     , "1.zig", 1, 1, "missing function name");
+
+    try ctx.testCompileError(
+        \\comptime {
+        \\    return;
+        \\}
+    , "1.zig", 2, 5, "return expression outside function definition");
+
+    try ctx.testCompileError(
+        \\export fn entry() void {
+        \\    defer return;
+        \\}
+    , "1.zig", 2, 11, "cannot return from defer expression");
 }