-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return-position noalias
#385
Comments
Specifically I think this code has UB since the returned box aliases fn id<T>(x: Box<T>) -> Box<T> { x }
fn main() {
let mut m = 0;
let b = unsafe { Box::from_raw(&mut m) };
let mut b2 = id(b);
*b2 = 5;
std::mem::forget(b2);
println!("{}", m);
} Miri however says this code is fine. Cc @nikic @comex do you know the exact assumptions introduced by return-position |
LangRef:
So yes, that example would be UB. |
So how is that supposed to work for boxes with custom allocators? @rust-lang/wg-allocators the type In particular this makes an allocator like the following unsound: struct OnceAlloc<'a> {
space: Cell<&'a mut [MaybeUninit<u8>]>,
}
unsafe impl<'shared, 'a: 'shared> Allocator for &'shared OnceAlloc<'a> {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let space = self.space.replace(&mut []);
let (ptr, len) = (space.as_mut_ptr(), space.len());
if ptr.align_offset(layout.align()) != 0 || len < layout.size() {
return Err(AllocError);
}
let slice_ptr = ptr::slice_from_raw_parts_mut(ptr as *mut u8, len);
unsafe { Ok(NonNull::new_unchecked(slice_ptr)) }
}
unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
} The issue is code like this let mut space = vec![MaybeUninit::new(0); 1];
let once_alloc = OnceAlloc { space: Cell::new(&mut space[..]) };
let boxed = Box::new_in([42u8; 1], &once_alloc);
drop(boxed); // basically a NOP LLVM assumes that (In this concrete case the |
Could that approach solve the same problem here? We could say that allocators such as the one you gave in your example are sound, but would be unsound if wrapped in this magic type since they don't uphold the necessary guarantees. |
That would help with the immediate example, but if the semantics of return-position Basically return-position |
I believe these LLVM attributes were only ever designed to work in a situation where |
Indeed, putting return-position fn id(x: Box<i32>) -> Box<i32> { x }
// given a: Box<i32>
*a = 1;
let b: Box<i32> = id(a);
print(b); then LLVM thinks that The bad aliasing assumption can be demonstrated using Exploitation detailsMy usual approach to exploiting bad noalias assumptions betweena and b involves accessing a first, then b , then a again – for instance, write 100 to a , write 200 to b , then read from a , and let LLVM's GVN pass optimize the read to return 100, even though it should return 200 because a and b secretly alias. But that isn't an option here where, before you can access b , you have to pass a through the id function and (because it's a Box ) lose access to it. Another approach is to rely on loop-invariant code motion (LICM), i.e. hoisting instructions out of loops if they compute same value on every loop iteration. But that's also difficult here (at least if the goal is to use only safe code), because just putting the code in a loop would result in the compiler complaining about use-after-move of a . You could replace a with a new value at the end of the loop, but that would make it not loop invariant. However, I managed to get this to work by having a be effectively undefined on the second and further iterations… at the cost of the code that uses it being not actually reachable on those iterations, because we break out of the loop first. This has the downside of allowing the optimizer to remove the loop entirely. Even though we only care about the first iteration, removing the loop entirely would prevent LICM from running. But luckily the LICM pass is executed before the pass that removes the loop.
Demo: https://rust.godbolt.org/z/6nvcqnesf It prints |
Wait that noalias also works backwards in time on pointers before the fn was called? Wow that is not what I expected. Seems pretty broken, at leat for our usecase.
|
In that case I see no option than to just stop using this attribute: rust-lang/rust#106371 |
…nikic do not add noalias in return position `noalias` as a return attribute in LLVM indicates that the returned pointer does not alias anything else that is reachable from the caller, *including things reachable before this function call*. This is clearly not the case with a function like `fn id(Box<T>) -> Box<T>`, so we cannot use this attribute. Fixes rust-lang/unsafe-code-guidelines#385 (including an actual miscompilation that `@comex` managed to produce).
Closed by rust-lang/rust#106371 |
do not add noalias in return position `noalias` as a return attribute in LLVM indicates that the returned pointer does not alias anything else that is reachable from the caller, *including things reachable before this function call*. This is clearly not the case with a function like `fn id(Box<T>) -> Box<T>`, so we cannot use this attribute. Fixes rust-lang/unsafe-code-guidelines#385 (including an actual miscompilation that `@comex` managed to produce).
LLVM supports the
noalias
attribute in return position, and Rust uses that for functions that returnBox
. However the semantics of that are mostly unclear I think -- this has very little to do with argument-positionnoalias
.I think in Stacked Borrows terms it corresponds to something like: give the return value a fresh tag, and remove all other tags from the stack.
Questions:
noalias
without usingBox
? @gnzlbg recently mentioned a usecase for that.The text was updated successfully, but these errors were encountered: