]> git.lizzy.rs Git - rust.git/commitdiff
2229: Reduce the size of closures with `capture_disjoint_fields`
authorAman Arora <me@aman-arora.com>
Mon, 28 Jun 2021 01:19:39 +0000 (21:19 -0400)
committerAman Arora <me@aman-arora.com>
Tue, 29 Jun 2021 07:16:43 +0000 (03:16 -0400)
One key observation while going over the closure size profile of rustc
was that we are disjointly capturing one or more fields starting at an
immutable reference.

Disjoint capture over immutable reference doesn't add too much value
because the fields can either be borrowed immutably or copied.

One possible edge case of the optimization is when a fields of a struct
have a longer lifetime than the structure, therefore we can't completely
get rid of all the accesses on top of sharef refs, only the rightmost
one. Here is a possible example:

```rust
struct MyStruct<'a> {
   a: &'static A,
   b: B,
   c: C<'a>,
}

fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static {
    let c = || drop(&*m.a.field_of_a);
    // Here we really do want to capture `*m.a` because that outlives `'static`

    // If we capture `m`, then the closure no longer outlives `'static'
    // it is constrained to `'a`
}
```

compiler/rustc_typeck/src/check/upvar.rs
src/test/ui/closures/2229_closure_analysis/diagnostics/cant-mutate-imm-borrow.rs
src/test/ui/closures/2229_closure_analysis/diagnostics/cant-mutate-imm-borrow.stderr
src/test/ui/closures/2229_closure_analysis/move_closure.rs
src/test/ui/closures/2229_closure_analysis/move_closure.stderr
src/test/ui/closures/2229_closure_analysis/optimization/edge_case.rs [new file with mode: 0644]
src/test/ui/closures/2229_closure_analysis/optimization/edge_case.stderr [new file with mode: 0644]
src/test/ui/closures/2229_closure_analysis/optimization/edge_case_run_pass.rs [new file with mode: 0644]
src/test/ui/closures/2229_closure_analysis/optimization/edge_case_run_pass.stderr [new file with mode: 0644]

index 7b5b14ae6c831caac17d0e61a1295264462bdde6..8418626b8e6e0b2f33fe73a24ccb3bfca670a1c2 100644 (file)
@@ -1609,11 +1609,17 @@ fn consume(
             "consume(place_with_id={:?}, diag_expr_id={:?}, mode={:?})",
             place_with_id, diag_expr_id, mode
         );
+
+        let place_with_id = PlaceWithHirId {
+            place: truncate_capture_for_optimization(&place_with_id.place),
+            ..*place_with_id
+        };
+
         if !self.capture_information.contains_key(&place_with_id.place) {
-            self.init_capture_info_for_place(place_with_id, diag_expr_id);
+            self.init_capture_info_for_place(&place_with_id, diag_expr_id);
         }
 
-        self.adjust_upvar_borrow_kind_for_consume(place_with_id, diag_expr_id, mode);
+        self.adjust_upvar_borrow_kind_for_consume(&place_with_id, diag_expr_id, mode);
     }
 
     fn borrow(
@@ -1636,6 +1642,8 @@ fn borrow(
             &place_with_id.place,
         );
 
+        let place = truncate_capture_for_optimization(&place);
+
         let place_with_id = PlaceWithHirId { place, ..*place_with_id };
 
         if !self.capture_information.contains_key(&place_with_id.place) {
@@ -1967,6 +1975,44 @@ fn determine_place_ancestry_relation(
     }
 }
 
+/// Reduces the precision of the captured place when the precision doesn't yeild any benefit from
+/// borrow checking prespective, allowing us to save us on the size of the capture.
+///
+///
+/// Fields that are read through a shared reference will always be read via a shared ref or a copy,
+/// and therefore capturing precise paths yields no benefit. This optimization truncates the
+/// rightmost deref of the capture if the deref is applied to a shared ref.
+///
+/// Reason we only drop the last deref is because of the following edge case:
+///
+/// ```rust
+/// struct MyStruct<'a> {
+///    a: &'static A,
+///    b: B,
+///    c: C<'a>,
+/// }
+///
+/// fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static {
+///     let c = || drop(&*m.a.field_of_a);
+///     // Here we really do want to capture `*m.a` because that outlives `'static`
+///
+///     // If we capture `m`, then the closure no longer outlives `'static'
+///     // it is constrained to `'a`
+/// }
+/// ```
+fn truncate_capture_for_optimization<'tcx>(place: &Place<'tcx>) -> Place<'tcx> {
+    let is_shared_ref = |ty: Ty<'_>| matches!(ty.kind(), ty::Ref(.., hir::Mutability::Not));
+
+    let idx = place.projections.iter().rposition(|proj| ProjectionKind::Deref == proj.kind);
+
+    match idx {
+        Some(idx) if is_shared_ref(place.ty_before_projection(idx)) => {
+            Place { projections: place.projections[0..=idx].to_vec(), ..place.clone() }
+        }
+        None | Some(_) => place.clone(),
+    }
+}
+
 /// Precise capture is enabled if the feature gate `capture_disjoint_fields` is enabled or if
 /// user is using Rust Edition 2021 or higher.
 ///
