Commit e323cf1264

Veikka Tuominen <git@vexu.eu>
2022-08-27 17:48:01
stage2: change how defers are stored in Zir
Storing defers this way has the benefits that the defer doesn't get analyzed multiple times in AstGen, it takes up less space, and it makes Sema aware of defers allowing for 'unreachable else prong' error on error sets in generic code. The disadvantage is that it is a bit more complex and errdefers with payloads now emit a placeholder instruction (but those are rare). Sema.zig before: Total ZIR bytes: 3.7794370651245117MiB Instructions: 238996 (2.051319122314453MiB) String Table Bytes: 89.2802734375KiB Extra Data Items: 430144 (1.640869140625MiB) Sema.zig after: Total ZIR bytes: 3.3344192504882812MiB Instructions: 211829 (1.8181428909301758MiB) String Table Bytes: 89.2802734375KiB Extra Data Items: 374611 (1.4290275573730469MiB)
1 parent c97d64b
doc/langref.html.in
@@ -5700,7 +5700,6 @@ fn bar() !void {
         try quux();
     } else |err| switch (err) {
         error.FileNotFound => try hello(),
-        else => try another(),
     }
 }
 
@@ -5716,10 +5715,6 @@ fn hello() !void {
     try bang2();
 }
 
-fn another() !void {
-    try bang1();
-}
-
 fn bang1() !void {
     return error.FileNotFound;
 }
src/AstGen.zig
@@ -135,7 +135,6 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
     var gz_instructions: std.ArrayListUnmanaged(Zir.Inst.Index) = .{};
     var gen_scope: GenZir = .{
         .force_comptime = true,
-        .in_defer = false,
         .parent = &top_scope.base,
         .anon_name_strategy = .parent,
         .decl_node_index = 0,
@@ -1856,6 +1855,16 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
             .gen_zir => {
                 const block_gz = scope.cast(GenZir).?;
 
+                if (block_gz.cur_defer_node != 0) {
+                    return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
+                        try astgen.errNoteNode(
+                            block_gz.cur_defer_node,
+                            "defer expression here",
+                            .{},
+                        ),
+                    });
+                }
+
                 const block_inst = blk: {
                     if (break_label != 0) {
                         if (block_gz.label) |*label| {
@@ -1919,17 +1928,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
             .namespace => break,
             .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
-            .defer_gen => {
-                const defer_gen = scope.cast(Scope.DeferGen).?;
-
-                return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
-                    try astgen.errNoteNode(
-                        defer_gen.defer_node,
-                        "defer expression here",
-                        .{},
-                    ),
-                });
-            },
             .top => unreachable,
         }
     }
@@ -1953,6 +1951,16 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
         switch (scope.tag) {
             .gen_zir => {
                 const gen_zir = scope.cast(GenZir).?;
+
+                if (gen_zir.cur_defer_node != 0) {
+                    return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
+                        try astgen.errNoteNode(
+                            gen_zir.cur_defer_node,
+                            "defer expression here",
+                            .{},
+                        ),
+                    });
+                }
                 const continue_block = gen_zir.continue_block;
                 if (continue_block == 0) {
                     scope = gen_zir.parent;
@@ -1985,21 +1993,9 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
             .defer_normal => {
                 const defer_scope = scope.cast(Scope.Defer).?;
                 scope = defer_scope.parent;
-                const expr_node = node_datas[defer_scope.defer_node].rhs;
-                try unusedResultDeferExpr(parent_gz, defer_scope, defer_scope.parent, expr_node);
+                try parent_gz.addDefer(defer_scope.index, defer_scope.len);
             },
             .defer_error => scope = scope.cast(Scope.Defer).?.parent,
-            .defer_gen => {
-                const defer_gen = scope.cast(Scope.DeferGen).?;
-
-                return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
-                    try astgen.errNoteNode(
-                        defer_gen.defer_node,
-                        "defer expression here",
-                        .{},
-                    ),
-                });
-            },
             .namespace => break,
             .top => unreachable,
         }
@@ -2064,7 +2060,6 @@ fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: Ast.Toke
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
             .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
-            .defer_gen => scope = scope.cast(Scope.DeferGen).?.parent,
             .namespace => break,
             .top => unreachable,
         }
