Description
we intend to transition const generics over to valtrees, this representation erases padding bytes.
We also intend to allow the following example to compile:
const fn some_operation::<const N: usize>() -> usize { N }
const fn other_operation(x: usize) -> usize { x + 1 }
fn outer<const N: usize>() -> [u8; other_operation(some_operation::<N>())] {
inner::<{ some_operation::<N>() }>()
}
fn inner<const M: usize>() -> [u8; other_operation(M)] {
[u0; other_operation(M)]
}
for this to compile, we have to unify the constant other_operation(some_operation::<N>())
with the constant other_operation(M)
using the generic argument some_operation::<N>()
as argument for M
. This is sound as long as const eval doesn't have side effects.
A concern is the interaction between padding bytes and valtrees. We intend to not remember padding bytes in constants used in the type system, but do remember them during const eval and can soundly get their value (afaik). This means that putting values into the type system in the middle of const eval can have observable effects, e.g. consider
#![feature(generic_const_exprs, adt_const_params)]
#[repr(C)]
#[derive(PartialEq, Eq)]
struct Foo {
v1: u8,
// _padding: u8,
v2: u16,
}
#[repr(C)]
struct FooWithPadding {
v1: u8,
padding: u8,
v2: u16,
}
const fn with_valid_padding<const N: u32>() -> &'static Foo {
unsafe {
&*(&N as *const u32).cast::<Foo>()
}
}
const fn reads_padding(v: &'static Foo) -> usize {
unsafe {
&*(v as *const Foo).cast::<FooWithPadding>()
}.padding as usize
}
fn direct_use<const N: u32>() -> [u8; reads_padding(with_valid_padding::<N>())] {
generic_reads_padding::<{ with_valid_padding::<N>() }>()
}
fn generic_reads_padding<const FOO: &'static Foo>() -> [u8; reads_padding(FOO)] {
[0; reads_padding(FOO)]
}
fn main() {
println!("{:x}", direct_use::<0x0000bb00>().len());
}
direct_use
must not compile with valtrees as the behavior of reads_padding
differs between the two constants.