1//! This file contains logic for bit-casting arbitrary values at comptime, including splicing
2//! bits together for comptime stores of bit-pointers. The strategy is to "flatten" values to
3//! a sequence of values in *packed* memory, and then unflatten through a combination of special
4//! cases (particularly for pointers and `undefined` values) and in-memory buffer reinterprets.
5//!
6//! This is a little awkward on big-endian targets, as non-packed datastructures (e.g. `extern struct`)
7//! have their fields reversed when represented as packed memory on such targets.
8
9/// If `host_bits` is `0`, attempts to convert the memory at offset
10/// `byte_offset` into `val` to a non-packed value of type `dest_ty`,
11/// ignoring `bit_offset`.
12///
13/// Otherwise, `byte_offset` is an offset in bytes into `val` to a
14/// non-packed value consisting of `host_bits` bits. A value of type
15/// `dest_ty` will be interpreted at a packed offset of `bit_offset`
16/// into this value.
17///
18/// Returns `null` if the operation must be performed at runtime.
19pub fn castMemory(
20 sema: *Sema,
21 val: Value,
22 dest_ty: Type,
23 byte_offset: u64,
24) CompileError!?Value {
25 const pt = sema.pt;
26 const zcu = pt.zcu;
27
28 const val_ty = val.typeOf(zcu);
29
30 if (dest_ty.toIntern() == val_ty.toIntern()) {
31 assert(byte_offset == 0);
32 return val;
33 }
34
35 val_ty.assertHasLayout(zcu);
36 dest_ty.assertHasLayout(zcu);
37
38 var unpack: UnpackValueBytes = .{
39 .pt = pt,
40 .arena = sema.arena,
41 .skip_bytes = byte_offset,
42 .remaining_bytes = dest_ty.abiSize(zcu),
43 .unpacked = .init(sema.arena),
44 };
45 unpack.add(val) catch |err| switch (err) {
46 error.ReinterpretDeclRef => return null,
47 error.OutOfMemory => |e| return e,
48 };
49
50 var pack: PackValueBytes = .{
51 .pt = pt,
52 .arena = sema.arena,
53 .unpacked = unpack.unpacked.items,
54 };
55 return pack.get(dest_ty) catch |err| switch (err) {
56 error.ReinterpretDeclRef => return null,
57 error.OutOfMemory => |e| return e,
58 };
59}
60
61/// Splice the value `splice_val` into `val` at the given `byte_offset`, replacing overlapping bits
62/// and returning the modified value.
63pub fn spliceMemory(
64 sema: *Sema,
65 val: Value,
66 splice_val: Value,
67 byte_offset: u64,
68) CompileError!?Value {
69 const pt = sema.pt;
70 const zcu = pt.zcu;
71 const val_ty = val.typeOf(zcu);
72 const splice_val_ty = splice_val.typeOf(zcu);
73
74 val_ty.assertHasLayout(zcu);
75 splice_val_ty.assertHasLayout(zcu);
76
77 var unpack: UnpackValueBytes = .{
78 .pt = pt,
79 .arena = sema.arena,
80 .skip_bytes = 0,
81 .remaining_bytes = byte_offset,
82 .unpacked = .init(sema.arena),
83 };
84 unpack.add(val) catch |err| switch (err) {
85 error.ReinterpretDeclRef => return null,
86 error.OutOfMemory => |e| return e,
87 };
88
89 const splice_len = splice_val_ty.abiSize(zcu);
90
91 unpack.remaining_bytes = splice_len;
92 unpack.add(splice_val) catch |err| switch (err) {
93 error.ReinterpretDeclRef => return null,
94 error.OutOfMemory => |e| return e,
95 };
96
97 unpack.skip_bytes = byte_offset + splice_len;
98 unpack.remaining_bytes = val_ty.abiSize(zcu) * 8 - byte_offset - splice_len;
99 unpack.add(val) catch |err| switch (err) {
100 error.ReinterpretDeclRef => return null,
101 error.OutOfMemory => |e| return e,
102 };
103
104 var pack: PackValueBytes = .{
105 .pt = pt,
106 .arena = sema.arena,
107 .unpacked = unpack.unpacked.items,
108 };
109 return pack.get(val_ty) catch |err| switch (err) {
110 error.ReinterpretDeclRef => return null,
111 error.OutOfMemory => |e| return e,
112 };
113}
114
115/// Recurses through struct fields, array elements, etc, to get a sequence of "primitive" values
116/// which are bit-packed in memory to represent a single value. `unpacked` represents a series
117/// of values in *packed* memory - therefore, on big-endian targets, the first element of this
118/// list contains bits from the *final* byte of the value.
119const UnpackValueBytes = struct {
120 pt: Zcu.PerThread,
121 arena: Allocator,
122 skip_bytes: u64,
123 remaining_bytes: u64,
124 unpacked: std.array_list.Managed(InternPool.Index),
125
126 fn add(unpack: *UnpackValueBytes, val: Value) (error{ReinterpretDeclRef} || Allocator.Error)!void {
127 const pt = unpack.pt;
128 const zcu = pt.zcu;
129 const ip = &zcu.intern_pool;
130
131 if (unpack.remaining_bytes == 0) {
132 return;
133 }
134
135 const ty = val.typeOf(zcu);
136 const size = ty.abiSize(zcu);
137
138 if (unpack.skip_bytes >= size) {
139 unpack.skip_bytes -= size;
140 return;
141 }
142
143 switch (ip.indexToKey(val.toIntern())) {
144 .int_type,
145 .ptr_type,
146 .array_type,
147 .vector_type,
148 .opt_type,
149 .anyframe_type,
150 .error_union_type,
151 .simple_type,
152 .struct_type,
153 .tuple_type,
154 .union_type,
155 .opaque_type,
156 .spirv_type,
157 .enum_type,
158 .func_type,
159 .error_set_type,
160 .inferred_error_set_type,
161 .@"extern",
162 .func,
163 .err,
164 .error_union,
165 .enum_literal,
166 .slice,
167 .memoized_call,
168 => unreachable, // ill-defined layout or not real values
169
170 .undef,
171 .int,
172 .enum_tag,
173 .simple_value,
174 .float,
175 .ptr,
176 .opt,
177 => try unpack.primitive(val),
178
179 .bitpack => |bitpack| try unpack.primitive(.fromInterned(bitpack.backing_int_val)),
180
181 .aggregate => switch (ty.zigTypeTag(zcu)) {
182 .vector => unreachable, // ill-defined layout
183 .array => {
184 for (0..@intCast(ty.arrayLen(zcu))) |elem_index| {
185 const elem_val = try val.elemValue(pt, @intCast(elem_index));
186 try unpack.add(elem_val);
187 }
188 if (ty.sentinel(zcu)) |s| {
189 try unpack.add(s);
190 }
191 },
192 .@"struct" => switch (ty.containerLayout(zcu)) {
193 .auto => unreachable, // ill-defined layout
194 .@"packed" => unreachable, // uses `.bitpack`, not `.aggregate`
195 .@"extern" => {
196 var it = ip.loadStructType(ty.toIntern()).iterateRuntimeOrder(ip);
197 var offset: u64 = 0;
198 while (it.next()) |field_index| {
199 const pad_bytes = ty.structFieldOffset(field_index, zcu) - offset;
200 const field_val = try val.fieldValue(pt, field_index);
201 try unpack.padding(pad_bytes);
202 try unpack.add(field_val);
203 offset += pad_bytes + field_val.typeOf(zcu).abiSize(zcu);
204 }
205 try unpack.padding(size - offset);
206 },
207 },
208 else => unreachable,
209 },
210
211 .un => |un| {
212 const payload_val = Value.fromInterned(un.val);
213 const pad_bytes = size - payload_val.typeOf(zcu).abiSize(zcu);
214 try unpack.add(payload_val);
215 try unpack.padding(pad_bytes);
216 },
217 }
218 }
219
220 fn padding(unpack: *UnpackValueBytes, num_bytes: u64) Allocator.Error!void {
221 if (num_bytes == 0) return;
222 const undef_u8 = try unpack.pt.undefValue(Type.u8);
223 for (0..@intCast(num_bytes)) |_| {
224 unpack.primitive(undef_u8) catch |err| switch (err) {
225 error.OutOfMemory => |e| return e,
226 error.ReinterpretDeclRef => unreachable,
227 };
228 }
229 }
230
231 fn primitive(unpack: *UnpackValueBytes, val: Value) (error{ReinterpretDeclRef} || Allocator.Error)!void {
232 const pt = unpack.pt;
233 const zcu = pt.zcu;
234
235 if (unpack.remaining_bytes == 0) {
236 return;
237 }
238
239 const ty = val.typeOf(pt.zcu);
240 const size = ty.abiSize(zcu);
241
242 if (unpack.skip_bytes >= size) {
243 unpack.skip_bytes -= size;
244 return;
245 }
246
247 if (unpack.skip_bytes > 0) {
248 const offset = unpack.skip_bytes;
249 unpack.skip_bytes = 0;
250 return unpack.splitPrimitive(val, offset, @min(size - offset, unpack.remaining_bytes));
251 }
252
253 if (unpack.remaining_bytes < size) {
254 return unpack.splitPrimitive(val, 0, unpack.remaining_bytes);
255 }
256
257 unpack.remaining_bytes -= size;
258 try unpack.unpacked.append(val.toIntern());
259 }
260
261 fn splitPrimitive(unpack: *UnpackValueBytes, val: Value, offset: u64, len: u64) (error{ReinterpretDeclRef} || Allocator.Error)!void {
262 const pt = unpack.pt;
263 const zcu = pt.zcu;
264 const ty = val.typeOf(pt.zcu);
265
266 assert(offset + len <= ty.abiSize(zcu));
267
268 try unpack.unpacked.ensureUnusedCapacity(@intCast(len));
269 unpack.remaining_bytes -= len;
270
271 switch (pt.zcu.intern_pool.indexToKey(val.toIntern())) {
272 // In the `ptr` case, this will return `error.ReinterpretDeclRef`
273 // if we're trying to split a non-integer pointer value.
274 .int, .float, .enum_tag, .ptr, .opt => {
275 const buf = try unpack.arena.alloc(u8, @intCast(ty.abiSize(zcu)));
276 val.writeToMemory(zcu, buf) catch |err| switch (err) {
277 error.IllDefinedMemoryLayout => unreachable,
278 else => |e| return e,
279 };
280 for (buf[@intCast(offset)..][0..@intCast(len)]) |byte_raw| {
281 const byte_val = try pt.intValue(.u8, byte_raw);
282 unpack.unpacked.appendAssumeCapacity(byte_val.toIntern());
283 }
284 },
285 .undef => {
286 const undef_u8 = try pt.undefValue(.u8);
287 for (0..@intCast(len)) |_| {
288 unpack.unpacked.appendAssumeCapacity(undef_u8.toIntern());
289 }
290 },
291 // The only values here with runtime bits are `true` and `false`.
292 // These are both 1 byte, so will never need splitting.
293 .simple_value => unreachable,
294 else => unreachable, // zero-bit or not primitives
295 }
296 }
297};
298
299/// Given a sequence of bit-packed values in packed memory (see `UnpackValueBytes`),
300/// reconstructs a value of an arbitrary type, with correct handling of `undefined`
301/// values and of pointers which align in virtual memory.
302const PackValueBytes = struct {
303 pt: Zcu.PerThread,
304 arena: Allocator,
305 byte_offset: u64 = 0,
306 unpacked: []const InternPool.Index,
307
308 fn get(pack: *PackValueBytes, ty: Type) (Allocator.Error || error{ReinterpretDeclRef})!Value {
309 const pt = pack.pt;
310 const zcu = pt.zcu;
311 const ip = &zcu.intern_pool;
312 const arena = pack.arena;
313 switch (ty.zigTypeTag(zcu)) {
314 .vector => unreachable, // ill-defined layout
315 .array => {
316 // Each element is padded up to its ABI size. The final element does not have trailing padding.
317 const elem_ty = ty.childType(zcu);
318 const elems = try arena.alloc(InternPool.Index, @intCast(ty.arrayLen(zcu)));
319
320 for (elems) |*elem| {
321 elem.* = (try pack.get(elem_ty)).toIntern();
322 }
323
324 if (ty.sentinel(zcu)) |s| {
325 _ = s; // TODO: validate sentinel was preserved!
326 pack.padding(elem_ty.abiSize(zcu));
327 }
328
329 return pt.aggregateValue(ty, elems);
330 },
331 .@"struct" => switch (ty.containerLayout(zcu)) {
332 .auto => unreachable, // ill-defined layout
333 .@"extern" => {
334 const elems = try arena.alloc(InternPool.Index, ty.structFieldCount(zcu));
335 @memset(elems, .none);
336 var offset: u64 = 0;
337 var it = ip.loadStructType(ty.toIntern()).iterateRuntimeOrder(ip);
338 while (it.next()) |field_index| {
339 const field_ty = ty.fieldType(field_index, zcu);
340 const pad_bytes = ty.structFieldOffset(field_index, zcu) - offset;
341 pack.padding(pad_bytes);
342 elems[field_index] = (try pack.get(field_ty)).toIntern();
343 offset += pad_bytes + field_ty.abiSize(zcu);
344 }
345 pack.padding(ty.abiSize(zcu) - offset);
346 // Any fields which do not have runtime bits should be OPV or comptime fields.
347 // Fill those values now.
348 for (elems, 0..) |*elem, field_index| {
349 if (elem.* != .none) continue;
350 const val = (try ty.structFieldValueComptime(pt, field_index)).?;
351 elem.* = val.toIntern();
352 }
353 return pt.aggregateValue(ty, elems);
354 },
355 .@"packed" => {
356 const backing_int_val = try pack.primitive(ty.backingIntType(zcu));
357 if (backing_int_val.isUndef(zcu)) return pt.undefValue(ty);
358 return pt.bitpackValue(ty, backing_int_val);
359 },
360 },
361 .@"union" => switch (ty.containerLayout(zcu)) {
362 .auto => unreachable, // ill-defined layout
363 .@"extern" => {
364 // We will attempt to read as the backing representation. If this emits
365 // `error.ReinterpretDeclRef`, we will try each union field, preferring larger ones.
366 // We will also attempt smaller fields when we get `undefined`, as if some bits are
367 // defined we want to include them.
368 // TODO: this is very very bad. We need a more sophisticated union representation.
369
370 const prev_unpacked = pack.unpacked;
371 const prev_byte_offset = pack.byte_offset;
372
373 const backing_ty = try ty.externUnionBackingType(pt);
374
375 const backing_result: enum { undef, reinterpret_decl_ref } = backing: {
376 const backing_val = pack.get(backing_ty) catch |err| switch (err) {
377 error.ReinterpretDeclRef => break :backing .reinterpret_decl_ref,
378 else => |e| return e,
379 };
380 if (backing_val.isUndef(zcu)) break :backing .undef;
381 return .fromInterned(try pt.internUnion(.{
382 .ty = ty.toIntern(),
383 .tag = .none,
384 .val = backing_val.toIntern(),
385 }));
386 };
387
388 const field_order = try pack.arena.alloc(u32, ty.unionTagTypeHypothetical(zcu).enumFieldCount(zcu));
389 for (field_order, 0..) |*f, i| f.* = @intCast(i);
390 // Sort `field_order` to put the fields with the largest ABI sizes first.
391 const SizeSortCtx = struct {
392 zcu: *const Zcu,
393 field_types: []const InternPool.Index,
394 fn lessThan(ctx: @This(), a_idx: u32, b_idx: u32) bool {
395 const a_ty: Type = .fromInterned(ctx.field_types[a_idx]);
396 const b_ty: Type = .fromInterned(ctx.field_types[b_idx]);
397 return a_ty.abiSize(ctx.zcu) > b_ty.abiSize(ctx.zcu);
398 }
399 };
400 std.mem.sortUnstable(u32, field_order, SizeSortCtx{
401 .zcu = zcu,
402 .field_types = zcu.typeToUnion(ty).?.field_types.get(ip),
403 }, SizeSortCtx.lessThan);
404
405 for (field_order) |field_index| {
406 pack.unpacked = prev_unpacked;
407 pack.byte_offset = prev_byte_offset;
408 const field_ty = ty.fieldType(field_index, zcu);
409 const field_val = pack.get(field_ty) catch |err| switch (err) {
410 error.ReinterpretDeclRef => continue,
411 else => |e| return e,
412 };
413 if (field_val.isUndef(zcu)) continue;
414 pack.padding(ty.abiSize(zcu) - field_ty.abiSize(zcu));
415 const tag_val = try pt.enumValueFieldIndex(ty.unionTagTypeHypothetical(zcu), field_index);
416 return pt.unionValue(ty, tag_val, field_val);
417 }
418
419 // No field could represent the value. Just do whatever happens when we try to read
420 // the backing type - either `undefined` or `error.ReinterpretDeclRef`.
421 switch (backing_result) {
422 .undef => return pt.undefValue(ty),
423 .reinterpret_decl_ref => return error.ReinterpretDeclRef,
424 }
425 },
426 .@"packed" => {
427 const backing_int_val = try pack.primitive(ty.backingIntType(zcu));
428 if (backing_int_val.isUndef(zcu)) return pt.undefValue(ty);
429 return pt.bitpackValue(ty, backing_int_val);
430 },
431 },
432 .@"enum" => {
433 const tag_int_val = try pack.primitive(ty.backingIntType(zcu));
434 if (tag_int_val.isUndef(zcu)) return pt.undefValue(ty);
435 return pt.enumValue(ty, tag_int_val);
436 },
437 else => return pack.primitive(ty),
438 }
439 }
440
441 fn padding(pack: *PackValueBytes, num_bytes: u64) void {
442 _ = pack.prepareBytes(num_bytes);
443 }
444
445 fn primitive(pack: *PackValueBytes, want_ty: Type) (Allocator.Error || error{ReinterpretDeclRef})!Value {
446 const pt = pack.pt;
447 const zcu = pt.zcu;
448
449 if (try want_ty.onePossibleValue(pt)) |opv| return opv;
450
451 const vals, const byte_offset = pack.prepareBytes(want_ty.abiSize(zcu));
452
453 for (vals) |val| {
454 if (!Value.fromInterned(val).isUndef(zcu)) break;
455 } else {
456 // All bits of the value are `undefined`.
457 return pt.undefValue(want_ty);
458 }
459
460 // TODO: we need to decide how to handle partially-undef values here.
461 // Currently, a value with some undefined bits becomes `0xAA` so that we
462 // preserve the well-defined bits, because we can't currently represent
463 // a partially-undefined primitive (e.g. an int with some undef bits).
464 // In future, we probably want to take one of these two routes:
465 // * Define that if any bits are `undefined`, the entire value is `undefined`.
466 // This is a major breaking change, and probably a footgun.
467 // * Introduce tracking for partially-undef values at comptime.
468 // This would complicate a lot of operations in Sema, such as basic
469 // arithmetic.
470 // This design complexity is tracked by #19634.
471
472 if (vals.len == 1 and
473 want_ty.isPtrAtRuntime(zcu) and
474 Value.fromInterned(vals[0]).typeOf(zcu).isPtrAtRuntime(zcu))
475 {
476 return pt.getCoerced(.fromInterned(vals[0]), want_ty);
477 }
478
479 // Reinterpret via an in-memory buffer.
480
481 var buf_len: u64 = 0;
482 for (vals) |ip_val| {
483 const val: Value = .fromInterned(ip_val);
484 buf_len += val.typeOf(zcu).abiSize(zcu);
485 }
486
487 const buf = try pack.arena.alloc(u8, @intCast(buf_len));
488 {
489 var offset: usize = 0;
490 for (vals) |ip_val| {
491 const val: Value = .fromInterned(ip_val);
492 const ty = val.typeOf(zcu);
493 const size = ty.abiSize(zcu);
494 if (val.isUndef(zcu)) {
495 @memset(buf[offset..][0..@intCast(size)], 0xAA);
496 } else {
497 val.writeToMemory(zcu, buf[offset..][0..@intCast(size)]) catch |err| switch (err) {
498 error.IllDefinedMemoryLayout => unreachable,
499 else => |e| return e,
500 };
501 }
502 offset += @intCast(size);
503 }
504 }
505 const bytes = buf[@intCast(byte_offset)..];
506
507 const target = zcu.getTarget();
508 const endian = target.cpu.arch.endian();
509 switch (want_ty.zigTypeTag(zcu)) {
510 .bool => return .makeBool(bytes[0] != 0),
511 .int => return .readIntFromMemory(want_ty, pt, bytes, pack.arena),
512 .float => switch (want_ty.floatBits(target)) {
513 16 => return pt.floatValue(want_ty, @as(f16, @bitCast(std.mem.readInt(u16, bytes[0..2], endian)))),
514 32 => return pt.floatValue(want_ty, @as(f32, @bitCast(std.mem.readInt(u32, bytes[0..4], endian)))),
515 64 => return pt.floatValue(want_ty, @as(f64, @bitCast(std.mem.readInt(u64, bytes[0..8], endian)))),
516 80 => return pt.floatValue(want_ty, @as(f80, @bitCast(std.mem.readInt(u80, bytes[0..10], endian)))),
517 128 => return pt.floatValue(want_ty, @as(f128, @bitCast(std.mem.readInt(u128, bytes[0..16], endian)))),
518 else => unreachable,
519 },
520 .pointer => {
521 assert(!want_ty.isSlice(zcu));
522 const ptr_addr = std.mem.readVarInt(u64, bytes[0..@intCast(want_ty.abiSize(zcu))], endian);
523 return pt.ptrIntValue(want_ty, ptr_addr);
524 },
525 .optional => {
526 assert(want_ty.isPtrLikeOptional(zcu));
527 const ptr_ty = want_ty.optionalChild(zcu);
528 const ptr_addr = std.mem.readVarInt(u64, bytes[0..@intCast(want_ty.abiSize(zcu))], endian);
529 return .fromInterned(try pt.intern(.{ .opt = .{
530 .ty = want_ty.toIntern(),
531 .val = if (ptr_addr == 0) .none else (try pt.ptrIntValue(ptr_ty, ptr_addr)).toIntern(),
532 } }));
533 },
534 else => unreachable,
535 }
536 }
537
538 fn prepareBytes(pack: *PackValueBytes, need_bytes: u64) struct { []const InternPool.Index, u64 } {
539 if (need_bytes == 0) return .{ &.{}, 0 };
540
541 const pt = pack.pt;
542 const zcu = pt.zcu;
543
544 var bytes: u64 = 0;
545 var len: usize = 0;
546 while (bytes < pack.byte_offset + need_bytes) {
547 bytes += Value.fromInterned(pack.unpacked[len]).typeOf(zcu).abiSize(zcu);
548 len += 1;
549 }
550
551 const result_vals = pack.unpacked[0..len];
552 const result_offset = pack.byte_offset;
553
554 const extra_bytes = bytes - pack.byte_offset - need_bytes;
555 if (extra_bytes == 0) {
556 pack.unpacked = pack.unpacked[len..];
557 pack.byte_offset = 0;
558 } else {
559 pack.unpacked = pack.unpacked[len - 1 ..];
560 pack.byte_offset = Value.fromInterned(pack.unpacked[0]).typeOf(zcu).abiSize(zcu) - extra_bytes;
561 }
562
563 return .{ result_vals, result_offset };
564 }
565};
566
567const std = @import("std");
568const Allocator = std.mem.Allocator;
569const assert = std.debug.assert;
570
571const Sema = @import("../Sema.zig");
572const Zcu = @import("../Zcu.zig");
573const InternPool = @import("../InternPool.zig");
574const Type = @import("../Type.zig");
575const Value = @import("../Value.zig");
576const CompileError = Zcu.CompileError;