diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index b76515763fbdb..586a362b5e3fe 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -45,7 +45,7 @@ impl Step for Std {
         let compiler = builder.compiler(0, builder.config.build);
 
         let mut cargo = builder.cargo(compiler, Mode::Std, target, cargo_subcommand(builder.kind));
-        std_cargo(builder, target, &mut cargo);
+        std_cargo(builder, target, compiler.stage, &mut cargo);
 
         builder.info(&format!("Checking std artifacts ({} -> {})", &compiler.host, target));
         run_cargo(
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index f44096af6dd53..c7ae73f9f8b91 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -86,7 +86,7 @@ impl Step for Std {
         target_deps.extend(copy_third_party_objects(builder, &compiler, target).into_iter());
 
         let mut cargo = builder.cargo(compiler, Mode::Std, target, "build");
-        std_cargo(builder, target, &mut cargo);
+        std_cargo(builder, target, compiler.stage, &mut cargo);
 
         builder.info(&format!(
             "Building stage{} std artifacts ({} -> {})",
@@ -164,7 +164,7 @@ fn copy_third_party_objects(
 
 /// Configure cargo to compile the standard library, adding appropriate env vars
 /// and such.
-pub fn std_cargo(builder: &Builder<'_>, target: Interned<String>, cargo: &mut Cargo) {
+pub fn std_cargo(builder: &Builder<'_>, target: Interned<String>, stage: u32, cargo: &mut Cargo) {
     if let Some(target) = env::var_os("MACOSX_STD_DEPLOYMENT_TARGET") {
         cargo.env("MACOSX_DEPLOYMENT_TARGET", target);
     }
@@ -231,6 +231,16 @@ pub fn std_cargo(builder: &Builder<'_>, target: Interned<String>, cargo: &mut Ca
             }
         }
     }
+
+    // libstd must be built with embedded bitcode so that the produced
+    // artifacts can be used for both LTO builds (which use bitcode) and
+    // non-LTO builds (which use object code).
+    //
+    // But we don't bother for the stage 0 compiler because it's never used
+    // with LTO.
+    if stage >= 1 {
+        cargo.rustflag("-Cembed-bitcode=yes");
+    }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index 04da3cc1015b8..fc217a707db94 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -394,7 +394,7 @@ impl Step for Std {
 
         let run_cargo_rustdoc_for = |package: &str| {
             let mut cargo = builder.cargo(compiler, Mode::Std, target, "rustdoc");
-            compile::std_cargo(builder, target, &mut cargo);
+            compile::std_cargo(builder, target, compiler.stage, &mut cargo);
 
             // Keep a whitelist so we do not build internal stdlib crates, these will be
             // build by the rustc step later if enabled.
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 85c5d28bb8924..125563b7b6086 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -1725,7 +1725,7 @@ impl Step for Crate {
         let mut cargo = builder.cargo(compiler, mode, target, test_kind.subcommand());
         match mode {
             Mode::Std => {
-                compile::std_cargo(builder, target, &mut cargo);
+                compile::std_cargo(builder, target, compiler.stage, &mut cargo);
             }
             Mode::Rustc => {
                 builder.ensure(compile::Rustc { compiler, target });
diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md
index 8dc6257ce2e58..49750ba26604a 100644
--- a/src/doc/rustc/src/codegen-options/index.md
+++ b/src/doc/rustc/src/codegen-options/index.md
@@ -381,6 +381,31 @@ the linker.
 
 The default is `yes` if not specified.
 
+## embed-bitcode
+
+This flag controls whether or not the compiler embeds LLVM bitcode into
+generated object files. It takes one of the following values:
+
+* `y`, `yes`, `on`, or no value: enable bitcode embedding (the default).
+* `n`, `no`, or `off`: disable bitcode embedding.
+
+LLVM bitcode embedding is only needed when link-time optimization (LTO) is
+being performed, but it is enabled by default for backwards compatibility
+reasons.
+
+The use of `-C embed-bitcode=no` can significantly improve compile times and
+reduce generated file sizes. For these reasons, Cargo uses `-C
+embed-bitcode=no` whenever appropriate to reduce compilation costs. Likewise,
+if you are building directly with `rustc` we recommend using `-C
+embed-bitcode=no` whenever you are not using LTO.
+
+If combined with `-C lto`, `-C embed-bitcode=no` will cause `rustc` to abort at
+start-up, because the combination is invalid.
+
+If combined with `-C linker-plugin-lto`, `-C embed-bitcode` (with any value)
+will be ignored, because in that case generated object files contain only LLVM
+bitcode, and no object code.
+
 [option-emit]: ../command-line-arguments.md#option-emit
 [option-o-optimize]: ../command-line-arguments.md#option-o-optimize
 [profile-guided optimization]: ../profile-guided-optimization.md
diff --git a/src/librustc_codegen_llvm/back/archive.rs b/src/librustc_codegen_llvm/back/archive.rs
index f1fe40d919eeb..a115a1e95163e 100644
--- a/src/librustc_codegen_llvm/back/archive.rs
+++ b/src/librustc_codegen_llvm/back/archive.rs
@@ -10,7 +10,7 @@ use std::str;
 use crate::llvm::archive_ro::{ArchiveRO, Child};
 use crate::llvm::{self, ArchiveKind};
 use rustc_codegen_ssa::back::archive::{find_library, ArchiveBuilder};
-use rustc_codegen_ssa::{looks_like_rust_object_file, METADATA_FILENAME, RLIB_BYTECODE_EXTENSION};
+use rustc_codegen_ssa::{looks_like_rust_object_file, METADATA_FILENAME};
 use rustc_session::Session;
 use rustc_span::symbol::Symbol;
 
@@ -129,8 +129,8 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
         let obj_start = name.to_owned();
 
         self.add_archive(rlib, move |fname: &str| {
-            // Ignore bytecode/metadata files, no matter the name.
-            if fname.ends_with(RLIB_BYTECODE_EXTENSION) || fname == METADATA_FILENAME {
+            // Ignore metadata files, no matter the name.
+            if fname == METADATA_FILENAME {
                 return true;
             }
 
diff --git a/src/librustc_codegen_llvm/back/bytecode.rs b/src/librustc_codegen_llvm/back/bytecode.rs
deleted file mode 100644
index 0c8ce39132abb..0000000000000
--- a/src/librustc_codegen_llvm/back/bytecode.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-//! Management of the encoding of LLVM bytecode into rlibs
-//!
-//! This module contains the management of encoding LLVM bytecode into rlibs,
-//! primarily for the usage in LTO situations. Currently the compiler will
-//! unconditionally encode LLVM-IR into rlibs regardless of what's happening
-//! elsewhere, so we currently compress the bytecode via deflate to avoid taking
-//! up too much space on disk.
-//!
-//! After compressing the bytecode we then have the rest of the format to
-//! basically deal with various bugs in various archive implementations. The
-//! format currently is:
-//!
-//!     RLIB LLVM-BYTECODE OBJECT LAYOUT
-//!     Version 2
-//!     Bytes    Data
-//!     0..10    "RUST_OBJECT" encoded in ASCII
-//!     11..14   format version as little-endian u32
-//!     15..19   the length of the module identifier string
-//!     20..n    the module identifier string
-//!     n..n+8   size in bytes of deflate compressed LLVM bitcode as
-//!              little-endian u64
-//!     n+9..    compressed LLVM bitcode
-//!     ?        maybe a byte to make this whole thing even length
-
-use std::io::{Read, Write};
-use std::ptr;
-use std::str;
-
-use flate2::read::DeflateDecoder;
-use flate2::write::DeflateEncoder;
-use flate2::Compression;
-
-// This is the "magic number" expected at the beginning of a LLVM bytecode
-// object in an rlib.
-pub const RLIB_BYTECODE_OBJECT_MAGIC: &[u8] = b"RUST_OBJECT";
-
-// The version number this compiler will write to bytecode objects in rlibs
-pub const RLIB_BYTECODE_OBJECT_VERSION: u8 = 2;
-
-pub fn encode(identifier: &str, bytecode: &[u8]) -> Vec<u8> {
-    let mut encoded = Vec::new();
-
-    // Start off with the magic string
-    encoded.extend_from_slice(RLIB_BYTECODE_OBJECT_MAGIC);
-
-    // Next up is the version
-    encoded.extend_from_slice(&[RLIB_BYTECODE_OBJECT_VERSION, 0, 0, 0]);
-
-    // Next is the LLVM module identifier length + contents
-    let identifier_len = identifier.len();
-    encoded.extend_from_slice(&[
-        (identifier_len >> 0) as u8,
-        (identifier_len >> 8) as u8,
-        (identifier_len >> 16) as u8,
-        (identifier_len >> 24) as u8,
-    ]);
-    encoded.extend_from_slice(identifier.as_bytes());
-
-    // Next is the LLVM module deflate compressed, prefixed with its length. We
-    // don't know its length yet, so fill in 0s
-    let deflated_size_pos = encoded.len();
-    encoded.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]);
-
-    let before = encoded.len();
-    DeflateEncoder::new(&mut encoded, Compression::fast()).write_all(bytecode).unwrap();
-    let after = encoded.len();
-
-    // Fill in the length we reserved space for before
-    let bytecode_len = (after - before) as u64;
-    encoded[deflated_size_pos + 0] = (bytecode_len >> 0) as u8;
-    encoded[deflated_size_pos + 1] = (bytecode_len >> 8) as u8;
-    encoded[deflated_size_pos + 2] = (bytecode_len >> 16) as u8;
-    encoded[deflated_size_pos + 3] = (bytecode_len >> 24) as u8;
-    encoded[deflated_size_pos + 4] = (bytecode_len >> 32) as u8;
-    encoded[deflated_size_pos + 5] = (bytecode_len >> 40) as u8;
-    encoded[deflated_size_pos + 6] = (bytecode_len >> 48) as u8;
-    encoded[deflated_size_pos + 7] = (bytecode_len >> 56) as u8;
-
-    // If the number of bytes written to the object so far is odd, add a
-    // padding byte to make it even. This works around a crash bug in LLDB
-    // (see issue #15950)
-    if encoded.len() % 2 == 1 {
-        encoded.push(0);
-    }
-
-    encoded
-}
-
-pub struct DecodedBytecode<'a> {
-    identifier: &'a str,
-    encoded_bytecode: &'a [u8],
-}
-
-impl<'a> DecodedBytecode<'a> {
-    pub fn new(data: &'a [u8]) -> Result<DecodedBytecode<'a>, &'static str> {
-        if !data.starts_with(RLIB_BYTECODE_OBJECT_MAGIC) {
-            return Err("magic bytecode prefix not found");
-        }
-        let data = &data[RLIB_BYTECODE_OBJECT_MAGIC.len()..];
-        if !data.starts_with(&[RLIB_BYTECODE_OBJECT_VERSION, 0, 0, 0]) {
-            return Err("wrong version prefix found in bytecode");
-        }
-        let data = &data[4..];
-        if data.len() < 4 {
-            return Err("bytecode corrupted");
-        }
-        let identifier_len =
-            unsafe { u32::from_le(ptr::read_unaligned(data.as_ptr() as *const u32)) as usize };
-        let data = &data[4..];
-        if data.len() < identifier_len {
-            return Err("bytecode corrupted");
-        }
-        let identifier = match str::from_utf8(&data[..identifier_len]) {
-            Ok(s) => s,
-            Err(_) => return Err("bytecode corrupted"),
-        };
-        let data = &data[identifier_len..];
-        if data.len() < 8 {
-            return Err("bytecode corrupted");
-        }
-        let bytecode_len =
-            unsafe { u64::from_le(ptr::read_unaligned(data.as_ptr() as *const u64)) as usize };
-        let data = &data[8..];
-        if data.len() < bytecode_len {
-            return Err("bytecode corrupted");
-        }
-        let encoded_bytecode = &data[..bytecode_len];
-
-        Ok(DecodedBytecode { identifier, encoded_bytecode })
-    }
-
-    pub fn bytecode(&self) -> Vec<u8> {
-        let mut data = Vec::new();
-        DeflateDecoder::new(self.encoded_bytecode).read_to_end(&mut data).unwrap();
-        data
-    }
-
-    pub fn identifier(&self) -> &'a str {
-        self.identifier
-    }
-}
diff --git a/src/librustc_codegen_llvm/back/lto.rs b/src/librustc_codegen_llvm/back/lto.rs
index 816329e06c7a5..506ca3105bd29 100644
--- a/src/librustc_codegen_llvm/back/lto.rs
+++ b/src/librustc_codegen_llvm/back/lto.rs
@@ -1,4 +1,3 @@
-use crate::back::bytecode::DecodedBytecode;
 use crate::back::write::{
     self, save_temp_bitcode, to_llvm_opt_settings, with_llvm_pmb, DiagnosticHandlers,
 };
@@ -10,7 +9,7 @@ use rustc_codegen_ssa::back::lto::{LtoModuleCodegen, SerializedModule, ThinModul
 use rustc_codegen_ssa::back::symbol_export;
 use rustc_codegen_ssa::back::write::{CodegenContext, FatLTOInput, ModuleConfig};
 use rustc_codegen_ssa::traits::*;
-use rustc_codegen_ssa::{ModuleCodegen, ModuleKind, RLIB_BYTECODE_EXTENSION};
+use rustc_codegen_ssa::{looks_like_rust_object_file, ModuleCodegen, ModuleKind};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::{FatalError, Handler};
 use rustc_hir::def_id::LOCAL_CRATE;
@@ -111,22 +110,21 @@ fn prepare_lto(
             }
 
             let archive = ArchiveRO::open(&path).expect("wanted an rlib");
-            let bytecodes = archive
+            let obj_files = archive
                 .iter()
                 .filter_map(|child| child.ok().and_then(|c| c.name().map(|name| (name, c))))
-                .filter(|&(name, _)| name.ends_with(RLIB_BYTECODE_EXTENSION));
-            for (name, data) in bytecodes {
+                .filter(|&(name, _)| looks_like_rust_object_file(name));
+            for (name, data) in obj_files {
                 let _timer =
                     cgcx.prof.generic_activity_with_arg("LLVM_lto_load_upstream_bitcode", name);
-                info!("adding bytecode {}", name);
-                let bc_encoded = data.data();
-
-                let (bc, id) = match DecodedBytecode::new(bc_encoded) {
-                    Ok(b) => Ok((b.bytecode(), b.identifier().to_string())),
-                    Err(e) => Err(diag_handler.fatal(&e)),
-                }?;
-                let bc = SerializedModule::FromRlib(bc);
-                upstream_modules.push((bc, CString::new(id).unwrap()));
+                info!("adding bitcode from {}", name);
+                match get_bitcode_slice_from_object_data(data.data()) {
+                    Ok(bc) => {
+                        let bc = SerializedModule::FromRlib(bc.to_vec());
+                        upstream_modules.push((bc, CString::new(name).unwrap()));
+                    }
+                    Err(e) => return Err(diag_handler.fatal(&e)),
+                }
             }
         }
     }
@@ -134,6 +132,26 @@ fn prepare_lto(
     Ok((symbol_white_list, upstream_modules))
 }
 
+fn get_bitcode_slice_from_object_data(obj: &[u8]) -> Result<&[u8], String> {
+    let mut len = 0;
+    let data =
+        unsafe { llvm::LLVMRustGetBitcodeSliceFromObjectData(obj.as_ptr(), obj.len(), &mut len) };
+    if !data.is_null() {
+        assert!(len != 0);
+        let bc = unsafe { slice::from_raw_parts(data, len) };
+
+        // `bc` must be a sub-slice of `obj`.
+        assert!(obj.as_ptr() <= bc.as_ptr());
+        assert!(bc[bc.len()..bc.len()].as_ptr() < obj[obj.len()..obj.len()].as_ptr());
+
+        Ok(bc)
+    } else {
+        assert!(len == 0);
+        let msg = llvm::last_error().unwrap_or_else(|| "unknown LLVM error".to_string());
+        Err(format!("failed to get bitcode from object file for LTO ({})", msg))
+    }
+}
+
 /// Performs fat LTO by merging all modules into a single one and returning it
 /// for further optimization.
 pub(crate) fn run_fat(
diff --git a/src/librustc_codegen_llvm/back/write.rs b/src/librustc_codegen_llvm/back/write.rs
index 5708cb4e65418..bb367b5efb6d4 100644
--- a/src/librustc_codegen_llvm/back/write.rs
+++ b/src/librustc_codegen_llvm/back/write.rs
@@ -1,5 +1,4 @@
 use crate::attributes;
-use crate::back::bytecode;
 use crate::back::lto::ThinBuffer;
 use crate::back::profiling::{
     selfprofile_after_pass_callback, selfprofile_before_pass_callback, LlvmSelfProfiler,
@@ -14,9 +13,9 @@ use crate::type_::Type;
 use crate::LlvmCodegenBackend;
 use crate::ModuleLlvm;
 use log::debug;
-use rustc_codegen_ssa::back::write::{BitcodeSection, CodegenContext, EmitObj, ModuleConfig};
+use rustc_codegen_ssa::back::write::{CodegenContext, EmitObj, ModuleConfig};
 use rustc_codegen_ssa::traits::*;
-use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, RLIB_BYTECODE_EXTENSION};
+use rustc_codegen_ssa::{CompiledModule, ModuleCodegen};
 use rustc_data_structures::small_c_str::SmallCStr;
 use rustc_errors::{FatalError, Handler};
 use rustc_fs_util::{link_or_copy, path_to_c_string};
@@ -662,28 +661,13 @@ pub(crate) unsafe fn codegen(
                 }
             }
 
-            if config.emit_obj == EmitObj::ObjectCode(BitcodeSection::Full) {
+            if config.emit_obj == (EmitObj::ObjectCode { bitcode_section: true }) {
                 let _timer = cgcx.prof.generic_activity_with_arg(
                     "LLVM_module_codegen_embed_bitcode",
                     &module.name[..],
                 );
-                embed_bitcode(cgcx, llcx, llmod, Some(data));
+                embed_bitcode(cgcx, llcx, llmod, data);
             }
-
-            if config.emit_bc_compressed {
-                let _timer = cgcx.prof.generic_activity_with_arg(
-                    "LLVM_module_codegen_emit_compressed_bitcode",
-                    &module.name[..],
-                );
-                let dst = bc_out.with_extension(RLIB_BYTECODE_EXTENSION);
-                let data = bytecode::encode(&module.name, data);
-                if let Err(e) = fs::write(&dst, data) {
-                    let msg = format!("failed to write bytecode to {}: {}", dst.display(), e);
-                    diag_handler.err(&msg);
-                }
-            }
-        } else if config.emit_obj == EmitObj::ObjectCode(BitcodeSection::Marker) {
-            embed_bitcode(cgcx, llcx, llmod, None);
         }
 
         if config.emit_ir {
@@ -742,7 +726,7 @@ pub(crate) unsafe fn codegen(
             // because that triggers various errors like invalid IR or broken
             // binaries. So we must clone the module to produce the asm output
             // if we are also producing object code.
-            let llmod = if let EmitObj::ObjectCode(_) = config.emit_obj {
+            let llmod = if let EmitObj::ObjectCode { .. } = config.emit_obj {
                 llvm::LLVMCloneModule(llmod)
             } else {
                 llmod
@@ -753,7 +737,7 @@ pub(crate) unsafe fn codegen(
         }
 
         match config.emit_obj {
-            EmitObj::ObjectCode(_) => {
+            EmitObj::ObjectCode { .. } => {
                 let _timer = cgcx
                     .prof
                     .generic_activity_with_arg("LLVM_module_codegen_emit_obj", &module.name[..]);
@@ -792,36 +776,31 @@ pub(crate) unsafe fn codegen(
     Ok(module.into_compiled_module(
         config.emit_obj != EmitObj::None,
         config.emit_bc,
-        config.emit_bc_compressed,
         &cgcx.output_filenames,
     ))
 }
 
 /// Embed the bitcode of an LLVM module in the LLVM module itself.
 ///
-/// This is done primarily for iOS where it appears to be standard to compile C
-/// code at least with `-fembed-bitcode` which creates two sections in the
-/// executable:
+/// This is much like compiling C code with `-fembed-bitcode` which creates two
+/// sections in the executable:
 ///
 /// * __LLVM,__bitcode
 /// * __LLVM,__cmdline
 ///
-/// It appears *both* of these sections are necessary to get the linker to
-/// recognize what's going on. For us though we just always throw in an empty
-/// cmdline section.
-///
-/// Furthermore debug/O1 builds don't actually embed bitcode but rather just
-/// embed an empty section.
+/// On iOS it appears *both* of these sections are necessary to get the linker
+/// to recognize what's going on. For us though we just always throw in an
+/// empty cmdline section.
 ///
-/// Basically all of this is us attempting to follow in the footsteps of clang
-/// on iOS. See #35968 for lots more info.
+/// This started with us attempting to follow in the footsteps of clang on iOS
+/// (see #35968 for lots more info) but it is now used for all targets.
 unsafe fn embed_bitcode(
     cgcx: &CodegenContext<LlvmCodegenBackend>,
     llcx: &llvm::Context,
     llmod: &llvm::Module,
-    bitcode: Option<&[u8]>,
+    bitcode: &[u8],
 ) {
-    let llconst = common::bytes_in_context(llcx, bitcode.unwrap_or(&[]));
+    let llconst = common::bytes_in_context(llcx, bitcode);
     let llglobal = llvm::LLVMAddGlobal(
         llmod,
         common::val_ty(llconst),
diff --git a/src/librustc_codegen_llvm/lib.rs b/src/librustc_codegen_llvm/lib.rs
index 939f9e9c2a0c7..5d4c7109d6580 100644
--- a/src/librustc_codegen_llvm/lib.rs
+++ b/src/librustc_codegen_llvm/lib.rs
@@ -39,7 +39,6 @@ use std::sync::Arc;
 
 mod back {
     pub mod archive;
-    pub mod bytecode;
     pub mod lto;
     mod profiling;
     pub mod write;
diff --git a/src/librustc_codegen_llvm/llvm/ffi.rs b/src/librustc_codegen_llvm/llvm/ffi.rs
index aeb34e5c9c954..ceeb528430fff 100644
--- a/src/librustc_codegen_llvm/llvm/ffi.rs
+++ b/src/librustc_codegen_llvm/llvm/ffi.rs
@@ -2138,6 +2138,11 @@ extern "C" {
         len: usize,
         Identifier: *const c_char,
     ) -> Option<&Module>;
+    pub fn LLVMRustGetBitcodeSliceFromObjectData(
+        Data: *const u8,
+        len: usize,
+        out_len: &mut usize,
+    ) -> *const u8;
     pub fn LLVMRustThinLTOGetDICompileUnit(
         M: &Module,
         CU1: &mut *mut c_void,
diff --git a/src/librustc_codegen_ssa/back/link.rs b/src/librustc_codegen_ssa/back/link.rs
index 4c66d901e7a99..77cec347448a5 100644
--- a/src/librustc_codegen_ssa/back/link.rs
+++ b/src/librustc_codegen_ssa/back/link.rs
@@ -18,10 +18,7 @@ use super::archive::ArchiveBuilder;
 use super::command::Command;
 use super::linker::Linker;
 use super::rpath::{self, RPathConfig};
-use crate::{
-    looks_like_rust_object_file, CodegenResults, CrateInfo, METADATA_FILENAME,
-    RLIB_BYTECODE_EXTENSION,
-};
+use crate::{looks_like_rust_object_file, CodegenResults, CrateInfo, METADATA_FILENAME};
 
 use cc::windows_registry;
 use tempfile::{Builder as TempFileBuilder, TempDir};
@@ -130,10 +127,6 @@ pub fn link_binary<'a, B: ArchiveBuilder<'a>>(
                     remove(sess, obj);
                 }
             }
-            for obj in codegen_results.modules.iter().filter_map(|m| m.bytecode_compressed.as_ref())
-            {
-                remove(sess, obj);
-            }
             if let Some(ref metadata_module) = codegen_results.metadata_module {
                 if let Some(ref obj) = metadata_module.object {
                     remove(sess, obj);
@@ -143,9 +136,6 @@ pub fn link_binary<'a, B: ArchiveBuilder<'a>>(
                 if let Some(ref obj) = allocator_module.object {
                     remove(sess, obj);
                 }
-                if let Some(ref bc) = allocator_module.bytecode_compressed {
-                    remove(sess, bc);
-                }
             }
         }
     });
@@ -378,14 +368,6 @@ fn link_rlib<'a, B: ArchiveBuilder<'a>>(
             // contain the metadata in a separate file.
             ab.add_file(&emit_metadata(sess, &codegen_results.metadata, tmpdir));
 
-            // For LTO purposes, the bytecode of this library is also inserted
-            // into the archive.
-            for bytecode in
-                codegen_results.modules.iter().filter_map(|m| m.bytecode_compressed.as_ref())
-            {
-                ab.add_file(bytecode);
-            }
-
             // After adding all files to the archive, we need to update the
             // symbol table of the archive. This currently dies on macOS (see
             // #11162), and isn't necessary there anyway
@@ -1842,7 +1824,7 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>(
 
             let mut any_objects = false;
             for f in archive.src_files() {
-                if f.ends_with(RLIB_BYTECODE_EXTENSION) || f == METADATA_FILENAME {
+                if f == METADATA_FILENAME {
                     archive.remove_file(&f);
                     continue;
                 }
diff --git a/src/librustc_codegen_ssa/back/write.rs b/src/librustc_codegen_ssa/back/write.rs
index b1fb1ef0e331c..7d161ef3cf122 100644
--- a/src/librustc_codegen_ssa/back/write.rs
+++ b/src/librustc_codegen_ssa/back/write.rs
@@ -5,7 +5,6 @@ use super::symbol_export::symbol_name_for_instance_in_crate;
 
 use crate::{
     CachedModuleCodegen, CodegenResults, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind,
-    RLIB_BYTECODE_EXTENSION,
 };
 
 use crate::traits::*;
@@ -24,7 +23,7 @@ use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
 use rustc_incremental::{
     copy_cgu_workproducts_to_incr_comp_cache_dir, in_incr_comp_dir, in_incr_comp_dir_sess,
 };
-use rustc_middle::dep_graph::{WorkProduct, WorkProductFileKind, WorkProductId};
+use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
 use rustc_middle::middle::cstore::EncodedMetadata;
 use rustc_middle::middle::exported_symbols::SymbolExportLevel;
 use rustc_middle::ty::TyCtxt;
@@ -61,21 +60,7 @@ pub enum EmitObj {
     Bitcode,
 
     // Object code, possibly augmented with a bitcode section.
-    ObjectCode(BitcodeSection),
-}
-
-/// What kind of llvm bitcode section to embed in an object file.
-#[derive(Clone, Copy, PartialEq)]
-pub enum BitcodeSection {
-    // No bitcode section.
-    None,
-
-    // An empty bitcode section (to placate tools such as the iOS linker that
-    // require this section even if they don't use it).
-    Marker,
-
-    // A full, uncompressed bitcode section.
-    Full,
+    ObjectCode { bitcode_section: bool },
 }
 
 /// Module-specific configuration for `optimize_and_codegen`.
@@ -100,7 +85,6 @@ pub struct ModuleConfig {
     pub emit_pre_lto_bc: bool,
     pub emit_no_opt_bc: bool,
     pub emit_bc: bool,
-    pub emit_bc_compressed: bool,
     pub emit_ir: bool,
     pub emit_asm: bool,
     pub emit_obj: EmitObj,
@@ -145,15 +129,11 @@ impl ModuleConfig {
             || sess.opts.cg.linker_plugin_lto.enabled()
         {
             EmitObj::Bitcode
-        } else if sess.opts.debugging_opts.embed_bitcode {
-            match sess.opts.optimize {
-                config::OptLevel::No | config::OptLevel::Less => {
-                    EmitObj::ObjectCode(BitcodeSection::Marker)
-                }
-                _ => EmitObj::ObjectCode(BitcodeSection::Full),
-            }
+        } else if sess.opts.cg.embed_bitcode.unwrap_or(true) {
+            // `unwrap_or(true)` because `-C embed-bitcode` defaults to true.
+            EmitObj::ObjectCode { bitcode_section: true }
         } else {
-            EmitObj::ObjectCode(BitcodeSection::None)
+            EmitObj::ObjectCode { bitcode_section: false }
         };
 
         ModuleConfig {
@@ -196,16 +176,6 @@ impl ModuleConfig {
                 save_temps || sess.opts.output_types.contains_key(&OutputType::Bitcode),
                 save_temps
             ),
-            emit_bc_compressed: match kind {
-                ModuleKind::Regular | ModuleKind::Allocator => {
-                    // Emit compressed bitcode files for the crate if we're
-                    // emitting an rlib. Whenever an rlib is created, the
-                    // bitcode is inserted into the archive in order to allow
-                    // LTO against it.
-                    need_crate_bitcode_for_rlib(sess)
-                }
-                ModuleKind::Metadata => false,
-            },
             emit_ir: if_regular!(
                 sess.opts.output_types.contains_key(&OutputType::LlvmAssembly),
                 false
@@ -261,9 +231,8 @@ impl ModuleConfig {
 
     pub fn bitcode_needed(&self) -> bool {
         self.emit_bc
-            || self.emit_bc_compressed
             || self.emit_obj == EmitObj::Bitcode
-            || self.emit_obj == EmitObj::ObjectCode(BitcodeSection::Full)
+            || self.emit_obj == EmitObj::ObjectCode { bitcode_section: true }
     }
 }
 
@@ -377,11 +346,6 @@ pub struct CompiledModules {
     pub allocator_module: Option<CompiledModule>,
 }
 
-fn need_crate_bitcode_for_rlib(sess: &Session) -> bool {
-    sess.crate_types.borrow().contains(&config::CrateType::Rlib)
-        && sess.opts.output_types.contains_key(&OutputType::Exe)
-}
-
 fn need_pre_lto_bitcode_for_incr_comp(sess: &Session) -> bool {
     if sess.opts.incremental.is_none() {
         return false;
@@ -476,13 +440,7 @@ fn copy_all_cgu_workproducts_to_incr_comp_cache_dir(
         let mut files = vec![];
 
         if let Some(ref path) = module.object {
-            files.push((WorkProductFileKind::Object, path.clone()));
-        }
-        if let Some(ref path) = module.bytecode {
-            files.push((WorkProductFileKind::Bytecode, path.clone()));
-        }
-        if let Some(ref path) = module.bytecode_compressed {
-            files.push((WorkProductFileKind::BytecodeCompressed, path.clone()));
+            files.push(path.clone());
         }
 
         if let Some((id, product)) =
@@ -819,29 +777,9 @@ fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
 ) -> Result<WorkItemResult<B>, FatalError> {
     let incr_comp_session_dir = cgcx.incr_comp_session_dir.as_ref().unwrap();
     let mut object = None;
-    let mut bytecode = None;
-    let mut bytecode_compressed = None;
-    for (kind, saved_file) in &module.source.saved_files {
-        let obj_out = match kind {
-            WorkProductFileKind::Object => {
-                let path = cgcx.output_filenames.temp_path(OutputType::Object, Some(&module.name));
-                object = Some(path.clone());
-                path
-            }
-            WorkProductFileKind::Bytecode => {
-                let path = cgcx.output_filenames.temp_path(OutputType::Bitcode, Some(&module.name));
-                bytecode = Some(path.clone());
-                path
-            }
-            WorkProductFileKind::BytecodeCompressed => {
-                let path = cgcx
-                    .output_filenames
-                    .temp_path(OutputType::Bitcode, Some(&module.name))
-                    .with_extension(RLIB_BYTECODE_EXTENSION);
-                bytecode_compressed = Some(path.clone());
-                path
-            }
-        };
+    for saved_file in &module.source.saved_files {
+        let obj_out = cgcx.output_filenames.temp_path(OutputType::Object, Some(&module.name));
+        object = Some(obj_out.clone());
         let source_file = in_incr_comp_dir(&incr_comp_session_dir, &saved_file);
         debug!(
             "copying pre-existing module `{}` from {:?} to {}",
@@ -861,15 +799,12 @@ fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
     }
 
     assert_eq!(object.is_some(), module_config.emit_obj != EmitObj::None);
-    assert_eq!(bytecode.is_some(), module_config.emit_bc);
-    assert_eq!(bytecode_compressed.is_some(), module_config.emit_bc_compressed);
 
     Ok(WorkItemResult::Compiled(CompiledModule {
         name: module.name,
         kind: ModuleKind::Regular,
         object,
-        bytecode,
-        bytecode_compressed,
+        bytecode: None,
     }))
 }
 
diff --git a/src/librustc_codegen_ssa/lib.rs b/src/librustc_codegen_ssa/lib.rs
index bf8441562c55b..fbe0d29a9a936 100644
--- a/src/librustc_codegen_ssa/lib.rs
+++ b/src/librustc_codegen_ssa/lib.rs
@@ -54,31 +54,18 @@ pub struct ModuleCodegen<M> {
 
 // FIXME(eddyb) maybe include the crate name in this?
 pub const METADATA_FILENAME: &str = "lib.rmeta";
-pub const RLIB_BYTECODE_EXTENSION: &str = "bc.z";
 
 impl<M> ModuleCodegen<M> {
     pub fn into_compiled_module(
         self,
         emit_obj: bool,
         emit_bc: bool,
-        emit_bc_compressed: bool,
         outputs: &OutputFilenames,
     ) -> CompiledModule {
         let object = emit_obj.then(|| outputs.temp_path(OutputType::Object, Some(&self.name)));
         let bytecode = emit_bc.then(|| outputs.temp_path(OutputType::Bitcode, Some(&self.name)));
-        let bytecode_compressed = emit_bc_compressed.then(|| {
-            outputs
-                .temp_path(OutputType::Bitcode, Some(&self.name))
-                .with_extension(RLIB_BYTECODE_EXTENSION)
-        });
-
-        CompiledModule {
-            name: self.name.clone(),
-            kind: self.kind,
-            object,
-            bytecode,
-            bytecode_compressed,
-        }
+
+        CompiledModule { name: self.name.clone(), kind: self.kind, object, bytecode }
     }
 }
 
@@ -88,7 +75,6 @@ pub struct CompiledModule {
     pub kind: ModuleKind,
     pub object: Option<PathBuf>,
     pub bytecode: Option<PathBuf>,
-    pub bytecode_compressed: Option<PathBuf>,
 }
 
 pub struct CachedModuleCodegen {
diff --git a/src/librustc_incremental/persist/load.rs b/src/librustc_incremental/persist/load.rs
index b75a428c62a09..99c799950c063 100644
--- a/src/librustc_incremental/persist/load.rs
+++ b/src/librustc_incremental/persist/load.rs
@@ -134,7 +134,7 @@ pub fn load_dep_graph(sess: &Session) -> DepGraphFuture {
 
             for swp in work_products {
                 let mut all_files_exist = true;
-                for &(_, ref file_name) in swp.work_product.saved_files.iter() {
+                for file_name in swp.work_product.saved_files.iter() {
                     let path = in_incr_comp_dir_sess(sess, file_name);
                     if !path.exists() {
                         all_files_exist = false;
diff --git a/src/librustc_incremental/persist/save.rs b/src/librustc_incremental/persist/save.rs
index 6d4ba45c2e6ed..4db6297712c59 100644
--- a/src/librustc_incremental/persist/save.rs
+++ b/src/librustc_incremental/persist/save.rs
@@ -74,9 +74,9 @@ pub fn save_work_product_index(
         if !new_work_products.contains_key(id) {
             work_product::delete_workproduct_files(sess, wp);
             debug_assert!(
-                wp.saved_files.iter().all(|&(_, ref file_name)| {
-                    !in_incr_comp_dir_sess(sess, file_name).exists()
-                })
+                wp.saved_files
+                    .iter()
+                    .all(|file_name| { !in_incr_comp_dir_sess(sess, file_name).exists() })
             );
         }
     }
@@ -85,7 +85,7 @@ pub fn save_work_product_index(
     debug_assert!({
         new_work_products
             .iter()
-            .flat_map(|(_, wp)| wp.saved_files.iter().map(|&(_, ref name)| name))
+            .flat_map(|(_, wp)| wp.saved_files.iter())
             .map(|name| in_incr_comp_dir_sess(sess, name))
             .all(|path| path.exists())
     });
diff --git a/src/librustc_incremental/persist/work_product.rs b/src/librustc_incremental/persist/work_product.rs
index 4dd81b1df5759..a15ee6d81dbbc 100644
--- a/src/librustc_incremental/persist/work_product.rs
+++ b/src/librustc_incremental/persist/work_product.rs
@@ -2,7 +2,7 @@
 
 use crate::persist::fs::*;
 use rustc_fs_util::link_or_copy;
-use rustc_middle::dep_graph::{WorkProduct, WorkProductFileKind, WorkProductId};
+use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
 use rustc_session::Session;
 use std::fs as std_fs;
 use std::path::PathBuf;
@@ -10,23 +10,18 @@ use std::path::PathBuf;
 pub fn copy_cgu_workproducts_to_incr_comp_cache_dir(
     sess: &Session,
     cgu_name: &str,
-    files: &[(WorkProductFileKind, PathBuf)],
+    files: &[PathBuf],
 ) -> Option<(WorkProductId, WorkProduct)> {
     debug!("copy_cgu_workproducts_to_incr_comp_cache_dir({:?},{:?})", cgu_name, files);
     sess.opts.incremental.as_ref()?;
 
     let saved_files = files
         .iter()
-        .map(|&(kind, ref path)| {
-            let extension = match kind {
-                WorkProductFileKind::Object => "o",
-                WorkProductFileKind::Bytecode => "bc",
-                WorkProductFileKind::BytecodeCompressed => "bc.z",
-            };
-            let file_name = format!("{}.{}", cgu_name, extension);
+        .map(|path| {
+            let file_name = format!("{}.o", cgu_name);
             let path_in_incr_dir = in_incr_comp_dir_sess(sess, &file_name);
             match link_or_copy(path, &path_in_incr_dir) {
-                Ok(_) => Some((kind, file_name)),
+                Ok(_) => Some(file_name),
                 Err(err) => {
                     sess.warn(&format!(
                         "error copying object file `{}` \
@@ -48,7 +43,7 @@ pub fn copy_cgu_workproducts_to_incr_comp_cache_dir(
 }
 
 pub fn delete_workproduct_files(sess: &Session, work_product: &WorkProduct) {
-    for &(_, ref file_name) in &work_product.saved_files {
+    for file_name in &work_product.saved_files {
         let path = in_incr_comp_dir_sess(sess, file_name);
         match std_fs::remove_file(&path) {
             Ok(()) => {}
diff --git a/src/librustc_interface/tests.rs b/src/librustc_interface/tests.rs
index c75f3b279a258..b2e5f6c54edc1 100644
--- a/src/librustc_interface/tests.rs
+++ b/src/librustc_interface/tests.rs
@@ -505,6 +505,10 @@ fn test_codegen_options_tracking_hash() {
     opts = reference.clone();
     opts.cg.linker_plugin_lto = LinkerPluginLto::LinkerPluginAuto;
     assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
+
+    opts = reference.clone();
+    opts.cg.embed_bitcode = Some(true);
+    assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
 }
 
 #[test]
diff --git a/src/librustc_middle/dep_graph/mod.rs b/src/librustc_middle/dep_graph/mod.rs
index f56df19bfb061..34add7f7a99eb 100644
--- a/src/librustc_middle/dep_graph/mod.rs
+++ b/src/librustc_middle/dep_graph/mod.rs
@@ -12,7 +12,7 @@ mod dep_node;
 pub(crate) use rustc_query_system::dep_graph::DepNodeParams;
 pub use rustc_query_system::dep_graph::{
     debug, hash_result, DepContext, DepNodeColor, DepNodeIndex, SerializedDepNodeIndex,
-    WorkProduct, WorkProductFileKind, WorkProductId,
+    WorkProduct, WorkProductId,
 };
 
 pub use dep_node::{label_strs, DepConstructor, DepKind, DepNode, DepNodeExt};
diff --git a/src/librustc_query_system/dep_graph/graph.rs b/src/librustc_query_system/dep_graph/graph.rs
index fa2b51058a378..5f14a09b24daa 100644
--- a/src/librustc_query_system/dep_graph/graph.rs
+++ b/src/librustc_query_system/dep_graph/graph.rs
@@ -861,14 +861,7 @@ impl<K: DepKind> DepGraph<K> {
 pub struct WorkProduct {
     pub cgu_name: String,
     /// Saved files associated with this CGU.
-    pub saved_files: Vec<(WorkProductFileKind, String)>,
-}
-
-#[derive(Clone, Copy, Debug, RustcEncodable, RustcDecodable, PartialEq)]
-pub enum WorkProductFileKind {
-    Object,
-    Bytecode,
-    BytecodeCompressed,
+    pub saved_files: Vec<String>,
 }
 
 #[derive(Clone)]
diff --git a/src/librustc_query_system/dep_graph/mod.rs b/src/librustc_query_system/dep_graph/mod.rs
index fbc91575ede41..4acb70c7e803b 100644
--- a/src/librustc_query_system/dep_graph/mod.rs
+++ b/src/librustc_query_system/dep_graph/mod.rs
@@ -6,7 +6,6 @@ mod query;
 mod serialized;
 
 pub use dep_node::{DepNode, DepNodeParams, WorkProductId};
-pub use graph::WorkProductFileKind;
 pub use graph::{hash_result, DepGraph, DepNodeColor, DepNodeIndex, TaskDeps, WorkProduct};
 pub use prev::PreviousDepGraph;
 pub use query::DepGraphQuery;
diff --git a/src/librustc_session/config.rs b/src/librustc_session/config.rs
index aaf30c583e263..56c577b12c907 100644
--- a/src/librustc_session/config.rs
+++ b/src/librustc_session/config.rs
@@ -1680,6 +1680,16 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
         );
     }
 
+    if let Some(false) = cg.embed_bitcode {
+        match cg.lto {
+            LtoCli::No | LtoCli::Unspecified => {}
+            LtoCli::Yes | LtoCli::NoParam | LtoCli::Thin | LtoCli::Fat => early_error(
+                error_format,
+                "options `-C embed-bitcode=no` and `-C lto` are incompatible",
+            ),
+        }
+    }
+
     let prints = collect_print_requests(&mut cg, &mut debugging_opts, matches, error_format);
 
     let cg = cg;
diff --git a/src/librustc_session/options.rs b/src/librustc_session/options.rs
index 8cd6ca86f4689..53b3065e22c60 100644
--- a/src/librustc_session/options.rs
+++ b/src/librustc_session/options.rs
@@ -727,6 +727,8 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options,
         "compile the program with profiling instrumentation"),
     profile_use: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
         "use the given `.profdata` file for profile-guided optimization"),
+    embed_bitcode: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "embed LLVM bitcode in object files (default: yes)"),
 }
 
 options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
@@ -926,8 +928,6 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
         "run `dsymutil` and delete intermediate object files"),
     ui_testing: Option<bool> = (None, parse_opt_bool, [UNTRACKED],
         "format compiler diagnostics in a way that's better suitable for UI testing"),
-    embed_bitcode: bool = (false, parse_bool, [TRACKED],
-        "embed LLVM bitcode in object files"),
     strip_debuginfo_if_disabled: Option<bool> = (None, parse_opt_bool, [TRACKED],
         "tell the linker to strip debuginfo when building without debuginfo enabled."),
     share_generics: Option<bool> = (None, parse_opt_bool, [TRACKED],
diff --git a/src/llvm-project b/src/llvm-project
index 9f9da27fbdb0b..3ba91917e52bd 160000
--- a/src/llvm-project
+++ b/src/llvm-project
@@ -1 +1 @@
-Subproject commit 9f9da27fbdb0ba7d887f8d2521e082f12b009417
+Subproject commit 3ba91917e52bd66ac37161ad4a1bc87d32aa2e18
diff --git a/src/rustllvm/PassWrapper.cpp b/src/rustllvm/PassWrapper.cpp
index 9e8614e3b6d34..5b955e6c949e2 100644
--- a/src/rustllvm/PassWrapper.cpp
+++ b/src/rustllvm/PassWrapper.cpp
@@ -13,6 +13,8 @@
 #include "llvm/IR/AssemblyAnnotationWriter.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Verifier.h"
+#include "llvm/Object/ObjectFile.h"
+#include "llvm/Object/IRObjectFile.h"
 #include "llvm/Passes/PassBuilder.h"
 #if LLVM_VERSION_GE(9, 0)
 #include "llvm/Passes/StandardInstrumentations.h"
@@ -1496,6 +1498,32 @@ LLVMRustParseBitcodeForLTO(LLVMContextRef Context,
   return wrap(std::move(*SrcOrError).release());
 }
 
+// Find the bitcode section in the object file data and return it as a slice.
+// Fail if the bitcode section is present but empty.
+//
+// On success, the return value is the pointer to the start of the slice and
+// `out_len` is filled with the (non-zero) length. On failure, the return value
+// is `nullptr` and `out_len` is set to zero.
+extern "C" const char*
+LLVMRustGetBitcodeSliceFromObjectData(const char *data,
+                                      size_t len,
+                                      size_t *out_len) {
+  *out_len = 0;
+
+  StringRef Data(data, len);
+  MemoryBufferRef Buffer(Data, ""); // The id is unused.
+
+  Expected<MemoryBufferRef> BitcodeOrError =
+    object::IRObjectFile::findBitcodeInMemBuffer(Buffer);
+  if (!BitcodeOrError) {
+    LLVMRustSetLastError(toString(BitcodeOrError.takeError()).c_str());
+    return nullptr;
+  }
+
+  *out_len = BitcodeOrError->getBufferSize();
+  return BitcodeOrError->getBufferStart();
+}
+
 // Rewrite all `DICompileUnit` pointers to the `DICompileUnit` specified. See
 // the comment in `back/lto.rs` for why this exists.
 extern "C" void
diff --git a/src/test/ui/lto-duplicate-symbols.stderr b/src/test/ui/lto-duplicate-symbols.stderr
index 02204830120a5..713b79bae32e6 100644
--- a/src/test/ui/lto-duplicate-symbols.stderr
+++ b/src/test/ui/lto-duplicate-symbols.stderr
@@ -1,6 +1,6 @@
 warning: Linking globals named 'foo': symbol multiply defined!
 
-error: failed to load bc of "lto_duplicate_symbols2.3a1fbbbh-cgu.0": 
+error: failed to load bc of "lto-duplicate-symbols2.lto_duplicate_symbols2.3a1fbbbh-cgu.0.rcgu.o": 
 
 error: aborting due to previous error; 1 warning emitted