Skip to content

Commit 648a35e

Browse files
committed
Add ability to transmute with generic consts
Previously if the expression contained generic consts and did not have a directly equivalent type, transmuting the type in this way was forbidden, despite the two sizes being identical. Instead, we should be able to lazily tell if the two consts are identical, and if so allow them to be transmuted.
1 parent 82bfda8 commit 648a35e

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed

compiler/rustc_hir_typeck/src/intrinsicck.rs

+7
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8484
let skeleton_string = |ty: Ty<'tcx>, sk| match sk {
8585
Ok(SizeSkeleton::Known(size)) => format!("{} bits", size.bits()),
8686
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
87+
Ok(SizeSkeleton::Generic(size)) => {
88+
if let Some(size) = size.try_eval_target_usize(tcx, self.param_env) {
89+
format!("{size} bytes")
90+
} else {
91+
format!("generic size")
92+
}
93+
}
8794
Err(LayoutError::Unknown(bad)) => {
8895
if bad == ty {
8996
"this type does not have a fixed size".to_owned()

compiler/rustc_middle/src/ty/layout.rs

+93
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,12 @@ pub enum SizeSkeleton<'tcx> {
281281
/// Any statically computable Layout.
282282
Known(Size),
283283

284+
/// This is a generic const expression (i.e. N * 2), which may contain some parameters.
285+
/// It must be of type usize, and represents the size of a type in bytes.
286+
/// It is not required to be evaluatable to a concrete value, but can be used to check
287+
/// that another SizeSkeleton is of equal size.
288+
Generic(ty::Const<'tcx>),
289+
284290
/// A potentially-fat pointer.
285291
Pointer {
286292
/// If true, this pointer is never null.
@@ -326,6 +332,35 @@ impl<'tcx> SizeSkeleton<'tcx> {
326332
),
327333
}
328334
}
335+
ty::Array(inner, len) if len.ty() == tcx.types.usize => {
336+
match SizeSkeleton::compute(inner, tcx, param_env)? {
337+
// This may succeed because the multiplication of two types may overflow
338+
// but a single size of a nested array will not.
339+
SizeSkeleton::Known(s) => {
340+
if let Some(c) = len.try_eval_target_usize(tcx, param_env) {
341+
let size = s
342+
.bytes()
343+
.checked_mul(c)
344+
.ok_or_else(|| LayoutError::SizeOverflow(ty))?;
345+
return Ok(SizeSkeleton::Known(Size::from_bytes(size)));
346+
}
347+
let len = tcx.expand_abstract_consts(len);
348+
let prev = ty::Const::from_target_usize(tcx, s.bytes());
349+
let Some(gen_size) = mul_sorted_consts(tcx, param_env, len, prev) else {
350+
return Err(LayoutError::SizeOverflow(ty));
351+
};
352+
Ok(SizeSkeleton::Generic(gen_size))
353+
}
354+
SizeSkeleton::Pointer { .. } => Err(err),
355+
SizeSkeleton::Generic(g) => {
356+
let len = tcx.expand_abstract_consts(len);
357+
let Some(gen_size) = mul_sorted_consts(tcx, param_env, len, g) else {
358+
return Err(LayoutError::SizeOverflow(ty));
359+
};
360+
Ok(SizeSkeleton::Generic(gen_size))
361+
}
362+
}
363+
}
329364

330365
ty::Adt(def, substs) => {
331366
// Only newtypes and enums w/ nullable pointer optimization.
@@ -355,6 +390,9 @@ impl<'tcx> SizeSkeleton<'tcx> {
355390
}
356391
ptr = Some(field);
357392
}
393+
SizeSkeleton::Generic(_) => {
394+
return Err(err);
395+
}
358396
}
359397
}
360398
Ok(ptr)
@@ -410,11 +448,66 @@ impl<'tcx> SizeSkeleton<'tcx> {
410448
(SizeSkeleton::Pointer { tail: a, .. }, SizeSkeleton::Pointer { tail: b, .. }) => {
411449
a == b
412450
}
451+
// constants are always pre-normalized into a canonical form so this
452+
// only needs to check if their pointers are identical.
453+
(SizeSkeleton::Generic(a), SizeSkeleton::Generic(b)) => a == b,
413454
_ => false,
414455
}
415456
}
416457
}
417458