index a5b4a19d8c3ff1d2972a9a5332244548531ac44a..77effcb006588f263df2c996d3763c3c2bb73ab5 100644 (file)
@@ -8,10 +8,10 @@ fn main() {
     let mut y = (&x, "Y");
     let z = (&mut y, "Z");
 
-    // `x.0` is mutable but we access `x` via `z.0.0`, which is an immutable reference and
+    // `x.0` is mutable but we access `x` via `*z.0.0`, which is an immutable reference and
     // therefore can't be mutated.
     let mut c = || {
-    //~^ ERROR: cannot borrow `z.0.0.0` as mutable, as it is behind a `&` reference
+    //~^ ERROR: cannot borrow `*z.0.0` as mutable, as it is behind a `&` reference
         z.0.0.0 = format!("X1");
     };
 
index cfe531e17d3d74bafe107b223d58f0a20c108da8..38c530b809a624680626c6b52c608c2113777974 100644 (file)
@@ -1,11 +1,11 @@
-error[E0596]: cannot borrow `z.0.0.0` as mutable, as it is behind a `&` reference
+error[E0596]: cannot borrow `*z.0.0` as mutable, as it is behind a `&` reference
   --> $DIR/cant-mutate-imm-borrow.rs:13:17
    |
 LL |     let mut c = || {
    |                 ^^ cannot borrow as mutable
 LL |
 LL |         z.0.0.0 = format!("X1");
-   |         ------- mutable borrow occurs due to use of `z.0.0.0` in closure
+   |         ------- mutable borrow occurs due to use of `*z.0.0` in closure
 
 error: aborting due to previous error
 
index 06db19974eb044b3fc12c88b84206269e3b0a890..76874e03dc02a128128636cff2e7d9ce89d8e6d9 100644 (file)
@@ -78,8 +78,8 @@ fn struct_contains_ref_to_another_struct_2() {
     //~^ ERROR: First Pass analysis includes:
     //~| ERROR: Min Capture analysis includes:
         let _t = t.0.0;
-        //~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
-        //~| NOTE: Min Capture t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+        //~^ NOTE: Capturing t[(0, 0),Deref] -> ImmBorrow
+        //~| NOTE: Min Capture t[(0, 0),Deref] -> ImmBorrow
     };
 
     c();
@@ -100,7 +100,7 @@ fn struct_contains_ref_to_another_struct_3() {
     //~^ ERROR: First Pass analysis includes:
     //~| ERROR: Min Capture analysis includes:
         let _t = t.0.0;
-        //~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+        //~^ NOTE: Capturing t[(0, 0),Deref] -> ImmBorrow
         //~| NOTE: Capturing t[(0, 0)] -> ByValue
         //~| NOTE: Min Capture t[(0, 0)] -> ByValue
     };
index 013cacfb9f2a565eea499842c027c5e8a0f289dd..b35aadfcbd41948b200812453362ea0c7012a824 100644 (file)
@@ -190,7 +190,7 @@ LL | |
 LL | |     };
    | |_____^
    |
-note: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+note: Capturing t[(0, 0),Deref] -> ImmBorrow
   --> $DIR/move_closure.rs:80:18
    |
 LL |         let _t = t.0.0;
@@ -208,7 +208,7 @@ LL | |
 LL | |     };
    | |_____^
    |
