@@ -22893,49 +22893,102 @@ fn ptrCastFull(
22893
22893
try Type.fromInterned(src_info.child).resolveLayout(pt);
22894
22894
try Type.fromInterned(dest_info.child).resolveLayout(pt);
22895
22895
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
+ } };
22928
22955
}
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
+ };
22932
22976
22933
22977
// The checking logic in this function must stay in sync with Sema.coerceInMemoryAllowedPtrs
22934
22978
22935
22979
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
+
22936
22989
check_size: {
22937
22990
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;
22939
22992
if (src_info.flags.size == .c) break :check_size;
22940
22993
if (dest_info.flags.size == .c) break :check_size;
22941
22994
return sema.failWithOwnedErrorMsg(block, msg: {
@@ -22993,7 +23046,7 @@ fn ptrCastFull(
22993
23046
const coerced_sent = try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, src_info.sentinel, dest_info.child);
22994
23047
if (dest_info.sentinel == coerced_sent) break :check_sent;
22995
23048
}
22996
- if (src_slice_like and src_info.flags.size == .one and dest_info.flags.size == .slice ) {
23049
+ if (is_array_ptr_to_slice ) {
22997
23050
// [*]nT -> []T
22998
23051
const arr_ty = Type.fromInterned(src_info.child);
22999
23052
if (arr_ty.sentinel(zcu)) |src_sentinel| {
@@ -23173,12 +23226,9 @@ fn ptrCastFull(
23173
23226
}
23174
23227
}
23175
23228
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,
23182
23232
};
23183
23233
23184
23234
if (dest_align.compare(.gt, src_align)) {
@@ -23197,47 +23247,24 @@ fn ptrCastFull(
23197
23247
}
23198
23248
}
23199
23249
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 {
23201
23264
// Any to non-slice
23202
23265
const new_ptr_val = try pt.getCoerced(ptr_val, dest_ty);
23203
23266
return Air.internedToRef(new_ptr_val.toIntern());
23204
23267
}
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
- } }));
23241
23268
}
23242
23269
23243
23270
try sema.validateRuntimeValue(block, operand_src, operand);
@@ -23246,6 +23273,11 @@ fn ptrCastFull(
23246
23273
const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu);
23247
23274
const need_align_check = can_cast_to_int and block.wantSafety() and dest_align.compare(.gt, src_align);
23248
23275
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
+
23249
23281
// `operand` might be a slice. If `need_operand_ptr`, we'll populate `operand_ptr` with the raw pointer.
23250
23282
const need_operand_ptr = src_info.flags.size != .slice or // we already have it
23251
23283
dest_info.flags.size != .slice or // the result is a raw pointer
@@ -23347,67 +23379,49 @@ fn ptrCastFull(
23347
23379
// We need to deconstruct the slice (if applicable) and reconstruct it.
23348
23380
assert(need_operand_ptr);
23349
23381
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: {
23352
23386
assert(need_operand_len);
23353
23387
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) {
23363
23396
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 );
23384
23417
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 );
23387
23420
const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
23388
23421
try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
23389
23422
}
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
+ },
23411
23425
};
23412
23426
23413
23427
const operand_ptr_ty = sema.typeOf(operand_ptr);
0 commit comments