Commit a59bcae59f

Andrew Kelley <andrew@ziglang.org>
2021-04-21 01:32:10
AstGen: basic defer implementation
1 parent b4aec0e
src/AstGen.zig
@@ -37,6 +37,8 @@ string_table: std.StringHashMapUnmanaged(u32) = .{},
 compile_errors: ArrayListUnmanaged(Zir.Inst.CompileErrors.Item) = .{},
 /// String table indexes, keeps track of all `@import` operands.
 imports: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
+/// The topmost block of the current function.
+fn_block: ?*GenZir = null,
 
 pub fn addExtra(astgen: *AstGen, extra: anytype) Allocator.Error!u32 {
     const fields = std.meta.fields(@TypeOf(extra));
@@ -82,7 +84,7 @@ pub fn generate(gpa: *Allocator, file: *Scope.File) InnerError!Zir {
     // First few indexes of extra are reserved and set at the end.
     try astgen.extra.resize(gpa, @typeInfo(Zir.ExtraIndex).Enum.fields.len);
 
-    var gen_scope: Scope.GenZir = .{
+    var gen_scope: GenZir = .{
         .force_comptime = true,
         .parent = &file.base,
         .decl_node_index = 0,
@@ -461,6 +463,8 @@ pub fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) Inn
         .local_var_decl => unreachable, // Handled in `blockExpr`.
         .simple_var_decl => unreachable, // Handled in `blockExpr`.
         .aligned_var_decl => unreachable, // Handled in `blockExpr`.
+        .@"defer" => unreachable, // Handled in `blockExpr`.
+        .@"errdefer" => unreachable, // Handled in `blockExpr`.
 
         .switch_case => unreachable, // Handled in `switchExpr`.
         .switch_case_one => unreachable, // Handled in `switchExpr`.
@@ -818,8 +822,6 @@ pub fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) Inn
         .@"await" => return astgen.failNode(node, "async and related features are not yet supported", .{}),
         .@"resume" => return astgen.failNode(node, "async and related features are not yet supported", .{}),
 
-        .@"defer" => return astgen.failNode(node, "TODO implement astgen.expr for .defer", .{}),
-        .@"errdefer" => return astgen.failNode(node, "TODO implement astgen.expr for .errdefer", .{}),
         .@"try" => return tryExpr(gz, scope, rl, node_datas[node].lhs),
 
         .array_init_one, .array_init_one_comma => {
@@ -1245,6 +1247,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) Inn
             },
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+            .defer_normal => @panic("TODO break/defer"),
+            .defer_error => @panic("TODO break/defer"),
             else => if (break_label != 0) {
                 const label_name = try astgen.identifierTokenString(break_label);
                 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
@@ -1290,6 +1294,8 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index)
             },
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+            .defer_normal => @panic("TODO continue/defer"),
+            .defer_error => @panic("TODO continue/defer"),
             else => if (break_label != 0) {
                 const label_name = try astgen.identifierTokenString(break_label);
                 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
@@ -1354,6 +1360,7 @@ 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,
             else => return,
         }
     }
@@ -1393,6 +1400,7 @@ fn labeledBlockExpr(
         .decl_node_index = gz.decl_node_index,
         .astgen = gz.astgen,
         .force_comptime = gz.force_comptime,
+        .ref_start_index = gz.ref_start_index,
         .instructions = .{},
         // TODO @as here is working around a stage1 miscompilation bug :(
         .label = @as(?GenZir.Label, GenZir.Label{
@@ -1463,16 +1471,16 @@ fn blockExprStmts(
 
     var scope = parent_scope;
     for (statements) |statement| {
-        if (!gz.force_comptime) {
-            _ = try gz.addNode(.dbg_stmt_node, statement);
-        }
         switch (node_tags[statement]) {
-            .global_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.globalVarDecl(statement)),
-            .local_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.localVarDecl(statement)),
-            .simple_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.simpleVarDecl(statement)),
+            // zig fmt: off
+            .global_var_decl  => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.globalVarDecl(statement)),
+            .local_var_decl   => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.localVarDecl(statement)),
+            .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)),
 
-            // zig fmt: off
+            .@"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),
 
             .assign_bit_shift_left  => try assignShift(gz, scope, statement, .shl),
@@ -1489,302 +1497,357 @@ fn blockExprStmts(
             .assign_add_wrap => try assignOp(gz, scope, statement, .addwrap),
             .assign_mul      => try assignOp(gz, scope, statement, .mul),
             .assign_mul_wrap => try assignOp(gz, scope, statement, .mulwrap),
+
+            else => try unusedResultExpr(gz, scope, statement),
             // zig fmt: on
+        }
+    }
 
