Commit 89f02d1c10

mlugg <mlugg@mlugg.co.uk>
2024-08-16 18:34:30
std.zig.Zir: fix declaration traversal
The old logic here had bitrotted, largely because there were some incorrect `else` cases. This is now implemented correctly for all current ZIR instructions. This prevents instructions being lost in incremental updates, which is important for updates to be minimal.
1 parent 84c2ebd
Changed files (2)
lib
std
src
lib/std/zig/Zir.zig
@@ -603,7 +603,7 @@ pub const Inst = struct {
         /// Uses the `un_node` field.
         typeof,
         /// Implements `@TypeOf` for one operand.
-        /// Uses the `pl_node` field.
+        /// Uses the `pl_node` field. Payload is `Block`.
         typeof_builtin,
         /// Given a value, look at the type of it, which must be an integer type.
         /// Returns the integer type for the RHS of a shift operation.
@@ -2727,6 +2727,9 @@ pub const Inst = struct {
         field_name_start: NullTerminatedString,
     };
 
+    /// There is a body of instructions at `extra[body_index..][0..body_len]`.
+    /// Trailing:
+    /// 0. operand: Ref // for each `operands_len`
     pub const TypeOfPeer = struct {
         src_node: i32,
         body_len: u32,
@@ -2844,6 +2847,40 @@ pub const Inst = struct {
         src_line: u32,
     };
 
+    /// Trailing:
+    /// 0. multi_cases_len: u32 // if `has_multi_cases`
+    /// 1. err_capture_inst: u32 // if `any_uses_err_capture`
+    /// 2. non_err_body {
+    ///        info: ProngInfo,
+    ///        inst: Index // for every `info.body_len`
+    ///     }
+    /// 3. else_body { // if `has_else`
+    ///        info: ProngInfo,
+    ///        inst: Index // for every `info.body_len`
+    ///     }
+    /// 4. scalar_cases: { // for every `scalar_cases_len`
+    ///        item: Ref,
+    ///        info: ProngInfo,
+    ///        inst: Index // for every `info.body_len`
+    ///     }
+    /// 5. multi_cases: { // for every `multi_cases_len`
+    ///        items_len: u32,
+    ///        ranges_len: u32,
+    ///        info: ProngInfo,
+    ///        item: Ref // for every `items_len`
+    ///        ranges: { // for every `ranges_len`
+    ///            item_first: Ref,
+    ///            item_last: Ref,
+    ///        }
+    ///        inst: Index // for every `info.body_len`
+    ///    }
+    ///
+    /// When analyzing a case body, the switch instruction itself refers to the
+    /// captured error, or to the success value in `non_err_body`. Whether this
+    /// is captured by reference or by value depends on whether the `byref` bit
+    /// is set for the corresponding body. `err_capture_inst` refers to the error
+    /// capture outside of the `switch`, i.e. `err` in
+    /// `x catch |err| switch (err) { ... }`.
     pub const SwitchBlockErrUnion = struct {
         operand: Ref,
         bits: Bits,
@@ -3153,7 +3190,7 @@ pub const Inst = struct {
     /// 1. captures_len: u32 // if has_captures_len
     /// 2. body_len: u32, // if has_body_len
     /// 3. fields_len: u32, // if has_fields_len
-    /// 4. decls_len: u37, // if has_decls_len
+    /// 4. decls_len: u32, // if has_decls_len
     /// 5. capture: Capture // for every captures_len
     /// 6. decl: Index, // for every decls_len; points to a `declaration` instruction
     /// 7. inst: Index // for every body_len
@@ -3624,33 +3661,492 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator {
     }
 }
 
-/// The iterator would have to allocate memory anyway to iterate. So here we populate
-/// an ArrayList as the result.
-pub fn findDecls(zir: Zir, list: *std.ArrayList(Inst.Index), decl_inst: Zir.Inst.Index) !void {
+/// Find all type declarations, recursively, within a `declaration` instruction. Does not recurse through
+/// said type declarations' declarations; to find all declarations, call this function on the declarations
+/// of the discovered types recursively.
+/// The iterator would have to allocate memory anyway to iterate, so an `ArrayList` is populated as the result.
+pub fn findDecls(zir: Zir, gpa: Allocator, list: *std.ArrayListUnmanaged(Inst.Index), decl_inst: Zir.Inst.Index) !void {
     list.clearRetainingCapacity();
     const declaration, const extra_end = zir.getDeclaration(decl_inst);
     const bodies = declaration.getBodies(extra_end, zir);
 
-    try zir.findDeclsBody(list, bodies.value_body);
-    if (bodies.align_body) |b| try zir.findDeclsBody(list, b);
-    if (bodies.linksection_body) |b| try zir.findDeclsBody(list, b);
-    if (bodies.addrspace_body) |b| try zir.findDeclsBody(list, b);
+    // `defer` instructions duplicate the same body arbitrarily many times, but we only want to traverse
+    // their contents once per defer. So, we store the extra index of the body here to deduplicate.
+    var found_defers: std.AutoHashMapUnmanaged(u32, void) = .{};
+    defer found_defers.deinit(gpa);
+
+    try zir.findDeclsBody(gpa, list, &found_defers, bodies.value_body);
+    if (bodies.align_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b);
+    if (bodies.linksection_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b);
+    if (bodies.addrspace_body) |b| try zir.findDeclsBody(gpa, list, &found_defers, b);
 }
 
 fn findDeclsInner(
     zir: Zir,
-    list: *std.ArrayList(Inst.Index),
+    gpa: Allocator,
+    list: *std.ArrayListUnmanaged(Inst.Index),
+    defers: *std.AutoHashMapUnmanaged(u32, void),
     inst: Inst.Index,
 ) Allocator.Error!void {
     const tags = zir.instructions.items(.tag);
     const datas = zir.instructions.items(.data);
 
     switch (tags[@intFromEnum(inst)]) {
+        .declaration => unreachable,
+
+        // Boring instruction tags first. These have no body and are not declarations or type declarations.
+        .add,
+        .addwrap,
+        .add_sat,
+        .add_unsafe,
+        .sub,
+        .subwrap,
+        .sub_sat,
+        .mul,
+        .mulwrap,
+        .mul_sat,
+        .div_exact,
+        .div_floor,
+        .div_trunc,
+        .mod,
+        .rem,
+        .mod_rem,
+        .shl,
+        .shl_exact,
+        .shl_sat,
+        .shr,
+        .shr_exact,
+        .param_anytype,
+        .param_anytype_comptime,
+        .array_cat,
+        .array_mul,
+        .array_type,
+        .array_type_sentinel,
+        .vector_type,
+        .elem_type,
+        .indexable_ptr_elem_type,
+        .vector_elem_type,
+        .indexable_ptr_len,
+        .anyframe_type,
+        .as_node,
+        .as_shift_operand,
+        .bit_and,
+        .bitcast,
+        .bit_not,
+        .bit_or,
+        .bool_not,
+        .bool_br_and,
+        .bool_br_or,
+        .@"break",
+        .break_inline,
+        .check_comptime_control_flow,
+        .builtin_call,
+        .cmp_lt,
+        .cmp_lte,
+        .cmp_eq,
+        .cmp_gte,
+        .cmp_gt,
+        .cmp_neq,
+        .error_set_decl,
+        .dbg_stmt,
+        .dbg_var_ptr,
+        .dbg_var_val,
+        .decl_ref,
+        .decl_val,
+        .load,
+        .div,
+        .elem_ptr_node,
+        .elem_ptr,
+        .elem_val_node,
+        .elem_val,
+        .elem_val_imm,
+        .ensure_result_used,
+        .ensure_result_non_error,
+        .ensure_err_union_payload_void,
+        .error_union_type,
+        .error_value,
+        .@"export",
+        .export_value,
+        .field_ptr,
+        .field_val,
+        .field_ptr_named,
+        .field_val_named,
+        .import,
+        .int,
+        .int_big,
+        .float,
+        .float128,
+        .int_type,
+        .is_non_null,
+        .is_non_null_ptr,
+        .is_non_err,
+        .is_non_err_ptr,
+        .ret_is_non_err,
+        .repeat,
+        .repeat_inline,
+        .for_len,
+        .merge_error_sets,
+        .ref,
+        .ret_node,
+        .ret_load,
+        .ret_implicit,
+        .ret_err_value,
+        .ret_err_value_code,
+        .ret_ptr,
+        .ret_type,
+        .ptr_type,
+        .slice_start,
+        .slice_end,
+        .slice_sentinel,
+        .slice_length,
+        .store_node,
+        .store_to_inferred_ptr,
+        .str,
+        .negate,
+        .negate_wrap,
+        .typeof,
+        .typeof_log2_int_type,
+        .@"unreachable",
+        .xor,
+        .optional_type,
+        .optional_payload_safe,
+        .optional_payload_unsafe,
+        .optional_payload_safe_ptr,
+        .optional_payload_unsafe_ptr,
+        .err_union_payload_unsafe,
+        .err_union_payload_unsafe_ptr,
+        .err_union_code,
+        .err_union_code_ptr,
+        .enum_literal,
+        .validate_deref,
+        .validate_destructure,
+        .field_type_ref,
+        .opt_eu_base_ptr_init,
+        .coerce_ptr_elem_ty,
+        .validate_ref_ty,
+        .struct_init_empty,
+        .struct_init_empty_result,
+        .struct_init_empty_ref_result,
+        .struct_init_anon,
+        .struct_init,
+        .struct_init_ref,
+        .validate_struct_init_ty,
+        .validate_struct_init_result_ty,
+        .validate_ptr_struct_init,
+        .struct_init_field_type,
+        .struct_init_field_ptr,
+        .array_init_anon,
+        .array_init,
+        .array_init_ref,
+        .validate_array_init_ty,
+        .validate_array_init_result_ty,
+        .validate_array_init_ref_ty,
+        .validate_ptr_array_init,
+        .array_init_elem_type,
+        .array_init_elem_ptr,
+        .union_init,
+        .type_info,
+        .size_of,
+        .bit_size_of,
+        .int_from_ptr,
+        .compile_error,
+        .set_eval_branch_quota,
+        .int_from_enum,
+        .align_of,
+        .int_from_bool,
+        .embed_file,
+        .error_name,
+        .panic,
+        .trap,
+        .set_runtime_safety,
+        .sqrt,
+        .sin,
+        .cos,
+        .tan,
+        .exp,
+        .exp2,
+        .log,
+        .log2,
+        .log10,
+        .abs,
+        .floor,
+        .ceil,
+        .trunc,
+        .round,
+        .tag_name,
+        .type_name,
+        .frame_type,
+        .frame_size,
+        .int_from_float,
+        .float_from_int,
+        .ptr_from_int,
+        .enum_from_int,
+        .float_cast,
+        .int_cast,
+        .ptr_cast,
+        .truncate,
+        .has_decl,
+        .has_field,
+        .clz,
+        .ctz,
+        .pop_count,
+        .byte_swap,
+        .bit_reverse,
+        .bit_offset_of,
+        .offset_of,
+        .splat,
+        .reduce,
+        .shuffle,
+        .atomic_load,
+        .atomic_rmw,
+        .atomic_store,
+        .mul_add,
+        .memcpy,
+        .memset,
+        .min,
+        .max,
+        .alloc,
+        .alloc_mut,
+        .alloc_comptime_mut,
+        .alloc_inferred,
+        .alloc_inferred_mut,
+        .alloc_inferred_comptime,
+        .alloc_inferred_comptime_mut,
+        .resolve_inferred_alloc,
+        .make_ptr_const,
+        .@"resume",
+        .@"await",
+        .save_err_ret_index,
+        .restore_err_ret_index_unconditional,
+        .restore_err_ret_index_fn_entry,
+        => return,
+
+        .extended => {
+            const extended = datas[@intFromEnum(inst)].extended;
+            switch (extended.opcode) {
+                .value_placeholder => unreachable,
+
+                // Once again, we start with the boring tags.
+                .variable,
+                .this,
+                .ret_addr,
+                .builtin_src,
+                .error_return_trace,
+                .frame,
+                .frame_address,
+                .alloc,
+                .builtin_extern,
+                .@"asm",
+                .asm_expr,
+                .compile_log,
+                .min_multi,
+                .max_multi,
+                .add_with_overflow,
+                .sub_with_overflow,
+                .mul_with_overflow,
+                .shl_with_overflow,
+                .c_undef,
+                .c_include,
+                .c_define,
+                .wasm_memory_size,
+                .wasm_memory_grow,
+                .prefetch,
+                .fence,
+                .set_float_mode,
+                .set_align_stack,
+                .set_cold,
+                .error_cast,
+                .await_nosuspend,
+                .breakpoint,
+                .disable_instrumentation,
+                .select,
+                .int_from_error,
+                .error_from_int,
+                .builtin_async_call,
+                .cmpxchg,
+                .c_va_arg,
+                .c_va_copy,
+                .c_va_end,
+                .c_va_start,
+                .ptr_cast_full,
+                .ptr_cast_no_dest,
+                .work_item_id,
+                .work_group_size,
+                .work_group_id,
+                .in_comptime,
+                .restore_err_ret_index,
+                .closure_get,
+                .field_parent_ptr,
+                => return,
+
+                // `@TypeOf` has a body.
+                .typeof_peer => {
+                    const extra = zir.extraData(Zir.Inst.TypeOfPeer, extended.operand);
+                    const body = zir.bodySlice(extra.data.body_index, extra.data.body_len);
+                    try zir.findDeclsBody(gpa, list, defers, body);
+                },
+
+                // Reifications and opaque declarations need tracking, but have no body.
+                .reify, .opaque_decl => return list.append(gpa, inst),
+
+                // Struct declarations need tracking and have bodies.
+                .struct_decl => {
+                    try list.append(gpa, inst);
+
+                    const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
+                    const extra = zir.extraData(Zir.Inst.StructDecl, extended.operand);
+                    var extra_index = extra.end;
+                    const captures_len = if (small.has_captures_len) blk: {
+                        const captures_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk captures_len;
+                    } else 0;
+                    const fields_len = if (small.has_fields_len) blk: {
+                        const fields_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk fields_len;
+                    } else 0;
+                    const decls_len = if (small.has_decls_len) blk: {
+                        const decls_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk decls_len;
+                    } else 0;
+                    extra_index += captures_len;
+                    if (small.has_backing_int) {
+                        const backing_int_body_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        if (backing_int_body_len == 0) {
+                            extra_index += 1; // backing_int_ref
+                        } else {
+                            const body = zir.bodySlice(extra_index, backing_int_body_len);
+                            extra_index += backing_int_body_len;
+                            try zir.findDeclsBody(gpa, list, defers, body);
+                        }
+                    }
+                    extra_index += decls_len;
+
+                    // This ZIR is structured in a slightly awkward way, so we have to split up the iteration.
+                    // `extra_index` iterates `flags` (bags of bits).
+                    // `fields_extra_index` iterates `fields`.
+                    // We accumulate the total length of bodies into `total_bodies_len`. This is sufficient because
+                    // the bodies are packed together in `extra` and we only need to traverse their instructions (we
+                    // don't really care about the structure).
+
+                    const bits_per_field = 4;
+                    const fields_per_u32 = 32 / bits_per_field;
+                    const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
+                    var cur_bit_bag: u32 = undefined;
+
+                    var fields_extra_index = extra_index + bit_bags_count;
+                    var total_bodies_len: u32 = 0;
+
+                    for (0..fields_len) |field_i| {
+                        if (field_i % fields_per_u32 == 0) {
+                            cur_bit_bag = zir.extra[extra_index];
+                            extra_index += 1;
+                        }
+
+                        const has_align = @as(u1, @truncate(cur_bit_bag)) != 0;
+                        cur_bit_bag >>= 1;
+                        const has_init = @as(u1, @truncate(cur_bit_bag)) != 0;
+                        cur_bit_bag >>= 2; // also skip `is_comptime`; we don't care
+                        const has_type_body = @as(u1, @truncate(cur_bit_bag)) != 0;
+                        cur_bit_bag >>= 1;
+
+                        fields_extra_index += @intFromBool(!small.is_tuple); // field_name
+                        fields_extra_index += 1; // doc_comment
+
+                        if (has_type_body) {
+                            const field_type_body_len = zir.extra[fields_extra_index];
+                            total_bodies_len += field_type_body_len;
+                        }
+                        fields_extra_index += 1; // field_type or field_type_body_len
+
+                        if (has_align) {
+                            const align_body_len = zir.extra[fields_extra_index];
+                            fields_extra_index += 1;
+                            total_bodies_len += align_body_len;
+                        }
+
+                        if (has_init) {
+                            const init_body_len = zir.extra[fields_extra_index];
+                            fields_extra_index += 1;
+                            total_bodies_len += init_body_len;
+                        }
+                    }
+
+                    // Now, `fields_extra_index` points to `bodies`. Let's treat this as one big body.
+                    const merged_bodies = zir.bodySlice(fields_extra_index, total_bodies_len);
+                    try zir.findDeclsBody(gpa, list, defers, merged_bodies);
+                },
+
+                // Union declarations need tracking and have a body.
+                .union_decl => {
+                    try list.append(gpa, inst);
+
+                    const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small);
+                    const extra = zir.extraData(Zir.Inst.UnionDecl, extended.operand);
+                    var extra_index = extra.end;
+                    extra_index += @intFromBool(small.has_tag_type);
+                    const captures_len = if (small.has_captures_len) blk: {
+                        const captures_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk captures_len;
+                    } else 0;
+                    const body_len = if (small.has_body_len) blk: {
+                        const body_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk body_len;
+                    } else 0;
+                    extra_index += @intFromBool(small.has_fields_len);
+                    const decls_len = if (small.has_decls_len) blk: {
+                        const decls_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk decls_len;
+                    } else 0;
+                    extra_index += captures_len;
+                    extra_index += decls_len;
+                    const body = zir.bodySlice(extra_index, body_len);
+                    try zir.findDeclsBody(gpa, list, defers, body);
+                },
+
+                // Enum declarations need tracking and have a body.
+                .enum_decl => {
+                    try list.append(gpa, inst);
+
+                    const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small);
+                    const extra = zir.extraData(Zir.Inst.EnumDecl, extended.operand);
+                    var extra_index = extra.end;
+                    extra_index += @intFromBool(small.has_tag_type);
+                    const captures_len = if (small.has_captures_len) blk: {
+                        const captures_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk captures_len;
+                    } else 0;
+                    const body_len = if (small.has_body_len) blk: {
+                        const body_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk body_len;
+                    } else 0;
+                    extra_index += @intFromBool(small.has_fields_len);
+                    const decls_len = if (small.has_decls_len) blk: {
+                        const decls_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :blk decls_len;
+                    } else 0;
+                    extra_index += captures_len;
+                    extra_index += decls_len;
+                    const body = zir.bodySlice(extra_index, body_len);
+                    try zir.findDeclsBody(gpa, list, defers, body);
+                },
+            }
+        },
+
         // Functions instructions are interesting and have a body.
         .func,
         .func_inferred,
         => {
-            try list.append(inst);
+            try list.append(gpa, inst);
 
             const inst_data = datas[@intFromEnum(inst)].pl_node;
             const extra = zir.extraData(Inst.Func, inst_data.payload_index);
@@ -3661,14 +4157,14 @@ fn findDeclsInner(
                 else => {
                     const body = zir.bodySlice(extra_index, extra.data.ret_body_len);
                     extra_index += body.len;
-                    try zir.findDeclsBody(list, body);
+                    try zir.findDeclsBody(gpa, list, defers, body);
                 },
             }
             const body = zir.bodySlice(extra_index, extra.data.body_len);
-            return zir.findDeclsBody(list, body);
+            return zir.findDeclsBody(gpa, list, defers, body);
         },
         .func_fancy => {
-            try list.append(inst);
+            try list.append(gpa, inst);
 
             const inst_data = datas[@intFromEnum(inst)].pl_node;
             const extra = zir.extraData(Inst.FuncFancy, inst_data.payload_index);
@@ -3679,7 +4175,7 @@ fn findDeclsInner(
                 const body_len = zir.extra[extra_index];
                 extra_index += 1;
                 const body = zir.bodySlice(extra_index, body_len);
-                try zir.findDeclsBody(list, body);
+                try zir.findDeclsBody(gpa, list, defers, body);
                 extra_index += body.len;
             } else if (extra.data.bits.has_align_ref) {
                 extra_index += 1;
@@ -3689,7 +4185,7 @@ fn findDeclsInner(
                 const body_len = zir.extra[extra_index];
                 extra_index += 1;
                 const body = zir.bodySlice(extra_index, body_len);
-                try zir.findDeclsBody(list, body);
+                try zir.findDeclsBody(gpa, list, defers, body);
                 extra_index += body.len;
             } else if (extra.data.bits.has_addrspace_ref) {
                 extra_index += 1;
@@ -3699,7 +4195,7 @@ fn findDeclsInner(
                 const body_len = zir.extra[extra_index];
                 extra_index += 1;
                 const body = zir.bodySlice(extra_index, body_len);
-                try zir.findDeclsBody(list, body);
+                try zir.findDeclsBody(gpa, list, defers, body);
                 extra_index += body.len;
             } else if (extra.data.bits.has_section_ref) {
                 extra_index += 1;
@@ -3709,7 +4205,7 @@ fn findDeclsInner(
                 const body_len = zir.extra[extra_index];
                 extra_index += 1;
                 const body = zir.bodySlice(extra_index, body_len);
-                try zir.findDeclsBody(list, body);
+                try zir.findDeclsBody(gpa, list, defers, body);
                 extra_index += body.len;
             } else if (extra.data.bits.has_cc_ref) {
                 extra_index += 1;
@@ -3719,7 +4215,7 @@ fn findDeclsInner(
                 const body_len = zir.extra[extra_index];
                 extra_index += 1;
                 const body = zir.bodySlice(extra_index, body_len);
-                try zir.findDeclsBody(list, body);
+                try zir.findDeclsBody(gpa, list, defers, body);
                 extra_index += body.len;
             } else if (extra.data.bits.has_ret_ty_ref) {
                 extra_index += 1;
@@ -3728,62 +4224,99 @@ fn findDeclsInner(
             extra_index += @intFromBool(extra.data.bits.has_any_noalias);
 
             const body = zir.bodySlice(extra_index, extra.data.body_len);
-            return zir.findDeclsBody(list, body);
-        },
-        .extended => {
-            const extended = datas[@intFromEnum(inst)].extended;
-            switch (extended.opcode) {
-
-                // Decl instructions are interesting but have no body.
-                // TODO yes they do have a body actually. recurse over them just like block instructions.
-                .struct_decl,
-                .union_decl,
-                .enum_decl,
-                .opaque_decl,
-                .reify,
-                => return list.append(inst),
-
-                else => return,
-            }
+            return zir.findDeclsBody(gpa, list, defers, body);
         },
 
         // Block instructions, recurse over the bodies.
 
-        .block, .block_comptime, .block_inline => {
+        .block,
+        .block_comptime,
+        .block_inline,
+        .c_import,
+        .typeof_builtin,
+        .loop,
+        => {
             const inst_data = datas[@intFromEnum(inst)].pl_node;
             const extra = zir.extraData(Inst.Block, inst_data.payload_index);
             const body = zir.bodySlice(extra.end, extra.data.body_len);
-            return zir.findDeclsBody(list, body);
+            return zir.findDeclsBody(gpa, list, defers, body);
         },
         .condbr, .condbr_inline => {
             const inst_data = datas[@intFromEnum(inst)].pl_node;
             const extra = zir.extraData(Inst.CondBr, inst_data.payload_index);
             const then_body = zir.bodySlice(extra.end, extra.data.then_body_len);
             const else_body = zir.bodySlice(extra.end + then_body.len, extra.data.else_body_len);
-            try zir.findDeclsBody(list, then_body);
-            try zir.findDeclsBody(list, else_body);
+            try zir.findDeclsBody(gpa, list, defers, then_body);
+            try zir.findDeclsBody(gpa, list, defers, else_body);
         },
         .@"try", .try_ptr => {
             const inst_data = datas[@intFromEnum(inst)].pl_node;
             const extra = zir.extraData(Inst.Try, inst_data.payload_index);
             const body = zir.bodySlice(extra.end, extra.data.body_len);
-            try zir.findDeclsBody(list, body);
+            try zir.findDeclsBody(gpa, list, defers, body);
         },
-        .switch_block => return findDeclsSwitch(zir, list, inst),
+        .switch_block, .switch_block_ref => return zir.findDeclsSwitch(gpa, list, defers, inst, .normal),
+        .switch_block_err_union => return zir.findDeclsSwitch(gpa, list, defers, inst, .err_union),
 
         .suspend_block => @panic("TODO iterate suspend block"),
 
-        else => return, // Regular instruction, not interesting.
+        .param, .param_comptime => {
+            const inst_data = datas[@intFromEnum(inst)].pl_tok;
+            const extra = zir.extraData(Inst.Param, inst_data.payload_index);
+            const body = zir.bodySlice(extra.end, extra.data.body_len);
+            try zir.findDeclsBody(gpa, list, defers, body);
+        },
+
+        inline .call, .field_call => |tag| {
+            const inst_data = datas[@intFromEnum(inst)].pl_node;
+            const extra = zir.extraData(switch (tag) {
+                .call => Inst.Call,
+                .field_call => Inst.FieldCall,
+                else => unreachable,
+            }, inst_data.payload_index);
+            // It's easiest to just combine all the arg bodies into one body, like we do above for `struct_decl`.
+            const args_len = extra.data.flags.args_len;
+            if (args_len > 0) {
+                const first_arg_start_off = args_len;
+                const final_arg_end_off = zir.extra[extra.end + args_len - 1];
+                const args_body = zir.bodySlice(extra.end + first_arg_start_off, final_arg_end_off - first_arg_start_off);
+                try zir.findDeclsBody(gpa, list, defers, args_body);
+            }
+        },
+        .@"defer" => {
+            const inst_data = datas[@intFromEnum(inst)].@"defer";
+            const gop = try defers.getOrPut(gpa, inst_data.index);
+            if (!gop.found_existing) {
+                const body = zir.bodySlice(inst_data.index, inst_data.len);
+                try zir.findDeclsBody(gpa, list, defers, body);
+            }
+        },
+        .defer_err_code => {
+            const inst_data = datas[@intFromEnum(inst)].defer_err_code;
+            const extra = zir.extraData(Inst.DeferErrCode, inst_data.payload_index).data;
+            const gop = try defers.getOrPut(gpa, extra.index);
+            if (!gop.found_existing) {
+                const body = zir.bodySlice(extra.index, extra.len);
+                try zir.findDeclsBody(gpa, list, defers, body);
+            }
+        },
     }
 }
 
 fn findDeclsSwitch(
     zir: Zir,
-    list: *std.ArrayList(Inst.Index),
+    gpa: Allocator,
+    list: *std.ArrayListUnmanaged(Inst.Index),
+    defers: *std.AutoHashMapUnmanaged(u32, void),
     inst: Inst.Index,
+    /// Distinguishes between `switch_block[_ref]` and `switch_block_err_union`.
+    comptime kind: enum { normal, err_union },
 ) Allocator.Error!void {
     const inst_data = zir.instructions.items(.data)[@intFromEnum(inst)].pl_node;
-    const extra = zir.extraData(Inst.SwitchBlock, inst_data.payload_index);
+    const extra = zir.extraData(switch (kind) {
+        .normal => Inst.SwitchBlock,
+        .err_union => Inst.SwitchBlockErrUnion,
+    }, inst_data.payload_index);
 
     var extra_index: usize = extra.end;
 
@@ -3793,18 +4326,35 @@ fn findDeclsSwitch(
         break :blk multi_cases_len;
     } else 0;
 
-    if (extra.data.bits.any_has_tag_capture) {
+    if (switch (kind) {
+        .normal => extra.data.bits.any_has_tag_capture,
+        .err_union => extra.data.bits.any_uses_err_capture,
+    }) {
         extra_index += 1;
     }
 
-    const special_prong = extra.data.bits.specialProng();
-    if (special_prong != .none) {
+    const has_special = switch (kind) {
+        .normal => extra.data.bits.specialProng() != .none,
+        .err_union => has_special: {
+            // Handle `non_err_body` first.
+            const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
+            extra_index += 1;
+            const body = zir.bodySlice(extra_index, prong_info.body_len);
+            extra_index += body.len;
+
+            try zir.findDeclsBody(gpa, list, defers, body);
+
+            break :has_special extra.data.bits.has_else;
+        },
+    };
+
+    if (has_special) {
         const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
         extra_index += 1;
         const body = zir.bodySlice(extra_index, prong_info.body_len);
         extra_index += body.len;
 
-        try zir.findDeclsBody(list, body);
+        try zir.findDeclsBody(gpa, list, defers, body);
     }
 
     {
@@ -3816,7 +4366,7 @@ fn findDeclsSwitch(
             const body = zir.bodySlice(extra_index, prong_info.body_len);
             extra_index += body.len;
 
-            try zir.findDeclsBody(list, body);
+            try zir.findDeclsBody(gpa, list, defers, body);
         }
     }
     {
@@ -3833,18 +4383,20 @@ fn findDeclsSwitch(
             const body = zir.bodySlice(extra_index, prong_info.body_len);
             extra_index += body.len;
 
-            try zir.findDeclsBody(list, body);
+            try zir.findDeclsBody(gpa, list, defers, body);
         }
     }
 }
 
 fn findDeclsBody(
     zir: Zir,
-    list: *std.ArrayList(Inst.Index),
+    gpa: Allocator,
+    list: *std.ArrayListUnmanaged(Inst.Index),
+    defers: *std.AutoHashMapUnmanaged(u32, void),
     body: []const Inst.Index,
 ) Allocator.Error!void {
     for (body) |member| {
-        try zir.findDeclsInner(list, member);
+        try zir.findDeclsInner(gpa, list, defers, member);
     }
 }
 
@@ -4042,7 +4594,7 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash {
                 return null;
             }
             const extra_index = extra.end +
-                1 +
+                extra.data.ret_body_len +
                 extra.data.body_len +
                 @typeInfo(Inst.Func.SrcLocs).Struct.fields.len;
             return @bitCast([4]u32{
src/Zcu.zig
@@ -2557,10 +2557,10 @@ pub fn mapOldZirToNew(
     });
 
     // Used as temporary buffers for namespace declaration instructions
-    var old_decls = std.ArrayList(Zir.Inst.Index).init(gpa);
-    defer old_decls.deinit();
-    var new_decls = std.ArrayList(Zir.Inst.Index).init(gpa);
-    defer new_decls.deinit();
+    var old_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .{};
+    defer old_decls.deinit(gpa);
+    var new_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .{};
+    defer new_decls.deinit(gpa);
 
     while (match_stack.popOrNull()) |match_item| {
         // Match the namespace declaration itself
@@ -2647,11 +2647,11 @@ pub fn mapOldZirToNew(
             // Match the `declaration` instruction
             try inst_map.put(gpa, old_decl_inst, new_decl_inst);
 
-            // Find namespace declarations within this declaration
-            try old_zir.findDecls(&old_decls, old_decl_inst);
-            try new_zir.findDecls(&new_decls, new_decl_inst);
+            // Find container type declarations within this declaration
+            try old_zir.findDecls(gpa, &old_decls, old_decl_inst);
+            try new_zir.findDecls(gpa, &new_decls, new_decl_inst);
 
-            // We don't have any smart way of matching up these namespace declarations, so we always
+            // We don't have any smart way of matching up these type declarations, so we always
             // correlate them based on source order.
             const n = @min(old_decls.items.len, new_decls.items.len);
             try match_stack.ensureUnusedCapacity(gpa, n);