Commit d48e4245b6

Andrew Kelley <andrew@ziglang.org>
2022-01-15 23:17:48
stage2: implement `@prefetch`
1 parent 3901b6f
lib/std/builtin.zig
@@ -643,12 +643,12 @@ pub const PrefetchOptions = struct {
     /// The cache that the prefetch should be preformed on.
     cache: Cache = .data,
 
-    pub const Rw = enum {
+    pub const Rw = enum(u1) {
         read,
         write,
     };
 
-    pub const Cache = enum {
+    pub const Cache = enum(u1) {
         instruction,
         data,
     };
src/arch/aarch64/CodeGen.zig
@@ -595,6 +595,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .error_name      => try self.airErrorName(inst),
             .splat           => try self.airSplat(inst),
             .vector_init     => try self.airVectorInit(inst),
+            .prefetch        => try self.airPrefetch(inst),
 
             .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
             .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -2597,6 +2598,11 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
     return bt.finishAir(result);
 }
 
+fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void {
+    const prefetch = self.air.instructions.items(.data)[inst].prefetch;
+    return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none });
+}
+
 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
src/arch/arm/CodeGen.zig
@@ -586,6 +586,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .error_name      => try self.airErrorName(inst),
             .splat           => try self.airSplat(inst),
             .vector_init     => try self.airVectorInit(inst),
+            .prefetch        => try self.airPrefetch(inst),
 
             .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
             .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -3696,6 +3697,11 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
     return bt.finishAir(result);
 }
 
+fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void {
+    const prefetch = self.air.instructions.items(.data)[inst].prefetch;
+    return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none });
+}
+
 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
src/arch/riscv64/CodeGen.zig
@@ -574,6 +574,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .error_name      => try self.airErrorName(inst),
             .splat           => try self.airSplat(inst),
             .vector_init     => try self.airVectorInit(inst),
+            .prefetch        => try self.airPrefetch(inst),
 
             .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
             .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -2096,6 +2097,11 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
     return bt.finishAir(result);
 }
 
+fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void {
+    const prefetch = self.air.instructions.items(.data)[inst].prefetch;
+    return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none });
+}
+
 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
src/arch/wasm/CodeGen.zig
@@ -1297,6 +1297,9 @@ fn copyLocal(self: *Self, value: WValue, ty: Type) InnerError!WValue {
 fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
     const air_tags = self.air.instructions.items(.tag);
     return switch (air_tags[inst]) {
+        .constant => unreachable,
+        .const_ty => unreachable,
+
         .add => self.airBinOp(inst, .add),
         .addwrap => self.airWrapBinOp(inst, .add),
         .sub => self.airBinOp(inst, .sub),
@@ -1330,7 +1333,6 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
         .bool_to_int => self.airBoolToInt(inst),
         .call => self.airCall(inst),
         .cond_br => self.airCondBr(inst),
-        .constant => unreachable,
         .dbg_stmt => WValue.none,
         .intcast => self.airIntcast(inst),
         .float_to_int => self.airFloatToInt(inst),
@@ -1358,6 +1360,9 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
         .ret => self.airRet(inst),
         .ret_ptr => self.airRetPtr(inst),
         .ret_load => self.airRetLoad(inst),
+        .splat => self.airSplat(inst),
+        .vector_init => self.airVectorInit(inst),
+        .prefetch => self.airPrefetch(inst),
 
         .slice => self.airSlice(inst),
         .slice_len => self.airSliceLen(inst),
@@ -1382,7 +1387,55 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
         .unwrap_errunion_err => self.airUnwrapErrUnionError(inst),
         .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
         .wrap_errunion_err => self.airWrapErrUnionErr(inst),
-        else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
+
+        .add_sat,
+        .sub_sat,
+        .mul_sat,
+        .div_float,
+        .div_floor,
+        .div_exact,
+        .rem,
+        .mod,
+        .max,
+        .min,
+        .assembly,
+        .shl_exact,
+        .shl_sat,
+        .ret_addr,
+        .clz,
+        .ctz,
+        .popcount,
+        .is_err_ptr,
+        .is_non_err_ptr,
+        .fptrunc,
+        .fpext,
+        .unwrap_errunion_payload_ptr,
+        .unwrap_errunion_err_ptr,
+        .set_union_tag,
+        .get_union_tag,
+        .ptr_slice_len_ptr,
+        .ptr_slice_ptr_ptr,
+        .int_to_float,
+        .memcpy,
+        .cmpxchg_weak,
+        .cmpxchg_strong,
+        .fence,
+        .atomic_load,
+        .atomic_store_unordered,
+        .atomic_store_monotonic,
+        .atomic_store_release,
+        .atomic_store_seq_cst,
+        .atomic_rmw,
+        .tag_name,
+        .error_name,
+
+        // For these 4, probably best to wait until https://github.com/ziglang/zig/issues/10248
+        // is implemented in the frontend before implementing them here in the wasm backend.
+        .add_with_overflow,
+        .sub_with_overflow,
+        .mul_with_overflow,
+        .shl_with_overflow,
+        => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
     };
 }
 
