about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2020-04-09 09:57:17 +0000
committerbors <bors@rust-lang.org>2020-04-09 09:57:17 +0000
commit93dc97a85381cc52eb872d27e50e4d518926a27c (patch)
treec2eb851ca9c9012962582fddbbd6f2948a1fa19e
parent11f6096a9ef6ad52de2956f4d2df200de7617077 (diff)
parent09052a6d354036a3431733eef145671292548e48 (diff)
downloadrust-93dc97a85381cc52eb872d27e50e4d518926a27c.tar.gz
rust-93dc97a85381cc52eb872d27e50e4d518926a27c.zip
Auto merge of #70943 - Centril:rollup-eowm2h3, r=Centril
Rollup of 7 pull requests

Successful merges:

 - #67705 (Use unrolled loop for searching NULL in [u16] on Windows)
 - #70367 (save/restore `pessimistic_yield` when entering bodies)
 - #70822 (Don't lint for self-recursion when the function can diverge)
 - #70868 (rustc_codegen_ssa: Refactor construction of linker arguments)
 - #70896 (Implement Chain with Option fuses)
 - #70916 (Support `#[track_caller]` on functions in `extern "Rust" { ... }`)
 - #70918 (rustc_session: forbid lints override regardless of position)

Failed merges:

r? @ghost
-rw-r--r--src/doc/rustc/src/lints/levels.md2
-rw-r--r--src/libcore/iter/adapters/chain.rs267
-rw-r--r--src/librustc_codegen_ssa/back/link.rs599
-rw-r--r--src/librustc_codegen_ssa/back/linker.rs75
-rw-r--r--src/librustc_error_codes/error_codes.rs2
-rw-r--r--src/librustc_error_codes/error_codes/E0738.md11
-rw-r--r--src/librustc_mir_build/build/mod.rs4
-rw-r--r--src/librustc_mir_build/lints.rs227
-rw-r--r--src/librustc_passes/check_attr.rs14
-rw-r--r--src/librustc_passes/region.rs9
-rw-r--r--src/librustc_session/config.rs8
-rw-r--r--src/libstd/sys/windows/mod.rs48
-rw-r--r--src/test/ui-fulldeps/lint-group-forbid-always-trumps-cli.rs7
-rw-r--r--src/test/ui-fulldeps/lint-group-forbid-always-trumps-cli.stderr10
-rw-r--r--src/test/ui/async-await/issues/issue-69307-nested.rs30
-rw-r--r--src/test/ui/async-await/issues/issue-69307.rs23
-rw-r--r--src/test/ui/lint/lint-unconditional-recursion.rs18
-rw-r--r--src/test/ui/rfc-2091-track-caller/error-extern-fn.rs9
-rw-r--r--src/test/ui/rfc-2091-track-caller/error-extern-fn.stderr9
-rw-r--r--src/test/ui/rfc-2091-track-caller/error-with-naked.rs17
-rw-r--r--src/test/ui/rfc-2091-track-caller/error-with-naked.stderr14
-rw-r--r--src/test/ui/rfc-2091-track-caller/track-caller-ffi.rs50
22 files changed, 892 insertions, 561 deletions
diff --git a/src/doc/rustc/src/lints/levels.md b/src/doc/rustc/src/lints/levels.md
index 3cfe2f698f3..64cbbbb0035 100644
--- a/src/doc/rustc/src/lints/levels.md
+++ b/src/doc/rustc/src/lints/levels.md
@@ -170,7 +170,7 @@ The order of these command line arguments is taken into account. The following a
 $ rustc lib.rs --crate-type=lib -D unused-variables -A unused-variables
 ```
 
-You can make use of this behavior by overriding the level of one specific lint out of a group of lints. The following example denies all the lints in the `unused` group, but explicitly allows the `unused-variables` lint in that group:
+You can make use of this behavior by overriding the level of one specific lint out of a group of lints. The following example denies all the lints in the `unused` group, but explicitly allows the `unused-variables` lint in that group (forbid still trumps everything regardless of ordering):
 
 ```bash
 $ rustc lib.rs --crate-type=lib -D unused -A unused-variables
diff --git a/src/libcore/iter/adapters/chain.rs b/src/libcore/iter/adapters/chain.rs
index 3611a1aadad..2dd405ced20 100644
--- a/src/libcore/iter/adapters/chain.rs
+++ b/src/libcore/iter/adapters/chain.rs
@@ -1,8 +1,7 @@
+use crate::iter::{DoubleEndedIterator, FusedIterator, Iterator, TrustedLen};
 use crate::ops::Try;
 use crate::usize;
 
-use super::super::{DoubleEndedIterator, FusedIterator, Iterator, TrustedLen};
-
 /// An iterator that links two iterators together, in a chain.
 ///
 /// This `struct` is created by the [`chain`] method on [`Iterator`]. See its
@@ -14,37 +13,34 @@ use super::super::{DoubleEndedIterator, FusedIterator, Iterator, TrustedLen};
 #[must_use = "iterators are lazy and do nothing unless consumed"]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub struct Chain<A, B> {
-    a: A,
-    b: B,
-    state: ChainState,
+    // These are "fused" with `Option` so we don't need separate state to track which part is
+    // already exhausted, and we may also get niche layout for `None`. We don't use the real `Fuse`
+    // adapter because its specialization for `FusedIterator` unconditionally descends into the
+    // iterator, and that could be expensive to keep revisiting stuff like nested chains. It also
+    // hurts compiler performance to add more iterator layers to `Chain`.
+    a: Option<A>,
+    b: Option<B>,
 }
 impl<A, B> Chain<A, B> {
     pub(in super::super) fn new(a: A, b: B) -> Chain<A, B> {
-        Chain { a, b, state: ChainState::Both }
+        Chain { a: Some(a), b: Some(b) }
     }
 }
 
