| 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); |
| 3 | const endian = builtin.cpu.arch.endian(); |
| 4 | const testing = @import("std").testing; |
| 5 | const ptr_size = @sizeOf(usize); |
| 6 | |
| 7 | test "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 | |
| 16 | test "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 | |
| 25 | test "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 | |
| 34 | test "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 | |
| 44 | test "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 | |
| 54 | test "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 | |
| 65 | fn bigToNativeEndian(comptime T: type, v: T) T { |
| 66 | return if (endian == .big) v else @byteSwap(v); |
| 67 | } |
| 68 | test "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 | |
| 90 | const 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 | }; |
| 102 | const 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 | }; |
| 112 | fn 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".field_names) |f_name| { |
| 122 | @field(pResult[i], f_name) = @field(pSource[i], f_name); |
| 123 | } |
| 124 | } |
| 125 | return result; |
| 126 | } |
| 127 | |
| 128 | fn 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 | |
| 161 | test "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 | |
| 176 | const 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 |
| 181 | test "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 | |
| 194 | test "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 | |
| 217 | test "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 | |
| 241 | test "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 | |
| 259 | test "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 | |
| 290 | test "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 | |
| 307 | test "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 | |
| 332 | test "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 | |
| 361 | test "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 | |
| 383 | test "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 | |
| 400 | test "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 | |
| 408 | test "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 | |
| 418 | test "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 | |
| 425 | test "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 | |
| 433 | test "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 | |
| 439 | test "write empty array to end" { |
| 440 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; |
| 441 | |
| 442 | comptime var array: [5]u8 = "hello".*; |
| 443 | array[5..5].* = .{}; |
| 444 | array[5..5].* = [0]u8{}; |
| 445 | array[5..5].* = [_]u8{}; |
| 446 | comptime std.debug.assert(std.mem.eql(u8, "hello", &array)); |
| 447 | } |
| 448 | |
| 449 | fn doublePtrTest() !void { |
| 450 | var a: u32 = 0; |
| 451 | const ptr = &a; |
| 452 | const double_ptr = &ptr; |
| 453 | setDoublePtr(double_ptr, 1); |
| 454 | setDoublePtr(double_ptr, 2); |
| 455 | setDoublePtr(double_ptr, 1); |
| 456 | try std.testing.expect(a == 1); |
| 457 | } |
| 458 | fn setDoublePtr(ptr: *const *const u32, value: u32) void { |
| 459 | setPtr(ptr.*, value); |
| 460 | } |
| 461 | fn setPtr(ptr: *const u32, value: u32) void { |
| 462 | const mut_ptr: *u32 = @constCast(ptr); |
| 463 | mut_ptr.* = value; |
| 464 | } |
| 465 | test "double pointer can mutate comptime state" { |
| 466 | try comptime doublePtrTest(); |
| 467 | } |
| 468 | |
| 469 | fn GenericIntApplier( |
| 470 | comptime Context: type, |
| 471 | comptime applyFn: fn (context: Context, arg: u32) void, |
| 472 | ) type { |
| 473 | return struct { |
| 474 | context: Context, |
| 475 | |
| 476 | const Self = @This(); |
| 477 | |
| 478 | inline fn any(self: *const Self) IntApplier { |
| 479 | return .{ |
| 480 | .context = @ptrCast(&self.context), |
| 481 | .applyFn = typeErasedApplyFn, |
| 482 | }; |
| 483 | } |
| 484 | |
| 485 | fn typeErasedApplyFn(context: *const anyopaque, arg: u32) void { |
| 486 | const ptr: *const Context = @ptrCast(@alignCast(context)); |
| 487 | applyFn(ptr.*, arg); |
| 488 | } |
| 489 | }; |
| 490 | } |
| 491 | const IntApplier = struct { |
| 492 | context: *const anyopaque, |
| 493 | applyFn: *const fn (context: *const anyopaque, arg: u32) void, |
| 494 | |
| 495 | fn apply(ia: IntApplier, arg: u32) void { |
| 496 | ia.applyFn(ia.context, arg); |
| 497 | } |
| 498 | }; |
| 499 | const Accumulator = struct { |
| 500 | value: u32, |
| 501 | |
| 502 | const Applier = GenericIntApplier(*u32, add); |
| 503 | |
| 504 | fn applier(a: *Accumulator) Applier { |
| 505 | return .{ .context = &a.value }; |
| 506 | } |
| 507 | |
| 508 | fn add(context: *u32, arg: u32) void { |
| 509 | context.* += arg; |
| 510 | } |
| 511 | }; |
| 512 | fn fieldPtrTest() u32 { |
| 513 | var a: Accumulator = .{ .value = 0 }; |
| 514 | const applier = a.applier(); |
| 515 | applier.any().apply(1); |
| 516 | applier.any().apply(1); |
| 517 | return a.value; |
| 518 | } |
| 519 | test "pointer in aggregate field can mutate comptime state" { |
| 520 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; |
| 521 | try comptime std.testing.expect(fieldPtrTest() == 2); |
| 522 | } |
| 523 | |
| 524 | test "comptime store of extern struct with void field" { |
| 525 | comptime { |
| 526 | var x: extern struct { a: u8, b: void } = undefined; |
| 527 | x = .{ .a = 123, .b = {} }; |
| 528 | std.debug.assert(x.a == 123); |
| 529 | } |
| 530 | } |
| 531 | |
| 532 | test "comptime store of extern struct with void field into array" { |
| 533 | comptime { |
| 534 | var x: [3]extern struct { a: u8, b: void } = undefined; |
| 535 | x[1] = .{ .a = 123, .b = {} }; |
| 536 | std.debug.assert(x[1].a == 123); |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | test "comptime store of packed struct with void field" { |
| 541 | comptime { |
| 542 | var x: packed struct { a: u8, b: void } = undefined; |
| 543 | x = .{ .a = 123, .b = {} }; |
| 544 | std.debug.assert(x.a == 123); |
| 545 | } |
| 546 | } |
| 547 | |
| 548 | test "comptime store of packed struct with void field into array" { |
| 549 | comptime { |
| 550 | var x: [3]packed struct { a: u8, b: void } = undefined; |
| 551 | x[1] = .{ .a = 123, .b = {} }; |
| 552 | std.debug.assert(x[1].a == 123); |
| 553 | } |
| 554 | } |
| 555 | |
| 556 | test "comptime store of reinterpreted zero-bit type" { |
| 557 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; |
| 558 | |
| 559 | const S = struct { |
| 560 | fn doTheTest(comptime T: type) void { |
| 561 | comptime var buf: T = undefined; |
| 562 | const ptr: *void = @ptrCast(&buf); |
| 563 | ptr.* = {}; |
| 564 | } |
| 565 | }; |
| 566 | S.doTheTest(void); |
| 567 | S.doTheTest(u0); |
| 568 | S.doTheTest([0]u8); |
| 569 | S.doTheTest([1]u0); |
| 570 | S.doTheTest([5]u0); |
| 571 | S.doTheTest([5]void); |
| 572 | S.doTheTest(packed struct(u0) {}); |
| 573 | } |
| 574 | |
| 575 | test "comptime store to extern struct reinterpreted as byte array" { |
| 576 | const T = extern struct { |
| 577 | x: u32, |
| 578 | y: f32, |
| 579 | z: [2]void, |
| 580 | }; |
| 581 | comptime var val: T = undefined; |
| 582 | |
| 583 | const bytes: *[@sizeOf(T)]u8 = @ptrCast(&val); |
| 584 | @memset(bytes, 0); |
| 585 | |
| 586 | comptime std.debug.assert(val.x == 0); |
| 587 | } |
| 588 | |
| 589 | test "reinterpret sentinel-terminated array as packed struct" { |
| 590 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; |
| 591 | |
| 592 | const S = packed struct(u16) { lo: u8, hi: u8 }; |
| 593 | const data: [2:0]u8 = .{ 0x12, 0x34 }; |
| 594 | const ptr: *align(1) const S = @ptrCast(&data); |
| 595 | switch (endian) { |
| 596 | .little => { |
| 597 | try testing.expect(ptr.lo == 0x12); |
| 598 | try testing.expect(ptr.hi == 0x34); |
| 599 | }, |
| 600 | .big => { |
| 601 | try testing.expect(ptr.lo == 0x34); |
| 602 | try testing.expect(ptr.hi == 0x12); |
| 603 | }, |
| 604 | } |
| 605 | } |