authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-09-15 14:49:40+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-09-23 13:51:18+01:00
log644041b3a492558592e1306d2214c9e6b25de53b
treecfddcc1e038f94a877181140c93912342a7239ad
parentfc3ff262374c704a01a5f8a8b0cd721e3b61a9c8

Sema: refactor detection of comptime-known consts

This was previously implemented by analyzing the AIR prior to the ZIR `make_ptr_const` instruction. This solution was highly delicate, and in particular broke down whenever there was a second `alloc` between the `store` and `alloc` instructions, which is especially common in destructure statements. Sema now uses a different strategy to detect whether a `const` is comptime-known. When the `alloc` is created, Sema begins tracking all pointers and stores which refer to that allocation in temporary local state. If any store is not comptime-known or has a higher runtime index than the allocation, the allocation is marked as being runtime-known. When we reach the `make_ptr_const` instruction, if the allocation is not marked as runtime-known, it must be comptime-known. Sema will use the set of `store` instructions to re-initialize the value in comptime memory. We optimize for the common case of a single `store` instruction by not creating a comptime alloc in this case, instead directly plucking the result value from the instruction. Resolves: #16083

4 files changed, 489 insertions(+), 170 deletions(-)

src/Sema.zig+422-169
...@@ -111,6 +111,35 @@ prev_stack_alignment_src: ?LazySrcLoc = null,...@@ -111,6 +111,35 @@ prev_stack_alignment_src: ?LazySrcLoc = null,
111/// the struct/enum/union type created should be placed. Otherwise, it is `.none`.111/// the struct/enum/union type created should be placed. Otherwise, it is `.none`.
112builtin_type_target_index: InternPool.Index = .none,112builtin_type_target_index: InternPool.Index = .none,
113113
114/// Links every pointer derived from a base `alloc` back to that `alloc`. Used
115/// to detect comptime-known `const`s.
116/// TODO: ZIR liveness analysis would allow us to remove elements from this map.
117base_allocs: std.AutoHashMapUnmanaged(Air.Inst.Index, Air.Inst.Index) = .{},
118
119/// Runtime `alloc`s are placed in this map to track all comptime-known writes
120/// before the corresponding `make_ptr_const` instruction.
121/// If any store to the alloc depends on a runtime condition or stores a runtime
122/// value, the corresponding element in this map is erased, to indicate that the
123/// alloc is not comptime-known.
124/// If the alloc remains in this map when `make_ptr_const` is reached, its value
125/// is comptime-known, and all stores to the pointer must be applied at comptime
126/// to determine the comptime value.
127/// Backed by gpa.
128maybe_comptime_allocs: std.AutoHashMapUnmanaged(Air.Inst.Index, MaybeComptimeAlloc) = .{},
129
130const MaybeComptimeAlloc = struct {
131 /// The runtime index of the `alloc` instruction.
132 runtime_index: Value.RuntimeIndex,
133 /// Backed by sema.arena. Tracks all comptime-known stores to this `alloc`. Due to
134 /// RLS, a single comptime-known allocation may have arbitrarily many stores.
135 /// This may also contain `set_union_tag` instructions.
136 stores: std.ArrayListUnmanaged(Air.Inst.Index) = .{},
137 /// Backed by sema.arena. Contains instructions such as `optional_payload_ptr_set`
138 /// which have side effects so will not be elided by Liveness: we must rewrite these
139 /// instructions to be nops instead of relying on Liveness.
140 non_elideable_pointers: std.ArrayListUnmanaged(Air.Inst.Index) = .{},
141};
142
114const std = @import("std");143const std = @import("std");
115const math = std.math;144const math = std.math;
116const mem = std.mem;145const mem = std.mem;
...@@ -840,6 +869,8 @@ pub fn deinit(sema: *Sema) void {...@@ -840,6 +869,8 @@ pub fn deinit(sema: *Sema) void {
840 sema.post_hoc_blocks.deinit(gpa);869 sema.post_hoc_blocks.deinit(gpa);
841 }870 }
842 sema.unresolved_inferred_allocs.deinit(gpa);871 sema.unresolved_inferred_allocs.deinit(gpa);
872 sema.base_allocs.deinit(gpa);
873 sema.maybe_comptime_allocs.deinit(gpa);
843 sema.* = undefined;874 sema.* = undefined;
844}875}
845876
...@@ -2643,6 +2674,7 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE...@@ -2643,6 +2674,7 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
2643 .placeholder = Air.refToIndex(bitcasted_ptr).?,2674 .placeholder = Air.refToIndex(bitcasted_ptr).?,
2644 });2675 });
26452676
2677 try sema.checkKnownAllocPtr(ptr, bitcasted_ptr);
2646 return bitcasted_ptr;2678 return bitcasted_ptr;
2647 },2679 },
2648 .inferred_alloc_comptime => {2680 .inferred_alloc_comptime => {
...@@ -2690,7 +2722,9 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE...@@ -2690,7 +2722,9 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
26902722
2691 const dummy_ptr = try trash_block.addTy(.alloc, sema.typeOf(ptr));2723 const dummy_ptr = try trash_block.addTy(.alloc, sema.typeOf(ptr));
2692 const dummy_operand = try trash_block.addBitCast(pointee_ty, .void_value);2724 const dummy_operand = try trash_block.addBitCast(pointee_ty, .void_value);
2693 return sema.coerceResultPtr(block, src, ptr, dummy_ptr, dummy_operand, &trash_block);2725 const new_ptr = try sema.coerceResultPtr(block, src, ptr, dummy_ptr, dummy_operand, &trash_block);
2726 try sema.checkKnownAllocPtr(ptr, new_ptr);
2727 return new_ptr;
2694}2728}
26952729
2696fn coerceResultPtr(2730fn coerceResultPtr(
...@@ -3719,7 +3753,13 @@ fn zirAllocExtended(...@@ -3719,7 +3753,13 @@ fn zirAllocExtended(
3719 .address_space = target_util.defaultAddressSpace(target, .local),3753 .address_space = target_util.defaultAddressSpace(target, .local),
3720 },3754 },
3721 });3755 });
3722 return block.addTy(.alloc, ptr_type);3756 const ptr = try block.addTy(.alloc, ptr_type);
3757 if (small.is_const) {
3758 const ptr_inst = Air.refToIndex(ptr).?;
3759 try sema.maybe_comptime_allocs.put(gpa, ptr_inst, .{ .runtime_index = block.runtime_index });
3760 try sema.base_allocs.put(gpa, ptr_inst, ptr_inst);
3761 }
3762 return ptr;
3723 }3763 }
37243764
3725 const result_index = try block.addInstAsIndex(.{3765 const result_index = try block.addInstAsIndex(.{
...@@ -3730,6 +3770,10 @@ fn zirAllocExtended(...@@ -3730,6 +3770,10 @@ fn zirAllocExtended(
3730 } },3770 } },
3731 });3771 });
3732 try sema.unresolved_inferred_allocs.putNoClobber(gpa, result_index, .{});3772 try sema.unresolved_inferred_allocs.putNoClobber(gpa, result_index, .{});
3773 if (small.is_const) {
3774 try sema.maybe_comptime_allocs.put(gpa, result_index, .{ .runtime_index = block.runtime_index });
3775 try sema.base_allocs.put(gpa, result_index, result_index);
3776 }
3733 return Air.indexToRef(result_index);3777 return Air.indexToRef(result_index);
3734}3778}
37353779
...@@ -3748,60 +3792,26 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro...@@ -3748,60 +3792,26 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
3748 const inst_data = sema.code.instructions.items(.data)[inst].un_node;3792 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
3749 const alloc = try sema.resolveInst(inst_data.operand);3793 const alloc = try sema.resolveInst(inst_data.operand);
3750 const alloc_ty = sema.typeOf(alloc);3794 const alloc_ty = sema.typeOf(alloc);
37513795 const ptr_info = alloc_ty.ptrInfo(mod);
3752 var ptr_info = alloc_ty.ptrInfo(mod);
3753 const elem_ty = ptr_info.child.toType();3796 const elem_ty = ptr_info.child.toType();
37543797
3755 // Detect if all stores to an `.alloc` were comptime-known.3798 if (try sema.resolveComptimeKnownAllocValue(block, alloc, null)) |val| {
3756 ct: {
3757 var search_index: usize = block.instructions.items.len;
3758 const air_tags = sema.air_instructions.items(.tag);
3759 const air_datas = sema.air_instructions.items(.data);
3760
3761 const store_inst = while (true) {
3762 if (search_index == 0) break :ct;
3763 search_index -= 1;
3764
3765 const candidate = block.instructions.items[search_index];
3766 switch (air_tags[candidate]) {
3767 .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue,
3768 .store, .store_safe => break candidate,
3769 else => break :ct,
3770 }
3771 };
3772
3773 while (true) {
3774 if (search_index == 0) break :ct;
3775 search_index -= 1;
3776
3777 const candidate = block.instructions.items[search_index];
3778 switch (air_tags[candidate]) {
3779 .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue,
3780 .alloc => {
3781 if (Air.indexToRef(candidate) != alloc) break :ct;
3782 break;
3783 },
3784 else => break :ct,
3785 }
3786 }
3787
3788 const store_op = air_datas[store_inst].bin_op;
3789 const store_val = (try sema.resolveMaybeUndefVal(store_op.rhs)) orelse break :ct;
3790 if (store_op.lhs != alloc) break :ct;
3791
3792 // Remove all the unnecessary runtime instructions.
3793 block.instructions.shrinkRetainingCapacity(search_index);
3794
3795 var anon_decl = try block.startAnonDecl();3799 var anon_decl = try block.startAnonDecl();
3796 defer anon_decl.deinit();3800 defer anon_decl.deinit();
3797 return sema.analyzeDeclRef(try anon_decl.finish(elem_ty, store_val, ptr_info.flags.alignment));3801 const new_mut_ptr = try sema.analyzeDeclRef(try anon_decl.finish(elem_ty, val.toValue(), ptr_info.flags.alignment));
3802 return sema.makePtrConst(block, new_mut_ptr);
3798 }3803 }
37993804
3800 // If this is already a comptime-mutable allocation, we don't want to emit an error - the stores3805 // If this is already a comptime-known allocation, we don't want to emit an error - the stores
3801 // were already performed at comptime! Just make the pointer constant as normal.3806 // were already performed at comptime! Just make the pointer constant as normal.
3802 implicit_ct: {3807 implicit_ct: {
3803 const ptr_val = try sema.resolveMaybeUndefVal(alloc) orelse break :implicit_ct;3808 const ptr_val = try sema.resolveMaybeUndefVal(alloc) orelse break :implicit_ct;
3804 if (ptr_val.isComptimeMutablePtr(mod)) break :implicit_ct;3809 if (!ptr_val.isComptimeMutablePtr(mod)) {
3810 // It could still be a constant pointer to a decl
3811 const decl_index = ptr_val.pointerDecl(mod) orelse break :implicit_ct;
3812 const decl_val = mod.declPtr(decl_index).val.toIntern();
3813 if (mod.intern_pool.isRuntimeValue(decl_val)) break :implicit_ct;
3814 }
3805 return sema.makePtrConst(block, alloc);3815 return sema.makePtrConst(block, alloc);
3806 }3816 }
38073817
...@@ -3812,9 +3822,234 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro...@@ -3812,9 +3822,234 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
3812 return sema.fail(block, init_src, "value with comptime-only type '{}' depends on runtime control flow", .{elem_ty.fmt(mod)});3822 return sema.fail(block, init_src, "value with comptime-only type '{}' depends on runtime control flow", .{elem_ty.fmt(mod)});
3813 }3823 }
38143824
3825 // This is a runtime value.
3815 return sema.makePtrConst(block, alloc);3826 return sema.makePtrConst(block, alloc);
3816}3827}
38173828
3829/// If `alloc` is an inferred allocation, `resolved_inferred_ty` is taken to be its resolved
3830/// type. Otherwise, it may be `null`, and the type will be inferred from `alloc`.
3831fn resolveComptimeKnownAllocValue(sema: *Sema, block: *Block, alloc: Air.Inst.Ref, resolved_alloc_ty: ?Type) CompileError!?InternPool.Index {
3832 const mod = sema.mod;
3833
3834 const alloc_ty = resolved_alloc_ty orelse sema.typeOf(alloc);
3835 const ptr_info = alloc_ty.ptrInfo(mod);
3836 const elem_ty = ptr_info.child.toType();
3837
3838 const alloc_inst = Air.refToIndex(alloc) orelse return null;
3839 const comptime_info = sema.maybe_comptime_allocs.fetchRemove(alloc_inst) orelse return null;
3840 const stores = comptime_info.value.stores.items;
3841
3842 // Since the entry existed in `maybe_comptime_allocs`, the allocation is comptime-known.
3843 // We will resolve and return its value.
3844
3845 // We expect to have emitted at least one store, unless the elem type is OPV.
3846 if (stores.len == 0) {
3847 const val = (try sema.typeHasOnePossibleValue(elem_ty)).?.toIntern();
3848 return sema.finishResolveComptimeKnownAllocValue(val, alloc_inst, comptime_info.value);
3849 }
3850
3851 // In general, we want to create a comptime alloc of the correct type and
3852 // apply the stores to that alloc in order. However, before going to all
3853 // that effort, let's optimize for the common case of a single store.
3854
3855 simple: {
3856 if (stores.len != 1) break :simple;
3857 const store_inst = stores[0];
3858 const store_data = sema.air_instructions.items(.data)[store_inst].bin_op;
3859 if (store_data.lhs != alloc) break :simple;
3860
3861 const val = Air.refToInterned(store_data.rhs).?;
3862 assert(mod.intern_pool.typeOf(val) == elem_ty.toIntern());
3863 return sema.finishResolveComptimeKnownAllocValue(val, alloc_inst, comptime_info.value);
3864 }
3865
3866 // The simple strategy failed: we must create a mutable comptime alloc and
3867 // perform all of the runtime store operations at comptime.
3868
3869 var anon_decl = try block.startAnonDecl();
3870 defer anon_decl.deinit();
3871 const decl_index = try anon_decl.finish(elem_ty, try mod.undefValue(elem_ty), ptr_info.flags.alignment);
3872
3873 const decl_ptr = try mod.intern(.{ .ptr = .{
3874 .ty = alloc_ty.toIntern(),
3875 .addr = .{ .mut_decl = .{
3876 .decl = decl_index,
3877 .runtime_index = block.runtime_index,
3878 } },
3879 } });
3880
3881 // Maps from pointers into the runtime allocs, to comptime-mutable pointers into the mut decl.
3882 var ptr_mapping = std.AutoHashMap(Air.Inst.Index, InternPool.Index).init(sema.arena);
3883 try ptr_mapping.ensureTotalCapacity(@intCast(stores.len));
3884 ptr_mapping.putAssumeCapacity(alloc_inst, decl_ptr);
3885
3886 var to_map = try std.ArrayList(Air.Inst.Index).initCapacity(sema.arena, stores.len);
3887 for (stores) |store_inst| {
3888 const bin_op = sema.air_instructions.items(.data)[store_inst].bin_op;
3889 to_map.appendAssumeCapacity(Air.refToIndex(bin_op.lhs).?);
3890 }
3891
3892 const tmp_air = sema.getTmpAir();
3893
3894 while (to_map.popOrNull()) |air_ptr| {
3895 if (ptr_mapping.contains(air_ptr)) continue;
3896 const PointerMethod = union(enum) {
3897 same_addr,
3898 opt_payload,
3899 eu_payload,
3900 field: u32,
3901 elem: u64,
3902 };
3903 const inst_tag = tmp_air.instructions.items(.tag)[air_ptr];
3904 const air_parent_ptr: Air.Inst.Ref, const method: PointerMethod = switch (inst_tag) {
3905 .struct_field_ptr => blk: {
3906 const data = tmp_air.extraData(
3907 Air.StructField,
3908 tmp_air.instructions.items(.data)[air_ptr].ty_pl.payload,
3909 ).data;
3910 break :blk .{
3911 data.struct_operand,
3912 .{ .field = data.field_index },
3913 };
3914 },
3915 .struct_field_ptr_index_0,
3916 .struct_field_ptr_index_1,
3917 .struct_field_ptr_index_2,
3918 .struct_field_ptr_index_3,
3919 => .{
3920 tmp_air.instructions.items(.data)[air_ptr].ty_op.operand,
3921 .{ .field = switch (inst_tag) {
3922 .struct_field_ptr_index_0 => 0,
3923 .struct_field_ptr_index_1 => 1,
3924 .struct_field_ptr_index_2 => 2,
3925 .struct_field_ptr_index_3 => 3,
3926 else => unreachable,
3927 } },
3928 },
3929 .ptr_slice_ptr_ptr => .{
3930 tmp_air.instructions.items(.data)[air_ptr].ty_op.operand,
3931 .{ .field = Value.slice_ptr_index },
3932 },
3933 .ptr_slice_len_ptr => .{
3934 tmp_air.instructions.items(.data)[air_ptr].ty_op.operand,
3935 .{ .field = Value.slice_len_index },
3936 },
3937 .ptr_elem_ptr => blk: {
3938 const data = tmp_air.extraData(
3939 Air.Bin,
3940 tmp_air.instructions.items(.data)[air_ptr].ty_pl.payload,
3941 ).data;
3942 const idx_val = (try sema.resolveMaybeUndefVal(data.rhs)).?;
3943 break :blk .{
3944 data.lhs,
3945 .{ .elem = idx_val.toUnsignedInt(mod) },
3946 };
3947 },
3948 .bitcast => .{
3949 tmp_air.instructions.items(.data)[air_ptr].ty_op.operand,
3950 .same_addr,
3951 },
3952 .optional_payload_ptr_set => .{
3953 tmp_air.instructions.items(.data)[air_ptr].ty_op.operand,
3954 .opt_payload,
3955 },
3956 .errunion_payload_ptr_set => .{
3957 tmp_air.instructions.items(.data)[air_ptr].ty_op.operand,
3958 .eu_payload,
3959 },
3960 else => unreachable,
3961 };
3962
3963 const decl_parent_ptr = ptr_mapping.get(Air.refToIndex(air_parent_ptr).?) orelse {
3964 // Resolve the parent pointer first.
3965 // Note that we add in what seems like the wrong order, because we're popping from the end of this array.
3966 try to_map.appendSlice(&.{ air_ptr, Air.refToIndex(air_parent_ptr).? });
3967 continue;
3968 };
3969 const new_ptr_ty = tmp_air.typeOfIndex(air_ptr, &mod.intern_pool).toIntern();
3970 const new_ptr = switch (method) {
3971 .same_addr => try mod.intern_pool.getCoerced(sema.gpa, decl_parent_ptr, new_ptr_ty),
3972 .opt_payload => try mod.intern(.{ .ptr = .{
3973 .ty = new_ptr_ty,
3974 .addr = .{ .opt_payload = decl_parent_ptr },
3975 } }),
3976 .eu_payload => try mod.intern(.{ .ptr = .{
3977 .ty = new_ptr_ty,
3978 .addr = .{ .eu_payload = decl_parent_ptr },
3979 } }),
3980 .field => |field_idx| try mod.intern(.{ .ptr = .{
3981 .ty = new_ptr_ty,
3982 .addr = .{ .field = .{
3983 .base = decl_parent_ptr,
3984 .index = field_idx,
3985 } },
3986 } }),
3987 .elem => |elem_idx| (try decl_parent_ptr.toValue().elemPtr(new_ptr_ty.toType(), @intCast(elem_idx), mod)).toIntern(),
3988 };
3989 try ptr_mapping.put(air_ptr, new_ptr);
3990 }
3991
3992 // We have a correlation between AIR pointers and decl pointers. Perform all stores at comptime.
3993
3994 for (stores) |store_inst| {
3995 switch (sema.air_instructions.items(.tag)[store_inst]) {
3996 .set_union_tag => {
3997 // If this tag has an OPV payload, there won't be a corresponding
3998 // store instruction, so we must set the union payload now.
3999 const bin_op = sema.air_instructions.items(.data)[store_inst].bin_op;
4000 const air_ptr_inst = Air.refToIndex(bin_op.lhs).?;
4001 const tag_val = (try sema.resolveMaybeUndefVal(bin_op.rhs)).?;
4002 const union_ty = sema.typeOf(bin_op.lhs).childType(mod);
4003 const payload_ty = union_ty.unionFieldType(tag_val, mod);
4004 if (try sema.typeHasOnePossibleValue(payload_ty)) |payload_val| {
4005 const new_ptr = ptr_mapping.get(air_ptr_inst).?;
4006 const store_val = try mod.unionValue(union_ty, tag_val, payload_val);
4007 try sema.storePtrVal(block, .unneeded, new_ptr.toValue(), store_val, union_ty);
4008 }
4009 },
4010 .store, .store_safe => {
4011 const bin_op = sema.air_instructions.items(.data)[store_inst].bin_op;
4012 const air_ptr_inst = Air.refToIndex(bin_op.lhs).?;
4013 const store_val = (try sema.resolveMaybeUndefVal(bin_op.rhs)).?;
4014 const new_ptr = ptr_mapping.get(air_ptr_inst).?;
4015 try sema.storePtrVal(block, .unneeded, new_ptr.toValue(), store_val, mod.intern_pool.typeOf(store_val.toIntern()).toType());
4016 },
4017 else => unreachable,
4018 }
4019 }
4020
4021 // The value is finalized - load it!
4022 const val = (try sema.pointerDeref(block, .unneeded, decl_ptr.toValue(), alloc_ty)).?.toIntern();
4023 return sema.finishResolveComptimeKnownAllocValue(val, alloc_inst, comptime_info.value);
4024}
4025
4026/// Given the resolved comptime-known value, rewrites the dead AIR to not
4027/// create a runtime stack allocation.
4028/// Same return type as `resolveComptimeKnownAllocValue` so we can tail call.
4029fn finishResolveComptimeKnownAllocValue(sema: *Sema, result_val: InternPool.Index, alloc_inst: Air.Inst.Index, comptime_info: MaybeComptimeAlloc) CompileError!?InternPool.Index {
4030 // We're almost done - we have the resolved comptime value. We just need to
4031 // eliminate the now-dead runtime instructions.
4032
4033 // We will rewrite the AIR to eliminate the alloc and all stores to it.
4034 // This will cause instructions deriving field pointers etc of the alloc to
4035 // become invalid, however, since we are removing all stores to those pointers,
4036 // they will be eliminated by Liveness before they reach codegen.
4037
4038 // The specifics of this instruction aren't really important: we just want
4039 // Liveness to elide it.
4040 const nop_inst: Air.Inst = .{ .tag = .bitcast, .data = .{ .ty_op = .{ .ty = .u8_type, .operand = .zero_u8 } } };
4041
4042 sema.air_instructions.set(alloc_inst, nop_inst);
4043 for (comptime_info.stores.items) |store_inst| {
4044 sema.air_instructions.set(store_inst, nop_inst);
4045 }
4046 for (comptime_info.non_elideable_pointers.items) |ptr_inst| {
4047 sema.air_instructions.set(ptr_inst, nop_inst);
4048 }
4049
4050 return result_val;
4051}
4052
3818fn makePtrConst(sema: *Sema, block: *Block, alloc: Air.Inst.Ref) CompileError!Air.Inst.Ref {4053fn makePtrConst(sema: *Sema, block: *Block, alloc: Air.Inst.Ref) CompileError!Air.Inst.Ref {
3819 const mod = sema.mod;4054 const mod = sema.mod;
3820 const alloc_ty = sema.typeOf(alloc);4055 const alloc_ty = sema.typeOf(alloc);
...@@ -3868,7 +4103,11 @@ fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I...@@ -3868,7 +4103,11 @@ fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
3868 .flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },4103 .flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
3869 });4104 });
3870 try sema.queueFullTypeResolution(var_ty);4105 try sema.queueFullTypeResolution(var_ty);
3871 return block.addTy(.alloc, ptr_type);4106 const ptr = try block.addTy(.alloc, ptr_type);
4107 const ptr_inst = Air.refToIndex(ptr).?;
4108 try sema.maybe_comptime_allocs.put(sema.gpa, ptr_inst, .{ .runtime_index = block.runtime_index });
4109 try sema.base_allocs.put(sema.gpa, ptr_inst, ptr_inst);
4110 return ptr;
3872}4111}
38734112
3874fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {4113fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
...@@ -3925,6 +4164,8 @@ fn zirAllocInferred(...@@ -3925,6 +4164,8 @@ fn zirAllocInferred(
3925 } },4164 } },
3926 });4165 });
3927 try sema.unresolved_inferred_allocs.putNoClobber(gpa, result_index, .{});4166 try sema.unresolved_inferred_allocs.putNoClobber(gpa, result_index, .{});
4167 try sema.maybe_comptime_allocs.put(gpa, result_index, .{ .runtime_index = block.runtime_index });
4168 try sema.base_allocs.put(sema.gpa, result_index, result_index);
3928 return Air.indexToRef(result_index);4169 return Air.indexToRef(result_index);
3929}4170}
39304171
...@@ -3992,91 +4233,15 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com...@@ -3992,91 +4233,15 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
39924233
3993 if (!ia1.is_const) {4234 if (!ia1.is_const) {
3994 try sema.validateVarType(block, ty_src, final_elem_ty, false);4235 try sema.validateVarType(block, ty_src, final_elem_ty, false);
3995 } else ct: {4236 } else if (try sema.resolveComptimeKnownAllocValue(block, ptr, final_ptr_ty)) |val| {
3996 // Detect if the value is comptime-known. In such case, the4237 var anon_decl = try block.startAnonDecl();
3997 // last 3 AIR instructions of the block will look like this:4238 defer anon_decl.deinit();
3998 //4239 const new_decl_index = try anon_decl.finish(final_elem_ty, val.toValue(), ia1.alignment);
3999 // %a = inferred_alloc4240 const new_mut_ptr = Air.refToInterned(try sema.analyzeDeclRef(new_decl_index)).?.toValue();
4000 // %b = bitcast(%a)4241 const new_const_ptr = (try mod.getCoerced(new_mut_ptr, final_ptr_ty)).toIntern();
4001 // %c = store(%b, %d)
4002 //
4003 // If `%d` is comptime-known, then we want to store the value
4004 // inside an anonymous Decl and then erase these three AIR
4005 // instructions from the block, replacing the inst_map entry
4006 // corresponding to the ZIR alloc instruction with a constant
4007 // decl_ref pointing at our new Decl.
4008 // dbg_stmt instructions may be interspersed into this pattern
4009 // which must be ignored.
4010 if (block.instructions.items.len < 3) break :ct;
4011 var search_index: usize = block.instructions.items.len;
4012 const air_tags = sema.air_instructions.items(.tag);
4013 const air_datas = sema.air_instructions.items(.data);
4014
4015 const store_inst = while (true) {
4016 if (search_index == 0) break :ct;
4017 search_index -= 1;
4018
4019 const candidate = block.instructions.items[search_index];
4020 switch (air_tags[candidate]) {
4021 .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue,
4022 .store, .store_safe => break candidate,
4023 else => break :ct,
4024 }
4025 };
4026
4027 const bitcast_inst = while (true) {
4028 if (search_index == 0) break :ct;
4029 search_index -= 1;
4030
4031 const candidate = block.instructions.items[search_index];
4032 switch (air_tags[candidate]) {
4033 .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue,
4034 .bitcast => break candidate,
4035 else => break :ct,
4036 }
4037 };
4038
4039 while (true) {
4040 if (search_index == 0) break :ct;
4041 search_index -= 1;
4042
4043 const candidate = block.instructions.items[search_index];
4044 if (candidate == ptr_inst) break;
4045 switch (air_tags[candidate]) {
4046 .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue,
4047 else => break :ct,
4048 }
4049 }
4050
4051 const store_op = air_datas[store_inst].bin_op;
4052 const store_val = (try sema.resolveMaybeUndefVal(store_op.rhs)) orelse break :ct;
4053 if (store_op.lhs != Air.indexToRef(bitcast_inst)) break :ct;
4054 if (air_datas[bitcast_inst].ty_op.operand != ptr) break :ct;
4055
4056 const new_decl_index = d: {
4057 var anon_decl = try block.startAnonDecl();
4058 defer anon_decl.deinit();
4059 const new_decl_index = try anon_decl.finish(final_elem_ty, store_val, ia1.alignment);
4060 break :d new_decl_index;
4061 };
4062 try mod.declareDeclDependency(sema.owner_decl_index, new_decl_index);
4063
4064 // Remove the instruction from the block so that codegen does not see it.
4065 block.instructions.shrinkRetainingCapacity(search_index);
4066 try sema.maybeQueueFuncBodyAnalysis(new_decl_index);
4067
4068 if (std.debug.runtime_safety) {
4069 // The inferred_alloc should never be referenced again
4070 sema.air_instructions.set(ptr_inst, .{ .tag = undefined, .data = undefined });
4071 }
4072
4073 const interned = try mod.intern(.{ .ptr = .{
4074 .ty = final_ptr_ty.toIntern(),
4075 .addr = .{ .decl = new_decl_index },
4076 } });
40774242
4078 // Remap the ZIR oeprand to the resolved pointer value4243 // Remap the ZIR oeprand to the resolved pointer value
4079 sema.inst_map.putAssumeCapacity(Zir.refToIndex(inst_data.operand).?, Air.internedToRef(interned));4244 sema.inst_map.putAssumeCapacity(Zir.refToIndex(inst_data.operand).?, Air.internedToRef(new_const_ptr));
40804245
4081 // Unless the block is comptime, `alloc_inferred` always produces4246 // Unless the block is comptime, `alloc_inferred` always produces
4082 // a runtime constant. The final inferred type needs to be4247 // a runtime constant. The final inferred type needs to be
...@@ -4199,6 +4364,7 @@ fn zirArrayBasePtr(...@@ -4199,6 +4364,7 @@ fn zirArrayBasePtr(
4199 .Array, .Vector => return base_ptr,4364 .Array, .Vector => return base_ptr,
4200 .Struct => if (elem_ty.isTuple(mod)) {4365 .Struct => if (elem_ty.isTuple(mod)) {
4201 // TODO validate element count4366 // TODO validate element count
4367 try sema.checkKnownAllocPtr(start_ptr, base_ptr);
4202 return base_ptr;4368 return base_ptr;
4203 },4369 },
4204 else => {},4370 else => {},
...@@ -4225,7 +4391,10 @@ fn zirFieldBasePtr(...@@ -4225,7 +4391,10 @@ fn zirFieldBasePtr(
42254391
4226 const elem_ty = sema.typeOf(base_ptr).childType(mod);4392 const elem_ty = sema.typeOf(base_ptr).childType(mod);
4227 switch (elem_ty.zigTypeTag(mod)) {4393 switch (elem_ty.zigTypeTag(mod)) {
4228 .Struct, .Union => return base_ptr,4394 .Struct, .Union => {
4395 try sema.checkKnownAllocPtr(start_ptr, base_ptr);
4396 return base_ptr;
4397 },
4229 else => {},4398 else => {},
4230 }4399 }
4231 return sema.failWithStructInitNotSupported(block, src, sema.typeOf(start_ptr).childType(mod));4400 return sema.failWithStructInitNotSupported(block, src, sema.typeOf(start_ptr).childType(mod));
...@@ -4636,7 +4805,8 @@ fn validateUnionInit(...@@ -4636,7 +4805,8 @@ fn validateUnionInit(
4636 }4805 }
46374806
4638 const new_tag = Air.internedToRef(tag_val.toIntern());4807 const new_tag = Air.internedToRef(tag_val.toIntern());
4639 _ = try block.addBinOp(.set_union_tag, union_ptr, new_tag);4808 const set_tag_inst = try block.addBinOp(.set_union_tag, union_ptr, new_tag);
4809 try sema.checkComptimeKnownStore(block, set_tag_inst);
4640}4810}
46414811
4642fn validateStructInit(4812fn validateStructInit(
...@@ -4939,6 +5109,7 @@ fn validateStructInit(...@@ -4939,6 +5109,7 @@ fn validateStructInit(
4939 try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(i), true)5109 try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(i), true)
4940 else5110 else
4941 try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(i), field_src, struct_ty, true);5111 try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(i), field_src, struct_ty, true);
5112 try sema.checkKnownAllocPtr(struct_ptr, default_field_ptr);
4942 const init = Air.internedToRef(field_values[i]);5113 const init = Air.internedToRef(field_values[i]);
4943 try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store);5114 try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store);
4944 }5115 }
...@@ -5366,6 +5537,7 @@ fn storeToInferredAlloc(...@@ -5366,6 +5537,7 @@ fn storeToInferredAlloc(
5366 // Create a store instruction as a placeholder. This will be replaced by a5537 // Create a store instruction as a placeholder. This will be replaced by a
5367 // proper store sequence once we know the stored type.5538 // proper store sequence once we know the stored type.
5368 const dummy_store = try block.addBinOp(.store, ptr, operand);5539 const dummy_store = try block.addBinOp(.store, ptr, operand);
5540 try sema.checkComptimeKnownStore(block, dummy_store);
5369 // Add the stored instruction to the set we will use to resolve peer types5541 // Add the stored instruction to the set we will use to resolve peer types
5370 // for the inferred allocation.5542 // for the inferred allocation.
5371 try inferred_alloc.prongs.append(sema.arena, .{5543 try inferred_alloc.prongs.append(sema.arena, .{
...@@ -8663,7 +8835,8 @@ fn analyzeOptionalPayloadPtr(...@@ -8663,7 +8835,8 @@ fn analyzeOptionalPayloadPtr(
8663 // If the pointer resulting from this function was stored at comptime,8835 // If the pointer resulting from this function was stored at comptime,
8664 // the optional non-null bit would be set that way. But in this case,8836 // the optional non-null bit would be set that way. But in this case,
8665 // we need to emit a runtime instruction to do it.8837 // we need to emit a runtime instruction to do it.
8666 _ = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr);8838 const opt_payload_ptr = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr);
8839 try sema.checkKnownAllocPtr(optional_ptr, opt_payload_ptr);
8667 }8840 }
8668 return Air.internedToRef((try mod.intern(.{ .ptr = .{8841 return Air.internedToRef((try mod.intern(.{ .ptr = .{
8669 .ty = child_pointer.toIntern(),8842 .ty = child_pointer.toIntern(),
...@@ -8687,11 +8860,14 @@ fn analyzeOptionalPayloadPtr(...@@ -8687,11 +8860,14 @@ fn analyzeOptionalPayloadPtr(
8687 const is_non_null = try block.addUnOp(.is_non_null_ptr, optional_ptr);8860 const is_non_null = try block.addUnOp(.is_non_null_ptr, optional_ptr);
8688 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);8861 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
8689 }8862 }
8690 const air_tag: Air.Inst.Tag = if (initializing)8863
8691 .optional_payload_ptr_set8864 if (initializing) {
8692 else8865 const opt_payload_ptr = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr);
8693 .optional_payload_ptr;8866 try sema.checkKnownAllocPtr(optional_ptr, opt_payload_ptr);
8694 return block.addTyOp(air_tag, child_pointer, optional_ptr);8867 return opt_payload_ptr;
8868 } else {
8869 return block.addTyOp(.optional_payload_ptr, child_pointer, optional_ptr);
8870 }
8695}8871}
86968872
8697/// Value in, value out.8873/// Value in, value out.
...@@ -8851,7 +9027,8 @@ fn analyzeErrUnionPayloadPtr(...@@ -8851,7 +9027,8 @@ fn analyzeErrUnionPayloadPtr(
8851 // the error union error code would be set that way. But in this case,9027 // the error union error code would be set that way. But in this case,
8852 // we need to emit a runtime instruction to do it.9028 // we need to emit a runtime instruction to do it.
8853 try sema.requireRuntimeBlock(block, src, null);9029 try sema.requireRuntimeBlock(block, src, null);
8854 _ = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand);9030 const eu_payload_ptr = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand);
9031 try sema.checkKnownAllocPtr(operand, eu_payload_ptr);
8855 }9032 }
8856 return Air.internedToRef((try mod.intern(.{ .ptr = .{9033 return Air.internedToRef((try mod.intern(.{ .ptr = .{
8857 .ty = operand_pointer_ty.toIntern(),9034 .ty = operand_pointer_ty.toIntern(),
...@@ -8878,11 +9055,13 @@ fn analyzeErrUnionPayloadPtr(...@@ -8878,11 +9055,13 @@ fn analyzeErrUnionPayloadPtr(
8878 try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr);9055 try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr);
8879 }9056 }
88809057
8881 const air_tag: Air.Inst.Tag = if (initializing)9058 if (initializing) {
8882 .errunion_payload_ptr_set9059 const eu_payload_ptr = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand);
8883 else9060 try sema.checkKnownAllocPtr(operand, eu_payload_ptr);
8884 .unwrap_errunion_payload_ptr;9061 return eu_payload_ptr;
8885 return block.addTyOp(air_tag, operand_pointer_ty, operand);9062 } else {
9063 return block.addTyOp(.unwrap_errunion_payload_ptr, operand_pointer_ty, operand);
9064 }
8886}9065}
88879066
8888/// Value in, value out9067/// Value in, value out
...@@ -22048,6 +22227,7 @@ fn ptrCastFull(...@@ -22048,6 +22227,7 @@ fn ptrCastFull(
22048 });22227 });
22049 } else {22228 } else {
22050 assert(dest_ptr_ty.eql(dest_ty, mod));22229 assert(dest_ptr_ty.eql(dest_ty, mod));
22230 try sema.checkKnownAllocPtr(operand, result_ptr);
22051 return result_ptr;22231 return result_ptr;
22052 }22232 }
22053}22233}
...@@ -22075,7 +22255,9 @@ fn zirPtrCastNoDest(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Inst...@@ -22075,7 +22255,9 @@ fn zirPtrCastNoDest(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Inst
22075 }22255 }
2207622256
22077 try sema.requireRuntimeBlock(block, src, null);22257 try sema.requireRuntimeBlock(block, src, null);
22078 return block.addBitCast(dest_ty, operand);22258 const new_ptr = try block.addBitCast(dest_ty, operand);
22259 try sema.checkKnownAllocPtr(operand, new_ptr);
22260 return new_ptr;
22079}22261}
2208022262
22081fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {22263fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
...@@ -26161,7 +26343,9 @@ fn fieldPtr(...@@ -26161,7 +26343,9 @@ fn fieldPtr(
26161 }26343 }
26162 try sema.requireRuntimeBlock(block, src, null);26344 try sema.requireRuntimeBlock(block, src, null);
2616326345
26164 return block.addTyOp(.ptr_slice_ptr_ptr, result_ty, inner_ptr);26346 const field_ptr = try block.addTyOp(.ptr_slice_ptr_ptr, result_ty, inner_ptr);
26347 try sema.checkKnownAllocPtr(inner_ptr, field_ptr);
26348 return field_ptr;
26165 } else if (ip.stringEqlSlice(field_name, "len")) {26349 } else if (ip.stringEqlSlice(field_name, "len")) {
26166 const result_ty = try sema.ptrType(.{26350 const result_ty = try sema.ptrType(.{
26167 .child = .usize_type,26351 .child = .usize_type,
...@@ -26183,7 +26367,9 @@ fn fieldPtr(...@@ -26183,7 +26367,9 @@ fn fieldPtr(
26183 }26367 }
26184 try sema.requireRuntimeBlock(block, src, null);26368 try sema.requireRuntimeBlock(block, src, null);
2618526369
26186 return block.addTyOp(.ptr_slice_len_ptr, result_ty, inner_ptr);26370 const field_ptr = try block.addTyOp(.ptr_slice_len_ptr, result_ty, inner_ptr);
26371 try sema.checkKnownAllocPtr(inner_ptr, field_ptr);
26372 return field_ptr;
26187 } else {26373 } else {
26188 return sema.fail(26374 return sema.fail(
26189 block,26375 block,
...@@ -26295,14 +26481,18 @@ fn fieldPtr(...@@ -26295,14 +26481,18 @@ fn fieldPtr(
26295 try sema.analyzeLoad(block, src, object_ptr, object_ptr_src)26481 try sema.analyzeLoad(block, src, object_ptr, object_ptr_src)
26296 else26482 else
26297 object_ptr;26483 object_ptr;
26298 return sema.structFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing);26484 const field_ptr = try sema.structFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing);
26485 try sema.checkKnownAllocPtr(inner_ptr, field_ptr);
26486 return field_ptr;
26299 },26487 },
26300 .Union => {26488 .Union => {
26301 const inner_ptr = if (is_pointer_to)26489 const inner_ptr = if (is_pointer_to)
26302 try sema.analyzeLoad(block, src, object_ptr, object_ptr_src)26490 try sema.analyzeLoad(block, src, object_ptr, object_ptr_src)
26303 else26491 else
26304 object_ptr;26492 object_ptr;
26305 return sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing);26493 const field_ptr = try sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing);
26494 try sema.checkKnownAllocPtr(inner_ptr, field_ptr);
26495 return field_ptr;
26306 },26496 },
26307 else => {},26497 else => {},
26308 }26498 }
...@@ -27066,21 +27256,24 @@ fn elemPtr(...@@ -27066,21 +27256,24 @@ fn elemPtr(
27066 };27256 };
27067 try checkIndexable(sema, block, src, indexable_ty);27257 try checkIndexable(sema, block, src, indexable_ty);
2706827258
27069 switch (indexable_ty.zigTypeTag(mod)) {27259 const elem_ptr = switch (indexable_ty.zigTypeTag(mod)) {
27070 .Array, .Vector => return sema.elemPtrArray(block, src, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init, oob_safety),27260 .Array, .Vector => try sema.elemPtrArray(block, src, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init, oob_safety),
27071 .Struct => {27261 .Struct => blk: {
27072 // Tuple field access.27262 // Tuple field access.
27073 const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, .{27263 const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, .{
27074 .needed_comptime_reason = "tuple field access index must be comptime-known",27264 .needed_comptime_reason = "tuple field access index must be comptime-known",
27075 });27265 });
27076 const index: u32 = @intCast(index_val.toUnsignedInt(mod));27266 const index: u32 = @intCast(index_val.toUnsignedInt(mod));
27077 return sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index, init);27267 break :blk try sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index, init);
27078 },27268 },
27079 else => {27269 else => {
27080 const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src);27270 const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src);
27081 return elemPtrOneLayerOnly(sema, block, src, indexable, elem_index, elem_index_src, init, oob_safety);27271 return elemPtrOneLayerOnly(sema, block, src, indexable, elem_index, elem_index_src, init, oob_safety);
27082 },27272 },
27083 }27273 };
27274
27275 try sema.checkKnownAllocPtr(indexable_ptr, elem_ptr);
27276 return elem_ptr;
27084}27277}
2708527278
27086/// Asserts that the type of indexable is pointer.27279/// Asserts that the type of indexable is pointer.
...@@ -27120,20 +27313,20 @@ fn elemPtrOneLayerOnly(...@@ -27120,20 +27313,20 @@ fn elemPtrOneLayerOnly(
27120 },27313 },
27121 .One => {27314 .One => {
27122 const child_ty = indexable_ty.childType(mod);27315 const child_ty = indexable_ty.childType(mod);
27123 switch (child_ty.zigTypeTag(mod)) {27316 const elem_ptr = switch (child_ty.zigTypeTag(mod)) {
27124 .Array, .Vector => {27317 .Array, .Vector => try sema.elemPtrArray(block, src, indexable_src, indexable, elem_index_src, elem_index, init, oob_safety),
27125 return sema.elemPtrArray(block, src, indexable_src, indexable, elem_index_src, elem_index, init, oob_safety);27318 .Struct => blk: {
27126 },
27127 .Struct => {
27128 assert(child_ty.isTuple(mod));27319 assert(child_ty.isTuple(mod));
27129 const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, .{27320 const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index, .{
27130 .needed_comptime_reason = "tuple field access index must be comptime-known",27321 .needed_comptime_reason = "tuple field access index must be comptime-known",
27131 });27322 });
27132 const index: u32 = @intCast(index_val.toUnsignedInt(mod));27323 const index: u32 = @intCast(index_val.toUnsignedInt(mod));
27133 return sema.tupleFieldPtr(block, indexable_src, indexable, elem_index_src, index, false);27324 break :blk try sema.tupleFieldPtr(block, indexable_src, indexable, elem_index_src, index, false);
27134 },27325 },
27135 else => unreachable, // Guaranteed by checkIndexable27326 else => unreachable, // Guaranteed by checkIndexable
27136 }27327 };
27328 try sema.checkKnownAllocPtr(indexable, elem_ptr);
27329 return elem_ptr;
27137 },27330 },
27138 }27331 }
27139}27332}
...@@ -27660,7 +27853,9 @@ fn coerceExtra(...@@ -27660,7 +27853,9 @@ fn coerceExtra(
27660 return sema.coerceInMemory(val, dest_ty);27853 return sema.coerceInMemory(val, dest_ty);
27661 }27854 }
27662 try sema.requireRuntimeBlock(block, inst_src, null);27855 try sema.requireRuntimeBlock(block, inst_src, null);
27663 return block.addBitCast(dest_ty, inst);27856 const new_val = try block.addBitCast(dest_ty, inst);
27857 try sema.checkKnownAllocPtr(inst, new_val);
27858 return new_val;
27664 }27859 }
2766527860
27666 switch (dest_ty.zigTypeTag(mod)) {27861 switch (dest_ty.zigTypeTag(mod)) {
...@@ -29379,8 +29574,9 @@ fn storePtr2(...@@ -29379,8 +29574,9 @@ fn storePtr2(
2937929574
29380 // We do this after the possible comptime store above, for the case of field_ptr stores29575 // We do this after the possible comptime store above, for the case of field_ptr stores
29381 // to unions because we want the comptime tag to be set, even if the field type is void.29576 // to unions because we want the comptime tag to be set, even if the field type is void.
29382 if ((try sema.typeHasOnePossibleValue(elem_ty)) != null)29577 if ((try sema.typeHasOnePossibleValue(elem_ty)) != null) {
29383 return;29578 return;
29579 }
2938429580
29385 if (air_tag == .bitcast) {29581 if (air_tag == .bitcast) {
29386 // `air_tag == .bitcast` is used as a special case for `zirCoerceResultPtr`29582 // `air_tag == .bitcast` is used as a special case for `zirCoerceResultPtr`
...@@ -29415,10 +29611,65 @@ fn storePtr2(...@@ -29415,10 +29611,65 @@ fn storePtr2(
29415 });29611 });
29416 }29612 }
2941729613
29418 if (is_ret) {29614 const store_inst = if (is_ret)
29419 _ = try block.addBinOp(.store, ptr, operand);29615 try block.addBinOp(.store, ptr, operand)
29420 } else {29616 else
29421 _ = try block.addBinOp(air_tag, ptr, operand);29617 try block.addBinOp(air_tag, ptr, operand);
29618
29619 try sema.checkComptimeKnownStore(block, store_inst);
29620
29621 return;
29622}
29623
29624/// Given an AIR store instruction, checks whether we are performing a
29625/// comptime-known store to a local alloc, and updates `maybe_comptime_allocs`
29626/// accordingly.
29627fn checkComptimeKnownStore(sema: *Sema, block: *Block, store_inst_ref: Air.Inst.Ref) !void {
29628 const store_inst = Air.refToIndex(store_inst_ref).?;
29629 const inst_data = sema.air_instructions.items(.data)[store_inst].bin_op;
29630 const ptr = Air.refToIndex(inst_data.lhs) orelse return;
29631 const operand = inst_data.rhs;
29632
29633 const maybe_base_alloc = sema.base_allocs.get(ptr) orelse return;
29634 const maybe_comptime_alloc = sema.maybe_comptime_allocs.getPtr(maybe_base_alloc) orelse return;
29635
29636 ct: {
29637 if (null == try sema.resolveMaybeUndefVal(operand)) break :ct;
29638 if (maybe_comptime_alloc.runtime_index != block.runtime_index) break :ct;
29639 return maybe_comptime_alloc.stores.append(sema.arena, store_inst);
29640 }
29641
29642 // Store is runtime-known
29643 _ = sema.maybe_comptime_allocs.remove(maybe_base_alloc);
29644}
29645
29646/// Given an AIR instruction transforming a pointer (struct_field_ptr,
29647/// ptr_elem_ptr, bitcast, etc), checks whether the base pointer refers to a
29648/// local alloc, and updates `base_allocs` accordingly.
29649fn checkKnownAllocPtr(sema: *Sema, base_ptr: Air.Inst.Ref, new_ptr: Air.Inst.Ref) !void {
29650 const base_ptr_inst = Air.refToIndex(base_ptr) orelse return;
29651 const new_ptr_inst = Air.refToIndex(new_ptr) orelse return;
29652 const alloc_inst = sema.base_allocs.get(base_ptr_inst) orelse return;
29653 try sema.base_allocs.put(sema.gpa, new_ptr_inst, alloc_inst);
29654
29655 switch (sema.air_instructions.items(.tag)[new_ptr_inst]) {
29656 .optional_payload_ptr_set, .errunion_payload_ptr_set => {
29657 const maybe_comptime_alloc = sema.maybe_comptime_allocs.getPtr(alloc_inst) orelse return;
29658 try maybe_comptime_alloc.non_elideable_pointers.append(sema.arena, new_ptr_inst);
29659 },
29660 .ptr_elem_ptr => {
29661 const tmp_air = sema.getTmpAir();
29662 const pl_idx = tmp_air.instructions.items(.data)[new_ptr_inst].ty_pl.payload;
29663 const bin = tmp_air.extraData(Air.Bin, pl_idx).data;
29664 const index_ref = bin.rhs;
29665
29666 // If the index value is runtime-known, this pointer is also runtime-known, so
29667 // we must in turn make the alloc value runtime-known.
29668 if (null == try sema.resolveMaybeUndefVal(index_ref)) {
29669 _ = sema.maybe_comptime_allocs.remove(alloc_inst);
29670 }
29671 },
29672 else => {},
29422 }29673 }
29423}29674}
2942429675
...@@ -30517,7 +30768,9 @@ fn coerceCompatiblePtrs(...@@ -30517,7 +30768,9 @@ fn coerceCompatiblePtrs(
30517 } else is_non_zero;30768 } else is_non_zero;
30518 try sema.addSafetyCheck(block, inst_src, ok, .cast_to_null);30769 try sema.addSafetyCheck(block, inst_src, ok, .cast_to_null);
30519 }30770 }
30520 return sema.bitCast(block, dest_ty, inst, inst_src, null);30771 const new_ptr = try sema.bitCast(block, dest_ty, inst, inst_src, null);
30772 try sema.checkKnownAllocPtr(inst, new_ptr);
30773 return new_ptr;
30521}30774}
3052230775
30523fn coerceEnumToUnion(30776fn coerceEnumToUnion(
src/Zir.zig+9-1
...@@ -941,8 +941,16 @@ pub const Inst = struct {...@@ -941,8 +941,16 @@ pub const Inst = struct {
941 /// Allocates stack local memory.941 /// Allocates stack local memory.
942 /// Uses the `un_node` union field. The operand is the type of the allocated object.942 /// Uses the `un_node` union field. The operand is the type of the allocated object.
943 /// The node source location points to a var decl node.943 /// The node source location points to a var decl node.
944 /// A `make_ptr_const` instruction should be used once the value has
945 /// been stored to the allocation. To ensure comptime value detection
946 /// functions, there are some restrictions on how this pointer should be
947 /// used prior to the `make_ptr_const` instruction: no pointer derived
948 /// from this `alloc` may be returned from a block or stored to another
949 /// address. In other words, it must be trivial to determine whether any
950 /// given pointer derives from this one.
944 alloc,951 alloc,
945 /// Same as `alloc` except mutable.952 /// Same as `alloc` except mutable. As such, `make_ptr_const` need not be used,
953 /// and there are no restrictions on the usage of the pointer.
946 alloc_mut,954 alloc_mut,
947 /// Allocates comptime-mutable memory.955 /// Allocates comptime-mutable memory.
948 /// Uses the `un_node` union field. The operand is the type of the allocated object.956 /// Uses the `un_node` union field. The operand is the type of the allocated object.
test/behavior/destructure.zig+40
...@@ -98,3 +98,43 @@ test "destructure from struct init with named tuple fields" {...@@ -98,3 +98,43 @@ test "destructure from struct init with named tuple fields" {
98 try expect(y == 200);98 try expect(y == 200);
99 try expect(z == 300);99 try expect(z == 300);
100}100}
101
102test "destructure of comptime-known tuple is comptime-known" {
103 const x, const y = .{ 1, 2 };
104
105 comptime assert(@TypeOf(x) == comptime_int);
106 comptime assert(x == 1);
107
108 comptime assert(@TypeOf(y) == comptime_int);
109 comptime assert(y == 2);
110}
111
112test "destructure of comptime-known tuple where some destinations are runtime-known is comptime-known" {
113 var z: u32 = undefined;
114 var x: u8, const y, z = .{ 1, 2, 3 };
115
116 comptime assert(@TypeOf(y) == comptime_int);
117 comptime assert(y == 2);
118
119 try expect(x == 1);
120 try expect(z == 3);
121}
122
123test "destructure of tuple with comptime fields results in some comptime-known values" {
124 var runtime: u32 = 42;
125 const a, const b, const c, const d = .{ 123, runtime, 456, runtime };
126
127 // a, c are comptime-known
128 // b, d are runtime-known
129
130 comptime assert(@TypeOf(a) == comptime_int);
131 comptime assert(@TypeOf(b) == u32);
132 comptime assert(@TypeOf(c) == comptime_int);
133 comptime assert(@TypeOf(d) == u32);
134
135 comptime assert(a == 123);
136 comptime assert(c == 456);
137
138 try expect(b == 42);
139 try expect(d == 42);
140}
test/behavior/eval.zig+18
...@@ -1724,3 +1724,21 @@ comptime {...@@ -1724,3 +1724,21 @@ comptime {
1724 assert(foo[1] == 2);1724 assert(foo[1] == 2);
1725 assert(foo[2] == 0x55);1725 assert(foo[2] == 0x55);
1726}1726}
1727
1728test "const with allocation before result is comptime-known" {
1729 const x = blk: {
1730 const y = [1]u32{2};
1731 _ = y;
1732 break :blk [1]u32{42};
1733 };
1734 comptime assert(@TypeOf(x) == [1]u32);
1735 comptime assert(x[0] == 42);
1736}
1737
1738test "const with specified type initialized with typed array is comptime-known" {
1739 const x: [3]u16 = [3]u16{ 1, 2, 3 };
1740 comptime assert(@TypeOf(x) == [3]u16);
1741 comptime assert(x[0] == 1);
1742 comptime assert(x[1] == 2);
1743 comptime assert(x[2] == 3);
1744}