authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-02-01 07:37:27+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-02-23 08:28:58+00:00
log5e20e9b4491dead0c56d9b0351677d5832058673
tree1b397bf48ac85822b52895439015f79b2372cadc
parentd6f8200294689b318e3a4f75be0ae4d05cfea9c0

Sema: allow `@ptrCast` of slices changing the length

Also, refactor `Sema.ptrCastFull` to not be a horrifying hellscape.

11 files changed, 597 insertions(+), 126 deletions(-)

lib/std/debug.zig+4
...@@ -54,6 +54,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {...@@ -54,6 +54,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
54 @tagName(accessed), @tagName(active),54 @tagName(accessed), @tagName(active),
55 });55 });
56 }56 }
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 }
57 pub fn reachedUnreachable() noreturn {61 pub fn reachedUnreachable() noreturn {
58 @branchHint(.cold);62 @branchHint(.cold);
59 call("reached unreachable code", @returnAddress());63 call("reached unreachable code", @returnAddress());
lib/std/debug/no_panic.zig+5
...@@ -35,6 +35,11 @@ pub fn inactiveUnionField(_: anytype, _: anytype) noreturn {...@@ -35,6 +35,11 @@ pub fn inactiveUnionField(_: anytype, _: anytype) noreturn {
35 @trap();35 @trap();
36}36}
3737
38pub fn sliceCastLenRemainder(_: usize) noreturn {
39 @branchHint(.cold);
40 @trap();
41}
42
38pub fn reachedUnreachable() noreturn {43pub fn reachedUnreachable() noreturn {
39 @branchHint(.cold);44 @branchHint(.cold);
40 @trap();45 @trap();
lib/std/debug/simple_panic.zig+5
...@@ -47,6 +47,11 @@ pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn {...@@ -47,6 +47,11 @@ pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn {
47 call("access of inactive union field", null);47 call("access of inactive union field", null);
48}48}
4949
50pub fn sliceCastLenRemainder(src_len: usize) noreturn {
51 _ = src_len;
52 call("slice length does not divide exactly into destination elements", null);
53}
54
50pub fn reachedUnreachable() noreturn {55pub fn reachedUnreachable() noreturn {
51 call("reached unreachable code", null);56 call("reached unreachable code", null);
52}57}
src/Sema.zig+287-125
...@@ -23414,18 +23414,29 @@ fn ptrCastFull(...@@ -23414,18 +23414,29 @@ fn ptrCastFull(
23414 return sema.fail(block, src, "illegal pointer cast to slice", .{});23414 return sema.fail(block, src, "illegal pointer cast to slice", .{});
23415 }23415 }
2341623416
23417 if (dest_info.flags.size == .slice) {23417 // Only defined if `src_slice_like`
23418 const src_elem_size = switch (src_info.flags.size) {23418 const src_slice_like_elem: Type = if (src_slice_like) switch (src_info.flags.size) {
23419 .slice => Type.fromInterned(src_info.child).abiSize(zcu),23419 .slice => .fromInterned(src_info.child),
23420 // pointer to array23420 // pointer to array
23421 .one => Type.fromInterned(src_info.child).childType(zcu).abiSize(zcu),23421 .one => Type.fromInterned(src_info.child).childType(zcu),
23422 else => unreachable,23422 else => unreachable,
23423 };23423 } else undefined;
23424 const dest_elem_size = Type.fromInterned(dest_info.child).abiSize(zcu);23424
23425 if (src_elem_size != dest_elem_size) {23425 const slice_needs_len_change: bool = if (dest_info.flags.size == .slice) need_len_change: {
23426 return sema.fail(block, src, "TODO: implement {s} between slices changing the length", .{operation});23426 const dest_elem: Type = .fromInterned(dest_info.child);
23427 if (src_slice_like_elem.toIntern() == dest_elem.toIntern()) {
23428 break :need_len_change false;
23427 }23429 }
23428 }23430 if (src_slice_like_elem.comptimeOnly(zcu) or dest_elem.comptimeOnly(zcu)) {
23431 return sema.fail(block, src, "cannot infer length of slice of '{}' from slice of '{}'", .{ dest_elem.fmt(pt), src_slice_like_elem.fmt(pt) });
23432 }
23433 const src_elem_size = src_slice_like_elem.abiSize(zcu);
23434 const dest_elem_size = dest_elem.abiSize(zcu);
23435 if (src_elem_size == 0 or dest_elem_size == 0) {
23436 return sema.fail(block, src, "cannot infer length of slice of '{}' from slice of '{}'", .{ dest_elem.fmt(pt), src_slice_like_elem.fmt(pt) });
23437 }
23438 break :need_len_change src_elem_size != dest_elem_size;
23439 } else false;
2342923440
23430 // The checking logic in this function must stay in sync with Sema.coerceInMemoryAllowedPtrs23441 // The checking logic in this function must stay in sync with Sema.coerceInMemoryAllowedPtrs
2343123442
...@@ -23638,155 +23649,300 @@ fn ptrCastFull(...@@ -23638,155 +23649,300 @@ fn ptrCastFull(
23638 }23649 }
23639 }23650 }
2364023651
23641 const ptr = if (src_info.flags.size == .slice and dest_info.flags.size != .slice) ptr: {23652 // Type validation done -- this cast is okay. Let's do it!
23642 if (operand_ty.zigTypeTag(zcu) == .optional) {23653 //
23643 break :ptr try sema.analyzeOptionalSlicePtr(block, operand_src, operand, operand_ty);23654 // `operand` is a maybe-optional pointer or slice.
23644 } else {23655 // `dest_ty` is a maybe-optional pointer or slice.
23645 break :ptr try sema.analyzeSlicePtr(block, operand_src, operand, operand_ty);23656 //
23646 }23657 // We have a few safety checks:
23647 } else operand;23658 // * if the destination does not allow zero, check the operand is not null / 0
23659 // * if the destination is more aligned than the operand, check the pointer alignment
23660 // * if `slice_needs_len_change`, check the element count divides neatly
2364823661
23649 const dest_ptr_ty = if (dest_info.flags.size == .slice and src_info.flags.size != .slice) blk: {23662 ct: {
23650 // Only convert to a many-pointer at first23663 if (flags.addrspace_cast) break :ct; // cannot `@addrSpaceCast` at comptime
23651 var info = dest_info;23664 const operand_val = try sema.resolveValue(operand) orelse break :ct;
23652 info.flags.size = .many;
23653 const ty = try pt.ptrTypeSema(info);
23654 if (dest_ty.zigTypeTag(zcu) == .optional) {
23655 break :blk try pt.optionalType(ty.toIntern());
23656 } else {
23657 break :blk ty;
23658 }
23659 } else dest_ty;
2366023665
23661 // Cannot do @addrSpaceCast at comptime23666 if (operand_val.isUndef(zcu)) {
23662 if (!flags.addrspace_cast) {23667 if (!dest_ty.ptrAllowsZero(zcu)) {
23663 if (try sema.resolveValue(ptr)) |ptr_val| {
23664 if (!dest_ty.ptrAllowsZero(zcu) and ptr_val.isUndef(zcu)) {
23665 return sema.failWithUseOfUndef(block, operand_src);23668 return sema.failWithUseOfUndef(block, operand_src);
23666 }23669 }
23667 if (!dest_ty.ptrAllowsZero(zcu) and ptr_val.isNull(zcu)) {23670 return pt.undefRef(dest_ty);
23671 }
23672
23673 if (operand_val.isNull(zcu)) {
23674 if (!dest_ty.ptrAllowsZero(zcu)) {
23668 return sema.fail(block, operand_src, "null pointer casted to type '{}'", .{dest_ty.fmt(pt)});23675 return sema.fail(block, operand_src, "null pointer casted to type '{}'", .{dest_ty.fmt(pt)});
23669 }23676 }
23670 if (dest_align.compare(.gt, src_align)) {23677 if (dest_ty.zigTypeTag(zcu) == .optional) {
23671 if (try ptr_val.getUnsignedIntSema(pt)) |addr| {23678 return Air.internedToRef((try pt.nullValue(dest_ty)).toIntern());
23672 if (!dest_align.check(addr)) {23679 } else {
23673 return sema.fail(block, operand_src, "pointer address 0x{X} is not aligned to {d} bytes", .{23680 return Air.internedToRef((try pt.ptrIntValue(dest_ty, 0)).toIntern());
23674 addr,23681 }
23675 dest_align.toByteUnits().?,23682 }
23676 });23683
23677 }23684 const ptr_val: Value, const maybe_len_val: ?Value = switch (src_info.flags.size) {
23685 .slice => switch (zcu.intern_pool.indexToKey(operand_val.toIntern())) {
23686 .slice => |slice| .{ .fromInterned(slice.ptr), .fromInterned(slice.len) },
23687 else => unreachable,
23688 },
23689 .one, .many, .c => .{ operand_val, null },
23690 };
23691
23692 if (dest_align.compare(.gt, src_align)) {
23693 if (try ptr_val.getUnsignedIntSema(pt)) |addr| {
23694 if (!dest_align.check(addr)) {
23695 return sema.fail(block, operand_src, "pointer address 0x{X} is not aligned to {d} bytes", .{
23696 addr,
23697 dest_align.toByteUnits().?,
23698 });
23678 }23699 }
23679 }23700 }
23680 if (dest_info.flags.size == .slice and src_info.flags.size != .slice) {23701 }
23681 if (ptr_val.isUndef(zcu)) return pt.undefRef(dest_ty);23702
23682 const arr_len = try pt.intValue(Type.usize, Type.fromInterned(src_info.child).arrayLen(zcu));23703 if (dest_info.flags.size != .slice) {
23683 const ptr_val_key = zcu.intern_pool.indexToKey(ptr_val.toIntern()).ptr;23704 // Any to non-slice
23684 return Air.internedToRef((try pt.intern(.{ .slice = .{23705 const new_ptr_val = try pt.getCoerced(ptr_val, dest_ty);
23706 return Air.internedToRef(new_ptr_val.toIntern());
23707 }
23708
23709 // Slice-like to slice, compatible element type
23710 // Here, we can preserve a lazy length.
23711 if (!slice_needs_len_change) {
23712 if (maybe_len_val) |len_val| {
23713 return Air.internedToRef(try pt.intern(.{ .slice = .{
23685 .ty = dest_ty.toIntern(),23714 .ty = dest_ty.toIntern(),
23686 .ptr = try pt.intern(.{ .ptr = .{23715 .ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
23687 .ty = dest_ty.slicePtrFieldType(zcu).toIntern(),23716 .len = len_val.toIntern(),
23688 .base_addr = ptr_val_key.base_addr,23717 } }));
23689 .byte_offset = ptr_val_key.byte_offset,
23690 } }),
23691 .len = arr_len.toIntern(),
23692 } })));
23693 } else {
23694 assert(dest_ptr_ty.eql(dest_ty, zcu));
23695 return Air.internedToRef((try pt.getCoerced(ptr_val, dest_ty)).toIntern());
23696 }23718 }
23697 }23719 }
23698 }
2369923720
23700 try sema.requireRuntimeBlock(block, src, null);23721 // Slice-like to slice, fallback
23701 try sema.validateRuntimeValue(block, operand_src, ptr);
2370223722
23703 if (block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu) and23723 const src_len: u64 = if (maybe_len_val) |val|
23704 (try Type.fromInterned(dest_info.child).hasRuntimeBitsSema(pt) or Type.fromInterned(dest_info.child).zigTypeTag(zcu) == .@"fn"))23724 try val.toUnsignedIntSema(pt)
23705 {
23706 const actual_ptr = if (src_info.flags.size == .slice)
23707 try sema.analyzeSlicePtr(block, src, ptr, operand_ty)
23708 else23725 else
23709 ptr;23726 Type.fromInterned(src_info.child).arrayLen(zcu);
23710 const ptr_int = try block.addBitCast(.usize, actual_ptr);23727
23711 const is_non_zero = try block.addBinOp(.cmp_neq, ptr_int, .zero_usize);23728 const dest_len: u64 = if (slice_needs_len_change) len: {
23712 const ok = if (src_info.flags.size == .slice and dest_info.flags.size == .slice) ok: {23729 const src_elem_size = src_slice_like_elem.abiSize(zcu);
23713 const len = try sema.analyzeSliceLen(block, operand_src, ptr);23730 const dest_elem_size = Type.fromInterned(dest_info.child).abiSize(zcu);
23714 const len_zero = try block.addBinOp(.cmp_eq, len, .zero_usize);23731 const bytes = src_len * src_elem_size;
23715 break :ok try block.addBinOp(.bool_or, len_zero, is_non_zero);23732 // Check: element count divides neatly
23716 } else is_non_zero;23733 break :len std.math.divExact(u64, bytes, dest_elem_size) catch |err| switch (err) {
23717 try sema.addSafetyCheck(block, src, ok, .cast_to_null);23734 error.DivisionByZero => unreachable,
23718 }23735 error.UnexpectedRemainder => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
23736 };
23737 } else src_len;
2371923738
23720 if (block.wantSafety() and23739 return Air.internedToRef(try pt.intern(.{ .slice = .{
23721 dest_align.compare(.gt, src_align) and23740 .ty = dest_ty.toIntern(),
23722 try Type.fromInterned(dest_info.child).hasRuntimeBitsSema(pt))23741 .ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
23723 {23742 .len = (try pt.intValue(.usize, dest_len)).toIntern(),
23724 const align_bytes_minus_1 = dest_align.toByteUnits().? - 1;23743 } }));
23725 const align_mask = Air.internedToRef((try pt.intValue(
23726 Type.usize,
23727 if (Type.fromInterned(dest_info.child).fnPtrMaskOrNull(zcu)) |mask|
23728 align_bytes_minus_1 & mask
23729 else
23730 align_bytes_minus_1,
23731 )).toIntern());
23732 const actual_ptr = if (src_info.flags.size == .slice)
23733 try sema.analyzeSlicePtr(block, src, ptr, operand_ty)
23734 else
23735 ptr;
23736 const ptr_int = try block.addBitCast(.usize, actual_ptr);
23737 const remainder = try block.addBinOp(.bit_and, ptr_int, align_mask);
23738 const is_aligned = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
23739 const ok = if (src_info.flags.size == .slice and dest_info.flags.size == .slice) ok: {
23740 const len = try sema.analyzeSliceLen(block, operand_src, ptr);
23741 const len_zero = try block.addBinOp(.cmp_eq, len, .zero_usize);
23742 break :ok try block.addBinOp(.bool_or, len_zero, is_aligned);
23743 } else is_aligned;
23744 try sema.addSafetyCheck(block, src, ok, .incorrect_alignment);
23745 }23744 }
2374623745
23747 // If we're going from an array pointer to a slice, this will only be the pointer part!23746 try sema.validateRuntimeValue(block, operand_src, operand);
23748 const result_ptr = if (flags.addrspace_cast) ptr: {23747
23749 // We can't change address spaces with a bitcast, so this requires two instructions23748 const need_null_check = block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu);
23750 var intermediate_info = src_info;23749 const need_align_check = block.wantSafety() and dest_align.compare(.gt, src_align);
23751 intermediate_info.flags.address_space = dest_info.flags.address_space;23750
23752 const intermediate_ptr_ty = try pt.ptrTypeSema(intermediate_info);23751 // `operand` might be a slice. If `need_operand_ptr`, we'll populate `operand_ptr` with the raw pointer.
23753 const intermediate_ty = if (dest_ptr_ty.zigTypeTag(zcu) == .optional) blk: {23752 const need_operand_ptr = src_info.flags.size != .slice or // we already have it
23753 dest_info.flags.size != .slice or // the result is a raw pointer
23754 need_null_check or // safety check happens on pointer
23755 need_align_check or // safety check happens on pointer
23756 flags.addrspace_cast or // AIR addrspace_cast acts on a pointer
23757 slice_needs_len_change; // to change the length, we reconstruct the slice
23758
23759 // This is not quite just the pointer part of `operand` -- it's also had the address space cast done already.
23760 const operand_ptr: Air.Inst.Ref = ptr: {
23761 if (!need_operand_ptr) break :ptr .none;
23762 // First, just get the pointer.
23763 const pre_addrspace_cast = inner: {
23764 if (src_info.flags.size != .slice) break :inner operand;
23765 if (operand_ty.zigTypeTag(zcu) == .optional) {
23766 break :inner try sema.analyzeOptionalSlicePtr(block, operand_src, operand, operand_ty);
23767 } else {
23768 break :inner try sema.analyzeSlicePtr(block, operand_src, operand, operand_ty);
23769 }
23770 };
23771 // Now, do an addrspace cast if necessary!
23772 if (!flags.addrspace_cast) break :ptr pre_addrspace_cast;
23773
23774 const intermediate_ptr_ty = try pt.ptrTypeSema(info: {
23775 var info = src_info;
23776 info.flags.address_space = dest_info.flags.address_space;
23777 break :info info;
23778 });
23779 const intermediate_ty = if (operand_ty.zigTypeTag(zcu) == .optional) blk: {
23754 break :blk try pt.optionalType(intermediate_ptr_ty.toIntern());23780 break :blk try pt.optionalType(intermediate_ptr_ty.toIntern());
23755 } else intermediate_ptr_ty;23781 } else intermediate_ptr_ty;
23756 const intermediate = try block.addInst(.{23782 break :ptr try block.addInst(.{
23757 .tag = .addrspace_cast,23783 .tag = .addrspace_cast,
23758 .data = .{ .ty_op = .{23784 .data = .{ .ty_op = .{
23759 .ty = Air.internedToRef(intermediate_ty.toIntern()),23785 .ty = Air.internedToRef(intermediate_ty.toIntern()),
23760 .operand = ptr,23786 .operand = pre_addrspace_cast,
23761 } },23787 } },
23762 });23788 });
23763 if (intermediate_ty.eql(dest_ptr_ty, zcu)) {
23764 // We only changed the address space, so no need for a bitcast
23765 break :ptr intermediate;
23766 }
23767 break :ptr try block.addBitCast(dest_ptr_ty, intermediate);
23768 } else ptr: {
23769 break :ptr try block.addBitCast(dest_ptr_ty, ptr);
23770 };23789 };
2377123790
23772 if (dest_info.flags.size == .slice and src_info.flags.size != .slice) {23791 // Whether we need to know if the (slice) operand has `len == 0`.
23773 // We have to construct a slice using the operand's child's array length23792 const need_operand_len_is_zero = src_info.flags.size == .slice and
23774 // Note that we know from the check at the start of the function that operand_ty is slice-like23793 dest_info.flags.size == .slice and
23775 const arr_len = Air.internedToRef((try pt.intValue(Type.usize, Type.fromInterned(src_info.child).arrayLen(zcu))).toIntern());23794 (need_null_check or need_align_check);
23795 // Whether we need to get the (slice) operand's `len`.
23796 const need_operand_len = need_len: {
23797 if (src_info.flags.size != .slice) break :need_len false;
23798 if (dest_info.flags.size != .slice) break :need_len false;
23799 if (need_operand_len_is_zero) break :need_len true;
23800 if (flags.addrspace_cast or slice_needs_len_change) break :need_len true;
23801 break :need_len false;
23802 };
23803 // `.none` if `!need_operand_len`.
23804 const operand_len: Air.Inst.Ref = len: {
23805 if (!need_operand_len) break :len .none;
23806 break :len try block.addTyOp(.slice_len, .usize, operand);
23807 };
23808 // `.none` if `!need_operand_len_is_zero`.
23809 const operand_len_is_zero: Air.Inst.Ref = zero: {
23810 if (!need_operand_len_is_zero) break :zero .none;
23811 assert(need_operand_len);
23812 break :zero try block.addBinOp(.cmp_eq, operand_len, .zero_usize);
23813 };
23814
23815 // `operand_ptr` converted to an integer, for safety checks.
23816 const operand_ptr_int: Air.Inst.Ref = if (need_null_check or need_align_check) i: {
23817 assert(need_operand_ptr);
23818 break :i try block.addBitCast(.usize, operand_ptr);
23819 } else .none;
23820
23821 if (need_null_check) {
23822 assert(operand_ptr_int != .none);
23823 const ptr_is_non_zero = try block.addBinOp(.cmp_neq, operand_ptr_int, .zero_usize);
23824 const ok = if (src_info.flags.size == .slice and dest_info.flags.size == .slice) ok: {
23825 break :ok try block.addBinOp(.bool_or, operand_len_is_zero, ptr_is_non_zero);
23826 } else ptr_is_non_zero;
23827 try sema.addSafetyCheck(block, src, ok, .cast_to_null);
23828 }
23829 if (need_align_check) {
23830 assert(operand_ptr_int != .none);
23831 const align_mask = try pt.intRef(.usize, mask: {
23832 const target_ptr_mask: u64 = Type.fromInterned(dest_info.child).fnPtrMaskOrNull(zcu) orelse ~@as(u64, 0);
23833 break :mask (dest_align.toByteUnits().? - 1) & target_ptr_mask;
23834 });
23835 const ptr_masked = try block.addBinOp(.bit_and, operand_ptr_int, align_mask);
23836 const is_aligned = try block.addBinOp(.cmp_eq, ptr_masked, .zero_usize);
23837 const ok = if (src_info.flags.size == .slice and dest_info.flags.size == .slice) ok: {
23838 break :ok try block.addBinOp(.bool_or, operand_len_is_zero, is_aligned);
23839 } else is_aligned;
23840 try sema.addSafetyCheck(block, src, ok, .incorrect_alignment);
23841 }
23842
23843 if (dest_info.flags.size == .slice) {
23844 if (src_info.flags.size == .slice and !flags.addrspace_cast and !slice_needs_len_change) {
23845 // Fast path: just bitcast!
23846 return block.addBitCast(dest_ty, operand);
23847 }
23848
23849 // We need to deconstruct the slice (if applicable) and reconstruct it.
23850 assert(need_operand_ptr);
23851
23852 const result_len: Air.Inst.Ref = len: {
23853 if (src_info.flags.size == .slice and !slice_needs_len_change) {
23854 assert(need_operand_len);
23855 break :len operand_len;
23856 }
23857
23858 const src_elem_size = src_slice_like_elem.abiSize(zcu);
23859 const dest_elem_size = Type.fromInterned(dest_info.child).abiSize(zcu);
23860 if (src_info.flags.size != .slice) {
23861 assert(src_slice_like);
23862 const src_len = Type.fromInterned(src_info.child).arrayLen(zcu);
23863 const bytes = src_len * src_elem_size;
23864 const dest_len = std.math.divExact(u64, bytes, dest_elem_size) catch |err| switch (err) {
23865 error.DivisionByZero => unreachable,
23866 error.UnexpectedRemainder => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
23867 };
23868 break :len try pt.intRef(.usize, dest_len);
23869 }
23870
23871 assert(need_operand_len);
23872
23873 // If `src_elem_size * n == dest_elem_size`, then just multiply the length by `n`.
23874 if (std.math.divExact(u64, src_elem_size, dest_elem_size)) |dest_per_src| {
23875 const multiplier = try pt.intRef(.usize, dest_per_src);
23876 break :len try block.addBinOp(.mul, operand_len, multiplier);
23877 } else |err| switch (err) {
23878 error.DivisionByZero => unreachable,
23879 error.UnexpectedRemainder => {}, // fall through to code below
23880 }
23881
23882 // If `src_elem_size == dest_elem_size * n`, then divide the length by `n`.
23883 // This incurs a safety check.
23884 if (std.math.divExact(u64, dest_elem_size, src_elem_size)) |src_per_dest| {
23885 const divisor = try pt.intRef(.usize, src_per_dest);
23886 if (block.wantSafety()) {
23887 // Check that the element count divides neatly.
23888 const remainder = try block.addBinOp(.rem, operand_len, divisor);
23889 const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
23890 try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
23891 }
23892 break :len try block.addBinOp(.div_exact, operand_len, divisor);
23893 } else |err| switch (err) {
23894 error.DivisionByZero => unreachable,
23895 error.UnexpectedRemainder => {}, // fall through to code below
23896 }
23897
23898 // Fallback: the elements don't divide easily.
23899 // We'll multiply up to a byte count, then divide down to a new element count.
23900 // This incurs a safety check.
23901
23902 const src_elem_size_ref = try pt.intRef(.usize, src_elem_size);
23903 const dest_elem_size_ref = try pt.intRef(.usize, dest_elem_size);
23904
23905 const byte_count = try block.addBinOp(.mul, operand_len, src_elem_size_ref);
23906 if (block.wantSafety()) {
23907 // Check that `byte_count` divides neatly into `dest_elem_size`.
23908 const remainder = try block.addBinOp(.rem, byte_count, dest_elem_size_ref);
23909 const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
23910 try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
23911 }
23912 break :len try block.addBinOp(.div_exact, byte_count, dest_elem_size_ref);
23913 };
23914
23915 const operand_ptr_ty = sema.typeOf(operand_ptr);
23916 const want_ptr_ty = switch (dest_ty.zigTypeTag(zcu)) {
23917 .optional => try pt.optionalType(dest_ty.childType(zcu).slicePtrFieldType(zcu).toIntern()),
23918 .pointer => dest_ty.slicePtrFieldType(zcu),
23919 else => unreachable,
23920 };
23921 const coerced_ptr = if (operand_ptr_ty.toIntern() != want_ptr_ty.toIntern()) ptr: {
23922 break :ptr try block.addBitCast(want_ptr_ty, operand_ptr);
23923 } else operand_ptr;
23924
23776 return block.addInst(.{23925 return block.addInst(.{
23777 .tag = .slice,23926 .tag = .slice,
23778 .data = .{ .ty_pl = .{23927 .data = .{ .ty_pl = .{
23779 .ty = Air.internedToRef(dest_ty.toIntern()),23928 .ty = Air.internedToRef(dest_ty.toIntern()),
23780 .payload = try sema.addExtra(Air.Bin{23929 .payload = try sema.addExtra(Air.Bin{
23781 .lhs = result_ptr,23930 .lhs = coerced_ptr,
23782 .rhs = arr_len,23931 .rhs = result_len,
23783 }),23932 }),
23784 } },23933 } },
23785 });23934 });
23786 } else {23935 } else {
23787 assert(dest_ptr_ty.eql(dest_ty, zcu));23936 assert(need_operand_ptr);
23788 try sema.checkKnownAllocPtr(block, operand, result_ptr);23937 // We just need to bitcast the pointer, if necessary.
23789 return result_ptr;23938 // It might not be necessary, since we might have just needed the `addrspace_cast`.
23939 const result = if (sema.typeOf(operand_ptr).toIntern() == dest_ty.toIntern())
23940 operand_ptr
23941 else
23942 try block.addBitCast(dest_ty, operand_ptr);
23943
23944 try sema.checkKnownAllocPtr(block, operand, result);
23945 return result;
23790 }23946 }
23791}23947}
2379223948
...@@ -38828,6 +38984,12 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.BuiltinDecl) CompileError!Typ...@@ -38828,6 +38984,12 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.BuiltinDecl) CompileError!Typ
38828 .return_type = .noreturn_type,38984 .return_type = .noreturn_type,
38829 }),38985 }),
3883038986
38987 // `fn (usize) noreturn`
38988 .@"panic.sliceCastLenRemainder" => try pt.funcType(.{
38989 .param_types = &.{.usize_type},
38990 .return_type = .noreturn_type,
38991 }),
38992
38831 // `fn (usize, usize) noreturn`38993 // `fn (usize, usize) noreturn`
38832 .@"panic.outOfBounds",38994 .@"panic.outOfBounds",
38833 .@"panic.startGreaterThanEnd",38995 .@"panic.startGreaterThanEnd",
src/Zcu.zig+2
...@@ -276,6 +276,7 @@ pub const BuiltinDecl = enum {...@@ -276,6 +276,7 @@ pub const BuiltinDecl = enum {
276 @"panic.outOfBounds",276 @"panic.outOfBounds",
277 @"panic.startGreaterThanEnd",277 @"panic.startGreaterThanEnd",
278 @"panic.inactiveUnionField",278 @"panic.inactiveUnionField",
279 @"panic.sliceCastLenRemainder",
279 @"panic.reachedUnreachable",280 @"panic.reachedUnreachable",
280 @"panic.unwrapNull",281 @"panic.unwrapNull",
281 @"panic.castToNull",282 @"panic.castToNull",
...@@ -352,6 +353,7 @@ pub const BuiltinDecl = enum {...@@ -352,6 +353,7 @@ pub const BuiltinDecl = enum {
352 .@"panic.outOfBounds",353 .@"panic.outOfBounds",
353 .@"panic.startGreaterThanEnd",354 .@"panic.startGreaterThanEnd",
354 .@"panic.inactiveUnionField",355 .@"panic.inactiveUnionField",
356 .@"panic.sliceCastLenRemainder",
355 .@"panic.reachedUnreachable",357 .@"panic.reachedUnreachable",
356 .@"panic.unwrapNull",358 .@"panic.unwrapNull",
357 .@"panic.castToNull",359 .@"panic.castToNull",
test/behavior/ptrcast.zig+156
...@@ -350,3 +350,159 @@ test "@ptrCast restructures sliced comptime-only array" {...@@ -350,3 +350,159 @@ test "@ptrCast restructures sliced comptime-only array" {
350 comptime assert(sub[2] == 5);350 comptime assert(sub[2] == 5);
351 comptime assert(sub[3] == 6);351 comptime assert(sub[3] == 6);
352}352}
353
354test "@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
372test "@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
390test "@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
408test "@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
426test "@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
447test "@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
468test "@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
489test "@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}
test/cases/compile_errors/slice_cast_change_len.zig created+57
...@@ -0,0 +1,57 @@
1comptime {
2 const in: []const comptime_int = &.{0};
3 const out: []const type = @ptrCast(in);
4 _ = out;
5}
6
7const One = u8;
8const Two = [2]u8;
9const Three = [3]u8;
10const Four = [4]u8;
11const Five = [5]u8;
12
13// []One -> []Two (small to big, divides neatly)
14comptime {
15 const in: []const One = &.{ 1, 0, 0 };
16 const out: []const Two = @ptrCast(in);
17 _ = out;
18}
19comptime {
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)
26comptime {
27 const in: []const Four = &.{.{ 0, 0, 0, 0 }};
28 const out: []const Five = @ptrCast(in);
29 _ = out;
30}
31comptime {
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)
38comptime {
39 const in: []const Three = &.{.{ 0, 0, 0 }};
40 const out: []const Two = @ptrCast(in);
41 _ = out;
42}
43comptime {
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
test/cases/safety/slice_cast_change_len_0.zig created+26
...@@ -0,0 +1,26 @@
1//! []One -> []Two (small to big, divides neatly)
2
3const One = u8;
4const Two = [2]u8;
5
6/// A runtime-known value to prevent these safety panics from being compile errors.
7var rt: u8 = 0;
8
9pub 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
16pub 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
23const std = @import("std");
24
25// run
26// backend=llvm
test/cases/safety/slice_cast_change_len_1.zig created+26
...@@ -0,0 +1,26 @@
1//! []Four -> []Five (small to big, does not divide)
2
3const Four = [4]u8;
4const Five = [5]u8;
5
6/// A runtime-known value to prevent these safety panics from being compile errors.
7var rt: u8 = 0;
8
9pub 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
16pub 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
23const std = @import("std");
24
25// run
26// backend=llvm
test/cases/safety/slice_cast_change_len_2.zig created+26
...@@ -0,0 +1,26 @@
1//! []Three -> []Two (big to small, does not divide)
2
3const Two = [2]u8;
4const Three = [3]u8;
5
6/// A runtime-known value to prevent these safety panics from being compile errors.
7var rt: u8 = 0;
8
9pub 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
16pub 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
23const std = @import("std");
24
25// run
26// backend=llvm
test/incremental/change_panic_handler_explicit+3-1
...@@ -20,6 +20,7 @@ pub const panic = struct {...@@ -20,6 +20,7 @@ pub const panic = struct {
20 pub const outOfBounds = no_panic.outOfBounds;20 pub const outOfBounds = no_panic.outOfBounds;
21 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;21 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
22 pub const inactiveUnionField = no_panic.inactiveUnionField;22 pub const inactiveUnionField = no_panic.inactiveUnionField;
23 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
23 pub const reachedUnreachable = no_panic.reachedUnreachable;24 pub const reachedUnreachable = no_panic.reachedUnreachable;
24 pub const unwrapNull = no_panic.unwrapNull;25 pub const unwrapNull = no_panic.unwrapNull;
25 pub const castToNull = no_panic.castToNull;26 pub const castToNull = no_panic.castToNull;
...@@ -66,6 +67,7 @@ pub const panic = struct {...@@ -66,6 +67,7 @@ pub const panic = struct {
66 pub const outOfBounds = no_panic.outOfBounds;67 pub const outOfBounds = no_panic.outOfBounds;
67 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;68 pub const startGreaterThanEnd = no_panic.startGreaterThanEnd;
68 pub const inactiveUnionField = no_panic.inactiveUnionField;69 pub const inactiveUnionField = no_panic.inactiveUnionField;
70 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
69 pub const reachedUnreachable = no_panic.reachedUnreachable;71 pub const reachedUnreachable = no_panic.reachedUnreachable;
70 pub const unwrapNull = no_panic.unwrapNull;72 pub const unwrapNull = no_panic.unwrapNull;
71 pub const castToNull = no_panic.castToNull;73 pub const castToNull = no_panic.castToNull;
...@@ -112,7 +114,7 @@ pub const panic = struct {...@@ -112,7 +114,7 @@ pub const panic = struct {
112 pub const outOfBounds = std.debug.no_panic.outOfBounds;114 pub const outOfBounds = std.debug.no_panic.outOfBounds;
113 pub const startGreaterThanEnd = std.debug.no_panic.startGreaterThanEnd;115 pub const startGreaterThanEnd = std.debug.no_panic.startGreaterThanEnd;
114 pub const inactiveUnionField = std.debug.no_panic.inactiveUnionField;116 pub const inactiveUnionField = std.debug.no_panic.inactiveUnionField;
115 pub const messages = std.debug.no_panic.messages;117 pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder;
116 pub const reachedUnreachable = no_panic.reachedUnreachable;118 pub const reachedUnreachable = no_panic.reachedUnreachable;
117 pub const unwrapNull = no_panic.unwrapNull;119 pub const unwrapNull = no_panic.unwrapNull;
118 pub const castToNull = no_panic.castToNull;120 pub const castToNull = no_panic.castToNull;