@@ -3211,7 +3264,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     return result;
 }
 
-fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
+fn airSplat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
 
     const ty_op = self.air.instructions.items(.data)[inst].ty_op;
@@ -3222,7 +3275,7 @@ fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
     return self.fail("TODO: Implement wasm airSplat", .{});
 }
 
-fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
+fn airVectorInit(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
     if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
 
     const vector_ty = self.air.typeOfIndex(inst);
@@ -3234,6 +3287,12 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
     return self.fail("TODO: Wasm backend: implement airVectorInit", .{});
 }
 
+fn airPrefetch(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+    const prefetch = self.air.instructions.items(.data)[inst].prefetch;
+    _ = prefetch;
+    return WValue{ .none = {} };
+}
+
 fn cmpOptionals(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.math.CompareOperator) InnerError!WValue {
     assert(operand_ty.hasCodeGenBits());
     assert(op == .eq or op == .neq);
src/arch/x86_64/CodeGen.zig
@@ -638,6 +638,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .error_name      => try self.airErrorName(inst),
             .splat           => try self.airSplat(inst),
             .vector_init     => try self.airVectorInit(inst),
+            .prefetch        => try self.airPrefetch(inst),
 
             .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
             .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -3693,6 +3694,11 @@ fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
     return bt.finishAir(result);
 }
 
+fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void {
+    const prefetch = self.air.instructions.items(.data)[inst].prefetch;
+    return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none });
+}
+
 fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
     // First section of indexes correspond to a set number of constant values.
     const ref_int = @enumToInt(inst);
src/codegen/c.zig
@@ -1278,6 +1278,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
             .error_name       => try airErrorName(f, inst),
             .splat            => try airSplat(f, inst),
             .vector_init      => try airVectorInit(f, inst),
+            .prefetch         => try airPrefetch(f, inst),
 
             .int_to_float,
             .float_to_int,
@@ -3089,6 +3090,18 @@ fn airVectorInit(f: *Function, inst: Air.Inst.Index) !CValue {
     return f.fail("TODO: C backend: implement airVectorInit", .{});
 }
 