-note: Min Capture t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+note: Min Capture t[(0, 0),Deref] -> ImmBorrow
   --> $DIR/move_closure.rs:80:18
    |
 LL |         let _t = t.0.0;
@@ -226,7 +226,7 @@ LL | |
 LL | |     };
    | |_____^
    |
-note: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+note: Capturing t[(0, 0),Deref] -> ImmBorrow
   --> $DIR/move_closure.rs:102:18
    |
 LL |         let _t = t.0.0;
diff --git a/src/test/ui/closures/2229_closure_analysis/optimization/edge_case.rs b/src/test/ui/closures/2229_closure_analysis/optimization/edge_case.rs
new file mode 100644 (file)
index 0000000..960c61c
--- /dev/null
@@ -0,0 +1,37 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+
+#![feature(rustc_attrs)]
+#![allow(unused)]
+#![allow(dead_code)]
+
+struct Int(i32);
+struct B<'a>(&'a i32);
+
+const I : Int = Int(0);
+const REF_I : &'static Int = &I;
+
+
+struct MyStruct<'a> {
+   a: &'static Int,
+   b: B<'a>,
+}
+
+fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static {
+    let c = #[rustc_capture_analysis] || drop(&m.a.0);
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    //~| ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+    //~| NOTE: Capturing m[Deref,(0, 0),Deref] -> ImmBorrow
+    //~| NOTE: Min Capture m[Deref,(0, 0),Deref] -> ImmBorrow
+    c
+}
+
+fn main() {
+    let t = 0;
+    let s = MyStruct { a: REF_I, b: B(&t) };
+    let _ = foo(&s);
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/optimization/edge_case.stderr b/src/test/ui/closures/2229_closure_analysis/optimization/edge_case.stderr
new file mode 100644 (file)
index 0000000..c9f9386
--- /dev/null
@@ -0,0 +1,45 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/edge_case.rs:23:13
+   |
+LL |     let c = #[rustc_capture_analysis] || drop(&m.a.0);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/edge_case.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error: First Pass analysis includes:
+  --> $DIR/edge_case.rs:23:39
+   |
+LL |     let c = #[rustc_capture_analysis] || drop(&m.a.0);
+   |                                       ^^^^^^^^^^^^^^^
+   |
+note: Capturing m[Deref,(0, 0),Deref] -> ImmBorrow
+  --> $DIR/edge_case.rs:23:48
+   |
+LL |     let c = #[rustc_capture_analysis] || drop(&m.a.0);
+   |                                                ^^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/edge_case.rs:23:39
+   |
+LL |     let c = #[rustc_capture_analysis] || drop(&m.a.0);
+   |                                       ^^^^^^^^^^^^^^^
+   |
+note: Min Capture m[Deref,(0, 0),Deref] -> ImmBorrow
+  --> $DIR/edge_case.rs:23:48
+   |
+LL |     let c = #[rustc_capture_analysis] || drop(&m.a.0);
+   |                                                ^^^^^
+
+error: aborting due to 3 previous errors; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/optimization/edge_case_run_pass.rs b/src/test/ui/closures/2229_closure_analysis/optimization/edge_case_run_pass.rs
new file mode 100644 (file)
index 0000000..2c4ba0c
--- /dev/null
@@ -0,0 +1,31 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+
+// run-pass
+
+#![allow(unused)]
+#![allow(dead_code)]
+
+struct Int(i32);
+struct B<'a>(&'a i32);
+
+const I : Int = Int(0);
+const REF_I : &'static Int = &I;
+
+struct MyStruct<'a> {
+   a: &'static Int,
+   b: B<'a>,
+}
+
+fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static {
+    let c = || drop(&m.a.0);
+    c
+}
+
+fn main() {
+    let t = 0;
+    let s = MyStruct { a: REF_I, b: B(&t) };
+    let _ = foo(&s);
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/optimization/edge_case_run_pass.stderr b/src/test/ui/closures/2229_closure_analysis/optimization/edge_case_run_pass.stderr
new file mode 100644 (file)
index 0000000..36906d1
--- /dev/null
@@ -0,0 +1,11 @@
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/edge_case_run_pass.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+warning: 1 warning emitted
+