1pub const ComptimeLoadResult = union(enum) {
2 success: MutableValue,
3
4 runtime_load,
5 undef,
6 err_payload: InternPool.NullTerminatedString,
7 null_payload,
8 inactive_union_field,
9 needed_well_defined: Type,
10 out_of_bounds: Type,
11 exceeds_host_size,
12};
13
14pub fn loadComptimePtr(sema: *Sema, block: *Block, src: LazySrcLoc, ptr: Value) !ComptimeLoadResult {
15 const pt = sema.pt;
16 const zcu = pt.zcu;
17
18 const ptr_info = ptr.typeOf(pt.zcu).ptrInfo(pt.zcu);
19 const elem_ty: Type = .fromInterned(ptr_info.child);
20 const host_size = ptr_info.packed_offset.host_size;
21
22 if (host_size == 0) {
23 return loadComptimePtrInner(sema, block, src, ptr, elem_ty, 0);
24 }
25
26 assert(elem_ty.hasBitRepresentation(zcu));
27 if (ptr_info.flags.vector_index == .none) {
28 if (ptr_info.packed_offset.bit_offset + elem_ty.bitSize(zcu) > host_size * 8) {
29 return .exceeds_host_size;
30 }
31 const load_ty: Type = try pt.intType(.unsigned, host_size * 8);
32 const backing_int_mv = switch (try loadComptimePtrInner(sema, block, src, ptr, load_ty, 0)) {
33 else => |result| return result,
34 .success => |mv| mv,
35 };
36 const backing_int_val = try backing_int_mv.intern(pt, sema.arena);
37 const buf = try sema.arena.alloc(u8, host_size);
38 @memset(buf, 0);
39 backing_int_val.writeToPackedMemory(zcu, buf, 0);
40 const result_val: Value = try .readFromPackedMemory(elem_ty, pt, buf, ptr_info.packed_offset.bit_offset);
41 return .{ .success = .{ .interned = result_val.toIntern() } };
42 }
43 if (@backingInt(ptr_info.flags.vector_index) >= host_size) {
44 return .exceeds_host_size;
45 }
46 const load_ty: Type = try pt.vectorType(.{
47 .len = host_size,
48 .child = elem_ty.toIntern(),
49 });
50 const vector_mv = switch (try loadComptimePtrInner(sema, block, src, ptr, load_ty, 0)) {
51 else => |result| return result,
52 .success => |mv| mv,
53 };
54 const vector_val = try vector_mv.intern(pt, sema.arena);
55 const result_val = try vector_val.elemValue(pt, @backingInt(ptr_info.flags.vector_index));
56 return .{ .success = .{ .interned = result_val.toIntern() } };
57}
58
59pub const ComptimeStoreResult = union(enum) {
60 success,
61
62 runtime_store,
63 comptime_field_mismatch: Value,
64 undef,
65 err_payload: InternPool.NullTerminatedString,
66 null_payload,
67 inactive_union_field,
68 needed_well_defined: Type,
69 out_of_bounds: Type,
70 exceeds_host_size,
71};
72
73/// Perform a comptime load of value `store_val` to a pointer.
74///
75/// Asserts that the type of `store_val` equals the element type of the pointer type.
76pub fn storeComptimePtr(
77 sema: *Sema,
78 block: *Block,
79 src: LazySrcLoc,
80 ptr: Value,
81 store_val: Value,
82) !ComptimeStoreResult {
83 const pt = sema.pt;
84 const zcu = pt.zcu;
85
86 const ptr_info = ptr.typeOf(pt.zcu).ptrInfo(pt.zcu);
87 const elem_ty: Type = .fromInterned(ptr_info.child);
88 const host_size = ptr_info.packed_offset.host_size;
89 assert(store_val.typeOf(zcu).toIntern() == elem_ty.toIntern());
90
91 if (host_size == 0) {
92 return storeComptimePtrInner(sema, block, src, ptr, store_val);
93 }
94
95 assert(elem_ty.hasBitRepresentation(zcu));
96 if (ptr_info.flags.vector_index == .none) {
97 if (ptr_info.packed_offset.bit_offset + elem_ty.bitSize(zcu) > host_size * 8) {
98 return .exceeds_host_size;
99 }
100 const backing_ty: Type = try pt.intType(.unsigned, host_size * 8);
101 const backing_int_mv = switch (try loadComptimePtrInner(sema, block, src, ptr, backing_ty, 0)) {
102 .success => |mv| mv,
103 .runtime_load => return .runtime_store,
104 inline else => |payload, tag| return @unionInit(ComptimeStoreResult, @tagName(tag), payload),
105 };
106 const old_backing_int_val = try backing_int_mv.intern(pt, sema.arena);
107 const buf = try sema.arena.alloc(u8, host_size);
108 @memset(buf, 0);
109 old_backing_int_val.writeToPackedMemory(zcu, buf, 0);
110 // Write the new element...
111 store_val.writeToPackedMemory(zcu, buf, ptr_info.packed_offset.bit_offset);
112 // ...then read the resulting backing integer value...
113 const new_backing_int_val: Value = try .readFromPackedMemory(backing_ty, pt, buf, 0);
114 // ...and store that back into memory
115 return storeComptimePtrInner(sema, block, src, ptr, new_backing_int_val);
116 }
117
118 if (@backingInt(ptr_info.flags.vector_index) >= host_size) {
119 return .exceeds_host_size;
120 }
121 const vec_ty: Type = try pt.vectorType(.{
122 .len = host_size,
123 .child = elem_ty.toIntern(),
124 });
125 const vector_mv = switch (try loadComptimePtrInner(sema, block, src, ptr, vec_ty, 0)) {
126 .success => |mv| mv,
127 .runtime_load => return .runtime_store,
128 inline else => |payload, tag| return @unionInit(ComptimeStoreResult, @tagName(tag), payload),
129 };
130 const old_vector_val = try vector_mv.intern(pt, sema.arena);
131 const elems_buf = try sema.arena.alloc(InternPool.Index, host_size);
132 for (elems_buf, 0..) |*elem, elem_index| {
133 const elem_val = try old_vector_val.elemValue(pt, elem_index);
134 elem.* = elem_val.toIntern();
135 }
136 elems_buf[@backingInt(ptr_info.flags.vector_index)] = store_val.toIntern();
137 const new_vector_val = try pt.aggregateValue(vec_ty, elems_buf);
138 return storeComptimePtrInner(sema, block, src, ptr, new_vector_val);
139}
140
141/// Like `storeComptimePtr`, except ignores the type of `ptr`, instead treating it as a single-item
142/// pointer to `store_val.typeOf(zcu)`.
143fn storeComptimePtrInner(
144 sema: *Sema,
145 block: *Block,
146 src: LazySrcLoc,
147 ptr: Value,
148 store_val: Value,
149) !ComptimeStoreResult {
150 const pt = sema.pt;
151 const zcu = pt.zcu;
152 const store_ty = store_val.typeOf(zcu);
153
154 if (store_ty.classify(zcu) == .one_possible_value) {
155 // zero-bit store; nothing to do
156 return .success;
157 }
158
159 const strat = try prepareComptimePtrStore(sema, block, src, ptr, store_ty, 0);
160
161 // Propagate errors and handle comptime fields.
162 switch (strat) {
163 .comptime_field => {
164 // To "store" to a comptime field, just perform a load of the field
165 // and see if the store value matches.
166 const expected_mv = switch (try loadComptimePtr(sema, block, src, ptr)) {
167 .success => |mv| mv,
168 .runtime_load => unreachable, // this is a comptime field
169 .exceeds_host_size => unreachable, // checked above
170 .undef => return .undef,
171 .err_payload => |err| return .{ .err_payload = err },
172 .null_payload => return .null_payload,
173 .inactive_union_field => return .inactive_union_field,
174 .needed_well_defined => |ty| return .{ .needed_well_defined = ty },
175 .out_of_bounds => |ty| return .{ .out_of_bounds = ty },
176 };
177 const expected = try expected_mv.intern(pt, sema.arena);
178 if (store_val.toIntern() != expected.toIntern()) {
179 return .{ .comptime_field_mismatch = expected };
180 }
181 return .success;
182 },
183 .runtime_store => return .runtime_store,
184 .undef => return .undef,
185 .err_payload => |err| return .{ .err_payload = err },
186 .null_payload => return .null_payload,
187 .inactive_union_field => return .inactive_union_field,
188 .needed_well_defined => |ty| return .{ .needed_well_defined = ty },
189 .out_of_bounds => |ty| return .{ .out_of_bounds = ty },
190
191 .direct => |direct| {
192 try checkComptimeVarStore(sema, block, src, direct.alloc);
193 const want_ty = direct.val.typeOf(zcu);
194 const coerced_store_val = try pt.getCoerced(store_val, want_ty);
195 direct.val.* = .{ .interned = coerced_store_val.toIntern() };
196 return .success;
197 },
198
199 .index => |index| {
200 try checkComptimeVarStore(sema, block, src, index.alloc);
201 const want_ty = index.val.typeOf(zcu).childType(zcu);
202 const coerced_store_val = try pt.getCoerced(store_val, want_ty);
203 try index.val.setElem(pt, sema.arena, @intCast(index.elem_index), .{ .interned = coerced_store_val.toIntern() });
204 return .success;
205 },
206
207 .flat_index => |flat| {
208 try checkComptimeVarStore(sema, block, src, flat.alloc);
209 const store_elems = store_val.typeOf(zcu).arrayBase(zcu)[1];
210 const flat_elems = try sema.arena.alloc(InternPool.Index, @intCast(store_elems));
211 {
212 var next_idx: u64 = 0;
213 var skip: u64 = 0;
214 try flattenArray(sema, .{ .interned = store_val.toIntern() }, &skip, &next_idx, flat_elems);
215 }
216 for (flat_elems, 0..) |elem, idx| {
217 // TODO: recursiveIndex in a loop does a lot of redundant work!
218 // Better would be to gather all the store targets into an array.
219 var index: u64 = flat.flat_elem_index + idx;
220 const val_ptr, const final_idx = (try recursiveIndex(sema, flat.val, &index)).?;
221 try val_ptr.setElem(pt, sema.arena, @intCast(final_idx), .{ .interned = elem });
222 }
223 return .success;
224 },
225
226 .reinterpret => |reinterpret| {
227 try checkComptimeVarStore(sema, block, src, reinterpret.alloc);
228 if (!reinterpret.val.typeOf(zcu).hasWellDefinedLayout(zcu)) {
229 return .{ .needed_well_defined = reinterpret.val.typeOf(zcu) };
230 }
231 if (!store_ty.hasWellDefinedLayout(zcu)) {
232 return .{ .needed_well_defined = store_ty };
233 }
234 const old_val = try reinterpret.val.intern(pt, sema.arena);
235 const new_val = try sema.spliceMemory(
236 old_val,
237 store_val,
238 reinterpret.byte_offset,
239 ) orelse return .runtime_store;
240 reinterpret.val.* = .{ .interned = new_val.toIntern() };
241 return .success;
242 },
243 }
244}
245
246/// Perform a comptime load of type `load_ty` from a pointer.
247/// The pointer's type is ignored.
248fn loadComptimePtrInner(
249 sema: *Sema,
250 block: *Block,
251 src: LazySrcLoc,
252 ptr_val: Value,
253 load_ty: Type,
254 /// If `load_ty` is an array, this is the number of array elements to skip
255 /// before `load_ty`. Otherwise, it is ignored and may be `undefined`.
256 array_offset: u64,
257) !ComptimeLoadResult {
258 const pt = sema.pt;
259 const zcu = pt.zcu;
260 const ip = &zcu.intern_pool;
261
262 const ptr = switch (ip.indexToKey(ptr_val.toIntern())) {
263 .undef => return .undef,
264 .ptr => |ptr| ptr,
265 else => unreachable,
266 };
267
268 const base_val: MutableValue = switch (ptr.base_addr) {
269 .nav => |nav_id| val: {
270 try sema.ensureNavResolved(block, src, nav_id, .fully);
271 const nav = ip.getNav(nav_id);
272 if (!nav.resolved.?.@"const") return .runtime_load;
273 // We let `.@"extern"` through here if it's a fn. This allows aliasing `extern fn`s.
274 if (ip.indexToKey(nav.resolved.?.value) == .@"extern" and
275 Type.fromInterned(nav.resolved.?.type).zigTypeTag(zcu) != .@"fn")
276 {
277 return .runtime_load;
278 }
279 break :val .{ .interned = nav.resolved.?.value };
280 },
281 .comptime_alloc => |alloc_index| sema.getComptimeAlloc(alloc_index).val,
282 .uav => |uav| .{ .interned = uav.val },
283 .comptime_field => |val| .{ .interned = val },
284 .int => return .runtime_load,
285 .eu_payload => |base_ptr_ip| val: {
286 const base_ptr = Value.fromInterned(base_ptr_ip);
287 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
288 switch (try loadComptimePtrInner(sema, block, src, base_ptr, base_ty, undefined)) {
289 .success => |eu_val| switch (eu_val.unpackErrorUnion(zcu)) {
290 .undef => return .undef,
291 .err => |err| return .{ .err_payload = err },
292 .payload => |payload| break :val payload,
293 },
294 else => |err| return err,
295 }
296 },
297 .opt_payload => |base_ptr_ip| val: {
298 const base_ptr = Value.fromInterned(base_ptr_ip);
299 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
300 switch (try loadComptimePtrInner(sema, block, src, base_ptr, base_ty, undefined)) {
301 .success => |eu_val| switch (eu_val.unpackOptional(zcu)) {
302 .undef => return .undef,
303 .null => return .null_payload,
304 .payload => |payload| break :val payload,
305 },
306 else => |err| return err,
307 }
308 },
309 .arr_elem => |base_index| val: {
310 const base_ptr = Value.fromInterned(base_index.base);
311 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
312
313 // We have a comptime-only array. This case is a little nasty.
314 // To avoid loading too much data, we want to figure out how many elements we need.
315 // If `load_ty` and the array share a base type, we'll load the correct number of elements.
316 // Otherwise, we'll be reinterpreting (which we can't do, since it's comptime-only); just
317 // load a single element and let the logic below emit its error.
318
319 const load_one_ty, const load_count = load_ty.arrayBase(zcu);
320 const count = if (load_one_ty.toIntern() == base_ty.toIntern()) load_count else 1;
321
322 const want_ty = try sema.pt.arrayType(.{
323 .len = count,
324 .child = base_ty.toIntern(),
325 });
326
327 switch (try loadComptimePtrInner(sema, block, src, base_ptr, want_ty, base_index.index)) {
328 .success => |arr_val| break :val arr_val,
329 else => |err| return err,
330 }
331 },
332 .field => |base_index| val: {
333 const base_ptr = Value.fromInterned(base_index.base);
334 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
335
336 // Field of a slice, or of an auto-layout struct or union.
337 const agg_val = switch (try loadComptimePtrInner(sema, block, src, base_ptr, base_ty, undefined)) {
338 .success => |val| val,
339 else => |err| return err,
340 };
341
342 const agg_ty = agg_val.typeOf(zcu);
343 switch (agg_ty.zigTypeTag(zcu)) {
344 .@"struct", .pointer => break :val try agg_val.getElem(sema.pt, @intCast(base_index.index)),
345 .@"union" => {
346 const tag_val: Value, const payload_mv: MutableValue = switch (agg_val) {
347 .un => |un| .{ Value.fromInterned(un.tag), un.payload.* },
348 .interned => |ip_index| switch (ip.indexToKey(ip_index)) {
349 .undef => return .undef,
350 .un => |un| .{ Value.fromInterned(un.tag), .{ .interned = un.val } },
351 else => unreachable,
352 },
353 else => unreachable,
354 };
355 const tag_ty = agg_ty.unionTagTypeHypothetical(zcu);
356 if (tag_ty.enumTagFieldIndex(tag_val, zcu).? != base_index.index) {
357 return .inactive_union_field;
358 }
359 break :val payload_mv;
360 },
361 else => unreachable,
362 }
363
364 break :val try agg_val.getElem(zcu, base_index.index);
365 },
366 };
367
368 if (ptr.byte_offset == 0) {
369 if (load_ty.zigTypeTag(zcu) != .array or array_offset == 0) {
370 if (.ok == try sema.coerceInMemoryAllowed(
371 block,
372 load_ty,
373 base_val.typeOf(zcu),
374 false,
375 zcu.getTarget(),
376 src,
377 src,
378 null,
379 )) {
380 // We already have a value which is IMC to the desired type.
381 return .{ .success = base_val };
382 }
383 }
384 }
385
386 restructure_array: {
387 // We might also be changing the length of an array, or restructuring it.
388 // e.g. [1][2][3]T -> [3][2]T.
389 // This case is important because it's permitted for types with ill-defined layouts.
390
391 const load_one_ty, const load_count = load_ty.arrayBase(zcu);
392
393 const extra_base_index: u64 = if (ptr.byte_offset == 0) 0 else idx: {
394 if (load_one_ty.comptimeOnly(zcu)) break :restructure_array;
395 const elem_len = load_one_ty.abiSize(zcu);
396 if (ptr.byte_offset % elem_len != 0) break :restructure_array;
397 break :idx @divExact(ptr.byte_offset, elem_len);
398 };
399
400 const val_one_ty, const val_count = base_val.typeOf(zcu).arrayBase(zcu);
401 if (.ok == try sema.coerceInMemoryAllowed(
402 block,
403 load_one_ty,
404 val_one_ty,
405 false,
406 zcu.getTarget(),
407 src,
408 src,
409 null,
410 )) {
411 // Changing the length of an array.
412 const skip_base: u64 = extra_base_index + if (load_ty.zigTypeTag(zcu) == .array) skip: {
413 break :skip load_ty.childType(zcu).arrayBase(zcu)[1] * array_offset;
414 } else 0;
415 if (skip_base + load_count > val_count) return .{ .out_of_bounds = base_val.typeOf(zcu) };
416 const elems = try sema.arena.alloc(InternPool.Index, @intCast(load_count));
417 var skip: u64 = skip_base;
418 var next_idx: u64 = 0;
419 try flattenArray(sema, base_val, &skip, &next_idx, elems);
420 next_idx = 0;
421 const val = try unflattenArray(sema, load_ty, elems, &next_idx);
422 return .{ .success = .{ .interned = val.toIntern() } };
423 }
424 }
425
426 // We need to reinterpret memory, which is only possible if neither the load
427 // type nor the type of the base value are comptime-only.
428
429 if (!load_ty.hasWellDefinedLayout(zcu)) {
430 return .{ .needed_well_defined = load_ty };
431 }
432
433 if (!base_val.typeOf(zcu).hasWellDefinedLayout(zcu)) {
434 return .{ .needed_well_defined = base_val.typeOf(zcu) };
435 }
436
437 var cur_val = base_val;
438 var cur_offset = ptr.byte_offset;
439
440 if (load_ty.zigTypeTag(zcu) == .array and array_offset > 0) {
441 cur_offset += load_ty.childType(zcu).abiSize(zcu) * array_offset;
442 }
443
444 const need_bytes = load_ty.abiSize(zcu);
445
446 if (cur_offset + need_bytes > cur_val.typeOf(zcu).abiSize(zcu)) {
447 return .{ .out_of_bounds = cur_val.typeOf(zcu) };
448 }
449
450 // In the worst case, we can reinterpret the entire value - however, that's
451 // pretty wasteful. If the memory region we're interested in refers to one
452 // field or array element, let's just look at that.
453 while (true) {
454 const cur_ty = cur_val.typeOf(zcu);
455 switch (cur_ty.zigTypeTag(zcu)) {
456 .noreturn,
457 .type,
458 .comptime_int,
459 .comptime_float,
460 .null,
461 .undefined,
462 .enum_literal,
463 .@"opaque",
464 .spirv,
465 .@"fn",
466 .error_union,
467 => unreachable, // ill-defined layout
468 .int,
469 .float,
470 .bool,
471 .void,
472 .pointer,
473 .error_set,
474 .@"anyframe",
475 .frame,
476 .@"enum",
477 .vector,
478 => break, // terminal types (no sub-values)
479 .optional => break, // this can only be a pointer-like optional so is terminal
480 .array => {
481 const elem_ty = cur_ty.childType(zcu);
482 const elem_size = elem_ty.abiSize(zcu);
483 const elem_idx = cur_offset / elem_size;
484 const next_elem_off = elem_size * (elem_idx + 1);
485 if (cur_offset + need_bytes <= next_elem_off) {
486 // We can look at a single array element.
487 cur_val = try cur_val.getElem(sema.pt, @intCast(elem_idx));
488 cur_offset -= elem_idx * elem_size;
489 } else {
490 break;
491 }
492 },
493 .@"struct" => switch (cur_ty.containerLayout(zcu)) {
494 .auto => unreachable, // ill-defined layout
495 .@"packed" => break, // let the memory reinterpret logic handle this
496 .@"extern" => for (0..cur_ty.structFieldCount(zcu)) |field_idx| {
497 const start_off = cur_ty.structFieldOffset(field_idx, zcu);
498 const end_off = start_off + cur_ty.fieldType(field_idx, zcu).abiSize(zcu);
499 if (cur_offset >= start_off and cur_offset + need_bytes <= end_off) {
500 cur_val = try cur_val.getElem(sema.pt, field_idx);
501 cur_offset -= start_off;
502 break;
503 }
504 } else break, // pointer spans multiple fields
505 },
506 .@"union" => switch (cur_ty.containerLayout(zcu)) {
507 .auto => unreachable, // ill-defined layout
508 .@"packed" => break, // let the memory reinterpret logic handle this
509 .@"extern" => {
510 // TODO: we have to let the memory reinterpret logic handle this for now.
511 // Otherwise, we might traverse into a union field which doesn't allow pointers.
512 // Figure out a solution!
513 if (true) break;
514 const payload: MutableValue = switch (cur_val) {
515 .un => |un| un.payload.*,
516 .interned => |ip_index| switch (ip.indexToKey(ip_index)) {
517 .un => |un| .{ .interned = un.val },
518 .undef => return .undef,
519 else => unreachable,
520 },
521 else => unreachable,
522 };
523 // The payload always has offset 0. If it's big enough
524 // to represent the whole load type, we can use it.
525 if (payload.typeOf(zcu).abiSize(zcu) >= need_bytes) {
526 cur_val = payload;
527 } else {
528 break;
529 }
530 },
531 },
532 }
533 }
534
535 // Fast path: check again if we're now at the type we want to load.
536 // If so, just return the loaded value.
537 if (cur_offset == 0 and cur_val.typeOf(zcu).toIntern() == load_ty.toIntern()) {
538 return .{ .success = cur_val };
539 }
540
541 // Otherwise, use the memory reinterpretation logic to pull out the bytes we need.
542 const reinterpret_val = try cur_val.intern(pt, sema.arena);
543 const result_val = try sema.castMemory(reinterpret_val, load_ty, cur_offset) orelse return .runtime_load;
544 return .{ .success = .{ .interned = result_val.toIntern() } };
545}
546
547const ComptimeStoreStrategy = union(enum) {
548 /// The store should be performed directly to this value, which `store_ty`
549 /// is in-memory coercible to.
550 direct: struct {
551 alloc: ComptimeAllocIndex,
552 val: *MutableValue,
553 },
554 /// The store should be performed at the index `elem_index` into `val`,
555 /// which is an array.
556 /// This strategy exists to avoid the need to convert the parent value
557 /// to the `aggregate` representation when `repeated` or `bytes` may
558 /// suffice.
559 index: struct {
560 alloc: ComptimeAllocIndex,
561 val: *MutableValue,
562 elem_index: u64,
563 },
564 /// The store should be performed on this array value, but it is being
565 /// restructured, e.g. [3][2][1]T -> [2][3]T.
566 /// This includes the case where it is a sub-array, e.g. [3]T -> [2]T.
567 /// This is only returned if `store_ty` is an array type, and its array
568 /// base type is IMC to that of the type of `val`.
569 flat_index: struct {
570 alloc: ComptimeAllocIndex,
571 val: *MutableValue,
572 flat_elem_index: u64,
573 },
574 /// This value should be reinterpreted using `Sema.spliceMemory` to perform
575 /// store. Only returned if `store_ty` and the type of `val` both have
576 /// well-defined layouts.
577 reinterpret: struct {
578 alloc: ComptimeAllocIndex,
579 val: *MutableValue,
580 byte_offset: u64,
581 },
582
583 comptime_field,
584 runtime_store,
585 undef,
586 err_payload: InternPool.NullTerminatedString,
587 null_payload,
588 inactive_union_field,
589 needed_well_defined: Type,
590 out_of_bounds: Type,
591
592 fn alloc(strat: ComptimeStoreStrategy) ComptimeAllocIndex {
593 return switch (strat) {
594 inline .direct, .index, .flat_index, .reinterpret => |info| info.alloc,
595 .comptime_field,
596 .runtime_store,
597 .undef,
598 .err_payload,
599 .null_payload,
600 .inactive_union_field,
601 .needed_well_defined,
602 .out_of_bounds,
603 => unreachable,
604 };
605 }
606};
607
608/// Decide the strategy we will use to perform a comptime store of type `store_ty` to a pointer.
609/// The pointer's type is ignored.
610fn prepareComptimePtrStore(
611 sema: *Sema,
612 block: *Block,
613 src: LazySrcLoc,
614 ptr_val: Value,
615 store_ty: Type,
616 /// If `store_ty` is an array, this is the number of array elements to skip
617 /// before `store_ty`. Otherwise, it is ignored and may be `undefined`.
618 array_offset: u64,
619) !ComptimeStoreStrategy {
620 const pt = sema.pt;
621 const zcu = pt.zcu;
622 const ip = &zcu.intern_pool;
623
624 const ptr = switch (ip.indexToKey(ptr_val.toIntern())) {
625 .undef => return .undef,
626 .ptr => |ptr| ptr,
627 else => unreachable,
628 };
629
630 // `base_strat` will not be an error case.
631 const base_strat: ComptimeStoreStrategy = switch (ptr.base_addr) {
632 .nav, .uav, .int => return .runtime_store,
633 .comptime_field => return .comptime_field,
634 .comptime_alloc => |alloc_index| .{ .direct = .{
635 .alloc = alloc_index,
636 .val = &sema.getComptimeAlloc(alloc_index).val,
637 } },
638 .eu_payload => |base_ptr_ip| base_val: {
639 const base_ptr = Value.fromInterned(base_ptr_ip);
640 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
641 const eu_val_ptr, const alloc = switch (try prepareComptimePtrStore(sema, block, src, base_ptr, base_ty, undefined)) {
642 .direct => |direct| .{ direct.val, direct.alloc },
643 .index => |index| .{
644 try index.val.elem(pt, sema.arena, @intCast(index.elem_index)),
645 index.alloc,
646 },
647 .flat_index => unreachable, // base_ty is not an array
648 .reinterpret => unreachable, // base_ty has ill-defined layout
649 else => |err| return err,
650 };
651 try eu_val_ptr.unintern(pt, sema.arena, false, false);
652 switch (eu_val_ptr.*) {
653 .interned => |ip_index| switch (ip.indexToKey(ip_index)) {
654 .undef => return .undef,
655 .error_union => |eu| return .{ .err_payload = eu.val.err_name },
656 else => unreachable,
657 },
658 .eu_payload => |data| break :base_val .{ .direct = .{
659 .val = data.child,
660 .alloc = alloc,
661 } },
662 else => unreachable,
663 }
664 },
665 .opt_payload => |base_ptr_ip| base_val: {
666 const base_ptr = Value.fromInterned(base_ptr_ip);
667 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
668 const opt_val_ptr, const alloc = switch (try prepareComptimePtrStore(sema, block, src, base_ptr, base_ty, undefined)) {
669 .direct => |direct| .{ direct.val, direct.alloc },
670 .index => |index| .{
671 try index.val.elem(pt, sema.arena, @intCast(index.elem_index)),
672 index.alloc,
673 },
674 .flat_index => unreachable, // base_ty is not an array
675 .reinterpret => unreachable, // base_ty has ill-defined layout
676 else => |err| return err,
677 };
678 try opt_val_ptr.unintern(pt, sema.arena, false, false);
679 switch (opt_val_ptr.*) {
680 .interned => |ip_index| switch (ip.indexToKey(ip_index)) {
681 .undef => return .undef,
682 .opt => return .null_payload,
683 else => unreachable,
684 },
685 .opt_payload => |data| break :base_val .{ .direct = .{
686 .val = data.child,
687 .alloc = alloc,
688 } },
689 else => unreachable,
690 }
691 },
692 .arr_elem => |base_index| base_val: {
693 const base_ptr = Value.fromInterned(base_index.base);
694 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
695
696 // We have a comptime-only array. This case is a little nasty.
697 // To avoid messing with too much data, we want to figure out how many elements we need to store.
698 // If `store_ty` and the array share a base type, we'll store the correct number of elements.
699 // Otherwise, we'll be reinterpreting (which we can't do, since it's comptime-only); just
700 // load a single element and let the logic below emit its error.
701
702 const store_one_ty, const store_count = store_ty.arrayBase(zcu);
703 const count = if (store_one_ty.toIntern() == base_ty.toIntern()) store_count else 1;
704
705 const want_ty = try pt.arrayType(.{
706 .len = count,
707 .child = base_ty.toIntern(),
708 });
709
710 const result = try prepareComptimePtrStore(sema, block, src, base_ptr, want_ty, base_index.index);
711 switch (result) {
712 .direct, .index, .flat_index => break :base_val result,
713 .reinterpret => unreachable, // comptime-only array so ill-defined layout
714 else => |err| return err,
715 }
716 },
717 .field => |base_index| strat: {
718 const base_ptr = Value.fromInterned(base_index.base);
719 const base_ty = base_ptr.typeOf(zcu).childType(zcu);
720
721 // Field of a slice, or of an auto-layout struct or union.
722 const agg_val, const alloc = switch (try prepareComptimePtrStore(sema, block, src, base_ptr, base_ty, undefined)) {
723 .direct => |direct| .{ direct.val, direct.alloc },
724 .index => |index| .{
725 try index.val.elem(pt, sema.arena, @intCast(index.elem_index)),
726 index.alloc,
727 },
728 .flat_index => unreachable, // base_ty is not an array
729 .reinterpret => unreachable, // base_ty has ill-defined layout
730 else => |err| return err,
731 };
732
733 const agg_ty = agg_val.typeOf(zcu);
734 switch (agg_ty.zigTypeTag(zcu)) {
735 .@"struct", .pointer => break :strat .{ .direct = .{
736 .val = try agg_val.elem(pt, sema.arena, @intCast(base_index.index)),
737 .alloc = alloc,
738 } },
739 .@"union" => {
740 if (agg_val.* == .interned and Value.fromInterned(agg_val.interned).isUndef(zcu)) {
741 return .undef;
742 }
743 try agg_val.unintern(pt, sema.arena, false, false);
744 const un = agg_val.un;
745 const tag_ty = agg_ty.unionTagTypeHypothetical(zcu);
746 if (tag_ty.enumTagFieldIndex(Value.fromInterned(un.tag), zcu).? != base_index.index) {
747 return .inactive_union_field;
748 }
749 break :strat .{ .direct = .{
750 .val = un.payload,
751 .alloc = alloc,
752 } };
753 },
754 else => unreachable,
755 }
756 },
757 };
758
759 if (ptr.byte_offset == 0) {
760 if (store_ty.zigTypeTag(zcu) != .array or array_offset == 0) direct: {
761 const base_val_ty = switch (base_strat) {
762 .direct => |direct| direct.val.typeOf(zcu),
763 .index => |index| index.val.typeOf(zcu).childType(zcu),
764 .flat_index, .reinterpret => break :direct,
765 else => unreachable,
766 };
767 if (.ok == try sema.coerceInMemoryAllowed(
768 block,
769 base_val_ty,
770 store_ty,
771 true,
772 zcu.getTarget(),
773 src,
774 src,
775 null,
776 )) {
777 // The base strategy already gets us a value which the desired type is IMC to.
778 return base_strat;
779 }
780 }
781 }
782
783 restructure_array: {
784 // We might also be changing the length of an array, or restructuring it.
785 // e.g. [1][2][3]T -> [3][2]T.
786 // This case is important because it's permitted for types with ill-defined layouts.
787
788 const store_one_ty, const store_count = store_ty.arrayBase(zcu);
789 const extra_base_index: u64 = if (ptr.byte_offset == 0) 0 else idx: {
790 if (store_one_ty.comptimeOnly(zcu)) break :restructure_array;
791 const elem_len = store_one_ty.abiSize(zcu);
792 if (ptr.byte_offset % elem_len != 0) break :restructure_array;
793 break :idx @divExact(ptr.byte_offset, elem_len);
794 };
795
796 const base_val, const base_elem_offset, const oob_ty = switch (base_strat) {
797 .direct => |direct| .{ direct.val, 0, direct.val.typeOf(zcu) },
798 .index => |index| restructure_info: {
799 const elem_ty = index.val.typeOf(zcu).childType(zcu);
800 const elem_off = elem_ty.arrayBase(zcu)[1] * index.elem_index;
801 break :restructure_info .{ index.val, elem_off, elem_ty };
802 },
803 .flat_index => |flat| .{ flat.val, flat.flat_elem_index, flat.val.typeOf(zcu) },
804 .reinterpret => break :restructure_array,
805 else => unreachable,
806 };
807 const val_one_ty, const val_count = base_val.typeOf(zcu).arrayBase(zcu);
808 if (.ok != try sema.coerceInMemoryAllowed(block, val_one_ty, store_one_ty, true, zcu.getTarget(), src, src, null)) {
809 break :restructure_array;
810 }
811 if (base_elem_offset + extra_base_index + store_count > val_count) return .{ .out_of_bounds = oob_ty };
812
813 if (store_ty.zigTypeTag(zcu) == .array) {
814 const skip = store_ty.childType(zcu).arrayBase(zcu)[1] * array_offset;
815 return .{ .flat_index = .{
816 .alloc = base_strat.alloc(),
817 .val = base_val,
818 .flat_elem_index = skip + base_elem_offset + extra_base_index,
819 } };
820 }
821
822 // `base_val` must be an array, since otherwise the "direct reinterpret" logic above noticed it.
823 assert(base_val.typeOf(zcu).zigTypeTag(zcu) == .array);
824
825 var index: u64 = base_elem_offset + extra_base_index;
826 const arr_val, const arr_index = (try recursiveIndex(sema, base_val, &index)).?;
827 return .{ .index = .{
828 .alloc = base_strat.alloc(),
829 .val = arr_val,
830 .elem_index = arr_index,
831 } };
832 }
833
834 // We need to reinterpret memory, which is only possible if neither the store
835 // type nor the type of the base value have an ill-defined layout.
836
837 if (!store_ty.hasWellDefinedLayout(zcu)) {
838 return .{ .needed_well_defined = store_ty };
839 }
840
841 var cur_val: *MutableValue, var cur_offset: u64 = switch (base_strat) {
842 .direct => |direct| .{ direct.val, 0 },
843 // It's okay to do `abiSize` - the comptime-only case will be caught below.
844 .index => |index| .{ index.val, index.elem_index * index.val.typeOf(zcu).childType(zcu).abiSize(zcu) },
845 .flat_index => |flat_index| .{
846 flat_index.val,
847 // It's okay to do `abiSize` - the comptime-only case will be caught below.
848 flat_index.flat_elem_index * flat_index.val.typeOf(zcu).arrayBase(zcu)[0].abiSize(zcu),
849 },
850 .reinterpret => |r| .{ r.val, r.byte_offset },
851 else => unreachable,
852 };
853 cur_offset += ptr.byte_offset;
854
855 if (!cur_val.typeOf(zcu).hasWellDefinedLayout(zcu)) {
856 return .{ .needed_well_defined = cur_val.typeOf(zcu) };
857 }
858
859 if (store_ty.zigTypeTag(zcu) == .array and array_offset > 0) {
860 cur_offset += store_ty.childType(zcu).abiSize(zcu) * array_offset;
861 }
862
863 const need_bytes = store_ty.abiSize(zcu);
864
865 if (cur_offset + need_bytes > cur_val.typeOf(zcu).abiSize(zcu)) {
866 return .{ .out_of_bounds = cur_val.typeOf(zcu) };
867 }
868
869 // In the worst case, we can reinterpret the entire value - however, that's
870 // pretty wasteful. If the memory region we're interested in refers to one
871 // field or array element, let's just look at that.
872 while (true) {
873 const cur_ty = cur_val.typeOf(zcu);
874 switch (cur_ty.zigTypeTag(zcu)) {
875 .noreturn,
876 .type,
877 .comptime_int,
878 .comptime_float,
879 .null,
880 .undefined,
881 .enum_literal,
882 .@"opaque",
883 .spirv,
884 .@"fn",
885 .error_union,
886 => unreachable, // ill-defined layout
887 .int,
888 .float,
889 .bool,
890 .void,
891 .pointer,
892 .error_set,
893 .@"anyframe",
894 .frame,
895 .@"enum",
896 .vector,
897 => break, // terminal types (no sub-values)
898 .optional => break, // this can only be a pointer-like optional so is terminal
899 .array => {
900 const elem_ty = cur_ty.childType(zcu);
901 const elem_size = elem_ty.abiSize(zcu);
902 const elem_idx = cur_offset / elem_size;
903 const next_elem_off = elem_size * (elem_idx + 1);
904 if (cur_offset + need_bytes <= next_elem_off) {
905 // We can look at a single array element.
906 cur_val = try cur_val.elem(pt, sema.arena, @intCast(elem_idx));
907 cur_offset -= elem_idx * elem_size;
908 } else {
909 break;
910 }
911 },
912 .@"struct" => switch (cur_ty.containerLayout(zcu)) {
913 .auto => unreachable, // ill-defined layout
914 .@"packed" => break, // let the memory reinterp logic handle this
915 .@"extern" => for (0..cur_ty.structFieldCount(zcu)) |field_idx| {
916 const start_off = cur_ty.structFieldOffset(field_idx, zcu);
917 const end_off = start_off + cur_ty.fieldType(field_idx, zcu).abiSize(zcu);
918 if (cur_offset >= start_off and cur_offset + need_bytes <= end_off) {
919 cur_val = try cur_val.elem(pt, sema.arena, field_idx);
920 cur_offset -= start_off;
921 break;
922 }
923 } else break, // pointer spans multiple fields
924 },
925 .@"union" => switch (cur_ty.containerLayout(zcu)) {
926 .auto => unreachable, // ill-defined layout
927 .@"packed" => break, // let the memory reinterp logic handle this
928 .@"extern" => {
929 // TODO: we have to let the memory reinterp logic handle this for now.
930 // Otherwise, we might traverse into a union field which doesn't allow pointers.
931 // Figure out a solution!
932 if (true) break;
933 try cur_val.unintern(pt, sema.arena, false, false);
934 const payload = switch (cur_val.*) {
935 .un => |un| un.payload,
936 else => unreachable,
937 };
938 // The payload always has offset 0. If it's big enough
939 // to represent the whole load type, we can use it.
940 if (payload.typeOf(zcu).abiSize(zcu) >= need_bytes) {
941 cur_val = payload;
942 } else {
943 break;
944 }
945 },
946 },
947 }
948 }
949
950 // Fast path: check again if we're now at the type we want to store.
951 // If so, we can use the `direct` strategy.
952 if (cur_offset == 0 and cur_val.typeOf(zcu).toIntern() == store_ty.toIntern()) {
953 return .{ .direct = .{
954 .alloc = base_strat.alloc(),
955 .val = cur_val,
956 } };
957 }
958
959 return .{ .reinterpret = .{
960 .alloc = base_strat.alloc(),
961 .val = cur_val,
962 .byte_offset = cur_offset,
963 } };
964}
965
966/// Given a potentially-nested array value, recursively flatten all of its elements into the given
967/// output array. The result can be used by `unflattenArray` to restructure array values.
968fn flattenArray(
969 sema: *Sema,
970 val: MutableValue,
971 skip: *u64,
972 next_idx: *u64,
973 out: []InternPool.Index,
974) Allocator.Error!void {
975 if (next_idx.* == out.len) return;
976
977 const zcu = sema.pt.zcu;
978
979 const ty = val.typeOf(zcu);
980 const base_elem_count = ty.arrayBase(zcu)[1];
981 if (skip.* >= base_elem_count) {
982 skip.* -= base_elem_count;
983 return;
984 }
985
986 if (ty.zigTypeTag(zcu) != .array) {
987 out[@intCast(next_idx.*)] = (try val.intern(sema.pt, sema.arena)).toIntern();
988 next_idx.* += 1;
989 return;
990 }
991
992 const arr_base_elem_count = ty.childType(zcu).arrayBase(zcu)[1];
993 for (0..@intCast(ty.arrayLen(zcu))) |elem_idx| {
994 // Optimization: the `getElem` here may be expensive since we might intern an
995 // element of the `bytes` representation, so avoid doing it unnecessarily.
996 if (next_idx.* == out.len) return;
997 if (skip.* >= arr_base_elem_count) {
998 skip.* -= arr_base_elem_count;
999 continue;
1000 }
1001 try flattenArray(sema, try val.getElem(sema.pt, elem_idx), skip, next_idx, out);
1002 }
1003 if (ty.sentinel(zcu)) |s| {
1004 try flattenArray(sema, .{ .interned = s.toIntern() }, skip, next_idx, out);
1005 }
1006}
1007
1008/// Given a sequence of non-array elements, "unflatten" them into the given array type.
1009/// Asserts that values of `elems` are in-memory coercible to the array base type of `ty`.
1010fn unflattenArray(
1011 sema: *Sema,
1012 ty: Type,
1013 elems: []const InternPool.Index,
1014 next_idx: *u64,
1015) Allocator.Error!Value {
1016 const pt = sema.pt;
1017 const zcu = pt.zcu;
1018 const arena = sema.arena;
1019
1020 if (ty.zigTypeTag(zcu) != .array) {
1021 const val = Value.fromInterned(elems[@intCast(next_idx.*)]);
1022 next_idx.* += 1;
1023 return pt.getCoerced(val, ty);
1024 }
1025
1026 const elem_ty = ty.childType(zcu);
1027 const buf = try arena.alloc(InternPool.Index, @intCast(ty.arrayLen(zcu)));
1028 for (buf) |*elem| {
1029 elem.* = (try unflattenArray(sema, elem_ty, elems, next_idx)).toIntern();
1030 }
1031 if (ty.sentinel(zcu) != null) {
1032 // TODO: validate sentinel
1033 _ = try unflattenArray(sema, elem_ty, elems, next_idx);
1034 }
1035 return pt.aggregateValue(ty, buf);
1036}
1037
1038/// Given a `MutableValue` representing a potentially-nested array, treats `index` as an index into
1039/// the array's base type. For instance, given a [3][3]T, the index 5 represents 'val[1][2]'.
1040/// The final level of array is not dereferenced. This allows use sites to use `setElem` to prevent
1041/// unnecessary `MutableValue` representation changes.
1042fn recursiveIndex(
1043 sema: *Sema,
1044 mv: *MutableValue,
1045 index: *u64,
1046) !?struct { *MutableValue, u64 } {
1047 const pt = sema.pt;
1048
1049 const ty = mv.typeOf(pt.zcu);
1050 assert(ty.zigTypeTag(pt.zcu) == .array);
1051
1052 const ty_base_elems = ty.arrayBase(pt.zcu)[1];
1053 if (index.* >= ty_base_elems) {
1054 index.* -= ty_base_elems;
1055 return null;
1056 }
1057
1058 const elem_ty = ty.childType(pt.zcu);
1059 if (elem_ty.zigTypeTag(pt.zcu) != .array) {
1060 assert(index.* < ty.arrayLenIncludingSentinel(pt.zcu)); // should be handled by initial check
1061 return .{ mv, index.* };
1062 }
1063
1064 for (0..@intCast(ty.arrayLenIncludingSentinel(pt.zcu))) |elem_index| {
1065 if (try recursiveIndex(sema, try mv.elem(pt, sema.arena, elem_index), index)) |result| {
1066 return result;
1067 }
1068 }
1069 unreachable; // should be handled by initial check
1070}
1071
1072fn checkComptimeVarStore(
1073 sema: *Sema,
1074 block: *Block,
1075 src: LazySrcLoc,
1076 alloc_index: ComptimeAllocIndex,
1077) !void {
1078 const runtime_index = sema.getComptimeAlloc(alloc_index).runtime_index;
1079 if (@backingInt(runtime_index) < @backingInt(block.runtime_index)) {
1080 if (block.runtime_cond) |cond_src| {
1081 const msg = msg: {
1082 const msg = try sema.errMsg(src, "store to comptime variable depends on runtime condition", .{});
1083 errdefer msg.destroy(sema.gpa);
1084 try sema.errNote(cond_src, msg, "runtime condition here", .{});
1085 break :msg msg;
1086 };
1087 return sema.failWithOwnedErrorMsg(block, msg);
1088 }
1089 if (block.runtime_loop) |loop_src| {
1090 const msg = msg: {
1091 const msg = try sema.errMsg(src, "cannot store to comptime variable in non-inline loop", .{});
1092 errdefer msg.destroy(sema.gpa);
1093 try sema.errNote(loop_src, msg, "non-inline loop here", .{});
1094 break :msg msg;
1095 };
1096 return sema.failWithOwnedErrorMsg(block, msg);
1097 }
1098 unreachable;
1099 }
1100}
1101
1102const std = @import("std");
1103const assert = std.debug.assert;
1104const Allocator = std.mem.Allocator;
1105
1106const InternPool = @import("../InternPool.zig");
1107const ComptimeAllocIndex = InternPool.ComptimeAllocIndex;
1108const Sema = @import("../Sema.zig");
1109const Block = Sema.Block;
1110const MutableValue = @import("../mutable_value.zig").MutableValue;
1111const Type = @import("../Type.zig");
1112const Value = @import("../Value.zig");
1113const Zcu = @import("../Zcu.zig");
1114const LazySrcLoc = Zcu.LazySrcLoc;