Skip to content

Commit c7491b9

Browse files
committed
Auto merge of #123402 - workingjubilee:rollup-0j5ihn6, r=workingjubilee
Rollup of 4 pull requests Successful merges: - #122411 ( Provide cabi_realloc on wasm32-wasip2 by default ) - #123349 (Fix capture analysis for by-move closure bodies) - #123359 (Link against libc++abi and libunwind as well when building LLVM wrappers on AIX) - #123388 (use a consistent style for links) r? `@ghost` `@rustbot` modify labels: rollup
2 parents 76cf07d + 0c0d888 commit c7491b9

File tree

10 files changed

+389
-42
lines changed

10 files changed

+389
-42
lines changed

compiler/rustc_llvm/build.rs

+6
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ fn main() {
391391
}
392392
}
393393

394+
// libc++abi and libunwind have to be specified explicitly on AIX.
395+
if target.contains("aix") {
396+
println!("cargo:rustc-link-lib=c++abi");
397+
println!("cargo:rustc-link-lib=unwind");
398+
}
399+
394400
// Libstdc++ depends on pthread which Rust doesn't link on MinGW
395401
// since nothing else requires it.
396402
if target.ends_with("windows-gnu") {

compiler/rustc_mir_transform/src/coroutine/by_move_body.rs

+118-31
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,66 @@
1-
//! A MIR pass which duplicates a coroutine's body and removes any derefs which
2-
//! would be present for upvars that are taken by-ref. The result of which will
3-
//! be a coroutine body that takes all of its upvars by-move, and which we stash
4-
//! into the `CoroutineInfo` for all coroutines returned by coroutine-closures.
1+
//! This pass constructs a second coroutine body sufficient for return from
2+
//! `FnOnce`/`AsyncFnOnce` implementations for coroutine-closures (e.g. async closures).
3+
//!
4+
//! Consider an async closure like:
5+
//! ```rust
6+
//! #![feature(async_closure)]
7+
//!
8+
//! let x = vec![1, 2, 3];
9+
//!
10+
//! let closure = async move || {
11+
//! println!("{x:#?}");
12+
//! };
13+
//! ```
14+
//!
15+
//! This desugars to something like:
16+
//! ```rust,ignore (invalid-borrowck)
17+
//! let x = vec![1, 2, 3];
18+
//!
19+
//! let closure = move || {
20+
//! async {
21+
//! println!("{x:#?}");
22+
//! }
23+
//! };
24+
//! ```
25+
//!
26+
//! Important to note here is that while the outer closure *moves* `x: Vec<i32>`
27+
//! into its upvars, the inner `async` coroutine simply captures a ref of `x`.
28+
//! This is the "magic" of async closures -- the futures that they return are
29+
//! allowed to borrow from their parent closure's upvars.
30+
//!
31+
//! However, what happens when we call `closure` with `AsyncFnOnce` (or `FnOnce`,
32+
//! since all async closures implement that too)? Well, recall the signature:
33+
//! ```
34+
//! use std::future::Future;
35+
//! pub trait AsyncFnOnce<Args>
36+
//! {
37+
//! type CallOnceFuture: Future<Output = Self::Output>;
38+
//! type Output;
39+
//! fn async_call_once(
40+
//! self,
41+
//! args: Args
42+
//! ) -> Self::CallOnceFuture;
43+
//! }
44+
//! ```
45+
//!
46+
//! This signature *consumes* the async closure (`self`) and returns a `CallOnceFuture`.
47+
//! How do we deal with the fact that the coroutine is supposed to take a reference
48+
//! to the captured `x` from the parent closure, when that parent closure has been
49+
//! destroyed?
50+
//!
51+
//! This is the second piece of magic of async closures. We can simply create a
52+
//! *second* `async` coroutine body where that `x` that was previously captured
53+
//! by reference is now captured by value. This means that we consume the outer
54+
//! closure and return a new coroutine that will hold onto all of these captures,
55+
//! and drop them when it is finished (i.e. after it has been `.await`ed).
56+
//!
57+
//! We do this with the analysis below, which detects the captures that come from
58+
//! borrowing from the outer closure, and we simply peel off a `deref` projection
59+
//! from them. This second body is stored alongside the first body, and optimized
60+
//! with it in lockstep. When we need to resolve a body for `FnOnce` or `AsyncFnOnce`,
61+
//! we use this "by move" body instead.
62+
63+
use itertools::Itertools;
564

665
use rustc_data_structures::unord::UnordSet;
766
use rustc_hir as hir;
@@ -14,6 +73,8 @@ pub struct ByMoveBody;
1473

1574
impl<'tcx> MirPass<'tcx> for ByMoveBody {
1675
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
76+
// We only need to generate by-move coroutine bodies for coroutines that come
77+
// from coroutine-closures.
1778
let Some(coroutine_def_id) = body.source.def_id().as_local() else {
1879
return;
1980
};
@@ -22,44 +83,70 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
2283
else {
2384
return;
2485
};
86+
87+
// Also, let's skip processing any bodies with errors, since there's no guarantee
88+
// the MIR body will be constructed well.
2589
let coroutine_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty;
2690
if coroutine_ty.references_error() {
2791
return;
2892
}
29-
let ty::Coroutine(_, args) = *coroutine_ty.kind() else { bug!("{body:#?}") };
3093

31-
let coroutine_kind = args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap();
94+
let ty::Coroutine(_, coroutine_args) = *coroutine_ty.kind() else { bug!("{body:#?}") };
95+
// We don't need to generate a by-move coroutine if the kind of the coroutine is
96+
// already `FnOnce` -- that means that any upvars that the closure consumes have
97+
// already been taken by-value.
98+
let coroutine_kind = coroutine_args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap();
3299
if coroutine_kind == ty::ClosureKind::FnOnce {
33100
return;
34101
}
35102

103+
let parent_def_id = tcx.local_parent(coroutine_def_id);
104+
let ty::CoroutineClosure(_, parent_args) =
105+
*tcx.type_of(parent_def_id).instantiate_identity().kind()
106+
else {
107+
bug!();
108+
};
109+
let parent_closure_args = parent_args.as_coroutine_closure();
110+
let num_args = parent_closure_args
111+
.coroutine_closure_sig()
112+
.skip_binder()
113+
.tupled_inputs_ty
114+
.tuple_fields()
115+
.len();
116+
36117
let mut by_ref_fields = UnordSet::default();
37-
let by_move_upvars = Ty::new_tup_from_iter(
38-
tcx,
39-
tcx.closure_captures(coroutine_def_id).iter().enumerate().map(|(idx, capture)| {
40-
if capture.is_by_ref() {
41-
by_ref_fields.insert(FieldIdx::from_usize(idx));
42-
}
43-
capture.place.ty()
44-
}),
45-
);
46-
let by_move_coroutine_ty = Ty::new_coroutine(
47-
tcx,
48-
coroutine_def_id.to_def_id(),
49-
ty::CoroutineArgs::new(
118+
for (idx, (coroutine_capture, parent_capture)) in tcx
119+
.closure_captures(coroutine_def_id)
120+
.iter()
121+
// By construction we capture all the args first.
122+
.skip(num_args)
123+
.zip_eq(tcx.closure_captures(parent_def_id))
124+
.enumerate()
125+
{
126+
// This upvar is captured by-move from the parent closure, but by-ref
127+
// from the inner async block. That means that it's being borrowed from
128+
// the outer closure body -- we need to change the coroutine to take the
129+
// upvar by value.
130+
if coroutine_capture.is_by_ref() && !parent_capture.is_by_ref() {
131+
by_ref_fields.insert(FieldIdx::from_usize(num_args + idx));
132+
}
133+
134+
// Make sure we're actually talking about the same capture.
135+
// FIXME(async_closures): We could look at the `hir::Upvar` instead?
136+
assert_eq!(coroutine_capture.place.ty(), parent_capture.place.ty());
137+
}
138+
139+
let by_move_coroutine_ty = tcx
140+
.instantiate_bound_regions_with_erased(parent_closure_args.coroutine_closure_sig())
141+
.to_coroutine_given_kind_and_upvars(
50142
tcx,
51-
ty::CoroutineArgsParts {
52-
parent_args: args.as_coroutine().parent_args(),
53-
kind_ty: Ty::from_closure_kind(tcx, ty::ClosureKind::FnOnce),
54-
resume_ty: args.as_coroutine().resume_ty(),
55-
yield_ty: args.as_coroutine().yield_ty(),
56-
return_ty: args.as_coroutine().return_ty(),
57-
witness: args.as_coroutine().witness(),
58-
tupled_upvars_ty: by_move_upvars,
59-
},
60-
)
61-
.args,
62-
);
143+
parent_closure_args.parent_args(),
144+
coroutine_def_id.to_def_id(),
145+
ty::ClosureKind::FnOnce,
146+
tcx.lifetimes.re_erased,
147+
parent_closure_args.tupled_upvars_ty(),
148+
parent_closure_args.coroutine_captures_by_ref_ty(),
149+
);
63150

64151
let mut by_move_body = body.clone();
65152
MakeByMoveBody { tcx, by_ref_fields, by_move_coroutine_ty }.visit_body(&mut by_move_body);

library/std/src/fs.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use crate::time::SystemTime;
3737
///
3838
/// # Examples
3939
///
40-
/// Creates a new file and write bytes to it (you can also use [`write()`]):
40+
/// Creates a new file and write bytes to it (you can also use [`write`]):
4141
///
4242
/// ```no_run
4343
/// use std::fs::File;
@@ -2018,7 +2018,7 @@ pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()>
20182018
/// the length of the `to` file as reported by `metadata`.
20192019
///
20202020
/// If you want to copy the contents of one file to another and you’re
2021-
/// working with [`File`]s, see the [`io::copy()`] function.
2021+
/// working with [`File`]s, see the [`io::copy`](io::copy()) function.
20222022
///
20232023
/// # Platform-specific behavior
20242024
///

library/std/src/sys/pal/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ cfg_if::cfg_if! {
3737
} else if #[cfg(target_os = "hermit")] {
3838
mod hermit;
3939
pub use self::hermit::*;
40-
} else if #[cfg(target_os = "wasi")] {
41-
mod wasi;
42-
pub use self::wasi::*;
4340
} else if #[cfg(all(target_os = "wasi", target_env = "p2"))] {
4441
mod wasip2;
4542
pub use self::wasip2::*;
43+
} else if #[cfg(target_os = "wasi")] {
44+
mod wasi;
45+
pub use self::wasi::*;
4646
} else if #[cfg(target_family = "wasm")] {
4747
mod wasm;
4848
pub use self::wasm::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! This module contains a canonical definition of the `cabi_realloc` function
2+
//! for the component model.
3+
//!
4+
//! The component model's canonical ABI for representing datatypes in memory
5+
//! makes use of this function when transferring lists and strings, for example.
6+
//! This function behaves like C's `realloc` but also takes alignment into
7+
//! account.
8+
//!
9+
//! Components are notably not required to export this function, but nearly
10+
//! all components end up doing so currently. This definition in the standard
11+
//! library removes the need for all compilations to define this themselves.
12+
//!
13+
//! More information about the canonical ABI can be found at
14+
//! <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md>
15+
//!
16+
//! Note that the name of this function is not standardized in the canonical ABI
17+
//! at this time. Instead it's a convention of the "componentization process"
18+
//! where a core wasm module is converted to a component to use this name.
19+
//! Additionally this is not the only possible definition of this function, so
20+
//! this is defined as a "weak" symbol. This means that other definitions are
21+
//! allowed to overwrite it if they are present in a compilation.
22+
23+
use crate::alloc::{self, Layout};
24+
use crate::ptr;
25+
26+
#[used]
27+
static FORCE_CODEGEN_OF_CABI_REALLOC: unsafe extern "C" fn(
28+
*mut u8,
29+
usize,
30+
usize,
31+
usize,
32+
) -> *mut u8 = cabi_realloc;
33+
34+
#[linkage = "weak"]
35+
#[no_mangle]
36+
pub unsafe extern "C" fn cabi_realloc(
37+
old_ptr: *mut u8,
38+
old_len: usize,
39+
align: usize,
40+
new_len: usize,
41+
) -> *mut u8 {
42+
let layout;
43+
let ptr = if old_len == 0 {
44+
if new_len == 0 {
45+
return ptr::without_provenance_mut(align);
46+
}
47+
layout = Layout::from_size_align_unchecked(new_len, align);
48+
alloc::alloc(layout)
49+
} else {
50+
debug_assert_ne!(new_len, 0, "non-zero old_len requires non-zero new_len!");
51+
layout = Layout::from_size_align_unchecked(old_len, align);
52+
alloc::realloc(old_ptr, layout, new_len)
53+
};
54+
if ptr.is_null() {
55+
// Print a nice message in debug mode, but in release mode don't
56+
// pull in so many dependencies related to printing so just emit an
57+
// `unreachable` instruction.
58+
if cfg!(debug_assertions) {
59+
alloc::handle_alloc_error(layout);
60+
} else {
61+
super::abort_internal();
62+
}
63+
}
64+
return ptr;
65+
}

library/std/src/sys/pal/wasip2/mod.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
pub mod alloc;
1111
#[path = "../wasi/args.rs"]
1212
pub mod args;
13-
#[path = "../unix/cmath.rs"]
14-
pub mod cmath;
1513
#[path = "../wasi/env.rs"]
1614
pub mod env;
1715
#[path = "../wasi/fd.rs"]
@@ -28,10 +26,6 @@ pub mod io;
2826
pub mod net;
2927
#[path = "../wasi/os.rs"]
3028
pub mod os;
31-
#[path = "../unix/os_str.rs"]
32-
pub mod os_str;
33-
#[path = "../unix/path.rs"]
34-
pub mod path;
3529
#[path = "../unsupported/pipe.rs"]
3630
pub mod pipe;
3731
#[path = "../unsupported/process.rs"]
@@ -72,3 +66,5 @@ pub use helpers::decode_error_kind;
7266
use helpers::err2io;
7367
pub use helpers::hashmap_random_keys;
7468
pub use helpers::is_interrupted;
69+
70+
mod cabi_realloc;

0 commit comments

Comments
 (0)