Skip to content
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

Merged

Conversation

oli-obk
Copy link
Contributor

@oli-obk oli-obk commented Nov 14, 2020

@oli-obk oli-obk added A-allocators Area: Custom and system allocators T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Nov 14, 2020
@rust-highfive
Copy link
Collaborator

r? @shepmaster

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 14, 2020
@RalfJung
Copy link
Member

Any idea how to ping the "allocators" WG? https://github.com/rust-lang/wg-allocators/

@oli-obk
Copy link
Contributor Author

oli-obk commented Nov 14, 2020

cc @TimDiekmann you have no pingable team ^^

@RalfJung
Copy link
Member

RalfJung commented Nov 14, 2020

If the allocator has an API to query the number of allocs, I think the compiler is not allowed to bypass this, as calling alloc has a side effect (e.g. incrementing a counter in the allocator).

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 Evil::new. But this is only possible if the compiler is allowed to drop calls to the allocation function.

So, if the compiler has to preserve the exact number of calls to Global::allocator, then heap allocation during CTFE will always have to use a sepcial compile-time allocator -- it is impossible to use the same code for compile-time and run-time allocation.

@TimDiekmann
Copy link
Member

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

@CAD97
Copy link
Contributor

CAD97 commented Nov 15, 2020

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. Global.dealloc(Global.alloc(...)) would optimize out, but Geiger.dealloc(Geiger.alloc(...)) wouldn't), but I don't think that's necessary, and would unnecessarily pessimize use of local allocators.

@TimDiekmann
Copy link
Member

A potential alternative is to only allow allocation optimization when using the global allocator. [...] I don't think that's necessary, and would unnecessarily pessimize use of local allocators.

This would also lead to confusion and unexpected performance regressions I guess. This would also introduce corner cases. Does Box<T, Global> behave like Box<T>? If Global == System, does Box<T> behave like Box<T, System>?

@RalfJung
Copy link
Member

A potential alternative is to only allow allocation optimization when using the global allocator

This would mean there could be no blanket impl of GlobalAlloc in terms of AllocRef.

(However, what you describe actually matches the current implementation. But I'd call that an implementation artifact.)

GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Nov 15, 2020
document that __rust_alloc is also magic to our LLVM fork

Based on [comments](rust-lang#79045 (comment)) by `@tmiasko` and `@bjorn3.`
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Nov 16, 2020
document that __rust_alloc is also magic to our LLVM fork

Based on [comments](rust-lang#79045 (comment)) by ``@tmiasko`` and ``@bjorn3.``
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Nov 17, 2020
document that __rust_alloc is also magic to our LLVM fork

Based on [comments](rust-lang#79045 (comment)) by ```@tmiasko``` and ```@bjorn3.```
@shepmaster
Copy link
Member

r? @RalfJung

@rust-highfive rust-highfive assigned RalfJung and unassigned shepmaster Nov 17, 2020
m-ou-se added a commit to m-ou-se/rust that referenced this pull request Nov 17, 2020
document that __rust_alloc is also magic to our LLVM fork

Based on [comments](rust-lang#79045 (comment)) by ````@tmiasko```` and ````@bjorn3.````
@RalfJung
Copy link
Member

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.

@TimDiekmann
Copy link
Member

As this behavior already occurs in release builds, this should definitely be added to #[global_allocator]. I guess, it's also possible to occur with AllocRef but the example for AllocRef::alloc may needs to be adjusted, as it does not use AllocRef.

@TimDiekmann
Copy link
Member

@rustbot modify labels: -S-waiting-on-review +S-waiting-on-author

@oli-obk
Copy link
Contributor Author

oli-obk commented Nov 19, 2020

@bors r=TimDiekmann rollup

@bors
Copy link
Contributor

bors commented Nov 19, 2020

📌 Commit 58d62b8 has been approved by TimDiekmann

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 19, 2020
@RalfJung
Copy link
Member

I think this should be tweaked some more -- see my feedback above.
@bors r-

@bors bors added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Nov 19, 2020
///
/// 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.
Copy link
Contributor

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.

Copy link
Contributor

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).

Copy link
Contributor

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.

Copy link
Member

@RalfJung RalfJung Dec 5, 2020

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.

@Amanieu
Copy link
Member

Amanieu commented Dec 5, 2020

I think that this should only apply to the global allocator since AllocRef may be used to allocate "special" memory (e.g. GPU memory) which cannot be replaced with just a stack allocation.

Specifically these semantics should be on the #[global_allocator] attribute rather than the AllocRef trait, but we don't really have a better place for it in the docs.

@RalfJung
Copy link
Member

RalfJung commented Dec 5, 2020

I think that this should only apply to the global allocator since AllocRef may be used to allocate "special" memory (e.g. GPU memory) which cannot be replaced with just a stack allocation.

Wouldn't that be unsound, since I could use that AllocRef with Box which will assume it to live in the normal address space?

@Amanieu
Copy link
Member

Amanieu commented Dec 5, 2020

Wouldn't that be unsound, since I could use that AllocRef with Box which will assume it to live in the normal address space?

In this context GPU memory would be mapped into the address space of the process and would be accessible just like normal memory.

@RalfJung
Copy link
Member

RalfJung commented Dec 5, 2020

Oh I see.

That would mean rustc must not assign any special semantics to AllocRef::alloc calls, and treat them like any other function call (except if that in turn happens to call the global allocator which is special). This seems reasonable, if we can ensure that it is indeed the case.

@oli-obk
Copy link
Contributor Author

oli-obk commented Dec 26, 2020

@bors r=TimDiekmann rollup

@bors
Copy link
Contributor

bors commented Dec 26, 2020

📌 Commit efcd8a9 has been approved by TimDiekmann

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Dec 26, 2020
@bors
Copy link
Contributor

bors commented Dec 26, 2020

⌛ Testing commit efcd8a9 with merge 0b644e4...

@bors
Copy link
Contributor

bors commented Dec 26, 2020

☀️ Test successful - checks-actions
Approved by: TimDiekmann
Pushing 0b644e4 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Dec 26, 2020
@bors bors merged commit 0b644e4 into rust-lang:master Dec 26, 2020
@rustbot rustbot added this to the 1.51.0 milestone Dec 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-allocators Area: Custom and system allocators merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.