|
29 | 29 | //!
|
30 | 30 | //! Each place that starts with access into the coroutine structure `_1` is replaced with the fresh local as
|
31 | 31 | //! the base.
|
32 |
| -//! For instance, `(_1.4 as Some).0` is rewritten into `(_34 as Some).0` when `_34` is the fresh local |
33 |
| -//! corresponding to the captured upvar stored in `_1.4`. |
| 32 | +//! Suppose we are to lower this coroutine into the MIR. |
| 33 | +//! ```ignore (illustrative) |
| 34 | +//! let mut captured = None; |
| 35 | +//! let _ = #[coroutine] || { |
| 36 | +//! yield (); |
| 37 | +//! if let Some(inner) = &mut captured { |
| 38 | +//! *inner = 42i32; // (*) |
| 39 | +//! } |
| 40 | +//! }; |
| 41 | +//! ``` |
| 42 | +//! `captured` is the only capture, whose mutable borrow is formally alotted to the first field `_1.0: &mut i32`. |
| 43 | +//! The highlighted line `(*)` should be lowered, roughly, into MIR `(*_1.0) = const 42i32;`. |
| 44 | +//! Now, by application of this pass, we create a new local `_4: &mut i32` and we perform the following |
| 45 | +//! code transformation. |
| 46 | +//! |
| 47 | +//! A new block is constructed to just perform the relocation of this mutable borrow. |
| 48 | +//! This block is inserted to the very beginning of the coroutine body control flow, |
| 49 | +//! so that this is executed before any proper coroutine code as it transits from `UNRESUME` state to |
| 50 | +//! any other state. |
| 51 | +//! This "prologue" will look like the following. |
| 52 | +//! ```ignore (illustrative) |
| 53 | +//! StorageLive(_5); |
| 54 | +//! StorageLive(_4); |
| 55 | +//! _5 = move (_1.0); |
| 56 | +//! _4 = move (_5); |
| 57 | +//! StorageDead(_5); |
| 58 | +//! ``` |
| 59 | +//! Note that we also create a trampoline local `_5` of the same type. |
| 60 | +//! |
| 61 | +//! ### Intricacy around the trampoline local |
| 62 | +//! The reason that we need the trampolines is because state transformation and coroutine |
| 63 | +//! layout calculation is not aware of potential storage conflict between captures as struct fields |
| 64 | +//! and other saved locals. |
| 65 | +//! The only guarantee that we can work with is one where any coroutine layout calculator respects |
| 66 | +//! the storage conflict constracts between *MIR locals*. |
| 67 | +//! It is known that calculators do not consider struct fields, where captures reside, as MIR locals. |
| 68 | +//! This is the source of potential memory overlaps. |
| 69 | +//! For instance, in a hypothetical situation, |
| 70 | +//! - `_1.0` is relocated to `_4`; |
| 71 | +//! - `_1.1` is relocated to `_6`; |
| 72 | +//! - `_4` and `_6` remains live at one of the first suspension state; |
| 73 | +//! - `_4` occupies the same offset of `_1.1` and `_6` occupies the same offset of `_1.0` |
| 74 | +//! as decided by some layout calculator; |
| 75 | +//! In this scenario, without trampolining, the relocations introduce undefined behaviour. |
| 76 | +//! |
| 77 | +//! As a proposal for a future design, it is best that coroutine captures receive their own |
| 78 | +//! MIR locals, possibly in a form of "function arguments" like `_1` itself. |
| 79 | +//! The trampolining transformation already attempts to restore the semantics of MIR locals to |
| 80 | +//! these captures and promoting them to "arguments" would make MIR safer to handle. |
34 | 81 | //!
|
35 |
| -//! This phase assumes that the initial built MIR respects the nature of captures. |
| 82 | +//! One should note that this phase assumes that the initial built MIR respects the nature of captures. |
36 | 83 | //! For instance, if the upvar `_1.4` is instead a by-ref-mut capture of a value of type `T`,
|
37 | 84 | //! this phase assumes that all access correctly built as operating on the place `(*_1.4)`.
|
38 | 85 | //! Based on the assumption, this phase replaces `_1.4` with a fresh local `_34: &mut T` and
|
|
0 commit comments