authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-29 10:04:15+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-29 15:52:19+01:00
loga8888afcc07479bf779105f977597b29aea3c28f
treead34e4c4b6fd0864ef6bd1cee22c2abb156f9414
parent3fbdd58a874c6b4dae84bed2ed31c945ff4adb54
signaturelock-open Commit is signed but in an unrecognized format.

Sema: remove redundant comptime-known initializer tracking

This logic predates certain Sema enhancements whose behavior it essentially tries to emulate in one specific case in a problematic way. In particular, this logic handled initializing comptime-known `const`s through RLS, which was reworked a few years back in 644041b to not rely on this logic, and catching runtime fields in comptime-only initializers, which has since been *correctly* fixed with better checks in `Sema.storePtr2`. That made the highly complex logic in `validateStructInit`, `validateUnionInit`, and `zirValidatePtrArrayInit` entirely redundant. Worse, it was also causing some tracked bugs, as well as a bug which I have identified and fixed in this PR (a corresponding behavior test is added). This commit simplifies union initialization by bringing the runtime logic more in line with the comptime logic: the tag is now always populated by `Sema.unionFieldPtr` based on `initializing`, where this previously happened only in the comptime case (with `validateUnionInit` instead handling it in the runtime case). Notably, this means that backends are now able to consider getting a pointer to an inactive union field as Illegal Behavior, because the `set_union_tag` instruction now appears *before* the `struct_field_ptr` instruction as you would probably expect it to. Resolves: #24520 Resolves: #24595

3 files changed, 105 insertions(+), 544 deletions(-)

