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

Test whether self-referential futures are compatible with stacked borrows #532

Closed
RalfJung opened this Issue Nov 19, 2018 · 12 comments

Comments

Projects
None yet
4 participants
@RalfJung
Copy link
Member

RalfJung commented Nov 19, 2018

I have no idea if an async function that borrows across a yield point is compatible with Stacked Borrows. We should find out! I just am not sure how.

@cramertj @withoutboats I could use your help. :) What is a minimal example for code that creates a self-referential future and then runs it? Ideally with no dependencies beyond libstd but that might not be possible.^^ Also, if there are different cases/"kinds" of self-referential fields in futures, we should make sure we cover all of them.

rustc must test these things somehow, right? Might be worth looking into that, too.

@RalfJung RalfJung changed the title Test whether self-referential generators are compatible with stacked borrows Test whether self-referential futures are compatible with stacked borrows Nov 19, 2018

@Nemo157

This comment has been minimized.

Copy link
Contributor

Nemo157 commented Nov 19, 2018

I'm actually working on trying to create a step-by-step example of how an async fn is first transformed into a self-referential generator then into a state machine. I should have a nice small self-contained example that uses both references into upvars and local variables ready in the next couple of days. (I already have this playground that I intended to use, but I just realised I made a mistake in expanding await! that means this doesn't actually require self-references into local variables, just upvars).

if there are different cases/"kinds" of self-referential fields in futures, we should make sure we cover all of them

There are only two different kinds that I know of, and fundamentally they should be the same. You can have references to upvars that have been moved into the generator and references to locals created while the generator is running. Once the generator transform has run these both just become references into the generator state variable, and I don't think they're distinguishable in the MIR.

@Nemo157

This comment has been minimized.

Copy link
Contributor

Nemo157 commented Nov 19, 2018

Actually I might as well throw something together now, here should be a small example showing all kinds of references in an async fn. y is an upvar of the generator created in foo and z is a local of it; a reference to each are moved into the async block on line 14, inside await! the result implementation of this block is stored into a local variable (so that variable is referencing both y and z in the outer generator) and polled to completion over a yield point. a is a local of the generator which has a reference taken but is dereferenced and dropped without hitting a yield point. Finally x is kept as reference out of the generator environment, as an example of non-self-referential references.

There's also the minimal framework for running an async fn inside main, this may be changing a little bit in the near future in a couple of ways, but I'll be creating an up-to-date version of this example as it does.

@withoutboats

This comment has been minimized.

Copy link

withoutboats commented Nov 19, 2018

@RalfJung might it be easier to focus on generators before async fn, since they have direct access to yield?

@cramertj

This comment has been minimized.

Copy link
Member

cramertj commented Nov 19, 2018

Here's a self-referential generator example:

#![feature(generators, generator_trait)]

use std::ops::Generator;

fn main() {
    let mut generator = || {
        let mut x = Box::new(5);
        let y = &mut *x;
        *y = 5;
        yield ();
        *y = 10;
        *x
    };
    
    unsafe { generator.resume() };
}
@RalfJung

This comment has been minimized.

Copy link
Member Author

RalfJung commented Nov 20, 2018

@cramertj Thanks! I thought there had to be async and a Future. I clearly know nothing about how async functions work.^^

@RalfJung

This comment has been minimized.

Copy link
Member Author

RalfJung commented Nov 20, 2018

To my big surprise, this actually passes in miri :D

EDIT: Even more interestingly though, it no longer passes locally...

@RalfJung

This comment has been minimized.

Copy link
Member Author

RalfJung commented Nov 20, 2018

Oh. The generator MIR transform happens after we added the retag statements, leading to... strange results.

However, the generator MIR stuff happens extremely late -- it happens after inlining! That seems really strange to me, shouldn't we first get the MIR "complete" before doing interesting transformations?

@RalfJung

This comment has been minimized.

Copy link
Member Author

RalfJung commented Nov 20, 2018

@RalfJung

This comment has been minimized.

Copy link
Member Author

RalfJung commented Nov 20, 2018

@withoutboats That makes sense, I wasn't aware of the layering here. This seems covered by #536 though, and it's actually working (with a small rustc fix), which is great!

However, something like @Nemo157's example also seems like a nice addition. Thanks for that @Nemo157! It also seems to pass, to my ever greater surprise.^^

@RalfJung RalfJung reopened this Nov 28, 2018

@RalfJung

This comment has been minimized.

Copy link
Member Author

RalfJung commented Nov 28, 2018

With barriers entering the picture, I now see a violation of Stacked Borrows in async functions. The error happens with the following stacktrace:

error[E0080]: constant evaluation error: Stopping looking for borrow being accessed (Shr(None)) because of barrier (765)
    --> /home/r/src/rust/rustc/src/libcore/ptr.rs:2928:9
     |
2928 |         &mut *self.as_ptr()
     |         ^^^^^^^^^^^^^^^^^^^ Stopping looking for borrow being accessed (Shr(None)) because of barrier (765)
     |
     = note: inside call to `<std::ptr::NonNull<T>><std::task::LocalWaker>::as_mut` at /home/r/src/rust/rustc/src/libstd/future.rs:101:16
     = note: inside call to `std::future::get_task_waker::<[closure@DefId(1/1:1773 ~ std[de45]::future[0]::poll_with_tls_waker[0]::{{closure}}[0]) 0:std::pin::Pin<&mut std::future::GenFuture<[static generator@tests/run-pass-fullmir/async-fn.rs:15:26: 15:37 y:&&u32, z:&&u32 {}]>>], std::task::Poll<u32>>` at /home/r/src/rust/rustc/src/libstd/future.rs:110:5
