Skip to content

WIP: Remove ResumeTy from async lowering #107562

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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 29 additions & 29 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
@@ -715,18 +715,28 @@ impl<'hir> LoweringContext<'_, 'hir> {
// whereas a generator does not.
let (inputs, params, task_context): (&[_], &[_], _) = match desugaring_kind {
hir::CoroutineDesugaring::Async | hir::CoroutineDesugaring::AsyncGen => {
// Resume argument type: `ResumeTy`
let unstable_span = self.mark_span_with_reason(
DesugaringKind::Async,
self.lower_span(span),
Some(Lrc::clone(&self.allow_gen_future)),
);
let resume_ty =
self.make_lang_item_qpath(hir::LangItem::ResumeTy, unstable_span, None);
// Resume argument type: `&mut Context<'_>`.
let context_lifetime_ident = Ident::with_dummy_span(kw::UnderscoreLifetime);
let context_lifetime = self.arena.alloc(hir::Lifetime {
hir_id: self.next_id(),
ident: context_lifetime_ident,
res: hir::LifetimeName::Infer,
});
let context_path =
hir::QPath::LangItem(hir::LangItem::Context, self.lower_span(span));
let context_ty = hir::MutTy {
ty: self.arena.alloc(hir::Ty {
hir_id: self.next_id(),
kind: hir::TyKind::Path(context_path),
span: self.lower_span(span),
}),
mutbl: hir::Mutability::Mut,
};

let input_ty = hir::Ty {
hir_id: self.next_id(),
kind: hir::TyKind::Path(resume_ty),
span: unstable_span,
kind: hir::TyKind::Ref(context_lifetime, context_ty),
span: self.lower_span(span),
};
let inputs = arena_vec![self; input_ty];

@@ -823,7 +833,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
/// mut __awaitee => loop {
/// match unsafe { ::std::future::Future::poll(
/// <::std::pin::Pin>::new_unchecked(&mut __awaitee),
/// ::std::future::get_context(task_context),
/// task_context,
/// ) } {
/// ::std::task::Poll::Ready(result) => break result,
/// ::std::task::Poll::Pending => {}
@@ -882,26 +892,21 @@ impl<'hir> LoweringContext<'_, 'hir> {
FutureKind::AsyncIterator => Some(Lrc::clone(&self.allow_for_await)),
};
let span = self.mark_span_with_reason(DesugaringKind::Await, await_kw_span, features);
let gen_future_span = self.mark_span_with_reason(
DesugaringKind::Await,
full_span,
Some(Lrc::clone(&self.allow_gen_future)),
);
let expr_hir_id = expr.hir_id;

// Note that the name of this binding must not be changed to something else because
// debuggers and debugger extensions expect it to be called `__awaitee`. They use
// this name to identify what is being awaited by a suspended async functions.
let awaitee_ident = Ident::with_dummy_span(sym::__awaitee);
let (awaitee_pat, awaitee_pat_hid) =
self.pat_ident_binding_mode(gen_future_span, awaitee_ident, hir::BindingMode::MUT);
self.pat_ident_binding_mode(full_span, awaitee_ident, hir::BindingMode::MUT);

let task_context_ident = Ident::with_dummy_span(sym::_task_context);

// unsafe {
// ::std::future::Future::poll(
// ::std::pin::Pin::new_unchecked(&mut __awaitee),
// ::std::future::get_context(task_context),
// task_context,
// )
// }
let poll_expr = {
@@ -919,21 +924,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::LangItem::PinNewUnchecked,
arena_vec![self; ref_mut_awaitee],
);
let get_context = self.expr_call_lang_item_fn_mut(
gen_future_span,
hir::LangItem::GetContext,
arena_vec![self; task_context],
);
let call = match await_kind {
FutureKind::Future => self.expr_call_lang_item_fn(
span,
hir::LangItem::FuturePoll,
arena_vec![self; new_unchecked, get_context],
arena_vec![self; new_unchecked, task_context],
),
FutureKind::AsyncIterator => self.expr_call_lang_item_fn(
span,
hir::LangItem::AsyncIteratorPollNext,
arena_vec![self; new_unchecked, get_context],
arena_vec![self; new_unchecked, task_context],
),
};
self.arena.alloc(self.expr_unsafe(call))
@@ -944,14 +944,14 @@ impl<'hir> LoweringContext<'_, 'hir> {
let loop_hir_id = self.lower_node_id(loop_node_id);
let ready_arm = {
let x_ident = Ident::with_dummy_span(sym::result);
let (x_pat, x_pat_hid) = self.pat_ident(gen_future_span, x_ident);
let x_expr = self.expr_ident(gen_future_span, x_ident, x_pat_hid);
let ready_field = self.single_pat_field(gen_future_span, x_pat);
let (x_pat, x_pat_hid) = self.pat_ident(full_span, x_ident);
let x_expr = self.expr_ident(full_span, x_ident, x_pat_hid);
let ready_field = self.single_pat_field(full_span, x_pat);
let ready_pat = self.pat_lang_item_variant(span, hir::LangItem::PollReady, ready_field);
let break_x = self.with_loop_scope(loop_hir_id, move |this| {
let expr_break =
hir::ExprKind::Break(this.lower_loop_destination(None), Some(x_expr));
this.arena.alloc(this.expr(gen_future_span, expr_break))
this.arena.alloc(this.expr(full_span, expr_break))
});
self.arm(ready_pat, break_x)
};
5 changes: 0 additions & 5 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
@@ -376,11 +376,6 @@ language_item_table! {
AsyncGenPending, sym::AsyncGenPending, async_gen_pending, Target::AssocConst, GenericRequirement::Exact(1);
AsyncGenFinished, sym::AsyncGenFinished, async_gen_finished, Target::AssocConst, GenericRequirement::Exact(1);

// FIXME(swatinem): the following lang items are used for async lowering and
// should become obsolete eventually.
ResumeTy, sym::ResumeTy, resume_ty, Target::Struct, GenericRequirement::None;
GetContext, sym::get_context, get_context_fn, Target::Fn, GenericRequirement::None;

Context, sym::Context, context, Target::Struct, GenericRequirement::None;
FuturePoll, sym::poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;

9 changes: 0 additions & 9 deletions compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
@@ -833,15 +833,6 @@ impl<'tcx> Ty<'tcx> {
let def_id = tcx.require_lang_item(LangItem::MaybeUninit, None);
Ty::new_generic_adt(tcx, def_id, ty)
}

/// Creates a `&mut Context<'_>` [`Ty`] with erased lifetimes.
pub fn new_task_context(tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
let context_did = tcx.require_lang_item(LangItem::Context, None);
let context_adt_ref = tcx.adt_def(context_did);
let context_args = tcx.mk_args(&[tcx.lifetimes.re_erased.into()]);
let context_ty = Ty::new_adt(tcx, context_adt_ref, context_args);
Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, context_ty)
}
}