-// The iterator protocol specifies that iteration ends with the return value
-// `None` from `.next()` (or `.next_back()`) and it is unspecified what
-// further calls return. The chain adaptor must account for this since it uses
-// two subiterators.
-//
-//  It uses three states:
-//
-//  - Both: `a` and `b` are remaining
-//  - Front: `a` remaining
-//  - Back: `b` remaining
-//
-//  The fourth state (neither iterator is remaining) only occurs after Chain has
-//  returned None once, so we don't need to store this state.
-#[derive(Clone, Debug)]
-enum ChainState {
-    // both front and back iterator are remaining
-    Both,
-    // only front is remaining
-    Front,
-    // only back is remaining
-    Back,
+/// Fuse the iterator if the expression is `None`.
+macro_rules! fuse {
+    ($self:ident . $iter:ident . $($call:tt)+) => {
+        match $self.$iter {
+            Some(ref mut iter) => match iter.$($call)+ {
+                None => {
+                    $self.$iter = None;
+                    None
+                }
+                item => item,
+            },
+            None => None,
+        }
+    };
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -57,88 +53,68 @@ where
 
     #[inline]
     fn next(&mut self) -> Option<A::Item> {
-        match self.state {
-            ChainState::Both => match self.a.next() {
-                elt @ Some(..) => elt,
-                None => {
-                    self.state = ChainState::Back;
-                    self.b.next()
-                }
-            },
-            ChainState::Front => self.a.next(),
-            ChainState::Back => self.b.next(),
+        match fuse!(self.a.next()) {
+            None => fuse!(self.b.next()),
+            item => item,
         }
     }
 
     #[inline]
     #[rustc_inherit_overflow_checks]
     fn count(self) -> usize {
-        match self.state {
-            ChainState::Both => self.a.count() + self.b.count(),
-            ChainState::Front => self.a.count(),
-            ChainState::Back => self.b.count(),
-        }
+        let a_count = match self.a {
+            Some(a) => a.count(),
+            None => 0,
+        };
+        let b_count = match self.b {
+            Some(b) => b.count(),
+            None => 0,
+        };
+        a_count + b_count
     }
 
-    fn try_fold<Acc, F, R>(&mut self, init: Acc, mut f: F) -> R
+    fn try_fold<Acc, F, R>(&mut self, mut acc: Acc, mut f: F) -> R
     where
         Self: Sized,
         F: FnMut(Acc, Self::Item) -> R,
         R: Try<Ok = Acc>,
     {
-        let mut accum = init;
-        match self.state {
-            ChainState::Both | ChainState::Front => {
-                accum = self.a.try_fold(accum, &mut f)?;
-                if let ChainState::Both = self.state {
-                    self.state = ChainState::Back;
-                }
-            }
-            _ => {}
+        if let Some(ref mut a) = self.a {
+            acc = a.try_fold(acc, &mut f)?;
+            self.a = None;
         }
-        if let ChainState::Back = self.state {
-            accum = self.b.try_fold(accum, &mut f)?;
+        if let Some(ref mut b) = self.b {
+            acc = b.try_fold(acc, f)?;
+            self.b = None;
         }
-        Try::from_ok(accum)
+        Try::from_ok(acc)
     }
 
-    fn fold<Acc, F>(self, init: Acc, mut f: F) -> Acc
+    fn fold<Acc, F>(self, mut acc: Acc, mut f: F) -> Acc
     where
         F: FnMut(Acc, Self::Item) -> Acc,
     {
-        let mut accum = init;
-        match self.state {
-            ChainState::Both | ChainState::Front => {
-                accum = self.a.fold(accum, &mut f);
-            }
-            _ => {}
+        if let Some(a) = self.a {
+            acc = a.fold(acc, &mut f);
         }
-        match self.state {
-            ChainState::Both | ChainState::Back => {
-                accum = self.b.fold(accum, &mut f);
-            }
-            _ => {}
+        if let Some(b) = self.b {
+            acc = b.fold(acc, f);
         }
-        accum
+        acc
     }
 
     #[inline]
     fn nth(&mut self, mut n: usize) -> Option<A::Item> {
-        match self.state {
-            ChainState::Both | ChainState::Front => {
-                for x in self.a.by_ref() {
-                    if n == 0 {
-                        return Some(x);
-                    }
-                    n -= 1;
-                }
-                if let ChainState::Both = self.state {
-                    self.state = ChainState::Back;
+        if let Some(ref mut a) = self.a {
+            while let Some(x) = a.next() {
+                if n == 0 {
+                    return Some(x);
                 }
+                n -= 1;
             }
-            ChainState::Back => {}
+            self.a = None;
         }
-        if let ChainState::Back = self.state { self.b.nth(n) } else { None }
+        fuse!(self.b.nth(n))
     }
 
     #[inline]
@@ -146,39 +122,32 @@ where
     where
         P: FnMut(&Self::Item) -> bool,
     {
-        match self.state {
-            ChainState::Both => match self.a.find(&mut predicate) {
-                None => {
-                    self.state = ChainState::Back;
-                    self.b.find(predicate)
-                }
-                v => v,
-            },
-            ChainState::Front => self.a.find(predicate),
-            ChainState::Back => self.b.find(predicate),
+        match fuse!(self.a.find(&mut predicate)) {
+            None => fuse!(self.b.find(predicate)),
+            item => item,
         }
     }
 
     #[inline]
     fn last(self) -> Option<A::Item> {
-        match self.state {
-            ChainState::Both => {
-                // Must exhaust a before b.
-                let a_last = self.a.last();
-                let b_last = self.b.last();
-                b_last.or(a_last)
-            }
-            ChainState::Front => self.a.last(),
-            ChainState::Back => self.b.last(),
-        }
+        // Must exhaust a before b.
+        let a_last = match self.a {
+            Some(a) => a.last(),
+            None => None,
+        };
+        let b_last = match self.b {
+            Some(b) => b.last(),
+            None => None,
+        };
+        b_last.or(a_last)
     }
 
     #[inline]
     fn size_hint(&self) -> (usize, Option<usize>) {
-        match self.state {
-            ChainState::Both => {
-                let (a_lower, a_upper) = self.a.size_hint();
-                let (b_lower, b_upper) = self.b.size_hint();
+        match self {
+            Chain { a: Some(a), b: Some(b) } => {
+                let (a_lower, a_upper) = a.size_hint();
+                let (b_lower, b_upper) = b.size_hint();
 
                 let lower = a_lower.saturating_add(b_lower);
 
@@ -189,8 +158,9 @@ where
 
                 (lower, upper)
             }
-            ChainState::Front => self.a.size_hint(),
-            ChainState::Back => self.b.size_hint(),
+            Chain { a: Some(a), b: None } => a.size_hint(),
+            Chain { a: None, b: Some(b) } => b.size_hint(),
+            Chain { a: None, b: None } => (0, Some(0)),
         }
     }
 }
@@ -203,82 +173,71 @@ where
 {
     #[inline]
     fn next_back(&mut self) -> Option<A::Item> {
-        match self.state {
-            ChainState::Both => match self.b.next_back() {
-                elt @ Some(..) => elt,
-                None => {
-                    self.state = ChainState::Front;
-                    self.a.next_back()
-                }
-            },
-            ChainState::Front => self.a.next_back(),
-            ChainState::Back => self.b.next_back(),
+        match fuse!(self.b.next_back()) {
+            None => fuse!(self.a.next_back()),
+            item => item,
         }
     }
 
     #[inline]
     fn nth_back(&mut self, mut n: usize) -> Option<A::Item> {
-        match self.state {
-            ChainState::Both | ChainState::Back => {
-                for x in self.b.by_ref().rev() {
-                    if n == 0 {
-                        return Some(x);
-                    }
-                    n -= 1;
-                }
-                if let ChainState::Both = self.state {
-                    self.state = ChainState::Front;
+        if let Some(ref mut b) = self.b {
+            while let Some(x) = b.next_back() {
+                if n == 0 {
+                    return Some(x);
                 }
+                n -= 1;
             }
-            ChainState::Front => {}
+            self.b = None;
         }
-        if let ChainState::Front = self.state { self.a.nth_back(n) } else { None }
+        fuse!(self.a.nth_back(n))
     }
 
-    fn try_rfold<Acc, F, R>(&mut self, init: Acc, mut f: F) -> R
+    #[inline]
+    fn rfind<P>(&mut self, mut predicate: P) -> Option<Self::Item>
+    where
+        P: FnMut(&Self::Item) -> bool,
+    {
+        match fuse!(self.b.rfind(&mut predicate)) {
+            None => fuse!(self.a.rfind(predicate)),
+            item => item,
+        }
+    }
+
+    fn try_rfold<Acc, F, R>(&mut self, mut acc: Acc, mut f: F) -> R
     where
         Self: Sized,
         F: FnMut(Acc, Self::Item) -> R,
         R: Try<Ok = Acc>,
     {
-        let mut accum = init;
-        match self.state {
-            ChainState::Both | ChainState::Back => {
-                accum = self.b.try_rfold(accum, &mut f)?;
-                if let ChainState::Both = self.state {
-                    self.state = ChainState::Front;
-                }
-            }
-            _ => {}
+        if let Some(ref mut b) = self.b {
+            acc = b.try_rfold(acc, &mut f)?;
+            self.b = None;
         }
-        if let ChainState::Front = self.state {
-            accum = self.a.try_rfold(accum, &mut f)?;
+        if let Some(ref mut a) = self.a {
+            acc = a.try_rfold(acc, f)?;
+            self.a = None;
         }
-        Try::from_ok(accum)
+        Try::from_ok(acc)
     }
 
-    fn rfold<Acc, F>(self, init: Acc, mut f: F) -> Acc
+    fn rfold<Acc, F>(self, mut acc: Acc, mut f: F) -> Acc
     where
         F: FnMut(Acc, Self::Item) -> Acc,
     {
-        let mut accum = init;
-        match self.state {
-            ChainState::Both | ChainState::Back => {
-                accum = self.b.rfold(accum, &mut f);
-            }
-            _ => {}
+        if let Some(b) = self.b {
+            acc = b.rfold(acc, &mut f);
         }
-        match self.state {
-            ChainState::Both | ChainState::Front => {
-                accum = self.a.rfold(accum, &mut f);
-            }
-            _ => {}
+        if let Some(a) = self.a {
+            acc = a.rfold(acc, f);
         }
-        accum
+        acc
     }
 }
 
 // Note: *both* must be fused to handle double-ended iterators.
+// Now that we "fuse" both sides, we *could* implement this unconditionally,
+// but we should be cautious about committing to that in the public API.
 #[stable(feature = "fused", since = "1.26.0")]
 impl<A, B> FusedIterator for Chain<A, B>
 where
diff --git a/src/librustc_codegen_ssa/back/link.rs b/src/librustc_codegen_ssa/back/link.rs
index 49786bc3b06..20e64f0c488 100644
--- a/src/librustc_codegen_ssa/back/link.rs
+++ b/src/librustc_codegen_ssa/back/link.rs
@@ -154,7 +154,7 @@ pub fn link_binary<'a, B: ArchiveBuilder<'a>>(
 // The third parameter is for env vars, used on windows to set up the
 // path for MSVC to find its DLLs, and gcc to find its bundled
 // toolchain
-pub fn get_linker(sess: &Session, linker: &Path, flavor: LinkerFlavor) -> (PathBuf, Command) {
+fn get_linker(sess: &Session, linker: &Path, flavor: LinkerFlavor) -> Command {
     let msvc_tool = windows_registry::find_tool(&sess.opts.target_triple.triple(), "link.exe");
 
     // If our linker looks like a batch script on Windows then to execute this
@@ -193,6 +193,7 @@ pub fn get_linker(sess: &Session, linker: &Path, flavor: LinkerFlavor) -> (PathB
                     _ => None,
                 };
                 if let Some(ref a) = arch {
+                    // FIXME: Move this to `fn linker_with_args`.
                     let mut arg = OsString::from("/LIBPATH:");
                     arg.push(format!("{}\\lib\\{}\\store", root_lib_path.display(), a.to_string()));
                     cmd.arg(&arg);
@@ -232,7 +233,7 @@ pub fn get_linker(sess: &Session, linker: &Path, flavor: LinkerFlavor) -> (PathB
     }
     cmd.env("PATH", env::join_paths(new_path).unwrap());
 
-    (linker.to_path_buf(), cmd)
+    cmd
 }
 
 pub fn each_linked_rlib(
@@ -284,11 +285,7 @@ pub fn each_linked_rlib(
 /// building an `.rlib` (stomping over one another), or writing an `.rmeta` into a
 /// directory being searched for `extern crate` (observing an incomplete file).
 /// The returned path is the temporary file containing the complete metadata.
-pub fn emit_metadata<'a>(
-    sess: &'a Session,
-    metadata: &EncodedMetadata,
-    tmpdir: &TempDir,
-) -> PathBuf {
+pub fn emit_metadata(sess: &Session, metadata: &EncodedMetadata, tmpdir: &TempDir) -> PathBuf {
     let out_filename = tmpdir.path().join(METADATA_FILENAME);
     let result = fs::write(&out_filename, &metadata.raw_data);
 
@@ -487,95 +484,18 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(
     target_cpu: &str,
 ) {
     info!("preparing {:?} to {:?}", crate_type, out_filename);
-    let (linker, flavor) = linker_and_flavor(sess);
-
-    let any_dynamic_crate = crate_type == config::CrateType::Dylib
-        || codegen_results.crate_info.dependency_formats.iter().any(|(ty, list)| {
-            *ty == crate_type && list.iter().any(|&linkage| linkage == Linkage::Dynamic)
-        });
-
-    // The invocations of cc share some flags across platforms
-    let (pname, mut cmd) = get_linker(sess, &linker, flavor);
-
-    if let Some(args) = sess.target.target.options.pre_link_args.get(&flavor) {
-        cmd.args(args);
-    }
-    if let Some(args) = sess.target.target.options.pre_link_args_crt.get(&flavor) {
-        if sess.crt_static(Some(crate_type)) {
-            cmd.args(args);
-        }
-    }
-    cmd.args(&sess.opts.debugging_opts.pre_link_args);
-
-    if sess.target.target.options.is_like_fuchsia {
-        let prefix = match sess.opts.debugging_opts.sanitizer {
-            Some(Sanitizer::Address) => "asan/",
-            _ => "",
-        };
-        cmd.arg(format!("--dynamic-linker={}ld.so.1", prefix));
-    }
-
-    let pre_link_objects = if crate_type == config::CrateType::Executable {
-        &sess.target.target.options.pre_link_objects_exe
-    } else {
-        &sess.target.target.options.pre_link_objects_dll
-    };
-    for obj in pre_link_objects {
-        cmd.arg(get_file_path(sess, obj));
-    }
-
-    if crate_type == config::CrateType::Executable && sess.crt_static(Some(crate_type)) {
-        for obj in &sess.target.target.options.pre_link_objects_exe_crt {
-            cmd.arg(get_file_path(sess, obj));
-        }
-    }
-
-    if sess.target.target.options.is_like_emscripten {
-        cmd.arg("-s");
-        cmd.arg(if sess.panic_strategy() == PanicStrategy::Abort {
-            "DISABLE_EXCEPTION_CATCHING=1"
-        } else {
-            "DISABLE_EXCEPTION_CATCHING=0"
-        });
-    }
+    let (linker_path, flavor) = linker_and_flavor(sess);
+    let mut cmd = linker_with_args::<B>(
+        &linker_path,
+        flavor,
+        sess,
+        crate_type,
+        tmpdir,
+        out_filename,
+        codegen_results,
+        target_cpu,
+    );
 
-    {
-        let mut linker = codegen_results.linker_info.to_linker(cmd, &sess, flavor, target_cpu);
-        link_sanitizer_runtime(sess, crate_type, &mut *linker);
-        link_args::<B>(
-            &mut *linker,
-            flavor,
-            sess,
-            crate_type,
-            tmpdir,
-            out_filename,
-            codegen_results,
-        );
-        cmd = linker.finalize();
-    }
-    if let Some(args) = sess.target.target.options.late_link_args.get(&flavor) {
-        cmd.args(args);
-    }
-    if any_dynamic_crate {
-        if let Some(args) = sess.target.target.options.late_link_args_dynamic.get(&flavor) {
-            cmd.args(args);
-        }
-    } else {
-        if let Some(args) = sess.target.target.options.late_link_args_static.get(&flavor) {
-            cmd.args(args);
-        }
-    }
-    for obj in &sess.target.target.options.post_link_objects {
-        cmd.arg(get_file_path(sess, obj));
-    }
-    if sess.crt_static(Some(crate_type)) {
-        for obj in &sess.target.target.options.post_link_objects_crt {
-            cmd.arg(get_file_path(sess, obj));
-        }
-    }
-    if let Some(args) = sess.target.target.options.post_link_args.get(&flavor) {
-        cmd.args(args);
-    }
     for &(ref k, ref v) in &sess.target.target.options.link_env {
         cmd.env(k, v);
     }
@@ -597,7 +517,7 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(
     let mut i = 0;
     loop {
         i += 1;
-        prog = sess.time("run_linker", || exec_linker(sess, &mut cmd, out_filename, tmpdir));
+        prog = sess.time("run_linker", || exec_linker(sess, &cmd, out_filename, tmpdir));
         let output = match prog {
             Ok(ref output) => output,
             Err(_) => break,
@@ -698,7 +618,7 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(
                 output.extend_from_slice(&prog.stdout);
                 sess.struct_err(&format!(
                     "linking with `{}` failed: {}",
-                    pname.display(),
+                    linker_path.display(),
                     prog.status
                 ))
                 .note(&format!("{:?}", &cmd))
@@ -714,9 +634,12 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(
 
             let mut linker_error = {
                 if linker_not_found {
-                    sess.struct_err(&format!("linker `{}` not found", pname.display()))
+                    sess.struct_err(&format!("linker `{}` not found", linker_path.display()))
                 } else {
-                    sess.struct_err(&format!("could not exec the linker `{}`", pname.display()))
+                    sess.struct_err(&format!(
+                        "could not exec the linker `{}`",
+                        linker_path.display()
+                    ))
                 }
             };
 
@@ -787,7 +710,7 @@ fn link_sanitizer_runtime(sess: &Session, crate_type: config::CrateType, linker:
             // PR #41352 for details).
             let libname = format!("rustc{}_rt.{}", channel, name);
             let rpath = default_tlib.to_str().expect("non-utf8 component in path");
-            linker.args(&["-Wl,-rpath".into(), "-Xlinker".into(), rpath.into()]);
+            linker.args(&["-Wl,-rpath", "-Xlinker", rpath]);
             linker.link_dylib(Symbol::intern(&libname));
         }
         "x86_64-unknown-linux-gnu" | "x86_64-fuchsia" | "aarch64-fuchsia" => {
@@ -817,7 +740,7 @@ pub fn ignored_for_lto(sess: &Session, info: &CrateInfo, cnum: CrateNum) -> bool
         && (info.compiler_builtins == Some(cnum) || info.is_no_builtins.contains(&cnum))
 }
 
-pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
+fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
     fn infer_from(
         sess: &Session,
         linker: Option<PathBuf>,
@@ -905,7 +828,7 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
 /// Returns a boolean indicating whether we should preserve the object files on
 /// the filesystem for their debug information. This is often useful with
 /// split-dwarf like schemes.
-pub fn preserve_objects_for_their_debuginfo(sess: &Session) -> bool {
+fn preserve_objects_for_their_debuginfo(sess: &Session) -> bool {
     // If the objects don't have debuginfo there's nothing to preserve.
     if sess.opts.debuginfo == config::DebugInfo::None {
         return false;
@@ -959,7 +882,7 @@ enum RlibFlavor {
     StaticlibBase,
 }
 
-pub fn print_native_static_libs(sess: &Session, all_native_libs: &[NativeLibrary]) {
+fn print_native_static_libs(sess: &Session, all_native_libs: &[NativeLibrary]) {
     let lib_args: Vec<_> = all_native_libs
         .iter()
         .filter(|l| relevant_lib(sess, l))
@@ -1061,7 +984,7 @@ fn get_crt_libs_path(sess: &Session) -> Option<PathBuf> {
     }
 }
 
-pub fn get_file_path(sess: &Session, name: &str) -> PathBuf {
+fn get_object_file_path(sess: &Session, name: &str) -> PathBuf {
     // prefer system {,dll}crt2.o libs, see get_crt_libs_path comment for more details
     if sess.target.target.llvm_target.contains("windows-gnu") {
         if let Some(compiler_libs_path) = get_crt_libs_path(sess) {
@@ -1085,9 +1008,9 @@ pub fn get_file_path(sess: &Session, name: &str) -> PathBuf {
     PathBuf::from(name)
 }
 
-pub fn exec_linker(
+fn exec_linker(
     sess: &Session,
-    cmd: &mut Command,
+    cmd: &Command,
     out_filename: &Path,
     tmpdir: &Path,
 ) -> io::Result<Output> {
@@ -1233,134 +1156,142 @@ pub fn exec_linker(
     }
 }
 
-fn link_args<'a, B: ArchiveBuilder<'a>>(
+/// Add begin object files defined by the target spec.
+fn add_pre_link_objects(cmd: &mut dyn Linker, sess: &Session, crate_type: config::CrateType) {
+    let pre_link_objects = if crate_type == config::CrateType::Executable {
+        &sess.target.target.options.pre_link_objects_exe
+    } else {
+        &sess.target.target.options.pre_link_objects_dll
+    };
+    for obj in pre_link_objects {
+        cmd.add_object(&get_object_file_path(sess, obj));
+    }
+
+    if crate_type == config::CrateType::Executable && sess.crt_static(Some(crate_type)) {
+        for obj in &sess.target.target.options.pre_link_objects_exe_crt {
+            cmd.add_object(&get_object_file_path(sess, obj));
+        }
+    }
+}
+
+/// Add end object files defined by the target spec.
+fn add_post_link_objects(cmd: &mut dyn Linker, sess: &Session, crate_type: config::CrateType) {
+    for obj in &sess.target.target.options.post_link_objects {
+        cmd.add_object(&get_object_file_path(sess, obj));
+    }
+    if sess.crt_static(Some(crate_type)) {
+        for obj in &sess.target.target.options.post_link_objects_crt {
+            cmd.add_object(&get_object_file_path(sess, obj));
+        }
+    }
+}
+
+/// Add arbitrary "pre-link" args defined by the target spec or from command line.
+/// FIXME: Determine where exactly these args need to be inserted.
+fn add_pre_link_args(
     cmd: &mut dyn Linker,
+    sess: &Session,
     flavor: LinkerFlavor,
-    sess: &'a Session,
     crate_type: config::CrateType,
-    tmpdir: &Path,
-    out_filename: &Path,
-    codegen_results: &CodegenResults,
 ) {
-    // Linker plugins should be specified early in the list of arguments
-    cmd.linker_plugin_lto();
-
-    // The default library location, we need this to find the runtime.
-    // The location of crates will be determined as needed.
-    let lib_path = sess.target_filesearch(PathKind::All).get_lib_path();
+    if let Some(args) = sess.target.target.options.pre_link_args.get(&flavor) {
+        cmd.args(args);
+    }
+    if let Some(args) = sess.target.target.options.pre_link_args_crt.get(&flavor) {
+        if sess.crt_static(Some(crate_type)) {
+            cmd.args(args);
+        }
+    }
+    cmd.args(&sess.opts.debugging_opts.pre_link_args);
+}
 
-    // target descriptor
-    let t = &sess.target.target;
+/// Add arbitrary "user defined" args defined from command line and by `#[link_args]` attributes.
+/// FIXME: Determine where exactly these args need to be inserted.
+fn add_user_defined_link_args(
+    cmd: &mut dyn Linker,
+    sess: &Session,
+    codegen_results: &CodegenResults,
+) {
+    cmd.args(&sess.opts.cg.link_args);
+    cmd.args(&*codegen_results.crate_info.link_args);
+}
 
-    // prefer system mingw-w64 libs, see get_crt_libs_path comment for more details
-    if cfg!(windows) && sess.target.target.llvm_target.contains("windows-gnu") {
-        if let Some(compiler_libs_path) = get_crt_libs_path(sess) {
-            cmd.include_path(&compiler_libs_path);
+/// Add arbitrary "late link" args defined by the target spec.
+/// FIXME: Determine where exactly these args need to be inserted.
+fn add_late_link_args(
+    cmd: &mut dyn Linker,
+    sess: &Session,
+    flavor: LinkerFlavor,
+    crate_type: config::CrateType,
+    codegen_results: &CodegenResults,
+) {
+    if let Some(args) = sess.target.target.options.late_link_args.get(&flavor) {
+        cmd.args(args);
+    }
+    let any_dynamic_crate = crate_type == config::CrateType::Dylib
+        || codegen_results.crate_info.dependency_formats.iter().any(|(ty, list)| {
+            *ty == crate_type && list.iter().any(|&linkage| linkage == Linkage::Dynamic)
+        });
+    if any_dynamic_crate {
+        if let Some(args) = sess.target.target.options.late_link_args_dynamic.get(&flavor) {
+            cmd.args(args);
+        }
+    } else {
+        if let Some(args) = sess.target.target.options.late_link_args_static.get(&flavor) {
+            cmd.args(args);
         }
     }
+}
 
-    cmd.include_path(&fix_windows_verbatim_for_gcc(&lib_path));
+/// Add arbitrary "post-link" args defined by the target spec.
+/// FIXME: Determine where exactly these args need to be inserted.
+fn add_post_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
+    if let Some(args) = sess.target.target.options.post_link_args.get(&flavor) {
+        cmd.args(args);
+    }
+}
 
+/// Add object files containing code from the current crate.
+fn add_local_crate_regular_objects(cmd: &mut dyn Linker, codegen_results: &CodegenResults) {
     for obj in codegen_results.modules.iter().filter_map(|m| m.object.as_ref()) {
         cmd.add_object(obj);
     }
-    cmd.output_filename(out_filename);
+}
 
-    if crate_type == config::CrateType::Executable && sess.target.target.options.is_like_windows {
-        if let Some(ref s) = codegen_results.windows_subsystem {
-            cmd.subsystem(s);
-        }
+/// Add object files for allocator code linked once for the whole crate tree.
+fn add_local_crate_allocator_objects(cmd: &mut dyn Linker, codegen_results: &CodegenResults) {
+    if let Some(obj) = codegen_results.allocator_module.as_ref().and_then(|m| m.object.as_ref()) {
+        cmd.add_object(obj);
     }
+}
 
-    // If we're building something like a dynamic library then some platforms
-    // need to make sure that all symbols are exported correctly from the
-    // dynamic library.
-    cmd.export_symbols(tmpdir, crate_type);
-
+/// Add object files containing metadata for the current crate.
+fn add_local_crate_metadata_objects(
+    cmd: &mut dyn Linker,
+    crate_type: config::CrateType,
+    codegen_results: &CodegenResults,
+) {
     // When linking a dynamic library, we put the metadata into a section of the
     // executable. This metadata is in a separate object file from the main
     // object file, so we link that in here.
     if crate_type == config::CrateType::Dylib || crate_type == config::CrateType::ProcMacro {
-        let obj = codegen_results.metadata_module.as_ref().and_then(|m| m.object.as_ref());
-        if let Some(obj) = obj {
+        if let Some(obj) = codegen_results.metadata_module.as_ref().and_then(|m| m.object.as_ref())
+        {
             cmd.add_object(obj);
         }
     }
+}
 
-    let obj = codegen_results.allocator_module.as_ref().and_then(|m| m.object.as_ref());
-    if let Some(obj) = obj {
-        cmd.add_object(obj);
-    }
-
-    // Try to strip as much out of the generated object by removing unused
-    // sections if possible. See more comments in linker.rs
-    if !sess.opts.cg.link_dead_code {
-        let keep_metadata = crate_type == config::CrateType::Dylib;
-        cmd.gc_sections(keep_metadata);
-    }
-
-    let attr_link_args = codegen_results.crate_info.link_args.iter();
-    let user_link_args: Vec<_> =
-        sess.opts.cg.link_args.iter().chain(attr_link_args).cloned().collect();
-
-    if crate_type == config::CrateType::Executable {
-        let mut position_independent_executable = false;
-
-        if t.options.position_independent_executables {
-            if is_pic(sess)
-                && !sess.crt_static(Some(crate_type))
-                && !user_link_args.iter().any(|x| x == "-static")
-            {
-                position_independent_executable = true;
-            }
-        }
-
-        if position_independent_executable {
-            cmd.position_independent_executable();
-        } else {
-            // recent versions of gcc can be configured to generate position
-            // independent executables by default. We have to pass -no-pie to
-            // explicitly turn that off. Not applicable to ld.
-            if sess.target.target.options.linker_is_gnu && flavor != LinkerFlavor::Ld {
-                cmd.no_position_independent_executable();
-            }
-        }
-    }
-
-    let relro_level = match sess.opts.debugging_opts.relro_level {
-        Some(level) => level,
-        None => t.options.relro_level,
-    };
-    match relro_level {
-        RelroLevel::Full => {
-            cmd.full_relro();
-        }
-        RelroLevel::Partial => {
-            cmd.partial_relro();
-        }
-        RelroLevel::Off => {
-            cmd.no_relro();
-        }
-        RelroLevel::None => {}
-    }
-
-    // Pass optimization flags down to the linker.
-    cmd.optimize();
-
-    // Pass debuginfo flags down to the linker.
-    cmd.debuginfo();
-
-    // We want to, by default, prevent the compiler from accidentally leaking in
-    // any system libraries, so we may explicitly ask linkers to not link to any
-    // libraries by default. Note that this does not happen for windows because
-    // windows pulls in some large number of libraries and I couldn't quite
-    // figure out which subset we wanted.
-    //
-    // This is all naturally configurable via the standard methods as well.
-    if !sess.opts.cg.default_linker_libraries.unwrap_or(false) && t.options.no_default_libraries {
-        cmd.no_default_libraries();
-    }
-
+/// Link native libraries corresponding to the current crate and all libraries corresponding to
+/// all its dependency crates.
+/// FIXME: Consider combining this with the functions above adding object files for the local crate.
+fn link_local_crate_native_libs_and_dependent_crate_libs<'a, B: ArchiveBuilder<'a>>(
+    cmd: &mut dyn Linker,
+    sess: &'a Session,
+    crate_type: config::CrateType,
+    codegen_results: &CodegenResults,
+    tmpdir: &Path,
+) {
     // Take careful note of the ordering of the arguments we pass to the linker
     // here. Linkers will assume that things on the left depend on things to the
     // right. Things on the right cannot depend on things on the left. This is
@@ -1398,22 +1329,73 @@ fn link_args<'a, B: ArchiveBuilder<'a>>(
     if sess.opts.debugging_opts.link_native_libraries.unwrap_or(true) {
         add_upstream_native_libraries(cmd, sess, codegen_results, crate_type);
     }
-    // Tell the linker what we're doing.
+}
+
+/// Add sysroot and other globally set directories to the directory search list.
+fn add_library_search_dirs(cmd: &mut dyn Linker, sess: &Session) {
+    // Prefer system mingw-w64 libs, see get_crt_libs_path comment for more details.
+    if cfg!(windows) && sess.target.target.llvm_target.contains("windows-gnu") {
+        if let Some(compiler_libs_path) = get_crt_libs_path(sess) {
+            cmd.include_path(&compiler_libs_path);
+        }
+    }
+
+    // The default library location, we need this to find the runtime.
+    // The location of crates will be determined as needed.
+    let lib_path = sess.target_filesearch(PathKind::All).get_lib_path();
+    cmd.include_path(&fix_windows_verbatim_for_gcc(&lib_path));
+}
+
+/// Add options requesting executables to be position-independent or not position-independent.
+fn add_position_independent_executable_args(
+    cmd: &mut dyn Linker,
+    sess: &Session,
+    flavor: LinkerFlavor,
+    crate_type: config::CrateType,
+    codegen_results: &CodegenResults,
+) {
     if crate_type != config::CrateType::Executable {
-        cmd.build_dylib(out_filename);
+        return;
     }
-    if crate_type == config::CrateType::Executable && sess.crt_static(Some(crate_type)) {
-        cmd.build_static_executable();
+
+    if sess.target.target.options.position_independent_executables {
+        let attr_link_args = &*codegen_results.crate_info.link_args;
+        let mut user_defined_link_args = sess.opts.cg.link_args.iter().chain(attr_link_args);
+        if is_pic(sess)
+            && !sess.crt_static(Some(crate_type))
+            && !user_defined_link_args.any(|x| x == "-static")
+        {
+            cmd.position_independent_executable();
+            return;
+        }
     }
 
-    if sess.opts.cg.profile_generate.enabled() {
-        cmd.pgo_gen();
+    // Recent versions of gcc can be configured to generate position
+    // independent executables by default. We have to pass -no-pie to
+    // explicitly turn that off. Not applicable to ld.
+    if sess.target.target.options.linker_is_gnu && flavor != LinkerFlavor::Ld {
+        cmd.no_position_independent_executable();
     }
+}
 
-    if sess.opts.debugging_opts.control_flow_guard != CFGuard::Disabled {
-        cmd.control_flow_guard();
+/// Add options making relocation sections in the produced ELF files read-only
+/// and suppressing lazy binding.
+fn add_relro_args(cmd: &mut dyn Linker, sess: &Session) {
+    match sess.opts.debugging_opts.relro_level.unwrap_or(sess.target.target.options.relro_level) {
+        RelroLevel::Full => cmd.full_relro(),
+        RelroLevel::Partial => cmd.partial_relro(),
+        RelroLevel::Off => cmd.no_relro(),
+        RelroLevel::None => {}
     }
+}
 
+/// Add library search paths used at runtime by dynamic linkers.
+fn add_rpath_args(
+    cmd: &mut dyn Linker,
+    sess: &Session,
+    codegen_results: &CodegenResults,
+    out_filename: &Path,
+) {
     // FIXME (#2397): At some point we want to rpath our guesses as to
     // where extern libraries might live, based on the
     // addl_lib_search_paths
@@ -1437,10 +1419,177 @@ fn link_args<'a, B: ArchiveBuilder<'a>>(
         };
         cmd.args(&rpath::get_rpath_flags(&mut rpath_config));
     }
+}
+
+/// Produce the linker command line containing linker path and arguments.
+/// `NO-OPT-OUT` marks the arguments that cannot be removed from the command line
+/// by the user without creating a custom target specification.
+/// `OBJECT-FILES` specify whether the arguments can add object files.
+/// `CUSTOMIZATION-POINT` means that arbitrary arguments defined by the user
+/// or by the target spec can be inserted here.
+/// `AUDIT-ORDER` - need to figure out whether the option is order-dependent or not.
+fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
+    path: &Path,
+    flavor: LinkerFlavor,
+    sess: &'a Session,
+    crate_type: config::CrateType,
+    tmpdir: &Path,
+    out_filename: &Path,
+    codegen_results: &CodegenResults,
+    target_cpu: &str,
+) -> Command {
+    let base_cmd = get_linker(sess, path, flavor);
+    // FIXME: Move `/LIBPATH` addition for uwp targets from the linker construction
+    // to the linker args construction.
+    assert!(base_cmd.get_args().is_empty() || sess.target.target.target_vendor == "uwp");
+    let cmd = &mut *codegen_results.linker_info.to_linker(base_cmd, &sess, flavor, target_cpu);
+
+    // NO-OPT-OUT, OBJECT-FILES-MAYBE, CUSTOMIZATION-POINT
+    add_pre_link_args(cmd, sess, flavor, crate_type);
+
+    // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
+    if sess.target.target.options.is_like_fuchsia {
+        let prefix = match sess.opts.debugging_opts.sanitizer {
+            Some(Sanitizer::Address) => "asan/",
+            _ => "",
+        };
+        cmd.arg(format!("--dynamic-linker={}ld.so.1", prefix));
+    }
+
+    // NO-OPT-OUT, OBJECT-FILES-YES
+    add_pre_link_objects(cmd, sess, crate_type);
+
+    // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
+    if sess.target.target.options.is_like_emscripten {
+        cmd.arg("-s");
+        cmd.arg(if sess.panic_strategy() == PanicStrategy::Abort {
+            "DISABLE_EXCEPTION_CATCHING=1"
+        } else {
+            "DISABLE_EXCEPTION_CATCHING=0"
+        });
+    }
+
+    // OBJECT-FILES-YES, AUDIT-ORDER
+    link_sanitizer_runtime(sess, crate_type, cmd);
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    // Linker plugins should be specified early in the list of arguments
+    // FIXME: How "early" exactly?
+    cmd.linker_plugin_lto();
+
+    // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
+    // FIXME: Order-dependent, at least relatively to other args adding searh directories.
+    add_library_search_dirs(cmd, sess);
+
+    // OBJECT-FILES-YES
+    add_local_crate_regular_objects(cmd, codegen_results);
+
+    // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
+    cmd.output_filename(out_filename);
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    if crate_type == config::CrateType::Executable && sess.target.target.options.is_like_windows {
+        if let Some(ref s) = codegen_results.windows_subsystem {
+            cmd.subsystem(s);
+        }
+    }
+
+    // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
+    // If we're building something like a dynamic library then some platforms
+    // need to make sure that all symbols are exported correctly from the
+    // dynamic library.
+    cmd.export_symbols(tmpdir, crate_type);
+
+    // OBJECT-FILES-YES
+    add_local_crate_metadata_objects(cmd, crate_type, codegen_results);
+
+    // OBJECT-FILES-YES
+    add_local_crate_allocator_objects(cmd, codegen_results);
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    // FIXME: Order dependent, applies to the following objects. Where should it be placed?
+    // Try to strip as much out of the generated object by removing unused
+    // sections if possible. See more comments in linker.rs
+    if !sess.opts.cg.link_dead_code {
+        let keep_metadata = crate_type == config::CrateType::Dylib;
+        cmd.gc_sections(keep_metadata);
+    }
+
+    // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
+    add_position_independent_executable_args(cmd, sess, flavor, crate_type, codegen_results);
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    add_relro_args(cmd, sess);
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    // Pass optimization flags down to the linker.
+    cmd.optimize();
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    // Pass debuginfo flags down to the linker.
+    cmd.debuginfo();
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    // We want to, by default, prevent the compiler from accidentally leaking in
+    // any system libraries, so we may explicitly ask linkers to not link to any
+    // libraries by default. Note that this does not happen for windows because
+    // windows pulls in some large number of libraries and I couldn't quite
+    // figure out which subset we wanted.
+    //
+    // This is all naturally configurable via the standard methods as well.
+    if !sess.opts.cg.default_linker_libraries.unwrap_or(false)
+        && sess.target.target.options.no_default_libraries
+    {
+        cmd.no_default_libraries();
+    }
+
+    // OBJECT-FILES-YES
+    link_local_crate_native_libs_and_dependent_crate_libs::<B>(
+        cmd,
+        sess,
+        crate_type,
+        codegen_results,
+        tmpdir,
+    );
+
+    // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
+    // Tell the linker what we're doing.
+    if crate_type != config::CrateType::Executable {
+        cmd.build_dylib(out_filename);
+    }
+    if crate_type == config::CrateType::Executable && sess.crt_static(Some(crate_type)) {
+        cmd.build_static_executable();
+    }
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    if sess.opts.cg.profile_generate.enabled() {
+        cmd.pgo_gen();
+    }
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    if sess.opts.debugging_opts.control_flow_guard != CFGuard::Disabled {
+        cmd.control_flow_guard();
+    }
+
+    // OBJECT-FILES-NO, AUDIT-ORDER
+    add_rpath_args(cmd, sess, codegen_results, out_filename);
+
+    // OBJECT-FILES-MAYBE, CUSTOMIZATION-POINT
+    add_user_defined_link_args(cmd, sess, codegen_results);
+
+    // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
+    cmd.finalize();
+
+    // NO-OPT-OUT, OBJECT-FILES-MAYBE, CUSTOMIZATION-POINT
+    add_late_link_args(cmd, sess, flavor, crate_type, codegen_results);
+
+    // NO-OPT-OUT, OBJECT-FILES-YES
+    add_post_link_objects(cmd, sess, crate_type);
+
+    // NO-OPT-OUT, OBJECT-FILES-MAYBE, CUSTOMIZATION-POINT
+    add_post_link_args(cmd, sess, flavor);
 
-    // Finally add all the linker arguments provided on the command line along
-    // with any #[link_args] attributes found inside the crate
-    cmd.args(&user_link_args);
+    cmd.take_cmd()
 }
 
 // # Native library linking
@@ -1454,7 +1603,7 @@ fn link_args<'a, B: ArchiveBuilder<'a>>(
 // Also note that the native libraries linked here are only the ones located
 // in the current crate. Upstream crates with native library dependencies
 // may have their native library pulled in above.
-pub fn add_local_native_libraries(
+fn add_local_native_libraries(
     cmd: &mut dyn Linker,
     sess: &Session,
     codegen_results: &CodegenResults,
@@ -1784,7 +1933,7 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>(
 // generic function calls a native function, then the generic function must
 // be instantiated in the target crate, meaning that the native symbol must
 // also be resolved in the target crate.
-pub fn add_upstream_native_libraries(
+fn add_upstream_native_libraries(
     cmd: &mut dyn Linker,
     sess: &Session,
     codegen_results: &CodegenResults,
@@ -1841,14 +1990,14 @@ pub fn add_upstream_native_libraries(
     }
 }
 
-pub fn relevant_lib(sess: &Session, lib: &NativeLibrary) -> bool {
+fn relevant_lib(sess: &Session, lib: &NativeLibrary) -> bool {
     match lib.cfg {
         Some(ref cfg) => rustc_attr::cfg_matches(cfg, &sess.parse_sess, None),
         None => true,
     }
 }
 
-pub fn are_upstream_rust_objects_already_included(sess: &Session) -> bool {
+fn are_upstream_rust_objects_already_included(sess: &Session) -> bool {
     match sess.lto() {
         config::Lto::Fat => true,
         config::Lto::Thin => {
diff --git a/src/librustc_codegen_ssa/back/linker.rs b/src/librustc_codegen_ssa/back/linker.rs
index c0c533524b0..0baa37ae9f1 100644
--- a/src/librustc_codegen_ssa/back/linker.rs
+++ b/src/librustc_codegen_ssa/back/linker.rs
@@ -6,6 +6,7 @@ use std::ffi::{OsStr, OsString};
 use std::fs::{self, File};
 use std::io::prelude::*;
 use std::io::{self, BufWriter};
+use std::mem;
 use std::path::{Path, PathBuf};
 
 use rustc_data_structures::fx::FxHashMap;
@@ -87,6 +88,7 @@ impl LinkerInfo {
 /// used to dispatch on whether a GNU-like linker (generally `ld.exe`) or an
 /// MSVC linker (e.g., `link.exe`) is being used.
 pub trait Linker {
+    fn cmd(&mut self) -> &mut Command;
     fn link_dylib(&mut self, lib: Symbol);
     fn link_rust_dylib(&mut self, lib: Symbol, path: &Path);
     fn link_framework(&mut self, framework: Symbol);
@@ -111,14 +113,26 @@ pub trait Linker {
     fn no_default_libraries(&mut self);
     fn build_dylib(&mut self, out_filename: &Path);
     fn build_static_executable(&mut self);
-    fn args(&mut self, args: &[String]);
     fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType);
     fn subsystem(&mut self, subsystem: &str);
     fn group_start(&mut self);
     fn group_end(&mut self);
     fn linker_plugin_lto(&mut self);
-    // Should have been finalize(self), but we don't support self-by-value on trait objects (yet?).
-    fn finalize(&mut self) -> Command;
+    fn finalize(&mut self);
+}
+
+impl dyn Linker + '_ {
+    pub fn arg(&mut self, arg: impl AsRef<OsStr>) {
+        self.cmd().arg(arg);
+    }
+
+    pub fn args(&mut self, args: impl IntoIterator<Item: AsRef<OsStr>>) {
+        self.cmd().args(args);
+    }
+
+    pub fn take_cmd(&mut self) -> Command {
+        mem::replace(self.cmd(), Command::new(""))
+    }
 }
 
 pub struct GccLinker<'a> {
@@ -208,6 +222,9 @@ impl<'a> GccLinker<'a> {
 }
 
 impl<'a> Linker for GccLinker<'a> {
+    fn cmd(&mut self) -> &mut Command {
+        &mut self.cmd
+    }
     fn link_dylib(&mut self, lib: Symbol) {
         self.hint_dynamic();
         self.cmd.arg(format!("-l{}", lib));
@@ -251,9 +268,6 @@ impl<'a> Linker for GccLinker<'a> {
     fn build_static_executable(&mut self) {
         self.cmd.arg("-static");
     }
-    fn args(&mut self, args: &[String]) {
-        self.cmd.args(args);
-    }
 
     fn link_rust_dylib(&mut self, lib: Symbol, _path: &Path) {
         self.hint_dynamic();
@@ -505,10 +519,8 @@ impl<'a> Linker for GccLinker<'a> {
         self.linker_arg(&subsystem);
     }
 
-    fn finalize(&mut self) -> Command {
+    fn finalize(&mut self) {
         self.hint_dynamic(); // Reset to default before returning the composed command line.
-
-        ::std::mem::replace(&mut self.cmd, Command::new(""))
     }
 
     fn group_start(&mut self) {
@@ -545,15 +557,15 @@ pub struct MsvcLinker<'a> {
 }
 
 impl<'a> Linker for MsvcLinker<'a> {
+    fn cmd(&mut self) -> &mut Command {
+        &mut self.cmd
+    }
     fn link_rlib(&mut self, lib: &Path) {
         self.cmd.arg(lib);
     }
     fn add_object(&mut self, path: &Path) {
         self.cmd.arg(path);
     }
-    fn args(&mut self, args: &[String]) {
-        self.cmd.args(args);
-    }
 
     fn build_dylib(&mut self, out_filename: &Path) {
         self.cmd.arg("/DLL");
@@ -758,9 +770,7 @@ impl<'a> Linker for MsvcLinker<'a> {
         }
     }
 
-    fn finalize(&mut self) -> Command {
-        ::std::mem::replace(&mut self.cmd, Command::new(""))
-    }
+    fn finalize(&mut self) {}
 
     // MSVC doesn't need group indicators
     fn group_start(&mut self) {}
@@ -778,6 +788,9 @@ pub struct EmLinker<'a> {
 }
 
 impl<'a> Linker for EmLinker<'a> {
+    fn cmd(&mut self) -> &mut Command {
+        &mut self.cmd
+    }
     fn include_path(&mut self, path: &Path) {
         self.cmd.arg("-L").arg(path);
     }
@@ -837,10 +850,6 @@ impl<'a> Linker for EmLinker<'a> {
         // noop
     }
 
-    fn args(&mut self, args: &[String]) {
-        self.cmd.args(args);
-    }
-
     fn framework_path(&mut self, _path: &Path) {
         bug!("frameworks are not supported on Emscripten")
     }
@@ -928,9 +937,7 @@ impl<'a> Linker for EmLinker<'a> {
         // noop
     }
 
-    fn finalize(&mut self) -> Command {
-        ::std::mem::replace(&mut self.cmd, Command::new(""))
-    }
+    fn finalize(&mut self) {}
 
     // Appears not necessary on Emscripten
     fn group_start(&mut self) {}
@@ -992,6 +999,10 @@ impl<'a> WasmLd<'a> {
 }
 
 impl<'a> Linker for WasmLd<'a> {
+    fn cmd(&mut self) -> &mut Command {
+        &mut self.cmd
+    }
+
     fn link_dylib(&mut self, lib: Symbol) {
         self.cmd.arg("-l").sym_arg(lib);
     }
@@ -1030,10 +1041,6 @@ impl<'a> Linker for WasmLd<'a> {
 
     fn build_static_executable(&mut self) {}
 
-    fn args(&mut self, args: &[String]) {
-        self.cmd.args(args);
-    }
-
     fn link_rust_dylib(&mut self, lib: Symbol, _path: &Path) {
         self.cmd.arg("-l").sym_arg(lib);
     }
@@ -1098,9 +1105,7 @@ impl<'a> Linker for WasmLd<'a> {
 
     fn no_position_independent_executable(&mut self) {}
 
-    fn finalize(&mut self) -> Command {
-        ::std::mem::replace(&mut self.cmd, Command::new(""))
-    }
+    fn finalize(&mut self) {}
 
     // Not needed for now with LLD
     fn group_start(&mut self) {}
@@ -1162,6 +1167,10 @@ pub struct PtxLinker<'a> {
 }
 
 impl<'a> Linker for PtxLinker<'a> {
+    fn cmd(&mut self) -> &mut Command {
+        &mut self.cmd
+    }
+
     fn link_rlib(&mut self, path: &Path) {
         self.cmd.arg("--rlib").arg(path);
     }
@@ -1182,10 +1191,6 @@ impl<'a> Linker for PtxLinker<'a> {
         self.cmd.arg("--bitcode").arg(path);
     }
 
-    fn args(&mut self, args: &[String]) {
-        self.cmd.args(args);
-    }
-
     fn optimize(&mut self) {
         match self.sess.lto() {
             Lto::Thin | Lto::Fat | Lto::ThinLocal => {
@@ -1200,14 +1205,12 @@ impl<'a> Linker for PtxLinker<'a> {
         self.cmd.arg("-o").arg(path);
     }
 
-    fn finalize(&mut self) -> Command {
+    fn finalize(&mut self) {
         // Provide the linker with fallback to internal `target-cpu`.
         self.cmd.arg("--fallback-arch").arg(match self.sess.opts.cg.target_cpu {
             Some(ref s) => s,
             None => &self.sess.target.target.options.cpu,
         });
-
-        ::std::mem::replace(&mut self.cmd, Command::new(""))
     }
 
     fn link_dylib(&mut self, _lib: Symbol) {
diff --git a/src/librustc_error_codes/error_codes.rs b/src/librustc_error_codes/error_codes.rs
index 6e690655f60..8d9982131c3 100644
--- a/src/librustc_error_codes/error_codes.rs
+++ b/src/librustc_error_codes/error_codes.rs
@@ -416,7 +416,6 @@ E0734: include_str!("./error_codes/E0734.md"),
 E0735: include_str!("./error_codes/E0735.md"),
 E0736: include_str!("./error_codes/E0736.md"),
 E0737: include_str!("./error_codes/E0737.md"),
-E0738: include_str!("./error_codes/E0738.md"),
 E0739: include_str!("./error_codes/E0739.md"),
 E0740: include_str!("./error_codes/E0740.md"),
 E0741: include_str!("./error_codes/E0741.md"),
@@ -614,4 +613,5 @@ E0751: include_str!("./error_codes/E0751.md"),
     E0722, // Malformed `#[optimize]` attribute
     E0724, // `#[ffi_returns_twice]` is only allowed in foreign functions
     E0726, // non-explicit (not `'_`) elided lifetime in unsupported position
+//  E0738, // Removed; errored on `#[track_caller] fn`s in `extern "Rust" { ... }`.
 }
diff --git a/src/librustc_error_codes/error_codes/E0738.md b/src/librustc_error_codes/error_codes/E0738.md
deleted file mode 100644
index 8f31b701e49..00000000000
--- a/src/librustc_error_codes/error_codes/E0738.md
+++ /dev/null
@@ -1,11 +0,0 @@
-`#[track_caller]` cannot be used to annotate foreign functions.
-
-Erroneous example:
-
-```compile_fail,E0738
-#![feature(track_caller)]
-extern "Rust" {
-    #[track_caller]
-    fn bar();
-}
-```
diff --git a/src/librustc_mir_build/build/mod.rs b/src/librustc_mir_build/build/mod.rs
index 04cb509d44e..6e1a4ecf47a 100644
--- a/src/librustc_mir_build/build/mod.rs
+++ b/src/librustc_mir_build/build/mod.rs
@@ -178,11 +178,11 @@ fn mir_build(tcx: TyCtxt<'_>, def_id: DefId) -> BodyAndCache<'_> {
             build::construct_const(cx, body_id, return_ty, return_ty_span)
         };
 
-        lints::check(tcx, &body, def_id);
-
         let mut body = BodyAndCache::new(body);
         body.ensure_predecessors();
 
+        lints::check(tcx, &body.unwrap_read_only(), def_id);
+
         // The borrow checker will replace all the regions here with its own
         // inference variables. There's no point having non-erased regions here.
         // The exception is `body.user_type_annotations`, which is used unmodified
diff --git a/src/librustc_mir_build/lints.rs b/src/librustc_mir_build/lints.rs
index b1628ec58c3..a7370c36f0b 100644
--- a/src/librustc_mir_build/lints.rs
+++ b/src/librustc_mir_build/lints.rs
@@ -1,138 +1,175 @@
 use rustc_hir::def_id::DefId;
 use rustc_hir::intravisit::FnKind;
 use rustc_index::bit_set::BitSet;
+use rustc_index::vec::IndexVec;
 use rustc_middle::hir::map::blocks::FnLikeNode;
-use rustc_middle::mir::{self, Body, TerminatorKind};
+use rustc_middle::mir::{BasicBlock, Body, ReadOnlyBodyAndCache, TerminatorKind, START_BLOCK};
 use rustc_middle::ty::subst::InternalSubsts;
 use rustc_middle::ty::{self, AssocItem, AssocItemContainer, Instance, TyCtxt};
 use rustc_session::lint::builtin::UNCONDITIONAL_RECURSION;
+use std::collections::VecDeque;
 
-crate fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId) {
+crate fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &ReadOnlyBodyAndCache<'_, 'tcx>, def_id: DefId) {
     let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
 
     if let Some(fn_like_node) = FnLikeNode::from_node(tcx.hir().get(hir_id)) {
-        check_fn_for_unconditional_recursion(tcx, fn_like_node.kind(), body, def_id);
+        if let FnKind::Closure(_) = fn_like_node.kind() {
+            // closures can't recur, so they don't matter.
+            return;
+        }
+
+        check_fn_for_unconditional_recursion(tcx, body, def_id);
     }
 }
 
 fn check_fn_for_unconditional_recursion<'tcx>(
     tcx: TyCtxt<'tcx>,
-    fn_kind: FnKind<'_>,
-    body: &Body<'tcx>,
+    body: &ReadOnlyBodyAndCache<'_, 'tcx>,
     def_id: DefId,
 ) {
-    if let FnKind::Closure(_) = fn_kind {
-        // closures can't recur, so they don't matter.
-        return;
-    }
+    let self_calls = find_blocks_calling_self(tcx, &body, def_id);
 
-    //FIXME(#54444) rewrite this lint to use the dataflow framework
-
-    // Walk through this function (say `f`) looking to see if
-    // every possible path references itself, i.e., the function is
-    // called recursively unconditionally. This is done by trying
-    // to find a path from the entry node to the exit node that
-    // *doesn't* call `f` by traversing from the entry while
-    // pretending that calls of `f` are sinks (i.e., ignoring any
-    // exit edges from them).
-    //
-    // NB. this has an edge case with non-returning statements,
-    // like `loop {}` or `panic!()`: control flow never reaches
-    // the exit node through these, so one can have a function
-    // that never actually calls itself but is still picked up by
-    // this lint:
-    //
-    //     fn f(cond: bool) {
-    //         if !cond { panic!() } // could come from `assert!(cond)`
-    //         f(false)
-    //     }
-    //
-    // In general, functions of that form may be able to call
-    // itself a finite number of times and then diverge. The lint
-    // considers this to be an error for two reasons, (a) it is
-    // easier to implement, and (b) it seems rare to actually want
-    // to have behaviour like the above, rather than
-    // e.g., accidentally recursing after an assert.
-
-    let basic_blocks = body.basic_blocks();
-    let mut reachable_without_self_call_queue = vec![mir::START_BLOCK];
-    let mut reached_exit_without_self_call = false;
-    let mut self_call_locations = vec![];
-    let mut visited = BitSet::new_empty(basic_blocks.len());
+    // Stores a list of `Span`s for every basic block. Those are the spans of self-calls where we
+    // know that one of them will definitely be reached. If the list is empty, the block either
+    // wasn't processed yet or will not always go to a self-call.
+    let mut results = IndexVec::from_elem_n(vec![], body.basic_blocks().len());
 
-    let param_env = tcx.param_env(def_id);
-    let trait_substs_count = match tcx.opt_associated_item(def_id) {
-        Some(AssocItem { container: AssocItemContainer::TraitContainer(trait_def_id), .. }) => {
-            tcx.generics_of(trait_def_id).count()
-        }
-        _ => 0,
-    };
-    let caller_substs = &InternalSubsts::identity_for_item(tcx, def_id)[..trait_substs_count];
+    // We start the analysis at the self calls and work backwards.
+    let mut queue: VecDeque<_> = self_calls.iter().collect();
 
-    while let Some(bb) = reachable_without_self_call_queue.pop() {
-        if !visited.insert(bb) {
-            //already done
+    while let Some(bb) = queue.pop_front() {
+        if !results[bb].is_empty() {
+            // Already propagated.
             continue;
         }
 
-        let block = &basic_blocks[bb];
-
-        if let Some(ref terminator) = block.terminator {
-            match terminator.kind {
-                TerminatorKind::Call { ref func, .. } => {
-                    let func_ty = func.ty(body, tcx);
-
-                    if let ty::FnDef(fn_def_id, substs) = func_ty.kind {
-                        let (call_fn_id, call_substs) = if let Some(instance) =
-                            Instance::resolve(tcx, param_env, fn_def_id, substs)
-                        {
-                            (instance.def_id(), instance.substs)
-                        } else {
-                            (fn_def_id, substs)
-                        };
-
-                        let is_self_call = call_fn_id == def_id
-                            && &call_substs[..caller_substs.len()] == caller_substs;
-
-                        if is_self_call {
-                            self_call_locations.push(terminator.source_info);
-
-                            //this is a self call so we shouldn't explore
-                            //further down this path
-                            continue;
-                        }
-                    }
+        let locations = if self_calls.contains(bb) {
+            // `bb` *is* a self-call.
+            // We don't look at successors here because they are irrelevant here and we don't want
+            // to lint them (eg. `f(); f()` should only lint the first call).
+            vec![bb]
+        } else {
+            // If *all* successors of `bb` lead to a self-call, emit notes at all of their
+            // locations.
+
+            // Determine all "relevant" successors. We ignore successors only reached via unwinding.
+            let terminator = body[bb].terminator();
+            let relevant_successors = match &terminator.kind {
+                TerminatorKind::Call { destination: None, .. }
+                | TerminatorKind::Yield { .. }
+                | TerminatorKind::GeneratorDrop => None.into_iter().chain(&[]),
+                TerminatorKind::SwitchInt { targets, .. } => None.into_iter().chain(targets),
+                TerminatorKind::Goto { target }
+                | TerminatorKind::Drop { target, .. }
+                | TerminatorKind::DropAndReplace { target, .. }
+                | TerminatorKind::Assert { target, .. }
+                | TerminatorKind::FalseEdges { real_target: target, .. }
+                | TerminatorKind::FalseUnwind { real_target: target, .. }
+                | TerminatorKind::Call { destination: Some((_, target)), .. } => {
+                    Some(target).into_iter().chain(&[])
                 }
-                TerminatorKind::Abort | TerminatorKind::Return => {
-                    //found a path!
-                    reached_exit_without_self_call = true;
-                    break;
+                TerminatorKind::Resume
+                | TerminatorKind::Abort
+                | TerminatorKind::Return
+                | TerminatorKind::Unreachable => {
+                    // We propagate backwards, so these should never be encountered here.
+                    unreachable!("unexpected terminator {:?}", terminator.kind)
                 }
-                _ => {}
+            };
+
+            // If all our successors are known to lead to self-calls, then we do too.
+            let all_are_self_calls =
+                relevant_successors.clone().all(|&succ| !results[succ].is_empty());
+
+            if all_are_self_calls {
+                // We'll definitely lead to a self-call. Merge all call locations of the successors
+                // for linting them later.
+                relevant_successors.flat_map(|&succ| results[succ].iter().copied()).collect()
+            } else {
+                // At least 1 successor does not always lead to a self-call, so we also don't.
+                vec![]
             }
+        };
 
-            for successor in terminator.successors() {
-                reachable_without_self_call_queue.push(*successor);
-            }
+        if !locations.is_empty() {
+            // This is a newly confirmed-to-always-reach-self-call block.
+            results[bb] = locations;
+
+            // Propagate backwards through the CFG.
+            debug!("propagate loc={:?} in {:?} -> {:?}", results[bb], bb, body.predecessors()[bb]);
+            queue.extend(body.predecessors()[bb].iter().copied());
         }
     }
 
-    // Check the number of self calls because a function that
-    // doesn't return (e.g., calls a `-> !` function or `loop { /*
-    // no break */ }`) shouldn't be linted unless it actually
-    // recurs.
-    if !reached_exit_without_self_call && !self_call_locations.is_empty() {
+    debug!("unconditional recursion results: {:?}", results);
+
+    let self_call_locations = &mut results[START_BLOCK];
+    self_call_locations.sort();
+    self_call_locations.dedup();
+
+    if !self_call_locations.is_empty() {
         let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
         let sp = tcx.sess.source_map().guess_head_span(tcx.hir().span(hir_id));
         tcx.struct_span_lint_hir(UNCONDITIONAL_RECURSION, hir_id, sp, |lint| {
             let mut db = lint.build("function cannot return without recursing");
             db.span_label(sp, "cannot return without recursing");
             // offer some help to the programmer.
-            for location in &self_call_locations {
-                db.span_label(location.span, "recursive call site");
+            for bb in self_call_locations {
+                let span = body.basic_blocks()[*bb].terminator().source_info.span;
+                db.span_label(span, "recursive call site");
             }
             db.help("a `loop` may express intention better if this is on purpose");
             db.emit();
         });
     }
 }
+
+/// Finds blocks with `Call` terminators that would end up calling back into the same method.
+fn find_blocks_calling_self<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    body: &Body<'tcx>,
+    def_id: DefId,
+) -> BitSet<BasicBlock> {
+    let param_env = tcx.param_env(def_id);
+
+    // If this is trait/impl method, extract the trait's substs.
+    let trait_substs_count = match tcx.opt_associated_item(def_id) {
+        Some(AssocItem { container: AssocItemContainer::TraitContainer(trait_def_id), .. }) => {
+            tcx.generics_of(trait_def_id).count()
+        }
+        _ => 0,
+    };
+    let trait_substs = &InternalSubsts::identity_for_item(tcx, def_id)[..trait_substs_count];
+
+    let mut self_calls = BitSet::new_empty(body.basic_blocks().len());
+
+    for (bb, data) in body.basic_blocks().iter_enumerated() {
+        if let TerminatorKind::Call { func, .. } = &data.terminator().kind {
+            let func_ty = func.ty(body, tcx);
+
+            if let ty::FnDef(fn_def_id, substs) = func_ty.kind {
+                let (call_fn_id, call_substs) =
+                    if let Some(instance) = Instance::resolve(tcx, param_env, fn_def_id, substs) {
+                        (instance.def_id(), instance.substs)
+                    } else {
+                        (fn_def_id, substs)
+                    };
+
+                // FIXME(#57965): Make this work across function boundaries
+
+                // If this is a trait fn, the substs on the trait have to match, or we might be
+                // calling into an entirely different method (for example, a call from the default
+                // method in the trait to `<A as Trait<B>>::method`, where `A` and/or `B` are
+                // specific types).
+                let is_self_call =
+                    call_fn_id == def_id && &call_substs[..trait_substs.len()] == trait_substs;
+
+                if is_self_call {
+                    self_calls.insert(bb);
+                }
+            }
+        }
+    }
+
+    self_calls
+}
diff --git a/src/librustc_passes/check_attr.rs b/src/librustc_passes/check_attr.rs
index 619a8c6f229..3f2c02f6c46 100644
--- a/src/librustc_passes/check_attr.rs
+++ b/src/librustc_passes/check_attr.rs
@@ -141,7 +141,7 @@ impl CheckAttrVisitor<'tcx> {
         target: Target,
     ) -> bool {
         match target {
-            Target::Fn if attr::contains_name(attrs, sym::naked) => {
+            _ if attr::contains_name(attrs, sym::naked) => {
                 struct_span_err!(
                     self.tcx.sess,
                     *attr_span,
@@ -151,17 +151,7 @@ impl CheckAttrVisitor<'tcx> {
                 .emit();
                 false
             }
-            Target::ForeignFn => {
-                struct_span_err!(
-                    self.tcx.sess,
-                    *attr_span,
-                    E0738,
-                    "`#[track_caller]` is not supported on foreign functions",
-                )
-                .emit();
-                false
-            }
-            Target::Fn | Target::Method(..) => true,
+            Target::Fn | Target::Method(..) | Target::ForeignFn => true,
             _ => {
                 struct_span_err!(
                     self.tcx.sess,
diff --git a/src/librustc_passes/region.rs b/src/librustc_passes/region.rs
index 485480d020f..4c02127c106 100644
--- a/src/librustc_passes/region.rs
+++ b/src/librustc_passes/region.rs
@@ -717,9 +717,17 @@ impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> {
             self.cx.parent
         );
 
+        // Save all state that is specific to the outer function
+        // body. These will be restored once down below, once we've
+        // visited the body.
         let outer_ec = mem::replace(&mut self.expr_and_pat_count, 0);
         let outer_cx = self.cx;
         let outer_ts = mem::take(&mut self.terminating_scopes);
+        // The 'pessimistic yield' flag is set to true when we are
+        // processing a `+=` statement and have to make pessimistic
+        // control flow assumptions. This doesn't apply to nested
+        // bodies within the `+=` statements. See #69307.
+        let outer_pessimistic_yield = mem::replace(&mut self.pessimistic_yield, false);
         self.terminating_scopes.insert(body.value.hir_id.local_id);
 
         if let Some(root_id) = self.cx.root_id {
@@ -771,6 +779,7 @@ impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> {
         self.expr_and_pat_count = outer_ec;
         self.cx = outer_cx;
         self.terminating_scopes = outer_ts;
+        self.pessimistic_yield = outer_pessimistic_yield;
     }
 
     fn visit_arm(&mut self, a: &'tcx Arm<'tcx>) {
diff --git a/src/librustc_session/config.rs b/src/librustc_session/config.rs
index 58a03dbe388..4e2423bc3b1 100644
--- a/src/librustc_session/config.rs
+++ b/src/librustc_session/config.rs
@@ -1017,7 +1017,13 @@ pub fn get_cmd_lint_options(
     let mut describe_lints = false;
 
     for &level in &[lint::Allow, lint::Warn, lint::Deny, lint::Forbid] {
-        for (arg_pos, lint_name) in matches.opt_strs_pos(level.as_str()) {
+        for (passed_arg_pos, lint_name) in matches.opt_strs_pos(level.as_str()) {
+            let arg_pos = if let lint::Forbid = level {
+                // forbid is always specified last, so it can't be overridden
+                usize::max_value()
+            } else {
+                passed_arg_pos
+            };
             if lint_name == "help" {
                 describe_lints = true;
             } else {
diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs
index 74dd41fd501..d745e87a072 100644
--- a/src/libstd/sys/windows/mod.rs
+++ b/src/libstd/sys/windows/mod.rs
@@ -81,10 +81,54 @@ pub fn decode_error_kind(errno: i32) -> ErrorKind {
     }
 }
 
+pub fn unrolled_find_u16s(needle: u16, haystack: &[u16]) -> Option<usize> {
+    let ptr = haystack.as_ptr();
+    let mut len = haystack.len();
+    let mut start = &haystack[..];
+
+    // For performance reasons unfold the loop eight times.
+    while len >= 8 {
+        if start[0] == needle {
+            return Some((start.as_ptr() as usize - ptr as usize) / 2);
+        }
+        if start[1] == needle {
+            return Some((start[1..].as_ptr() as usize - ptr as usize) / 2);
+        }
+        if start[2] == needle {
+            return Some((start[2..].as_ptr() as usize - ptr as usize) / 2);
+        }
+        if start[3] == needle {
+            return Some((start[3..].as_ptr() as usize - ptr as usize) / 2);
+        }
+        if start[4] == needle {
+            return Some((start[4..].as_ptr() as usize - ptr as usize) / 2);
+        }
+        if start[5] == needle {
+            return Some((start[5..].as_ptr() as usize - ptr as usize) / 2);
+        }
+        if start[6] == needle {
+            return Some((start[6..].as_ptr() as usize - ptr as usize) / 2);
+        }
+        if start[7] == needle {
+            return Some((start[7..].as_ptr() as usize - ptr as usize) / 2);
+        }
+
+        start = &start[8..];
+        len -= 8;
+    }
+
+    for (i, c) in start.iter().enumerate() {
+        if *c == needle {
+            return Some((start.as_ptr() as usize - ptr as usize) / 2 + i);
+        }
+    }
+    None
+}
+
 pub fn to_u16s<S: AsRef<OsStr>>(s: S) -> crate::io::Result<Vec<u16>> {
     fn inner(s: &OsStr) -> crate::io::Result<Vec<u16>> {
         let mut maybe_result: Vec<u16> = s.encode_wide().collect();
-        if maybe_result.iter().any(|&u| u == 0) {
+        if unrolled_find_u16s(0, &maybe_result).is_some() {
             return Err(crate::io::Error::new(
                 ErrorKind::InvalidInput,
                 "strings passed to WinAPI cannot contain NULs",
@@ -214,7 +258,7 @@ fn wide_char_to_multi_byte(
 }
 
 pub fn truncate_utf16_at_nul(v: &[u16]) -> &[u16] {
-    match v.iter().position(|c| *c == 0) {
+    match unrolled_find_u16s(0, v) {
         // don't include the 0
         Some(i) => &v[..i],
         None => v,
diff --git a/src/test/ui-fulldeps/lint-group-forbid-always-trumps-cli.rs b/src/test/ui-fulldeps/lint-group-forbid-always-trumps-cli.rs
new file mode 100644
index 00000000000..fc19bc03906
--- /dev/null
+++ b/src/test/ui-fulldeps/lint-group-forbid-always-trumps-cli.rs
@@ -0,0 +1,7 @@
+// aux-build:lint-group-plugin-test.rs
+// compile-flags: -F unused -A unused
+
+fn main() {
+    let x = 1;
+    //~^ ERROR unused variable: `x`
+}
diff --git a/src/test/ui-fulldeps/lint-group-forbid-always-trumps-cli.stderr b/src/test/ui-fulldeps/lint-group-forbid-always-trumps-cli.stderr
new file mode 100644
index 00000000000..6bab367b0d1
--- /dev/null
+++ b/src/test/ui-fulldeps/lint-group-forbid-always-trumps-cli.stderr
@@ -0,0 +1,10 @@
+error: unused variable: `x`
+  --> $DIR/lint-group-forbid-always-trumps-cli.rs:5:9
+   |
+LL |     let x = 1;
+   |         ^ help: if this is intentional, prefix it with an underscore: `_x`
+   |
+   = note: `-F unused-variables` implied by `-F unused`
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/async-await/issues/issue-69307-nested.rs b/src/test/ui/async-await/issues/issue-69307-nested.rs
new file mode 100644
index 00000000000..b7cdf3987f1
--- /dev/null
+++ b/src/test/ui/async-await/issues/issue-69307-nested.rs
@@ -0,0 +1,30 @@
+// Regression test for #69307
+//
+// Having a `async { .. foo.await .. }` block appear inside of a `+=`
+// expression was causing an ICE due to a failure to save/restore
+// state in the AST numbering pass when entering a nested body.
+//
+// check-pass
+// edition:2018
+
+fn block_on<F>(_: F) -> usize {
+    0
+}
+
+fn main() {}
+
+async fn bar() {
+    let mut sum = 0;
+    sum += {
+        block_on(async {
+            baz().await;
+            let mut inner = 1;
+            inner += block_on(async {
+                baz().await;
+                0
+            })
+        })
+    };
+}
+
+async fn baz() {}
diff --git a/src/test/ui/async-await/issues/issue-69307.rs b/src/test/ui/async-await/issues/issue-69307.rs
new file mode 100644
index 00000000000..4dae96ec8a6
--- /dev/null
+++ b/src/test/ui/async-await/issues/issue-69307.rs
@@ -0,0 +1,23 @@
+// Regression test for #69307
+//
+// Having a `async { .. foo.await .. }` block appear inside of a `+=`
+// expression was causing an ICE due to a failure to save/restore
+// state in the AST numbering pass when entering a nested body.
+//
+// check-pass
+// edition:2018
+
+fn block_on<F>(_: F) -> usize {
+    0
+}
+
+fn main() {}
+
+async fn bar() {
+    let mut sum = 0;
+    sum += block_on(async {
+        baz().await;
+    });
+}
+
+async fn baz() {}
diff --git a/src/test/ui/lint/lint-unconditional-recursion.rs b/src/test/ui/lint/lint-unconditional-recursion.rs
index ab60a326cd2..d2a0329585b 100644
--- a/src/test/ui/lint/lint-unconditional-recursion.rs
+++ b/src/test/ui/lint/lint-unconditional-recursion.rs
@@ -131,4 +131,22 @@ trait Bar {
     }
 }
 
+// Do not trigger on functions that may diverge instead of self-recursing (#54444)
+
+pub fn loops(x: bool) {
+    if x {
+        loops(x);
+    } else {
+        loop {}
+    }
+}
+
+pub fn panics(x: bool) {
+    if x {
+        panics(!x);
+    } else {
+        panic!("panics");
+    }
+}
+
 fn main() {}
diff --git a/src/test/ui/rfc-2091-track-caller/error-extern-fn.rs b/src/test/ui/rfc-2091-track-caller/error-extern-fn.rs
deleted file mode 100644
index 9f6a69a51c0..00000000000
--- a/src/test/ui/rfc-2091-track-caller/error-extern-fn.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-#![feature(track_caller)]
-#![allow(dead_code)]
-
-extern "Rust" {
-    #[track_caller] //~ ERROR: `#[track_caller]` is not supported on foreign functions
-    fn bar();
-}
-
-fn main() {}
diff --git a/src/test/ui/rfc-2091-track-caller/error-extern-fn.stderr b/src/test/ui/rfc-2091-track-caller/error-extern-fn.stderr
deleted file mode 100644
index b03f5fbbdb2..00000000000
--- a/src/test/ui/rfc-2091-track-caller/error-extern-fn.stderr
+++ /dev/null
@@ -1,9 +0,0 @@
-error[E0738]: `#[track_caller]` is not supported on foreign functions
-  --> $DIR/error-extern-fn.rs:5:5
-   |
-LL |     #[track_caller]
-   |     ^^^^^^^^^^^^^^^
-
-error: aborting due to previous error
-
-For more information about this error, try `rustc --explain E0738`.
diff --git a/src/test/ui/rfc-2091-track-caller/error-with-naked.rs b/src/test/ui/rfc-2091-track-caller/error-with-naked.rs
index dd9e5d04135..f4573848333 100644
--- a/src/test/ui/rfc-2091-track-caller/error-with-naked.rs
+++ b/src/test/ui/rfc-2091-track-caller/error-with-naked.rs
@@ -1,8 +1,21 @@
 #![feature(naked_functions, track_caller)]
 
-#[track_caller]
+#[track_caller] //~ ERROR cannot use `#[track_caller]` with `#[naked]`
 #[naked]
 fn f() {}
-//~^^^ ERROR cannot use `#[track_caller]` with `#[naked]`
+
+struct S;
+
+impl S {
+    #[track_caller] //~ ERROR cannot use `#[track_caller]` with `#[naked]`
+    #[naked]
+    fn g() {}
+}
+
+extern "Rust" {
+    #[track_caller] //~ ERROR cannot use `#[track_caller]` with `#[naked]`
+    #[naked]
+    fn h();
+}
 
 fn main() {}
diff --git a/src/test/ui/rfc-2091-track-caller/error-with-naked.stderr b/src/test/ui/rfc-2091-track-caller/error-with-naked.stderr
index 2f5003cfdb7..1249d1df071 100644
--- a/src/test/ui/rfc-2091-track-caller/error-with-naked.stderr
+++ b/src/test/ui/rfc-2091-track-caller/error-with-naked.stderr
@@ -4,6 +4,18 @@ error[E0736]: cannot use `#[track_caller]` with `#[naked]`
 LL | #[track_caller]
    | ^^^^^^^^^^^^^^^
 
-error: aborting due to previous error
+error[E0736]: cannot use `#[track_caller]` with `#[naked]`
+  --> $DIR/error-with-naked.rs:16:5
+   |
+LL |     #[track_caller]
+   |     ^^^^^^^^^^^^^^^
+
+error[E0736]: cannot use `#[track_caller]` with `#[naked]`
+  --> $DIR/error-with-naked.rs:10:5
+   |
+LL |     #[track_caller]
+   |     ^^^^^^^^^^^^^^^
+
+error: aborting due to 3 previous errors
 
 For more information about this error, try `rustc --explain E0736`.
diff --git a/src/test/ui/rfc-2091-track-caller/track-caller-ffi.rs b/src/test/ui/rfc-2091-track-caller/track-caller-ffi.rs
new file mode 100644
index 00000000000..23c17d743c4
--- /dev/null
+++ b/src/test/ui/rfc-2091-track-caller/track-caller-ffi.rs
@@ -0,0 +1,50 @@
+// run-pass
+
+#![feature(track_caller)]
+
+use std::panic::Location;
+
+extern "Rust" {
+    #[track_caller]
+    fn rust_track_caller_ffi_test_tracked() -> &'static Location<'static>;
+    fn rust_track_caller_ffi_test_untracked() -> &'static Location<'static>;
+}
+
+fn rust_track_caller_ffi_test_nested_tracked() -> &'static Location<'static> {
+    unsafe { rust_track_caller_ffi_test_tracked() }
+}
+
+mod provides {
+    use std::panic::Location;
+    #[track_caller] // UB if we did not have this!
+    #[no_mangle]
+    fn rust_track_caller_ffi_test_tracked() -> &'static Location<'static> {
+        Location::caller()
+    }
+    #[no_mangle]
+    fn rust_track_caller_ffi_test_untracked() -> &'static Location<'static> {
+        Location::caller()
+    }
+}
+
+fn main() {
+    let location = Location::caller();
+    assert_eq!(location.file(), file!());
+    assert_eq!(location.line(), 31);
+    assert_eq!(location.column(), 20);
+
+    let tracked = unsafe { rust_track_caller_ffi_test_tracked() };
+    assert_eq!(tracked.file(), file!());
+    assert_eq!(tracked.line(), 36);
+    assert_eq!(tracked.column(), 28);
+
+    let untracked = unsafe { rust_track_caller_ffi_test_untracked() };
+    assert_eq!(untracked.file(), file!());
+    assert_eq!(untracked.line(), 26);
+    assert_eq!(untracked.column(), 9);
+
+    let contained = rust_track_caller_ffi_test_nested_tracked();
+    assert_eq!(contained.file(), file!());
+    assert_eq!(contained.line(), 14);
+    assert_eq!(contained.column(), 14);
+}