+fn airPrefetch(f: *Function, inst: Air.Inst.Index) !CValue {
+    const prefetch = f.air.instructions.items(.data)[inst].prefetch;
+    const ptr = try f.resolveInst(prefetch.ptr);
+    const writer = f.object.writer();
+    try writer.writeAll("zig_prefetch(");
+    try f.writeCValue(writer, ptr);
+    try writer.print(", {d}, {d});\n", .{
+        @enumToInt(prefetch.rw), prefetch.locality,
+    });
+    return CValue.none;
+}
+
 fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
     return switch (order) {
         .Unordered => "memory_order_relaxed",
src/codegen/llvm.zig
@@ -2073,6 +2073,7 @@ pub const FuncGen = struct {
                 .error_name     => try self.airErrorName(inst),
                 .splat          => try self.airSplat(inst),
                 .vector_init    => try self.airVectorInit(inst),
+                .prefetch       => try self.airPrefetch(inst),
 
                 .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
                 .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -4384,6 +4385,67 @@ pub const FuncGen = struct {
         return vector;
     }
 
+    fn airPrefetch(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const prefetch = self.air.instructions.items(.data)[inst].prefetch;
+
+        comptime assert(@enumToInt(std.builtin.PrefetchOptions.Rw.read) == 0);
+        comptime assert(@enumToInt(std.builtin.PrefetchOptions.Rw.write) == 1);
+
+        // TODO these two asserts should be able to be comptime because the type is a u2
+        assert(prefetch.locality >= 0);
+        assert(prefetch.locality <= 3);
+
+        comptime assert(@enumToInt(std.builtin.PrefetchOptions.Cache.instruction) == 0);
+        comptime assert(@enumToInt(std.builtin.PrefetchOptions.Cache.data) == 1);
+
+        // LLVM fails during codegen of instruction cache prefetchs for these architectures.
+        // This is an LLVM bug as the prefetch intrinsic should be a noop if not supported
+        // by the target.
+        // To work around this, don't emit llvm.prefetch in this case.
+        // See https://bugs.llvm.org/show_bug.cgi?id=21037
+        const target = self.dg.module.getTarget();
+        switch (prefetch.cache) {
+            .instruction => switch (target.cpu.arch) {
+                .x86_64, .i386 => return null,
+                .arm, .armeb, .thumb, .thumbeb => {
+                    switch (prefetch.rw) {
+                        .write => return null,
+                        else => {},
+                    }
+                },
+                else => {},
+            },
+            .data => {},
+        }
+
+        const llvm_u8 = self.context.intType(8);
+        const llvm_ptr_u8 = llvm_u8.pointerType(0);
+        const llvm_u32 = self.context.intType(32);
+
+        const llvm_fn_name = "llvm.prefetch.p0i8";
+        const fn_val = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
+            // declare void @llvm.prefetch(i8*, i32, i32, i32)
+            const llvm_void = self.context.voidType();
+            const param_types = [_]*const llvm.Type{
+                llvm_ptr_u8, llvm_u32, llvm_u32, llvm_u32,
+            };
+            const fn_type = llvm.functionType(llvm_void, &param_types, param_types.len, .False);
+            break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
+        };
+
+        const ptr = try self.resolveInst(prefetch.ptr);
+        const ptr_u8 = self.builder.buildBitCast(ptr, llvm_ptr_u8, "");
+
+        const params = [_]*const llvm.Value{
+            ptr_u8,
+            llvm_u32.constInt(@enumToInt(prefetch.rw), .False),
+            llvm_u32.constInt(prefetch.locality, .False),
+            llvm_u32.constInt(@enumToInt(prefetch.cache), .False),
+        };
+        _ = self.builder.buildCall(fn_val, &params, params.len, .C, .Auto, "");
+        return null;
+    }
+
     fn getErrorNameTable(self: *FuncGen) !*const llvm.Value {
         if (self.dg.object.error_name_table) |table| {
             return table;
src/link/C/zig.h
@@ -74,6 +74,12 @@
 #define zig_frame_address() 0
 #endif
 
+#if defined(__GNUC__)
+#define zig_prefetch(addr, rw, locality) __builtin_prefetch(addr, rw, locality)
+#else
+#define zig_prefetch(addr, rw, locality)
+#endif
+
 #if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
 #include <stdatomic.h>
 #define zig_cmpxchg_strong(obj, expected, desired, succ, fail) atomic_compare_exchange_strong_explicit(obj, &(expected), desired, succ, fail)
src/Air.zig
@@ -515,6 +515,11 @@ pub const Inst = struct {
         /// is a `Ref`. Length of the array is given by the vector type.
         vector_init,
 
+        /// Communicates an intent to load memory.
+        /// Result is always unused.
+        /// Uses the `prefetch` field.
+        prefetch,
+
         pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
             return switch (op) {
                 .lt => .cmp_lt,
@@ -586,6 +591,12 @@ pub const Inst = struct {
             ptr: Ref,
             order: std.builtin.AtomicOrder,
         },
+        prefetch: struct {
+            ptr: Ref,
+            rw: std.builtin.PrefetchOptions.Rw,
+            locality: u2,
+            cache: std.builtin.PrefetchOptions.Cache,
+        },
 
         // Make sure we don't accidentally add a field to make this union
         // bigger than expected. Note that in Debug builds, Zig is allowed
@@ -823,6 +834,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .memset,
         .memcpy,
         .set_union_tag,
+        .prefetch,
         => return Type.initTag(.void),
 
         .ptrtoint,
src/Liveness.zig
@@ -342,6 +342,11 @@ fn analyzeInst(
             return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
         },
 
+        .prefetch => {
+            const prefetch = inst_datas[inst].prefetch;
+            return trackOperands(a, new_set, inst, main_tomb, .{ prefetch.ptr, .none, .none });
+        },
+
         .call => {
             const inst_data = inst_datas[inst].pl_op;
             const callee = inst_data.operand;
src/print_air.zig
@@ -226,6 +226,7 @@ const Writer = struct {
             .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst),
             .fence => try w.writeFence(s, inst),
             .atomic_load => try w.writeAtomicLoad(s, inst),
+            .prefetch => try w.writePrefetch(s, inst),
             .atomic_store_unordered => try w.writeAtomicStore(s, inst, .Unordered),
             .atomic_store_monotonic => try w.writeAtomicStore(s, inst, .Monotonic),
             .atomic_store_release => try w.writeAtomicStore(s, inst, .Release),
@@ -350,6 +351,15 @@ const Writer = struct {
         try s.print(", {s}", .{@tagName(atomic_load.order)});
     }
 
+    fn writePrefetch(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
+        const prefetch = w.air.instructions.items(.data)[inst].prefetch;
+
+        try w.writeOperand(s, inst, 0, prefetch.ptr);
+        try s.print(", {s}, {d}, {s}", .{
+            @tagName(prefetch.rw), prefetch.locality, @tagName(prefetch.cache),
+        });
+    }
+
     fn writeAtomicStore(
         w: *Writer,
         s: anytype,
src/Sema.zig
@@ -10274,49 +10274,53 @@ fn zirStructInitEmpty(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
     const obj_ty = try sema.resolveType(block, src, inst_data.operand);
-    const gpa = sema.gpa;
 
     switch (obj_ty.zigTypeTag()) {
-        .Struct => {
-            // This logic must be synchronized with that in `zirStructInit`.
-            const struct_ty = try sema.resolveTypeFields(block, src, obj_ty);
-            const struct_obj = struct_ty.castTag(.@"struct").?.data;
-
-            // The init values to use for the struct instance.
-            const field_inits = try gpa.alloc(Air.Inst.Ref, struct_obj.fields.count());
-            defer gpa.free(field_inits);
-
-            var root_msg: ?*Module.ErrorMsg = null;
-
-            for (struct_obj.fields.values()) |field, i| {
-                if (field.default_val.tag() == .unreachable_value) {
-                    const field_name = struct_obj.fields.keys()[i];
-                    const template = "missing struct field: {s}";
-                    const args = .{field_name};
-                    if (root_msg) |msg| {
-                        try sema.errNote(block, src, msg, template, args);
-                    } else {
-                        root_msg = try sema.errMsg(block, src, template, args);
-                    }
-                } else {
-                    field_inits[i] = try sema.addConstant(field.ty, field.default_val);
-                }
-            }
-            return sema.finishStructInit(block, src, field_inits, root_msg, struct_obj, struct_ty, false);
-        },
-        .Array => {
-            if (obj_ty.sentinel()) |sentinel| {
-                const val = try Value.Tag.empty_array_sentinel.create(sema.arena, sentinel);
-                return sema.addConstant(obj_ty, val);
-            } else {
-                return sema.addConstant(obj_ty, Value.initTag(.empty_array));
-            }
-        },
+        .Struct => return structInitEmpty(sema, block, obj_ty, src, src),
+        .Array => return arrayInitEmpty(sema, obj_ty),
         .Void => return sema.addConstant(obj_ty, Value.void),
         else => unreachable,
     }
 }
 
+fn structInitEmpty(sema: *Sema, block: *Block, obj_ty: Type, dest_src: LazySrcLoc, init_src: LazySrcLoc) CompileError!Air.Inst.Ref {
+    const gpa = sema.gpa;
+    // This logic must be synchronized with that in `zirStructInit`.
+    const struct_ty = try sema.resolveTypeFields(block, dest_src, obj_ty);
+    const struct_obj = struct_ty.castTag(.@"struct").?.data;
+
+    // The init values to use for the struct instance.
+    const field_inits = try gpa.alloc(Air.Inst.Ref, struct_obj.fields.count());
+    defer gpa.free(field_inits);
+
+    var root_msg: ?*Module.ErrorMsg = null;
+
+    for (struct_obj.fields.values()) |field, i| {
+        if (field.default_val.tag() == .unreachable_value) {
+            const field_name = struct_obj.fields.keys()[i];
+            const template = "missing struct field: {s}";
+            const args = .{field_name};
+            if (root_msg) |msg| {
+                try sema.errNote(block, init_src, msg, template, args);
+            } else {
+                root_msg = try sema.errMsg(block, init_src, template, args);
+            }
+        } else {
+            field_inits[i] = try sema.addConstant(field.ty, field.default_val);
+        }
+    }
+    return sema.finishStructInit(block, dest_src, field_inits, root_msg, struct_obj, struct_ty, false);
+}
+
+fn arrayInitEmpty(sema: *Sema, obj_ty: Type) CompileError!Air.Inst.Ref {
+    if (obj_ty.sentinel()) |sentinel| {
+        const val = try Value.Tag.empty_array_sentinel.create(sema.arena, sentinel);
+        return sema.addConstant(obj_ty, val);
+    } else {
+        return sema.addConstant(obj_ty, Value.initTag(.empty_array));
+    }
+}
+
 fn zirUnionInitPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
@@ -12284,8 +12288,38 @@ fn zirPrefetch(
     extended: Zir.Inst.Extended.InstData,
 ) CompileError!Air.Inst.Ref {
     const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
-    const src: LazySrcLoc = .{ .node_offset = extra.node };
-    return sema.fail(block, src, "TODO: implement Sema.zirPrefetch", .{});
+    const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
+    const opts_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
+    const options_ty = try sema.getBuiltinType(block, opts_src, "PrefetchOptions");
+    const ptr = sema.resolveInst(extra.lhs);
+    try sema.checkPtrType(block, ptr_src, sema.typeOf(ptr));
+    const options = try sema.coerce(block, options_ty, sema.resolveInst(extra.rhs), opts_src);
+
+    const rw = try sema.fieldVal(block, opts_src, options, "rw", opts_src);
+    const rw_val = try sema.resolveConstValue(block, opts_src, rw);
+    const rw_tag = rw_val.toEnum(std.builtin.PrefetchOptions.Rw);
+
+    const locality = try sema.fieldVal(block, opts_src, options, "locality", opts_src);
+    const locality_val = try sema.resolveConstValue(block, opts_src, locality);
+    const locality_int = @intCast(u2, locality_val.toUnsignedInt());
+
+    const cache = try sema.fieldVal(block, opts_src, options, "cache", opts_src);
+    const cache_val = try sema.resolveConstValue(block, opts_src, cache);
+    const cache_tag = cache_val.toEnum(std.builtin.PrefetchOptions.Cache);
+
+    if (!block.is_comptime) {
+        _ = try block.addInst(.{
+            .tag = .prefetch,
+            .data = .{ .prefetch = .{
+                .ptr = ptr,
+                .rw = rw_tag,
+                .locality = locality_int,
+                .cache = cache_tag,
+            } },
+        });
+    }
+
+    return Air.Inst.Ref.void_value;
 }
 
 fn zirBuiltinExtern(
@@ -13739,6 +13773,11 @@ fn coerce(
         },
         .Array => switch (inst_ty.zigTypeTag()) {
             .Vector => return sema.coerceVectorInMemory(block, dest_ty, dest_ty_src, inst, inst_src),
+            .Struct => {
+                if (inst == .empty_struct) {
+                    return arrayInitEmpty(sema, dest_ty);
+                }
+            },
             else => {},
         },
         .Vector => switch (inst_ty.zigTypeTag()) {
@@ -13746,6 +13785,11 @@ fn coerce(
             .Vector => return sema.coerceVectors(block, dest_ty, dest_ty_src, inst, inst_src),
             else => {},
         },
+        .Struct => {
+            if (inst == .empty_struct) {
+                return structInitEmpty(sema, block, dest_ty, dest_ty_src, inst_src);
+            }
+        },
         else => {},
     }
 
test/behavior.zig
@@ -10,6 +10,7 @@ test {
     _ = @import("behavior/fn_in_struct_in_comptime.zig");
     _ = @import("behavior/hasdecl.zig");
     _ = @import("behavior/hasfield.zig");
+    _ = @import("behavior/prefetch.zig");
     _ = @import("behavior/pub_enum.zig");
     _ = @import("behavior/type_info.zig");
     _ = @import("behavior/type.zig");
@@ -182,7 +183,6 @@ test {
                         _ = @import("behavior/optional_stage1.zig");
                         _ = @import("behavior/pointers_stage1.zig");
                         _ = @import("behavior/popcount_stage1.zig");
-                        _ = @import("behavior/prefetch.zig");
                         _ = @import("behavior/ptrcast_stage1.zig");
                         _ = @import("behavior/reflection.zig");
                         _ = @import("behavior/saturating_arithmetic_stage1.zig");