| Age | Commit message (Collapse) | Author | Lines |
|
https://github.com/rust-lang/rust/issues/60532
r? @alexreg
|
|
|
|
rename InterpretCx -> InterpCx
That's more consistent with InterpResult and InterpError.
r? @oli-obk
|
|
|
|
This is a builidng block toward removing a lot of duplicated code
between miri and the cosnt-propagator pass.
See this thread for more info:
https://rust-lang.zulipchat.com/#narrow/stream/189540-t-compiler.2Fwg-mir-opt/topic/Using.20.60InterpCx.60.20more/near/169030661
|
|
Don't store locals that have been moved from in generators
This avoids reserving storage in generators for locals that are moved
out of (and not re-initialized) prior to yield points. Fixes #59123.
This adds a new dataflow analysis, `RequiresStorage`, to determine whether the storage of a local can be destroyed without being observed by the program. The rules are:
1. StorageLive(x) => mark x live
2. StorageDead(x) => mark x dead
3. If a local is moved from, _and has never had its address taken_, mark it dead
4. If (any part of) a local is initialized, mark it live'
This is used to determine whether to save a local in the generator object at all, as well as which locals can be overlapped in the generator layout.
Here's the size in bytes of all testcases included in the change, before and after the change:
async fn test |Size before |Size after
-----------------|------------|----------
single | 1028 | 1028
single_with_noop | 2056 | 1032
joined | 5132 | 3084
joined_with_noop | 8208 | 3084
generator test |Size before |Size after
----------------------------|------------|----------
move_before_yield | 1028 | 1028
move_before_yield_with_noop | 2056 | 1032
overlap_move_points | 3080 | 2056
## Future work
Note that there is a possible extension to this optimization, which modifies rule 3 to read: "If a local is moved from, _**and either has never had its address taken, or is Freeze and has never been mutably borrowed**_, mark it dead." This was discussed at length in #59123 and then #61849. Because this would cause some behavior to be UB which was not UB before, it's a step that needs to be taken carefully.
A more immediate priority for me is inlining `std::mem::size_of_val(&x)` so it becomes apparent that the address of `x` is not taken. This way, using `size_of_val` to look at the size of your inner futures does not affect the size of your outer future.
cc @cramertj @eddyb @Matthias247 @nikomatsakis @RalfJung @Zoxc
|
|
|
|
|
|
|
|
|
|
That's more consistent with InterpResult and InterpError.
|
|
This avoids reserving storage in generators for locals that are moved
out of (and not re-initialized) prior to yield points.
|
|
|
|
Implement From<Local> for Place and PlaceBase
r? @oli-obk
More tiny bits of Place 2.0 moved into master
|
|
HirIdification: almost there
I'm beginning to run out of stuff to HirIdify :wink:.
This time I targeted mainly `hir::map::{find, get_parent_node}`, but a few other bits got changed too.
r? @Zoxc
|
|
|
|
rustc_mir: Hide initial block state when defining transfer functions
This PR addresses [this FIXME](https://github.com/rust-lang/rust/blob/2887008e0ce0824be4e0e9562c22ea397b165c97/src/librustc_mir/dataflow/mod.rs#L594-L596).
This makes `sets.on_entry` inaccessible in `{before_,}{statement,terminator}_effect`. This field was meant to allow implementors of `BitDenotation` to access the initial state for each block (optionally with the effect of all previous statements applied via `accumulates_intrablock_state`) while defining transfer functions. However, the ability to set the initial value for the entry set of each basic block (except for START_BLOCK) no longer exists. As a result, this functionality is mostly useless, and when it *was* used it was used erroneously (see #62007).
Since `on_entry` is now useless, we can also remove `BlockSets`, which held the `gen`, `kill`, and `on_entry` bitvectors and replace it with a `GenKill` struct. Variables of this type are called `trans` since they represent a transfer function. `GenKill`s are stored contiguously in `AllSets`, which reduces the number of bounds checks and may improve cache performance: one is almost never accessed without the other.
Replacing `BlockSets` with `GenKill` allows us to define some new helper functions which streamline dataflow iteration and the dataflow-at-location APIs. Notably, `state_for_location` used a subtle side-effect of the `kill`/`kill_all` setters to apply the transfer function, and could be incorrect if a transfer function depended on effects of previous statements in the block on `gen_set`.
Additionally, this PR merges `BitSetOperator` and `InitialFlow` into one trait. Since the value of `InitialFlow` defines the semantics of the `join` operation, there's no reason to have seperate traits for each. We can add a default impl of `join` which branches based on `BOTTOM_VALUE`. This should get optimized away.
|
|
|
|
Use ecx for const-prop local storage
r? @oli-obk
|
|
|
|
This commit makes `sets.on_entry` inaccessible in
`{before_,}{statement,terminator}_effect`. This field was meant to allow
implementors of `BitDenotation` to access the initial state for each
block (optionally with the effect of all previous statements applied via
`accumulates_intrablock_state`) while defining transfer functions.
However, the ability to set the initial value for the entry set of each
basic block (except for START_BLOCK) no longer exists. As a result, this
functionality is mostly useless, and when it *was* used it was used
erroneously (see #62007).
Since `on_entry` is now useless, we can also remove `BlockSets`, which
held the `gen`, `kill`, and `on_entry` bitvectors and replace it with a
`GenKill` struct. Variables of this type are called `trans` since they
represent a transfer function. `GenKill`s are stored contiguously in
`AllSets`, which reduces the number of bounds checks and may improve
cache performance: one is almost never accessed without the other.
Replacing `BlockSets` with `GenKill` allows us to define some new helper
functions which streamline dataflow iteration and the
dataflow-at-location APIs. Notably, `state_for_location` used a subtle
side-effect of the `kill`/`kill_all` setters to apply the transfer
function, and could be incorrect if a transfer function depended on
effects of previous statements in the block on `gen_set`.
|
|
|
|
This moves us closer to just using `InterpCx` for interpretation.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Optimize matches
Attempt to fix or improve #60571
This is breaking some diagnostics because the MIR for match arms isn't in source order any more.
cc @centril
|
|
|
|
|
|
|
|
We never have more than one imaginary target, so we have no reason for
a `Vec`
|
|
|
|
|
|
|
|
|
|
|
|
Generator optimization: Overlap locals that never have storage live at the same time
The specific goal of this optimization is to optimize async fns which use `await!`. Notably, `await!` has an enclosing scope around the futures it awaits ([definition](https://github.com/rust-lang/rust/blob/08bfe16129b0621bc90184f8704523d4929695ef/src/libstd/macros.rs#L365-L381)), which we rely on to implement the optimization.
More generally, the optimization allows overlapping the storage of some locals which are never storage-live at the same time. **We care about storage-liveness when computing the layout, because knowing a field is `StorageDead` is the only way to prove it will not be accessed, either directly or through a reference.**
To determine whether we can overlap two locals in the generator layout, we look at whether they might *both* be `StorageLive` at any point in the MIR. We use the `MaybeStorageLive` dataflow analysis for this. We iterate over every location in the MIR, and build a bitset for each local of the locals it might potentially conflict with.
Next, we assign every saved local to one or more variants. The variants correspond to suspension points, and we include the set of locals live across a given suspension point in the variant. (Note that we use liveness instead of storage-liveness here; this ensures that the local has actually been initialized in each variant it has been included in. If the local is not live across a suspension point, then it doesn't need to be included in that variant.). It's important to note that the variants are a "view" into our layout.
For the layout computation, we use a simplified approach.
1. Start with the set of locals assigned to only one variant. The rest are disqualified.
2. For each pair of locals which may conflict *and are not assigned to the same variant*, we pick one local to disqualify from overlapping.
Disqualified locals go into a non-overlapping "prefix" at the beginning of our layout. This means they always have space reserved for them. All the locals that are allowed to overlap in each variant are then laid out after this prefix, in the "overlap zone".
So, if A and B were disqualified, and X, Y, and Z were all eligible for overlap, our generator might look something like this:
You can think of a generator as an enum, where some fields are shared between variants. e.g.
```rust
enum Generator {
Unresumed,
Poisoned,
Returned,
Suspend0(A, B, X),
Suspend1(B),
Suspend2(A, Y, Z),
}
```
where every mention of `A` and `B` refer to the same field, which does not move when changing variants. Note that `A` and `B` would automatically be sent to the prefix in this example. Assuming that `X` is never `StorageLive` at the same time as either `Y` or `Z`, it would be allowed to overlap with them.
Note that if two locals (`Y` and `Z` in this case) are assigned to the same variant in our generator, their memory would never overlap in the layout. Thus they can both be eligible for the overlapping section, even if they are storage-live at the same time.
---
Depends on:
- [x] #59897 Multi-variant layouts for generators
- [x] #60840 Preserve local scopes in generator MIR
- [x] #61373 Emit StorageDead along unwind paths for generators
Before merging:
- [x] ~Wrap the types of all generator fields in `MaybeUninitialized` in layout::ty::field~ (opened #60889)
- [x] Make PR description more complete (e.g. explain why storage liveness is important and why we have to check every location)
- [x] Clean up TODO
- [x] Fix the layout code to enforce that the same field never moves around in the generator
- [x] Add tests for async/await
- [x] ~Reduce # bits we store by half, since the conflict relation is symmetric~ (note: decided not to do this, for simplicity)
- [x] Store liveness information for each yield point in our `GeneratorLayout`, that way we can emit more useful debuginfo AND tell miri which fields are definitely initialized for a given variant (see discussion at https://github.com/rust-lang/rust/pull/59897#issuecomment-489468627)
|
|
Rollup of 11 pull requests
Successful merges:
- #61518 (Add loops to doc list of things not stable in const fn)
- #61526 (move some tests into subfolders)
- #61550 (Windows 10 SDK is also required now.)
- #61606 (Remove some legacy proc macro flavors)
- #61652 (Mention slice patterns in array)
- #61686 (librustc_errors: Add some more documentation)
- #61698 (typeck: Fix const generic in repeat param ICE.)
- #61707 (Azure: retry failed awscli installs)
- #61715 (make sure make_ascii_lowercase actually leaves upper-case non-ASCII characters alone)
- #61724 (core: use memcmp optimization for 128 bit integer slices)
- #61726 (Use `for_each` in `Iterator::partition`)
Failed merges:
r? @ghost
|
|
|
|
Add loops to doc list of things not stable in const fn
Closes #61508
|
|
|
|
|
|
|
|
|
|
|
|
|