Files
zig/test/behavior/ptrcast.zig
Brian Ferri 25cafe0b34 spirv: fix vector casts, packed field pointers and debug names of comptime-known values (#37040)
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>
2026-10-04 11:50:49 +02:00

575 lines
19 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
const assert = std.debug.assert;
const native_endian = builtin.target.cpu.arch.endian();
test "reinterpret bytes as integer with nonzero offset" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try testReinterpretBytesAsInteger();
try comptime testReinterpretBytesAsInteger();
}
fn testReinterpretBytesAsInteger() !void {
const bytes = "\x12\x34\x56\x78\xab";
const expected = switch (native_endian) {
.little => 0xab785634,
.big => 0x345678ab,
};
try expect(@as(*align(1) const u32, @ptrCast(bytes[1..5])).* == expected);
}
test "reinterpret an array over multiple elements, with no well-defined layout" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try testReinterpretWithOffsetAndNoWellDefinedLayout();
try comptime testReinterpretWithOffsetAndNoWellDefinedLayout();
}
fn testReinterpretWithOffsetAndNoWellDefinedLayout() !void {
const bytes: ?[5]?u8 = [5]?u8{ 0x12, 0x34, 0x56, 0x78, 0x9a };
const ptr = &bytes.?[1];
const copy: [4]?u8 = @as(*const [4]?u8, @ptrCast(ptr)).*;
_ = copy;
//try expect(@ptrCast(*align(1)?u8, bytes[1..5]).* == );
}
test "reinterpret bytes inside auto-layout struct as integer with nonzero offset" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try testReinterpretStructWrappedBytesAsInteger();
try comptime testReinterpretStructWrappedBytesAsInteger();
}
fn testReinterpretStructWrappedBytesAsInteger() !void {
const S = struct { bytes: [5:0]u8 };
const obj = S{ .bytes = "\x12\x34\x56\x78\xab".* };
const expected = switch (native_endian) {
.little => 0xab785634,
.big => 0x345678ab,
};
try expect(@as(*align(1) const u32, @ptrCast(obj.bytes[1..5])).* == expected);
}
test "reinterpret bytes of an array into an extern struct" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try testReinterpretBytesAsExternStruct();
try comptime testReinterpretBytesAsExternStruct();
}
fn testReinterpretBytesAsExternStruct() !void {
var bytes align(2) = [_]u8{ 1, 2, 3, 4, 5, 6 };
const S = extern struct {
a: u8,
b: u16,
c: u8,
};
const ptr: *const S = @ptrCast(&bytes);
const val = ptr.c;
try expect(val == 5);
}
test "reinterpret bytes of an extern struct (with under-aligned fields) into another" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try testReinterpretExternStructAsExternStruct();
try comptime testReinterpretExternStructAsExternStruct();
}
fn testReinterpretExternStructAsExternStruct() !void {
const S1 = extern struct {
a: u8,
b: u16,
c: u8,
};
comptime var bytes align(2) = S1{ .a = 0, .b = 0, .c = 5 };
const S2 = extern struct {
a: u32 align(2),
c: u8,
};
const ptr: *const S2 = @ptrCast(&bytes);
const val = ptr.c;
try expect(val == 5);
}
test "reinterpret bytes of an extern struct into another" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try testReinterpretOverAlignedExternStructAsExternStruct();
try comptime testReinterpretOverAlignedExternStructAsExternStruct();
}
fn testReinterpretOverAlignedExternStructAsExternStruct() !void {
const S1 = extern struct {
a: u32,
b: u32,
c: u8,
};
comptime var bytes: S1 = .{ .a = 0, .b = 0, .c = 5 };
const S2 = extern struct {
a0: u32,
a1: u16,
a2: u16,
c: u8,
};
const ptr: *const S2 = @ptrCast(&bytes);
const val = ptr.c;
try expect(val == 5);
}
test "lower reinterpreted comptime field ptr (with under-aligned fields)" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
// Test lowering a field ptr
comptime var bytes align(2) = [_]u8{ 1, 2, 3, 4, 5, 6 };
const S = extern struct {
a: u32 align(2),
c: u8,
};
comptime var ptr = @as(*const S, @ptrCast(&bytes));
const val = &ptr.c;
try expect(val.* == 5);
// Test lowering an elem ptr
comptime var src_value = S{ .a = 15, .c = 5 };
comptime var ptr2 = @as(*[@sizeOf(S)]u8, @ptrCast(&src_value));
const val2 = &ptr2[4];
try expect(val2.* == 5);
}
test "lower reinterpreted comptime field ptr" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
// Test lowering a field ptr
comptime var bytes align(4) = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
const S = extern struct {
a: u32,
c: u8,
};
comptime var ptr = @as(*const S, @ptrCast(&bytes));
const val = &ptr.c;
try expect(val.* == 5);
// Test lowering an elem ptr
comptime var src_value = S{ .a = 15, .c = 5 };
comptime var ptr2 = @as(*[@sizeOf(S)]u8, @ptrCast(&src_value));
const val2 = &ptr2[4];
try expect(val2.* == 5);
}
test "reinterpret struct field at comptime" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const numNative = comptime Bytes.init(0x12345678);
if (native_endian != .little) {
try expect(std.mem.eql(u8, &[_]u8{ 0x12, 0x34, 0x56, 0x78 }, &numNative.bytes));
} else {
try expect(std.mem.eql(u8, &[_]u8{ 0x78, 0x56, 0x34, 0x12 }, &numNative.bytes));
}
}
const Bytes = struct {
bytes: [4]u8,
pub fn init(v: u32) Bytes {
var res: Bytes = undefined;
@as(*align(1) u32, @ptrCast(&res.bytes)).* = v;
return res;
}
};
test "ptrcast of const integer has the correct object size" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const is_value = ~@as(isize, @intCast(std.math.minInt(isize)));
const is_bytes = @as([*]const u8, @ptrCast(&is_value))[0..@sizeOf(isize)];
if (@sizeOf(isize) == 8) {
switch (native_endian) {
.little => {
try expect(is_bytes[0] == 0xff);
try expect(is_bytes[1] == 0xff);
try expect(is_bytes[2] == 0xff);
try expect(is_bytes[3] == 0xff);
try expect(is_bytes[4] == 0xff);
try expect(is_bytes[5] == 0xff);
try expect(is_bytes[6] == 0xff);
try expect(is_bytes[7] == 0x7f);
},
.big => {
try expect(is_bytes[0] == 0x7f);
try expect(is_bytes[1] == 0xff);
try expect(is_bytes[2] == 0xff);
try expect(is_bytes[3] == 0xff);
try expect(is_bytes[4] == 0xff);
try expect(is_bytes[5] == 0xff);
try expect(is_bytes[6] == 0xff);
try expect(is_bytes[7] == 0xff);
},
}
}
}
test "implicit optional pointer to optional anyopaque pointer" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
var buf: [4]u8 = "aoeu".*;
const x: ?[*]u8 = &buf;
const y: ?*anyopaque = x;
const z: *[4]u8 = @ptrCast(y);
try expect(std.mem.eql(u8, z, "aoeu"));
}
test "@ptrCast slice to slice" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn foo(slice: []u32) []i32 {
return @as([]i32, @ptrCast(slice));
}
};
var buf: [4]u32 = .{ 0, 0, 0, 0 };
const alias = S.foo(&buf);
alias[1] = 42;
try expect(buf[1] == 42);
try expect(alias.len == 4);
}
test "comptime @ptrCast a subset of an array, then write through it" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
comptime {
var buff: [16]u8 align(4) = undefined;
const len_bytes = @as(*u32, @ptrCast(&buff));
len_bytes.* = 16;
const source = "abcdef";
@memcpy(buff[4 .. 4 + source.len], source);
}
}
test "@ptrCast undefined value at comptime" {
const S = struct {
fn transmute(comptime T: type, comptime U: type, value: T) U {
return @as(*const U, @ptrCast(&value)).*;
}
};
comptime {
const x = S.transmute(u64, i32, undefined);
_ = x;
}
}
test "comptime @ptrCast with packed struct leaves value unmodified" {
const S = packed struct { three: u3 };
const st: S = .{ .three = 6 };
try expect(st.three == 6);
const p: *const [1]u3 = @ptrCast(&st);
try expect(p.*[0] == 6);
try expect(st.three == 6);
}
test "@ptrCast restructures comptime-only array" {
{
const a3a2: [3][2]comptime_int = .{
.{ 1, 2 },
.{ 3, 4 },
.{ 5, 6 },
};
const a2a3: *const [2][3]comptime_int = @ptrCast(&a3a2);
comptime assert(a2a3[0][0] == 1);
comptime assert(a2a3[0][1] == 2);
comptime assert(a2a3[0][2] == 3);
comptime assert(a2a3[1][0] == 4);
comptime assert(a2a3[1][1] == 5);
comptime assert(a2a3[1][2] == 6);
}
{
const a6a1: [6][1]comptime_int = .{
.{1}, .{2}, .{3}, .{4}, .{5}, .{6},
};
const a1a2a3: *const [1][2][3]comptime_int = @ptrCast(&a6a1);
comptime assert(a1a2a3[0][0][0] == 1);
comptime assert(a1a2a3[0][0][1] == 2);
comptime assert(a1a2a3[0][0][2] == 3);
comptime assert(a1a2a3[0][1][0] == 4);
comptime assert(a1a2a3[0][1][1] == 5);
comptime assert(a1a2a3[0][1][2] == 6);
}
{
const a1: [1]comptime_int = .{123};
const raw: *const comptime_int = @ptrCast(&a1);
comptime assert(raw.* == 123);
}
{
const raw: comptime_int = 123;
const a1: *const [1]comptime_int = @ptrCast(&raw);
comptime assert(a1[0] == 123);
}
}
test "@ptrCast restructures sliced comptime-only array" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const a3a2: [4][2]comptime_int = .{
.{ 1, 2 },
.{ 3, 4 },
.{ 5, 6 },
.{ 7, 8 },
};
const sub: *const [4]comptime_int = @ptrCast(a3a2[1..]);
comptime assert(sub[0] == 3);
comptime assert(sub[1] == 4);
comptime assert(sub[2] == 5);
comptime assert(sub[3] == 6);
}
test "@ptrCast slice multiplying length" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn doTheTest(zero: u32) !void {
const in: []const u32 = &.{ zero, zero };
const out: []const u8 = @ptrCast(in);
try expect(out.len == 8);
try expect(@as([*]const u8, @ptrCast(in.ptr)) == out.ptr);
}
};
try S.doTheTest(0);
try comptime S.doTheTest(0);
}
test "@ptrCast array pointer to slice multiplying length" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn doTheTest(zero: u32) !void {
const in: *const [2]u32 = &.{ zero, zero };
const out: []const u8 = @ptrCast(in);
try expect(out.len == 8);
try expect(out.ptr == @as([*]const u8, @ptrCast(in.ptr)));
}
};
try S.doTheTest(0);
try comptime S.doTheTest(0);
}
test "@ptrCast slice dividing length" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn doTheTest(zero: u8) !void {
const in: []const u8 = &.{ zero, zero, zero, zero, zero, zero, zero, zero };
const out: []align(1) const u32 = @ptrCast(in);
try expect(out.len == 2);
try expect(out.ptr == @as([*]align(1) const u32, @ptrCast(in.ptr)));
}
};
try S.doTheTest(0);
try comptime S.doTheTest(0);
}
test "@ptrCast array pointer to slice dividing length" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn doTheTest(zero: u8) !void {
const in: *const [8]u8 = &.{ zero, zero, zero, zero, zero, zero, zero, zero };
const out: []align(1) const u32 = @ptrCast(in);
try expect(out.len == 2);
try expect(out.ptr == @as([*]align(1) const u32, @ptrCast(in.ptr)));
}
};
try S.doTheTest(0);
try comptime S.doTheTest(0);
}
test "@ptrCast slice with complex length increase" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const TwoBytes = [2]u8;
const ThreeBytes = [3]u8;
const S = struct {
fn doTheTest(zero: ThreeBytes) !void {
const in: []const ThreeBytes = &.{ zero, zero };
const out: []const TwoBytes = @ptrCast(in);
try expect(out.len == 3);
try expect(out.ptr == @as([*]const TwoBytes, @ptrCast(in.ptr)));
}
};
try S.doTheTest(@splat(0));
try comptime S.doTheTest(@splat(0));
}
test "@ptrCast array pointer to slice with complex length increase" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const TwoBytes = [2]u8;
const ThreeBytes = [3]u8;
const S = struct {
fn doTheTest(zero: ThreeBytes) !void {
const in: *const [2]ThreeBytes = &.{ zero, zero };
const out: []const TwoBytes = @ptrCast(in);
try expect(out.len == 3);
try expect(out.ptr == @as([*]const TwoBytes, @ptrCast(in.ptr)));
}
};
try S.doTheTest(@splat(0));
try comptime S.doTheTest(@splat(0));
}
test "@ptrCast slice with complex length decrease" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const TwoBytes = [2]u8;
const ThreeBytes = [3]u8;
const S = struct {
fn doTheTest(zero: TwoBytes) !void {
const in: []const TwoBytes = &.{ zero, zero, zero };
const out: []const ThreeBytes = @ptrCast(in);
try expect(out.len == 2);
try expect(out.ptr == @as([*]const ThreeBytes, @ptrCast(in.ptr)));
}
};
try S.doTheTest(@splat(0));
try comptime S.doTheTest(@splat(0));
}
test "@ptrCast array pointer to slice with complex length decrease" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const TwoBytes = [2]u8;
const ThreeBytes = [3]u8;
const S = struct {
fn doTheTest(zero: TwoBytes) !void {
const in: *const [3]TwoBytes = &.{ zero, zero, zero };
const out: []const ThreeBytes = @ptrCast(in);
try expect(out.len == 2);
try expect(out.ptr == @as([*]const ThreeBytes, @ptrCast(in.ptr)));
}
};
try S.doTheTest(@splat(0));
try comptime S.doTheTest(@splat(0));
}
test "@ptrCast slice of zero-bit type to different slice" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn doTheTest(comptime T: type, zero_bits: []const T) !void {
const out: []const u8 = @ptrCast(zero_bits);
try expect(out.len == 0);
}
};
try S.doTheTest(void, &.{ {}, {}, {} });
try S.doTheTest(u0, &.{ 0, 0, 0, 0 });
try S.doTheTest(packed struct(u0) {}, &.{ .{}, .{} });
try comptime S.doTheTest(void, &.{ {}, {}, {} });
try comptime S.doTheTest(u0, &.{ 0, 0, 0, 0 });
try comptime S.doTheTest(packed struct(u0) {}, &.{ .{}, .{} });
}
test "@ptrCast single-item pointer to slice with length 1" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn doTheTest(comptime T: type, ptr: *const T) !void {
const slice: []const T = @ptrCast(ptr);
try expect(slice.len == 1);
try expect(&slice[0] == ptr);
}
};
try S.doTheTest(u8, &123);
try S.doTheTest(void, &{});
try S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
try comptime S.doTheTest(u8, &123);
try comptime S.doTheTest(void, &{});
try comptime S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
}
test "@ptrCast single-item pointer to slice of bytes" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn doTheTest(comptime T: type, ptr: *const T) !void {
const slice: []const u8 = @ptrCast(ptr);
try expect(slice.len == @sizeOf(T));
try expect(slice.ptr == @as([*]const u8, @ptrCast(ptr)));
}
};
try S.doTheTest(u16, &123);
try S.doTheTest(void, &{});
try S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
try comptime S.doTheTest(u16, &123);
try comptime S.doTheTest(void, &{});
try comptime S.doTheTest(struct { x: u32 }, &.{ .x = 123 });
}
test "@ptrCast array pointer removing sentinel" {
const in: *const [4:0]u8 = &.{ 1, 2, 3, 4 };
const out: []const i8 = @ptrCast(in);
comptime assert(out.len == 4);
comptime assert(out[0] == 1);
comptime assert(out[1] == 2);
comptime assert(out[2] == 3);
comptime assert(out[3] == 4);
}
test "@ptrcast larger type to smaller one" {
const T = packed struct { x: u17 };
const a: u32 = 0;
const b: *const T = @ptrCast(&a);
const c = b.x;
_ = c;
}