Skip to content

Commit f2077f5

Browse files
committed
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.
1 parent ac8720f commit f2077f5

File tree

4 files changed

+204
-138
lines changed

4 files changed

+204
-138
lines changed

src/Sema.zig

Lines changed: 148 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -22893,49 +22893,102 @@ fn ptrCastFull(
2289322893
try Type.fromInterned(src_info.child).resolveLayout(pt);
2289422894
try Type.fromInterned(dest_info.child).resolveLayout(pt);
2289522895

22896-
const src_slice_like = src_info.flags.size == .slice or
22897-
(src_info.flags.size == .one and Type.fromInterned(src_info.child).zigTypeTag(zcu) == .array);
22898-
22899-
const dest_slice_like = dest_info.flags.size == .slice or
22900-
(dest_info.flags.size == .one and Type.fromInterned(dest_info.child).zigTypeTag(zcu) == .array);
22901-
22902-
if (dest_info.flags.size == .slice and !src_slice_like) {
22903-
return sema.fail(block, src, "illegal pointer cast to slice", .{});
22904-
}
22905-
22906-
// Only defined if `src_slice_like`
22907-
const src_slice_like_elem: Type = if (src_slice_like) switch (src_info.flags.size) {
22908-
.slice => .fromInterned(src_info.child),
22909-
// pointer to array
22910-
.one => Type.fromInterned(src_info.child).childType(zcu),
22911-
else => unreachable,
22912-
} else undefined;
22913-
22914-
const slice_needs_len_change: bool = if (dest_info.flags.size == .slice) need_len_change: {
22915-
const dest_elem: Type = .fromInterned(dest_info.child);
22916-
if (src_slice_like_elem.toIntern() == dest_elem.toIntern()) {
22917-
break :need_len_change false;
22918-
}
22919-
if (src_slice_like_elem.comptimeOnly(zcu) or dest_elem.comptimeOnly(zcu)) {
22920-
return sema.fail(block, src, "cannot infer length of slice of '{}' from slice of '{}'", .{ dest_elem.fmt(pt), src_slice_like_elem.fmt(pt) });
22921-
}
22922-
// It's okay for `src_slice_like_elem` to be 0-bit; the resulting slice will just always have 0 elements.
22923-
// However, `dest_elem` can't be 0-bit. If it were, then either the source slice has 0 bits and we don't
22924-
// know how what `result.len` should be, or the source has >0 bits and there is no valid `result.len`.
22925-
const dest_elem_size = dest_elem.abiSize(zcu);
22926-
if (dest_elem_size == 0) {
22927-
return sema.fail(block, src, "cannot infer length of slice of '{}' from slice of '{}'", .{ dest_elem.fmt(pt), src_slice_like_elem.fmt(pt) });
22896+
const DestSliceLen = union(enum) {
22897+
undef,
22898+
constant: u64,
22899+
equal_runtime_src_slice,
22900+
change_runtime_src_slice: struct {
22901+
bytes_per_src: u64,
22902+
bytes_per_dest: u64,
22903+
},
22904+
};
22905+
// Populated iff the destination type is a slice.
22906+
const dest_slice_len: ?DestSliceLen = len: {
22907+
switch (dest_info.flags.size) {
22908+
.slice => {},
22909+
.many, .c, .one => break :len null,
22910+
}
22911+
// `null` means the operand is a runtime-known slice (so the length is runtime-known).
22912+
const opt_src_len: ?u64 = switch (src_info.flags.size) {
22913+
.one => 1,
22914+
.slice => src_len: {
22915+
const operand_val = try sema.resolveValue(operand) orelse break :src_len null;
22916+
if (operand_val.isUndef(zcu)) break :len .undef;
22917+
const slice_val = switch (operand_ty.zigTypeTag(zcu)) {
22918+
.optional => operand_val.optionalValue(zcu) orelse break :len .undef,
22919+
.pointer => operand_val,
22920+
else => unreachable,
22921+
};
22922+
const slice_len_resolved = try sema.resolveLazyValue(.fromInterned(zcu.intern_pool.sliceLen(slice_val.toIntern())));
22923+
if (slice_len_resolved.isUndef(zcu)) break :len .undef;
22924+
break :src_len slice_len_resolved.toUnsignedInt(zcu);
22925+
},
22926+
.many, .c => {
22927+
return sema.fail(block, src, "cannot infer length of slice from {s}", .{pointerSizeString(src_info.flags.size)});
22928+
},
22929+
};
22930+
const dest_elem_ty: Type = .fromInterned(dest_info.child);
22931+
const src_elem_ty: Type = .fromInterned(src_info.child);
22932+
if (dest_elem_ty.toIntern() == src_elem_ty.toIntern()) {
22933+
break :len if (opt_src_len) |l| .{ .constant = l } else .equal_runtime_src_slice;
22934+
}
22935+
if (!src_elem_ty.comptimeOnly(zcu) and !dest_elem_ty.comptimeOnly(zcu)) {
22936+
const src_elem_size = src_elem_ty.abiSize(zcu);
22937+
const dest_elem_size = dest_elem_ty.abiSize(zcu);
22938+
if (dest_elem_size == 0) {
22939+
return sema.fail(block, src, "cannot infer length of slice of zero-bit '{}' from '{}'", .{ dest_elem_ty.fmt(pt), operand_ty.fmt(pt) });
22940+
}
22941+
if (opt_src_len) |src_len| {
22942+
const bytes = src_len * src_elem_size;
22943+
const dest_len = std.math.divExact(u64, bytes, dest_elem_size) catch switch (src_info.flags.size) {
22944+
.slice => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
22945+
.one => return sema.fail(block, src, "type '{}' does not divide exactly into destination elements", .{src_elem_ty.fmt(pt)}),
22946+
else => unreachable,
22947+
};
22948+
break :len .{ .constant = dest_len };
22949+
}
22950+
assert(src_info.flags.size == .slice);
22951+
break :len .{ .change_runtime_src_slice = .{
22952+
.bytes_per_src = src_elem_size,
22953+
.bytes_per_dest = dest_elem_size,
22954+
} };
2292822955
}
22929-
const src_elem_size = src_slice_like_elem.abiSize(zcu);
22930-
break :need_len_change src_elem_size != dest_elem_size;
22931-
} else false;
22956+
// We apply rules for comptime memory consistent with comptime loads/stores, where arrays of
22957+
// comptime-only types can be "restructured".
22958+
const dest_base_ty: Type, const dest_base_per_elem: u64 = dest_elem_ty.arrayBase(zcu);
22959+
const src_base_ty: Type, const src_base_per_elem: u64 = src_elem_ty.arrayBase(zcu);
22960+
// The source value has `src_len * src_base_per_elem` values of type `src_base_ty`.
22961+
// The result value will have `dest_len * dest_base_per_elem` values of type `dest_base_ty`.
22962+
if (dest_base_ty.toIntern() != src_base_ty.toIntern()) {
22963+
return sema.fail(block, src, "cannot infer length of comptime-only '{}' from incompatible '{}'", .{ dest_ty.fmt(pt), operand_ty.fmt(pt) });
22964+
}
22965+
// `src_base_ty` is comptime-only, so `src_elem_ty` is comptime-only, so `operand_ty` is
22966+
// comptime-only, so `operand` is comptime-known, so `opt_src_len` is non-`null`.
22967+
const src_len = opt_src_len.?;
22968+
const base_len = src_len * src_base_per_elem;
22969+
const dest_len = std.math.divExact(u64, base_len, dest_base_per_elem) catch switch (src_info.flags.size) {
22970+
.slice => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
22971+
.one => return sema.fail(block, src, "type '{}' does not divide exactly into destination elements", .{src_elem_ty.fmt(pt)}),
22972+
else => unreachable,
22973+
};
22974+
break :len .{ .constant = dest_len };
22975+
};
2293222976

2293322977
// The checking logic in this function must stay in sync with Sema.coerceInMemoryAllowedPtrs
2293422978

2293522979
if (!flags.ptr_cast) {
22980+
const is_array_ptr_to_slice = b: {
22981+
if (dest_info.flags.size != .slice) break :b false;
22982+
if (src_info.flags.size != .one) break :b false;
22983+
const src_pointer_child: Type = .fromInterned(src_info.child);
22984+
if (src_pointer_child.zigTypeTag(zcu) != .array) break :b false;
22985+
const src_elem = src_pointer_child.childType(zcu);
22986+
break :b src_elem.toIntern() == dest_info.child;
22987+
};
22988+
2293622989
check_size: {
2293722990
if (src_info.flags.size == dest_info.flags.size) break :check_size;
22938-
if (src_slice_like and dest_slice_like) break :check_size;
22991+
if (is_array_ptr_to_slice) break :check_size;
2293922992
if (src_info.flags.size == .c) break :check_size;
2294022993
if (dest_info.flags.size == .c) break :check_size;
2294122994
return sema.failWithOwnedErrorMsg(block, msg: {
@@ -22993,7 +23046,7 @@ fn ptrCastFull(
2299323046
const coerced_sent = try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, src_info.sentinel, dest_info.child);
2299423047
if (dest_info.sentinel == coerced_sent) break :check_sent;
2299523048
}
22996-
if (src_slice_like and src_info.flags.size == .one and dest_info.flags.size == .slice) {
23049+
if (is_array_ptr_to_slice) {
2299723050
// [*]nT -> []T
2299823051
const arr_ty = Type.fromInterned(src_info.child);
2299923052
if (arr_ty.sentinel(zcu)) |src_sentinel| {
@@ -23173,12 +23226,9 @@ fn ptrCastFull(
2317323226
}
2317423227
}
2317523228

23176-
const ptr_val: Value, const maybe_len_val: ?Value = switch (src_info.flags.size) {
23177-
.slice => switch (zcu.intern_pool.indexToKey(operand_val.toIntern())) {
23178-
.slice => |slice| .{ .fromInterned(slice.ptr), .fromInterned(slice.len) },
23179-
else => unreachable,
23180-
},
23181-
.one, .many, .c => .{ operand_val, null },
23229+
const ptr_val: Value = switch (src_info.flags.size) {
23230+
.slice => .fromInterned(zcu.intern_pool.indexToKey(operand_val.toIntern()).slice.ptr),
23231+
.one, .many, .c => operand_val,
2318223232
};
2318323233

2318423234
if (dest_align.compare(.gt, src_align)) {
@@ -23197,47 +23247,24 @@ fn ptrCastFull(
2319723247
}
2319823248
}
2319923249

23200-
if (dest_info.flags.size != .slice) {
23250+
if (dest_info.flags.size == .slice) {
23251+
// Because the operand is comptime-known and not `null`, the slice length has already been computed:
23252+
const len: Value = switch (dest_slice_len.?) {
23253+
.undef => try pt.undefValue(.usize),
23254+
.constant => |n| try pt.intValue(.usize, n),
23255+
.equal_runtime_src_slice => unreachable,
23256+
.change_runtime_src_slice => unreachable,
23257+
};
23258+
return Air.internedToRef(try pt.intern(.{ .slice = .{
23259+
.ty = dest_ty.toIntern(),
23260+
.ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
23261+
.len = len.toIntern(),
23262+
} }));
23263+
} else {
2320123264
// Any to non-slice
2320223265
const new_ptr_val = try pt.getCoerced(ptr_val, dest_ty);
2320323266
return Air.internedToRef(new_ptr_val.toIntern());
2320423267
}
23205-
23206-
// Slice-like to slice, compatible element type
23207-
// Here, we can preserve a lazy length.
23208-
if (!slice_needs_len_change) {
23209-
if (maybe_len_val) |len_val| {
23210-
return Air.internedToRef(try pt.intern(.{ .slice = .{
23211-
.ty = dest_ty.toIntern(),
23212-
.ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
23213-
.len = len_val.toIntern(),
23214-
} }));
23215-
}
23216-
}
23217-
23218-
// Slice-like to slice, fallback
23219-
23220-
const src_len: u64 = if (maybe_len_val) |val|
23221-
try val.toUnsignedIntSema(pt)
23222-
else
23223-
Type.fromInterned(src_info.child).arrayLen(zcu);
23224-
23225-
const dest_len: u64 = if (slice_needs_len_change) len: {
23226-
const src_elem_size = src_slice_like_elem.abiSize(zcu);
23227-
const dest_elem_size = Type.fromInterned(dest_info.child).abiSize(zcu);
23228-
const bytes = src_len * src_elem_size;
23229-
// Check: element count divides neatly
23230-
break :len std.math.divExact(u64, bytes, dest_elem_size) catch |err| switch (err) {
23231-
error.DivisionByZero => unreachable,
23232-
error.UnexpectedRemainder => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
23233-
};
23234-
} else src_len;
23235-
23236-
return Air.internedToRef(try pt.intern(.{ .slice = .{
23237-
.ty = dest_ty.toIntern(),
23238-
.ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
23239-
.len = (try pt.intValue(.usize, dest_len)).toIntern(),
23240-
} }));
2324123268
}
2324223269

2324323270
try sema.validateRuntimeValue(block, operand_src, operand);
@@ -23246,6 +23273,11 @@ fn ptrCastFull(
2324623273
const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu);
2324723274
const need_align_check = can_cast_to_int and block.wantSafety() and dest_align.compare(.gt, src_align);
2324823275

23276+
const slice_needs_len_change = if (dest_slice_len) |l| switch (l) {
23277+
.undef, .equal_runtime_src_slice => false,
23278+
.constant, .change_runtime_src_slice => true,
23279+
} else false;
23280+
2324923281
// `operand` might be a slice. If `need_operand_ptr`, we'll populate `operand_ptr` with the raw pointer.
2325023282
const need_operand_ptr = src_info.flags.size != .slice or // we already have it
2325123283
dest_info.flags.size != .slice or // the result is a raw pointer
@@ -23347,67 +23379,49 @@ fn ptrCastFull(
2334723379
// We need to deconstruct the slice (if applicable) and reconstruct it.
2334823380
assert(need_operand_ptr);
2334923381

23350-
const result_len: Air.Inst.Ref = len: {
23351-
if (src_info.flags.size == .slice and !slice_needs_len_change) {
23382+
const result_len: Air.Inst.Ref = switch (dest_slice_len.?) {
23383+
.undef => try pt.undefRef(.usize),
23384+
.constant => |n| try pt.intRef(.usize, n),
23385+
.equal_runtime_src_slice => len: {
2335223386
assert(need_operand_len);
2335323387
break :len operand_len;
23354-
}
23355-
23356-
const src_elem_size = src_slice_like_elem.abiSize(zcu);
23357-
const dest_elem_size = Type.fromInterned(dest_info.child).abiSize(zcu);
23358-
if (src_info.flags.size != .slice) {
23359-
assert(src_slice_like);
23360-
const src_len = Type.fromInterned(src_info.child).arrayLen(zcu);
23361-
const bytes = src_len * src_elem_size;
23362-
const dest_len = std.math.divExact(u64, bytes, dest_elem_size) catch |err| switch (err) {
23388+
},
23389+
.change_runtime_src_slice => |change| len: {
23390+
assert(need_operand_len);
23391+
// If `mul / div` is a whole number, then just multiply the length by it.
23392+
if (std.math.divExact(u64, change.bytes_per_src, change.bytes_per_dest)) |dest_per_src| {
23393+
const multiplier = try pt.intRef(.usize, dest_per_src);
23394+
break :len try block.addBinOp(.mul, operand_len, multiplier);
23395+
} else |err| switch (err) {
2336323396
error.DivisionByZero => unreachable,
23364-
error.UnexpectedRemainder => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
23365-
};
23366-
break :len try pt.intRef(.usize, dest_len);
23367-
}
23368-
23369-
assert(need_operand_len);
23370-
23371-
// If `src_elem_size * n == dest_elem_size`, then just multiply the length by `n`.
23372-
if (std.math.divExact(u64, src_elem_size, dest_elem_size)) |dest_per_src| {
23373-
const multiplier = try pt.intRef(.usize, dest_per_src);
23374-
break :len try block.addBinOp(.mul, operand_len, multiplier);
23375-
} else |err| switch (err) {
23376-
error.DivisionByZero => unreachable,
23377-
error.UnexpectedRemainder => {}, // fall through to code below
23378-
}
23379-
23380-
// If `src_elem_size == dest_elem_size * n`, then divide the length by `n`.
23381-
// This incurs a safety check.
23382-
if (std.math.divExact(u64, dest_elem_size, src_elem_size)) |src_per_dest| {
23383-
const divisor = try pt.intRef(.usize, src_per_dest);
23397+
error.UnexpectedRemainder => {}, // fall through to code below
23398+
}
23399+
// If `div / mul` is a whole number, then just divide the length by it.
23400+
// This incurs a safety check.
23401+
if (std.math.divExact(u64, change.bytes_per_dest, change.bytes_per_src)) |src_per_dest| {
23402+
const divisor = try pt.intRef(.usize, src_per_dest);
23403+
if (block.wantSafety()) {
23404+
// Check that the element count divides neatly.
23405+
const remainder = try block.addBinOp(.rem, operand_len, divisor);
23406+
const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
23407+
try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
23408+
}
23409+
break :len try block.addBinOp(.div_exact, operand_len, divisor);
23410+
} else |err| switch (err) {
23411+
error.DivisionByZero => unreachable,
23412+
error.UnexpectedRemainder => {}, // fall through to code below
23413+
}
23414+
// Fallback: the elements don't divide easily. We'll multiply *and* divide. This incurs a safety check.
23415+
const total_bytes_ref = try block.addBinOp(.mul, operand_len, try pt.intRef(.usize, change.bytes_per_src));
23416+
const bytes_per_dest_ref = try pt.intRef(.usize, change.bytes_per_dest);
2338423417
if (block.wantSafety()) {
23385-
// Check that the element count divides neatly.
23386-
const remainder = try block.addBinOp(.rem, operand_len, divisor);
23418+
// Check that `total_bytes_ref` divides neatly into `bytes_per_dest_ref`.
23419+
const remainder = try block.addBinOp(.rem, total_bytes_ref, bytes_per_dest_ref);
2338723420
const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
2338823421
try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
2338923422
}
23390-
break :len try block.addBinOp(.div_exact, operand_len, divisor);
23391-
} else |err| switch (err) {
23392-
error.DivisionByZero => unreachable,
23393-
error.UnexpectedRemainder => {}, // fall through to code below
23394-
}
23395-
23396-
// Fallback: the elements don't divide easily.
23397-
// We'll multiply up to a byte count, then divide down to a new element count.
23398-
// This incurs a safety check.
23399-
23400-
const src_elem_size_ref = try pt.intRef(.usize, src_elem_size);
23401-
const dest_elem_size_ref = try pt.intRef(.usize, dest_elem_size);
23402-
23403-
const byte_count = try block.addBinOp(.mul, operand_len, src_elem_size_ref);
23404-
if (block.wantSafety()) {
23405-
// Check that `byte_count` divides neatly into `dest_elem_size`.
23406-
const remainder = try block.addBinOp(.rem, byte_count, dest_elem_size_ref);
23407-
const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
23408-
try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
23409-
}
23410-
break :len try block.addBinOp(.div_exact, byte_count, dest_elem_size_ref);
23423+
break :len try block.addBinOp(.div_exact, total_bytes_ref, bytes_per_dest_ref);
23424+
},
2341123425
};
2341223426

2341323427
const operand_ptr_ty = sema.typeOf(operand_ptr);

0 commit comments

Comments
 (0)