-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Document that heap allocations are not guaranteed to happen, even if explicitly performed in the code #79045
Document that heap allocations are not guaranteed to happen, even if explicitly performed in the code #79045
Conversation
r? @shepmaster (rust_highfive has picked a reviewer for you, use r? to override) |
Any idea how to ping the "allocators" WG? https://github.com/rust-lang/wg-allocators/ |
cc @TimDiekmann you have no pingable team ^^ |
Note that this makes it impossible to ever support the "normal" allocator during CTFE. After all, allocations that occur during CTFE will never hit the real allocator. Now imagine something like this: // Type invariant: constructing an element of this type will cause a heap allocation
pub struct Evil(());
impl Evil {
pub const fn new() {
drop(Box::new(0));
Evil
}
pub check(&self) {
let number_of_heap_allocs = /* call private allocator API */;
if number_of_heap_allocs == 0 { unsafe { unreachable_unchecked() }}
}
} It would not be hard to permit CTFE to perform "transient" allocations as long as the final computed value does not depend on them -- something like So, if the compiler has to preserve the exact number of calls to |
As the allocator-wg isn't pingable, I'll mention the same people I link when doing a FCP in the WG: cc @Amanieu @Lokathor @Wodann @CAD97 @scottjmaddox @vertexclique |
This mostly matches my prior understanding, so I have no objections. A potential alternative is to only allow allocation optimization when using the global allocator (so e.g. |
This would also lead to confusion and unexpected performance regressions I guess. This would also introduce corner cases. Does |
This would mean there could be no blanket impl of (However, what you describe actually matches the current implementation. But I'd call that an implementation artifact.) |
document that __rust_alloc is also magic to our LLVM fork Based on [comments](rust-lang#79045 (comment)) by `@tmiasko` and `@bjorn3.`
document that __rust_alloc is also magic to our LLVM fork Based on [comments](rust-lang#79045 (comment)) by ``@tmiasko`` and ``@bjorn3.``
document that __rust_alloc is also magic to our LLVM fork Based on [comments](rust-lang#79045 (comment)) by ```@tmiasko``` and ```@bjorn3.```
r? @RalfJung |
document that __rust_alloc is also magic to our LLVM fork Based on [comments](rust-lang#79045 (comment)) by ````@tmiasko```` and ````@bjorn3.````
Since I am the one who proposed this remark to @oli-obk, I think it'd be better if someone from WG-allocators could do the reviewing. r? @TimDiekmann Also there are some open discussions, so I do not think this is ready for merging quite yet. |
As this behavior already occurs in release builds, this should definitely be added to |
@rustbot modify labels: -S-waiting-on-review +S-waiting-on-author |
@bors r=TimDiekmann rollup |
📌 Commit 58d62b8 has been approved by |
I think this should be tweaked some more -- see my feedback above. |
library/core/src/alloc/global.rs
Outdated
/// | ||
/// Note that allocation/deallocation pairs being moved to the stack is not the only | ||
/// optimization that can be applied. You may generally not rely on heap allocations | ||
/// happening, if they can be removed without changing program behaviour. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the case of a program like
use std::alloc::Layout;
use std::alloc::{alloc, dealloc};
fn main() {
unsafe {
let layout = Layout::from_size_align(
0xffff_ffff_ffff_ffff, // big data
1
).unwrap();
let ptr = alloc(layout);
if ptr.is_null() {
panic!("allocation failed");
}
dealloc(ptr, layout);
};
}
is removing this heap allocation (and ptr.is_null()
becoming a constant false
) considered changing program behavior? the truthiness of ptr.is_null()
is not a consequence of the allocation happening, but the allocator implementation.
i'm not sure if this paragraph should be widened to say that in some circumstances program behavior can change, or the above main
becoming a no-op is a rustc bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that 99.9% of allocation is done via ptr::NonNull::new(alloc(layout)).unwrap_or_else(|| handle_alloc_error(layout))
(Box
does nearly exactly this), if the allocation removal optimization is ever allowed to apply, it needs to be able to optimize the allocation out even when doing an arbitrary -> !
on allocation failure (as handle_alloc_error
calls the dynamic alloc error hook).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the wording to apply here would be to say (somehow) that allocation removal optimization happens assuming that allocation is infallible, or iow that an optimized out allocation always succeeds, or iow that "without changing program behavior when allocation succeeds" or similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is removing this heap allocation (and ptr.is_null() becoming a constant false) considered changing program behavior?
I wish I knew the answer.^^ In practice, LLVM will optimize away even such huge allocations that can never succeed. So the LLVM devs consider this to not be changing program behavior.
However, I know of no formal model that would actually explain this, and it might even be inconsistent. Somehow the formal model needs to say that the allocation can succeed even if it is too big to fit into the finite memory, and then proceed in a consistent way even if the program sanity-checks the integer addresses that it got (since those checks can be optimized away if their result is unused) and so on.
I consider this to be one of the hardest unanswered questions in formal semantics for low-level languages: to either show that this makes sense, or to show that the optimization is broken and leads to miscompilations.
I think that this should only apply to the global allocator since Specifically these semantics should be on the |
Wouldn't that be unsound, since I could use that |
In this context GPU memory would be mapped into the address space of the process and would be accessible just like normal memory. |
Oh I see. That would mean rustc must not assign any special semantics to |
Co-authored-by: Ralf Jung <[email protected]>
Co-authored-by: Ralf Jung <[email protected]>
@bors r=TimDiekmann rollup |
📌 Commit efcd8a9 has been approved by |
☀️ Test successful - checks-actions |
cc @RalfJung