-            else => {
-                // We need to emit an error if the result is not `noreturn` or `void`, but
-                // we want to avoid adding the ZIR instruction if possible for performance.
-                const maybe_unused_result = try expr(gz, scope, .none, statement);
-                const elide_check = if (gz.refToIndex(maybe_unused_result)) |inst| b: {
-                    // Note that this array becomes invalid after appending more items to it
-                    // in the above while loop.
-                    const zir_tags = gz.astgen.instructions.items(.tag);
-                    switch (zir_tags[inst]) {
-                        // For some instructions, swap in a slightly different ZIR tag
-                        // so we can avoid a separate ensure_result_used instruction.
-                        .call_none_chkused => unreachable,
-                        .call_none => {
-                            zir_tags[inst] = .call_none_chkused;
-                            break :b true;
-                        },
-                        .call_chkused => unreachable,
-                        .call => {
-                            zir_tags[inst] = .call_chkused;
-                            break :b true;
-                        },
+    try genDefers(gz, parent_scope, scope, .none);
+}
 
-                        // ZIR instructions that might be a type other than `noreturn` or `void`.
-                        .add,
-                        .addwrap,
-                        .alloc,
-                        .alloc_mut,
-                        .alloc_inferred,
-                        .alloc_inferred_mut,
-                        .array_cat,
-                        .array_mul,
-                        .array_type,
-                        .array_type_sentinel,
-                        .elem_type,
-                        .indexable_ptr_len,
-                        .as,
-                        .as_node,
-                        .@"asm",
-                        .asm_volatile,
-                        .bit_and,
-                        .bitcast,
-                        .bitcast_result_ptr,
-                        .bit_or,
-                        .block,
-                        .block_inline,
-                        .block_inline_var,
-                        .loop,
-                        .bool_br_and,
-                        .bool_br_or,
-                        .bool_not,
-                        .bool_and,
-                        .bool_or,
-                        .call_compile_time,
-                        .cmp_lt,
-                        .cmp_lte,
-                        .cmp_eq,
-                        .cmp_gte,
-                        .cmp_gt,
-                        .cmp_neq,
-                        .coerce_result_ptr,
-                        .decl_ref,
-                        .decl_val,
-                        .load,
-                        .div,
-                        .elem_ptr,
-                        .elem_val,
-                        .elem_ptr_node,
-                        .elem_val_node,
-                        .field_ptr,
-                        .field_val,
-                        .field_ptr_named,
-                        .field_val_named,
-                        .func,
-                        .func_inferred,
-                        .int,
-                        .float,
-                        .float128,
-                        .intcast,
-                        .int_type,
-                        .is_non_null,
-                        .is_null,
-                        .is_non_null_ptr,
-                        .is_null_ptr,
-                        .is_err,
-                        .is_err_ptr,
-                        .mod_rem,
-                        .mul,
-                        .mulwrap,
-                        .param_type,
-                        .ptrtoint,
-                        .ref,
-                        .shl,
-                        .shr,
-                        .str,
-                        .sub,
-                        .subwrap,
-                        .negate,
-                        .negate_wrap,
-                        .typeof,
-                        .typeof_elem,
-                        .xor,
-                        .optional_type,
-                        .optional_type_from_ptr_elem,
-                        .optional_payload_safe,
-                        .optional_payload_unsafe,
-                        .optional_payload_safe_ptr,
-                        .optional_payload_unsafe_ptr,
-                        .err_union_payload_safe,
-                        .err_union_payload_unsafe,
-                        .err_union_payload_safe_ptr,
-                        .err_union_payload_unsafe_ptr,
-                        .err_union_code,
-                        .err_union_code_ptr,
-                        .ptr_type,
-                        .ptr_type_simple,
-                        .enum_literal,
-                        .enum_literal_small,
-                        .merge_error_sets,
-                        .error_union_type,
-                        .bit_not,
-                        .error_value,
-                        .error_to_int,
-                        .int_to_error,
-                        .slice_start,
-                        .slice_end,
-                        .slice_sentinel,
-                        .import,
-                        .typeof_peer,
-                        .switch_block,
-                        .switch_block_multi,
-                        .switch_block_else,
-                        .switch_block_else_multi,
-                        .switch_block_under,
-                        .switch_block_under_multi,
-                        .switch_block_ref,
-                        .switch_block_ref_multi,
-                        .switch_block_ref_else,
-                        .switch_block_ref_else_multi,
-                        .switch_block_ref_under,
-                        .switch_block_ref_under_multi,
-                        .switch_capture,
-                        .switch_capture_ref,
-                        .switch_capture_multi,
-                        .switch_capture_multi_ref,
-                        .switch_capture_else,
-                        .switch_capture_else_ref,
-                        .struct_init_empty,
-                        .struct_init,
-                        .struct_init_anon,
-                        .array_init,
-                        .array_init_anon,
-                        .array_init_ref,
-                        .array_init_anon_ref,
-                        .union_init_ptr,
-                        .field_type,
-                        .field_type_ref,
-                        .struct_decl,
-                        .struct_decl_packed,
-                        .struct_decl_extern,
-                        .union_decl,
-                        .enum_decl,
-                        .enum_decl_nonexhaustive,
-                        .opaque_decl,
-                        .error_set_decl,
-                        .int_to_enum,
-                        .enum_to_int,
-                        .type_info,
-                        .size_of,
-                        .bit_size_of,
-                        .add_with_overflow,
-                        .sub_with_overflow,
-                        .mul_with_overflow,
-                        .shl_with_overflow,
-                        .log2_int_type,
-                        .typeof_log2_int_type,
-                        .ptr_to_int,
-                        .align_of,
-                        .bool_to_int,
-                        .embed_file,
-                        .error_name,
-                        .sqrt,
-                        .sin,
-                        .cos,
-                        .exp,
-                        .exp2,
-                        .log,
-                        .log2,
-                        .log10,
-                        .fabs,
-                        .floor,
-                        .ceil,
-                        .trunc,
-                        .round,
-                        .tag_name,
-                        .reify,
-                        .type_name,
-                        .frame_type,
-                        .frame_size,
-                        .float_to_int,
-                        .int_to_float,
-                        .int_to_ptr,
-                        .float_cast,
-                        .int_cast,
-                        .err_set_cast,
-                        .ptr_cast,
-                        .truncate,
-                        .align_cast,
-                        .has_decl,
-                        .has_field,
-                        .clz,
-                        .ctz,
-                        .pop_count,
-                        .byte_swap,
-                        .bit_reverse,
-                        .div_exact,
-                        .div_floor,
-                        .div_trunc,
-                        .mod,
-                        .rem,
-                        .shl_exact,
-                        .shr_exact,
-                        .bit_offset_of,
-                        .byte_offset_of,
-                        .cmpxchg_strong,
-                        .cmpxchg_weak,
-                        .splat,
-                        .reduce,
-                        .shuffle,
-                        .atomic_load,
-                        .atomic_rmw,
-                        .atomic_store,
-                        .mul_add,
-                        .builtin_call,
-                        .field_ptr_type,
-                        .field_parent_ptr,
-                        .memcpy,
-                        .memset,
-                        .builtin_async_call,
-                        .c_import,
-                        .extended,
-                        => break :b false,
-
-                        // ZIR instructions that are always either `noreturn` or `void`.
-                        .breakpoint,
-                        .fence,
-                        .dbg_stmt_node,
-                        .ensure_result_used,
-                        .ensure_result_non_error,
-                        .@"export",
-                        .set_eval_branch_quota,
-                        .compile_log,
-                        .ensure_err_payload_void,
-                        .@"break",
-                        .break_inline,
-                        .condbr,
-                        .condbr_inline,
-                        .compile_error,
-                        .ret_node,
-                        .ret_tok,
-                        .ret_coerce,
-                        .@"unreachable",
-                        .store,
-                        .store_node,
-                        .store_to_block_ptr,
-                        .store_to_inferred_ptr,
-                        .resolve_inferred_alloc,
-                        .repeat,
-                        .repeat_inline,
-                        .validate_struct_init_ptr,
-                        .validate_array_init_ptr,
-                        .panic,
-                        .set_align_stack,
-                        .set_cold,
-                        .set_float_mode,
-                        .set_runtime_safety,
-                        => break :b true,
-                    }
-                } else switch (maybe_unused_result) {
-                    .none => unreachable,
+fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) InnerError!void {
+    try emitDbgNode(gz, statement);
+    // We need to emit an error if the result is not `noreturn` or `void`, but
+    // we want to avoid adding the ZIR instruction if possible for performance.
+    const maybe_unused_result = try expr(gz, scope, .none, statement);
+    const elide_check = if (gz.refToIndex(maybe_unused_result)) |inst| b: {
+        // Note that this array becomes invalid after appending more items to it
+        // in the above while loop.
+        const zir_tags = gz.astgen.instructions.items(.tag);
+        switch (zir_tags[inst]) {
+            // For some instructions, swap in a slightly different ZIR tag
+            // so we can avoid a separate ensure_result_used instruction.
+            .call_none_chkused => unreachable,
+            .call_none => {
+                zir_tags[inst] = .call_none_chkused;
+                break :b true;
+            },
+            .call_chkused => unreachable,
+            .call => {
+                zir_tags[inst] = .call_chkused;
+                break :b true;
+            },
 
-                    .void_value,
-                    .unreachable_value,
-                    => true,
+            // ZIR instructions that might be a type other than `noreturn` or `void`.
+            .add,
+            .addwrap,
+            .alloc,
+            .alloc_mut,
+            .alloc_inferred,
+            .alloc_inferred_mut,
+            .array_cat,
+            .array_mul,
+            .array_type,
+            .array_type_sentinel,
+            .elem_type,
+            .indexable_ptr_len,
+            .as,
+            .as_node,
+            .@"asm",
+            .asm_volatile,
+            .bit_and,
+            .bitcast,
+            .bitcast_result_ptr,
+            .bit_or,
+            .block,
+            .block_inline,
+            .block_inline_var,
+            .loop,
+            .bool_br_and,
+            .bool_br_or,
+            .bool_not,
+            .bool_and,
+            .bool_or,
+            .call_compile_time,
+            .cmp_lt,
+            .cmp_lte,
+            .cmp_eq,
+            .cmp_gte,
+            .cmp_gt,
+            .cmp_neq,
+            .coerce_result_ptr,
+            .decl_ref,
+            .decl_val,
+            .load,
+            .div,
+            .elem_ptr,
+            .elem_val,
+            .elem_ptr_node,
+            .elem_val_node,
+            .field_ptr,
+            .field_val,
+            .field_ptr_named,
+            .field_val_named,
+            .func,
+            .func_inferred,
+            .int,
+            .float,
+            .float128,
+            .intcast,
+            .int_type,
+            .is_non_null,
+            .is_null,
+            .is_non_null_ptr,
+            .is_null_ptr,
+            .is_err,
+            .is_err_ptr,
+            .mod_rem,
+            .mul,
+            .mulwrap,
+            .param_type,
+            .ptrtoint,
+            .ref,
+            .shl,
+            .shr,
+            .str,
+            .sub,
+            .subwrap,
+            .negate,
+            .negate_wrap,
+            .typeof,
+            .typeof_elem,
+            .xor,
+            .optional_type,
+            .optional_type_from_ptr_elem,
+            .optional_payload_safe,
+            .optional_payload_unsafe,
+            .optional_payload_safe_ptr,
+            .optional_payload_unsafe_ptr,
+            .err_union_payload_safe,
+            .err_union_payload_unsafe,
+            .err_union_payload_safe_ptr,
+            .err_union_payload_unsafe_ptr,
+            .err_union_code,
+            .err_union_code_ptr,
+            .ptr_type,
+            .ptr_type_simple,
+            .enum_literal,
+            .enum_literal_small,
+            .merge_error_sets,
+            .error_union_type,
+            .bit_not,
+            .error_value,
+            .error_to_int,
+            .int_to_error,
+            .slice_start,
+            .slice_end,
+            .slice_sentinel,
+            .import,
+            .typeof_peer,
+            .switch_block,
+            .switch_block_multi,
+            .switch_block_else,
+            .switch_block_else_multi,
+            .switch_block_under,
+            .switch_block_under_multi,
+            .switch_block_ref,
+            .switch_block_ref_multi,
+            .switch_block_ref_else,
+            .switch_block_ref_else_multi,
+            .switch_block_ref_under,
+            .switch_block_ref_under_multi,
+            .switch_capture,
+            .switch_capture_ref,
+            .switch_capture_multi,
+            .switch_capture_multi_ref,
+            .switch_capture_else,
+            .switch_capture_else_ref,
+            .struct_init_empty,
+            .struct_init,
+            .struct_init_anon,
+            .array_init,
+            .array_init_anon,
+            .array_init_ref,
+            .array_init_anon_ref,
+            .union_init_ptr,
+            .field_type,
+            .field_type_ref,
+            .struct_decl,
+            .struct_decl_packed,
+            .struct_decl_extern,
+            .union_decl,
+            .enum_decl,
+            .enum_decl_nonexhaustive,
+            .opaque_decl,
+            .error_set_decl,
+            .int_to_enum,
+            .enum_to_int,
+            .type_info,
+            .size_of,
+            .bit_size_of,
+            .add_with_overflow,
+            .sub_with_overflow,
+            .mul_with_overflow,
+            .shl_with_overflow,
+            .log2_int_type,
+            .typeof_log2_int_type,
+            .ptr_to_int,
+            .align_of,
+            .bool_to_int,
+            .embed_file,
+            .error_name,
+            .sqrt,
+            .sin,
+            .cos,
+            .exp,
+            .exp2,
+            .log,
+            .log2,
+            .log10,
+            .fabs,
+            .floor,
+            .ceil,
+            .trunc,
+            .round,
+            .tag_name,
+            .reify,
+            .type_name,
+            .frame_type,
+            .frame_size,
+            .float_to_int,
+            .int_to_float,
+            .int_to_ptr,
+            .float_cast,
+            .int_cast,
+            .err_set_cast,
+            .ptr_cast,
+            .truncate,
+            .align_cast,
+            .has_decl,
+            .has_field,
+            .clz,
+            .ctz,
+            .pop_count,
+            .byte_swap,
+            .bit_reverse,
+            .div_exact,
+            .div_floor,
+            .div_trunc,
+            .mod,
+            .rem,
+            .shl_exact,
+            .shr_exact,
+            .bit_offset_of,
+            .byte_offset_of,
+            .cmpxchg_strong,
+            .cmpxchg_weak,
+            .splat,
+            .reduce,
+            .shuffle,
+            .atomic_load,
+            .atomic_rmw,
+            .atomic_store,
+            .mul_add,
+            .builtin_call,
+            .field_ptr_type,
+            .field_parent_ptr,
+            .memcpy,
+            .memset,
+            .builtin_async_call,
+            .c_import,
+            .extended,
+            => break :b false,
+
+            // ZIR instructions that are always either `noreturn` or `void`.
+            .breakpoint,
+            .fence,
+            .dbg_stmt_node,
+            .ensure_result_used,
+            .ensure_result_non_error,
+            .@"export",
+            .set_eval_branch_quota,
+            .compile_log,
+            .ensure_err_payload_void,
+            .@"break",
+            .break_inline,
+            .condbr,
+            .condbr_inline,
+            .compile_error,
+            .ret_node,
+            .ret_tok,
+            .ret_coerce,
+            .@"unreachable",
+            .store,
+            .store_node,
+            .store_to_block_ptr,
+            .store_to_inferred_ptr,
+            .resolve_inferred_alloc,
+            .repeat,
+            .repeat_inline,
+            .validate_struct_init_ptr,
+            .validate_array_init_ptr,
+            .panic,
+            .set_align_stack,
+            .set_cold,
+            .set_float_mode,
+            .set_runtime_safety,
+            => break :b true,
+        }
+    } else switch (maybe_unused_result) {
+        .none => unreachable,
 
-                    else => false,
-                };
-                if (!elide_check) {
-                    _ = try gz.addUnNode(.ensure_result_used, maybe_unused_result, statement);
-                }
+        .void_value,
+        .unreachable_value,
+        => true,
+
+        else => false,
+    };
+    if (!elide_check) {
+        _ = try gz.addUnNode(.ensure_result_used, maybe_unused_result, statement);
+    }
+}
+
+fn genDefers(
+    gz: *GenZir,
+    outer_scope: *Scope,
+    inner_scope: *Scope,
+    err_code: Zir.Inst.Ref,
+) InnerError!void {
+    const astgen = gz.astgen;
+    const tree = &astgen.file.tree;
+    const node_datas = tree.nodes.items(.data);
+
+    var scope = inner_scope;
+    while (scope != outer_scope) {
+        switch (scope.tag) {
+            .gen_zir => scope = scope.cast(GenZir).?.parent,
+            .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
+            .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+            .defer_normal => {
+                const defer_scope = scope.cast(Scope.Defer).?;
+                scope = defer_scope.parent;
+                const expr_node = node_datas[defer_scope.defer_node].rhs;
+                try unusedResultExpr(gz, defer_scope.parent, expr_node);
             },
+            .defer_error => {
+                const defer_scope = scope.cast(Scope.Defer).?;
+                scope = defer_scope.parent;
+                if (err_code == .none) continue;
+                const expr_node = node_datas[defer_scope.defer_node].rhs;
+                try unusedResultExpr(gz, defer_scope.parent, expr_node);
+            },
+            else => unreachable,
         }
     }
 }
 
