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}