master
1const std = @import("std");
2const builtin = @import("builtin");
3const endian = builtin.cpu.arch.endian();
4const testing = @import("std").testing;
5const ptr_size = @sizeOf(usize);
6
7test "type pun signed and unsigned as single pointer" {
8 comptime {
9 var x: u32 = 0;
10 const y = @as(*i32, @ptrCast(&x));
11 y.* = -1;
12 try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
13 }
14}
15
16test "type pun signed and unsigned as many pointer" {
17 comptime {
18 var x: u32 = 0;
19 const y = @as([*]i32, @ptrCast(&x));
20 y[0] = -1;
21 try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
22 }
23}
24
25test "type pun signed and unsigned as array pointer" {
26 comptime {
27 var x: u32 = 0;
28 const y = @as(*[1]i32, @ptrCast(&x));
29 y[0] = -1;
30 try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
31 }
32}
33
34test "type pun signed and unsigned as offset many pointer" {
35 comptime {
36 var x: [11]u32 = undefined;
37 var y: [*]i32 = @ptrCast(&x[10]);
38 y -= 10;
39 y[10] = -1;
40 try testing.expectEqual(@as(u32, 0xFFFFFFFF), x[10]);
41 }
42}
43
44test "type pun signed and unsigned as array pointer with pointer arithemtic" {
45 comptime {
46 var x: [11]u32 = undefined;
47 const y = @as([*]i32, @ptrCast(&x[10])) - 10;
48 const z: *[15]i32 = y[0..15];
49 z[10] = -1;
50 try testing.expectEqual(@as(u32, 0xFFFFFFFF), x[10]);
51 }
52}
53
54test "type pun value and struct" {
55 comptime {
56 const StructOfU32 = extern struct { x: u32 };
57 var inst: StructOfU32 = .{ .x = 0 };
58 @as(*i32, @ptrCast(&inst.x)).* = -1;
59 try testing.expectEqual(@as(u32, 0xFFFFFFFF), inst.x);
60 @as(*i32, @ptrCast(&inst)).* = -2;
61 try testing.expectEqual(@as(u32, 0xFFFFFFFE), inst.x);
62 }
63}
64
65fn bigToNativeEndian(comptime T: type, v: T) T {
66 return if (endian == .big) v else @byteSwap(v);
67}
68test "type pun endianness" {
69 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
70
71 comptime {
72 const StructOfBytes = extern struct { x: [4]u8 };
73 var inst: StructOfBytes = .{ .x = [4]u8{ 0, 0, 0, 0 } };
74 const structPtr = @as(*align(1) u32, @ptrCast(&inst));
75 const arrayPtr = @as(*align(1) u32, @ptrCast(&inst.x));
76 inst.x[0] = 0xFE;
77 inst.x[2] = 0xBE;
78 try testing.expectEqual(bigToNativeEndian(u32, 0xFE00BE00), structPtr.*);
79 try testing.expectEqual(bigToNativeEndian(u32, 0xFE00BE00), arrayPtr.*);
80 structPtr.* = bigToNativeEndian(u32, 0xDEADF00D);
81 try testing.expectEqual(bigToNativeEndian(u32, 0xDEADF00D), structPtr.*);
82 try testing.expectEqual(bigToNativeEndian(u32, 0xDEADF00D), arrayPtr.*);
83 try testing.expectEqual(@as(u8, 0xDE), inst.x[0]);
84 try testing.expectEqual(@as(u8, 0xAD), inst.x[1]);
85 try testing.expectEqual(@as(u8, 0xF0), inst.x[2]);
86 try testing.expectEqual(@as(u8, 0x0D), inst.x[3]);
87 }
88}
89
90const Bits = packed struct {
91 // Note: This struct has only single byte words so it
92 // doesn't need to be byte swapped.
93 p0: u1,
94 p1: u4,
95 p2: u3,
96 p3: u2,
97 p4: u6,
98 p5: u8,
99 p6: u7,
100 p7: u1,
101};
102const ShuffledBits = packed struct {
103 p1: u4,
104 p3: u2,
105 p7: u1,
106 p0: u1,
107 p5: u8,
108 p2: u3,
109 p6: u7,
110 p4: u6,
111};
112fn shuffle(ptr: usize, comptime From: type, comptime To: type) usize {
113 if (@sizeOf(From) != @sizeOf(To))
114 @compileError("Mismatched sizes! " ++ @typeName(From) ++ " and " ++ @typeName(To) ++ " must have the same size!");
115 const array_len = @divExact(ptr_size, @sizeOf(From));
116 var result: usize = 0;
117 const pSource = @as(*align(1) const [array_len]From, @ptrCast(&ptr));
118 const pResult = @as(*align(1) [array_len]To, @ptrCast(&result));
119 var i: usize = 0;
120 while (i < array_len) : (i += 1) {
121 inline for (@typeInfo(To).@"struct".fields) |f| {
122 @field(pResult[i], f.name) = @field(pSource[i], f.name);
123 }
124 }
125 return result;
126}
127
128fn doTypePunBitsTest(as_bits: *Bits) !void {
129 const as_u32 = @as(*align(1) u32, @ptrCast(as_bits));
130 const as_bytes = @as(*[4]u8, @ptrCast(as_bits));
131 as_u32.* = bigToNativeEndian(u32, 0xB0A7DEED);
132 try testing.expectEqual(@as(u1, 0x00), as_bits.p0);
133 try testing.expectEqual(@as(u4, 0x08), as_bits.p1);
134 try testing.expectEqual(@as(u3, 0x05), as_bits.p2);
135 try testing.expectEqual(@as(u2, 0x03), as_bits.p3);
136 try testing.expectEqual(@as(u6, 0x29), as_bits.p4);
137 try testing.expectEqual(@as(u8, 0xDE), as_bits.p5);
138 try testing.expectEqual(@as(u7, 0x6D), as_bits.p6);
139 try testing.expectEqual(@as(u1, 0x01), as_bits.p7);
140
141 as_bits.p6 = 0x2D;
142 as_bits.p1 = 0x0F;
143 try testing.expectEqual(bigToNativeEndian(u32, 0xBEA7DEAD), as_u32.*);
144
145 // clobbering one bit doesn't clobber the word
146 as_bits.p7 = undefined;
147 try testing.expectEqual(@as(u7, 0x2D), as_bits.p6);
148 // even when read as a whole
149 const u = as_u32.*;
150 _ = u; // u is undefined
151 try testing.expectEqual(@as(u7, 0x2D), as_bits.p6);
152 // or if a field which shares the byte is modified
153 as_bits.p6 = 0x6D;
154 try testing.expectEqual(@as(u7, 0x6D), as_bits.p6);
155
156 // but overwriting the undefined will clear it
157 as_bytes[3] = 0xAF;
158 try testing.expectEqual(bigToNativeEndian(u32, 0xBEA7DEAF), as_u32.*);
159}
160
161test "type pun bits" {
162 if (true) {
163 // TODO: currently, marking one bit of `Bits` as `undefined` does
164 // mark the whole value as `undefined`, since the pointer interpretation
165 // logic reads it back in as a `u32`, which is partially-undef and thus
166 // has value `undefined`. We need an improved comptime memory representation
167 // to make this work.
168 return error.SkipZigTest;
169 }
170 comptime {
171 var v: u32 = undefined;
172 try doTypePunBitsTest(@as(*Bits, @ptrCast(&v)));
173 }
174}
175
176const imports = struct {
177 var global_u32: u32 = 0;
178};
179
180// Make sure lazy values work on their own, before getting into more complex tests
181test "basic pointer preservation" {
182 if (true) {
183 // TODO https://github.com/ziglang/zig/issues/9646
184 return error.SkipZigTest;
185 }
186
187 comptime {
188 const lazy_address = @intFromPtr(&imports.global_u32);
189 try testing.expectEqual(@intFromPtr(&imports.global_u32), lazy_address);
190 try testing.expectEqual(&imports.global_u32, @as(*u32, @ptrFromInt(lazy_address)));
191 }
192}
193
194test "byte copy preserves linker value" {
195 if (true) {
196 // TODO https://github.com/ziglang/zig/issues/9646
197 return error.SkipZigTest;
198 }
199
200 const ct_value = comptime blk: {
201 const lazy = &imports.global_u32;
202 var result: *u32 = undefined;
203 const pSource = @as(*const [ptr_size]u8, @ptrCast(&lazy));
204 const pResult = @as(*[ptr_size]u8, @ptrCast(&result));
205 var i: usize = 0;
206 while (i < ptr_size) : (i += 1) {
207 pResult[i] = pSource[i];
208 try testing.expectEqual(pSource[i], pResult[i]);
209 }
210 try testing.expectEqual(&imports.global_u32, result);
211 break :blk result;
212 };
213
214 try testing.expectEqual(&imports.global_u32, ct_value);
215}
216
217test "unordered byte copy preserves linker value" {
218 if (true) {
219 // TODO https://github.com/ziglang/zig/issues/9646
220 return error.SkipZigTest;
221 }
222
223 const ct_value = comptime blk: {
224 const lazy = &imports.global_u32;
225 var result: *u32 = undefined;
226 const pSource = @as(*const [ptr_size]u8, @ptrCast(&lazy));
227 const pResult = @as(*[ptr_size]u8, @ptrCast(&result));
228 if (ptr_size > 8) @compileError("This array needs to be expanded for platform with very big pointers");
229 const shuffled_indices = [_]usize{ 4, 5, 2, 6, 1, 3, 0, 7 };
230 for (shuffled_indices) |i| {
231 pResult[i] = pSource[i];
232 try testing.expectEqual(pSource[i], pResult[i]);
233 }
234 try testing.expectEqual(&imports.global_u32, result);
235 break :blk result;
236 };
237
238 try testing.expectEqual(&imports.global_u32, ct_value);
239}
240
241test "shuffle chunks of linker value" {
242 if (true) {
243 // TODO https://github.com/ziglang/zig/issues/9646
244 return error.SkipZigTest;
245 }
246
247 const lazy_address = @intFromPtr(&imports.global_u32);
248 const shuffled1_rt = shuffle(lazy_address, Bits, ShuffledBits);
249 const unshuffled1_rt = shuffle(shuffled1_rt, ShuffledBits, Bits);
250 try testing.expectEqual(lazy_address, unshuffled1_rt);
251 const shuffled1_ct = comptime shuffle(lazy_address, Bits, ShuffledBits);
252 const shuffled1_ct_2 = comptime shuffle(lazy_address, Bits, ShuffledBits);
253 try comptime testing.expectEqual(shuffled1_ct, shuffled1_ct_2);
254 const unshuffled1_ct = comptime shuffle(shuffled1_ct, ShuffledBits, Bits);
255 try comptime testing.expectEqual(lazy_address, unshuffled1_ct);
256 try testing.expectEqual(shuffled1_ct, shuffled1_rt);
257}
258
259test "dance on linker values" {
260 if (true) {
261 // TODO https://github.com/ziglang/zig/issues/9646
262 return error.SkipZigTest;
263 }
264
265 comptime {
266 var arr: [2]usize = undefined;
267 arr[0] = @intFromPtr(&imports.global_u32);
268 arr[1] = @intFromPtr(&imports.global_u32);
269
270 const weird_ptr = @as([*]Bits, @ptrCast(@as([*]u8, @ptrCast(&arr)) + @sizeOf(usize) - 3));
271 try doTypePunBitsTest(&weird_ptr[0]);
272 if (ptr_size > @sizeOf(Bits))
273 try doTypePunBitsTest(&weird_ptr[1]);
274
275 const arr_bytes: *[2][ptr_size]u8 = @ptrCast(&arr);
276
277 var rebuilt_bytes: [ptr_size]u8 = undefined;
278 var i: usize = 0;
279 while (i < ptr_size - 3) : (i += 1) {
280 rebuilt_bytes[i] = arr_bytes[0][i];
281 }
282 while (i < ptr_size) : (i += 1) {
283 rebuilt_bytes[i] = arr_bytes[1][i];
284 }
285
286 try testing.expectEqual(&imports.global_u32, @as(*u32, @ptrFromInt(@as(usize, @bitCast(rebuilt_bytes)))));
287 }
288}
289
290test "offset array ptr by element size" {
291 comptime {
292 const VirtualStruct = struct { x: u32 };
293 var arr: [4]VirtualStruct = .{
294 .{ .x = bigToNativeEndian(u32, 0x0004080c) },
295 .{ .x = bigToNativeEndian(u32, 0x0105090d) },
296 .{ .x = bigToNativeEndian(u32, 0x02060a0e) },
297 .{ .x = bigToNativeEndian(u32, 0x03070b0f) },
298 };
299
300 const buf: [*]align(@alignOf(VirtualStruct)) u8 = @ptrCast(&arr);
301
302 const second_element: *VirtualStruct = @ptrCast(buf + 2 * @sizeOf(VirtualStruct));
303 try testing.expectEqual(bigToNativeEndian(u32, 0x02060a0e), second_element.x);
304 }
305}
306
307test "offset instance by field size" {
308 if (true) {
309 // TODO https://github.com/ziglang/zig/issues/9646
310 return error.SkipZigTest;
311 }
312
313 comptime {
314 const VirtualStruct = struct { x: u32, y: u32, z: u32, w: u32 };
315 var inst = VirtualStruct{ .x = 0, .y = 1, .z = 2, .w = 3 };
316
317 var ptr = @intFromPtr(&inst);
318 ptr -= 4;
319 ptr += @offsetOf(VirtualStruct, "x");
320 try testing.expectEqual(@as(u32, 0), @as([*]u32, @ptrFromInt(ptr))[1]);
321 ptr -= @offsetOf(VirtualStruct, "x");
322 ptr += @offsetOf(VirtualStruct, "y");
323 try testing.expectEqual(@as(u32, 1), @as([*]u32, @ptrFromInt(ptr))[1]);
324 ptr = ptr - @offsetOf(VirtualStruct, "y") + @offsetOf(VirtualStruct, "z");
325 try testing.expectEqual(@as(u32, 2), @as([*]u32, @ptrFromInt(ptr))[1]);
326 ptr = @intFromPtr(&inst.z) - 4 - @offsetOf(VirtualStruct, "z");
327 ptr += @offsetOf(VirtualStruct, "w");
328 try testing.expectEqual(@as(u32, 3), @as(*u32, @ptrFromInt(ptr + 4)).*);
329 }
330}
331
332test "offset field ptr by enclosing array element size" {
333 if (true) {
334 // TODO https://github.com/ziglang/zig/issues/9646
335 return error.SkipZigTest;
336 }
337
338 comptime {
339 const VirtualStruct = struct { x: u32 };
340 var arr: [4]VirtualStruct = .{
341 .{ .x = bigToNativeEndian(u32, 0x0004080c) },
342 .{ .x = bigToNativeEndian(u32, 0x0105090d) },
343 .{ .x = bigToNativeEndian(u32, 0x02060a0e) },
344 .{ .x = bigToNativeEndian(u32, 0x03070b0f) },
345 };
346
347 var i: usize = 0;
348 while (i < 4) : (i += 1) {
349 var ptr: [*]u8 = @ptrCast(&arr[0]);
350 ptr += i;
351 ptr += @offsetOf(VirtualStruct, "x");
352 var j: usize = 0;
353 while (j < 4) : (j += 1) {
354 const base = ptr + j * @sizeOf(VirtualStruct);
355 try testing.expectEqual(@as(u8, @intCast(i * 4 + j)), base[0]);
356 }
357 }
358 }
359}
360
361test "accessing reinterpreted memory of parent object" {
362 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
363
364 const S = extern struct {
365 a: f32,
366 b: [4]u8,
367 c: f32,
368 };
369 const expected = if (endian == .little) 102 else 38;
370
371 comptime {
372 const x = S{
373 .a = 1.5,
374 .b = [_]u8{ 1, 2, 3, 4 },
375 .c = 2.6,
376 };
377 const ptr = &x.b[0];
378 const b = @as([*c]const u8, @ptrCast(ptr))[5];
379 try testing.expect(b == expected);
380 }
381}
382
383test "bitcast packed union to integer" {
384 const U = packed union {
385 x: i2,
386 y: u2,
387 };
388
389 comptime {
390 const a: U = .{ .x = -1 };
391 const b: U = .{ .y = 2 };
392 const cast_a: u2 = @bitCast(a);
393 const cast_b: u2 = @bitCast(b);
394
395 try testing.expectEqual(@as(u2, 3), cast_a);
396 try testing.expectEqual(@as(u2, 2), cast_b);
397 }
398}
399
400test "mutate entire slice at comptime" {
401 comptime {
402 var buf: [3]u8 = undefined;
403 const x: [2]u8 = .{ 1, 2 }; // Avoid RLS
404 buf[1..3].* = x;
405 }
406}
407
408test "dereference undefined pointer to zero-bit type" {
409 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
410
411 const p0: *void = undefined;
412 try testing.expectEqual({}, p0.*);
413
414 const p1: *[0]u32 = undefined;
415 try testing.expect(p1.*.len == 0);
416}
417
418test "type pun extern struct" {
419 const S = extern struct { f: u8 };
420 comptime var s = S{ .f = 123 };
421 @as(*u8, @ptrCast(&s)).* = 72;
422 try testing.expectEqual(@as(u8, 72), s.f);
423}
424
425test "type pun @ptrFromInt" {
426 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
427
428 const p: *u8 = @ptrFromInt(42);
429 // note that expectEqual hides the bug
430 try testing.expect(@as(*const [*]u8, @ptrCast(&p)).* == @as([*]u8, @ptrFromInt(42)));
431}
432
433test "type pun null pointer-like optional" {
434 const p: ?*u8 = null;
435 // note that expectEqual hides the bug
436 try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null);
437}
438
439test "write empty array to end" {
440 comptime var array: [5]u8 = "hello".*;
441 array[5..5].* = .{};
442 array[5..5].* = [0]u8{};
443 array[5..5].* = [_]u8{};
444 comptime std.debug.assert(std.mem.eql(u8, "hello", &array));
445}
446
447fn doublePtrTest() !void {
448 var a: u32 = 0;
449 const ptr = &a;
450 const double_ptr = &ptr;
451 setDoublePtr(double_ptr, 1);
452 setDoublePtr(double_ptr, 2);
453 setDoublePtr(double_ptr, 1);
454 try std.testing.expect(a == 1);
455}
456fn setDoublePtr(ptr: *const *const u32, value: u32) void {
457 setPtr(ptr.*, value);
458}
459fn setPtr(ptr: *const u32, value: u32) void {
460 const mut_ptr: *u32 = @constCast(ptr);
461 mut_ptr.* = value;
462}
463test "double pointer can mutate comptime state" {
464 try comptime doublePtrTest();
465}
466
467fn GenericIntApplier(
468 comptime Context: type,
469 comptime applyFn: fn (context: Context, arg: u32) void,
470) type {
471 return struct {
472 context: Context,
473
474 const Self = @This();
475
476 inline fn any(self: *const Self) IntApplier {
477 return .{
478 .context = @ptrCast(&self.context),
479 .applyFn = typeErasedApplyFn,
480 };
481 }
482
483 fn typeErasedApplyFn(context: *const anyopaque, arg: u32) void {
484 const ptr: *const Context = @ptrCast(@alignCast(context));
485 applyFn(ptr.*, arg);
486 }
487 };
488}
489const IntApplier = struct {
490 context: *const anyopaque,
491 applyFn: *const fn (context: *const anyopaque, arg: u32) void,
492
493 fn apply(ia: IntApplier, arg: u32) void {
494 ia.applyFn(ia.context, arg);
495 }
496};
497const Accumulator = struct {
498 value: u32,
499
500 const Applier = GenericIntApplier(*u32, add);
501
502 fn applier(a: *Accumulator) Applier {
503 return .{ .context = &a.value };
504 }
505
506 fn add(context: *u32, arg: u32) void {
507 context.* += arg;
508 }
509};
510fn fieldPtrTest() u32 {
511 var a: Accumulator = .{ .value = 0 };
512 const applier = a.applier();
513 applier.any().apply(1);
514 applier.any().apply(1);
515 return a.value;
516}
517test "pointer in aggregate field can mutate comptime state" {
518 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
519
520 try comptime std.testing.expect(fieldPtrTest() == 2);
521}
522
523test "comptime store of extern struct with void field" {
524 comptime {
525 var x: extern struct { a: u8, b: void } = undefined;
526 x = .{ .a = 123, .b = {} };
527 std.debug.assert(x.a == 123);
528 }
529}
530
531test "comptime store of extern struct with void field into array" {
532 comptime {
533 var x: [3]extern struct { a: u8, b: void } = undefined;
534 x[1] = .{ .a = 123, .b = {} };
535 std.debug.assert(x[1].a == 123);
536 }
537}
538
539test "comptime store of packed struct with void field" {
540 comptime {
541 var x: packed struct { a: u8, b: void } = undefined;
542 x = .{ .a = 123, .b = {} };
543 std.debug.assert(x.a == 123);
544 }
545}
546
547test "comptime store of packed struct with void field into array" {
548 comptime {
549 var x: [3]packed struct { a: u8, b: void } = undefined;
550 x[1] = .{ .a = 123, .b = {} };
551 std.debug.assert(x[1].a == 123);
552 }
553}
554
555test "comptime store of reinterpreted zero-bit type" {
556 const S = struct {
557 fn doTheTest(comptime T: type) void {
558 comptime var buf: T = undefined;
559 const ptr: *void = @ptrCast(&buf);
560 ptr.* = {};
561 }
562 };
563 S.doTheTest(void);
564 S.doTheTest(u0);
565 S.doTheTest([0]u8);
566 S.doTheTest([1]u0);
567 S.doTheTest([5]u0);
568 S.doTheTest([5]void);
569 S.doTheTest(packed struct(u0) {});
570}
571
572test "comptime store to extern struct reinterpreted as byte array" {
573 const T = extern struct {
574 x: u32,
575 y: f32,
576 z: [2]void,
577 };
578 comptime var val: T = undefined;
579
580 const bytes: *[@sizeOf(T)]u8 = @ptrCast(&val);
581 @memset(bytes, 0);
582
583 comptime std.debug.assert(val.x == 0);
584}