@@ -2208,8 +2203,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
             .simple_var_decl  => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.simpleVarDecl(statement)),
             .aligned_var_decl => scope = try varDecl(gz, scope, statement, block_arena_allocator, tree.alignedVarDecl(statement)),
 
-            .@"defer"    => scope = try makeDeferScope(gz.astgen, scope, statement, block_arena_allocator, .defer_normal),
-            .@"errdefer" => scope = try makeDeferScope(gz.astgen, scope, statement, block_arena_allocator, .defer_error),
+            .@"defer"    => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_normal),
+            .@"errdefer" => scope = try deferStmt(gz, scope, statement, block_arena_allocator, .defer_error),
 
             .assign => try assign(gz, scope, statement),
 
@@ -2253,28 +2248,6 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
     try checkUsed(gz, parent_scope, scope);
 }
 
-fn unusedResultDeferExpr(gz: *GenZir, defer_scope: *Scope.Defer, expr_scope: *Scope, expr_node: Ast.Node.Index) InnerError!void {
-    const astgen = gz.astgen;
-    const prev_offset = astgen.source_offset;
-    const prev_line = astgen.source_line;
-    const prev_column = astgen.source_column;
-    defer {
-        astgen.source_offset = prev_offset;
-        astgen.source_line = prev_line;
-        astgen.source_column = prev_column;
-    }
-    astgen.source_offset = defer_scope.source_offset;
-    astgen.source_line = defer_scope.source_line;
-    astgen.source_column = defer_scope.source_column;
-
-    var defer_gen: Scope.DeferGen = .{
-        .parent = expr_scope,
-        .defer_node = defer_scope.defer_node,
-    };
-
-    _ = try unusedResultExpr(gz, &defer_gen.base, expr_node);
-}
-
 /// Returns AST source node of the thing that is noreturn if the statement is
 /// definitely `noreturn`. Otherwise returns 0.
 fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) InnerError!Ast.Node.Index {
@@ -2584,6 +2557,9 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
             .validate_struct_init_ty,
             .validate_deref,
             => break :b true,
+
+            .@"defer" => unreachable,
+            .defer_err_code => unreachable,
         }
     } else switch (maybe_unused_result) {
         .none => unreachable,
@@ -2603,15 +2579,12 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
     return noreturn_src_node;
 }
 
