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

Move OOM handling to liballoc and remove the oom lang item #51607

Closed
wants to merge 3 commits into from

Conversation

SimonSapin
Copy link
Contributor

This removes the need for no_std programs to define that lang item themselves: fixes #51540

@rust-highfive
Copy link
Collaborator

r? @petrochenkov

(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 Jun 17, 2018
@nagisa
Copy link
Member

nagisa commented Jun 18, 2018

Why is all this difficult dance of compiler generated functions necessary when a plain function definition with weak linkage would do the job (I think)?

pub fn take_oom_hook() -> fn(Layout) {
let hook = HOOK.swap(ptr::null_mut(), Ordering::SeqCst);
if hook.is_null() {
default_oom_hook
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not "take" the default hook, though. Should be reflected in the documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you expect that "taking" the default would replace it with a no-op? This is a behavior change not directly related to this PR and is maybe better discussed on the tracking issue: #51245

@SimonSapin
Copy link
Contributor Author

Does weak linkage of Rust symbols actually work on all platforms? How is it used?

@petrochenkov
Copy link
Contributor

r? @nagisa @alexcrichton
I haven't followed the allocator-related work.

@bors
Copy link
Contributor

bors commented Jun 19, 2018

☔ The latest upstream changes (presumably #51543) made this pull request unmergeable. Please resolve the merge conflicts.

@SimonSapin SimonSapin force-pushed the oom-hook branch 3 times, most recently from 46d3b1b to d70a0cb Compare June 19, 2018 22:51
@glandium
Copy link
Contributor

glandium commented Jun 20, 2018

AFAIK, weak linkage is not really a thing with MSVC. The closest would be __declspec(selectany), which doesn't given any guarantee about which is picked. It's meant for COMDAT things.

@Ericson2314
Copy link
Contributor

Ericson2314 commented Jun 20, 2018

is the HOOK stuff a public interface? Just curious: I trust this PR doesn't change the publicness of that. I only recall user-defined and mutable panic/unwinding handlers.

@SimonSapin
Copy link
Contributor Author

@Ericson2314 Yes, std::alloc::set_alloc_error_hook and take_alloc_error_hook are public functions. They were added recently to replace the Alloc::oom / GlobalAlloc::oom methods that you could override when implementing the traits: #51245. This PR also makes them available in the alloc crate.

@Ericson2314
Copy link
Contributor

Thanks for the info.

@Amanieu
Copy link
Member

Amanieu commented Jun 27, 2018

I think that this will break liballoc on thumbv6m-none-eabi since that target does not support atomic operations at all. See #37492

@SimonSapin
Copy link
Contributor Author

@Amanieu If #[cfg]’ing away Arc is an acceptable way to deal with targets like this, what do you think of #[cfg]’ing away set_alloc_error_hook and take_alloc_error_hook?

@Amanieu
Copy link
Member

Amanieu commented Jun 27, 2018

I think that's fine, considering #37492 did pretty much the same thing by always using the default OOM handler.

cc @japaric

@SimonSapin
Copy link
Contributor Author

I’ve verified that ./x.py dist --target thumbv6m-none-eabi failed on this PR because of “no AtomicPtr in sync::atomic”, and now succeeds after adding the appropriate #[cfg]s.

@SimonSapin SimonSapin force-pushed the oom-hook branch 2 times, most recently from 6e239a9 to 9a46f59 Compare June 27, 2018 15:49
@rust-highfive
Copy link
Collaborator

The job x86_64-gnu-llvm-3.9 of your PR failed on Travis (raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
[00:45:48] ....................................................................................................
[00:45:53] ....................................................................................................
[00:45:59] ....................................................................................................
[00:46:04] ....................................................................................................
[00:46:10] ...................F...i............................................................................
[00:46:20] ....................................................................................................
[00:46:26] ....................................................................................................
[00:46:32] ...........................................i........................................................
[00:46:34] ............................
[00:46:34] ............................
[00:46:34] failures:
[00:46:34] 
[00:46:34] ---- [ui] ui/missing-allocator.rs stdout ----
[00:46:34] diff of stderr:
[00:46:34] 
[00:46:34] 1 error: no global memory allocator found but one is required; link to std or add #[global_allocator] to a static item that implements the GlobalAlloc trait.
[00:46:34] 2 
[00:46:34] - error: aborting due to previous error
[00:46:34] + error[E0522]: definition of an unknown language item: `oom`
[00:46:34] +   --> $DIR/missing-allocator.rs:23:1
[00:46:34] +    |
[00:46:34] + LL | #[lang = "oom"]
[00:46:34] +    | ^^^^^^^^^^^^^^^ definition of unknown language item `oom`
[00:46:34] + error: aborting due to 2 previous errors
[00:46:34] + 
[00:46:34] + For more information about this error, try `rustc --explain E0522`.
[00:46:34] 5 
[00:46:34] 5 
[00:46:34] 
[00:46:34] 
[00:46:34] The actual stderr differed from the expected stderr.
[00:46:34] Actual stderr saved to /checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/missing-allocator/missing-allocator.stderr
[00:46:34] To update references, rerun the tests and pass the `--bless` flag
[00:46:34] To only update this specific test, also pass `--test-args missing-allocator.rs`
[00:46:34] error: 1 errors occurred comparing output.
[00:46:34] status: exit code: 101
[00:46:34] status: exit code: 101
[00:46:34] command: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/src/test/ui/missing-allocator.rs" "--target=x86_64-unknown-linux-gnu" "--error-format" "json" "-Zui-testing" "-o" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/missing-allocator/a" "-Crpath" "-O" "-Zunstable-options" "-Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers" "-C" "panic=abort" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/missing-allocator/auxiliary" "-A" "unused"
[00:46:34] ------------------------------------------
[00:46:34] 
[00:46:34] ------------------------------------------
[00:46:34] stderr:
[00:46:34] stderr:
[00:46:34] ------------------------------------------
[00:46:34] {"message":"no global memory allocator found but one is required; link to std or add #[global_allocator] to a static item that implements the GlobalAlloc trait.","code":null,"level":"error","spans":[],"children":[],"rendered":"error: no global memory allocator found but one is required; link to std or add #[global_allocator] to a static item that implements the GlobalAlloc trait.\n\n"}
[00:46:34] {"message":"definition of an unknown language item: `oom`","code":{"code":"E0522","explanation":"\nThe lang attribute is intended for marking special items that are built-in to\nRust itself. This includes special traits (like `Copy` and `Sized`) that affect\nhow the compiler behaves, as well as special functions that may be automatically\ninvoked (such as the handler for out-of-bounds accesses when indexing a slice).\nErroneous code example:\n\n```compile_fail,E0522\n#![feature(lang_items)]\n\n#[lang = \"cookie\"]\nfn cookie() -> ! { // error: definition of an unknown language item: `cookie`\n    loop {}\n}\n```\n"},"level":"error","spans":[{"file_name":"/checkout/src/test/ui/missing-allocator.rs","byte_start":699,"byte_end":714,"line_start":23,"line_end":23,"column_start":1,"column_end":16,"is_primary":true,"text":[{"text":"#[lang = \"oom\"]","highlight_start":1,"highlight_end":16}],"label":"definition of unknown language item `oom`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"error[E0522]: definition of an unknown language item: `oom`\n  --> /checkout/src/test/ui/missing-allocator.rs:23:1\n   |\nLL | #[lang = \"oom\"]\n   | ^^^^^^^^^^^^^^^ definition of unknown language item `oom`\n\n"}
[00:46:34] {"message":"aborting due to 2 previous errors","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to 2 previous errors\n\n"}
[00:46:34] {"message":"For more information about this error, try `rustc --explain E0522`.","code":null,"level":"","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0522`.\n"}
[00:46:34] ------------------------------------------
[00:46:34] 
[00:46:34] thread '[ui] ui/missing-allocator.rs' panicked at 'explicit panic', tools/compiletest/src/runtest.rs:3139:9
[00:46:34] note: Run with `RUST_BACKTRACE=1` for a backtrace.
---
[00:46:34] 
[00:46:34] thread 'main' panicked at 'Some tests failed', tools/compiletest/src/main.rs:498:22
[00:46:34] 
[00:46:34] 
[00:46:34] command did not execute successfully: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage0-tools-bin/compiletest" "--compile-lib-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib" "--run-lib-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib/rustlib/x86_64-unknown-linux-gnu/lib" "--rustc-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "--src-base" "/checkout/src/test/ui" "--build-base" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui" "--stage-id" "stage2-x86_64-unknown-linux-gnu" "--mode" "ui" "--target" "x86_64-unknown-linux-gnu" "--host" "x86_64-unknown-linux-gnu" "--llvm-filecheck" "/usr/lib/llvm-3.9/bin/FileCheck" "--host-rustcflags" "-Crpath -O -Zunstable-options " "--target-rustcflags" "-Crpath -O -Zunstable-options  -Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers" "--docck-python" "/usr/bin/python2.7" "--lldb-python" "/usr/bin/python2.7" "--gdb" "/usr/bin/gdb" "--quiet" "--llvm-version" "3.9.1\n" "--system-llvm" "--cc" "" "--cxx" "" "--cflags" "" "--llvm-components" "" "--llvm-cxxflags" "" "--adb-path" "adb" "--adb-test-dir" "/data/tmp/work" "--android-cross-path" "" "--color" "always"
[00:46:34] 
[00:46:34] 
[00:46:34] failed to run: /checkout/obj/build/bootstrap/debug/bootstrap test
[00:46:34] Build completed unsuccessfully in 0:02:07
[00:46:34] Build completed unsuccessfully in 0:02:07
[00:46:34] Makefile:58: recipe for target 'check' failed
[00:46:34] make: *** [check] Error 1

The command "stamp sh -x -c "$RUN_SCRIPT"" exited with 2.
travis_time:start:0d07aff4
$ date && (curl -fs --head https://google.com | grep ^Date: | sed 's/Date: //g' || true)

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

This removes the need for `no_std` programs to define that lang item themselves:

Fixes rust-lang#51540
@japaric
Copy link
Member

japaric commented Jun 28, 2018

Is the plan to stabilize not having an "oom" lang item for the edition release? Or do we have time
to RFC this?

My preference would be to keep the "oom" lang item and stabilize some #[oom] attribute (similar to
#[panic_implementation]) because (a) smallest devices won't have to pay for the overridable oom
handler, which has a performance cost (not so important because it's a cold path) and binary size
cost (more important), even when they don't override the handler. And (b) because I think an
overridable oom handler can be implemented on top of #[oom] and encapsulated as a standalone crate
(e.g. #![no_std] extern crate oom; oom::set_hook(..); ).

This removes the need for no_std programs to define that lang item themselves

Is this PR just for convenience then? The alloc crate is unstable and there no plans to stabilize
it this year AFAIK so not requiring an unstable oom lang item is not enough to allow allocation in
no_std context on stable.

@Amanieu
Copy link
Member

Amanieu commented Jun 28, 2018

@japaric There is a RFC to stabilize the alloc crate: rust-lang/rfcs#2480

Also see #51846 for a similar concern regarding lang items used by liballoc.

@SimonSapin
Copy link
Contributor Author

SimonSapin commented Jun 28, 2018

not requiring an unstable oom lang item is not enough to allow allocation in
no_std context on stable

I believe it is the only remaining blocker, in addition to allowing to import the crate itself: rust-lang/rfcs#2480

@SimonSapin
Copy link
Contributor Author

binary size cost (more important), even when they don't override the handler

Is this cost really significant? The "mandatory" code simplifies to:

pub fn handle_alloc_error(layout: Layout) -> ! {
    let hook = HOOK.load(Ordering::SeqCst);
    if hook.is_null() {
        // No-op when std is not linked
        __rust_maybe_default_alloc_error_hook(layout.size(), layout.align())
    } else {
        let hook: fn(Layout) = unsafe { mem::transmute(hook) }
        hook(layout);
    }
    // No-op when std is not linked
    __rust_maybe_abort_internal();
    intrinsics::abort()
}

static HOOK: AtomicPtr<()> = …;

Or, with LTO and without std:

pub fn handle_alloc_error(layout: Layout) -> ! {
    let hook = HOOK.load(Ordering::SeqCst);
    if !hook.is_null() {
        let hook: fn(Layout) = unsafe { mem::transmute(hook) }
        hook(layout);
    }
    intrinsics::abort()
}

static HOOK: AtomicPtr<()> = …;

… which is one pointer-sized static and one ~four-instructions function.

@japaric
Copy link
Member

japaric commented Jun 30, 2018

Is this cost really significant?

Is doubles the size of an allocator written for minimal footprint considered significant?

This program:

#[global_allocator]
static ALLOC: Alloc = Alloc;

fn main() {
    unsafe `{
        Alloc::init();
        let mut x = Box::new(0);
        ptr::write_volatile(&mut *x, 1);
    }
}

Compiled for ARMv7-M with opt-level=s produces:

  • alloc = NOP (e.g. always returns 4 as *mut u8) is the baseline
  • alloc = bump_pointer + oom_lang = intrinsics::abort (or intrinsics::breakpoint) is 72 bytes above the baseline.
  • alloc = bump_pointer + oom_hook = intrinsics::abort (default) is 90 bytes above the baseline.
  • alloc = bump_pointer + oom_hook = intrinsics::breakpoint (override) is 150 bytes above the baseline.

@Ericson2314
Copy link
Contributor

Ericson2314 commented Jul 2, 2018

rust-lang/rfcs#2492 provides a more general way to defer the definition of the OOM hook (along with the other global resources defined in alloc). @japaric it completely solves cost concerns as code gen is concerns until all type variables are substituted for concrete definitions, just like like generics, at which point everything is static without indirection.

Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the @SimonSapin! On the technical side of things this looks pretty good to me.

One thing I'm not sure about, though, is the story around liballoc and stabilization in general. In that sense this is more related to rust-lang/rfcs#2480, but I think that this PR may want to hold off on landing until rust-lang/rfcs#2480 is decided on.

My main concern is how we've got this weird #![default_lib_allocator] attribute which is the only way to customize the default method of oom printing/aborting. This seems like it's sort of a hack to get extern crate alloc with no other definitions continuing to work tomorrow as it does today. One possible route we may want to take, for example, is to forbid extern crate alloc unless the crate graph somehow defines what to do on OOM or how to abort.

In any case I can comment with more info on the RFC!

// This function is generated by the compiler
// and calls __rust_abort_internal in src/libstd/alloc.rs
// if the `std` crate is linked.
fn __rust_maybe_abort_internal();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially reading this I was a little confused by this about why both the hook and the abort mechanism were configurable. I think, though, it's to do sys::abort_internal, right? (as opposed to intrinsics::abort)

If that's the case, mind adding a small comment here alluding to that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's to do sys::abort_internal, right? (as opposed to intrinsics::abort)

Yes, exactly

};

for method in OOM_HANDLING_METHODS {
let callee = if has_plaftom_functions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"has_platform_functions"

@SimonSapin
Copy link
Contributor Author

this weird #![default_lib_allocator] attribute which is the only way to customize the default method of oom printing/aborting

I definitely view #![default_lib_allocator] as an internal implementation detail of the standard library. The idea if this PR is that you can dynamically register a custom hook that replaces the default printing. If the hook does not return (e.g. by panicking), it can also replace the default aborting.

One possible route we may want to take, for example, is to forbid extern crate alloc unless the crate graph somehow defines what to do on OOM or how to abort.

Yes, this is an alternative being discussed in the tracking issue #51540. I find @japaric’s arguments for static dispatch (rather than dynamic hooks with an atomic function pointer) to be convincing, so I was tempted to close this PR. (I’m less decided on deliberately not providing a default, though.)

In any case I can comment with more info on the RFC!

Please do comment with your thoughts on rust-lang/rfcs#2480, but I think that it is fairly orthogonal to this. Stabilizing the crate with OOM handling still unstable already unblocks the use case of libraries that want to be both Stable-compatible and no_std-compatible. (Even if these sets of users are still disjoint for now.)

@alexcrichton
Copy link
Member

FWIW an attribute like #[oom] I also think would be a great idea (and I think I'm also convinced that it may be worth it over this dynamic strategy), and it could be implemented pretty similar to #[panic_implementation]

@SimonSapin
Copy link
Contributor Author

Alright. Closing in favor of something statically-dispatched. Let’s discuss in #51540.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tracking issue for the #[alloc_error_handler] attribute (for no_std + liballoc)
10 participants