+fn deferStmt(
+    gz: *GenZir,
+    scope: *Scope,
+    node: ast.Node.Index,
+    block_arena: *Allocator,
+    scope_tag: Scope.Tag,
+) InnerError!*Scope {
+    const defer_scope = try block_arena.create(Scope.Defer);
+    defer_scope.* = .{
+        .base = .{ .tag = scope_tag },
+        .parent = scope,
+        .defer_node = node,
+    };
+    return &defer_scope.base;
+}
+
 fn varDecl(
     gz: *GenZir,
     scope: *Scope,
@@ -1792,6 +1855,7 @@ fn varDecl(
     block_arena: *Allocator,
     var_decl: ast.full.VarDecl,
 ) InnerError!*Scope {
+    try emitDbgNode(gz, node);
     const astgen = gz.astgen;
     if (var_decl.comptime_token) |comptime_token| {
         return astgen.failTok(comptime_token, "TODO implement comptime locals", .{});
@@ -1841,6 +1905,7 @@ fn varDecl(
                 s = local_ptr.parent;
             },
             .gen_zir => s = s.cast(GenZir).?.parent,
+            .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
             .file => break,
             else => unreachable,
         };
@@ -1877,6 +1942,7 @@ fn varDecl(
                 .parent = scope,
                 .decl_node_index = gz.decl_node_index,
                 .force_comptime = gz.force_comptime,
+                .ref_start_index = gz.ref_start_index,
                 .astgen = astgen,
             };
             defer init_scope.instructions.deinit(gpa);
@@ -1984,7 +2050,14 @@ fn varDecl(
     }
 }
 
+fn emitDbgNode(gz: *GenZir, node: ast.Node.Index) !void {
+    if (!gz.force_comptime) {
+        _ = try gz.addNode(.dbg_stmt_node, node);
+    }
+}
+
 fn assign(gz: *GenZir, scope: *Scope, infix_node: ast.Node.Index) InnerError!void {
+    try emitDbgNode(gz, infix_node);
     const astgen = gz.astgen;
     const tree = &astgen.file.tree;
     const node_datas = tree.nodes.items(.data);
@@ -2011,6 +2084,7 @@ fn assignOp(
     infix_node: ast.Node.Index,
     op_inst_tag: Zir.Inst.Tag,
 ) InnerError!void {
+    try emitDbgNode(gz, infix_node);
     const astgen = gz.astgen;
     const tree = &astgen.file.tree;
     const node_datas = tree.nodes.items(.data);
@@ -2033,6 +2107,7 @@ fn assignShift(
     infix_node: ast.Node.Index,
     op_inst_tag: Zir.Inst.Tag,
 ) InnerError!void {
+    try emitDbgNode(gz, infix_node);
     const astgen = gz.astgen;
     const tree = &astgen.file.tree;
     const node_datas = tree.nodes.items(.data);
@@ -2257,12 +2332,12 @@ fn fnDecl(
     const param_types = try gpa.alloc(Zir.Inst.Ref, param_count);
     defer gpa.free(param_types);
 
-    var decl_gz: Scope.GenZir = .{
+    var decl_gz: GenZir = .{
         .force_comptime = true,
         .decl_node_index = fn_proto.ast.proto_node,
         .parent = &gz.base,
         .astgen = astgen,
-        .ref_start_index = @intCast(u32, Zir.Inst.Ref.typed_value_map.len + param_count),
+        .ref_start_index = @intCast(u32, Zir.Inst.Ref.typed_value_map.len),
     };
     defer decl_gz.instructions.deinit(gpa);
 
@@ -2357,7 +2432,7 @@ fn fnDecl(
             return astgen.failTok(fn_proto.ast.fn_token, "non-extern function is variadic", .{});
         }
 
-        var fn_gz: Scope.GenZir = .{
+        var fn_gz: GenZir = .{
             .force_comptime = false,
             .decl_node_index = fn_proto.ast.proto_node,
             .parent = &decl_gz.base,
@@ -2366,6 +2441,9 @@ fn fnDecl(
         };
         defer fn_gz.instructions.deinit(gpa);
 
+        const prev_fn_block = astgen.fn_block;
+        astgen.fn_block = &fn_gz;
+
         // Iterate over the parameters. We put the param names as the first N
         // items inside `extra` so that debug info later can refer to the parameter names
         // even while the respective source code is unloaded.
@@ -2406,6 +2484,8 @@ fn fnDecl(
             _ = try fn_gz.addUnTok(.ret_coerce, .void_value, tree.lastToken(body_node));
         }
 
+        astgen.fn_block = prev_fn_block;
+
         break :func try decl_gz.addFunc(.{
             .src_node = fn_proto.ast.proto_node,
             .ret_ty = return_type_inst,
@@ -2580,9 +2660,18 @@ fn testDecl(
     scope: *Scope,
     node: ast.Node.Index,
 ) InnerError!void {
+    const gpa = astgen.gpa;
     const tree = &astgen.file.tree;
     const node_datas = tree.nodes.items(.data);
-    const test_expr = node_datas[node].rhs;
+    const body_node = node_datas[node].rhs;
+
+    var decl_block: GenZir = .{
+        .force_comptime = true,
+        .decl_node_index = node,
+        .parent = &gz.base,
+        .astgen = astgen,
+    };
+    defer decl_block.instructions.deinit(gpa);
 
     const test_name: u32 = blk: {
         const main_tokens = tree.nodes.items(.main_token);
@@ -2590,13 +2679,49 @@ fn testDecl(
         const test_token = main_tokens[node];
         const str_lit_token = test_token + 1;
         if (token_tags[str_lit_token] == .string_literal) {
-            break :blk (try gz.strLitAsString(str_lit_token)).index;
+            break :blk (try decl_block.strLitAsString(str_lit_token)).index;
         }
         break :blk 0;
     };
 
-    // TODO probably we want to put these into a block and store a list of them
-    const block_inst = try expr(gz, scope, .none, test_expr);
+    var fn_block: GenZir = .{
+        .force_comptime = false,
+        .decl_node_index = node,
+        .parent = &decl_block.base,
+        .astgen = astgen,
+    };
+    defer fn_block.instructions.deinit(gpa);
+
+    const prev_fn_block = astgen.fn_block;
+    astgen.fn_block = &fn_block;
+
+    const block_result = try expr(&fn_block, &fn_block.base, .none, body_node);
+    if (fn_block.instructions.items.len == 0 or !fn_block.refIsNoReturn(block_result)) {
+        // Since we are adding the return instruction here, we must handle the coercion.
+        // We do this by using the `ret_coerce` instruction.
+        _ = try fn_block.addUnTok(.ret_coerce, .void_value, tree.lastToken(body_node));
+    }
+
+    astgen.fn_block = prev_fn_block;
+
+    const func_inst = try decl_block.addFunc(.{
+        .src_node = node,
+        .ret_ty = .void_type,
+        .param_types = &[0]Zir.Inst.Ref{},
+        .body = fn_block.instructions.items,
+        .cc = .none,
+        .lib_name = 0,
+        .is_var_args = false,
+        .is_inferred_error = true,
+    });
+
+    const block_inst = try gz.addBlock(.block_inline, node);
+    _ = try decl_block.addBreak(.break_inline, block_inst, func_inst);
+    try decl_block.setBlockBody(block_inst);
+
+    // TODO collect these into a test decl list
+    _ = test_name;
+    _ = block_inst;
 }
 
 fn structDeclInner(
@@ -2628,6 +2753,7 @@ fn structDeclInner(
         .decl_node_index = node,
         .astgen = astgen,
         .force_comptime = true,
+        .ref_start_index = gz.ref_start_index,
     };
     defer block_scope.instructions.deinit(gpa);
 
@@ -2943,6 +3069,7 @@ fn containerDecl(
                 .decl_node_index = node,
                 .astgen = astgen,
                 .force_comptime = true,
+                .ref_start_index = gz.ref_start_index,
             };
             defer block_scope.instructions.deinit(gpa);
 
@@ -3168,6 +3295,7 @@ fn tryExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = parent_gz.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     block_scope.setBreakResultLoc(rl);
@@ -3200,11 +3328,13 @@ fn tryExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = block_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer then_scope.instructions.deinit(astgen.gpa);
 
     const err_code = try then_scope.addUnNode(err_ops[1], operand, node);
+    try genDefers(&then_scope, &astgen.fn_block.?.base, scope, err_code);
     const then_result = try then_scope.addUnNode(.ret_node, err_code, node);
 
     var else_scope: GenZir = .{
@@ -3212,6 +3342,7 @@ fn tryExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = block_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer else_scope.instructions.deinit(astgen.gpa);
@@ -3264,6 +3395,7 @@ fn orelseCatchExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = parent_gz.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     block_scope.setBreakResultLoc(rl);
@@ -3300,6 +3432,7 @@ fn orelseCatchExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = block_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer then_scope.instructions.deinit(astgen.gpa);
@@ -3332,6 +3465,7 @@ fn orelseCatchExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = block_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer else_scope.instructions.deinit(astgen.gpa);
@@ -3532,6 +3666,7 @@ fn boolBinOp(
         .decl_node_index = gz.decl_node_index,
         .astgen = gz.astgen,
         .force_comptime = gz.force_comptime,
+        .ref_start_index = gz.ref_start_index,
     };
     defer rhs_scope.instructions.deinit(gz.astgen.gpa);
     const rhs = try expr(&rhs_scope, &rhs_scope.base, bool_rl, node_datas[node].rhs);
@@ -3558,6 +3693,7 @@ fn ifExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = parent_gz.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     block_scope.setBreakResultLoc(rl);
@@ -3608,6 +3744,7 @@ fn ifExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = block_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer then_scope.instructions.deinit(astgen.gpa);
@@ -3662,6 +3799,7 @@ fn ifExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = block_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer else_scope.instructions.deinit(astgen.gpa);
@@ -3812,6 +3950,7 @@ fn whileExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = parent_gz.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     loop_scope.setBreakResultLoc(rl);
@@ -3822,6 +3961,7 @@ fn whileExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = loop_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer continue_scope.instructions.deinit(astgen.gpa);
@@ -3891,6 +4031,7 @@ fn whileExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = continue_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer then_scope.instructions.deinit(astgen.gpa);
@@ -3942,6 +4083,7 @@ fn whileExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = continue_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer else_scope.instructions.deinit(astgen.gpa);
@@ -4043,6 +4185,7 @@ fn forExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = parent_gz.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     loop_scope.setBreakResultLoc(rl);
@@ -4053,6 +4196,7 @@ fn forExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = loop_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer cond_scope.instructions.deinit(astgen.gpa);
@@ -4096,6 +4240,7 @@ fn forExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = cond_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer then_scope.instructions.deinit(astgen.gpa);
@@ -4141,6 +4286,7 @@ fn forExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = cond_scope.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer else_scope.instructions.deinit(astgen.gpa);
@@ -4443,6 +4589,7 @@ fn switchExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = parent_gz.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     block_scope.setBreakResultLoc(rl);
@@ -4457,6 +4604,7 @@ fn switchExpr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = parent_gz.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer case_scope.instructions.deinit(gpa);
@@ -4900,15 +5048,21 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
     const main_tokens = tree.nodes.items(.main_token);
 
     const operand_node = node_datas[node].lhs;
-    const operand: Zir.Inst.Ref = if (operand_node != 0) operand: {
+    if (operand_node != 0) {
         const rl: ResultLoc = if (nodeMayNeedMemoryLocation(tree, operand_node)) .{
             .ptr = try gz.addNodeExtended(.ret_ptr, node),
         } else .{
             .ty = try gz.addNodeExtended(.ret_type, node),
         };
-        break :operand try expr(gz, scope, rl, operand_node);
-    } else .void_value;
-    _ = try gz.addUnNode(.ret_node, operand, node);
+        const operand = try expr(gz, scope, rl, operand_node);
+        // TODO check operand to see if we need to generate errdefers
+        try genDefers(gz, &astgen.fn_block.?.base, scope, .none);
+        _ = try gz.addUnNode(.ret_node, operand, node);
+        return Zir.Inst.Ref.unreachable_value;
+    }
+    // Returning a void value; skip error defers.
+    try genDefers(gz, &astgen.fn_block.?.base, scope, .none);
+    _ = try gz.addUnNode(.ret_node, .void_value, node);
     return Zir.Inst.Ref.unreachable_value;
 }
 
@@ -5309,6 +5463,7 @@ fn asRlPtr(
         .decl_node_index = parent_gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = parent_gz.force_comptime,
+        .ref_start_index = parent_gz.ref_start_index,
         .instructions = .{},
     };
     defer as_scope.instructions.deinit(astgen.gpa);
@@ -5998,6 +6153,7 @@ fn cImport(
         .decl_node_index = gz.decl_node_index,
         .astgen = astgen,
         .force_comptime = true,
+        .ref_start_index = gz.ref_start_index,
         .instructions = .{},
     };
     defer block_scope.instructions.deinit(gpa);
@@ -6454,7 +6610,7 @@ fn rvalue(
 
 /// Given an identifier token, obtain the string for it.
 /// If the token uses @"" syntax, parses as a string, reports errors if applicable,
-/// and allocates the result within `scope.arena()`.
+/// and allocates the result within `astgen.arena`.
 /// Otherwise, returns a reference to the source code bytes directly.
 /// See also `appendIdentStr` and `parseStrLit`.
 pub fn identifierTokenString(astgen: *AstGen, token: ast.TokenIndex) InnerError![]const u8 {
src/Module.zig
@@ -514,31 +514,26 @@ pub const Scope = struct {
     pub const NameHash = [16]u8;
 
     pub fn cast(base: *Scope, comptime T: type) ?*T {
+        if (T == Defer) {
+            switch (base.tag) {
+                .defer_normal, .defer_error => return @fieldParentPtr(T, "base", base),
+                else => return null,
+            }
+        }
         if (base.tag != T.base_tag)
             return null;
 
         return @fieldParentPtr(T, "base", base);
     }
 
-    /// Returns the arena Allocator associated with the Decl of the Scope.
-    pub fn arena(scope: *Scope) *Allocator {
-        switch (scope.tag) {
-            .block => return scope.cast(Block).?.sema.arena,
-            .gen_zir => return scope.cast(GenZir).?.astgen.arena,
-            .local_val => return scope.cast(LocalVal).?.gen_zir.astgen.arena,
-            .local_ptr => return scope.cast(LocalPtr).?.gen_zir.astgen.arena,
-            .file => unreachable,
-            .namespace => unreachable,
-            .decl_ref => unreachable,
-        }
-    }
-
     pub fn ownerDecl(scope: *Scope) ?*Decl {
         return switch (scope.tag) {
             .block => scope.cast(Block).?.sema.owner_decl,
             .gen_zir => unreachable,
             .local_val => unreachable,
             .local_ptr => unreachable,
+            .defer_normal => unreachable,
+            .defer_error => unreachable,
             .file => null,
             .namespace => null,
             .decl_ref => scope.cast(DeclRef).?.decl,
@@ -551,6 +546,8 @@ pub const Scope = struct {
             .gen_zir => unreachable,
             .local_val => unreachable,
             .local_ptr => unreachable,
+            .defer_normal => unreachable,
+            .defer_error => unreachable,
             .file => null,
             .namespace => null,
             .decl_ref => scope.cast(DeclRef).?.decl,
@@ -564,25 +561,14 @@ pub const Scope = struct {
             .gen_zir => unreachable,
             .local_val => unreachable,
             .local_ptr => unreachable,
+            .defer_normal => unreachable,
+            .defer_error => unreachable,
             .file => return scope.cast(File).?.namespace,
             .namespace => return scope.cast(Namespace).?,
             .decl_ref => return scope.cast(DeclRef).?.decl.namespace,
         }
     }
 
-    /// Asserts the scope is a child of a `GenZir` and returns it.
-    pub fn getGenZir(scope: *Scope) *GenZir {
-        return switch (scope.tag) {
-            .block => unreachable,
-            .gen_zir => scope.cast(GenZir).?,
-            .local_val => return scope.cast(LocalVal).?.gen_zir,
-            .local_ptr => return scope.cast(LocalPtr).?.gen_zir,
-            .file => unreachable,
-            .namespace => unreachable,
-            .decl_ref => unreachable,
-        };
-    }
-
     /// Asserts the scope has a parent which is a Namespace or File and
     /// returns the sub_file_path field.
     pub fn subFilePath(base: *Scope) []const u8 {
@@ -593,6 +579,8 @@ pub const Scope = struct {
             .gen_zir => unreachable,
             .local_val => unreachable,
             .local_ptr => unreachable,
+            .defer_normal => unreachable,
+            .defer_error => unreachable,
             .decl_ref => unreachable,
         }
     }
@@ -604,9 +592,11 @@ pub const Scope = struct {
             cur = switch (cur.tag) {
                 .namespace => return @fieldParentPtr(Namespace, "base", cur).file_scope,
                 .file => return @fieldParentPtr(File, "base", cur),
-                .gen_zir => @fieldParentPtr(GenZir, "base", cur).parent,
-                .local_val => @fieldParentPtr(LocalVal, "base", cur).parent,
-                .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent,
+                .gen_zir => return @fieldParentPtr(GenZir, "base", cur).astgen.file,
+                .local_val => return @fieldParentPtr(LocalVal, "base", cur).gen_zir.astgen.file,
+                .local_ptr => return @fieldParentPtr(LocalPtr, "base", cur).gen_zir.astgen.file,
+                .defer_normal => @fieldParentPtr(Defer, "base", cur).parent,
+                .defer_error => @fieldParentPtr(Defer, "base", cur).parent,
                 .block => return @fieldParentPtr(Block, "base", cur).src_decl.namespace.file_scope,
                 .decl_ref => return @fieldParentPtr(DeclRef, "base", cur).decl.namespace.file_scope,
             };
@@ -634,6 +624,8 @@ pub const Scope = struct {
         /// `Decl` for use with `srcDecl` and `ownerDecl`.
         /// Has no parents or children.
         decl_ref,
+        defer_normal,
+        defer_error,
     };
 
     /// The container that structs, enums, unions, and opaques have.
@@ -1709,7 +1701,7 @@ pub const Scope = struct {
     pub const LocalVal = struct {
         pub const base_tag: Tag = .local_val;
         base: Scope = Scope{ .tag = base_tag },
-        /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`.
+        /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`.
         parent: *Scope,
         gen_zir: *GenZir,
         name: []const u8,
@@ -1724,7 +1716,7 @@ pub const Scope = struct {
     pub const LocalPtr = struct {
         pub const base_tag: Tag = .local_ptr;
         base: Scope = Scope{ .tag = base_tag },
-        /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`.
+        /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`.
         parent: *Scope,
         gen_zir: *GenZir,
         name: []const u8,
@@ -1733,6 +1725,13 @@ pub const Scope = struct {
         token_src: ast.TokenIndex,
     };
 
+    pub const Defer = struct {
+        base: Scope,
+        /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`.
+        parent: *Scope,
+        defer_node: ast.Node.Index,
+    };
+
     pub const DeclRef = struct {
         pub const base_tag: Tag = .decl_ref;
         base: Scope = Scope{ .tag = base_tag },
@@ -3821,7 +3820,7 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) In
             }
             mod.failed_decls.putAssumeCapacityNoClobber(block.sema.owner_decl, err_msg);
         },
-        .gen_zir, .local_val, .local_ptr => unreachable,
+        .gen_zir, .local_val, .local_ptr, .defer_normal, .defer_error => unreachable,
         .file => unreachable,
         .namespace => unreachable,
         .decl_ref => {
src/Zir.zig
@@ -2304,6 +2304,7 @@ const Writer = struct {
             .byte_swap,
             .bit_reverse,
             .elem_type,
+            .bitcast_result_ptr,
             => try self.writeUnNode(stream, inst),
 
             .ref,
@@ -2424,6 +2425,7 @@ const Writer = struct {
             .splat,
             .reduce,
             .atomic_load,
+            .bitcast,
             => try self.writePlNodeBin(stream, inst),
 
             .call,
@@ -2509,13 +2511,40 @@ const Writer = struct {
             .switch_capture_else_ref,
             => try self.writeSwitchCapture(stream, inst),
 
-            .bitcast,
-            .bitcast_result_ptr,
-            .extended,
-            => try stream.writeAll("TODO)"),
+            .extended => try self.writeExtended(stream, inst),
+        }
+    }
+
+    fn writeExtended(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+        const extended = self.code.instructions.items(.data)[inst].extended;
+        try stream.print("{s}(", .{@tagName(extended.opcode)});
+        switch (extended.opcode) {
+            .ret_ptr,
+            .ret_type,
+            .this,
+            .ret_addr,
+            .error_return_trace,
+            .frame,
+            .frame_address,
+            .builtin_src,
+            => try self.writeExtNode(stream, extended),
+
+            .func,
+            .c_undef,
+            .c_include,
+            .c_define,
+            .wasm_memory_size,
+            .wasm_memory_grow,
+            => try stream.writeAll("TODO))"),
         }
     }
 
+    fn writeExtNode(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void {
+        const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) };
+        try stream.writeAll(")) ");
+        try self.writeSrc(stream, src);
+    }
+
     fn writeBin(self: *Writer, stream: anytype, inst: Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[inst].bin;
         try self.writeInstRef(stream, inst_data.lhs);
BRANCH_TODO
@@ -1,3 +1,7 @@
+ * defer
+   - `break`
+   - `continue`
+ * nested function decl: how to refer to params?
  * look for cached zir code
  * save zir code to cache
  * keep track of file dependencies/dependants
@@ -13,6 +17,15 @@
    on each usingnamespace decl
  * handle usingnamespace cycles
 
+ * compile error for return inside defer expression
+
+ * when block has noreturn statement
+   - avoid emitting defers
+   - compile error for unreachable code
+ 
+ * detect `return error.Foo` and emit ZIR that unconditionally generates errdefers
+ * `return`: check return operand and generate errdefers if necessary
+
  * have failed_trees and just put the file in there
    - this way we can emit all the parse errors not just the first one
    - but maybe we want just the first one?