impl<'tcx> rustc_type_ir::inherent::Ty<TyCtxt<'tcx>> for Ty<'tcx> {
105 changes: 1 addition & 104 deletions compiler/rustc_mir_transform/src/coroutine.rs
Original file line number Diff line number Diff line change
@@ -517,109 +517,14 @@ fn replace_local<'tcx>(
new_local
}

/// Transforms the `body` of the coroutine applying the following transforms:
///
/// - Eliminates all the `get_context` calls that async lowering created.
/// - Replace all `Local` `ResumeTy` types with `&mut Context<'_>` (`context_mut_ref`).
///
/// The `Local`s that have their types replaced are:
/// - The `resume` argument itself.
/// - The argument to `get_context`.
/// - The yielded value of a `yield`.
///
/// The `ResumeTy` hides a `&mut Context<'_>` behind an unsafe raw pointer, and the
/// `get_context` function is being used to convert that back to a `&mut Context<'_>`.
///
/// Ideally the async lowering would not use the `ResumeTy`/`get_context` indirection,
/// but rather directly use `&mut Context<'_>`, however that would currently
/// lead to higher-kinded lifetime errors.
/// See <https://github.com/rust-lang/rust/issues/105501>.
///
/// The async lowering step and the type / lifetime inference / checking are
/// still using the `ResumeTy` indirection for the time being, and that indirection
/// is removed here. After this transform, the coroutine body only knows about `&mut Context<'_>`.
fn transform_async_context<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let context_mut_ref = Ty::new_task_context(tcx);

