mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-10-29 03:06:10 +08:00
I'm writing a small numeric library whose kernels run on the CPU, on CUDA and on Vulkan through the self-hosted SPIR-V backend, and I was making them generic over the float type. `f64` needs its own `exp` and `log` on the GPU, so I wrote them as polynomial series, and the `f64` Vulkan module stopped validating. This fixes the three backend bugs underneath that, and a crash I ran into after merging master.
The series take floats apart with `@bitCast` on vectors. Between vectors of the same length this reaches the Vulkan/OpenGL fallback in `bitCast`, which stores the source into a function variable of the destination type:
```zig
export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void {
var x: @Vector(2, f32) = .{ 1, 2 };
_ = &x;
const bits: @Vector(2, i32) = @bitCast(x);
_ = bits;
}
```
```
$ zig build-exe -fno-llvm -target spirv64-vulkan -mcpu vulkan_v1_2 bitcast.zig -femit-bin=bitcast.spv
$ spirv-val --target-env vulkan1.2 bitcast.spv
error: line 45: OpStore Pointer <id> '36[%36]'s type does not match Object <id> '29[%29]'s type.
```
An elementwise vector bitcast is now a scalar `bitCast` per lane, the way `buildConvert` and friends already unroll vectors. Going through the scalar path also normalizes lanes of strange integers: a single `OpBitcast` on the whole vector validates for `@Vector(n, u3)` to `@Vector(n, i3)`, but leaves lanes that don't compare equal to the negative values they should hold.
The series also convert between integer and float lanes. A vector of one lane, or of more than four without `vector16`, is an array in SPIR-V, and `airFloatFromInt` and `airIntFromFloat` emitted a single `OpConvert*` with that array as its result type:
```zig
export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void {
var x: @Vector(1, i32) = .{1};
_ = &x;
const y: @Vector(1, f32) = @floatFromInt(x);
_ = y;
}
```
```
$ spirv-val --target-env vulkan1.2 convert.spv
error: line 40: Expected float scalar or vector type as Result Type: ConvertSToF
%y = OpConvertSToF %_arr_f32_u32_1 %26
```
`@floatCast` and `@intCast` already go through `buildConvert`, which unrolls the vector; these two now do as well, with `buildConvert` picking the int/float conversion opcodes.
The coefficients sit in a comptime array that the series take as a comptime slice, and that alone fails to compile, although nothing reads the slice at runtime:
```zig
const weights = [_]f32{ 1, 2, 3 };
inline fn first(comptime w: []const f32) f32 {
return w[0];
}
export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void {
var x: f32 = first(&weights);
_ = &x;
}
```
```
$ zig build-exe -fno-llvm -target spirv64-vulkan -mcpu vulkan_v1_2 dbgvar.zig -femit-bin=dbgvar.spv
dbgvar.zig:7:8: error: TODO (SPIR-V): pointer to constant 'dbgvar.weights'
```
It appears `airDbgVar` lowers the operand only to attach an `OpName` to it, and a pointer to a non-extern constant is still a TODO. A comptime-known value has no runtime variable of its own to name, so `airDbgVar` now leaves interned operands alone. The same thing held back 58 of the behavior tests skipped on SPIR-V, and the test commit un-skips them.
After merging master, four of those tests crashed the compiler in `structFieldPtr`, which has `unreachable` for packed containers since ebb8683ef1 ([#36996](https://codeberg.org/ziglang/zig/pulls/36996)):
```zig
const Flags = packed struct { a: bool, b: bool };
export fn main() callconv(.{ .spirv_kernel = .{ .x = 1, .y = 1, .z = 1 } }) void {
var flags: Flags = .{ .a = true, .b = false };
const b = &flags.b;
b.* = true;
}
```
```
thread 3961624 panic: reached unreachable code
src/codegen/spirv/CodeGen.zig:6906:27: 0x4767157 in structFieldPtr (main.zig)
.@"packed" => unreachable,
```
My reading is that the `unreachable` holds for the pointer derivations it was written for, since `constantPtr` now turns packed pointers into pointers to their host integer first, but `airStructFieldPtr` and `airStructFieldPtrIndex` still pass packed structs through. A pointer to a packed field is a bit-pointer into the struct's own host integer, so it is the struct pointer: that is what the old code computed, and what the C backend now does with `.begin`. Packed unions get back the TODO error they had before. I may be missing a plan for packed field pointers here, and I'm happy to change it if so. The commit also un-skips three packed tests that crash on master the same way. I suppose nothing caught this because those tests are skipped on SPIR-V and the `spirv64-vulkan` behavior target is commented out in `test/tests.zig`.
I ran the behavior tests through `tools/unskip_spirv_tests.zig` and [zig-spirv-test-executor](https://github.com/Snektron/zig-spirv-test-executor) on a Radeon 680M, and the new and un-skipped tests on an RTX 4060 as well. Every fix has tests that fail on master and pass with it; the vector ones compare lane by lane, since `expectEqual` on vectors doesn't compile for SPIR-V yet. Running each behavior file whole, nothing that passes on master fails. Five more skips already pass on master without these changes, so I left them out, but I can add them. The new vector tests copy the aarch64, arm, sparc64 and riscv64 skips of the tests around them, which I couldn't check.
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37040
Reviewed-by: Ali Cheraghi <alichraghi@noreply.codeberg.org>
609 lines
19 KiB
Zig
609 lines
19 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const endian = builtin.cpu.arch.endian();
|
|
const testing = std.testing;
|
|
const assert = std.debug.assert;
|
|
const ptr_size = @sizeOf(usize);
|
|
|
|
test "type pun signed and unsigned as single pointer" {
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as(*i32, @ptrCast(&x));
|
|
y.* = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as many pointer" {
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as([*]i32, @ptrCast(&x));
|
|
y[0] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as array pointer" {
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as(*[1]i32, @ptrCast(&x));
|
|
y[0] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as offset many pointer" {
|
|
comptime {
|
|
var x: [11]u32 = undefined;
|
|
var y: [*]i32 = @ptrCast(&x[10]);
|
|
y -= 10;
|
|
y[10] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x[10]);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as array pointer with pointer arithemtic" {
|
|
comptime {
|
|
var x: [11]u32 = undefined;
|
|
const y = @as([*]i32, @ptrCast(&x[10])) - 10;
|
|
const z: *[15]i32 = y[0..15];
|
|
z[10] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x[10]);
|
|
}
|
|
}
|
|
|
|
test "type pun value and struct" {
|
|
comptime {
|
|
const StructOfU32 = extern struct { x: u32 };
|
|
var inst: StructOfU32 = .{ .x = 0 };
|
|
@as(*i32, @ptrCast(&inst.x)).* = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), inst.x);
|
|
@as(*i32, @ptrCast(&inst)).* = -2;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFE), inst.x);
|
|
}
|
|
}
|
|
|
|
fn bigToNativeEndian(comptime T: type, v: T) T {
|
|
return if (endian == .big) v else @byteSwap(v);
|
|
}
|
|
test "type pun endianness" {
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
|
|
comptime {
|
|
const StructOfBytes = extern struct { x: [4]u8 };
|
|
var inst: StructOfBytes = .{ .x = [4]u8{ 0, 0, 0, 0 } };
|
|
const structPtr = @as(*align(1) u32, @ptrCast(&inst));
|
|
const arrayPtr = @as(*align(1) u32, @ptrCast(&inst.x));
|
|
inst.x[0] = 0xFE;
|
|
inst.x[2] = 0xBE;
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xFE00BE00), structPtr.*);
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xFE00BE00), arrayPtr.*);
|
|
structPtr.* = bigToNativeEndian(u32, 0xDEADF00D);
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xDEADF00D), structPtr.*);
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xDEADF00D), arrayPtr.*);
|
|
try testing.expectEqual(@as(u8, 0xDE), inst.x[0]);
|
|
try testing.expectEqual(@as(u8, 0xAD), inst.x[1]);
|
|
try testing.expectEqual(@as(u8, 0xF0), inst.x[2]);
|
|
try testing.expectEqual(@as(u8, 0x0D), inst.x[3]);
|
|
}
|
|
}
|
|
|
|
const Bits = packed struct {
|
|
// Note: This struct has only single byte words so it
|
|
// doesn't need to be byte swapped.
|
|
p0: u1,
|
|
p1: u4,
|
|
p2: u3,
|
|
p3: u2,
|
|
p4: u6,
|
|
p5: u8,
|
|
p6: u7,
|
|
p7: u1,
|
|
};
|
|
const ShuffledBits = packed struct {
|
|
p1: u4,
|
|
p3: u2,
|
|
p7: u1,
|
|
p0: u1,
|
|
p5: u8,
|
|
p2: u3,
|
|
p6: u7,
|
|
p4: u6,
|
|
};
|
|
fn shuffle(ptr: usize, comptime From: type, comptime To: type) usize {
|
|
if (@sizeOf(From) != @sizeOf(To))
|
|
@compileError("Mismatched sizes! " ++ @typeName(From) ++ " and " ++ @typeName(To) ++ " must have the same size!");
|
|
const array_len = @divExact(ptr_size, @sizeOf(From));
|
|
var result: usize = 0;
|
|
const pSource = @as(*align(1) const [array_len]From, @ptrCast(&ptr));
|
|
const pResult = @as(*align(1) [array_len]To, @ptrCast(&result));
|
|
var i: usize = 0;
|
|
while (i < array_len) : (i += 1) {
|
|
inline for (@typeInfo(To).@"struct".field_names) |f_name| {
|
|
@field(pResult[i], f_name) = @field(pSource[i], f_name);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn doTypePunBitsTest(as_bits: *Bits) !void {
|
|
const as_u32 = @as(*align(1) u32, @ptrCast(as_bits));
|
|
const as_bytes = @as(*[4]u8, @ptrCast(as_bits));
|
|
as_u32.* = bigToNativeEndian(u32, 0xB0A7DEED);
|
|
try testing.expectEqual(@as(u1, 0x00), as_bits.p0);
|
|
try testing.expectEqual(@as(u4, 0x08), as_bits.p1);
|
|
try testing.expectEqual(@as(u3, 0x05), as_bits.p2);
|
|
try testing.expectEqual(@as(u2, 0x03), as_bits.p3);
|
|
try testing.expectEqual(@as(u6, 0x29), as_bits.p4);
|
|
try testing.expectEqual(@as(u8, 0xDE), as_bits.p5);
|
|
try testing.expectEqual(@as(u7, 0x6D), as_bits.p6);
|
|
try testing.expectEqual(@as(u1, 0x01), as_bits.p7);
|
|
|
|
as_bits.p6 = 0x2D;
|
|
as_bits.p1 = 0x0F;
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xBEA7DEAD), as_u32.*);
|
|
|
|
// clobbering one bit doesn't clobber the word
|
|
as_bits.p7 = undefined;
|
|
try testing.expectEqual(@as(u7, 0x2D), as_bits.p6);
|
|
// even when read as a whole
|
|
const u = as_u32.*;
|
|
_ = u; // u is undefined
|
|
try testing.expectEqual(@as(u7, 0x2D), as_bits.p6);
|
|
// or if a field which shares the byte is modified
|
|
as_bits.p6 = 0x6D;
|
|
try testing.expectEqual(@as(u7, 0x6D), as_bits.p6);
|
|
|
|
// but overwriting the undefined will clear it
|
|
as_bytes[3] = 0xAF;
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xBEA7DEAF), as_u32.*);
|
|
}
|
|
|
|
test "type pun bits" {
|
|
if (true) {
|
|
// TODO: currently, marking one bit of `Bits` as `undefined` does
|
|
// mark the whole value as `undefined`, since the pointer interpretation
|
|
// logic reads it back in as a `u32`, which is partially-undef and thus
|
|
// has value `undefined`. We need an improved comptime memory representation
|
|
// to make this work.
|
|
return error.SkipZigTest;
|
|
}
|
|
comptime {
|
|
var v: u32 = undefined;
|
|
try doTypePunBitsTest(@as(*Bits, @ptrCast(&v)));
|
|
}
|
|
}
|
|
|
|
const imports = struct {
|
|
var global_u32: u32 = 0;
|
|
};
|
|
|
|
// Make sure lazy values work on their own, before getting into more complex tests
|
|
test "basic pointer preservation" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
const lazy_address = @intFromPtr(&imports.global_u32);
|
|
try testing.expectEqual(@intFromPtr(&imports.global_u32), lazy_address);
|
|
try testing.expectEqual(&imports.global_u32, @as(*u32, @ptrFromInt(lazy_address)));
|
|
}
|
|
}
|
|
|
|
test "byte copy preserves linker value" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const ct_value = comptime blk: {
|
|
const lazy = &imports.global_u32;
|
|
var result: *u32 = undefined;
|
|
const pSource = @as(*const [ptr_size]u8, @ptrCast(&lazy));
|
|
const pResult = @as(*[ptr_size]u8, @ptrCast(&result));
|
|
var i: usize = 0;
|
|
while (i < ptr_size) : (i += 1) {
|
|
pResult[i] = pSource[i];
|
|
try testing.expectEqual(pSource[i], pResult[i]);
|
|
}
|
|
try testing.expectEqual(&imports.global_u32, result);
|
|
break :blk result;
|
|
};
|
|
|
|
try testing.expectEqual(&imports.global_u32, ct_value);
|
|
}
|
|
|
|
test "unordered byte copy preserves linker value" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const ct_value = comptime blk: {
|
|
const lazy = &imports.global_u32;
|
|
var result: *u32 = undefined;
|
|
const pSource = @as(*const [ptr_size]u8, @ptrCast(&lazy));
|
|
const pResult = @as(*[ptr_size]u8, @ptrCast(&result));
|
|
if (ptr_size > 8) @compileError("This array needs to be expanded for platform with very big pointers");
|
|
const shuffled_indices = [_]usize{ 4, 5, 2, 6, 1, 3, 0, 7 };
|
|
for (shuffled_indices) |i| {
|
|
pResult[i] = pSource[i];
|
|
try testing.expectEqual(pSource[i], pResult[i]);
|
|
}
|
|
try testing.expectEqual(&imports.global_u32, result);
|
|
break :blk result;
|
|
};
|
|
|
|
try testing.expectEqual(&imports.global_u32, ct_value);
|
|
}
|
|
|
|
test "shuffle chunks of linker value" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const lazy_address = @intFromPtr(&imports.global_u32);
|
|
const shuffled1_rt = shuffle(lazy_address, Bits, ShuffledBits);
|
|
const unshuffled1_rt = shuffle(shuffled1_rt, ShuffledBits, Bits);
|
|
try testing.expectEqual(lazy_address, unshuffled1_rt);
|
|
const shuffled1_ct = comptime shuffle(lazy_address, Bits, ShuffledBits);
|
|
const shuffled1_ct_2 = comptime shuffle(lazy_address, Bits, ShuffledBits);
|
|
try comptime testing.expectEqual(shuffled1_ct, shuffled1_ct_2);
|
|
const unshuffled1_ct = comptime shuffle(shuffled1_ct, ShuffledBits, Bits);
|
|
try comptime testing.expectEqual(lazy_address, unshuffled1_ct);
|
|
try testing.expectEqual(shuffled1_ct, shuffled1_rt);
|
|
}
|
|
|
|
test "dance on linker values" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var arr: [2]usize = undefined;
|
|
arr[0] = @intFromPtr(&imports.global_u32);
|
|
arr[1] = @intFromPtr(&imports.global_u32);
|
|
|
|
const weird_ptr = @as([*]Bits, @ptrCast(@as([*]u8, @ptrCast(&arr)) + @sizeOf(usize) - 3));
|
|
try doTypePunBitsTest(&weird_ptr[0]);
|
|
if (ptr_size > @sizeOf(Bits))
|
|
try doTypePunBitsTest(&weird_ptr[1]);
|
|
|
|
const arr_bytes: *[2][ptr_size]u8 = @ptrCast(&arr);
|
|
|
|
var rebuilt_bytes: [ptr_size]u8 = undefined;
|
|
var i: usize = 0;
|
|
while (i < ptr_size - 3) : (i += 1) {
|
|
rebuilt_bytes[i] = arr_bytes[0][i];
|
|
}
|
|
while (i < ptr_size) : (i += 1) {
|
|
rebuilt_bytes[i] = arr_bytes[1][i];
|
|
}
|
|
|
|
try testing.expectEqual(&imports.global_u32, @as(*u32, @ptrFromInt(@as(usize, @bitCast(rebuilt_bytes)))));
|
|
}
|
|
}
|
|
|
|
test "offset array ptr by element size" {
|
|
comptime {
|
|
const VirtualStruct = struct { x: u32 };
|
|
var arr: [4]VirtualStruct = .{
|
|
.{ .x = bigToNativeEndian(u32, 0x0004080c) },
|
|
.{ .x = bigToNativeEndian(u32, 0x0105090d) },
|
|
.{ .x = bigToNativeEndian(u32, 0x02060a0e) },
|
|
.{ .x = bigToNativeEndian(u32, 0x03070b0f) },
|
|
};
|
|
|
|
const buf: [*]align(@alignOf(VirtualStruct)) u8 = @ptrCast(&arr);
|
|
|
|
const second_element: *VirtualStruct = @ptrCast(buf + 2 * @sizeOf(VirtualStruct));
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0x02060a0e), second_element.x);
|
|
}
|
|
}
|
|
|
|
test "offset instance by field size" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
const VirtualStruct = struct { x: u32, y: u32, z: u32, w: u32 };
|
|
var inst = VirtualStruct{ .x = 0, .y = 1, .z = 2, .w = 3 };
|
|
|
|
var ptr = @intFromPtr(&inst);
|
|
ptr -= 4;
|
|
ptr += @offsetOf(VirtualStruct, "x");
|
|
try testing.expectEqual(@as(u32, 0), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr -= @offsetOf(VirtualStruct, "x");
|
|
ptr += @offsetOf(VirtualStruct, "y");
|
|
try testing.expectEqual(@as(u32, 1), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr = ptr - @offsetOf(VirtualStruct, "y") + @offsetOf(VirtualStruct, "z");
|
|
try testing.expectEqual(@as(u32, 2), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr = @intFromPtr(&inst.z) - 4 - @offsetOf(VirtualStruct, "z");
|
|
ptr += @offsetOf(VirtualStruct, "w");
|
|
try testing.expectEqual(@as(u32, 3), @as(*u32, @ptrFromInt(ptr + 4)).*);
|
|
}
|
|
}
|
|
|
|
test "offset field ptr by enclosing array element size" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
const VirtualStruct = struct { x: u32 };
|
|
var arr: [4]VirtualStruct = .{
|
|
.{ .x = bigToNativeEndian(u32, 0x0004080c) },
|
|
.{ .x = bigToNativeEndian(u32, 0x0105090d) },
|
|
.{ .x = bigToNativeEndian(u32, 0x02060a0e) },
|
|
.{ .x = bigToNativeEndian(u32, 0x03070b0f) },
|
|
};
|
|
|
|
var i: usize = 0;
|
|
while (i < 4) : (i += 1) {
|
|
var ptr: [*]u8 = @ptrCast(&arr[0]);
|
|
ptr += i;
|
|
ptr += @offsetOf(VirtualStruct, "x");
|
|
var j: usize = 0;
|
|
while (j < 4) : (j += 1) {
|
|
const base = ptr + j * @sizeOf(VirtualStruct);
|
|
try testing.expectEqual(@as(u8, @intCast(i * 4 + j)), base[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
test "accessing reinterpreted memory of parent object" {
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
|
|
const S = extern struct {
|
|
a: f32,
|
|
b: [4]u8,
|
|
c: f32,
|
|
};
|
|
const expected = if (endian == .little) 102 else 38;
|
|
|
|
comptime {
|
|
const x = S{
|
|
.a = 1.5,
|
|
.b = [_]u8{ 1, 2, 3, 4 },
|
|
.c = 2.6,
|
|
};
|
|
const ptr = &x.b[0];
|
|
const b = @as([*c]const u8, @ptrCast(ptr))[5];
|
|
try testing.expect(b == expected);
|
|
}
|
|
}
|
|
|
|
test "bitcast packed union to integer" {
|
|
const U = packed union {
|
|
x: i2,
|
|
y: u2,
|
|
};
|
|
|
|
comptime {
|
|
const a: U = .{ .x = -1 };
|
|
const b: U = .{ .y = 2 };
|
|
const cast_a: u2 = @bitCast(a);
|
|
const cast_b: u2 = @bitCast(b);
|
|
|
|
try testing.expectEqual(@as(u2, 3), cast_a);
|
|
try testing.expectEqual(@as(u2, 2), cast_b);
|
|
}
|
|
}
|
|
|
|
test "mutate entire slice at comptime" {
|
|
comptime {
|
|
var buf: [3]u8 = undefined;
|
|
const x: [2]u8 = .{ 1, 2 }; // Avoid RLS
|
|
buf[1..3].* = x;
|
|
}
|
|
}
|
|
|
|
test "dereference undefined pointer to zero-bit type" {
|
|
const p0: *void = undefined;
|
|
try testing.expectEqual({}, p0.*);
|
|
|
|
const p1: *[0]u32 = undefined;
|
|
try testing.expect(p1.*.len == 0);
|
|
}
|
|
|
|
test "type pun extern struct" {
|
|
const S = extern struct { f: u8 };
|
|
comptime var s = S{ .f = 123 };
|
|
@as(*u8, @ptrCast(&s)).* = 72;
|
|
try testing.expectEqual(@as(u8, 72), s.f);
|
|
}
|
|
|
|
test "type pun @ptrFromInt" {
|
|
const p: *u8 = @ptrFromInt(42);
|
|
// note that expectEqual hides the bug
|
|
try testing.expect(@as(*const [*]u8, @ptrCast(&p)).* == @as([*]u8, @ptrFromInt(42)));
|
|
}
|
|
|
|
test "type pun null pointer-like optional" {
|
|
const p: ?*u8 = null;
|
|
// note that expectEqual hides the bug
|
|
try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null);
|
|
}
|
|
|
|
test "write empty array to end" {
|
|
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
|
|
|
|
comptime var array: [5]u8 = "hello".*;
|
|
array[5..5].* = .{};
|
|
array[5..5].* = [0]u8{};
|
|
array[5..5].* = [_]u8{};
|
|
comptime std.debug.assert(std.mem.eql(u8, "hello", &array));
|
|
}
|
|
|
|
fn doublePtrTest() !void {
|
|
var a: u32 = 0;
|
|
const ptr = &a;
|
|
const double_ptr = &ptr;
|
|
setDoublePtr(double_ptr, 1);
|
|
setDoublePtr(double_ptr, 2);
|
|
setDoublePtr(double_ptr, 1);
|
|
try std.testing.expect(a == 1);
|
|
}
|
|
fn setDoublePtr(ptr: *const *const u32, value: u32) void {
|
|
setPtr(ptr.*, value);
|
|
}
|
|
fn setPtr(ptr: *const u32, value: u32) void {
|
|
const mut_ptr: *u32 = @constCast(ptr);
|
|
mut_ptr.* = value;
|
|
}
|
|
test "double pointer can mutate comptime state" {
|
|
try comptime doublePtrTest();
|
|
}
|
|
|
|
fn GenericIntApplier(
|
|
comptime Context: type,
|
|
comptime applyFn: fn (context: Context, arg: u32) void,
|
|
) type {
|
|
return struct {
|
|
context: Context,
|
|
|
|
const Self = @This();
|
|
|
|
inline fn any(self: *const Self) IntApplier {
|
|
return .{
|
|
.context = @ptrCast(&self.context),
|
|
.applyFn = typeErasedApplyFn,
|
|
};
|
|
}
|
|
|
|
fn typeErasedApplyFn(context: *const anyopaque, arg: u32) void {
|
|
const ptr: *const Context = @ptrCast(@alignCast(context));
|
|
applyFn(ptr.*, arg);
|
|
}
|
|
};
|
|
}
|
|
const IntApplier = struct {
|
|
context: *const anyopaque,
|
|
applyFn: *const fn (context: *const anyopaque, arg: u32) void,
|
|
|
|
fn apply(ia: IntApplier, arg: u32) void {
|
|
ia.applyFn(ia.context, arg);
|
|
}
|
|
};
|
|
const Accumulator = struct {
|
|
value: u32,
|
|
|
|
const Applier = GenericIntApplier(*u32, add);
|
|
|
|
fn applier(a: *Accumulator) Applier {
|
|
return .{ .context = &a.value };
|
|
}
|
|
|
|
fn add(context: *u32, arg: u32) void {
|
|
context.* += arg;
|
|
}
|
|
};
|
|
fn fieldPtrTest() u32 {
|
|
var a: Accumulator = .{ .value = 0 };
|
|
const applier = a.applier();
|
|
applier.any().apply(1);
|
|
applier.any().apply(1);
|
|
return a.value;
|
|
}
|
|
test "pointer in aggregate field can mutate comptime state" {
|
|
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
|
|
try comptime std.testing.expect(fieldPtrTest() == 2);
|
|
}
|
|
|
|
test "comptime store of extern struct with void field" {
|
|
comptime {
|
|
var x: extern struct { a: u8, b: void } = undefined;
|
|
x = .{ .a = 123, .b = {} };
|
|
std.debug.assert(x.a == 123);
|
|
}
|
|
}
|
|
|
|
test "comptime store of extern struct with void field into array" {
|
|
comptime {
|
|
var x: [3]extern struct { a: u8, b: void } = undefined;
|
|
x[1] = .{ .a = 123, .b = {} };
|
|
std.debug.assert(x[1].a == 123);
|
|
}
|
|
}
|
|
|
|
test "comptime store of packed struct with void field" {
|
|
comptime {
|
|
var x: packed struct { a: u8, b: void } = undefined;
|
|
x = .{ .a = 123, .b = {} };
|
|
std.debug.assert(x.a == 123);
|
|
}
|
|
}
|
|
|
|
test "comptime store of packed struct with void field into array" {
|
|
comptime {
|
|
var x: [3]packed struct { a: u8, b: void } = undefined;
|
|
x[1] = .{ .a = 123, .b = {} };
|
|
std.debug.assert(x[1].a == 123);
|
|
}
|
|
}
|
|
|
|
test "comptime store of reinterpreted zero-bit type" {
|
|
const S = struct {
|
|
fn doTheTest(comptime T: type) void {
|
|
comptime var buf: T = undefined;
|
|
const ptr: *void = @ptrCast(&buf);
|
|
ptr.* = {};
|
|
}
|
|
};
|
|
S.doTheTest(void);
|
|
S.doTheTest(u0);
|
|
S.doTheTest([0]u8);
|
|
S.doTheTest([1]u0);
|
|
S.doTheTest([5]u0);
|
|
S.doTheTest([5]void);
|
|
S.doTheTest(packed struct(u0) {});
|
|
}
|
|
|
|
test "comptime store to extern struct reinterpreted as byte array" {
|
|
const T = extern struct {
|
|
x: u32,
|
|
y: f32,
|
|
z: [2]void,
|
|
};
|
|
comptime var val: T = undefined;
|
|
|
|
const bytes: *[@sizeOf(T)]u8 = @ptrCast(&val);
|
|
@memset(bytes, 0);
|
|
|
|
comptime std.debug.assert(val.x == 0);
|
|
}
|
|
|
|
test "reinterpret sentinel-terminated array as packed struct" {
|
|
const S = packed struct(u16) { lo: u8, hi: u8 };
|
|
const data: [2:0]u8 = .{ 0x12, 0x34 };
|
|
const ptr: *align(1) const S = @ptrCast(&data);
|
|
switch (endian) {
|
|
.little => {
|
|
try testing.expect(ptr.lo == 0x12);
|
|
try testing.expect(ptr.hi == 0x34);
|
|
},
|
|
.big => {
|
|
try testing.expect(ptr.lo == 0x34);
|
|
try testing.expect(ptr.hi == 0x12);
|
|
},
|
|
}
|
|
}
|
|
|
|
test "reinterpret pointer as optional pointer via double-pointer coercion" {
|
|
const p0: *const *anyopaque = &@ptrFromInt(0x1000);
|
|
const p1: *const ?*anyopaque = p0;
|
|
|
|
const loaded = p1.*;
|
|
|
|
comptime assert(@TypeOf(loaded) == ?*anyopaque);
|
|
comptime assert(@intFromPtr(loaded) == 0x1000);
|
|
}
|