mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Sema: allow @ptrCast single-item pointer to slice
Also, rework this logic a little to make it simpler. The length of the result slice is now computed in one place.
This commit is contained in:
parent
ac8720f399
commit
f2077f57ae
236
src/Sema.zig
236
src/Sema.zig
@ -22893,49 +22893,102 @@ fn ptrCastFull(
|
||||
try Type.fromInterned(src_info.child).resolveLayout(pt);
|
||||
try Type.fromInterned(dest_info.child).resolveLayout(pt);
|
||||
|
||||
const src_slice_like = src_info.flags.size == .slice or
|
||||
(src_info.flags.size == .one and Type.fromInterned(src_info.child).zigTypeTag(zcu) == .array);
|
||||
|
||||
const dest_slice_like = dest_info.flags.size == .slice or
|
||||
(dest_info.flags.size == .one and Type.fromInterned(dest_info.child).zigTypeTag(zcu) == .array);
|
||||
|
||||
if (dest_info.flags.size == .slice and !src_slice_like) {
|
||||
return sema.fail(block, src, "illegal pointer cast to slice", .{});
|
||||
const DestSliceLen = union(enum) {
|
||||
undef,
|
||||
constant: u64,
|
||||
equal_runtime_src_slice,
|
||||
change_runtime_src_slice: struct {
|
||||
bytes_per_src: u64,
|
||||
bytes_per_dest: u64,
|
||||
},
|
||||
};
|
||||
// Populated iff the destination type is a slice.
|
||||
const dest_slice_len: ?DestSliceLen = len: {
|
||||
switch (dest_info.flags.size) {
|
||||
.slice => {},
|
||||
.many, .c, .one => break :len null,
|
||||
}
|
||||
|
||||
// Only defined if `src_slice_like`
|
||||
const src_slice_like_elem: Type = if (src_slice_like) switch (src_info.flags.size) {
|
||||
.slice => .fromInterned(src_info.child),
|
||||
// pointer to array
|
||||
.one => Type.fromInterned(src_info.child).childType(zcu),
|
||||
// `null` means the operand is a runtime-known slice (so the length is runtime-known).
|
||||
const opt_src_len: ?u64 = switch (src_info.flags.size) {
|
||||
.one => 1,
|
||||
.slice => src_len: {
|
||||
const operand_val = try sema.resolveValue(operand) orelse break :src_len null;
|
||||
if (operand_val.isUndef(zcu)) break :len .undef;
|
||||
const slice_val = switch (operand_ty.zigTypeTag(zcu)) {
|
||||
.optional => operand_val.optionalValue(zcu) orelse break :len .undef,
|
||||
.pointer => operand_val,
|
||||
else => unreachable,
|
||||
} else undefined;
|
||||
|
||||
const slice_needs_len_change: bool = if (dest_info.flags.size == .slice) need_len_change: {
|
||||
const dest_elem: Type = .fromInterned(dest_info.child);
|
||||
if (src_slice_like_elem.toIntern() == dest_elem.toIntern()) {
|
||||
break :need_len_change false;
|
||||
};
|
||||
const slice_len_resolved = try sema.resolveLazyValue(.fromInterned(zcu.intern_pool.sliceLen(slice_val.toIntern())));
|
||||
if (slice_len_resolved.isUndef(zcu)) break :len .undef;
|
||||
break :src_len slice_len_resolved.toUnsignedInt(zcu);
|
||||
},
|
||||
.many, .c => {
|
||||
return sema.fail(block, src, "cannot infer length of slice from {s}", .{pointerSizeString(src_info.flags.size)});
|
||||
},
|
||||
};
|
||||
const dest_elem_ty: Type = .fromInterned(dest_info.child);
|
||||
const src_elem_ty: Type = .fromInterned(src_info.child);
|
||||
if (dest_elem_ty.toIntern() == src_elem_ty.toIntern()) {
|
||||
break :len if (opt_src_len) |l| .{ .constant = l } else .equal_runtime_src_slice;
|
||||
}
|
||||
if (src_slice_like_elem.comptimeOnly(zcu) or dest_elem.comptimeOnly(zcu)) {
|
||||
return sema.fail(block, src, "cannot infer length of slice of '{}' from slice of '{}'", .{ dest_elem.fmt(pt), src_slice_like_elem.fmt(pt) });
|
||||
}
|
||||
// It's okay for `src_slice_like_elem` to be 0-bit; the resulting slice will just always have 0 elements.
|
||||
// However, `dest_elem` can't be 0-bit. If it were, then either the source slice has 0 bits and we don't
|
||||
// know how what `result.len` should be, or the source has >0 bits and there is no valid `result.len`.
|
||||
const dest_elem_size = dest_elem.abiSize(zcu);
|
||||
if (!src_elem_ty.comptimeOnly(zcu) and !dest_elem_ty.comptimeOnly(zcu)) {
|
||||
const src_elem_size = src_elem_ty.abiSize(zcu);
|
||||
const dest_elem_size = dest_elem_ty.abiSize(zcu);
|
||||
if (dest_elem_size == 0) {
|
||||
return sema.fail(block, src, "cannot infer length of slice of '{}' from slice of '{}'", .{ dest_elem.fmt(pt), src_slice_like_elem.fmt(pt) });
|
||||
return sema.fail(block, src, "cannot infer length of slice of zero-bit '{}' from '{}'", .{ dest_elem_ty.fmt(pt), operand_ty.fmt(pt) });
|
||||
}
|
||||
const src_elem_size = src_slice_like_elem.abiSize(zcu);
|
||||
break :need_len_change src_elem_size != dest_elem_size;
|
||||
} else false;
|
||||
if (opt_src_len) |src_len| {
|
||||
const bytes = src_len * src_elem_size;
|
||||
const dest_len = std.math.divExact(u64, bytes, dest_elem_size) catch switch (src_info.flags.size) {
|
||||
.slice => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
|
||||
.one => return sema.fail(block, src, "type '{}' does not divide exactly into destination elements", .{src_elem_ty.fmt(pt)}),
|
||||
else => unreachable,
|
||||
};
|
||||
break :len .{ .constant = dest_len };
|
||||
}
|
||||
assert(src_info.flags.size == .slice);
|
||||
break :len .{ .change_runtime_src_slice = .{
|
||||
.bytes_per_src = src_elem_size,
|
||||
.bytes_per_dest = dest_elem_size,
|
||||
} };
|
||||
}
|
||||
// We apply rules for comptime memory consistent with comptime loads/stores, where arrays of
|
||||
// comptime-only types can be "restructured".
|
||||
const dest_base_ty: Type, const dest_base_per_elem: u64 = dest_elem_ty.arrayBase(zcu);
|
||||
const src_base_ty: Type, const src_base_per_elem: u64 = src_elem_ty.arrayBase(zcu);
|
||||
// The source value has `src_len * src_base_per_elem` values of type `src_base_ty`.
|
||||
// The result value will have `dest_len * dest_base_per_elem` values of type `dest_base_ty`.
|
||||
if (dest_base_ty.toIntern() != src_base_ty.toIntern()) {
|
||||
return sema.fail(block, src, "cannot infer length of comptime-only '{}' from incompatible '{}'", .{ dest_ty.fmt(pt), operand_ty.fmt(pt) });
|
||||
}
|
||||
// `src_base_ty` is comptime-only, so `src_elem_ty` is comptime-only, so `operand_ty` is
|
||||
// comptime-only, so `operand` is comptime-known, so `opt_src_len` is non-`null`.
|
||||
const src_len = opt_src_len.?;
|
||||
const base_len = src_len * src_base_per_elem;
|
||||
const dest_len = std.math.divExact(u64, base_len, dest_base_per_elem) catch switch (src_info.flags.size) {
|
||||
.slice => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
|
||||
.one => return sema.fail(block, src, "type '{}' does not divide exactly into destination elements", .{src_elem_ty.fmt(pt)}),
|
||||
else => unreachable,
|
||||
};
|
||||
break :len .{ .constant = dest_len };
|
||||
};
|
||||
|
||||
// The checking logic in this function must stay in sync with Sema.coerceInMemoryAllowedPtrs
|
||||
|
||||
if (!flags.ptr_cast) {
|
||||
const is_array_ptr_to_slice = b: {
|
||||
if (dest_info.flags.size != .slice) break :b false;
|
||||
if (src_info.flags.size != .one) break :b false;
|
||||
const src_pointer_child: Type = .fromInterned(src_info.child);
|
||||
if (src_pointer_child.zigTypeTag(zcu) != .array) break :b false;
|
||||
const src_elem = src_pointer_child.childType(zcu);
|
||||
break :b src_elem.toIntern() == dest_info.child;
|
||||
};
|
||||
|
||||
check_size: {
|
||||
if (src_info.flags.size == dest_info.flags.size) break :check_size;
|
||||
if (src_slice_like and dest_slice_like) break :check_size;
|
||||
if (is_array_ptr_to_slice) break :check_size;
|
||||
if (src_info.flags.size == .c) break :check_size;
|
||||
if (dest_info.flags.size == .c) break :check_size;
|
||||
return sema.failWithOwnedErrorMsg(block, msg: {
|
||||
@ -22993,7 +23046,7 @@ fn ptrCastFull(
|
||||
const coerced_sent = try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, src_info.sentinel, dest_info.child);
|
||||
if (dest_info.sentinel == coerced_sent) break :check_sent;
|
||||
}
|
||||
if (src_slice_like and src_info.flags.size == .one and dest_info.flags.size == .slice) {
|
||||
if (is_array_ptr_to_slice) {
|
||||
// [*]nT -> []T
|
||||
const arr_ty = Type.fromInterned(src_info.child);
|
||||
if (arr_ty.sentinel(zcu)) |src_sentinel| {
|
||||
@ -23173,12 +23226,9 @@ fn ptrCastFull(
|
||||
}
|
||||
}
|
||||
|
||||
const ptr_val: Value, const maybe_len_val: ?Value = switch (src_info.flags.size) {
|
||||
.slice => switch (zcu.intern_pool.indexToKey(operand_val.toIntern())) {
|
||||
.slice => |slice| .{ .fromInterned(slice.ptr), .fromInterned(slice.len) },
|
||||
else => unreachable,
|
||||
},
|
||||
.one, .many, .c => .{ operand_val, null },
|
||||
const ptr_val: Value = switch (src_info.flags.size) {
|
||||
.slice => .fromInterned(zcu.intern_pool.indexToKey(operand_val.toIntern()).slice.ptr),
|
||||
.one, .many, .c => operand_val,
|
||||
};
|
||||
|
||||
if (dest_align.compare(.gt, src_align)) {
|
||||
@ -23197,47 +23247,24 @@ fn ptrCastFull(
|
||||
}
|
||||
}
|
||||
|
||||
if (dest_info.flags.size != .slice) {
|
||||
if (dest_info.flags.size == .slice) {
|
||||
// Because the operand is comptime-known and not `null`, the slice length has already been computed:
|
||||
const len: Value = switch (dest_slice_len.?) {
|
||||
.undef => try pt.undefValue(.usize),
|
||||
.constant => |n| try pt.intValue(.usize, n),
|
||||
.equal_runtime_src_slice => unreachable,
|
||||
.change_runtime_src_slice => unreachable,
|
||||
};
|
||||
return Air.internedToRef(try pt.intern(.{ .slice = .{
|
||||
.ty = dest_ty.toIntern(),
|
||||
.ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
|
||||
.len = len.toIntern(),
|
||||
} }));
|
||||
} else {
|
||||
// Any to non-slice
|
||||
const new_ptr_val = try pt.getCoerced(ptr_val, dest_ty);
|
||||
return Air.internedToRef(new_ptr_val.toIntern());
|
||||
}
|
||||
|
||||
// Slice-like to slice, compatible element type
|
||||
// Here, we can preserve a lazy length.
|
||||
if (!slice_needs_len_change) {
|
||||
if (maybe_len_val) |len_val| {
|
||||
return Air.internedToRef(try pt.intern(.{ .slice = .{
|
||||
.ty = dest_ty.toIntern(),
|
||||
.ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
|
||||
.len = len_val.toIntern(),
|
||||
} }));
|
||||
}
|
||||
}
|
||||
|
||||
// Slice-like to slice, fallback
|
||||
|
||||
const src_len: u64 = if (maybe_len_val) |val|
|
||||
try val.toUnsignedIntSema(pt)
|
||||
else
|
||||
Type.fromInterned(src_info.child).arrayLen(zcu);
|
||||
|
||||
const dest_len: u64 = if (slice_needs_len_change) len: {
|
||||
const src_elem_size = src_slice_like_elem.abiSize(zcu);
|
||||
const dest_elem_size = Type.fromInterned(dest_info.child).abiSize(zcu);
|
||||
const bytes = src_len * src_elem_size;
|
||||
// Check: element count divides neatly
|
||||
break :len std.math.divExact(u64, bytes, dest_elem_size) catch |err| switch (err) {
|
||||
error.DivisionByZero => unreachable,
|
||||
error.UnexpectedRemainder => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
|
||||
};
|
||||
} else src_len;
|
||||
|
||||
return Air.internedToRef(try pt.intern(.{ .slice = .{
|
||||
.ty = dest_ty.toIntern(),
|
||||
.ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
|
||||
.len = (try pt.intValue(.usize, dest_len)).toIntern(),
|
||||
} }));
|
||||
}
|
||||
|
||||
try sema.validateRuntimeValue(block, operand_src, operand);
|
||||
@ -23246,6 +23273,11 @@ fn ptrCastFull(
|
||||
const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu);
|
||||
const need_align_check = can_cast_to_int and block.wantSafety() and dest_align.compare(.gt, src_align);
|
||||
|
||||
const slice_needs_len_change = if (dest_slice_len) |l| switch (l) {
|
||||
.undef, .equal_runtime_src_slice => false,
|
||||
.constant, .change_runtime_src_slice => true,
|
||||
} else false;
|
||||
|
||||
// `operand` might be a slice. If `need_operand_ptr`, we'll populate `operand_ptr` with the raw pointer.
|
||||
const need_operand_ptr = src_info.flags.size != .slice or // we already have it
|
||||
dest_info.flags.size != .slice or // the result is a raw pointer
|
||||
@ -23347,39 +23379,26 @@ fn ptrCastFull(
|
||||
// We need to deconstruct the slice (if applicable) and reconstruct it.
|
||||
assert(need_operand_ptr);
|
||||
|
||||
const result_len: Air.Inst.Ref = len: {
|
||||
if (src_info.flags.size == .slice and !slice_needs_len_change) {
|
||||
const result_len: Air.Inst.Ref = switch (dest_slice_len.?) {
|
||||
.undef => try pt.undefRef(.usize),
|
||||
.constant => |n| try pt.intRef(.usize, n),
|
||||
.equal_runtime_src_slice => len: {
|
||||
assert(need_operand_len);
|
||||
break :len operand_len;
|
||||
}
|
||||
|
||||
const src_elem_size = src_slice_like_elem.abiSize(zcu);
|
||||
const dest_elem_size = Type.fromInterned(dest_info.child).abiSize(zcu);
|
||||
if (src_info.flags.size != .slice) {
|
||||
assert(src_slice_like);
|
||||
const src_len = Type.fromInterned(src_info.child).arrayLen(zcu);
|
||||
const bytes = src_len * src_elem_size;
|
||||
const dest_len = std.math.divExact(u64, bytes, dest_elem_size) catch |err| switch (err) {
|
||||
error.DivisionByZero => unreachable,
|
||||
error.UnexpectedRemainder => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
|
||||
};
|
||||
break :len try pt.intRef(.usize, dest_len);
|
||||
}
|
||||
|
||||
},
|
||||
.change_runtime_src_slice => |change| len: {
|
||||
assert(need_operand_len);
|
||||
|
||||
// If `src_elem_size * n == dest_elem_size`, then just multiply the length by `n`.
|
||||
if (std.math.divExact(u64, src_elem_size, dest_elem_size)) |dest_per_src| {
|
||||
// If `mul / div` is a whole number, then just multiply the length by it.
|
||||
if (std.math.divExact(u64, change.bytes_per_src, change.bytes_per_dest)) |dest_per_src| {
|
||||
const multiplier = try pt.intRef(.usize, dest_per_src);
|
||||
break :len try block.addBinOp(.mul, operand_len, multiplier);
|
||||
} else |err| switch (err) {
|
||||
error.DivisionByZero => unreachable,
|
||||
error.UnexpectedRemainder => {}, // fall through to code below
|
||||
}
|
||||
|
||||
// If `src_elem_size == dest_elem_size * n`, then divide the length by `n`.
|
||||
// If `div / mul` is a whole number, then just divide the length by it.
|
||||
// This incurs a safety check.
|
||||
if (std.math.divExact(u64, dest_elem_size, src_elem_size)) |src_per_dest| {
|
||||
if (std.math.divExact(u64, change.bytes_per_dest, change.bytes_per_src)) |src_per_dest| {
|
||||
const divisor = try pt.intRef(.usize, src_per_dest);
|
||||
if (block.wantSafety()) {
|
||||
// Check that the element count divides neatly.
|
||||
@ -23392,22 +23411,17 @@ fn ptrCastFull(
|
||||
error.DivisionByZero => unreachable,
|
||||
error.UnexpectedRemainder => {}, // fall through to code below
|
||||
}
|
||||
|
||||
// Fallback: the elements don't divide easily.
|
||||
// We'll multiply up to a byte count, then divide down to a new element count.
|
||||
// This incurs a safety check.
|
||||
|
||||
const src_elem_size_ref = try pt.intRef(.usize, src_elem_size);
|
||||
const dest_elem_size_ref = try pt.intRef(.usize, dest_elem_size);
|
||||
|
||||
const byte_count = try block.addBinOp(.mul, operand_len, src_elem_size_ref);
|
||||
// Fallback: the elements don't divide easily. We'll multiply *and* divide. This incurs a safety check.
|
||||
const total_bytes_ref = try block.addBinOp(.mul, operand_len, try pt.intRef(.usize, change.bytes_per_src));
|
||||
const bytes_per_dest_ref = try pt.intRef(.usize, change.bytes_per_dest);
|
||||
if (block.wantSafety()) {
|
||||
// Check that `byte_count` divides neatly into `dest_elem_size`.
|
||||
const remainder = try block.addBinOp(.rem, byte_count, dest_elem_size_ref);
|
||||
// Check that `total_bytes_ref` divides neatly into `bytes_per_dest_ref`.
|
||||
const remainder = try block.addBinOp(.rem, total_bytes_ref, bytes_per_dest_ref);
|
||||
const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
|
||||
try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
|
||||
}
|
||||
break :len try block.addBinOp(.div_exact, byte_count, dest_elem_size_ref);
|
||||
break :len try block.addBinOp(.div_exact, total_bytes_ref, bytes_per_dest_ref);
|
||||
},
|
||||
};
|
||||
|
||||
const operand_ptr_ty = sema.typeOf(operand_ptr);
|
||||
|
||||
@ -527,3 +527,47 @@ test "@ptrCast slice of zero-bit type to different slice" {
|
||||
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_aarch64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
||||
|
||||
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_aarch64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
export fn foo(ptr: *u8) void {
|
||||
const slice: []align(1) u16 = @ptrCast(ptr);
|
||||
_ = slice;
|
||||
}
|
||||
|
||||
// error
|
||||
//
|
||||
// :2:35: error: type 'u8' does not divide exactly into destination elements
|
||||
@ -48,10 +48,10 @@ comptime {
|
||||
|
||||
// error
|
||||
//
|
||||
// :3:31: error: cannot infer length of slice of 'type' from slice of 'comptime_int'
|
||||
// :3:31: error: cannot infer length of comptime-only '[]const type' from incompatible '[]const comptime_int'
|
||||
// :16:30: error: slice length '3' does not divide exactly into destination elements
|
||||
// :21:30: error: slice length '3' does not divide exactly into destination elements
|
||||
// :21:30: error: type '[3]u8' does not divide exactly into destination elements
|
||||
// :28:31: error: slice length '1' does not divide exactly into destination elements
|
||||
// :33:31: error: slice length '1' does not divide exactly into destination elements
|
||||
// :33:31: error: type '[1][4]u8' does not divide exactly into destination elements
|
||||
// :40:30: error: slice length '1' does not divide exactly into destination elements
|
||||
// :45:30: error: slice length '1' does not divide exactly into destination elements
|
||||
// :45:30: error: type '[1][3]u8' does not divide exactly into destination elements
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user