// replace the type of the `resume` argument
replace_resume_ty_local(tcx, body, Local::new(2), context_mut_ref);

let get_context_def_id = tcx.require_lang_item(LangItem::GetContext, None);

for bb in START_BLOCK..body.basic_blocks.next_index() {
let bb_data = &body[bb];
if bb_data.is_cleanup {
continue;
}

match &bb_data.terminator().kind {
TerminatorKind::Call { func, .. } => {
let func_ty = func.ty(body, tcx);
if let ty::FnDef(def_id, _) = *func_ty.kind() {
if def_id == get_context_def_id {
let local = eliminate_get_context_call(&mut body[bb]);
replace_resume_ty_local(tcx, body, local, context_mut_ref);
}
}
}
TerminatorKind::Yield { resume_arg, .. } => {
replace_resume_ty_local(tcx, body, resume_arg.local, context_mut_ref);
}
_ => {}
}
}
}

fn eliminate_get_context_call<'tcx>(bb_data: &mut BasicBlockData<'tcx>) -> Local {
let terminator = bb_data.terminator.take().unwrap();
let TerminatorKind::Call { args, destination, target, .. } = terminator.kind else {
bug!();
};
let [arg] = *Box::try_from(args).unwrap();
let local = arg.node.place().unwrap().local;

let arg = Rvalue::Use(arg.node);
let assign = Statement {
source_info: terminator.source_info,
kind: StatementKind::Assign(Box::new((destination, arg))),
};
bb_data.statements.push(assign);
bb_data.terminator = Some(Terminator {
source_info: terminator.source_info,
kind: TerminatorKind::Goto { target: target.unwrap() },
});
local
}

#[cfg_attr(not(debug_assertions), allow(unused))]
fn replace_resume_ty_local<'tcx>(
tcx: TyCtxt<'tcx>,
body: &mut Body<'tcx>,
local: Local,
context_mut_ref: Ty<'tcx>,
) {
let local_ty = std::mem::replace(&mut body.local_decls[local].ty, context_mut_ref);
// We have to replace the `ResumeTy` that is used for type and borrow checking
// with `&mut Context<'_>` in MIR.
#[cfg(debug_assertions)]
{
if let ty::Adt(resume_ty_adt, _) = local_ty.kind() {
let expected_adt = tcx.adt_def(tcx.require_lang_item(LangItem::ResumeTy, None));
assert_eq!(*resume_ty_adt, expected_adt);
} else {
panic!("expected `ResumeTy`, found `{:?}`", local_ty);
};
}
}

