Skip to content

Commit 10fee1c

Browse files
committed
Sema: allow @ptrCast of slices changing the length
Also, refactor `Sema.ptrCastFull` to avoid emitting AIR `int_from_ptr` on slices.
1 parent 58c00a8 commit 10fee1c

11 files changed

+585
-113
lines changed

lib/std/debug.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
5454
@tagName(accessed), @tagName(active),
5555
});
5656
}
57+
pub fn sliceCastLenRemainder(src_len: usize) noreturn {
58+
@branchHint(.cold);
59+
std.debug.panicExtra(@returnAddress(), "slice length '{d}' does not divide exactly into destination elements", .{src_len});
60+
}
5761
pub fn reachedUnreachable() noreturn {
5862
@branchHint(.cold);
5963
call("reached unreachable code", @returnAddress());

lib/std/debug/no_panic.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ pub fn inactiveUnionField(_: anytype, _: anytype) noreturn {
3535
@trap();
3636
}
3737

38+
pub fn sliceCastLenRemainder(_: usize) noreturn {
39+
@branchHint(.cold);
40+
@trap();
41+
}
42+
3843
pub fn reachedUnreachable() noreturn {
3944
@branchHint(.cold);
4045
@trap();

lib/std/debug/simple_panic.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn {
4747
call("access of inactive union field", null);
4848
}
4949

50+
pub fn sliceCastLenRemainder(src_len: usize) noreturn {
51+
_ = src_len;
52+
call("slice length does not divide exactly into destination elements", null);
53+
}
54+
5055
pub fn reachedUnreachable() noreturn {
5156
call("reached unreachable code", null);
5257
}

src/Sema.zig

Lines changed: 275 additions & 112 deletions
Large diffs are not rendered by default.

src/Zcu.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ pub const BuiltinDecl = enum {
272272
@"panic.outOfBounds",
273273
@"panic.startGreaterThanEnd",
274274
@"panic.inactiveUnionField",
275+
@"panic.sliceCastLenRemainder",
275276
@"panic.reachedUnreachable",
276277
@"panic.unwrapNull",
277278
@"panic.castToNull",
@@ -348,6 +349,7 @@ pub const BuiltinDecl = enum {
348349
.@"panic.outOfBounds",
349350
.@"panic.startGreaterThanEnd",
350351
.@"panic.inactiveUnionField",
352+
.@"panic.sliceCastLenRemainder",
351353
.@"panic.reachedUnreachable",
352354
.@"panic.unwrapNull",
353355
.@"panic.castToNull",

test/behavior/ptrcast.zig

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,159 @@ test "@ptrCast restructures sliced comptime-only array" {
350350
comptime assert(sub[2] == 5);
351351
comptime assert(sub[3] == 6);
352352
}
353+
354+
test "@ptrCast slice multiplying length" {
355+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
356+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
357+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
358+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
359+
360+
const S = struct {
361+
fn doTheTest(zero: u32) !void {
362+
const in: []const u32 = &.{ zero, zero };
363+
const out: []const u8 = @ptrCast(in);
364+
try expect(out.len == 8);
365+
try expect(@as([*]const u8, @ptrCast(in.ptr)) == out.ptr);
366+
}
367+
};
368+
try S.doTheTest(0);
369+
try comptime S.doTheTest(0);
370+
}
371+
372+
test "@ptrCast array pointer to slice multiplying length" {
373+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
374+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
375+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
376+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
377+
378+
const S = struct {
379+
fn doTheTest(zero: u32) !void {
380+
const in: *const [2]u32 = &.{ zero, zero };
381+
const out: []const u8 = @ptrCast(in);
382+
try expect(out.len == 8);
383+
try expect(out.ptr == @as([*]const u8, @ptrCast(in.ptr)));
384+
}
385+
};
386+
try S.doTheTest(0);
387+
try comptime S.doTheTest(0);
388+
}
389+
390+
test "@ptrCast slice dividing length" {
391+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
392+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
393+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
394+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
395+
396+
const S = struct {
397+
fn doTheTest(zero: u8) !void {
398+
const in: []const u8 = &.{ zero, zero, zero, zero, zero, zero, zero, zero };
399+
const out: []align(1) const u32 = @ptrCast(in);
400+
try expect(out.len == 2);
401+
try expect(out.ptr == @as([*]align(1) const u32, @ptrCast(in.ptr)));
402+
}
403+
};
404+
try S.doTheTest(0);
405+
try comptime S.doTheTest(0);
406+
}
407+
408+
test "@ptrCast array pointer to slice dividing length" {
409+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
410+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
411+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
412+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
413+
414+
const S = struct {
415+
fn doTheTest(zero: u8) !void {
416+
const in: *const [8]u8 = &.{ zero, zero, zero, zero, zero, zero, zero, zero };
417+
const out: []align(1) const u32 = @ptrCast(in);
418+
try expect(out.len == 2);
419+
try expect(out.ptr == @as([*]align(1) const u32, @ptrCast(in.ptr)));
420+
}
421+
};
422+
try S.doTheTest(0);
423+
try comptime S.doTheTest(0);
424+
}
425+
426+
test "@ptrCast slice with complex length increase" {
427+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
428+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
429+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
430+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
431+
432+
const TwoBytes = [2]u8;
433+
const ThreeBytes = [3]u8;
434+
435+
const S = struct {
436+
fn doTheTest(zero: ThreeBytes) !void {
437+
const in: []const ThreeBytes = &.{ zero, zero };
438+
const out: []const TwoBytes = @ptrCast(in);
439+
try expect(out.len == 3);
440+
try expect(out.ptr == @as([*]const TwoBytes, @ptrCast(in.ptr)));
441+
}
442+
};
443+
try S.doTheTest(@splat(0));
444+
try comptime S.doTheTest(@splat(0));
445+
}
446+
447+
test "@ptrCast array pointer to slice with complex length increase" {
448+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
449+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
450+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
451+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
452+
453+
const TwoBytes = [2]u8;
454+
const ThreeBytes = [3]u8;
455+
456+
const S = struct {
457+
fn doTheTest(zero: ThreeBytes) !void {
458+
const in: *const [2]ThreeBytes = &.{ zero, zero };
459+
const out: []const TwoBytes = @ptrCast(in);
460+
try expect(out.len == 3);
461+
try expect(out.ptr == @as([*]const TwoBytes, @ptrCast(in.ptr)));
462+
}
463+
};
464+
try S.doTheTest(@splat(0));
465+
try comptime S.doTheTest(@splat(0));
466+
}
467+
468+
test "@ptrCast slice with complex length decrease" {
469+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
470+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
471+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
472+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
473+
474+
const TwoBytes = [2]u8;
475+
const ThreeBytes = [3]u8;
476+
477+
const S = struct {
478+
fn doTheTest(zero: TwoBytes) !void {
479+
const in: []const TwoBytes = &.{ zero, zero, zero };
480+
const out: []const ThreeBytes = @ptrCast(in);
481+
try expect(out.len == 2);
482+
try expect(out.ptr == @as([*]const ThreeBytes, @ptrCast(in.ptr)));
483+
}
484+
};
485+
try S.doTheTest(@splat(0));
486+
try comptime S.doTheTest(@splat(0));
487+
}
488+
489+
test "@ptrCast array pointer to slice with complex length decrease" {
490+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
491+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
492+
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
493+
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
494+
495+
const TwoBytes = [2]u8;
496+
const ThreeBytes = [3]u8;
497+
498+
const S = struct {
499+
fn doTheTest(zero: TwoBytes) !void {
500+
const in: *const [3]TwoBytes = &.{ zero, zero, zero };
501+
const out: []const ThreeBytes = @ptrCast(in);
502+
try expect(out.len == 2);
503+
try expect(out.ptr == @as([*]const ThreeBytes, @ptrCast(in.ptr)));
504+
}
505+
};
506+
try S.doTheTest(@splat(0));
507+
try comptime S.doTheTest(@splat(0));
508+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
comptime {
2+
const in: []const comptime_int = &.{0};
3+
const out: []const type = @ptrCast(in);
4+
_ = out;
5+
}
6+
7+
const One = u8;
8+
const Two = [2]u8;
9+
const Three = [3]u8;
10+
const Four = [4]u8;
11+
const Five = [5]u8;
12+
13+
// []One -> []Two (small to big, divides neatly)
14+
comptime {
15+
const in: []const One = &.{ 1, 0, 0 };
16+
const out: []const Two = @ptrCast(in);
17+
_ = out;
18+
}
19+
comptime {
20+
const in: *const [3]One = &.{ 1, 0, 0 };
21+
const out: []const Two = @ptrCast(in);
22+
_ = out;
23+
}
24+
25+
// []Four -> []Five (small to big, does not divide)
26+
comptime {
27+
const in: []const Four = &.{.{ 0, 0, 0, 0 }};
28+
const out: []const Five = @ptrCast(in);
29+
_ = out;
30+
}
31+
comptime {
32+
const in: *const [1]Four = &.{.{ 0, 0, 0, 0 }};
33+
const out: []const Five = @ptrCast(in);
34+
_ = out;
35+
}
36+
37+
// []Three -> []Two (big to small, does not divide)
38+
comptime {
39+
const in: []const Three = &.{.{ 0, 0, 0 }};
40+
const out: []const Two = @ptrCast(in);
41+
_ = out;
42+
}
43+
comptime {
44+
const in: *const [1]Three = &.{.{ 0, 0, 0 }};
45+
const out: []const Two = @ptrCast(in);
46+
_ = out;
47+
}
48+
49+
// error
50+
//
51+
// :3:31: error: cannot infer length of slice of 'type' from slice of 'comptime_int'
52+
// :16:30: error: slice length '3' does not divide exactly into destination elements
53+
// :21:30: error: slice length '3' does not divide exactly into destination elements
54+
// :28:31: error: slice length '1' does not divide exactly into destination elements
55+
// :33:31: error: slice length '1' does not divide exactly into destination elements
56+
// :40:30: error: slice length '1' does not divide exactly into destination elements
57+
// :45:30: error: slice length '1' does not divide exactly into destination elements
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! []One -> []Two (small to big, divides neatly)
2+
3+
const One = u8;
4+
const Two = [2]u8;
5+
6+
/// A runtime-known value to prevent these safety panics from being compile errors.
7+
var rt: u8 = 0;
8+
9+
pub fn main() void {
10+
const in: []const One = &.{ 1, 0, rt };
11+
const out: []const Two = @ptrCast(in);
12+
_ = out;
13+
std.process.exit(1);
14+
}
15+
16+
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
17+
if (std.mem.eql(u8, message, "slice length '3' does not divide exactly into destination elements")) {
18+
std.process.exit(0);
19+
}
20+
std.process.exit(1);
21+
}
22+
23+
const std = @import("std");
24+
25+
// run
26+
// backend=llvm
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! []Four -> []Five (small to big, does not divide)
2+
3+
const Four = [4]u8;
4+
const Five = [5]u8;
5+
6+
/// A runtime-known value to prevent these safety panics from being compile errors.
7+
var rt: u8 = 0;
8+
9+
pub fn main() void {
10+
const in: []const Four = &.{.{ 0, 0, 0, rt }};
11+
const out: []const Five = @ptrCast(in);
12+
_ = out;
13+
std.process.exit(1);
14+
}
15+
16+
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
17+
if (std.mem.eql(u8, message, "slice length '1' does not divide exactly into destination elements")) {
18+
std.process.exit(0);
19+
}
20+
std.process.exit(1);
21+
}
22+
23+
const std = @import("std");
24+
25+
// run
26+
// backend=llvm
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! []Three -> []Two (big to small, does not divide)
2+
3+
const Two = [2]u8;
4+
const Three = [3]u8;
5+
6+
/// A runtime-known value to prevent these safety panics from being compile errors.
7+
var rt: u8 = 0;
8+
9+
pub fn main() void {
10+
const in: []const Three = &.{.{ 0, 0, rt }};
11+
const out: []const Two = @ptrCast(in);
12+
_ = out;
13+
std.process.exit(1);
14+
}
15+
16+
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
17+
if (std.mem.eql(u8, message, "slice length '1' does not divide exactly into destination elements")) {
18+
std.process.exit(0);
19+
}
20+
std.process.exit(1);
21+
}
22+
23+
const std = @import("std");
24+
25+
// run
26+
// backend=llvm

test/incremental/change_panic_handler_explicit

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub const panic = struct {
2020
pub const outOfBounds = no_panic.outOfBounds;
2121
pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
2222
pub const inactiveUnionField = no_panic.inactiveUnionField;
23+
pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
2324
pub const reachedUnreachable = no_panic.reachedUnreachable;
2425
pub const unwrapNull = no_panic.unwrapNull;
2526
pub const castToNull = no_panic.castToNull;
@@ -66,6 +67,7 @@ pub const panic = struct {
6667
pub const outOfBounds = no_panic.outOfBounds;
6768
pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
6869
pub const inactiveUnionField = no_panic.inactiveUnionField;
70+
pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
6971
pub const reachedUnreachable = no_panic.reachedUnreachable;
7072
pub const unwrapNull = no_panic.unwrapNull;
7173
pub const castToNull = no_panic.castToNull;
@@ -112,7 +114,7 @@ pub const panic = struct {
112114
pub const outOfBounds = std.debug.no_panic.outOfBounds;
113115
pub const startGreaterThanEnd = std.debug.no_panic.startGreaterThanEnd;
114116
pub const inactiveUnionField = std.debug.no_panic.inactiveUnionField;
115-
pub const messages = std.debug.no_panic.messages;
117+
pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
116118
pub const reachedUnreachable = no_panic.reachedUnreachable;
117119
pub const unwrapNull = no_panic.unwrapNull;
118120
pub const castToNull = no_panic.castToNull;

0 commit comments

Comments
 (0)