-fn countDefers(astgen: *AstGen, outer_scope: *Scope, inner_scope: *Scope) struct {
+fn countDefers(outer_scope: *Scope, inner_scope: *Scope) struct {
     have_any: bool,
     have_normal: bool,
     have_err: bool,
     need_err_code: bool,
 } {
-    const tree = astgen.tree;
-    const node_datas = tree.nodes.items(.data);
-
     var have_normal = false;
     var have_err = false;
     var need_err_code = false;
@@ -2621,7 +2594,6 @@ fn countDefers(astgen: *AstGen, outer_scope: *Scope, inner_scope: *Scope) struct
             .gen_zir => scope = scope.cast(GenZir).?.parent,
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
-            .defer_gen => scope = scope.cast(Scope.DeferGen).?.parent,
             .defer_normal => {
                 const defer_scope = scope.cast(Scope.Defer).?;
                 scope = defer_scope.parent;
@@ -2634,7 +2606,7 @@ fn countDefers(astgen: *AstGen, outer_scope: *Scope, inner_scope: *Scope) struct
 
                 have_err = true;
 
-                const have_err_payload = node_datas[defer_scope.defer_node].lhs != 0;
+                const have_err_payload = defer_scope.remapped_err_code != 0;
                 need_err_code = need_err_code or have_err_payload;
             },
             .namespace => unreachable,
@@ -2661,9 +2633,7 @@ fn genDefers(
     inner_scope: *Scope,
     which_ones: DefersToEmit,
 ) InnerError!void {
-    const astgen = gz.astgen;
-    const tree = astgen.tree;
-    const node_datas = tree.nodes.items(.data);
+    const gpa = gz.astgen.gpa;
 
     var scope = inner_scope;
     while (scope != outer_scope) {
@@ -2671,51 +2641,40 @@ fn genDefers(
             .gen_zir => scope = scope.cast(GenZir).?.parent,
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
-            .defer_gen => scope = scope.cast(Scope.DeferGen).?.parent,
             .defer_normal => {
                 const defer_scope = scope.cast(Scope.Defer).?;
                 scope = defer_scope.parent;
-                const expr_node = node_datas[defer_scope.defer_node].rhs;
-                const prev_in_defer = gz.in_defer;
-                gz.in_defer = true;
-                defer gz.in_defer = prev_in_defer;
-                try unusedResultDeferExpr(gz, defer_scope, defer_scope.parent, expr_node);
+                try gz.addDefer(defer_scope.index, defer_scope.len);
             },
             .defer_error => {
                 const defer_scope = scope.cast(Scope.Defer).?;
                 scope = defer_scope.parent;
                 switch (which_ones) {
                     .both_sans_err => {
-                        const expr_node = node_datas[defer_scope.defer_node].rhs;
-                        const prev_in_defer = gz.in_defer;
-                        gz.in_defer = true;
-                        defer gz.in_defer = prev_in_defer;
-                        try unusedResultDeferExpr(gz, defer_scope, defer_scope.parent, expr_node);
+                        try gz.addDefer(defer_scope.index, defer_scope.len);
                     },
                     .both => |err_code| {
-                        const expr_node = node_datas[defer_scope.defer_node].rhs;
-                        const payload_token = node_datas[defer_scope.defer_node].lhs;
-                        const prev_in_defer = gz.in_defer;
-                        gz.in_defer = true;
-                        defer gz.in_defer = prev_in_defer;
-                        var local_val_scope: Scope.LocalVal = undefined;
-                        try gz.addDbgBlockBegin();
-                        const sub_scope = if (payload_token == 0) defer_scope.parent else blk: {
-                            const ident_name = try astgen.identAsString(payload_token);
-                            local_val_scope = .{
-                                .parent = defer_scope.parent,
-                                .gen_zir = gz,
-                                .name = ident_name,
-                                .inst = err_code,
-                                .token_src = payload_token,
-                                .id_cat = .@"capture",
-                            };
-                            try gz.addDbgVar(.dbg_var_val, ident_name, err_code);
-                            break :blk &local_val_scope.base;
-                        };
-                        try unusedResultDeferExpr(gz, defer_scope, sub_scope, expr_node);
-                        try checkUsed(gz, scope, sub_scope);
-                        try gz.addDbgBlockEnd();
+                        if (defer_scope.remapped_err_code == 0) {
+                            try gz.addDefer(defer_scope.index, defer_scope.len);
+                        } else {
+                            try gz.instructions.ensureUnusedCapacity(gpa, 1);
+                            try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
+
+                            const payload_index = try gz.astgen.addExtra(Zir.Inst.DeferErrCode{
+                                .remapped_err_code = defer_scope.remapped_err_code,
+                                .index = defer_scope.index,
+                                .len = defer_scope.len,
+                            });
+                            const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
+                            gz.astgen.instructions.appendAssumeCapacity(.{
+                                .tag = .defer_err_code,
+                                .data = .{ .defer_err_code = .{
+                                    .err_code = err_code,
+                                    .payload_index = payload_index,
+                                } },
+                            });
+                            gz.instructions.appendAssumeCapacity(new_index);
+                        }
                     },
                     .normal_only => continue,
                 }
@@ -2752,35 +2711,68 @@ fn checkUsed(
                 scope = s.parent;
             },
             .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
-            .defer_gen => scope = scope.cast(Scope.DeferGen).?.parent,
             .namespace => unreachable,
             .top => unreachable,
         }
     }
 }
 
-fn makeDeferScope(
-    astgen: *AstGen,
+fn deferStmt(
+    gz: *GenZir,
     scope: *Scope,
     node: Ast.Node.Index,
     block_arena: Allocator,
     scope_tag: Scope.Tag,
 ) InnerError!*Scope {
-    const tree = astgen.tree;
+    var defer_gen = gz.makeSubBlock(scope);
+    defer_gen.cur_defer_node = node;
+    defer_gen.any_defer_node = node;
+    defer defer_gen.unstack();
+
+    const tree = gz.astgen.tree;
     const node_datas = tree.nodes.items(.data);
     const expr_node = node_datas[node].rhs;
-    const token_starts = tree.tokens.items(.start);
-    const node_start = token_starts[tree.firstToken(expr_node)];
+
+    const payload_token = node_datas[node].lhs;
+    var local_val_scope: Scope.LocalVal = undefined;
+    var remapped_err_code: Zir.Inst.Index = 0;
+    const have_err_code = scope_tag == .defer_error and payload_token != 0;
+    const sub_scope = if (!have_err_code) &defer_gen.base else blk: {
+        try gz.addDbgBlockBegin();
+        const ident_name = try gz.astgen.identAsString(payload_token);
+        remapped_err_code = @intCast(u32, try gz.astgen.instructions.addOne(gz.astgen.gpa));
+        const remapped_err_code_ref = Zir.indexToRef(remapped_err_code);
+        local_val_scope = .{
+            .parent = &defer_gen.base,
+            .gen_zir = gz,
+            .name = ident_name,
+            .inst = remapped_err_code_ref,
+            .token_src = payload_token,
+            .id_cat = .@"capture",
+        };
+        try gz.addDbgVar(.dbg_var_val, ident_name, remapped_err_code_ref);
+        break :blk &local_val_scope.base;
+    };
+    _ = try unusedResultExpr(&defer_gen, sub_scope, expr_node);
+    try checkUsed(gz, scope, sub_scope);
+    if (have_err_code) try gz.addDbgBlockEnd();
+    _ = try defer_gen.addBreak(.break_inline, 0, .void_value);
+
+    const body = defer_gen.instructionsSlice();
+    const body_len = gz.astgen.countBodyLenAfterFixups(body);
+
+    const index = @intCast(u32, gz.astgen.extra.items.len);
+    try gz.astgen.extra.ensureUnusedCapacity(gz.astgen.gpa, body_len);
+    gz.astgen.appendBodyWithFixups(body);
+
     const defer_scope = try block_arena.create(Scope.Defer);
-    astgen.advanceSourceCursor(node_start);
 
     defer_scope.* = .{
         .base = .{ .tag = scope_tag },
         .parent = scope,
-        .defer_node = node,
-        .source_offset = astgen.source_offset,
-        .source_line = astgen.source_line,
-        .source_column = astgen.source_column,
+        .index = index,
+        .len = body_len,
+        .remapped_err_code = remapped_err_code,
     };
     return &defer_scope.base;
 }
@@ -3461,7 +3453,6 @@ fn fnDecl(
 
     var decl_gz: GenZir = .{
         .force_comptime = true,
-        .in_defer = false,
         .decl_node_index = fn_proto.ast.proto_node,
         .decl_line = astgen.source_line,
         .parent = scope,
@@ -3473,7 +3464,6 @@ fn fnDecl(
 
     var fn_gz: GenZir = .{
         .force_comptime = false,
-        .in_defer = false,
         .decl_node_index = fn_proto.ast.proto_node,
         .decl_line = decl_gz.decl_line,
         .parent = &decl_gz.base,
@@ -3812,7 +3802,6 @@ fn globalVarDecl(
         .decl_line = astgen.source_line,
         .astgen = astgen,
         .force_comptime = true,
-        .in_defer = false,
         .anon_name_strategy = .parent,
         .instructions = gz.instructions,
         .instructions_top = gz.instructions.items.len,
@@ -3964,7 +3953,6 @@ fn comptimeDecl(
 
     var decl_block: GenZir = .{
         .force_comptime = true,
-        .in_defer = false,
         .decl_node_index = node,
         .decl_line = astgen.source_line,
         .parent = scope,
@@ -4019,7 +4007,6 @@ fn usingnamespaceDecl(
 
     var decl_block: GenZir = .{
         .force_comptime = true,
-        .in_defer = false,
         .decl_node_index = node,
         .decl_line = astgen.source_line,
         .parent = scope,
@@ -4067,7 +4054,6 @@ fn testDecl(
 
     var decl_block: GenZir = .{
         .force_comptime = true,
-        .in_defer = false,
         .decl_node_index = node,
         .decl_line = astgen.source_line,
         .parent = scope,
@@ -4132,7 +4118,6 @@ fn testDecl(
                 .local_val, .local_ptr => unreachable, // a test cannot be in a local scope
                 .gen_zir => s = s.cast(GenZir).?.parent,
                 .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
-                .defer_gen => s = s.cast(Scope.DeferGen).?.parent,
                 .namespace => {
                     const ns = s.cast(Scope.Namespace).?;
                     if (ns.decls.get(name_str_index)) |i| {
@@ -4164,7 +4149,6 @@ fn testDecl(
 
     var fn_block: GenZir = .{
         .force_comptime = false,
-        .in_defer = false,
         .decl_node_index = node,
         .decl_line = decl_block.decl_line,
         .parent = &decl_block.base,
@@ -4288,7 +4272,6 @@ fn structDeclInner(
         .decl_line = decl_line,
         .astgen = astgen,
         .force_comptime = true,
-        .in_defer = false,
         .instructions = gz.instructions,
         .instructions_top = gz.instructions.items.len,
     };
@@ -4489,7 +4472,6 @@ fn unionDeclInner(
         .decl_line = astgen.source_line,
         .astgen = astgen,
         .force_comptime = true,
-        .in_defer = false,
         .instructions = gz.instructions,
         .instructions_top = gz.instructions.items.len,
     };
@@ -4773,7 +4755,6 @@ fn containerDecl(
                 .decl_line = astgen.source_line,
                 .astgen = astgen,
                 .force_comptime = true,
-                .in_defer = false,
                 .instructions = gz.instructions,
                 .instructions_top = gz.instructions.items.len,
             };
@@ -4879,7 +4860,6 @@ fn containerDecl(
                 .decl_line = astgen.source_line,
                 .astgen = astgen,
                 .force_comptime = true,
-                .in_defer = false,
                 .instructions = gz.instructions,
                 .instructions_top = gz.instructions.items.len,
             };
@@ -5110,7 +5090,15 @@ fn tryExpr(
         return astgen.failNode(node, "'try' outside function scope", .{});
     };
 
-    if (parent_gz.in_defer) return astgen.failNode(node, "'try' not allowed inside defer expression", .{});
+    if (parent_gz.any_defer_node != 0) {
+        return astgen.failNodeNotes(node, "'try' not allowed inside defer expression", .{}, &.{
+            try astgen.errNoteNode(
+                parent_gz.any_defer_node,
+                "defer expression here",
+                .{},
+            ),
+        });
+    }
 
     // Ensure debug line/column information is emitted for this try expression.
     // Then we will save the line/column so that we can emit another one that goes
@@ -6656,7 +6644,15 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
         return astgen.failNode(node, "'return' outside function scope", .{});
     }
 
-    if (gz.in_defer) return astgen.failNode(node, "cannot return from defer expression", .{});
+    if (gz.any_defer_node != 0) {
+        return astgen.failNodeNotes(node, "cannot return from defer expression", .{}, &.{
+            try astgen.errNoteNode(
+                gz.any_defer_node,
+                "defer expression here",
+                .{},
+            ),
+        });
+    }
 
     // Ensure debug line/column information is emitted for this return expression.
     // Then we will save the line/column so that we can emit another one that goes
@@ -6683,7 +6679,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
         // for detecting whether to add something to the function's inferred error set.
         const ident_token = node_datas[operand_node].rhs;
         const err_name_str_index = try astgen.identAsString(ident_token);
-        const defer_counts = countDefers(astgen, defer_outer, scope);
+        const defer_counts = countDefers(defer_outer, scope);
         if (!defer_counts.need_err_code) {
             try genDefers(gz, defer_outer, scope, .both_sans_err);
             try emitDbgStmt(gz, ret_line, ret_column);
@@ -6724,7 +6720,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
             return Zir.Inst.Ref.unreachable_value;
         },
         .maybe => {
-            const defer_counts = countDefers(astgen, defer_outer, scope);
+            const defer_counts = countDefers(defer_outer, scope);
             if (!defer_counts.have_err) {
                 // Only regular defers; no branch needed.
                 try genDefers(gz, defer_outer, scope, .normal_only);
@@ -6921,7 +6917,6 @@ fn localVarRef(
         },
         .gen_zir => s = s.cast(GenZir).?.parent,
         .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
-        .defer_gen => s = s.cast(Scope.DeferGen).?.parent,
         .namespace => {
             const ns = s.cast(Scope.Namespace).?;
             if (ns.decls.get(name_str_index)) |i| {
@@ -7550,7 +7545,6 @@ fn builtinCall(
                         },
                         .gen_zir => s = s.cast(GenZir).?.parent,
                         .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
-                        .defer_gen => s = s.cast(Scope.DeferGen).?.parent,
                         .namespace => {
                             const ns = s.cast(Scope.Namespace).?;
                             if (ns.decls.get(decl_name)) |i| {
@@ -10046,7 +10040,6 @@ const Scope = struct {
         local_ptr,
         defer_normal,
         defer_error,
-        defer_gen,
         namespace,
         top,
     };
@@ -10105,10 +10098,9 @@ const Scope = struct {
         base: Scope,
         /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`.
         parent: *Scope,
-        defer_node: Ast.Node.Index,
-        source_offset: u32,
-        source_line: u32,
-        source_column: u32,
+        index: u32,
+        len: u32,
+        remapped_err_code: Zir.Inst.Index = 0,
     };
 
     /// Represents a global scope that has any number of declarations in it.
@@ -10144,13 +10136,6 @@ const Scope = struct {
         const base_tag: Scope.Tag = .top;
         base: Scope = Scope{ .tag = base_tag },
     };
-
-    const DeferGen = struct {
-        const base_tag: Scope.Tag = .defer_gen;
-        base: Scope = Scope{ .tag = base_tag },
-        parent: *Scope,
-        defer_node: Ast.Node.Index,
-    };
 };
 
 /// This is a temporary structure; references to it are valid only
@@ -10161,7 +10146,6 @@ const GenZir = struct {
     force_comptime: bool,
     /// This is set to true for inline loops; false otherwise.
     is_inline: bool = false,
-    in_defer: bool,
     c_import: bool = false,
     /// How decls created in this scope should be named.
     anon_name_strategy: Zir.Inst.NameStrategy = .anon,
@@ -10204,6 +10188,10 @@ const GenZir = struct {
 
     suspend_node: Ast.Node.Index = 0,
     nosuspend_node: Ast.Node.Index = 0,
+    /// Set if this GenZir is a defer.
+    cur_defer_node: Ast.Node.Index = 0,
+    // Set if this GenZir is a defer or it is inside a defer.
+    any_defer_node: Ast.Node.Index = 0,
 
     /// Namespace members are lazy.  When executing a decl within a namespace,
     /// any references to external instructions need to be treated specially.
@@ -10244,7 +10232,6 @@ const GenZir = struct {
     fn makeSubBlock(gz: *GenZir, scope: *Scope) GenZir {
         return .{
             .force_comptime = gz.force_comptime,
-            .in_defer = gz.in_defer,
             .c_import = gz.c_import,
             .decl_node_index = gz.decl_node_index,
             .decl_line = gz.decl_line,
@@ -10253,6 +10240,7 @@ const GenZir = struct {
             .astgen = gz.astgen,
             .suspend_node = gz.suspend_node,
             .nosuspend_node = gz.nosuspend_node,
+            .any_defer_node = gz.any_defer_node,
             .instructions = gz.instructions,
             .instructions_top = gz.instructions.items.len,
         };
@@ -11107,6 +11095,16 @@ const GenZir = struct {
         });
     }
 
+    fn addDefer(gz: *GenZir, index: u32, len: u32) !void {
+        _ = try gz.add(.{
+            .tag = .@"defer",
+            .data = .{ .@"defer" = .{
+                .index = index,
+                .len = len,
+            } },
+        });
+    }
+
     fn addDecl(
         gz: *GenZir,
         tag: Zir.Inst.Tag,
@@ -11670,7 +11668,6 @@ fn detectLocalShadowing(
         },
         .gen_zir => s = s.cast(GenZir).?.parent,
         .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
-        .defer_gen => s = s.cast(Scope.DeferGen).?.parent,
         .top => break,
     };
 }
@@ -11809,7 +11806,6 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast.
             .namespace => s = s.cast(Scope.Namespace).?.parent,
             .gen_zir => s = s.cast(GenZir).?.parent,
             .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
-            .defer_gen => s = s.cast(Scope.DeferGen).?.parent,
             .top => break,
         };
         gop.value_ptr.* = member_node;
src/print_zir.zig
@@ -446,6 +446,9 @@ const Writer = struct {
 
             .closure_get => try self.writeInstNode(stream, inst),
 
+            .@"defer" => try self.writeDefer(stream, inst),
+            .defer_err_code => try self.writeDeferErrCode(stream, inst),
+
             .extended => try self.writeExtended(stream, inst),
         }
     }
@@ -2364,6 +2367,26 @@ const Writer = struct {
         try stream.print("{d}, {d})", .{ inst_data.line + 1, inst_data.column + 1 });
     }
 
+    fn writeDefer(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
+        const inst_data = self.code.instructions.items(.data)[inst].@"defer";
+        const body = self.code.extra[inst_data.index..][0..inst_data.len];
+        try self.writeBracedBody(stream, body);
+        try stream.writeByte(')');
+    }
+
+    fn writeDeferErrCode(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
+        const inst_data = self.code.instructions.items(.data)[inst].defer_err_code;
+        const extra = self.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data;
+
+        try self.writeInstRef(stream, Zir.indexToRef(extra.remapped_err_code));
+        try stream.writeAll(" = ");
+        try self.writeInstRef(stream, inst_data.err_code);
+        try stream.writeAll(", ");
+        const body = self.code.extra[extra.index..][0..extra.len];
+        try self.writeBracedBody(stream, body);
+        try stream.writeByte(')');
+    }
+
     fn writeInstRef(self: *Writer, stream: anytype, ref: Zir.Inst.Ref) !void {
         var i: usize = @enumToInt(ref);
 
src/Sema.zig
@@ -1461,6 +1461,29 @@ fn analyzeBodyInner(
             //        break break_data.inst;
             //    }
             //},
+            .@"defer" => blk: {
+                const inst_data = sema.code.instructions.items(.data)[inst].@"defer";
+                const defer_body = sema.code.extra[inst_data.index..][0..inst_data.len];
+                const break_inst = sema.analyzeBodyInner(block, defer_body) catch |err| switch (err) {
+                    error.ComptimeBreak => sema.comptime_break_inst,
+                    else => |e| return e,
+                };
+                if (break_inst != defer_body[defer_body.len - 1]) break always_noreturn;
+                break :blk Air.Inst.Ref.void_value;
+            },
+            .defer_err_code => blk: {
+                const inst_data = sema.code.instructions.items(.data)[inst].defer_err_code;
+                const extra = sema.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data;
+                const defer_body = sema.code.extra[extra.index..][0..extra.len];
+                const err_code = try sema.resolveInst(inst_data.err_code);
+                try sema.inst_map.put(sema.gpa, extra.remapped_err_code, err_code);
+                const break_inst = sema.analyzeBodyInner(block, defer_body) catch |err| switch (err) {
+                    error.ComptimeBreak => sema.comptime_break_inst,
+                    else => |e| return e,
+                };
+                if (break_inst != defer_body[defer_body.len - 1]) break always_noreturn;
+                break :blk Air.Inst.Ref.void_value;
+            },
         };
         if (sema.typeOf(air_inst).isNoReturn())
             break always_noreturn;
@@ -9394,11 +9417,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 }
 
                 if (special_prong == .@"else" and seen_errors.count() == operand_ty.errorSetNames().len) {
-
-                    // TODO re-enable if defer implementation is improved
-                    // https://github.com/ziglang/zig/issues/11798
-                    if (true) break :else_validation;
-
                     // In order to enable common patterns for generic code allow simple else bodies
                     // else => unreachable,
                     // else => return,
@@ -9415,6 +9433,12 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                         .as_node,
                         .ret_node,
                         .@"unreachable",
+                        .@"defer",
+                        .defer_err_code,
+                        .err_union_code,
+                        .ret_err_value_code,
+                        .is_non_err,
+                        .condbr,
                         => {},
                         else => break,
                     } else break :else_validation;
src/Zir.zig
@@ -995,6 +995,13 @@ pub const Inst = struct {
         /// closure_capture instruction ref.
         closure_get,
 
+        /// A defer statement.
+        /// Uses the `defer` union field.
+        @"defer",
+        /// An errdefer statement with a code.
+        /// Uses the `err_defer_code` union field.
+        defer_err_code,
+
         /// The ZIR instruction tag is one of the `Extended` ones.
         /// Uses the `extended` union field.
         extended,
@@ -1244,6 +1251,8 @@ pub const Inst = struct {
                 .try_ptr,
                 //.try_inline,
                 //.try_ptr_inline,
+                .@"defer",
+                .defer_err_code,
                 => false,
 
                 .@"break",
@@ -1311,6 +1320,8 @@ pub const Inst = struct {
                 .memcpy,
                 .memset,
                 .check_comptime_control_flow,
+                .@"defer",
+                .defer_err_code,
                 => true,
 
                 .param,
@@ -1819,6 +1830,9 @@ pub const Inst = struct {
                 .closure_capture = .un_tok,
                 .closure_get = .inst_node,
 
+                .@"defer" = .@"defer",
+                .defer_err_code = .defer_err_code,
+
                 .extended = .extended,
             });
         };
@@ -2575,6 +2589,14 @@ pub const Inst = struct {
                 return zir.nullTerminatedString(self.str);
             }
         },
+        @"defer": struct {
+            index: u32,
+            len: u32,
+        },
+        defer_err_code: struct {
+            err_code: Ref,
+            payload_index: u32,
+        },
 
         // Make sure we don't accidentally add a field to make this union
         // bigger than expected. Note that in Debug builds, Zig is allowed
@@ -2611,6 +2633,8 @@ pub const Inst = struct {
             dbg_stmt,
             inst_node,
             str_op,
+            @"defer",
+            defer_err_code,
         };
     };
 
@@ -3550,6 +3574,12 @@ pub const Inst = struct {
         line: u32,
         column: u32,
     };
+
+    pub const DeferErrCode = struct {
+        remapped_err_code: Index,
+        index: u32,
+        len: u32,
+    };
 };
 
 pub const SpecialProng = enum { none, @"else", under };
test/behavior/defer.zig
@@ -127,3 +127,20 @@ test "errdefer with payload" {
     try S.doTheTest();
     comptime try S.doTheTest();
 }
+
+test "simple else prong doesn't emit an error for unreachable else prong" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+
+    const S = struct {
+        fn foo() error{Foo}!void {
+            return error.Foo;
+        }
+    };
+    var a: u32 = 0;
+    defer a += 1;
+    S.foo() catch |err| switch (err) {
+        error.Foo => a += 1,
+        else => |e| return e,
+    };
+    try expect(a == 1);
+}
test/cases/compile_errors/return_from_defer_expression.zig
@@ -19,3 +19,4 @@ export fn entry() usize { return @sizeOf(@TypeOf(testTrickyDefer)); }
 // target=native
 //
 // :4:11: error: 'try' not allowed inside defer expression
+// :4:5: note: defer expression here
test/cases/compile_errors/uncreachable_else_prong_err_set.zig
@@ -0,0 +1,25 @@
+pub export fn complex() void {
+    var a: error{ Foo, Bar } = error.Foo;
+    switch (a) {
+        error.Foo => unreachable,
+        error.Bar => unreachable,
+        else => {
+            @compileError("<something complex here>");
+        },
+    }
+}
+
+pub export fn simple() void {
+    var a: error{ Foo, Bar } = error.Foo;
+    switch (a) {
+        error.Foo => unreachable,
+        error.Bar => unreachable,
+        else => |e| return e,
+    }
+}
+
+// error
+// backend=llvm
+// target=native
+//
+// :6:14: error: unreachable else prong; all cases already handled
test/cases/returns_in_try.zig
@@ -13,4 +13,6 @@ pub fn b() !void {
 // error
 //
 // :7:11: error: 'try' not allowed inside defer expression
+// :7:5: note: defer expression here
 // :10:11: error: cannot return from defer expression
+// :10:5: note: defer expression here