459+
/// When creating the layout for types with abstract conts in their size (i.e. [usize; 4 * N]),
460+
/// to ensure that they have a canonical order and can be compared directly we combine all
461+
/// constants, and sort the other terms. This allows comparison of expressions of sizes,
462+
/// allowing for things like transmutating between types that depend on generic consts.
463+
/// This returns `None` if multiplication of constants overflows.
464+
fn mul_sorted_consts<'tcx>(
465+
tcx: TyCtxt<'tcx>,
466+
param_env: ty::ParamEnv<'tcx>,
467+
a: ty::Const<'tcx>,
468+
b: ty::Const<'tcx>,
469+
) -> Option<ty::Const<'tcx>> {
470+
use crate::mir::BinOp::Mul;
471+
use ty::ConstKind::Expr;
472+
use ty::Expr::Binop;
473+
474+
let mut work = vec![a, b];
475+
let mut done = vec![];
476+
while let Some(n) = work.pop() {
477+
if let Expr(Binop(Mul, l, r)) = n.kind() {
478+
work.push(l);
479+
work.push(r)
480+
} else {
481+
done.push(n);
482+
}
483+
}
484+
let mut k = 1;
485+
let mut overflow = false;
486+
for _ in done.drain_filter(|c| {
487+
let Some(c) = c.try_eval_target_usize(tcx, param_env) else {
488+
return false;
489+
};
490+
let Some(next) = c.checked_mul(k) else {
491+
overflow = true;
492+
return true;
493+
};
494+
k *= next;
495+
true
496+
}) {}
497+
if overflow {
498+
return None;
499+
}
500+
if k != 1 {
501+
done.push(ty::Const::from_target_usize(tcx, k));
502+
} else if k == 0 {
503+
return Some(ty::Const::from_target_usize(tcx, 0));
504+
}
505+
done.sort_unstable();
506+
507+
// create a single tree from the buffer
508+
done.into_iter().reduce(|acc, n| tcx.mk_const(Expr(Binop(Mul, n, acc)), n.ty()))
509+
}
510+
418511
pub trait HasTyCtxt<'tcx>: HasDataLayout {
419512
fn tcx(&self) -> TyCtxt<'tcx>;
420513
}
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![feature(generic_const_exprs)]
2+
#![allow(incomplete_features)]
3+
4+
fn foo<const W: usize, const H: usize>(v: [[u32;H+1]; W]) -> [[u32; W+1]; H] {
5+
unsafe {
6+
std::mem::transmute(v)
7+
//~^ ERROR cannot transmute
8+
}
9+
}
10+
11+
fn bar<const W: bool, const H: usize>(v: [[u32; H]; W]) -> [[u32; W]; H] {
12+
//~^ ERROR mismatched types
13+
//~| ERROR mismatched types
14+
unsafe {
15+
std::mem::transmute(v)
16+
//~^ ERROR cannot transmute between types
17+
}
18+
}
19+
20+
fn baz<const W: usize, const H: usize>(v: [[u32; H]; W]) -> [u32; W * H * H] {
21+
unsafe {
22+
std::mem::transmute(v)
23+
//~^ ERROR cannot transmute
24+
}
25+
}
26+
27+
fn overflow(v: [[[u32; 8888888]; 9999999]; 777777777]) -> [[[u32; 9999999]; 777777777]; 8888888] {
28+
unsafe {
29+
std::mem::transmute(v)
30+
//~^ ERROR cannot transmute
31+
}
32+
}
33+
34+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2+
--> $DIR/transmute-fail.rs:6:5
3+
|
4+
LL | std::mem::transmute(v)
5+
| ^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: source type: `[[u32; H+1]; W]` (generic size)
8+
= note: target type: `[[u32; W+1]; H]` (generic size)
9+
10+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
11+
--> $DIR/transmute-fail.rs:15:5
12+
|
13+
LL | std::mem::transmute(v)
14+
| ^^^^^^^^^^^^^^^^^^^
15+
|
16+
= note: source type: `[[u32; H]; W]` (this type does not have a fixed size)
17+
= note: target type: `[[u32; W]; H]` (size can vary because of [u32; W])
18+
19+
error[E0308]: mismatched types
20+
--> $DIR/transmute-fail.rs:11:53
21+
|
22+
LL | fn bar<const W: bool, const H: usize>(v: [[u32; H]; W]) -> [[u32; W]; H] {
23+
| ^ expected `usize`, found `bool`
24+
25+
error[E0308]: mismatched types
26+
--> $DIR/transmute-fail.rs:11:67
27+
|
28+
LL | fn bar<const W: bool, const H: usize>(v: [[u32; H]; W]) -> [[u32; W]; H] {
29+
| ^ expected `usize`, found `bool`
30+
31+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
32+
--> $DIR/transmute-fail.rs:22:5
33+
|
34+
LL | std::mem::transmute(v)
35+
| ^^^^^^^^^^^^^^^^^^^
36+
|
37+
= note: source type: `[[u32; H]; W]` (generic size)
38+
= note: target type: `[u32; W * H * H]` (generic size)
39+
40+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
41+
--> $DIR/transmute-fail.rs:29:5
42+
|
43+
LL | std::mem::transmute(v)
44+
| ^^^^^^^^^^^^^^^^^^^
45+
|
46+
= note: source type: `[[[u32; 8888888]; 9999999]; 777777777]` (values of the type `[[[u32; 8888888]; 9999999]; 777777777]` are too big for the current architecture)
47+
= note: target type: `[[[u32; 9999999]; 777777777]; 8888888]` (values of the type `[[[u32; 9999999]; 777777777]; 8888888]` are too big for the current architecture)
48+
49+
error: aborting due to 6 previous errors
50+
51+
Some errors have detailed explanations: E0308, E0512.
52+
For more information about an error, try `rustc --explain E0308`.

tests/ui/const-generics/transmute.rs

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// run-pass
2+
#![feature(generic_const_exprs)]
3+
#![allow(incomplete_features)]
4+
5+
fn transpose<const W: usize, const H: usize>(v: [[u32;H]; W]) -> [[u32; W]; H] {
6+
unsafe {
7+
std::mem::transmute(v)
8+
}
9+
}
10+
11+
fn ident<const W: usize, const H: usize>(v: [[u32; H]; W]) -> [[u32; H]; W] {
12+
unsafe {
13+
std::mem::transmute(v)
14+
}
15+
}
16+
17+
fn flatten<const W: usize, const H: usize>(v: [[u32; H]; W]) -> [u32; W * H] {
18+
unsafe {
19+
std::mem::transmute(v)
20+
}
21+
}
22+
23+
fn coagulate<const W: usize, const H: usize>(v: [u32; H*W]) -> [[u32; W];H] {
24+
unsafe {
25+
std::mem::transmute(v)
26+
}
27+
}
28+
29+
fn flatten_3d<const W: usize, const H: usize, const D: usize>(
30+
v: [[[u32; D]; H]; W]
31+
) -> [u32; D * W * H] {
32+
unsafe {
33+
std::mem::transmute(v)
34+
}
35+
}
36+
37+
fn flatten_somewhat<const W: usize, const H: usize, const D: usize>(
38+
v: [[[u32; D]; H]; W]
39+
) -> [[u32; D * W]; H] {
40+
unsafe {
41+
std::mem::transmute(v)
42+
}
43+
}
44+
45+
fn known_size<const L: usize>(v: [u16; L]) -> [u8; L * 2] {
46+
unsafe {
47+
std::mem::transmute(v)
48+
}
49+
}
50+
51+
fn condense_bytes<const L: usize>(v: [u8; L * 2]) -> [u16; L] {
52+
unsafe {
53+
std::mem::transmute(v)
54+
}
55+
}
56+
57+
fn singleton_each<const L: usize>(v: [u8; L]) -> [[u8;1]; L] {
58+
unsafe {
59+
std::mem::transmute(v)
60+
}
61+
}
62+
63+
fn transpose_with_const<const W: usize, const H: usize>(
64+
v: [[u32; 2 * H]; W + W]
65+
) -> [[u32; W + W]; 2 * H] {
66+
unsafe {
67+
std::mem::transmute(v)
68+
}
69+
}
70+
71+
fn main() {
72+
let _ = transpose([[0; 8]; 16]);
73+
let _ = transpose_with_const::<8,4>([[0; 8]; 16]);
74+
let _ = ident([[0; 8]; 16]);
75+
let _ = flatten([[0; 13]; 5]);
76+
let _: [[_; 5]; 13] = coagulate([0; 65]);
77+
let _ = flatten_3d([[[0; 3]; 13]; 5]);
78+
let _ = flatten_somewhat([[[0; 3]; 13]; 5]);
79+
let _ = known_size([16; 13]);
80+
let _: [u16; 5] = condense_bytes([16u8; 10]);
81+
let _ = singleton_each([16; 10]);
82+
}

0 commit comments

Comments
 (0)