/// Transforms the `body` of the coroutine applying the following transform:
///
/// - Remove the `resume` argument.
///
/// Ideally the async lowering would not add the `resume` argument.
///
/// The async lowering step and the type / lifetime inference / checking are
/// still using the `resume` argument for the time being. After this transform,
/// still using the `resume` argument for the time being. After this transform
/// the coroutine body doesn't have the `resume` argument.
fn transform_gen_context<'tcx>(body: &mut Body<'tcx>) {
// This leaves the local representing the `resume` argument in place,
@@ -1574,14 +1479,6 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform {
// RETURN_PLACE then is a fresh unused local with type ret_ty.
let old_ret_local = replace_local(RETURN_PLACE, new_ret_ty, body, tcx);

// Replace all occurrences of `ResumeTy` with `&mut Context<'_>` within async bodies.
if matches!(
coroutine_kind,
CoroutineKind::Desugared(CoroutineDesugaring::Async | CoroutineDesugaring::AsyncGen, _)
) {
transform_async_context(tcx, body);
}

// We also replace the resume argument and insert an `Assign`.
// This is needed because the resume argument `_2` might be live across a `yield`, in which
// case there is no `Assign` to it that the transform can turn into a store to the coroutine
2 changes: 0 additions & 2 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -309,7 +309,6 @@ symbols! {
Relaxed,
Release,
Result,
ResumeTy,
Return,
Right,
Rust,
@@ -1014,7 +1013,6 @@ symbols! {
generic_const_exprs,
generic_const_items,
generic_param_attrs,
get_context,
global_alloc_ty,
global_allocator,
global_asm,
32 changes: 2 additions & 30 deletions compiler/rustc_ty_utils/src/abi.rs
Original file line number Diff line number Diff line change
@@ -179,21 +179,7 @@ fn fn_sig_for_fn_abi<'tcx>(
let poll_args = tcx.mk_args(&[sig.return_ty.into()]);
let ret_ty = Ty::new_adt(tcx, poll_adt_ref, poll_args);

// We have to replace the `ResumeTy` that is used for type and borrow checking
// with `&mut Context<'_>` which is used in codegen.
#[cfg(debug_assertions)]
{
if let ty::Adt(resume_ty_adt, _) = sig.resume_ty.kind() {
let expected_adt =
tcx.adt_def(tcx.require_lang_item(LangItem::ResumeTy, None));
assert_eq!(*resume_ty_adt, expected_adt);
} else {
panic!("expected `ResumeTy`, found `{:?}`", sig.resume_ty);
};
}
let context_mut_ref = Ty::new_task_context(tcx);

(Some(context_mut_ref), ret_ty)
(Some(sig.resume_ty), ret_ty)
}
hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _) => {
// The signature should be `Iterator::next(_) -> Option<Yield>`
@@ -215,21 +201,7 @@ fn fn_sig_for_fn_abi<'tcx>(
// Yield type is already `Poll<Option<yield_ty>>`
let ret_ty = sig.yield_ty;

// We have to replace the `ResumeTy` that is used for type and borrow checking
// with `&mut Context<'_>` which is used in codegen.
#[cfg(debug_assertions)]
{
if let ty::Adt(resume_ty_adt, _) = sig.resume_ty.kind() {
let expected_adt =
tcx.adt_def(tcx.require_lang_item(LangItem::ResumeTy, None));
assert_eq!(*resume_ty_adt, expected_adt);
} else {
panic!("expected `ResumeTy`, found `{:?}`", sig.resume_ty);
};
}
let context_mut_ref = Ty::new_task_context(tcx);

(Some(context_mut_ref), ret_ty)
(Some(sig.resume_ty), ret_ty)
}
hir::CoroutineKind::Coroutine(_) => {
// The signature should be `Coroutine::resume(_, Resume) -> CoroutineState<Yield, Return>`
4 changes: 2 additions & 2 deletions library/core/src/future/mod.rs
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ pub use self::join::join;
/// non-Send/Sync as well, and we don't want that.
///
/// It also simplifies the HIR lowering of `.await`.
#[lang = "ResumeTy"]
#[cfg_attr(bootstrap, lang = "ResumeTy")]
#[doc(hidden)]
#[unstable(feature = "gen_future", issue = "50547")]
#[derive(Debug, Copy, Clone)]
@@ -56,7 +56,7 @@ unsafe impl Send for ResumeTy {}
#[unstable(feature = "gen_future", issue = "50547")]
unsafe impl Sync for ResumeTy {}

#[lang = "get_context"]
#[cfg_attr(bootstrap, lang = "get_context")]
#[doc(hidden)]
#[unstable(feature = "gen_future", issue = "50547")]
#[must_use]
4 changes: 2 additions & 2 deletions tests/ui/async-await/unreachable-lint.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//@ check-pass
//@ edition:2018
#![deny(unreachable_code)]

async fn foo() {
endless().await;
//~^ ERROR unreachable expression
}

async fn endless() -> ! {
loop {}
}

fn main() { }
fn main() {}
17 changes: 17 additions & 0 deletions tests/ui/async-await/unreachable-lint.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error: unreachable expression
--> $DIR/unreachable-lint.rs:5:5
|
LL | endless().await;
| ^^^^^^^^^^^^^^^
| |
| unreachable expression
| any code following this expression is unreachable
|
note: the lint level is defined here
--> $DIR/unreachable-lint.rs:2:9
|
LL | #![deny(unreachable_code)]
| ^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

2 changes: 1 addition & 1 deletion tests/ui/lang-items/required-lang-item.rs
Original file line number Diff line number Diff line change
@@ -7,4 +7,4 @@
#[lang="copy"] pub trait Copy { }
#[lang="sized"] pub trait Sized { }

async fn x() {} //~ ERROR requires `ResumeTy` lang_item
async fn x() {} //~ ERROR requires `future_trait` lang_item
6 changes: 3 additions & 3 deletions tests/ui/lang-items/required-lang-item.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: requires `ResumeTy` lang_item
--> $DIR/required-lang-item.rs:10:14
error: requires `future_trait` lang_item
--> $DIR/required-lang-item.rs:10:1
|
LL | async fn x() {}
| ^^
| ^^^^^^^^^^^^

error: aborting due to 1 previous error

4 changes: 2 additions & 2 deletions tests/ui/regions/closure-in-projection-issue-97405.rs
Original file line number Diff line number Diff line change
@@ -22,11 +22,11 @@ fn good_generic_fn<T>() {
// This should fail because `T` ends up in the upvars of the closure.
fn bad_generic_fn<T: Copy>(t: T) {
assert_static(opaque(async move { t; }).next());
//~^ ERROR the associated type `<impl Iterator as Iterator>::Item` may not live long enough
//~^ ERROR the parameter type `T` may not live long enough
assert_static(opaque(move || { t; }).next());
//~^ ERROR the associated type `<impl Iterator as Iterator>::Item` may not live long enough
assert_static(opaque(opaque(async move { t; }).next()).next());
//~^ ERROR the associated type `<impl Iterator as Iterator>::Item` may not live long enough
//~^ ERROR the parameter type `T` may not live long enough
}

fn main() {}
22 changes: 14 additions & 8 deletions tests/ui/regions/closure-in-projection-issue-97405.stderr
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
error[E0310]: the associated type `<impl Iterator as Iterator>::Item` may not live long enough
error[E0310]: the parameter type `T` may not live long enough
--> $DIR/closure-in-projection-issue-97405.rs:24:5
|
LL | assert_static(opaque(async move { t; }).next());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| the associated type `<impl Iterator as Iterator>::Item` must be valid for the static lifetime...
| ...so that the type `<impl Iterator as Iterator>::Item` will meet its required lifetime bounds
| the parameter type `T` must be valid for the static lifetime...
| ...so that the type `T` will meet its required lifetime bounds
|
= help: consider adding an explicit lifetime bound `<impl Iterator as Iterator>::Item: 'static`...
help: consider adding an explicit lifetime bound
|
LL | fn bad_generic_fn<T: Copy + 'static>(t: T) {
| +++++++++

error[E0310]: the associated type `<impl Iterator as Iterator>::Item` may not live long enough
--> $DIR/closure-in-projection-issue-97405.rs:26:5
@@ -20,16 +23,19 @@ LL | assert_static(opaque(move || { t; }).next());
|
= help: consider adding an explicit lifetime bound `<impl Iterator as Iterator>::Item: 'static`...

error[E0310]: the associated type `<impl Iterator as Iterator>::Item` may not live long enough
error[E0310]: the parameter type `T` may not live long enough
--> $DIR/closure-in-projection-issue-97405.rs:28:5
|
LL | assert_static(opaque(opaque(async move { t; }).next()).next());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| the associated type `<impl Iterator as Iterator>::Item` must be valid for the static lifetime...
| ...so that the type `<impl Iterator as Iterator>::Item` will meet its required lifetime bounds
| the parameter type `T` must be valid for the static lifetime...
| ...so that the type `T` will meet its required lifetime bounds
|
= help: consider adding an explicit lifetime bound `<impl Iterator as Iterator>::Item: 'static`...
help: consider adding an explicit lifetime bound
|
LL | fn bad_generic_fn<T: Copy + 'static>(t: T) {
| +++++++++

error: aborting due to 3 previous errors