# `Deref` is not a projection
### Motivation
The MIR uses `Place` to refer to memory locations. A `Place` consists of a base, either a static or a local, and a series of projections starting from that base. With one exception, these projections are merely refinements of the base: they specify a field, an array element, or the portion of memory corresponding to a given enum variant. However, the `Deref` projection is unique in that the target of the `Place` refers to a completely different region of memory than the base. As a result, several analyses must iterate over each place to see whether that place contains a `Deref`.
## The problem
```rust
let mut a = (17,13);
let mut b = (99, &mut a);
let x = &mut (*b.1).0;
// Given code above, emitted MIR would be this.
let mut _0: ();
let mut _1: (i32, i32);
let mut _3: &mut (i32, i32);
// ...
_4 = &mut ((*(_2.1: &mut (i32, i32))).0: i32);
_5 = &mut ((*(_2.1: &mut (i32, i32))).1: i32);
```
This is the result for a basic case, more complex this gets longer the `Deref` chain becomes, which results more complexity to be solved by MIR analyses.
### Solution
Convert
```rust
let x = (*a.b).c
```
into
```rust
let tmp = a.b;
let x = (*tmp).c
```
### Plan
- Create new mir-opt(derefer) that does the above mentioned convertion
- Pull `derefer`, earlier in the optimization phases
- Create new `Rvalue` for `derefer`
- *everything above has already been done*
- Pull `derefer` before borrowck
- Make it so MIR `Body` is built in a way that the transformation becomes unnecessary
- Remove `ProjectionElem::Deref` entirely.
- add a `deref: bool` field to `Place`, which indicates that the place must be dereferenced before its projections are applied.
### Changes so far
Highlevel overview of work done so far from oldest PR to the newest
- 95649 : Added new mir-opt `deref_separator` that converts `let x = (*a.b).c` into => `tmp = a.b let x= (*tmp).c`
- 95857 : Make `deref_separator` work for nested derefs
- 96116 : By using `visit_place` started working directly on `Places` instead of statements, made major changes to make it work with `Terminator`s.
- 96549 : Pulled `deref_separator` before `add_retag`, to make this work added new field to `LocalInfo`, `DerefTemp`
- 97025 : Added new `MirPhase` `Derefered` Added validation layer for `Derefered`, that validates to see if there is a `ProjectionElem::Deref` after `deref_separator` runs.
- 98145 : Pulled `deref_separator` before `elaborate_drops`, to make it work created new `Rvalue::CopyForDeref` that acts as a differentiator to detect `temp` values created by `deref_separator`.
# Appendix
This is the explanation for main body of `deref_separator`
```rust=
fn visit_place(&mut self, place: &mut Place<'tcx>,
cntxt: PlaceContext, loc: Location){
if !place.projection.is_empty()
&& cntxt != PlaceContext::NonUse(VarDebugInfo)
&& place.projection[1..].contains(&ProjectionElem::Deref)
```
3. Projections shouldn't be empty...
4. Currently, `Derefer` doesn't work for `VarDebugInfo` so we skip them
5. Since the problem only starts when `Derefer` is not at the start of the projection list, we skip non problematic ones.
---------------
```rust=
let mut place_local = place.local;
let mut last_len = 0;
let mut last_deref_idx = 0;
let mut prev_temp: Option<Local> = None;
for (idx, elem) in place.projection[0..].iter().enumerate() {
if *elem == ProjectionElem::Deref {
last_deref_idx = idx;
}
}
```
1. We need to have a starting local.
2. We start copying from the beginning so this will start at 0 (this is going to make more sense later)
3. Position of the last `Deref` in the slice of projections.
4. We have to destroy the last temp we created, this is `None` for now
------------
```rust=
for (idx, (p_ref, p_elem)) in place.iter_projections().enumerate() {
if !p_ref.projection.is_empty() && p_elem == ProjectionElem::Deref {
let ty = p_ref.ty(&self.local_decls, self.tcx).ty;
let temp = self.patcher.new_local_with_info(
ty,
self.local_decls[p_ref.local].source_info.span,
Some(Box::new(LocalInfo::DerefTemp)),
```
1. `idx` will be used to compare with `last_deref_idx`
2. We only care for `Deref`
4. `temp` is created with `MirPatch`, it will be used for derefing
7. We mark this as `DerefTemp`, so we can retrieve this information when needed (it will be needed in other mir-opts)
-----
```rust=
self.patcher.add_statement(loc, StatementKind::StrorageLive(temp));
let deref_place = Place::from(place_local)
.project_deeper(&p_ref.projection[last_len..], self.tcx);
```
1. We mark the temp we just created as living
2. These last 2 lines are quite important, I will be explaining it with example code below.
```rust=
fn main () {
let mut a = (42, 43);
let mut b = (99, &mut a);
let mut c = (11, &mut b);
let mut d = (13, &mut c);
let x = &mut (*d.1).1.1.1;
}
```
Given this code `deref_place` and `last_len` will have these values.
```rust
projections[Field(field[1], &mut (i32, &mut (i32, &mut (i32, i32)))]
last_len = 0
// in the next loop
projections[Deref,Field(field[1], &mut (i32, &mut (i32, i32)))]
last_len = 1
// in the last loop
projections[Deref, Field(field[1], &mut (i32, i32))]
last_len = 3
```
As you can see, we cut away `&` with each loop creating new temp that holds next referenced value.
---
```rust=
self.patcher.add_assign(loc, Place::From(temp),
Rvalue::CopyForDeref(deref_place),);
place_local = temp;
last_len = p_ref.projection.len();
```
2. We assign newly created `deref_place` to our `temp` using, `Rvalue::CopyForDeref` which is basically `Copy` but let's us differentiate it from normal `Copy`, which is used to un-do this whole proccess to bypass `elaborate_drops`.
3. In the next loop `deref_place` will be created from this temps local value.
4. We update last_len as explained above.
----
```rust=
if idx == last_deref_idx {
let temp_place = Place::from(temp)
.project_deeper(&place.projection[idx..], self.tcx);
*place = temp_place;
}
```
1. Remember `last_deref_idx` ? this is where we use it.
2. For the example given above temp place's projections are ` Deref,
Field(
field[1],
i32,
),` as you can see final projection is clean, meaning we successfully transformed it from this
`_8 = &mut ((*((*((*(_6.1: &mut (i32, &mut (i32, &mut (i32, i32))))).1: &mut (i32, &mut (i32, i32)))).1: &mut (i32, i32))).1: i32);`
to this `_8 = &mut ((*_12).1: i32); `
3. We swap the original place, with our final value.
---
```rust=
if let Some(prev_temp) = prev_temp {
self.patcher.add_statement(loc, StatementKind::StorageDead(prev_temp));
}
prev_temp = Some(temp)
```
1. Since we have to eliminate previously created temps we do it here
4. Since we are at the end of our loop we assign, temp as `prev_temp` to be destroyed in the next loop.