about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_mir_build/src/build/expr/stmt.rs40
-rw-r--r--compiler/rustc_mir_build/src/build/scope.rs85
-rw-r--r--tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff108
-rw-r--r--tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff109
-rw-r--r--tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir118
-rw-r--r--tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir118
-rw-r--r--tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff184
-rw-r--r--tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff184
-rw-r--r--tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir202
-rw-r--r--tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir202
-rw-r--r--tests/mir-opt/tail_call_drops.rs41
-rw-r--r--tests/ui/explicit-tail-calls/drop-order.rs70
12 files changed, 1443 insertions, 18 deletions
diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs
index 60ab843257d..7d2c32e000b 100644
--- a/compiler/rustc_mir_build/src/build/expr/stmt.rs
+++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs
@@ -95,7 +95,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             }
             ExprKind::Become { value } => {
                 let v = &this.thir[value];
-                let ExprKind::Scope { value, .. } = v.kind else {
+                let ExprKind::Scope { value, lint_level, region_scope } = v.kind else {
                     span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
                 };
 
@@ -104,27 +104,31 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
                 };
 
-                let fun = unpack!(block = this.as_local_operand(block, fun));
-                let args: Vec<_> = args
-                    .into_iter()
-                    .copied()
-                    .map(|arg| Spanned {
-                        node: unpack!(block = this.as_local_call_operand(block, arg)),
-                        span: this.thir.exprs[arg].span,
-                    })
-                    .collect();
+                this.in_scope((region_scope, source_info), lint_level, |this| {
+                    let fun = unpack!(block = this.as_local_operand(block, fun));
+                    let args: Vec<_> = args
+                        .into_iter()
+                        .copied()
+                        .map(|arg| Spanned {
+                            node: unpack!(block = this.as_local_call_operand(block, arg)),
+                            span: this.thir.exprs[arg].span,
+                        })
+                        .collect();
 
-                this.record_operands_moved(&args);
+                    this.record_operands_moved(&args);
 
-                debug!("expr_into_dest: fn_span={:?}", fn_span);
+                    debug!("expr_into_dest: fn_span={:?}", fn_span);
 
-                this.cfg.terminate(
-                    block,
-                    source_info,
-                    TerminatorKind::TailCall { func: fun, args, fn_span },
-                );
+                    unpack!(block = this.break_for_tail_call(block, &args, source_info));
 
-                this.cfg.start_new_block().unit()
+                    this.cfg.terminate(
+                        block,
+                        source_info,
+                        TerminatorKind::TailCall { func: fun, args, fn_span },
+                    );
+
+                    this.cfg.start_new_block().unit()
+                })
             }
             _ => {
                 assert!(
diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs
index 9e7534a283d..948301e2ece 100644
--- a/compiler/rustc_mir_build/src/build/scope.rs
+++ b/compiler/rustc_mir_build/src/build/scope.rs
@@ -745,6 +745,91 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         self.cfg.terminate(block, source_info, TerminatorKind::UnwindResume);
     }
 
+    /// Sets up the drops for explict tail calls.
+    ///
+    /// Unlike other kinds of early exits, tail calls do not go through the drop tree.
+    /// Instead, all scheduled drops are immediately added to the CFG.
+    pub(crate) fn break_for_tail_call(
+        &mut self,
+        mut block: BasicBlock,
+        args: &[Spanned<Operand<'tcx>>],
+        source_info: SourceInfo,
+    ) -> BlockAnd<()> {
+        let arg_drops: Vec<_> = args
+            .iter()
+            .rev()
+            .filter_map(|arg| match &arg.node {
+                Operand::Copy(_) => bug!("copy op in tail call args"),
+                Operand::Move(place) => {
+                    let local =
+                        place.as_local().unwrap_or_else(|| bug!("projection in tail call args"));
+
+                    Some(DropData { source_info, local, kind: DropKind::Value })
+                }
+                Operand::Constant(_) => None,
+            })
+            .collect();
+
+        let mut unwind_to = self.diverge_cleanup_target(
+            self.scopes.scopes.iter().rev().nth(1).unwrap().region_scope,
+            DUMMY_SP,
+        );
+        let unwind_drops = &mut self.scopes.unwind_drops;
+
+        // the innermost scope contains only the destructors for the tail call arguments
+        // we only want to drop these in case of a panic, so we skip it
+        for scope in self.scopes.scopes[1..].iter().rev().skip(1) {
+            // FIXME(explicit_tail_calls) code duplication with `build_scope_drops`
+            for drop_data in scope.drops.iter().rev() {
+                let source_info = drop_data.source_info;
+                let local = drop_data.local;
+
+                match drop_data.kind {
+                    DropKind::Value => {
+                        // `unwind_to` should drop the value that we're about to
+                        // schedule. If dropping this value panics, then we continue
+                        // with the *next* value on the unwind path.
+                        debug_assert_eq!(unwind_drops.drops[unwind_to].data.local, drop_data.local);
+                        debug_assert_eq!(unwind_drops.drops[unwind_to].data.kind, drop_data.kind);
+                        unwind_to = unwind_drops.drops[unwind_to].next;
+
+                        let mut unwind_entry_point = unwind_to;
+
+                        // the tail call arguments must be dropped if any of these drops panic
+                        for drop in arg_drops.iter().copied() {
+                            unwind_entry_point = unwind_drops.add_drop(drop, unwind_entry_point);
+                        }
+
+                        unwind_drops.add_entry_point(block, unwind_entry_point);
+
+                        let next = self.cfg.start_new_block();
+                        self.cfg.terminate(
+                            block,
+                            source_info,
+                            TerminatorKind::Drop {
+                                place: local.into(),
+                                target: next,
+                                unwind: UnwindAction::Continue,
+                                replace: false,
+                            },
+                        );
+                        block = next;
+                    }
+                    DropKind::Storage => {
+                        // Only temps and vars need their storage dead.
+                        assert!(local.index() > self.arg_count);
+                        self.cfg.push(
+                            block,
+                            Statement { source_info, kind: StatementKind::StorageDead(local) },
+                        );
+                    }
+                }
+            }
+        }
+
+        block.unit()
+    }
+
     fn leave_top_scope(&mut self, block: BasicBlock) -> BasicBlock {
         // If we are emitting a `drop` statement, we need to have the cached
         // diverge cleanup pads ready in case that drop panics.
diff --git a/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff
new file mode 100644
index 00000000000..44673ea00a9
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff
@@ -0,0 +1,108 @@
+- // MIR for `f` before ElaborateDrops
++ // MIR for `f` after ElaborateDrops
+  
+  fn f() -> () {
+      let mut _0: ();
+      let mut _1: !;
+      let _2: std::string::String;
+      let _6: ();
+      let mut _7: std::string::String;
++     let mut _8: bool;
+      scope 1 {
+          debug _a => _2;
+          let _3: i32;
+          scope 2 {
+              debug _b => _3;
+              let _4: std::string::String;
+              scope 3 {
+                  debug _c => _4;
+                  let _5: std::string::String;
+                  scope 4 {
+                      debug _d => _5;
+                  }
+              }
+          }
+      }
+  
+      bb0: {
++         _8 = const false;
+          StorageLive(_2);
+          _2 = String::new() -> [return: bb1, unwind: bb12];
+      }
+  
+      bb1: {
+          StorageLive(_3);
+          _3 = const 12_i32;
+          StorageLive(_4);
+          _4 = String::new() -> [return: bb2, unwind: bb11];
+      }
+  
+      bb2: {
++         _8 = const true;
+          StorageLive(_5);
+          _5 = String::new() -> [return: bb3, unwind: bb10];
+      }
+  
+      bb3: {
+          StorageLive(_6);
+          StorageLive(_7);
++         _8 = const false;
+          _7 = move _4;
+          _6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb8];
+      }
+  
+      bb4: {
+          StorageDead(_7);
+          StorageDead(_6);
+          drop(_5) -> [return: bb5, unwind: bb10];
+      }
+  
+      bb5: {
+          StorageDead(_5);
+-         drop(_4) -> [return: bb6, unwind: bb11];
++         goto -> bb6;
+      }
+  
+      bb6: {
++         _8 = const false;
+          StorageDead(_4);
+          StorageDead(_3);
+          drop(_2) -> [return: bb7, unwind: bb12];
+      }
+  
+      bb7: {
+          StorageDead(_2);
+          tailcall g();
+      }
+  
+      bb8 (cleanup): {
+-         drop(_7) -> [return: bb9, unwind terminate(cleanup)];
++         goto -> bb9;
+      }
+  
+      bb9 (cleanup): {
+          drop(_5) -> [return: bb10, unwind terminate(cleanup)];
+      }
+  
+      bb10 (cleanup): {
+-         drop(_4) -> [return: bb11, unwind terminate(cleanup)];
++         goto -> bb14;
+      }
+  
+      bb11 (cleanup): {
+          drop(_2) -> [return: bb12, unwind terminate(cleanup)];
+      }
+  
+      bb12 (cleanup): {
+          resume;
++     }
++ 
++     bb13 (cleanup): {
++         drop(_4) -> [return: bb11, unwind terminate(cleanup)];
++     }
++ 
++     bb14 (cleanup): {
++         switchInt(_8) -> [0: bb11, otherwise: bb13];
+      }
+  }
+  
diff --git a/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff
new file mode 100644
index 00000000000..a6d33a24595
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff
@@ -0,0 +1,109 @@
+- // MIR for `f` before ElaborateDrops
++ // MIR for `f` after ElaborateDrops
+  
+  fn f() -> () {
+      let mut _0: ();
+      let mut _1: !;
+      let _2: std::string::String;
+      let _6: ();
+      let mut _7: std::string::String;
++     let mut _8: bool;
+      scope 1 {
+          debug _a => _2;
+          let _3: i32;
+          scope 2 {
+              debug _b => _3;
+              let _4: std::string::String;
+              scope 3 {
+                  debug _c => _4;
+                  let _5: std::string::String;
+                  scope 4 {
+                      debug _d => _5;
+                  }
+              }
+          }
+      }
+  
+      bb0: {
++         _8 = const false;
+          StorageLive(_2);
+          _2 = String::new() -> [return: bb1, unwind continue];
+      }
+  
+      bb1: {
+          StorageLive(_3);
+          _3 = const 12_i32;
+          StorageLive(_4);
+          _4 = String::new() -> [return: bb2, unwind: bb11];
+      }
+  
+      bb2: {
++         _8 = const true;
+          StorageLive(_5);
+          _5 = String::new() -> [return: bb3, unwind: bb10];
+      }
+  
+      bb3: {
+          StorageLive(_6);
+          StorageLive(_7);
++         _8 = const false;
+          _7 = move _4;
+          _6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb8];
+      }
+  
+      bb4: {
+          StorageDead(_7);
+          StorageDead(_6);
+          drop(_5) -> [return: bb5, unwind: bb10];
+      }
+  
+      bb5: {
+          StorageDead(_5);
+-         drop(_4) -> [return: bb6, unwind: bb11];
++         goto -> bb6;
+      }
+  
+      bb6: {
++         _8 = const false;
+          StorageDead(_4);
+          StorageDead(_3);
+-         drop(_2) -> [return: bb7, unwind continue];
++         drop(_2) -> [return: bb7, unwind: bb12];
+      }
+  
+      bb7: {
+          StorageDead(_2);
+          tailcall g();
+      }
+  
+      bb8 (cleanup): {
+-         drop(_7) -> [return: bb9, unwind terminate(cleanup)];
++         goto -> bb9;
+      }
+  
+      bb9 (cleanup): {
+          drop(_5) -> [return: bb10, unwind terminate(cleanup)];
+      }
+  
+      bb10 (cleanup): {
+-         drop(_4) -> [return: bb11, unwind terminate(cleanup)];
++         goto -> bb14;
+      }
+  
+      bb11 (cleanup): {
+          drop(_2) -> [return: bb12, unwind terminate(cleanup)];
+      }
+  
+      bb12 (cleanup): {
+          resume;
++     }
++ 
++     bb13 (cleanup): {
++         drop(_4) -> [return: bb11, unwind terminate(cleanup)];
++     }
++ 
++     bb14 (cleanup): {
++         switchInt(_8) -> [0: bb11, otherwise: bb13];
+      }
+  }
+  
diff --git a/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir b/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir
new file mode 100644
index 00000000000..2c3d62491d7
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir
@@ -0,0 +1,118 @@
+// MIR for `f` after built
+
+fn f() -> () {
+    let mut _0: ();
+    let mut _1: !;
+    let _2: std::string::String;
+    let _6: ();
+    let mut _7: std::string::String;
+    scope 1 {
+        debug _a => _2;
+        let _3: i32;
+        scope 2 {
+            debug _b => _3;
+            let _4: std::string::String;
+            scope 3 {
+                debug _c => _4;
+                let _5: std::string::String;
+                scope 4 {
+                    debug _d => _5;
+                }
+            }
+        }
+    }
+
+    bb0: {
+        StorageLive(_2);
+        _2 = String::new() -> [return: bb1, unwind: bb17];
+    }
+
+    bb1: {
+        FakeRead(ForLet(None), _2);
+        StorageLive(_3);
+        _3 = const 12_i32;
+        FakeRead(ForLet(None), _3);
+        StorageLive(_4);
+        _4 = String::new() -> [return: bb2, unwind: bb16];
+    }
+
+    bb2: {
+        FakeRead(ForLet(None), _4);
+        StorageLive(_5);
+        _5 = String::new() -> [return: bb3, unwind: bb15];
+    }
+
+    bb3: {
+        FakeRead(ForLet(None), _5);
+        StorageLive(_6);
+        StorageLive(_7);
+        _7 = move _4;
+        _6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb13];
+    }
+
+    bb4: {
+        StorageDead(_7);
+        StorageDead(_6);
+        drop(_5) -> [return: bb5, unwind: bb15];
+    }
+
+    bb5: {
+        StorageDead(_5);
+        drop(_4) -> [return: bb6, unwind: bb16];
+    }
+
+    bb6: {
+        StorageDead(_4);
+        StorageDead(_3);
+        drop(_2) -> [return: bb7, unwind: bb17];
+    }
+
+    bb7: {
+        StorageDead(_2);
+        tailcall g();
+    }
+
+    bb8: {
+        drop(_5) -> [return: bb9, unwind: bb15];
+    }
+
+    bb9: {
+        StorageDead(_5);
+        drop(_4) -> [return: bb10, unwind: bb16];
+    }
+
+    bb10: {
+        StorageDead(_4);
+        StorageDead(_3);
+        drop(_2) -> [return: bb11, unwind: bb17];
+    }
+
+    bb11: {
+        StorageDead(_2);
+        unreachable;
+    }
+
+    bb12: {
+        return;
+    }
+
+    bb13 (cleanup): {
+        drop(_7) -> [return: bb14, unwind terminate(cleanup)];
+    }
+
+    bb14 (cleanup): {
+        drop(_5) -> [return: bb15, unwind terminate(cleanup)];
+    }
+
+    bb15 (cleanup): {
+        drop(_4) -> [return: bb16, unwind terminate(cleanup)];
+    }
+
+    bb16 (cleanup): {
+        drop(_2) -> [return: bb17, unwind terminate(cleanup)];
+    }
+
+    bb17 (cleanup): {
+        resume;
+    }
+}
diff --git a/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir b/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir
new file mode 100644
index 00000000000..2c3d62491d7
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir
@@ -0,0 +1,118 @@
+// MIR for `f` after built
+
+fn f() -> () {
+    let mut _0: ();
+    let mut _1: !;
+    let _2: std::string::String;
+    let _6: ();
+    let mut _7: std::string::String;
+    scope 1 {
+        debug _a => _2;
+        let _3: i32;
+        scope 2 {
+            debug _b => _3;
+            let _4: std::string::String;
+            scope 3 {
+                debug _c => _4;
+                let _5: std::string::String;
+                scope 4 {
+                    debug _d => _5;
+                }
+            }
+        }
+    }
+
+    bb0: {
+        StorageLive(_2);
+        _2 = String::new() -> [return: bb1, unwind: bb17];
+    }
+
+    bb1: {
+        FakeRead(ForLet(None), _2);
+        StorageLive(_3);
+        _3 = const 12_i32;
+        FakeRead(ForLet(None), _3);
+        StorageLive(_4);
+        _4 = String::new() -> [return: bb2, unwind: bb16];
+    }
+
+    bb2: {
+        FakeRead(ForLet(None), _4);
+        StorageLive(_5);
+        _5 = String::new() -> [return: bb3, unwind: bb15];
+    }
+
+    bb3: {
+        FakeRead(ForLet(None), _5);
+        StorageLive(_6);
+        StorageLive(_7);
+        _7 = move _4;
+        _6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb13];
+    }
+
+    bb4: {
+        StorageDead(_7);
+        StorageDead(_6);
+        drop(_5) -> [return: bb5, unwind: bb15];
+    }
+
+    bb5: {
+        StorageDead(_5);
+        drop(_4) -> [return: bb6, unwind: bb16];
+    }
+
+    bb6: {
+        StorageDead(_4);
+        StorageDead(_3);
+        drop(_2) -> [return: bb7, unwind: bb17];
+    }
+
+    bb7: {
+        StorageDead(_2);
+        tailcall g();
+    }
+
+    bb8: {
+        drop(_5) -> [return: bb9, unwind: bb15];
+    }
+
+    bb9: {
+        StorageDead(_5);
+        drop(_4) -> [return: bb10, unwind: bb16];
+    }
+
+    bb10: {
+        StorageDead(_4);
+        StorageDead(_3);
+        drop(_2) -> [return: bb11, unwind: bb17];
+    }
+
+    bb11: {
+        StorageDead(_2);
+        unreachable;
+    }
+
+    bb12: {
+        return;
+    }
+
+    bb13 (cleanup): {
+        drop(_7) -> [return: bb14, unwind terminate(cleanup)];
+    }
+
+    bb14 (cleanup): {
+        drop(_5) -> [return: bb15, unwind terminate(cleanup)];
+    }
+
+    bb15 (cleanup): {
+        drop(_4) -> [return: bb16, unwind terminate(cleanup)];
+    }
+
+    bb16 (cleanup): {
+        drop(_2) -> [return: bb17, unwind terminate(cleanup)];
+    }
+
+    bb17 (cleanup): {
+        resume;
+    }
+}
diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff
new file mode 100644
index 00000000000..c7df2bb2207
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff
@@ -0,0 +1,184 @@
+- // MIR for `f_with_arg` before ElaborateDrops
++ // MIR for `f_with_arg` after ElaborateDrops
+  
+  fn f_with_arg(_1: String, _2: String) -> () {
+      debug _arg1 => _1;
+      debug _arg2 => _2;
+      let mut _0: ();
+      let mut _3: !;
+      let _4: std::string::String;
+      let _8: ();
+      let mut _9: std::string::String;
+      let mut _10: std::string::String;
+      let mut _11: std::string::String;
++     let mut _12: bool;
+      scope 1 {
+          debug _a => _4;
+          let _5: i32;
+          scope 2 {
+              debug _b => _5;
+              let _6: std::string::String;
+              scope 3 {
+                  debug _c => _6;
+                  let _7: std::string::String;
+                  scope 4 {
+                      debug _d => _7;
+                  }
+              }
+          }
+      }
+  
+      bb0: {
++         _12 = const false;
+          StorageLive(_4);
+          _4 = String::new() -> [return: bb1, unwind: bb27];
+      }
+  
+      bb1: {
+          StorageLive(_5);
+          _5 = const 12_i32;
+          StorageLive(_6);
+          _6 = String::new() -> [return: bb2, unwind: bb26];
+      }
+  
+      bb2: {
++         _12 = const true;
+          StorageLive(_7);
+          _7 = String::new() -> [return: bb3, unwind: bb25];
+      }
+  
+      bb3: {
+          StorageLive(_8);
+          StorageLive(_9);
++         _12 = const false;
+          _9 = move _6;
+          _8 = std::mem::drop::<String>(move _9) -> [return: bb4, unwind: bb23];
+      }
+  
+      bb4: {
+          StorageDead(_9);
+          StorageDead(_8);
+          StorageLive(_10);
+          _10 = String::new() -> [return: bb5, unwind: bb24];
+      }
+  
+      bb5: {
+          StorageLive(_11);
+          _11 = String::new() -> [return: bb6, unwind: bb22];
+      }
+  
+      bb6: {
+          drop(_7) -> [return: bb7, unwind: bb20];
+      }
+  
+      bb7: {
+          StorageDead(_7);
+-         drop(_6) -> [return: bb8, unwind: bb18];
++         goto -> bb8;
+      }
+  
+      bb8: {
++         _12 = const false;
+          StorageDead(_6);
+          StorageDead(_5);
+          drop(_4) -> [return: bb9, unwind: bb16];
+      }
+  
+      bb9: {
+          StorageDead(_4);
+          drop(_2) -> [return: bb10, unwind: bb14];
+      }
+  
+      bb10: {
+          drop(_1) -> [return: bb11, unwind: bb12];
+      }
+  
+      bb11: {
+          tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) });
+      }
+  
+      bb12 (cleanup): {
+          drop(_10) -> [return: bb13, unwind terminate(cleanup)];
+      }
+  
+      bb13 (cleanup): {
+          drop(_11) -> [return: bb29, unwind terminate(cleanup)];
+      }
+  
+      bb14 (cleanup): {
+          drop(_10) -> [return: bb15, unwind terminate(cleanup)];
+      }
+  
+      bb15 (cleanup): {
+          drop(_11) -> [return: bb28, unwind terminate(cleanup)];
+      }
+  
+      bb16 (cleanup): {
+          drop(_10) -> [return: bb17, unwind terminate(cleanup)];
+      }
+  
+      bb17 (cleanup): {
+          drop(_11) -> [return: bb27, unwind terminate(cleanup)];
+      }
+  
+      bb18 (cleanup): {
+-         drop(_10) -> [return: bb19, unwind terminate(cleanup)];
++         goto -> bb19;
+      }
+  
+      bb19 (cleanup): {
+-         drop(_11) -> [return: bb26, unwind terminate(cleanup)];
++         goto -> bb26;
+      }
+  
+      bb20 (cleanup): {
+          drop(_10) -> [return: bb21, unwind terminate(cleanup)];
+      }
+  
+      bb21 (cleanup): {
+          drop(_11) -> [return: bb25, unwind terminate(cleanup)];
+      }
+  
+      bb22 (cleanup): {
+          drop(_10) -> [return: bb24, unwind terminate(cleanup)];
+      }
+  
+      bb23 (cleanup): {
+-         drop(_9) -> [return: bb24, unwind terminate(cleanup)];
++         goto -> bb24;
+      }
+  
+      bb24 (cleanup): {
+          drop(_7) -> [return: bb25, unwind terminate(cleanup)];
+      }
+  
+      bb25 (cleanup): {
+-         drop(_6) -> [return: bb26, unwind terminate(cleanup)];
++         goto -> bb31;
+      }
+  
+      bb26 (cleanup): {
+          drop(_4) -> [return: bb27, unwind terminate(cleanup)];
+      }
+  
+      bb27 (cleanup): {
+          drop(_2) -> [return: bb28, unwind terminate(cleanup)];
+      }
+  
+      bb28 (cleanup): {
+          drop(_1) -> [return: bb29, unwind terminate(cleanup)];
+      }
+  
+      bb29 (cleanup): {
+          resume;
++     }
++ 
++     bb30 (cleanup): {
++         drop(_6) -> [return: bb26, unwind terminate(cleanup)];
++     }
++ 
++     bb31 (cleanup): {
++         switchInt(_12) -> [0: bb26, otherwise: bb30];
+      }
+  }
+  
diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff
new file mode 100644
index 00000000000..c7df2bb2207
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff
@@ -0,0 +1,184 @@
+- // MIR for `f_with_arg` before ElaborateDrops
++ // MIR for `f_with_arg` after ElaborateDrops
+  
+  fn f_with_arg(_1: String, _2: String) -> () {
+      debug _arg1 => _1;
+      debug _arg2 => _2;
+      let mut _0: ();
+      let mut _3: !;
+      let _4: std::string::String;
+      let _8: ();
+      let mut _9: std::string::String;
+      let mut _10: std::string::String;
+      let mut _11: std::string::String;
++     let mut _12: bool;
+      scope 1 {
+          debug _a => _4;
+          let _5: i32;
+          scope 2 {
+              debug _b => _5;
+              let _6: std::string::String;
+              scope 3 {
+                  debug _c => _6;
+                  let _7: std::string::String;
+                  scope 4 {
+                      debug _d => _7;
+                  }
+              }
+          }
+      }
+  
+      bb0: {
++         _12 = const false;
+          StorageLive(_4);
+          _4 = String::new() -> [return: bb1, unwind: bb27];
+      }
+  
+      bb1: {
+          StorageLive(_5);
+          _5 = const 12_i32;
+          StorageLive(_6);
+          _6 = String::new() -> [return: bb2, unwind: bb26];
+      }
+  
+      bb2: {
++         _12 = const true;
+          StorageLive(_7);
+          _7 = String::new() -> [return: bb3, unwind: bb25];
+      }
+  
+      bb3: {
+          StorageLive(_8);
+          StorageLive(_9);
++         _12 = const false;
+          _9 = move _6;
+          _8 = std::mem::drop::<String>(move _9) -> [return: bb4, unwind: bb23];
+      }
+  
+      bb4: {
+          StorageDead(_9);
+          StorageDead(_8);
+          StorageLive(_10);
+          _10 = String::new() -> [return: bb5, unwind: bb24];
+      }
+  
+      bb5: {
+          StorageLive(_11);
+          _11 = String::new() -> [return: bb6, unwind: bb22];
+      }
+  
+      bb6: {
+          drop(_7) -> [return: bb7, unwind: bb20];
+      }
+  
+      bb7: {
+          StorageDead(_7);
+-         drop(_6) -> [return: bb8, unwind: bb18];
++         goto -> bb8;
+      }
+  
+      bb8: {
++         _12 = const false;
+          StorageDead(_6);
+          StorageDead(_5);
+          drop(_4) -> [return: bb9, unwind: bb16];
+      }
+  
+      bb9: {
+          StorageDead(_4);
+          drop(_2) -> [return: bb10, unwind: bb14];
+      }
+  
+      bb10: {
+          drop(_1) -> [return: bb11, unwind: bb12];
+      }
+  
+      bb11: {
+          tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) });
+      }
+  
+      bb12 (cleanup): {
+          drop(_10) -> [return: bb13, unwind terminate(cleanup)];
+      }
+  
+      bb13 (cleanup): {
+          drop(_11) -> [return: bb29, unwind terminate(cleanup)];
+      }
+  
+      bb14 (cleanup): {
+          drop(_10) -> [return: bb15, unwind terminate(cleanup)];
+      }
+  
+      bb15 (cleanup): {
+          drop(_11) -> [return: bb28, unwind terminate(cleanup)];
+      }
+  
+      bb16 (cleanup): {
+          drop(_10) -> [return: bb17, unwind terminate(cleanup)];
+      }
+  
+      bb17 (cleanup): {
+          drop(_11) -> [return: bb27, unwind terminate(cleanup)];
+      }
+  
+      bb18 (cleanup): {
+-         drop(_10) -> [return: bb19, unwind terminate(cleanup)];
++         goto -> bb19;
+      }
+  
+      bb19 (cleanup): {
+-         drop(_11) -> [return: bb26, unwind terminate(cleanup)];
++         goto -> bb26;
+      }
+  
+      bb20 (cleanup): {
+          drop(_10) -> [return: bb21, unwind terminate(cleanup)];
+      }
+  
+      bb21 (cleanup): {
+          drop(_11) -> [return: bb25, unwind terminate(cleanup)];
+      }
+  
+      bb22 (cleanup): {
+          drop(_10) -> [return: bb24, unwind terminate(cleanup)];
+      }
+  
+      bb23 (cleanup): {
+-         drop(_9) -> [return: bb24, unwind terminate(cleanup)];
++         goto -> bb24;
+      }
+  
+      bb24 (cleanup): {
+          drop(_7) -> [return: bb25, unwind terminate(cleanup)];
+      }
+  
+      bb25 (cleanup): {
+-         drop(_6) -> [return: bb26, unwind terminate(cleanup)];
++         goto -> bb31;
+      }
+  
+      bb26 (cleanup): {
+          drop(_4) -> [return: bb27, unwind terminate(cleanup)];
+      }
+  
+      bb27 (cleanup): {
+          drop(_2) -> [return: bb28, unwind terminate(cleanup)];
+      }
+  
+      bb28 (cleanup): {
+          drop(_1) -> [return: bb29, unwind terminate(cleanup)];
+      }
+  
+      bb29 (cleanup): {
+          resume;
++     }
++ 
++     bb30 (cleanup): {
++         drop(_6) -> [return: bb26, unwind terminate(cleanup)];
++     }
++ 
++     bb31 (cleanup): {
++         switchInt(_12) -> [0: bb26, otherwise: bb30];
+      }
+  }
+  
diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir
new file mode 100644
index 00000000000..744f1989acc
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir
@@ -0,0 +1,202 @@
+// MIR for `f_with_arg` after built
+
+fn f_with_arg(_1: String, _2: String) -> () {
+    debug _arg1 => _1;
+    debug _arg2 => _2;
+    let mut _0: ();
+    let mut _3: !;
+    let _4: std::string::String;
+    let _8: ();
+    let mut _9: std::string::String;
+    let mut _10: std::string::String;
+    let mut _11: std::string::String;
+    scope 1 {
+        debug _a => _4;
+        let _5: i32;
+        scope 2 {
+            debug _b => _5;
+            let _6: std::string::String;
+            scope 3 {
+                debug _c => _6;
+                let _7: std::string::String;
+                scope 4 {
+                    debug _d => _7;
+                }
+            }
+        }
+    }
+
+    bb0: {
+        StorageLive(_4);
+        _4 = String::new() -> [return: bb1, unwind: bb34];
+    }
+
+    bb1: {
+        FakeRead(ForLet(None), _4);
+        StorageLive(_5);
+        _5 = const 12_i32;
+        FakeRead(ForLet(None), _5);
+        StorageLive(_6);
+        _6 = String::new() -> [return: bb2, unwind: bb33];
+    }
+
+    bb2: {
+        FakeRead(ForLet(None), _6);
+        StorageLive(_7);
+        _7 = String::new() -> [return: bb3, unwind: bb32];
+    }
+
+    bb3: {
+        FakeRead(ForLet(None), _7);
+        StorageLive(_8);
+        StorageLive(_9);
+        _9 = move _6;
+        _8 = std::mem::drop::<String>(move _9) -> [return: bb4, unwind: bb30];
+    }
+
+    bb4: {
+        StorageDead(_9);
+        StorageDead(_8);
+        StorageLive(_10);
+        _10 = String::new() -> [return: bb5, unwind: bb31];
+    }
+
+    bb5: {
+        StorageLive(_11);
+        _11 = String::new() -> [return: bb6, unwind: bb29];
+    }
+
+    bb6: {
+        drop(_7) -> [return: bb7, unwind: bb27];
+    }
+
+    bb7: {
+        StorageDead(_7);
+        drop(_6) -> [return: bb8, unwind: bb25];
+    }
+
+    bb8: {
+        StorageDead(_6);
+        StorageDead(_5);
+        drop(_4) -> [return: bb9, unwind: bb23];
+    }
+
+    bb9: {
+        StorageDead(_4);
+        drop(_2) -> [return: bb10, unwind: bb21];
+    }
+
+    bb10: {
+        drop(_1) -> [return: bb11, unwind: bb19];
+    }
+
+    bb11: {
+        tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) });
+    }
+
+    bb12: {
+        StorageDead(_11);
+        StorageDead(_10);
+        drop(_7) -> [return: bb13, unwind: bb32];
+    }
+
+    bb13: {
+        StorageDead(_7);
+        drop(_6) -> [return: bb14, unwind: bb33];
+    }
+
+    bb14: {
+        StorageDead(_6);
+        StorageDead(_5);
+        drop(_4) -> [return: bb15, unwind: bb34];
+    }
+
+    bb15: {
+        StorageDead(_4);
+        unreachable;
+    }
+
+    bb16: {
+        drop(_2) -> [return: bb17, unwind: bb35];
+    }
+
+    bb17: {
+        drop(_1) -> [return: bb18, unwind: bb36];
+    }
+
+    bb18: {
+        return;
+    }
+
+    bb19 (cleanup): {
+        drop(_10) -> [return: bb20, unwind terminate(cleanup)];
+    }
+
+    bb20 (cleanup): {
+        drop(_11) -> [return: bb36, unwind terminate(cleanup)];
+    }
+
+    bb21 (cleanup): {
+        drop(_10) -> [return: bb22, unwind terminate(cleanup)];
+    }
+
+    bb22 (cleanup): {
+        drop(_11) -> [return: bb35, unwind terminate(cleanup)];
+    }
+
+    bb23 (cleanup): {
+        drop(_10) -> [return: bb24, unwind terminate(cleanup)];
+    }
+
+    bb24 (cleanup): {
+        drop(_11) -> [return: bb34, unwind terminate(cleanup)];
+    }
+
+    bb25 (cleanup): {
+        drop(_10) -> [return: bb26, unwind terminate(cleanup)];
+    }
+
+    bb26 (cleanup): {
+        drop(_11) -> [return: bb33, unwind terminate(cleanup)];
+    }
+
+    bb27 (cleanup): {
+        drop(_10) -> [return: bb28, unwind terminate(cleanup)];
+    }
+
+    bb28 (cleanup): {
+        drop(_11) -> [return: bb32, unwind terminate(cleanup)];
+    }
+
+    bb29 (cleanup): {
+        drop(_10) -> [return: bb31, unwind terminate(cleanup)];
+    }
+
+    bb30 (cleanup): {
+        drop(_9) -> [return: bb31, unwind terminate(cleanup)];
+    }
+
+    bb31 (cleanup): {
+        drop(_7) -> [return: bb32, unwind terminate(cleanup)];
+    }
+
+    bb32 (cleanup): {
+        drop(_6) -> [return: bb33, unwind terminate(cleanup)];
+    }
+
+    bb33 (cleanup): {
+        drop(_4) -> [return: bb34, unwind terminate(cleanup)];
+    }
+
+    bb34 (cleanup): {
+        drop(_2) -> [return: bb35, unwind terminate(cleanup)];
+    }
+
+    bb35 (cleanup): {
+        drop(_1) -> [return: bb36, unwind terminate(cleanup)];
+    }
+
+    bb36 (cleanup): {
+        resume;
+    }
+}
diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir
new file mode 100644
index 00000000000..744f1989acc
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir
@@ -0,0 +1,202 @@
+// MIR for `f_with_arg` after built
+
+fn f_with_arg(_1: String, _2: String) -> () {
+    debug _arg1 => _1;
+    debug _arg2 => _2;
+    let mut _0: ();
+    let mut _3: !;
+    let _4: std::string::String;
+    let _8: ();
+    let mut _9: std::string::String;
+    let mut _10: std::string::String;
+    let mut _11: std::string::String;
+    scope 1 {
+        debug _a => _4;
+        let _5: i32;
+        scope 2 {
+            debug _b => _5;
+            let _6: std::string::String;
+            scope 3 {
+                debug _c => _6;
+                let _7: std::string::String;
+                scope 4 {
+                    debug _d => _7;
+                }
+            }
+        }
+    }
+
+    bb0: {
+        StorageLive(_4);
+        _4 = String::new() -> [return: bb1, unwind: bb34];
+    }
+
+    bb1: {
+        FakeRead(ForLet(None), _4);
+        StorageLive(_5);
+        _5 = const 12_i32;
+        FakeRead(ForLet(None), _5);
+        StorageLive(_6);
+        _6 = String::new() -> [return: bb2, unwind: bb33];
+    }
+
+    bb2: {
+        FakeRead(ForLet(None), _6);
+        StorageLive(_7);
+        _7 = String::new() -> [return: bb3, unwind: bb32];
+    }
+
+    bb3: {
+        FakeRead(ForLet(None), _7);
+        StorageLive(_8);
+        StorageLive(_9);
+        _9 = move _6;
+        _8 = std::mem::drop::<String>(move _9) -> [return: bb4, unwind: bb30];
+    }
+
+    bb4: {
+        StorageDead(_9);
+        StorageDead(_8);
+        StorageLive(_10);
+        _10 = String::new() -> [return: bb5, unwind: bb31];
+    }
+
+    bb5: {
+        StorageLive(_11);
+        _11 = String::new() -> [return: bb6, unwind: bb29];
+    }
+
+    bb6: {
+        drop(_7) -> [return: bb7, unwind: bb27];
+    }
+
+    bb7: {
+        StorageDead(_7);
+        drop(_6) -> [return: bb8, unwind: bb25];
+    }
+
+    bb8: {
+        StorageDead(_6);
+        StorageDead(_5);
+        drop(_4) -> [return: bb9, unwind: bb23];
+    }
+
+    bb9: {
+        StorageDead(_4);
+        drop(_2) -> [return: bb10, unwind: bb21];
+    }
+
+    bb10: {
+        drop(_1) -> [return: bb11, unwind: bb19];
+    }
+
+    bb11: {
+        tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) });
+    }
+
+    bb12: {
+        StorageDead(_11);
+        StorageDead(_10);
+        drop(_7) -> [return: bb13, unwind: bb32];
+    }
+
+    bb13: {
+        StorageDead(_7);
+        drop(_6) -> [return: bb14, unwind: bb33];
+    }
+
+    bb14: {
+        StorageDead(_6);
+        StorageDead(_5);
+        drop(_4) -> [return: bb15, unwind: bb34];
+    }
+
+    bb15: {
+        StorageDead(_4);
+        unreachable;
+    }
+
+    bb16: {
+        drop(_2) -> [return: bb17, unwind: bb35];
+    }
+
+    bb17: {
+        drop(_1) -> [return: bb18, unwind: bb36];
+    }
+
+    bb18: {
+        return;
+    }
+
+    bb19 (cleanup): {
+        drop(_10) -> [return: bb20, unwind terminate(cleanup)];
+    }
+
+    bb20 (cleanup): {
+        drop(_11) -> [return: bb36, unwind terminate(cleanup)];
+    }
+
+    bb21 (cleanup): {
+        drop(_10) -> [return: bb22, unwind terminate(cleanup)];
+    }
+
+    bb22 (cleanup): {
+        drop(_11) -> [return: bb35, unwind terminate(cleanup)];
+    }
+
+    bb23 (cleanup): {
+        drop(_10) -> [return: bb24, unwind terminate(cleanup)];
+    }
+
+    bb24 (cleanup): {
+        drop(_11) -> [return: bb34, unwind terminate(cleanup)];
+    }
+
+    bb25 (cleanup): {
+        drop(_10) -> [return: bb26, unwind terminate(cleanup)];
+    }
+
+    bb26 (cleanup): {
+        drop(_11) -> [return: bb33, unwind terminate(cleanup)];
+    }
+
+    bb27 (cleanup): {
+        drop(_10) -> [return: bb28, unwind terminate(cleanup)];
+    }
+
+    bb28 (cleanup): {
+        drop(_11) -> [return: bb32, unwind terminate(cleanup)];
+    }
+
+    bb29 (cleanup): {
+        drop(_10) -> [return: bb31, unwind terminate(cleanup)];
+    }
+
+    bb30 (cleanup): {
+        drop(_9) -> [return: bb31, unwind terminate(cleanup)];
+    }
+
+    bb31 (cleanup): {
+        drop(_7) -> [return: bb32, unwind terminate(cleanup)];
+    }
+
+    bb32 (cleanup): {
+        drop(_6) -> [return: bb33, unwind terminate(cleanup)];
+    }
+
+    bb33 (cleanup): {
+        drop(_4) -> [return: bb34, unwind terminate(cleanup)];
+    }
+
+    bb34 (cleanup): {
+        drop(_2) -> [return: bb35, unwind terminate(cleanup)];
+    }
+
+    bb35 (cleanup): {
+        drop(_1) -> [return: bb36, unwind terminate(cleanup)];
+    }
+
+    bb36 (cleanup): {
+        resume;
+    }
+}
diff --git a/tests/mir-opt/tail_call_drops.rs b/tests/mir-opt/tail_call_drops.rs
new file mode 100644
index 00000000000..56f4852a95f
--- /dev/null
+++ b/tests/mir-opt/tail_call_drops.rs
@@ -0,0 +1,41 @@
+// skip-filecheck
+// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
+#![allow(incomplete_features)]
+#![feature(explicit_tail_calls)]
+
+// EMIT_MIR tail_call_drops.f.built.after.mir
+//   Expected result:
+//   drop(_d) -> drop(_c) -> drop(_a) -> tailcall g()
+//
+// EMIT_MIR tail_call_drops.f.ElaborateDrops.diff
+//   Expected result:
+//   drop(_d) ->             drop(_a) -> tailcall g()
+fn f() {
+    let _a = String::new();
+    let _b = 12;
+    let _c = String::new();
+    let _d = String::new();
+
+    drop(_c);
+
+    become g();
+}
+
+fn g() {}
+
+// EMIT_MIR tail_call_drops.f_with_arg.built.after.mir
+// EMIT_MIR tail_call_drops.f_with_arg.ElaborateDrops.diff
+fn f_with_arg(_arg1: String, _arg2: String) {
+    let _a = String::new();
+    let _b = 12;
+    let _c = String::new();
+    let _d = String::new();
+
+    drop(_c);
+
+    become g_with_arg(String::new(), String::new());
+}
+
+fn g_with_arg(_arg1: String, _arg2: String) {}
+
+fn main() {}
diff --git a/tests/ui/explicit-tail-calls/drop-order.rs b/tests/ui/explicit-tail-calls/drop-order.rs
new file mode 100644
index 00000000000..e20730446ec
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/drop-order.rs
@@ -0,0 +1,70 @@
+// FIXME(explicit_tail_calls): enable this test once rustc_codegen_ssa supports tail calls
+//@ ignore-test: tail calls are not implemented in rustc_codegen_ssa yet, so this causes 🧊
+//@ run-pass
+#![allow(incomplete_features)]
+#![feature(explicit_tail_calls)]
+use std::cell::RefCell;
+
+fn main() {
+    let tail_counter = Default::default();
+    tail_recursive(0, &tail_counter);
+    assert_eq!(tail_counter.into_inner(), (0..128).collect::<Vec<u8>>());
+
+    let simply_counter = Default::default();
+    simply_recursive(0, &simply_counter);
+    assert_eq!(simply_counter.into_inner(), (0..128).rev().collect::<Vec<u8>>());
+
+    let scope_counter = Default::default();
+    out_of_inner_scope(&scope_counter);
+    assert_eq!(scope_counter.into_inner(), (0..8).collect::<Vec<u8>>());
+}
+
+fn tail_recursive(n: u8, order: &RefCell<Vec<u8>>) {
+    if n >= 128 {
+        return;
+    }
+
+    let _local = DropCounter(n, order);
+
+    become tail_recursive(n + 1, order)
+}
+
+fn simply_recursive(n: u8, order: &RefCell<Vec<u8>>) {
+    if n >= 128 {
+        return;
+    }
+
+    let _local = DropCounter(n, order);
+
+    return simply_recursive(n + 1, order);
+}
+
+fn out_of_inner_scope(order: &RefCell<Vec<u8>>) {
+    fn inner(order: &RefCell<Vec<u8>>) {
+        let _7 = DropCounter(7, order);
+        let _6 = DropCounter(6, order);
+    }
+
+    let _5 = DropCounter(5, order);
+    let _4 = DropCounter(4, order);
+
+    if true {
+        let _3 = DropCounter(3, order);
+        let _2 = DropCounter(2, order);
+        loop {
+            let _1 = DropCounter(1, order);
+            let _0 = DropCounter(0, order);
+
+            become inner(order);
+        }
+    }
+}
+
+struct DropCounter<'a>(u8, &'a RefCell<Vec<u8>>);
+
+impl Drop for DropCounter<'_> {
+    #[track_caller]
+    fn drop(&mut self) {
+        self.1.borrow_mut().push(self.0);
+    }
+}