src/Sema.zig+89-540
...@@ -4829,13 +4829,13 @@ fn zirValidatePtrStructInit(...@@ -4829,13 +4829,13 @@ fn zirValidatePtrStructInit(
4829 agg_ty,4829 agg_ty,
4830 init_src,4830 init_src,
4831 instrs,4831 instrs,
4832 object_ptr,
4832 ),4833 ),
4833 .@"union" => return sema.validateUnionInit(4834 .@"union" => return sema.validateUnionInit(
4834 block,4835 block,
4835 agg_ty,4836 agg_ty,
4836 init_src,4837 init_src,
4837 instrs,4838 instrs,
4838 object_ptr,
4839 ),4839 ),
4840 else => unreachable,4840 else => unreachable,
4841 }4841 }
...@@ -4847,164 +4847,28 @@ fn validateUnionInit(...@@ -4847,164 +4847,28 @@ fn validateUnionInit(
4847 union_ty: Type,4847 union_ty: Type,
4848 init_src: LazySrcLoc,4848 init_src: LazySrcLoc,
4849 instrs: []const Zir.Inst.Index,4849 instrs: []const Zir.Inst.Index,
4850 union_ptr: Air.Inst.Ref,
4851) CompileError!void {4850) CompileError!void {
4852 const pt = sema.pt;4851 if (instrs.len == 1) {
4853 const zcu = pt.zcu;4852 // Trvial validation done, and the union tag was already set by machinery in `unionFieldPtr`.
4854 const gpa = sema.gpa;
4855
4856 if (instrs.len != 1) {
4857 const msg = msg: {
4858 const msg = try sema.errMsg(
4859 init_src,
4860 "cannot initialize multiple union fields at once; unions can only have one active field",
4861 .{},
4862 );
4863 errdefer msg.destroy(gpa);
4864
4865 for (instrs[1..]) |inst| {
4866 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
4867 const inst_src = block.src(.{ .node_offset_initializer = inst_data.src_node });
4868 try sema.errNote(inst_src, msg, "additional initializer here", .{});
4869 }
4870 try sema.addDeclaredHereNote(msg, union_ty);
4871 break :msg msg;
4872 };
4873 return sema.failWithOwnedErrorMsg(block, msg);
4874 }
4875
4876 if (block.isComptime() and
4877 (try sema.resolveDefinedValue(block, init_src, union_ptr)) != null)
4878 {
4879 // In this case, comptime machinery already did everything. No work to do here.
4880 return;4853 return;
4881 }4854 }
4855 const msg = msg: {
4856 const msg = try sema.errMsg(
4857 init_src,
4858 "cannot initialize multiple union fields at once; unions can only have one active field",
4859 .{},
4860 );
4861 errdefer msg.destroy(sema.gpa);
48824862
4883 const field_ptr = instrs[0];4863 for (instrs[1..]) |inst| {
4884 const field_ptr_data = sema.code.instructions.items(.data)[@intFromEnum(field_ptr)].pl_node;4864 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
4885 const field_src = block.src(.{ .node_offset_initializer = field_ptr_data.src_node });4865 const inst_src = block.src(.{ .node_offset_initializer = inst_data.src_node });
4886 const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;4866 try sema.errNote(inst_src, msg, "additional initializer here", .{});
4887 const field_name = try zcu.intern_pool.getOrPutString(
4888 gpa,
4889 pt.tid,
4890 sema.code.nullTerminatedString(field_ptr_extra.field_name_start),
4891 .no_embedded_nulls,
4892 );
4893 const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_src);
4894 const air_tags = sema.air_instructions.items(.tag);
4895 const air_datas = sema.air_instructions.items(.data);
4896 const field_ptr_ref = sema.inst_map.get(field_ptr).?;
4897
4898 // Our task here is to determine if the union is comptime-known. In such case,
4899 // we erase the runtime AIR instructions for initializing the union, and replace
4900 // the mapping with the comptime value. Either way, we will need to populate the tag.
4901
4902 // We expect to see something like this in the current block AIR:
4903 // %a = alloc(*const U)
4904 // %b = bitcast(*U, %a)
4905 // %c = field_ptr(..., %b)
4906 // %e!= store(%c!, %d!)
4907 // If %d is a comptime operand, the union is comptime.
4908 // If the union is comptime, we want `first_block_index`
4909 // to point at %c so that the bitcast becomes the last instruction in the block.
4910 //
4911 // Store instruction may be missing; if field type has only one possible value, this case is handled below.
4912 //
4913 // In the case of a comptime-known pointer to a union, the
4914 // the field_ptr instruction is missing, so we have to pattern-match
4915 // based only on the store instructions.
4916 // `first_block_index` needs to point to the `field_ptr` if it exists;
4917 // the `store` otherwise.
4918 var first_block_index = block.instructions.items.len;
4919 var block_index = block.instructions.items.len - 1;
4920 var init_val: ?Value = null;
4921 var init_ref: ?Air.Inst.Ref = null;
4922 while (block_index > 0) : (block_index -= 1) {
4923 const store_inst = block.instructions.items[block_index];
4924 if (store_inst.toRef() == field_ptr_ref) {
4925 first_block_index = block_index;
4926 break;
4927 }
4928 switch (air_tags[@intFromEnum(store_inst)]) {
4929 .store, .store_safe => {},
4930 else => continue,
4931 }4867 }
4932 const bin_op = air_datas[@intFromEnum(store_inst)].bin_op;4868 try sema.addDeclaredHereNote(msg, union_ty);
4933 var ptr_ref = bin_op.lhs;4869 break :msg msg;
4934 if (ptr_ref.toIndex()) |ptr_inst| if (air_tags[@intFromEnum(ptr_inst)] == .bitcast) {4870 };
4935 ptr_ref = air_datas[@intFromEnum(ptr_inst)].ty_op.operand;4871 return sema.failWithOwnedErrorMsg(block, msg);
4936 };
4937 if (ptr_ref != field_ptr_ref) continue;
4938 first_block_index = @min(if (field_ptr_ref.toIndex()) |field_ptr_inst|
4939 std.mem.lastIndexOfScalar(
4940 Air.Inst.Index,
4941 block.instructions.items[0..block_index],
4942 field_ptr_inst,
4943 ).?
4944 else
4945 block_index, first_block_index);
4946 init_ref = bin_op.rhs;
4947 init_val = try sema.resolveValue(bin_op.rhs);
4948 break;
4949 }
4950
4951 const tag_ty = union_ty.unionTagTypeHypothetical(zcu);
4952 const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index);
4953 const field_type = union_ty.unionFieldType(tag_val, zcu).?;
4954
4955 if (try sema.typeHasOnePossibleValue(field_type)) |field_only_value| {
4956 init_val = field_only_value;
4957 }
4958
4959 if (init_val) |val| {
4960 // Our task is to delete all the `field_ptr` and `store` instructions, and insert
4961 // instead a single `store` to the result ptr with a comptime union value.
4962 block_index = first_block_index;
4963 for (block.instructions.items[first_block_index..]) |cur_inst| {
4964 switch (air_tags[@intFromEnum(cur_inst)]) {
4965 .struct_field_ptr,
4966 .struct_field_ptr_index_0,
4967 .struct_field_ptr_index_1,
4968 .struct_field_ptr_index_2,
4969 .struct_field_ptr_index_3,
4970 => if (cur_inst.toRef() == field_ptr_ref) continue,
4971 .bitcast => if (air_datas[@intFromEnum(cur_inst)].ty_op.operand == field_ptr_ref) continue,
4972 .store, .store_safe => {
4973 var ptr_ref = air_datas[@intFromEnum(cur_inst)].bin_op.lhs;
4974 if (ptr_ref.toIndex()) |ptr_inst| if (air_tags[@intFromEnum(ptr_inst)] == .bitcast) {
4975 ptr_ref = air_datas[@intFromEnum(ptr_inst)].ty_op.operand;
4976 };
4977 if (ptr_ref == field_ptr_ref) continue;
4978 },
4979 else => {},
4980 }
4981 block.instructions.items[block_index] = cur_inst;
4982 block_index += 1;
4983 }
4984 block.instructions.shrinkRetainingCapacity(block_index);
4985
4986 const union_val = try pt.internUnion(.{
4987 .ty = union_ty.toIntern(),
4988 .tag = tag_val.toIntern(),
4989 .val = val.toIntern(),
4990 });
4991 const union_init = Air.internedToRef(union_val);
4992 try sema.storePtr2(block, init_src, union_ptr, init_src, union_init, init_src, .store);
4993 return;
4994 } else if (try union_ty.comptimeOnlySema(pt)) {
4995 const src = block.nodeOffset(field_ptr_data.src_node);
4996 return sema.failWithNeededComptime(block, src, .{ .comptime_only = .{
4997 .ty = union_ty,
4998 .msg = .union_init,
4999 } });
5000 }
5001 if (init_ref) |v| try sema.validateRuntimeValue(block, block.nodeOffset(field_ptr_data.src_node), v);
5002
5003 if ((try sema.typeHasOnePossibleValue(tag_ty)) == null) {
5004 const new_tag = Air.internedToRef(tag_val.toIntern());
5005 const set_tag_inst = try block.addBinOp(.set_union_tag, union_ptr, new_tag);
5006 try sema.checkComptimeKnownStore(block, set_tag_inst, LazySrcLoc.unneeded); // `unneeded` since this isn't a "proper" store
5007 }
5008}4872}
50094873
5010fn validateStructInit(4874fn validateStructInit(
...@@ -5013,187 +4877,62 @@ fn validateStructInit(...@@ -5013,187 +4877,62 @@ fn validateStructInit(
5013 struct_ty: Type,4877 struct_ty: Type,
5014 init_src: LazySrcLoc,4878 init_src: LazySrcLoc,
5015 instrs: []const Zir.Inst.Index,4879 instrs: []const Zir.Inst.Index,
4880 struct_ptr: Air.Inst.Ref,
5016) CompileError!void {4881) CompileError!void {
5017 const pt = sema.pt;4882 const pt = sema.pt;
5018 const zcu = pt.zcu;4883 const zcu = pt.zcu;
5019 const gpa = sema.gpa;4884 const gpa = sema.gpa;
5020 const ip = &zcu.intern_pool;4885 const ip = &zcu.intern_pool;
50214886
5022 const field_indices = try gpa.alloc(u32, instrs.len);4887 // Tracks whether each field was explicitly initialized.
5023 defer gpa.free(field_indices);4888 const found_fields = try gpa.alloc(bool, struct_ty.structFieldCount(zcu));
5024
5025 // Maps field index to field_ptr index of where it was already initialized.
5026 const found_fields = try gpa.alloc(Zir.Inst.OptionalIndex, struct_ty.structFieldCount(zcu));
5027 defer gpa.free(found_fields);4889 defer gpa.free(found_fields);
5028 @memset(found_fields, .none);4890 @memset(found_fields, false);
50294891
5030 var struct_ptr_zir_ref: Zir.Inst.Ref = undefined;4892 for (instrs) |field_ptr| {
5031
5032 for (instrs, field_indices) |field_ptr, *field_index| {
5033 const field_ptr_data = sema.code.instructions.items(.data)[@intFromEnum(field_ptr)].pl_node;4893 const field_ptr_data = sema.code.instructions.items(.data)[@intFromEnum(field_ptr)].pl_node;
5034 const field_src = block.src(.{ .node_offset_initializer = field_ptr_data.src_node });4894 const field_src = block.src(.{ .node_offset_initializer = field_ptr_data.src_node });
5035 const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;4895 const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
5036 struct_ptr_zir_ref = field_ptr_extra.lhs;
5037 const field_name = try ip.getOrPutString(4896 const field_name = try ip.getOrPutString(
5038 gpa,4897 gpa,
5039 pt.tid,4898 pt.tid,
5040 sema.code.nullTerminatedString(field_ptr_extra.field_name_start),4899 sema.code.nullTerminatedString(field_ptr_extra.field_name_start),
5041 .no_embedded_nulls,4900 .no_embedded_nulls,
5042 );4901 );
5043 field_index.* = if (struct_ty.isTuple(zcu))4902 const field_index = if (struct_ty.isTuple(zcu))
5044 try sema.tupleFieldIndex(block, struct_ty, field_name, field_src)4903 try sema.tupleFieldIndex(block, struct_ty, field_name, field_src)
5045 else4904 else
5046 try sema.structFieldIndex(block, struct_ty, field_name, field_src);4905 try sema.structFieldIndex(block, struct_ty, field_name, field_src);
5047 assert(found_fields[field_index.*] == .none);4906 assert(found_fields[field_index] == false);
5048 found_fields[field_index.*] = field_ptr.toOptional();4907 found_fields[field_index] = true;
5049 }4908 }
50504909
5051 var root_msg: ?*Zcu.ErrorMsg = null;4910 // Our job is simply to deal with default field values. Specifically, any field which was not
5052 errdefer if (root_msg) |msg| msg.destroy(sema.gpa);4911 // explicitly initialized must have its default value stored to the field pointer, or, if the
50534912 // field has no default value, a compile error must be emitted instead.
5054 const struct_ptr = try sema.resolveInst(struct_ptr_zir_ref);
5055 if (block.isComptime() and
5056 (try sema.resolveDefinedValue(block, init_src, struct_ptr)) != null)
5057 {
5058 try struct_ty.resolveLayout(pt);
5059 // In this case the only thing we need to do is evaluate the implicit
5060 // store instructions for default field values, and report any missing fields.
5061 // Avoid the cost of the extra machinery for detecting a comptime struct init value.
5062 for (found_fields, 0..) |field_ptr, i_usize| {
5063 const i: u32 = @intCast(i_usize);
5064 if (field_ptr != .none) continue;
50654913
5066 try struct_ty.resolveStructFieldInits(pt);4914 // In the past, this code had other responsibilities, which involved some nasty AIR rewrites. However,
5067 const default_val = struct_ty.structFieldDefaultValue(i, zcu);4915 // that work was actually all redundant:
5068 if (default_val.toIntern() == .unreachable_value) {4916 //
5069 const field_name = struct_ty.structFieldName(i, zcu).unwrap() orelse {4917 // * If the struct value is comptime-known, field stores remain a perfectly valid way of initializing
5070 const template = "missing tuple field with index {d}";4918 // the struct through RLS; there is no need to turn the field stores into one store. Comptime-known
5071 if (root_msg) |msg| {4919 // consts are handled correctly either way thanks to `maybe_comptime_allocs` and friends.
5072 try sema.errNote(init_src, msg, template, .{i});4920 //
5073 } else {4921 // * If the struct type is comptime-only, we need to make sure all of the fields were comptime-known.
5074 root_msg = try sema.errMsg(init_src, template, .{i});4922 // But the comptime-only type means that `struct_ptr` must be a comptime-mutable pointer, so the
5075 }4923 // field stores were to comptime-mutable pointers, so have already errored if not comptime-known.
5076 continue;4924 //
5077 };4925 // * If the value is runtime-known, then comptime-known fields must be validated as runtime values.
5078 const template = "missing struct field: {f}";4926 // But this was already handled for every field store by the machinery in `checkComptimeKnownStore`.
5079 const args = .{field_name.fmt(ip)};
5080 if (root_msg) |msg| {
5081 try sema.errNote(init_src, msg, template, args);
5082 } else {
5083 root_msg = try sema.errMsg(init_src, template, args);
5084 }
5085 continue;
5086 }
5087
5088 const field_src = init_src; // TODO better source location
5089 const default_field_ptr = if (struct_ty.isTuple(zcu))
5090 try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(i), true)
5091 else
5092 try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(i), struct_ty);
5093 const init = Air.internedToRef(default_val.toIntern());
5094 try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store);
5095 }
5096
5097 if (root_msg) |msg| {
5098 try sema.addDeclaredHereNote(msg, struct_ty);
5099 root_msg = null;
5100 return sema.failWithOwnedErrorMsg(block, msg);
5101 }
5102
5103 return;
5104 }
5105
5106 var fields_allow_runtime = true;
5107
5108 var struct_is_comptime = true;
5109 var first_block_index = block.instructions.items.len;
5110
5111 const require_comptime = try struct_ty.comptimeOnlySema(pt);
5112 const air_tags = sema.air_instructions.items(.tag);
5113 const air_datas = sema.air_instructions.items(.data);
5114
5115 try struct_ty.resolveStructFieldInits(pt);
51164927
5117 // We collect the comptime field values in case the struct initialization4928 var root_msg: ?*Zcu.ErrorMsg = null;
5118 // ends up being comptime-known.4929 errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
5119 const field_values = try sema.arena.alloc(InternPool.Index, struct_ty.structFieldCount(zcu));
51204930
5121 field: for (found_fields, 0..) |opt_field_ptr, i_usize| {4931 for (found_fields, 0..) |explicit, i_usize| {
4932 if (explicit) continue;
5122 const i: u32 = @intCast(i_usize);4933 const i: u32 = @intCast(i_usize);
5123 if (opt_field_ptr.unwrap()) |field_ptr| {
5124 // Determine whether the value stored to this pointer is comptime-known.
5125 const field_ty = struct_ty.fieldType(i, zcu);
5126 if (try sema.typeHasOnePossibleValue(field_ty)) |opv| {
5127 field_values[i] = opv.toIntern();
5128 continue;
5129 }
5130
5131 const field_ptr_ref = sema.inst_map.get(field_ptr).?;
5132
5133 //std.debug.print("validateStructInit (field_ptr_ref=%{d}):\n", .{field_ptr_ref});
5134 //for (block.instructions.items) |item| {
5135 // std.debug.print(" %{d} = {s}\n", .{item, @tagName(air_tags[@intFromEnum(item)])});
5136 //}
5137
5138 // We expect to see something like this in the current block AIR:
5139 // %a = field_ptr(...)
5140 // store(%a, %b)
5141 // With an optional bitcast between the store and the field_ptr.
5142 // If %b is a comptime operand, this field is comptime.
5143 //
5144 // However, in the case of a comptime-known pointer to a struct, the
5145 // the field_ptr instruction is missing, so we have to pattern-match
5146 // based only on the store instructions.
5147 // `first_block_index` needs to point to the `field_ptr` if it exists;
5148 // the `store` otherwise.
5149
5150 // Possible performance enhancement: save the `block_index` between iterations
5151 // of the for loop.
5152 var block_index = block.instructions.items.len;
5153 while (block_index > 0) {
5154 block_index -= 1;
5155 const store_inst = block.instructions.items[block_index];
5156 if (store_inst.toRef() == field_ptr_ref) {
5157 struct_is_comptime = false;
5158 continue :field;
5159 }
5160 switch (air_tags[@intFromEnum(store_inst)]) {
5161 .store, .store_safe => {},
5162 else => continue,
5163 }
5164 const bin_op = air_datas[@intFromEnum(store_inst)].bin_op;
5165 var ptr_ref = bin_op.lhs;
5166 if (ptr_ref.toIndex()) |ptr_inst| if (air_tags[@intFromEnum(ptr_inst)] == .bitcast) {
5167 ptr_ref = air_datas[@intFromEnum(ptr_inst)].ty_op.operand;
5168 };
5169 if (ptr_ref != field_ptr_ref) continue;
5170 first_block_index = @min(if (field_ptr_ref.toIndex()) |field_ptr_inst|
5171 std.mem.lastIndexOfScalar(
5172 Air.Inst.Index,
5173 block.instructions.items[0..block_index],
5174 field_ptr_inst,
5175 ).?
5176 else
5177 block_index, first_block_index);
5178 if (!sema.checkRuntimeValue(bin_op.rhs)) fields_allow_runtime = false;
5179 if (try sema.resolveValue(bin_op.rhs)) |val| {
5180 field_values[i] = val.toIntern();
5181 } else if (require_comptime) {
5182 const field_ptr_data = sema.code.instructions.items(.data)[@intFromEnum(field_ptr)].pl_node;
5183 const src = block.nodeOffset(field_ptr_data.src_node);
5184 return sema.failWithNeededComptime(block, src, .{ .comptime_only = .{
5185 .ty = struct_ty,
5186 .msg = .struct_init,
5187 } });
5188 } else {
5189 struct_is_comptime = false;
5190 }
5191 continue :field;
5192 }
5193 struct_is_comptime = false;
5194 continue :field;
5195 }
51964934
4935 try struct_ty.resolveStructFieldInits(pt);
5197 const default_val = struct_ty.structFieldDefaultValue(i, zcu);4936 const default_val = struct_ty.structFieldDefaultValue(i, zcu);
5198 if (default_val.toIntern() == .unreachable_value) {4937 if (default_val.toIntern() == .unreachable_value) {
5199 const field_name = struct_ty.structFieldName(i, zcu).unwrap() orelse {4938 const field_name = struct_ty.structFieldName(i, zcu).unwrap() orelse {
...@@ -5214,70 +4953,6 @@ fn validateStructInit(...@@ -5214,70 +4953,6 @@ fn validateStructInit(
5214 }4953 }
5215 continue;4954 continue;
5216 }4955 }
5217 field_values[i] = default_val.toIntern();
5218 }
5219
5220 if (!struct_is_comptime and !fields_allow_runtime and root_msg == null) {
5221 root_msg = try sema.errMsg(init_src, "runtime value contains reference to comptime var", .{});
5222 try sema.errNote(init_src, root_msg.?, "comptime var pointers are not available at runtime", .{});
5223 }
5224
5225 if (root_msg) |msg| {
5226 try sema.addDeclaredHereNote(msg, struct_ty);
5227 root_msg = null;
5228 return sema.failWithOwnedErrorMsg(block, msg);
5229 }
5230
5231 if (struct_is_comptime) {
5232 // Our task is to delete all the `field_ptr` and `store` instructions, and insert
5233 // instead a single `store` to the struct_ptr with a comptime struct value.
5234 var init_index: usize = 0;
5235 var field_ptr_ref = Air.Inst.Ref.none;
5236 var block_index = first_block_index;
5237 for (block.instructions.items[first_block_index..]) |cur_inst| {
5238 while (field_ptr_ref == .none and init_index < instrs.len) : (init_index += 1) {
5239 const field_ty = struct_ty.fieldType(field_indices[init_index], zcu);
5240 if (try field_ty.onePossibleValue(pt)) |_| continue;
5241 field_ptr_ref = sema.inst_map.get(instrs[init_index]).?;
5242 }
5243 switch (air_tags[@intFromEnum(cur_inst)]) {
5244 .struct_field_ptr,
5245 .struct_field_ptr_index_0,
5246 .struct_field_ptr_index_1,
5247 .struct_field_ptr_index_2,
5248 .struct_field_ptr_index_3,
5249 => if (cur_inst.toRef() == field_ptr_ref) continue,
5250 .bitcast => if (air_datas[@intFromEnum(cur_inst)].ty_op.operand == field_ptr_ref) continue,
5251 .store, .store_safe => {
5252 var ptr_ref = air_datas[@intFromEnum(cur_inst)].bin_op.lhs;
5253 if (ptr_ref.toIndex()) |ptr_inst| if (air_tags[@intFromEnum(ptr_inst)] == .bitcast) {
5254 ptr_ref = air_datas[@intFromEnum(ptr_inst)].ty_op.operand;
5255 };
5256 if (ptr_ref == field_ptr_ref) {
5257 field_ptr_ref = .none;
5258 continue;
5259 }
5260 },
5261 else => {},
5262 }
5263 block.instructions.items[block_index] = cur_inst;
5264 block_index += 1;
5265 }
5266 block.instructions.shrinkRetainingCapacity(block_index);
5267
5268 const struct_val = try pt.intern(.{ .aggregate = .{
5269 .ty = struct_ty.toIntern(),
5270 .storage = .{ .elems = field_values },
5271 } });
5272 const struct_init = Air.internedToRef(struct_val);
5273 try sema.storePtr2(block, init_src, struct_ptr, init_src, struct_init, init_src, .store);
5274 return;
5275 }
5276 try struct_ty.resolveLayout(pt);
5277
5278 // Our task is to insert `store` instructions for all the default field values.
5279 for (found_fields, 0..) |field_ptr, i| {
5280 if (field_ptr != .none) continue;
52814956
5282 const field_src = init_src; // TODO better source location4957 const field_src = init_src; // TODO better source location
5283 const default_field_ptr = if (struct_ty.isTuple(zcu))4958 const default_field_ptr = if (struct_ty.isTuple(zcu))
...@@ -5285,8 +4960,13 @@ fn validateStructInit(...@@ -5285,8 +4960,13 @@ fn validateStructInit(
5285 else4960 else
5286 try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(i), struct_ty);4961 try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(i), struct_ty);
5287 try sema.checkKnownAllocPtr(block, struct_ptr, default_field_ptr);4962 try sema.checkKnownAllocPtr(block, struct_ptr, default_field_ptr);
5288 const init = Air.internedToRef(field_values[i]);4963 try sema.storePtr2(block, init_src, default_field_ptr, init_src, .fromValue(default_val), field_src, .store);
5289 try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store);4964 }
4965
4966 if (root_msg) |msg| {
4967 try sema.addDeclaredHereNote(msg, struct_ty);
4968 root_msg = null;
4969 return sema.failWithOwnedErrorMsg(block, msg);
5290 }4970 }
5291}4971}
52924972
...@@ -5307,15 +4987,14 @@ fn zirValidatePtrArrayInit(...@@ -5307,15 +4987,14 @@ fn zirValidatePtrArrayInit(
5307 const array_ty = sema.typeOf(array_ptr).childType(zcu).optEuBaseType(zcu);4987 const array_ty = sema.typeOf(array_ptr).childType(zcu).optEuBaseType(zcu);
5308 const array_len = array_ty.arrayLen(zcu);4988 const array_len = array_ty.arrayLen(zcu);
53094989
5310 // Collect the comptime element values in case the array literal ends up4990 // Analagously to `validateStructInit`, our job is to handle default fields; either emitting AIR
5311 // being comptime-known.4991 // to initialize them, or emitting a compile error if an unspecified field has no default. For
5312 const element_vals = try sema.arena.alloc(4992 // tuples, there are literally default field values, although they're guaranteed to be comptime
5313 InternPool.Index,4993 // fields so we don't need to initialize them. For arrays, we may have a sentinel, which is never
5314 try sema.usizeCast(block, init_src, array_len),4994 // specified so we always need to initialize here. For vectors, there's no such thing.
5315 );
53164995
5317 if (instrs.len != array_len) switch (array_ty.zigTypeTag(zcu)) {4996 switch (array_ty.zigTypeTag(zcu)) {
5318 .@"struct" => {4997 .@"struct" => if (instrs.len != array_len) {
5319 var root_msg: ?*Zcu.ErrorMsg = null;4998 var root_msg: ?*Zcu.ErrorMsg = null;
5320 errdefer if (root_msg) |msg| msg.destroy(sema.gpa);4999 errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
53215000
...@@ -5332,8 +5011,6 @@ fn zirValidatePtrArrayInit(...@@ -5332,8 +5011,6 @@ fn zirValidatePtrArrayInit(
5332 }5011 }
5333 continue;5012 continue;
5334 }5013 }
5335
5336 element_vals[i] = default_val;
5337 }5014 }
53385015
5339 if (root_msg) |msg| {5016 if (root_msg) |msg| {
...@@ -5341,162 +5018,25 @@ fn zirValidatePtrArrayInit(...@@ -5341,162 +5018,25 @@ fn zirValidatePtrArrayInit(
5341 return sema.failWithOwnedErrorMsg(block, msg);5018 return sema.failWithOwnedErrorMsg(block, msg);
5342 }5019 }
5343 },5020 },
5344 .array => {5021
5022 .array => if (instrs.len != array_len) {
5345 return sema.fail(block, init_src, "expected {d} array elements; found {d}", .{5023 return sema.fail(block, init_src, "expected {d} array elements; found {d}", .{
5346 array_len, instrs.len,5024 array_len, instrs.len,
5347 });5025 });
5026 } else if (array_ty.sentinel(zcu)) |sentinel| {
5027 const array_len_ref = try pt.intRef(.usize, array_len);
5028 const sentinel_ptr = try sema.elemPtrArray(block, init_src, init_src, array_ptr, init_src, array_len_ref, true, true);
5029 try sema.checkKnownAllocPtr(block, array_ptr, sentinel_ptr);
5030 try sema.storePtr2(block, init_src, sentinel_ptr, init_src, .fromValue(sentinel), init_src, .store);
5348 },5031 },
5349 .vector => {5032
5033 .vector => if (instrs.len != array_len) {
5350 return sema.fail(block, init_src, "expected {d} vector elements; found {d}", .{5034 return sema.fail(block, init_src, "expected {d} vector elements; found {d}", .{
5351 array_len, instrs.len,5035 array_len, instrs.len,
5352 });5036 });
5353 },5037 },
5354 else => unreachable,
5355 };
5356
5357 if (block.isComptime() and
5358 (try sema.resolveDefinedValue(block, init_src, array_ptr)) != null)
5359 {
5360 // In this case the comptime machinery will have evaluated the store instructions
5361 // at comptime so we have almost nothing to do here. However, in case of a
5362 // sentinel-terminated array, the sentinel will not have been populated by
5363 // any ZIR instructions at comptime; we need to do that here.
5364 if (array_ty.sentinel(zcu)) |sentinel_val| {
5365 const array_len_ref = try pt.intRef(.usize, array_len);
5366 const sentinel_ptr = try sema.elemPtrArray(block, init_src, init_src, array_ptr, init_src, array_len_ref, true, true);
5367 const sentinel = Air.internedToRef(sentinel_val.toIntern());
5368 try sema.storePtr2(block, init_src, sentinel_ptr, init_src, sentinel, init_src, .store);
5369 }
5370 return;
5371 }
5372
5373 // If the array has one possible value, the value is always comptime-known.
5374 if (try sema.typeHasOnePossibleValue(array_ty)) |array_opv| {
5375 const array_init = Air.internedToRef(array_opv.toIntern());
5376 try sema.storePtr2(block, init_src, array_ptr, init_src, array_init, init_src, .store);
5377 return;
5378 }
5379
5380 var array_is_comptime = true;
5381 var first_block_index = block.instructions.items.len;
5382
5383 const air_tags = sema.air_instructions.items(.tag);
5384 const air_datas = sema.air_instructions.items(.data);
5385
5386 outer: for (instrs, 0..) |elem_ptr, i| {
5387 // Determine whether the value stored to this pointer is comptime-known.
5388
5389 if (array_ty.isTuple(zcu)) {
5390 if (array_ty.structFieldIsComptime(i, zcu))
5391 try array_ty.resolveStructFieldInits(pt);
5392 if (try array_ty.structFieldValueComptime(pt, i)) |opv| {
5393 element_vals[i] = opv.toIntern();
5394 continue;
5395 }
5396 }
5397
5398 const elem_ptr_ref = sema.inst_map.get(elem_ptr).?;
53995038
5400 // We expect to see something like this in the current block AIR:5039 else => unreachable,
5401 // %a = elem_ptr(...)
5402 // store(%a, %b)
5403 // With an optional bitcast between the store and the elem_ptr.
5404 // If %b is a comptime operand, this element is comptime.
5405 //
5406 // However, in the case of a comptime-known pointer to an array, the
5407 // the elem_ptr instruction is missing, so we have to pattern-match
5408 // based only on the store instructions.
5409 // `first_block_index` needs to point to the `elem_ptr` if it exists;
5410 // the `store` otherwise.
5411 //
5412 // This is nearly identical to similar logic in `validateStructInit`.
5413
5414 // Possible performance enhancement: save the `block_index` between iterations
5415 // of the for loop.
5416 var block_index = block.instructions.items.len;
5417 while (block_index > 0) {
5418 block_index -= 1;
5419 const store_inst = block.instructions.items[block_index];
5420 if (store_inst.toRef() == elem_ptr_ref) {
5421 array_is_comptime = false;
5422 continue :outer;
5423 }
5424 switch (air_tags[@intFromEnum(store_inst)]) {
5425 .store, .store_safe => {},
5426 else => continue,
5427 }
5428 const bin_op = air_datas[@intFromEnum(store_inst)].bin_op;
5429 var ptr_ref = bin_op.lhs;
5430 if (ptr_ref.toIndex()) |ptr_inst| if (air_tags[@intFromEnum(ptr_inst)] == .bitcast) {
5431 ptr_ref = air_datas[@intFromEnum(ptr_inst)].ty_op.operand;
5432 };
5433 if (ptr_ref != elem_ptr_ref) continue;
5434 first_block_index = @min(if (elem_ptr_ref.toIndex()) |elem_ptr_inst|
5435 std.mem.lastIndexOfScalar(
5436 Air.Inst.Index,
5437 block.instructions.items[0..block_index],
5438 elem_ptr_inst,
5439 ).?
5440 else
5441 block_index, first_block_index);
5442 if (try sema.resolveValue(bin_op.rhs)) |val| {
5443 element_vals[i] = val.toIntern();
5444 } else {
5445 array_is_comptime = false;
5446 }
5447 continue :outer;
5448 }
5449 array_is_comptime = false;
5450 continue :outer;
5451 }
5452
5453 if (array_is_comptime) {
5454 if (try sema.resolveDefinedValue(block, init_src, array_ptr)) |ptr_val| {
5455 switch (zcu.intern_pool.indexToKey(ptr_val.toIntern())) {
5456 .ptr => |ptr| switch (ptr.base_addr) {
5457 .comptime_field => return, // This store was validated by the individual elem ptrs.
5458 else => {},
5459 },
5460 else => {},
5461 }
5462 }
5463
5464 // Our task is to delete all the `elem_ptr` and `store` instructions, and insert
5465 // instead a single `store` to the array_ptr with a comptime struct value.
5466 var elem_index: usize = 0;
5467 var elem_ptr_ref = Air.Inst.Ref.none;
5468 var block_index = first_block_index;
5469 for (block.instructions.items[first_block_index..]) |cur_inst| {
5470 while (elem_ptr_ref == .none and elem_index < instrs.len) : (elem_index += 1) {
5471 if (array_ty.isTuple(zcu) and array_ty.structFieldIsComptime(elem_index, zcu)) continue;
5472 elem_ptr_ref = sema.inst_map.get(instrs[elem_index]).?;
5473 }
5474 switch (air_tags[@intFromEnum(cur_inst)]) {
5475 .ptr_elem_ptr => if (cur_inst.toRef() == elem_ptr_ref) continue,
5476 .bitcast => if (air_datas[@intFromEnum(cur_inst)].ty_op.operand == elem_ptr_ref) continue,
5477 .store, .store_safe => {
5478 var ptr_ref = air_datas[@intFromEnum(cur_inst)].bin_op.lhs;
5479 if (ptr_ref.toIndex()) |ptr_inst| if (air_tags[@intFromEnum(ptr_inst)] == .bitcast) {
5480 ptr_ref = air_datas[@intFromEnum(ptr_inst)].ty_op.operand;
5481 };
5482 if (ptr_ref == elem_ptr_ref) {
5483 elem_ptr_ref = .none;
5484 continue;
5485 }
5486 },
5487 else => {},
5488 }
5489 block.instructions.items[block_index] = cur_inst;
5490 block_index += 1;
5491 }
5492 block.instructions.shrinkRetainingCapacity(block_index);
5493
5494 const array_val = try pt.intern(.{ .aggregate = .{
5495 .ty = array_ty.toIntern(),
5496 .storage = .{ .elems = element_vals },
5497 } });
5498 const array_init = Air.internedToRef(array_val);
5499 try sema.storePtr2(block, init_src, array_ptr, init_src, array_init, init_src, .store);
5500 }5040 }
5501}5041}
55025042
...@@ -28015,15 +27555,24 @@ fn unionFieldPtr(...@@ -28015,15 +27555,24 @@ fn unionFieldPtr(
28015 return Air.internedToRef(field_ptr_val.toIntern());27555 return Air.internedToRef(field_ptr_val.toIntern());
28016 }27556 }
2801727557
28018 if (!initializing and union_obj.flagsUnordered(ip).layout == .auto and block.wantSafety() and27558 // If the union has a tag, we must either set or or safety check it depending on `initializing`.
28019 union_ty.unionTagTypeSafety(zcu) != null and union_obj.field_types.len > 1)27559 tag: {
28020 {27560 if (union_ty.containerLayout(zcu) != .auto) break :tag;
28021 const wanted_tag_val = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_ty), enum_field_index);27561 const tag_ty: Type = .fromInterned(union_obj.enum_tag_ty);
28022 const wanted_tag = Air.internedToRef(wanted_tag_val.toIntern());27562 if (try sema.typeHasOnePossibleValue(tag_ty) != null) break :tag;
28023 // TODO would it be better if get_union_tag supported pointers to unions?27563 // There is a hypothetical non-trivial tag. We must set it even if not there at runtime, but
28024 const union_val = try block.addTyOp(.load, union_ty, union_ptr);27564 // only emit a safety check if it's available at runtime (i.e. it's safety-tagged).
28025 const active_tag = try block.addTyOp(.get_union_tag, .fromInterned(union_obj.enum_tag_ty), union_val);27565 const want_tag = try pt.enumValueFieldIndex(tag_ty, enum_field_index);
28026 try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, wanted_tag);27566 if (initializing) {
27567 const set_tag_inst = try block.addBinOp(.set_union_tag, union_ptr, .fromValue(want_tag));
27568 try sema.checkComptimeKnownStore(block, set_tag_inst, .unneeded); // `unneeded` since this isn't a "proper" store
27569 } else if (block.wantSafety() and union_obj.hasTag(ip)) {
27570 // The tag exists at runtime (safety tag), so emit a safety check.
27571 // TODO would it be better if get_union_tag supported pointers to unions?
27572 const union_val = try block.addTyOp(.load, union_ty, union_ptr);
27573 const active_tag = try block.addTyOp(.get_union_tag, tag_ty, union_val);
27574 try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, .fromValue(want_tag));
27575 }
28027 }27576 }
28028 if (field_ty.zigTypeTag(zcu) == .noreturn) {27577 if (field_ty.zigTypeTag(zcu) == .noreturn) {
28029 _ = try block.addNoOp(.unreach);27578 _ = try block.addNoOp(.unreach);
test/behavior/array.zig+13-1
...@@ -540,7 +540,6 @@ test "sentinel element count towards the ABI size calculation" {...@@ -540,7 +540,6 @@ test "sentinel element count towards the ABI size calculation" {
540}540}
541541
542test "zero-sized array with recursive type definition" {542test "zero-sized array with recursive type definition" {
543 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
544 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO543 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
545 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;544 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
546545
...@@ -1098,3 +1097,16 @@ test "initialize pointer to anyopaque with reference to empty array initializer"...@@ -1098,3 +1097,16 @@ test "initialize pointer to anyopaque with reference to empty array initializer"
1098 // We can't check the value, but it's zero-bit, so the type matching is good enough.1097 // We can't check the value, but it's zero-bit, so the type matching is good enough.
1099 comptime assert(@TypeOf(loaded) == @TypeOf(.{}));1098 comptime assert(@TypeOf(loaded) == @TypeOf(.{}));
1100}1099}
1100
1101test "sentinel of runtime-known array initialization is populated" {
1102 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1103
1104 var rt: u32 = undefined;
1105 rt = 42;
1106
1107 const arr: [1:123]u32 = .{rt};
1108 const elems: [*]const u32 = &arr;
1109
1110 try expect(elems[0] == 42);
1111 try expect(elems[1] == 123);
1112}
test/behavior/field_parent_ptr.zig+3-3
...@@ -2,7 +2,6 @@ const expect = @import("std").testing.expect;...@@ -2,7 +2,6 @@ const expect = @import("std").testing.expect;
2const builtin = @import("builtin");2const builtin = @import("builtin");
33
4test "@fieldParentPtr struct" {4test "@fieldParentPtr struct" {
5 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
6 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;5 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
7 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;6 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
87
...@@ -591,6 +590,7 @@ test "@fieldParentPtr unaligned packed struct" {...@@ -591,6 +590,7 @@ test "@fieldParentPtr unaligned packed struct" {
591 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;590 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
592 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;591 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
593 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;592 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
593 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
594594
595 const C = packed struct {595 const C = packed struct {
596 a: bool = true,596 a: bool = true,
...@@ -729,6 +729,7 @@ test "@fieldParentPtr aligned packed struct" {...@@ -729,6 +729,7 @@ test "@fieldParentPtr aligned packed struct" {
729 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;729 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
730 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;730 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
731 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;731 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
732 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
732733
733 const C = packed struct {734 const C = packed struct {
734 a: f32 = 3.14,735 a: f32 = 3.14,
...@@ -866,6 +867,7 @@ test "@fieldParentPtr nested packed struct" {...@@ -866,6 +867,7 @@ test "@fieldParentPtr nested packed struct" {
866 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;867 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
867 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;868 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
868 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;869 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
870 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
869871
870 {872 {
871 const C = packed struct {873 const C = packed struct {
...@@ -1340,7 +1342,6 @@ test "@fieldParentPtr packed struct last zero-bit field" {...@@ -1340,7 +1342,6 @@ test "@fieldParentPtr packed struct last zero-bit field" {
1340}1342}
13411343
1342test "@fieldParentPtr tagged union" {1344test "@fieldParentPtr tagged union" {
1343 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1344 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1345 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1345 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;1346 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
13461347
...@@ -1477,7 +1478,6 @@ test "@fieldParentPtr tagged union" {...@@ -1477,7 +1478,6 @@ test "@fieldParentPtr tagged union" {
1477}1478}
14781479
1479test "@fieldParentPtr untagged union" {1480test "@fieldParentPtr untagged union" {
1480 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1481 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;1481 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1482 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1482 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
14831483