note: inside call to `std::future::poll_with_tls_waker::<std::future::GenFuture<[static generator@tests/run-pass-fullmir/async-fn.rs:15:26: 15:37 y:&&u32, z:&&u32 {}]>>` at <::std::macros::await macros>:4:49
    --> tests/run-pass-fullmir/async-fn.rs:15:13
     |
15   |     let y = await!(async { *y + *z });
     |             ^^^^^^^^^^^^^^^^^^^^^^^^^
     = note: inside call to closure at /home/r/src/rust/rustc/src/libstd/future.rs:46:46
     = note: inside call to closure at /home/r/src/rust/rustc/src/libstd/future.rs:77:5
     = note: inside call to `std::future::set_task_waker::<[closure@DefId(1/1:1762 ~ std[de45]::future[0]::{{impl}}[1]::poll[0]::{{closure}}[0]) 0:std::pin::Pin<&mut std::future::GenFuture<[static generator@tests/run-pass-fullmir/async-fn.rs:11:42: 19:2 y:u32, x:&u32 for<'r, 's, 't0, 't1, 't2, 't3> {u32, &'r u32, &'s u32, impl std::future::Future, ()}]>>], std::task::Poll<u32>>` at /home/r/src/rust/rustc/src/libstd/future.rs:46:9
note: inside call to `<std::future::GenFuture<T> as std::future::Future><[static generator@tests/run-pass-fullmir/async-fn.rs:11:42: 19:2 y:u32, x:&u32 for<'r, 's, 't0, 't1, 't2, 't3> {u32, &'r u32, &'s u32, impl std::future::Future, ()}]>::poll` at tests/run-pass-fullmir/async-fn.rs:34:16
    --> tests/run-pass-fullmir/async-fn.rs:34:16
     |
34   |     assert_eq!(unsafe { Pin::new_unchecked(&mut fut) }.poll(&lw), Poll::Ready(31));
     |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     = note: inside call to `main` at /home/r/src/rust/rustc/src/libstd/rt.rs:74:34

The relevant parts are: There is a call to set_task_waker, which does

/// Sets the thread-local task context used by async/await futures.
pub fn set_task_waker<F, R>(lw: &LocalWaker, f: F) -> R
where
    F: FnOnce() -> R
{
    let old_waker = TLS_WAKER.with(|tls_waker| {
        tls_waker.replace(Some(NonNull::from(lw)))
    });
    let _reset_waker = SetOnDrop(old_waker);
    f()
}

Notice that lw is a shared reference. This gets called with some f that calls get_task_waker:

pub fn get_task_waker<F, R>(f: F) -> R
where
    F: FnOnce(&LocalWaker) -> R
{
    let waker_ptr = TLS_WAKER.with(|tls_waker| {
        // Clear the entry so that nested `get_task_waker` calls
        // will fail or set their own value.
        tls_waker.replace(None)
    });
    let _reset_waker = SetOnDrop(waker_ptr);

    let mut waker_ptr = waker_ptr.expect(
        "TLS LocalWaker not set. This is a rustc bug. \
        Please file an issue on https://github.com/rust-lang/rust.");
    unsafe { f(waker_ptr.as_mut()) }
}

Notice the as_mut: This creates a mutable reference, while there still is an aliasing shared reference live in set_task_waker! That seems a clear violation of mutable references being unique to me.

It also likely is just a typo, as_ref() should work as well because f just needs a shared reference.

@RalfJung

This comment has been minimized.

Copy link
Member Author

RalfJung commented Nov 28, 2018

I confirmed that rust-lang/rust#56319 fixes this.

pietroalbini added a commit to pietroalbini/rust that referenced this issue Nov 28, 2018

Rollup merge of rust-lang#56319 - RalfJung:async-mutable-ref, r=cramertj
fix futures creating aliasing mutable and shared ref

Fixes the problem described in rust-lang/miri#532 (comment): `set_task_waker` takes a shared reference and puts a copy into the TLS (in a `NonNull`), but `get_task_waker` gets it back out as a mutable reference. That violates "mutable references must not alias anything"!

pietroalbini added a commit to pietroalbini/rust that referenced this issue Nov 28, 2018

Rollup merge of rust-lang#56319 - RalfJung:async-mutable-ref, r=cram…
…ertj

 fix futures creating aliasing mutable and shared ref

 Fixes the problem described in rust-lang/miri#532 (comment):
 `set_task_waker` takes a shared reference and puts a copy into the TLS
 (in a `NonNull`), but `get_task_waker` gets it back out as a mutable
 reference. That violates "mutable references must not alias anything"!
@withoutboats

This comment has been minimized.

Copy link

withoutboats commented Nov 28, 2018

This bug would have also been eliminated when the desugaring uses some sort of resume argument instead of TLS (that is, the entire context in which the mistake occurred is incidental complexity to the implementation)

GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this issue Nov 29, 2018

Rollup merge of rust-lang#56319 - RalfJung:async-mutable-ref, r=cramertj
fix futures creating aliasing mutable and shared ref

Fixes the problem described in rust-lang/miri#532 (comment): `set_task_waker` takes a shared reference and puts a copy into the TLS (in a `NonNull`), but `get_task_waker` gets it back out as a mutable reference. That violates "mutable references must not alias anything"!

@RalfJung RalfJung closed this Feb 23, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.