Description
Location
rust/library/core/src/ptr/const_ptr.rs
Lines 810 to 854 in 33855f8
rust/library/core/src/ptr/const_ptr.rs
Lines 349 to 393 in 33855f8
Summary
The documentation for core::ptr::add
currently states that it:
rust/library/core/src/ptr/const_ptr.rs
Line 810 in 33855f8
Except it's not a convenience for just doing .offset(count as isize)
, both according to Miri and the constant interpreter:
#[no_mangle]
pub const fn mul() -> i32 {
const {
let x = &[0i32; 2];
let x = x.as_ptr().wrapping_add(1);
unsafe {
// has UB according to MIRI and fails compilation on nightly if you comment this out
// mul nuw i64 -1, 4 is poison
// x.add(!0).read();
// DB and passes compilation
// mul nsw i64 -1, 4 is defined
x.offset(-1).read();
}
x[0]
}
}
So one might infer that the following requirement:
rust/library/core/src/ptr/const_ptr.rs
Line 819 in 33855f8
means that the count * size_of::<T>()
must not overflow in the unsigned sense for add
and signed sense for offset
.
However, it's unclear if overflowing isize
means the unsigned multiplication result has to be positive, or if the unsigned multiplication also has to not wrap in the signed sense. Both Miri and the constant interpreter allow the following:
#[no_mangle]
pub const fn mulnsw() -> i32 {
const {
let x = &[0i32; 2];
let x = x.as_ptr().wrapping_add(1);
let offset = !0usize >> 2; // will be -4 when multiplied
let mut ret = 0;
unsafe {
// has no UB according to MIRI and does not fail compilation on nightly
// does not wrap in the unsigned sense
// however wraps in the signed sense
// has UB in our codegen
ret = x.add(offset).read();
// UB in codegen due to signed overflow
// both MIRI and constant interpreter fail
// ret = x.offset(offset as isize).read();
}
ret
}
}
However, x.add(offset)
will also get lowered to getelementptr inbounds i32, %x, i64 %offset
, which has UB according to the LLVM langref.
The multiplication of an index by the type size does not wrap the pointer index type in a signed sense (mul nsw).
If the core::ptr::add
does have defined behavior in the above case, then we can't directly use getelementptr inbounds
for types larger than a byte, without doing a mul nuw
beforehand, and then doing getelementptr inbounds i8
. However, if it's UB, then the docs should make it clear.
So core::ptr::add
still carries the responsibility of preventing signed overflow in the multiplication(at least according to our codegen semantics). However, it's unclear if "must not overflow isize", means that the unsigned result has to fit in [0, isize::MAX]
, as the following passes:
#[no_mangle]
pub const fn addaddr() -> i32 {
const {
let x = &[0i32; 2];
let x = x.as_ptr().wrapping_add(1).cast::<u8>();
unsafe {
// -1 * 1 doesn't overflow in both the signed and unsigned sense
// however the result doesn't fit in [0, isize::MAX]
// passes MIRI and the constant interpreter
x.byte_add(!0).read();
}
x[0]
}
}
To summarize:
- Does the multiplication of
count * size_of::<T>()
need to not overflow in both the signed and unsigned sense forcore::ptr::add
? - In
core::ptr::add
, is the resulting offset treated as a signed integer or unsigned integer for the following requirement:rust/library/core/src/ptr/const_ptr.rs
Lines 821 to 824 in 33855f8
Rationale for inquiry:
LLVM recently added a nuw
attribute to the getelementptr
instruction in order to inform the optimizer that the offset is non-negative. I was checking the core::ptr::add
documentation to see if we could use it for that, however, I found some contradictory information w.r.t the docs and the current behavior of the constant interpreter and Miri.