about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBryanskiy <ivakin.kir@gmail.com>2024-09-30 21:07:36 +0300
committerBryanskiy <ivakin.kir@gmail.com>2025-05-04 22:03:15 +0300
commit14535312b522c0524dd94633cc6a49992b12cecd (patch)
treef397262aa2e597623ac7dc9a721da0360398808d
parent62c5f58f57670ce65e7fec34f8c4ba00c27da2d9 (diff)
downloadrust-14535312b522c0524dd94633cc6a49992b12cecd.tar.gz
rust-14535312b522c0524dd94633cc6a49992b12cecd.zip
Initial support for dynamically linked crates
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_ast_lowering/src/item.rs4
-rw-r--r--compiler/rustc_ast_passes/src/ast_validation.rs8
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/mod.rs4
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs35
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/item.rs11
-rw-r--r--compiler/rustc_codegen_gcc/src/back/lto.rs6
-rw-r--r--compiler/rustc_codegen_llvm/src/back/lto.rs3
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs6
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs14
-rw-r--r--compiler/rustc_codegen_ssa/src/back/linker.rs2
-rw-r--r--compiler/rustc_codegen_ssa/src/back/symbol_export.rs6
-rw-r--r--compiler/rustc_codegen_ssa/src/base.rs2
-rw-r--r--compiler/rustc_driver_impl/src/lib.rs7
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs6
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-rw-r--r--compiler/rustc_interface/src/passes.rs28
-rw-r--r--compiler/rustc_metadata/Cargo.toml1
-rw-r--r--compiler/rustc_metadata/src/creader.rs5
-rw-r--r--compiler/rustc_metadata/src/dependency_format.rs71
-rw-r--r--compiler/rustc_metadata/src/locator.rs147
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder.rs11
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs2
-rw-r--r--compiler/rustc_metadata/src/rmeta/encoder.rs21
-rw-r--r--compiler/rustc_metadata/src/rmeta/mod.rs2
-rw-r--r--compiler/rustc_middle/src/arena.rs2
-rw-r--r--compiler/rustc_middle/src/query/mod.rs10
-rw-r--r--compiler/rustc_middle/src/ty/context.rs14
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs4
-rw-r--r--compiler/rustc_mir_build/src/builder/mod.rs4
-rw-r--r--compiler/rustc_passes/messages.ftl19
-rw-r--r--compiler/rustc_passes/src/check_attr.rs1
-rw-r--r--compiler/rustc_passes/src/check_export.rs398
-rw-r--r--compiler/rustc_passes/src/errors.rs54
-rw-r--r--compiler/rustc_passes/src/lang_items.rs7
-rw-r--r--compiler/rustc_passes/src/lib.rs2
-rw-r--r--compiler/rustc_passes/src/reachable.rs10
-rw-r--r--compiler/rustc_passes/src/weak_lang_items.rs3
-rw-r--r--compiler/rustc_session/src/config.rs12
-rw-r--r--compiler/rustc_session/src/cstore.rs1
-rw-r--r--compiler/rustc_session/src/options.rs2
-rw-r--r--compiler/rustc_session/src/output.rs8
-rw-r--r--compiler/rustc_span/src/symbol.rs2
-rw-r--r--compiler/rustc_symbol_mangling/src/export.rs181
-rw-r--r--compiler/rustc_symbol_mangling/src/lib.rs23
-rw-r--r--compiler/rustc_symbol_mangling/src/v0.rs20
-rw-r--r--rustfmt.toml1
-rw-r--r--tests/run-make/export/compile-interface-error/app.rs3
-rw-r--r--tests/run-make/export/compile-interface-error/liblibr.rs5
-rw-r--r--tests/run-make/export/compile-interface-error/rmake.rs9
-rw-r--r--tests/run-make/export/disambiguator/app.rs7
-rw-r--r--tests/run-make/export/disambiguator/libr.rs27
-rw-r--r--tests/run-make/export/disambiguator/rmake.rs12
-rw-r--r--tests/run-make/export/extern-opt/app.rs6
-rw-r--r--tests/run-make/export/extern-opt/libinterface.rs4
-rw-r--r--tests/run-make/export/extern-opt/libr.rs5
-rw-r--r--tests/run-make/export/extern-opt/rmake.rs23
-rw-r--r--tests/run-make/export/simple/app.rs8
-rw-r--r--tests/run-make/export/simple/libr.rs22
-rw-r--r--tests/run-make/export/simple/rmake.rs12
-rw-r--r--tests/ui/attributes/export/crate-type-2.rs2
-rw-r--r--tests/ui/attributes/export/crate-type-2.stderr9
-rw-r--r--tests/ui/attributes/export/crate-type.rs2
-rw-r--r--tests/ui/attributes/export/crate-type.stderr9
-rw-r--r--tests/ui/attributes/export/exportable.rs139
-rw-r--r--tests/ui/attributes/export/exportable.stderr130
-rw-r--r--tests/ui/attributes/export/lang-item.rs8
-rw-r--r--tests/ui/attributes/export/lang-item.stderr8
-rw-r--r--tests/ui/feature-gates/feature-gate-export_stable.rs5
-rw-r--r--tests/ui/feature-gates/feature-gate-export_stable.stderr13
70 files changed, 1534 insertions, 117 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4adee6166f7..774ca16699e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4031,6 +4031,7 @@ dependencies = [
  "rustc_session",
  "rustc_span",
  "rustc_target",
+ "tempfile",
  "tracing",
 ]
 
diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs
index c009abd729d..f48a571b86a 100644
--- a/compiler/rustc_ast_lowering/src/item.rs
+++ b/compiler/rustc_ast_lowering/src/item.rs
@@ -1310,7 +1310,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
             // create a fake body so that the entire rest of the compiler doesn't have to deal with
             // this as a special case.
             return self.lower_fn_body(decl, contract, |this| {
-                if attrs.iter().any(|a| a.has_name(sym::rustc_intrinsic)) {
+                if attrs.iter().any(|a| a.has_name(sym::rustc_intrinsic))
+                    || this.tcx.is_sdylib_interface_build()
+                {
                     let span = this.lower_span(span);
                     let empty_block = hir::Block {
                         hir_id: this.next_id(),
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index 9b64bcc6df4..cbf4f2f5eb2 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -84,6 +84,8 @@ struct AstValidator<'a> {
 
     lint_node_id: NodeId,
 
+    is_sdylib_interface: bool,
+
     lint_buffer: &'a mut LintBuffer,
 }
 
@@ -952,7 +954,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
                 self.check_defaultness(item.span, *defaultness);
 
                 let is_intrinsic = item.attrs.iter().any(|a| a.has_name(sym::rustc_intrinsic));
-                if body.is_none() && !is_intrinsic {
+                if body.is_none() && !is_intrinsic && !self.is_sdylib_interface {
                     self.dcx().emit_err(errors::FnWithoutBody {
                         span: item.span,
                         replace_span: self.ending_semi_or_hi(item.span),
@@ -1441,7 +1443,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
                     });
                 }
                 AssocItemKind::Fn(box Fn { body, .. }) => {
-                    if body.is_none() {
+                    if body.is_none() && !self.is_sdylib_interface {
                         self.dcx().emit_err(errors::AssocFnWithoutBody {
                             span: item.span,
                             replace_span: self.ending_semi_or_hi(item.span),
@@ -1689,6 +1691,7 @@ pub fn check_crate(
     sess: &Session,
     features: &Features,
     krate: &Crate,
+    is_sdylib_interface: bool,
     lints: &mut LintBuffer,
 ) -> bool {
     let mut validator = AstValidator {
@@ -1701,6 +1704,7 @@ pub fn check_crate(
         disallow_tilde_const: Some(TildeConstReason::Item),
         extern_mod_safety: None,
         lint_node_id: CRATE_NODE_ID,
+        is_sdylib_interface,
         lint_buffer: lints,
     };
     visit::walk_crate(&mut validator, krate);
diff --git a/compiler/rustc_ast_pretty/src/pprust/mod.rs b/compiler/rustc_ast_pretty/src/pprust/mod.rs
index 551506f2aef..a05e2bd6a5d 100644
--- a/compiler/rustc_ast_pretty/src/pprust/mod.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/mod.rs
@@ -7,7 +7,9 @@ use std::borrow::Cow;
 use rustc_ast as ast;
 use rustc_ast::token::{Token, TokenKind};
 use rustc_ast::tokenstream::{TokenStream, TokenTree};
-pub use state::{AnnNode, Comments, PpAnn, PrintState, State, print_crate};
+pub use state::{
+    AnnNode, Comments, PpAnn, PrintState, State, print_crate, print_crate_as_interface,
+};
 
 /// Print the token kind precisely, without converting `$crate` into its respective crate name.
 pub fn token_kind_to_string(tok: &TokenKind) -> Cow<'static, str> {
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index 28d5eb87c27..0990c9b27eb 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -221,6 +221,7 @@ pub struct State<'a> {
     pub s: pp::Printer,
     comments: Option<Comments<'a>>,
     ann: &'a (dyn PpAnn + 'a),
+    is_sdylib_interface: bool,
 }
 
 const INDENT_UNIT: isize = 4;
@@ -237,9 +238,36 @@ pub fn print_crate<'a>(
     edition: Edition,
     g: &AttrIdGenerator,
 ) -> String {
+    let mut s = State {
+        s: pp::Printer::new(),
+        comments: Some(Comments::new(sm, filename, input)),
+        ann,
+        is_sdylib_interface: false,
+    };
+
+    print_crate_inner(&mut s, krate, is_expanded, edition, g);
+    s.s.eof()
+}
+
+pub fn print_crate_as_interface(
+    krate: &ast::Crate,
+    edition: Edition,
+    g: &AttrIdGenerator,
+) -> String {
     let mut s =
-        State { s: pp::Printer::new(), comments: Some(Comments::new(sm, filename, input)), ann };
+        State { s: pp::Printer::new(), comments: None, ann: &NoAnn, is_sdylib_interface: true };
 
+    print_crate_inner(&mut s, krate, false, edition, g);
+    s.s.eof()
+}
+
+fn print_crate_inner<'a>(
+    s: &mut State<'a>,
+    krate: &ast::Crate,
+    is_expanded: bool,
+    edition: Edition,
+    g: &AttrIdGenerator,
+) {
     // We need to print shebang before anything else
     // otherwise the resulting code will not compile
     // and shebang will be useless.
@@ -282,8 +310,7 @@ pub fn print_crate<'a>(
         s.print_item(item);
     }
     s.print_remaining_comments();
-    s.ann.post(&mut s, AnnNode::Crate(krate));
-    s.s.eof()
+    s.ann.post(s, AnnNode::Crate(krate));
 }
 
 /// Should two consecutive tokens be printed with a space between them?
@@ -1111,7 +1138,7 @@ impl<'a> PrintState<'a> for State<'a> {
 
 impl<'a> State<'a> {
     pub fn new() -> State<'a> {
-        State { s: pp::Printer::new(), comments: None, ann: &NoAnn }
+        State { s: pp::Printer::new(), comments: None, ann: &NoAnn, is_sdylib_interface: false }
     }
 
     fn commasep_cmnt<T, F, G>(&mut self, b: Breaks, elts: &[T], mut op: F, mut get_span: G)
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs
index 1e02ac8fd5d..70cf2f2a459 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs
@@ -160,6 +160,10 @@ impl<'a> State<'a> {
 
     /// Pretty-prints an item.
     pub(crate) fn print_item(&mut self, item: &ast::Item) {
+        if self.is_sdylib_interface && item.span.is_dummy() {
+            // Do not print prelude for interface files.
+            return;
+        }
         self.hardbreak_if_not_bol();
         self.maybe_print_comment(item.span.lo());
         self.print_outer_attributes(&item.attrs);
@@ -682,6 +686,13 @@ impl<'a> State<'a> {
             self.print_contract(contract);
         }
         if let Some((body, (cb, ib))) = body_cb_ib {
+            if self.is_sdylib_interface {
+                self.word(";");
+                self.end(ib); // end inner head-block
+                self.end(cb); // end outer head-block
+                return;
+            }
+
             self.nbsp();
             self.print_block_with_attrs(body, attrs, cb, ib);
         } else {
diff --git a/compiler/rustc_codegen_gcc/src/back/lto.rs b/compiler/rustc_codegen_gcc/src/back/lto.rs
index e5221c7da31..faeb2643ecb 100644
--- a/compiler/rustc_codegen_gcc/src/back/lto.rs
+++ b/compiler/rustc_codegen_gcc/src/back/lto.rs
@@ -44,7 +44,11 @@ use crate::{GccCodegenBackend, GccContext, SyncContext, to_gcc_opt_level};
 
 pub fn crate_type_allows_lto(crate_type: CrateType) -> bool {
     match crate_type {
-        CrateType::Executable | CrateType::Dylib | CrateType::Staticlib | CrateType::Cdylib => true,
+        CrateType::Executable
+        | CrateType::Dylib
+        | CrateType::Staticlib
+        | CrateType::Cdylib
+        | CrateType::Sdylib => true,
         CrateType::Rlib | CrateType::ProcMacro => false,
     }
 }
diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs
index 39b3a23e0b1..cb329323f5d 100644
--- a/compiler/rustc_codegen_llvm/src/back/lto.rs
+++ b/compiler/rustc_codegen_llvm/src/back/lto.rs
@@ -42,7 +42,8 @@ fn crate_type_allows_lto(crate_type: CrateType) -> bool {
         | CrateType::Dylib
         | CrateType::Staticlib
         | CrateType::Cdylib
-        | CrateType::ProcMacro => true,
+        | CrateType::ProcMacro
+        | CrateType::Sdylib => true,
         CrateType::Rlib => false,
     }
 }
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs
index 4ffe551df09..8f0948b8183 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs
@@ -95,7 +95,11 @@ pub(crate) fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool {
     // in the `.debug_gdb_scripts` section. For that reason, we make sure that the
     // section is only emitted for leaf crates.
     let embed_visualizers = cx.tcx.crate_types().iter().any(|&crate_type| match crate_type {
-        CrateType::Executable | CrateType::Dylib | CrateType::Cdylib | CrateType::Staticlib => {
+        CrateType::Executable
+        | CrateType::Dylib
+        | CrateType::Cdylib
+        | CrateType::Staticlib
+        | CrateType::Sdylib => {
             // These are crate types for which we will embed pretty printers since they
             // are treated as leaf crates.
             true
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 323538969d7..159c17b0af7 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -1053,9 +1053,10 @@ fn link_natively(
                 strip_with_external_utility(sess, stripcmd, out_filename, &["--strip-debug"])
             }
             // Per the manpage, `-x` is the maximum safe strip level for dynamic libraries. (#93988)
-            (Strip::Symbols, CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro) => {
-                strip_with_external_utility(sess, stripcmd, out_filename, &["-x"])
-            }
+            (
+                Strip::Symbols,
+                CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro | CrateType::Sdylib,
+            ) => strip_with_external_utility(sess, stripcmd, out_filename, &["-x"]),
             (Strip::Symbols, _) => {
                 strip_with_external_utility(sess, stripcmd, out_filename, &["--strip-all"])
             }
@@ -1243,8 +1244,10 @@ fn add_sanitizer_libraries(
     // which should be linked to both executables and dynamic libraries.
     // Everywhere else the runtimes are currently distributed as static
     // libraries which should be linked to executables only.
-    if matches!(crate_type, CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro)
-        && !(sess.target.is_like_darwin || sess.target.is_like_msvc)
+    if matches!(
+        crate_type,
+        CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro | CrateType::Sdylib
+    ) && !(sess.target.is_like_darwin || sess.target.is_like_msvc)
     {
         return;
     }
@@ -1938,6 +1941,7 @@ fn add_late_link_args(
     codegen_results: &CodegenResults,
 ) {
     let any_dynamic_crate = crate_type == CrateType::Dylib
+        || crate_type == CrateType::Sdylib
         || codegen_results.crate_info.dependency_formats.iter().any(|(ty, list)| {
             *ty == crate_type && list.iter().any(|&linkage| linkage == Linkage::Dynamic)
         });
diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs
index e1f903726fb..8fc83908efb 100644
--- a/compiler/rustc_codegen_ssa/src/back/linker.rs
+++ b/compiler/rustc_codegen_ssa/src/back/linker.rs
@@ -1817,7 +1817,7 @@ pub(crate) fn linked_symbols(
     crate_type: CrateType,
 ) -> Vec<(String, SymbolExportKind)> {
     match crate_type {
-        CrateType::Executable | CrateType::Cdylib | CrateType::Dylib => (),
+        CrateType::Executable | CrateType::Cdylib | CrateType::Dylib | CrateType::Sdylib => (),
         CrateType::Staticlib | CrateType::ProcMacro | CrateType::Rlib => {
             return Vec::new();
         }
diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
index 50fb08b2868..1bfdbc0b620 100644
--- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
+++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
@@ -29,7 +29,7 @@ fn crate_export_threshold(crate_type: CrateType) -> SymbolExportLevel {
         CrateType::Executable | CrateType::Staticlib | CrateType::ProcMacro | CrateType::Cdylib => {
             SymbolExportLevel::C
         }
-        CrateType::Rlib | CrateType::Dylib => SymbolExportLevel::Rust,
+        CrateType::Rlib | CrateType::Dylib | CrateType::Sdylib => SymbolExportLevel::Rust,
     }
 }
 
@@ -45,7 +45,7 @@ pub fn crates_export_threshold(crate_types: &[CrateType]) -> SymbolExportLevel {
 }
 
 fn reachable_non_generics_provider(tcx: TyCtxt<'_>, _: LocalCrate) -> DefIdMap<SymbolExportInfo> {
-    if !tcx.sess.opts.output_types.should_codegen() {
+    if !tcx.sess.opts.output_types.should_codegen() && !tcx.is_sdylib_interface_build() {
         return Default::default();
     }
 
@@ -168,7 +168,7 @@ fn exported_symbols_provider_local<'tcx>(
     tcx: TyCtxt<'tcx>,
     _: LocalCrate,
 ) -> &'tcx [(ExportedSymbol<'tcx>, SymbolExportInfo)] {
-    if !tcx.sess.opts.output_types.should_codegen() {
+    if !tcx.sess.opts.output_types.should_codegen() && !tcx.is_sdylib_interface_build() {
         return &[];
     }
 
diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index f5480da2808..89439e40937 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -1092,7 +1092,7 @@ impl CrateInfo {
         }
 
         let embed_visualizers = tcx.crate_types().iter().any(|&crate_type| match crate_type {
-            CrateType::Executable | CrateType::Dylib | CrateType::Cdylib => {
+            CrateType::Executable | CrateType::Dylib | CrateType::Cdylib | CrateType::Sdylib => {
                 // These are crate types for which we invoke the linker and can embed
                 // NatVis visualizers.
                 true
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index d18fa892814..fdf8053b15a 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -54,8 +54,8 @@ use rustc_metadata::locator;
 use rustc_middle::ty::TyCtxt;
 use rustc_parse::{new_parser_from_file, new_parser_from_source_str, unwrap_or_emit_fatal};
 use rustc_session::config::{
-    CG_OPTIONS, ErrorOutputType, Input, OptionDesc, OutFileName, OutputType, UnstableOptions,
-    Z_OPTIONS, nightly_options, parse_target_triple,
+    CG_OPTIONS, CrateType, ErrorOutputType, Input, OptionDesc, OutFileName, OutputType,
+    UnstableOptions, Z_OPTIONS, nightly_options, parse_target_triple,
 };
 use rustc_session::getopts::{self, Matches};
 use rustc_session::lint::{Lint, LintId};
@@ -352,6 +352,8 @@ pub fn run_compiler(at_args: &[String], callbacks: &mut (dyn Callbacks + Send))
 
             passes::write_dep_info(tcx);
 
+            passes::write_interface(tcx);
+
             if sess.opts.output_types.contains_key(&OutputType::DepInfo)
                 && sess.opts.output_types.len() == 1
             {
@@ -816,6 +818,7 @@ fn print_crate_info(
                 let supported_crate_types = CRATE_TYPES
                     .iter()
                     .filter(|(_, crate_type)| !invalid_output_for_target(&sess, *crate_type))
+                    .filter(|(_, crate_type)| *crate_type != CrateType::Sdylib)
                     .map(|(crate_type_sym, _)| *crate_type_sym)
                     .collect::<BTreeSet<_>>();
                 for supported_crate_type in supported_crate_types {
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index a5e6b1c00d6..c117e0fcf7c 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -536,6 +536,12 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     // Unstable attributes:
     // ==========================================================================
 
+    // Linking:
+    gated!(
+        export_stable, Normal, template!(Word), WarnFollowing,
+        EncodeCrossCrate::No, experimental!(export_stable)
+    ),
+
     // Testing:
     gated!(
         test_runner, CrateLevel, template!(List: "path"), ErrorFollowing,
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index 1a011dfff3f..f1bc2c5ea88 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -485,6 +485,8 @@ declare_features! (
     (unstable, explicit_extern_abis, "CURRENT_RUSTC_VERSION", Some(134986)),
     /// Allows explicit tail calls via `become` expression.
     (incomplete, explicit_tail_calls, "1.72.0", Some(112788)),
+    /// Allows using `#[export_stable]` which indicates that an item is exportable.
+    (incomplete, export_stable, "CURRENT_RUSTC_VERSION", Some(139939)),
     /// Allows using `aapcs`, `efiapi`, `sysv64` and `win64` as calling conventions
     /// for functions with varargs.
     (unstable, extended_varargs_abi_support, "1.65.0", Some(100189)),
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 493b1d5eaa9..f4d11a7c0be 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -31,10 +31,11 @@ use rustc_resolve::Resolver;
 use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType};
 use rustc_session::cstore::Untracked;
 use rustc_session::output::{collect_crate_types, filename_for_input};
+use rustc_session::parse::feature_err;
 use rustc_session::search_paths::PathKind;
 use rustc_session::{Limit, Session};
 use rustc_span::{
-    ErrorGuaranteed, FileName, SourceFileHash, SourceFileHashAlgorithm, Span, Symbol, sym,
+    DUMMY_SP, ErrorGuaranteed, FileName, SourceFileHash, SourceFileHashAlgorithm, Span, Symbol, sym,
 };
 use rustc_target::spec::PanicStrategy;
 use rustc_trait_selection::traits;
@@ -237,6 +238,7 @@ fn configure_and_expand(
             sess,
             features,
             &krate,
+            tcx.is_sdylib_interface_build(),
             resolver.lint_buffer(),
         )
     });
@@ -253,6 +255,9 @@ fn configure_and_expand(
             sess.dcx().emit_err(errors::MixedProcMacroCrate);
         }
     }
+    if crate_types.contains(&CrateType::Sdylib) && !tcx.features().export_stable() {
+        feature_err(sess, sym::export_stable, DUMMY_SP, "`sdylib` crate type is unstable").emit();
+    }
 
     if is_proc_macro_crate && sess.panic_strategy() == PanicStrategy::Abort {
         sess.dcx().emit_warn(errors::ProcMacroCratePanicAbort);
@@ -742,6 +747,25 @@ pub fn write_dep_info(tcx: TyCtxt<'_>) {
     }
 }
 
+pub fn write_interface<'tcx>(tcx: TyCtxt<'tcx>) {
+    if !tcx.crate_types().contains(&rustc_session::config::CrateType::Sdylib) {
+        return;
+    }
+    let _timer = tcx.sess.timer("write_interface");
+    let (_, krate) = &*tcx.resolver_for_lowering().borrow();
+
+    let krate = rustc_ast_pretty::pprust::print_crate_as_interface(
+        krate,
+        tcx.sess.psess.edition,
+        &tcx.sess.psess.attr_id_generator,
+    );
+    let export_output = tcx.output_filenames(()).interface_path();
+    let mut file = fs::File::create_buffered(export_output).unwrap();
+    if let Err(err) = write!(file, "{}", krate) {
+        tcx.dcx().fatal(format!("error writing interface file: {}", err));
+    }
+}
+
 pub static DEFAULT_QUERY_PROVIDERS: LazyLock<Providers> = LazyLock::new(|| {
     let providers = &mut Providers::default();
     providers.analysis = analysis;
@@ -930,6 +954,8 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
                 CStore::from_tcx(tcx).report_unused_deps(tcx);
             },
             {
+                tcx.ensure_ok().exportable_items(LOCAL_CRATE);
+                tcx.ensure_ok().stable_order_of_exportable_impls(LOCAL_CRATE);
                 tcx.par_hir_for_each_module(|module| {
                     tcx.ensure_ok().check_mod_loops(module);
                     tcx.ensure_ok().check_mod_attrs(module);
diff --git a/compiler/rustc_metadata/Cargo.toml b/compiler/rustc_metadata/Cargo.toml
index b11f9260be7..26878c488b7 100644
--- a/compiler/rustc_metadata/Cargo.toml
+++ b/compiler/rustc_metadata/Cargo.toml
@@ -26,6 +26,7 @@ rustc_serialize = { path = "../rustc_serialize" }
 rustc_session = { path = "../rustc_session" }
 rustc_span = { path = "../rustc_span" }
 rustc_target = { path = "../rustc_target" }
+tempfile = "3.7.1"
 tracing = "0.1"
 # tidy-alphabetical-end
 
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index 1c3222bbfeb..07fb2de8a3e 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -148,7 +148,7 @@ impl<'a> std::fmt::Debug for CrateDump<'a> {
             writeln!(fmt, "  hash: {}", data.hash())?;
             writeln!(fmt, "  reqd: {:?}", data.dep_kind())?;
             writeln!(fmt, "  priv: {:?}", data.is_private_dep())?;
-            let CrateSource { dylib, rlib, rmeta } = data.source();
+            let CrateSource { dylib, rlib, rmeta, sdylib_interface } = data.source();
             if let Some(dylib) = dylib {
                 writeln!(fmt, "  dylib: {}", dylib.0.display())?;
             }
@@ -158,6 +158,9 @@ impl<'a> std::fmt::Debug for CrateDump<'a> {
             if let Some(rmeta) = rmeta {
                 writeln!(fmt, "   rmeta: {}", rmeta.0.display())?;
             }
+            if let Some(sdylib_interface) = sdylib_interface {
+                writeln!(fmt, "   sdylib interface: {}", sdylib_interface.0.display())?;
+            }
         }
         Ok(())
     }
diff --git a/compiler/rustc_metadata/src/dependency_format.rs b/compiler/rustc_metadata/src/dependency_format.rs
index be31aa629c8..fcae33c73c9 100644
--- a/compiler/rustc_metadata/src/dependency_format.rs
+++ b/compiler/rustc_metadata/src/dependency_format.rs
@@ -88,45 +88,42 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
         return IndexVec::new();
     }
 
-    let preferred_linkage = match ty {
-        // Generating a dylib without `-C prefer-dynamic` means that we're going
-        // to try to eagerly statically link all dependencies. This is normally
-        // done for end-product dylibs, not intermediate products.
-        //
-        // Treat cdylibs and staticlibs similarly. If `-C prefer-dynamic` is set,
-        // the caller may be code-size conscious, but without it, it makes sense
-        // to statically link a cdylib or staticlib. For staticlibs we use
-        // `-Z staticlib-prefer-dynamic` for now. This may be merged into
-        // `-C prefer-dynamic` in the future.
-        CrateType::Dylib | CrateType::Cdylib => {
-            if sess.opts.cg.prefer_dynamic {
-                Linkage::Dynamic
-            } else {
-                Linkage::Static
+    let preferred_linkage =
+        match ty {
+            // Generating a dylib without `-C prefer-dynamic` means that we're going
+            // to try to eagerly statically link all dependencies. This is normally
+            // done for end-product dylibs, not intermediate products.
+            //
+            // Treat cdylibs and staticlibs similarly. If `-C prefer-dynamic` is set,
+            // the caller may be code-size conscious, but without it, it makes sense
+            // to statically link a cdylib or staticlib. For staticlibs we use
+            // `-Z staticlib-prefer-dynamic` for now. This may be merged into
+            // `-C prefer-dynamic` in the future.
+            CrateType::Dylib | CrateType::Cdylib | CrateType::Sdylib => {
+                if sess.opts.cg.prefer_dynamic { Linkage::Dynamic } else { Linkage::Static }
             }
-        }
-        CrateType::Staticlib => {
-            if sess.opts.unstable_opts.staticlib_prefer_dynamic {
-                Linkage::Dynamic
-            } else {
-                Linkage::Static
+            CrateType::Staticlib => {
+                if sess.opts.unstable_opts.staticlib_prefer_dynamic {
+                    Linkage::Dynamic
+                } else {
+                    Linkage::Static
+                }
             }
-        }
 
-        // If the global prefer_dynamic switch is turned off, or the final
-        // executable will be statically linked, prefer static crate linkage.
-        CrateType::Executable if !sess.opts.cg.prefer_dynamic || sess.crt_static(Some(ty)) => {
-            Linkage::Static
-        }
-        CrateType::Executable => Linkage::Dynamic,
+            // If the global prefer_dynamic switch is turned off, or the final
+            // executable will be statically linked, prefer static crate linkage.
+            CrateType::Executable if !sess.opts.cg.prefer_dynamic || sess.crt_static(Some(ty)) => {
+                Linkage::Static
+            }
+            CrateType::Executable => Linkage::Dynamic,
 
-        // proc-macro crates are mostly cdylibs, but we also need metadata.
-        CrateType::ProcMacro => Linkage::Static,
+            // proc-macro crates are mostly cdylibs, but we also need metadata.
+            CrateType::ProcMacro => Linkage::Static,
 
-        // No linkage happens with rlibs, we just needed the metadata (which we
-        // got long ago), so don't bother with anything.
-        CrateType::Rlib => Linkage::NotLinked,
-    };
+            // No linkage happens with rlibs, we just needed the metadata (which we
+            // got long ago), so don't bother with anything.
+            CrateType::Rlib => Linkage::NotLinked,
+        };
 
     let mut unavailable_as_static = Vec::new();
 
@@ -165,7 +162,9 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
 
     let all_dylibs = || {
         tcx.crates(()).iter().filter(|&&cnum| {
-            !tcx.dep_kind(cnum).macros_only() && tcx.used_crate_source(cnum).dylib.is_some()
+            !tcx.dep_kind(cnum).macros_only()
+                && (tcx.used_crate_source(cnum).dylib.is_some()
+                    || tcx.used_crate_source(cnum).sdylib_interface.is_some())
         })
     };
 
@@ -273,7 +272,7 @@ fn calculate_type(tcx: TyCtxt<'_>, ty: CrateType) -> DependencyList {
         match *kind {
             Linkage::NotLinked | Linkage::IncludedFromDylib => {}
             Linkage::Static if src.rlib.is_some() => continue,
-            Linkage::Dynamic if src.dylib.is_some() => continue,
+            Linkage::Dynamic if src.dylib.is_some() || src.sdylib_interface.is_some() => continue,
             kind => {
                 let kind = match kind {
                     Linkage::Static => "rlib",
diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs
index f0a898d678c..10123cb9a9d 100644
--- a/compiler/rustc_metadata/src/locator.rs
+++ b/compiler/rustc_metadata/src/locator.rs
@@ -220,7 +220,7 @@ use std::{cmp, fmt};
 
 use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
 use rustc_data_structures::memmap::Mmap;
-use rustc_data_structures::owned_slice::slice_owned;
+use rustc_data_structures::owned_slice::{OwnedSlice, slice_owned};
 use rustc_data_structures::svh::Svh;
 use rustc_errors::{DiagArgValue, IntoDiagArg};
 use rustc_fs_util::try_canonicalize;
@@ -231,6 +231,7 @@ use rustc_session::search_paths::PathKind;
 use rustc_session::utils::CanonicalizedPath;
 use rustc_span::{Span, Symbol};
 use rustc_target::spec::{Target, TargetTuple};
+use tempfile::Builder as TempFileBuilder;
 use tracing::{debug, info};
 
 use crate::creader::{Library, MetadataLoader};
@@ -277,6 +278,7 @@ pub(crate) enum CrateFlavor {
     Rlib,
     Rmeta,
     Dylib,
+    SDylib,
 }
 
 impl fmt::Display for CrateFlavor {
@@ -285,6 +287,7 @@ impl fmt::Display for CrateFlavor {
             CrateFlavor::Rlib => "rlib",
             CrateFlavor::Rmeta => "rmeta",
             CrateFlavor::Dylib => "dylib",
+            CrateFlavor::SDylib => "sdylib",
         })
     }
 }
@@ -295,6 +298,7 @@ impl IntoDiagArg for CrateFlavor {
             CrateFlavor::Rlib => DiagArgValue::Str(Cow::Borrowed("rlib")),
             CrateFlavor::Rmeta => DiagArgValue::Str(Cow::Borrowed("rmeta")),
             CrateFlavor::Dylib => DiagArgValue::Str(Cow::Borrowed("dylib")),
+            CrateFlavor::SDylib => DiagArgValue::Str(Cow::Borrowed("sdylib")),
         }
     }
 }
@@ -379,14 +383,18 @@ impl<'a> CrateLocator<'a> {
             &format!("{}{}{}", self.target.dll_prefix, self.crate_name, extra_prefix);
         let staticlib_prefix =
             &format!("{}{}{}", self.target.staticlib_prefix, self.crate_name, extra_prefix);
+        let interface_prefix = rmeta_prefix;
 
         let rmeta_suffix = ".rmeta";
         let rlib_suffix = ".rlib";
         let dylib_suffix = &self.target.dll_suffix;
         let staticlib_suffix = &self.target.staticlib_suffix;
+        let interface_suffix = ".rs";
 
-        let mut candidates: FxIndexMap<_, (FxIndexMap<_, _>, FxIndexMap<_, _>, FxIndexMap<_, _>)> =
-            Default::default();
+        let mut candidates: FxIndexMap<
+            _,
+            (FxIndexMap<_, _>, FxIndexMap<_, _>, FxIndexMap<_, _>, FxIndexMap<_, _>),
+        > = Default::default();
 
         // First, find all possible candidate rlibs and dylibs purely based on
         // the name of the files themselves. We're trying to match against an
@@ -417,6 +425,7 @@ impl<'a> CrateLocator<'a> {
                 (rlib_prefix.as_str(), rlib_suffix, CrateFlavor::Rlib),
                 (rmeta_prefix.as_str(), rmeta_suffix, CrateFlavor::Rmeta),
                 (dylib_prefix, dylib_suffix, CrateFlavor::Dylib),
+                (interface_prefix, interface_suffix, CrateFlavor::SDylib),
             ] {
                 if prefix == staticlib_prefix && suffix == staticlib_suffix {
                     should_check_staticlibs = false;
@@ -425,7 +434,7 @@ impl<'a> CrateLocator<'a> {
                     for (hash, spf) in matches {
                         info!("lib candidate: {}", spf.path.display());
 
-                        let (rlibs, rmetas, dylibs) =
+                        let (rlibs, rmetas, dylibs, interfaces) =
                             candidates.entry(hash.to_string()).or_default();
                         {
                             // As a perforamnce optimisation we canonicalize the path and skip
@@ -446,6 +455,7 @@ impl<'a> CrateLocator<'a> {
                             CrateFlavor::Rlib => rlibs.insert(path, search_path.kind),
                             CrateFlavor::Rmeta => rmetas.insert(path, search_path.kind),
                             CrateFlavor::Dylib => dylibs.insert(path, search_path.kind),
+                            CrateFlavor::SDylib => interfaces.insert(path, search_path.kind),
                         };
                     }
                 }
@@ -472,8 +482,8 @@ impl<'a> CrateLocator<'a> {
         // libraries corresponds to the crate id and hash criteria that this
         // search is being performed for.
         let mut libraries = FxIndexMap::default();
-        for (_hash, (rlibs, rmetas, dylibs)) in candidates {
-            if let Some((svh, lib)) = self.extract_lib(rlibs, rmetas, dylibs)? {
+        for (_hash, (rlibs, rmetas, dylibs, interfaces)) in candidates {
+            if let Some((svh, lib)) = self.extract_lib(rlibs, rmetas, dylibs, interfaces)? {
                 libraries.insert(svh, lib);
             }
         }
@@ -508,6 +518,7 @@ impl<'a> CrateLocator<'a> {
         rlibs: FxIndexMap<PathBuf, PathKind>,
         rmetas: FxIndexMap<PathBuf, PathKind>,
         dylibs: FxIndexMap<PathBuf, PathKind>,
+        interfaces: FxIndexMap<PathBuf, PathKind>,
     ) -> Result<Option<(Svh, Library)>, CrateError> {
         let mut slot = None;
         // Order here matters, rmeta should come first.
@@ -515,12 +526,17 @@ impl<'a> CrateLocator<'a> {
         // Make sure there's at most one rlib and at most one dylib.
         //
         // See comment in `extract_one` below.
-        let source = CrateSource {
-            rmeta: self.extract_one(rmetas, CrateFlavor::Rmeta, &mut slot)?,
-            rlib: self.extract_one(rlibs, CrateFlavor::Rlib, &mut slot)?,
-            dylib: self.extract_one(dylibs, CrateFlavor::Dylib, &mut slot)?,
-        };
-        Ok(slot.map(|(svh, metadata, _)| (svh, Library { source, metadata })))
+        let rmeta = self.extract_one(rmetas, CrateFlavor::Rmeta, &mut slot)?;
+        let rlib = self.extract_one(rlibs, CrateFlavor::Rlib, &mut slot)?;
+        let sdylib_interface = self.extract_one(interfaces, CrateFlavor::SDylib, &mut slot)?;
+        let dylib = self.extract_one(dylibs, CrateFlavor::Dylib, &mut slot)?;
+
+        if sdylib_interface.is_some() && dylib.is_none() {
+            return Err(CrateError::FullMetadataNotFound(self.crate_name, CrateFlavor::SDylib));
+        }
+
+        let source = CrateSource { rmeta, rlib, dylib, sdylib_interface };
+        Ok(slot.map(|(svh, metadata, _, _)| (svh, Library { source, metadata })))
     }
 
     fn needs_crate_flavor(&self, flavor: CrateFlavor) -> bool {
@@ -550,7 +566,7 @@ impl<'a> CrateLocator<'a> {
         &mut self,
         m: FxIndexMap<PathBuf, PathKind>,
         flavor: CrateFlavor,
-        slot: &mut Option<(Svh, MetadataBlob, PathBuf)>,
+        slot: &mut Option<(Svh, MetadataBlob, PathBuf, CrateFlavor)>,
     ) -> Result<Option<(PathBuf, PathKind)>, CrateError> {
         // If we are producing an rlib, and we've already loaded metadata, then
         // we should not attempt to discover further crate sources (unless we're
@@ -586,6 +602,7 @@ impl<'a> CrateLocator<'a> {
                 &lib,
                 self.metadata_loader,
                 self.cfg_version,
+                Some(self.crate_name),
             ) {
                 Ok(blob) => {
                     if let Some(h) = self.crate_matches(&blob, &lib) {
@@ -610,6 +627,11 @@ impl<'a> CrateLocator<'a> {
                 }
                 Err(MetadataError::LoadFailure(err)) => {
                     info!("no metadata found: {}", err);
+                    // Metadata was loaded from interface file earlier.
+                    if let Some((.., CrateFlavor::SDylib)) = slot {
+                        ret = Some((lib, kind));
+                        continue;
+                    }
                     // The file was present and created by the same compiler version, but we
                     // couldn't load it for some reason. Give a hard error instead of silently
                     // ignoring it, but only if we would have given an error anyway.
@@ -679,7 +701,7 @@ impl<'a> CrateLocator<'a> {
                     return Err(CrateError::FullMetadataNotFound(self.crate_name, flavor));
                 }
             } else {
-                *slot = Some((hash, metadata, lib.clone()));
+                *slot = Some((hash, metadata, lib.clone(), flavor));
             }
             ret = Some((lib, kind));
         }
@@ -736,6 +758,7 @@ impl<'a> CrateLocator<'a> {
         let mut rlibs = FxIndexMap::default();
         let mut rmetas = FxIndexMap::default();
         let mut dylibs = FxIndexMap::default();
+        let mut sdylib_interfaces = FxIndexMap::default();
         for loc in &self.exact_paths {
             let loc_canon = loc.canonicalized();
             let loc_orig = loc.original();
@@ -763,6 +786,9 @@ impl<'a> CrateLocator<'a> {
                     rmetas.insert(loc_canon.clone(), PathKind::ExternFlag);
                     continue;
                 }
+                if file.ends_with(".rs") {
+                    sdylib_interfaces.insert(loc_canon.clone(), PathKind::ExternFlag);
+                }
             }
             let dll_prefix = self.target.dll_prefix.as_ref();
             let dll_suffix = self.target.dll_suffix.as_ref();
@@ -776,7 +802,8 @@ impl<'a> CrateLocator<'a> {
         }
 
         // Extract the dylib/rlib/rmeta triple.
-        self.extract_lib(rlibs, rmetas, dylibs).map(|opt| opt.map(|(_, lib)| lib))
+        self.extract_lib(rlibs, rmetas, dylibs, sdylib_interfaces)
+            .map(|opt| opt.map(|(_, lib)| lib))
     }
 
     pub(crate) fn into_error(self, dep_root: Option<CratePaths>) -> CrateError {
@@ -797,6 +824,7 @@ fn get_metadata_section<'p>(
     filename: &'p Path,
     loader: &dyn MetadataLoader,
     cfg_version: &'static str,
+    crate_name: Option<Symbol>,
 ) -> Result<MetadataBlob, MetadataError<'p>> {
     if !filename.exists() {
         return Err(MetadataError::NotPresent(filename));
@@ -805,6 +833,55 @@ fn get_metadata_section<'p>(
         CrateFlavor::Rlib => {
             loader.get_rlib_metadata(target, filename).map_err(MetadataError::LoadFailure)?
         }
+        CrateFlavor::SDylib => {
+            let compiler = std::env::current_exe().map_err(|_err| {
+                MetadataError::LoadFailure(
+                    "couldn't obtain current compiler binary when loading sdylib interface"
+                        .to_string(),
+                )
+            })?;
+
+            let tmp_path = match TempFileBuilder::new().prefix("rustc").tempdir() {
+                Ok(tmp_path) => tmp_path,
+                Err(error) => {
+                    return Err(MetadataError::LoadFailure(format!(
+                        "couldn't create a temp dir: {}",
+                        error
+                    )));
+                }
+            };
+
+            let crate_name = crate_name.unwrap();
+            debug!("compiling {}", filename.display());
+            // FIXME: This will need to be done either within the current compiler session or
+            // as a separate compiler session in the same process.
+            let res = std::process::Command::new(compiler)
+                .arg(&filename)
+                .arg("--emit=metadata")
+                .arg(format!("--crate-name={}", crate_name))
+                .arg(format!("--out-dir={}", tmp_path.path().display()))
+                .arg("-Zbuild-sdylib-interface")
+                .output()
+                .map_err(|err| {
+                    MetadataError::LoadFailure(format!("couldn't compile interface: {}", err))
+                })?;
+
+            if !res.status.success() {
+                return Err(MetadataError::LoadFailure(format!(
+                    "couldn't compile interface: {}",
+                    std::str::from_utf8(&res.stderr).unwrap_or_default()
+                )));
+            }
+
+            // Load interface metadata instead of crate metadata.
+            let interface_metadata_name = format!("lib{}.rmeta", crate_name);
+            let rmeta_file = tmp_path.path().join(interface_metadata_name);
+            debug!("loading interface metadata from {}", rmeta_file.display());
+            let rmeta = get_rmeta_metadata_section(&rmeta_file)?;
+            let _ = std::fs::remove_file(rmeta_file);
+
+            rmeta
+        }
         CrateFlavor::Dylib => {
             let buf =
                 loader.get_dylib_metadata(target, filename).map_err(MetadataError::LoadFailure)?;
@@ -834,24 +911,7 @@ fn get_metadata_section<'p>(
             // Header is okay -> inflate the actual metadata
             buf.slice(|buf| &buf[data_start..(data_start + metadata_len)])
         }
-        CrateFlavor::Rmeta => {
-            // mmap the file, because only a small fraction of it is read.
-            let file = std::fs::File::open(filename).map_err(|_| {
-                MetadataError::LoadFailure(format!(
-                    "failed to open rmeta metadata: '{}'",
-                    filename.display()
-                ))
-            })?;
-            let mmap = unsafe { Mmap::map(file) };
-            let mmap = mmap.map_err(|_| {
-                MetadataError::LoadFailure(format!(
-                    "failed to mmap rmeta metadata: '{}'",
-                    filename.display()
-                ))
-            })?;
-
-            slice_owned(mmap, Deref::deref)
-        }
+        CrateFlavor::Rmeta => get_rmeta_metadata_section(filename)?,
     };
     let Ok(blob) = MetadataBlob::new(raw_bytes) else {
         return Err(MetadataError::LoadFailure(format!(
@@ -877,6 +937,25 @@ fn get_metadata_section<'p>(
     }
 }
 
+fn get_rmeta_metadata_section<'a, 'p>(filename: &'p Path) -> Result<OwnedSlice, MetadataError<'a>> {
+    // mmap the file, because only a small fraction of it is read.
+    let file = std::fs::File::open(filename).map_err(|_| {
+        MetadataError::LoadFailure(format!(
+            "failed to open rmeta metadata: '{}'",
+            filename.display()
+        ))
+    })?;
+    let mmap = unsafe { Mmap::map(file) };
+    let mmap = mmap.map_err(|_| {
+        MetadataError::LoadFailure(format!(
+            "failed to mmap rmeta metadata: '{}'",
+            filename.display()
+        ))
+    })?;
+
+    Ok(slice_owned(mmap, Deref::deref))
+}
+
 /// A diagnostic function for dumping crate metadata to an output stream.
 pub fn list_file_metadata(
     target: &Target,
@@ -887,7 +966,7 @@ pub fn list_file_metadata(
     cfg_version: &'static str,
 ) -> IoResult<()> {
     let flavor = get_flavor_from_path(path);
-    match get_metadata_section(target, flavor, path, metadata_loader, cfg_version) {
+    match get_metadata_section(target, flavor, path, metadata_loader, cfg_version, None) {
         Ok(metadata) => metadata.list_crate_metadata(out, ls_kinds),
         Err(msg) => write!(out, "{msg}\n"),
     }
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index 3c2245347f9..bd813cadedc 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -1489,6 +1489,17 @@ impl<'a> CrateMetadataRef<'a> {
         tcx.arena.alloc_from_iter(self.root.lang_items_missing.decode(self))
     }
 
+    fn get_exportable_items(self) -> impl Iterator<Item = DefId> {
+        self.root.exportable_items.decode(self).map(move |index| self.local_def_id(index))
+    }
+
+    fn get_stable_order_of_exportable_impls(self) -> impl Iterator<Item = (DefId, usize)> {
+        self.root
+            .stable_order_of_exportable_impls
+            .decode(self)
+            .map(move |v| (self.local_def_id(v.0), v.1))
+    }
+
     fn exported_symbols<'tcx>(
         self,
         tcx: TyCtxt<'tcx>,
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index ecc2dcc5318..76bae39ef8c 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -406,6 +406,8 @@ provide! { tcx, def_id, other, cdata,
     used_crate_source => { Arc::clone(&cdata.source) }
     debugger_visualizers => { cdata.get_debugger_visualizers() }
 
+    exportable_items => { tcx.arena.alloc_from_iter(cdata.get_exportable_items()) }
+    stable_order_of_exportable_impls => { tcx.arena.alloc(cdata.get_stable_order_of_exportable_impls().collect()) }
     exported_symbols => {
         let syms = cdata.exported_symbols(tcx);
 
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index 3ea61d1b40a..bbff570d6c6 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -673,6 +673,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
         let debugger_visualizers =
             stat!("debugger-visualizers", || self.encode_debugger_visualizers());
 
+        let exportable_items = stat!("exportable-items", || self.encode_exportable_items());
+
+        let stable_order_of_exportable_impls =
+            stat!("exportable-items", || self.encode_stable_order_of_exportable_impls());
+
         // Encode exported symbols info. This is prefetched in `encode_metadata`.
         let exported_symbols = stat!("exported-symbols", || {
             self.encode_exported_symbols(tcx.exported_symbols(LOCAL_CRATE))
@@ -740,6 +745,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
                 traits,
                 impls,
                 incoherent_impls,
+                exportable_items,
+                stable_order_of_exportable_impls,
                 exported_symbols,
                 interpret_alloc_index,
                 tables,
@@ -2149,6 +2156,20 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
         self.lazy_array(&all_impls)
     }
 
+    fn encode_exportable_items(&mut self) -> LazyArray<DefIndex> {
+        empty_proc_macro!(self);
+        self.lazy_array(self.tcx.exportable_items(LOCAL_CRATE).iter().map(|def_id| def_id.index))
+    }
+
+    fn encode_stable_order_of_exportable_impls(&mut self) -> LazyArray<(DefIndex, usize)> {
+        empty_proc_macro!(self);
+        let stable_order_of_exportable_impls =
+            self.tcx.stable_order_of_exportable_impls(LOCAL_CRATE);
+        self.lazy_array(
+            stable_order_of_exportable_impls.iter().map(|(def_id, idx)| (def_id.index, *idx)),
+        )
+    }
+
     // Encodes all symbols exported from this crate into the metadata.
     //
     // This pass is seeded off the reachability list calculated in the
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index 5aa81f41b7b..c86cf567283 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -280,6 +280,8 @@ pub(crate) struct CrateRoot {
     tables: LazyTables,
     debugger_visualizers: LazyArray<DebuggerVisualizerFile>,
 
+    exportable_items: LazyArray<DefIndex>,
+    stable_order_of_exportable_impls: LazyArray<(DefIndex, usize)>,
     exported_symbols: LazyArray<(ExportedSymbol<'static>, SymbolExportInfo)>,
 
     syntax_contexts: SyntaxContextTable,
diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs
index 2dcb0de92b7..a0f45974089 100644
--- a/compiler/rustc_middle/src/arena.rs
+++ b/compiler/rustc_middle/src/arena.rs
@@ -91,6 +91,8 @@ macro_rules! arena_types {
             [] autodiff_item: rustc_ast::expand::autodiff_attrs::AutoDiffItem,
             [] ordered_name_set: rustc_data_structures::fx::FxIndexSet<rustc_span::Symbol>,
             [] valtree: rustc_middle::ty::ValTreeKind<'tcx>,
+            [] stable_order_of_exportable_impls:
+                rustc_data_structures::fx::FxIndexMap<rustc_hir::def_id::DefId, usize>,
 
             // Note that this deliberately duplicates items in the `rustc_hir::arena`,
             // since we need to allocate this type on both the `rustc_hir` arena
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 88f4c4ae4d3..6af9d4aae30 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -2239,6 +2239,16 @@ rustc_queries! {
         separate_provide_extern
     }
 
+    query stable_order_of_exportable_impls(_: CrateNum) -> &'tcx FxIndexMap<DefId, usize> {
+        desc { "fetching the stable impl's order" }
+        separate_provide_extern
+    }
+
+    query exportable_items(_: CrateNum) -> &'tcx [DefId] {
+        desc { "fetching all exportable items in a crate" }
+        separate_provide_extern
+    }
+
     /// The list of symbols exported from the given crate.
     ///
     /// - All names contained in `exported_symbols(cnum)` are guaranteed to
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 0f7f8527088..d660234618e 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -1828,9 +1828,10 @@ impl<'tcx> TyCtxt<'tcx> {
         self.crate_types()
             .iter()
             .map(|ty| match *ty {
-                CrateType::Executable | CrateType::Staticlib | CrateType::Cdylib => {
-                    MetadataKind::None
-                }
+                CrateType::Executable
+                | CrateType::Staticlib
+                | CrateType::Cdylib
+                | CrateType::Sdylib => MetadataKind::None,
                 CrateType::Rlib => MetadataKind::Uncompressed,
                 CrateType::Dylib | CrateType::ProcMacro => MetadataKind::Compressed,
             })
@@ -2133,7 +2134,8 @@ impl<'tcx> TyCtxt<'tcx> {
                 CrateType::Executable
                 | CrateType::Staticlib
                 | CrateType::ProcMacro
-                | CrateType::Cdylib => false,
+                | CrateType::Cdylib
+                | CrateType::Sdylib => false,
 
                 // FIXME rust-lang/rust#64319, rust-lang/rust#64872:
                 // We want to block export of generics from dylibs,
@@ -3315,6 +3317,10 @@ impl<'tcx> TyCtxt<'tcx> {
             && self.impl_trait_header(def_id).unwrap().constness == hir::Constness::Const
     }
 
+    pub fn is_sdylib_interface_build(self) -> bool {
+        self.sess.opts.unstable_opts.build_sdylib_interface
+    }
+
     pub fn intrinsic(self, def_id: impl IntoQueryParam<DefId> + Copy) -> Option<ty::IntrinsicDef> {
         match self.def_kind(def_id) {
             DefKind::Fn | DefKind::AssocFn => {}
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index 2f4c03f0953..43dd03f72f0 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -1978,6 +1978,10 @@ impl<'tcx> TyCtxt<'tcx> {
         None
     }
 
+    pub fn is_exportable(self, def_id: DefId) -> bool {
+        self.exportable_items(def_id.krate).contains(&def_id)
+    }
+
     /// Check if the given `DefId` is `#\[automatically_derived\]`, *and*
     /// whether it was produced by expanding a builtin derive macro.
     pub fn is_builtin_derived(self, def_id: DefId) -> bool {
diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs
index 59a52ae67cb..9cf051a8760 100644
--- a/compiler/rustc_mir_build/src/builder/mod.rs
+++ b/compiler/rustc_mir_build/src/builder/mod.rs
@@ -998,7 +998,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             self.source_scope = source_scope;
         }
 
-        if self.tcx.intrinsic(self.def_id).is_some_and(|i| i.must_be_overridden) {
+        if self.tcx.intrinsic(self.def_id).is_some_and(|i| i.must_be_overridden)
+            || self.tcx.is_sdylib_interface_build()
+        {
             let source_info = self.source_info(rustc_span::DUMMY_SP);
             self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
             self.cfg.start_new_block().unit()
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index 413726ddd82..6d815e510ea 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -356,6 +356,8 @@ passes_ignored_derived_impls =
 passes_implied_feature_not_exist =
     feature `{$implied_by}` implying `{$feature}` does not exist
 
+passes_incorrect_crate_type = lang items are not allowed in stable dylibs
+
 passes_incorrect_do_not_recommend_args =
     `#[diagnostic::do_not_recommend]` does not expect any arguments
 
@@ -742,6 +744,23 @@ passes_trait_impl_const_stable =
 passes_transparent_incompatible =
     transparent {$target} cannot have other repr hints
 
+passes_unexportable_adt_with_private_fields = ADT types with private fields are not exportable
+    .note = `{$field_name}` is private
+
+passes_unexportable_fn_abi = only functions with "C" ABI are exportable
+
+passes_unexportable_generic_fn = generic functions are not exportable
+
+passes_unexportable_item = {$descr}'s are not exportable
+
+passes_unexportable_priv_item = private items are not exportable
+    .note = is only usable at visibility `{$vis_descr}`
+
+passes_unexportable_type_in_interface = {$desc} with `#[export_stable]` attribute uses type `{$ty}`, which is not exportable
+    .label = not exportable
+
+passes_unexportable_type_repr = types with unstable layout are not exportable
+
 passes_unknown_external_lang_item =
     unknown external lang item: `{$lang_item}`
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index e5b20901c0c..c68f8df49fc 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -277,6 +277,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                             | sym::cfg_attr
                             | sym::cfg_trace
                             | sym::cfg_attr_trace
+                            | sym::export_stable // handled in `check_export`
                             // need to be fixed
                             | sym::cfi_encoding // FIXME(cfi_encoding)
                             | sym::pointee // FIXME(derive_coerce_pointee)
diff --git a/compiler/rustc_passes/src/check_export.rs b/compiler/rustc_passes/src/check_export.rs
new file mode 100644
index 00000000000..2bb698689be
--- /dev/null
+++ b/compiler/rustc_passes/src/check_export.rs
@@ -0,0 +1,398 @@
+use std::iter;
+use std::ops::ControlFlow;
+
+use rustc_abi::ExternAbi;
+use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
+use rustc_hir as hir;
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::middle::privacy::{EffectiveVisibility, Level};
+use rustc_middle::query::{LocalCrate, Providers};
+use rustc_middle::ty::{
+    self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, Visibility,
+};
+use rustc_session::config::CrateType;
+use rustc_span::{Span, sym};
+
+use crate::errors::UnexportableItem;
+
+struct ExportableItemCollector<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    exportable_items: FxIndexSet<DefId>,
+    in_exportable_mod: bool,
+    seen_exportable_in_mod: bool,
+}
+
+impl<'tcx> ExportableItemCollector<'tcx> {
+    fn new(tcx: TyCtxt<'tcx>) -> ExportableItemCollector<'tcx> {
+        ExportableItemCollector {
+            tcx,
+            exportable_items: Default::default(),
+            in_exportable_mod: false,
+            seen_exportable_in_mod: false,
+        }
+    }
+
+    fn report_wrong_site(&self, def_id: LocalDefId) {
+        let def_descr = self.tcx.def_descr(def_id.to_def_id());
+        self.tcx.dcx().emit_err(UnexportableItem::Item {
+            descr: &format!("{}", def_descr),
+            span: self.tcx.def_span(def_id),
+        });
+    }
+
+    fn item_is_exportable(&self, def_id: LocalDefId) -> bool {
+        let has_attr = self.tcx.has_attr(def_id, sym::export_stable);
+        if !self.in_exportable_mod && !has_attr {
+            return false;
+        }
+
+        let visibilities = self.tcx.effective_visibilities(());
+        let is_pub = visibilities.is_directly_public(def_id);
+
+        if has_attr && !is_pub {
+            let vis = visibilities.effective_vis(def_id).cloned().unwrap_or(
+                EffectiveVisibility::from_vis(Visibility::Restricted(
+                    self.tcx.parent_module_from_def_id(def_id).to_local_def_id(),
+                )),
+            );
+            let vis = vis.at_level(Level::Direct);
+            let span = self.tcx.def_span(def_id);
+
+            self.tcx.dcx().emit_err(UnexportableItem::PrivItem {
+                vis_note: span,
+                vis_descr: &vis.to_string(def_id, self.tcx),
+                span,
+            });
+            return false;
+        }
+
+        is_pub && (has_attr || self.in_exportable_mod)
+    }
+
+    fn add_exportable(&mut self, def_id: LocalDefId) {
+        self.seen_exportable_in_mod = true;
+        self.exportable_items.insert(def_id.to_def_id());
+    }
+
+    fn walk_item_with_mod(&mut self, item: &'tcx hir::Item<'tcx>) {
+        let def_id = item.hir_id().owner.def_id;
+        let old_exportable_mod = self.in_exportable_mod;
+        if self.tcx.get_attr(def_id, sym::export_stable).is_some() {
+            self.in_exportable_mod = true;
+        }
+        let old_seen_exportable_in_mod = std::mem::replace(&mut self.seen_exportable_in_mod, false);
+
+        intravisit::walk_item(self, item);
+
+        if self.seen_exportable_in_mod || self.in_exportable_mod {
+            self.exportable_items.insert(def_id.to_def_id());
+        }
+
+        self.seen_exportable_in_mod = old_seen_exportable_in_mod;
+        self.in_exportable_mod = old_exportable_mod;
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for ExportableItemCollector<'tcx> {
+    type NestedFilter = nested_filter::All;
+
+    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
+        self.tcx
+    }
+
+    fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
+        let def_id = item.hir_id().owner.def_id;
+        // Applying #[extern] attribute to modules is simply equivalent to
+        // applying the attribute to every public item within it.
+        match item.kind {
+            hir::ItemKind::Mod(..) => {
+                self.walk_item_with_mod(item);
+                return;
+            }
+            hir::ItemKind::Impl(impl_) if impl_.of_trait.is_none() => {
+                self.walk_item_with_mod(item);
+                return;
+            }
+            _ => {}
+        }
+
+        if !self.item_is_exportable(def_id) {
+            return;
+        }
+
+        match item.kind {
+            hir::ItemKind::Fn { .. }
+            | hir::ItemKind::Struct(..)
+            | hir::ItemKind::Enum(..)
+            | hir::ItemKind::Union(..)
+            | hir::ItemKind::TyAlias(..) => {
+                self.add_exportable(def_id);
+            }
+            hir::ItemKind::Use(path, _) => {
+                for res in &path.res {
+                    // Only local items are exportable.
+                    if let Some(res_id) = res.opt_def_id()
+                        && let Some(res_id) = res_id.as_local()
+                    {
+                        self.add_exportable(res_id);
+                    }
+                }
+            }
+            // handled above
+            hir::ItemKind::Mod(..) => unreachable!(),
+            hir::ItemKind::Impl(impl_) if impl_.of_trait.is_none() => {
+                unreachable!();
+            }
+            _ => self.report_wrong_site(def_id),
+        }
+    }
+
+    fn visit_impl_item(&mut self, item: &'tcx hir::ImplItem<'tcx>) {
+        let def_id = item.hir_id().owner.def_id;
+        if !self.item_is_exportable(def_id) {
+            return;
+        }
+        match item.kind {
+            hir::ImplItemKind::Fn(..) | hir::ImplItemKind::Type(..) => {
+                self.add_exportable(def_id);
+            }
+            _ => self.report_wrong_site(def_id),
+        }
+    }
+
+    fn visit_foreign_item(&mut self, item: &'tcx hir::ForeignItem<'tcx>) {
+        let def_id = item.hir_id().owner.def_id;
+        if !self.item_is_exportable(def_id) {
+            self.report_wrong_site(def_id);
+        }
+    }
+
+    fn visit_trait_item(&mut self, item: &'tcx hir::TraitItem<'tcx>) {
+        let def_id = item.hir_id().owner.def_id;
+        if !self.item_is_exportable(def_id) {
+            self.report_wrong_site(def_id);
+        }
+    }
+}
+
+struct ExportableItemsChecker<'tcx, 'a> {
+    tcx: TyCtxt<'tcx>,
+    exportable_items: &'a FxIndexSet<DefId>,
+    item_id: DefId,
+}
+
+impl<'tcx, 'a> ExportableItemsChecker<'tcx, 'a> {
+    fn check(&mut self) {
+        match self.tcx.def_kind(self.item_id) {
+            DefKind::Fn | DefKind::AssocFn => self.check_fn(),
+            DefKind::Enum | DefKind::Struct | DefKind::Union => self.check_ty(),
+            _ => {}
+        }
+    }
+
+    fn check_fn(&mut self) {
+        let def_id = self.item_id.expect_local();
+        let span = self.tcx.def_span(def_id);
+
+        if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
+            self.tcx.dcx().emit_err(UnexportableItem::GenericFn(span));
+            return;
+        }
+
+        let sig = self.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
+        if !matches!(sig.abi, ExternAbi::C { .. }) {
+            self.tcx.dcx().emit_err(UnexportableItem::FnAbi(span));
+            return;
+        }
+
+        let sig = self
+            .tcx
+            .try_normalize_erasing_regions(ty::TypingEnv::non_body_analysis(self.tcx, def_id), sig)
+            .unwrap_or(sig);
+
+        let hir_id = self.tcx.local_def_id_to_hir_id(def_id);
+        let decl = self.tcx.hir_fn_decl_by_hir_id(hir_id).unwrap();
+
+        for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
+            self.check_nested_types_are_exportable(*input_ty, input_hir.span);
+        }
+
+        if let hir::FnRetTy::Return(ret_hir) = decl.output {
+            self.check_nested_types_are_exportable(sig.output(), ret_hir.span);
+        }
+    }
+
+    fn check_ty(&mut self) {
+        let ty = self.tcx.type_of(self.item_id).skip_binder();
+        if let ty::Adt(adt_def, _) = ty.kind() {
+            if !adt_def.repr().inhibit_struct_field_reordering() {
+                self.tcx
+                    .dcx()
+                    .emit_err(UnexportableItem::TypeRepr(self.tcx.def_span(self.item_id)));
+            }
+
+            // FIXME: support `#[export(unsafe_stable_abi = "hash")]` syntax
+            for variant in adt_def.variants() {
+                for field in &variant.fields {
+                    if !field.vis.is_public() {
+                        self.tcx.dcx().emit_err(UnexportableItem::AdtWithPrivFields {
+                            span: self.tcx.def_span(self.item_id),
+                            vis_note: self.tcx.def_span(field.did),
+                            field_name: field.name.as_str(),
+                        });
+                    }
+                }
+            }
+        }
+    }
+
+    fn check_nested_types_are_exportable(&mut self, ty: Ty<'tcx>, ty_span: Span) {
+        let res = ty.visit_with(self);
+        if let Some(err_cause) = res.break_value() {
+            self.tcx.dcx().emit_err(UnexportableItem::TypeInInterface {
+                span: self.tcx.def_span(self.item_id),
+                desc: self.tcx.def_descr(self.item_id),
+                ty: &format!("{}", err_cause),
+                ty_span,
+            });
+        }
+    }
+}
+
+impl<'tcx, 'a> TypeVisitor<TyCtxt<'tcx>> for ExportableItemsChecker<'tcx, 'a> {
+    type Result = ControlFlow<Ty<'tcx>>;
+
+    fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
+        match ty.kind() {
+            ty::Adt(adt_def, _) => {
+                let did = adt_def.did();
+                let exportable = if did.is_local() {
+                    self.exportable_items.contains(&did)
+                } else {
+                    self.tcx.is_exportable(did)
+                };
+                if !exportable {
+                    return ControlFlow::Break(ty);
+                }
+                for variant in adt_def.variants() {
+                    for field in &variant.fields {
+                        let field_ty = self.tcx.type_of(field.did).instantiate_identity();
+                        field_ty.visit_with(self)?;
+                    }
+                }
+
+                return ty.super_visit_with(self);
+            }
+
+            ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Bool | ty::Char | ty::Error(_) => {}
+
+            ty::Array(_, _)
+            | ty::Ref(_, _, _)
+            | ty::Param(_)
+            | ty::Closure(_, _)
+            | ty::Dynamic(_, _, _)
+            | ty::Coroutine(_, _)
+            | ty::Foreign(_)
+            | ty::Str
+            | ty::Tuple(_)
+            | ty::Pat(..)
+            | ty::Slice(_)
+            | ty::RawPtr(_, _)
+            | ty::FnDef(_, _)
+            | ty::FnPtr(_, _)
+            | ty::CoroutineClosure(_, _)
+            | ty::CoroutineWitness(_, _)
+            | ty::Never
+            | ty::UnsafeBinder(_)
+            | ty::Alias(ty::AliasTyKind::Opaque, _) => {
+                return ControlFlow::Break(ty);
+            }
+
+            ty::Alias(..) | ty::Infer(_) | ty::Placeholder(_) | ty::Bound(..) => unreachable!(),
+        }
+        ControlFlow::Continue(())
+    }
+}
+
+/// Exportable items:
+///
+/// 1. Structs/enums/unions with a stable representation (e.g. repr(i32) or repr(C)).
+/// 2. Primitive types.
+/// 3. Non-generic functions with a stable ABI (e.g. extern "C") for which every user
+///    defined type used in the signature is also marked as `#[export]`.
+fn exportable_items_provider_local<'tcx>(tcx: TyCtxt<'tcx>, _: LocalCrate) -> &'tcx [DefId] {
+    if !tcx.crate_types().contains(&CrateType::Sdylib) && !tcx.is_sdylib_interface_build() {
+        return &[];
+    }
+
+    let mut visitor = ExportableItemCollector::new(tcx);
+    tcx.hir_walk_toplevel_module(&mut visitor);
+    let exportable_items = visitor.exportable_items;
+    for item_id in exportable_items.iter() {
+        let mut validator =
+            ExportableItemsChecker { tcx, exportable_items: &exportable_items, item_id: *item_id };
+        validator.check();
+    }
+
+    tcx.arena.alloc_from_iter(exportable_items.into_iter())
+}
+
+struct ImplsOrderVisitor<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    order: FxIndexMap<DefId, usize>,
+}
+
+impl<'tcx> ImplsOrderVisitor<'tcx> {
+    fn new(tcx: TyCtxt<'tcx>) -> ImplsOrderVisitor<'tcx> {
+        ImplsOrderVisitor { tcx, order: Default::default() }
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for ImplsOrderVisitor<'tcx> {
+    type NestedFilter = nested_filter::All;
+
+    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
+        self.tcx
+    }
+
+    fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
+        if let hir::ItemKind::Impl(impl_) = item.kind
+            && impl_.of_trait.is_none()
+            && self.tcx.is_exportable(item.owner_id.def_id.to_def_id())
+        {
+            self.order.insert(item.owner_id.def_id.to_def_id(), self.order.len());
+        }
+        intravisit::walk_item(self, item);
+    }
+}
+
+/// During symbol mangling rustc uses a special index to distinguish between two impls of
+/// the same type in the same module(See `DisambiguatedDefPathData`). For exportable items
+/// we cannot use the current approach because it is dependent on the compiler's
+/// implementation.
+///
+/// In order to make disambiguation independent of the compiler version we can assign an
+/// id to each impl according to the relative order of elements in the source code.
+fn stable_order_of_exportable_impls<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    _: LocalCrate,
+) -> &'tcx FxIndexMap<DefId, usize> {
+    if !tcx.crate_types().contains(&CrateType::Sdylib) && !tcx.is_sdylib_interface_build() {
+        return tcx.arena.alloc(FxIndexMap::<DefId, usize>::default());
+    }
+
+    let mut vis = ImplsOrderVisitor::new(tcx);
+    tcx.hir_walk_toplevel_module(&mut vis);
+    tcx.arena.alloc(vis.order)
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers {
+        exportable_items: exportable_items_provider_local,
+        stable_order_of_exportable_impls,
+        ..*providers
+    };
+}
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index b1b4b9ee927..00682a9c7a7 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1422,6 +1422,13 @@ pub(crate) struct IncorrectTarget<'a> {
     pub at_least: bool,
 }
 
+#[derive(Diagnostic)]
+#[diag(passes_incorrect_crate_type)]
+pub(crate) struct IncorrectCrateType {
+    #[primary_span]
+    pub span: Span,
+}
+
 #[derive(LintDiagnostic)]
 #[diag(passes_useless_assignment)]
 pub(crate) struct UselessAssignment<'a> {
@@ -1919,3 +1926,50 @@ pub(crate) struct UnsupportedAttributesInWhere {
     #[primary_span]
     pub span: MultiSpan,
 }
+
+#[derive(Diagnostic)]
+pub(crate) enum UnexportableItem<'a> {
+    #[diag(passes_unexportable_item)]
+    Item {
+        #[primary_span]
+        span: Span,
+        descr: &'a str,
+    },
+
+    #[diag(passes_unexportable_generic_fn)]
+    GenericFn(#[primary_span] Span),
+
+    #[diag(passes_unexportable_fn_abi)]
+    FnAbi(#[primary_span] Span),
+
+    #[diag(passes_unexportable_type_repr)]
+    TypeRepr(#[primary_span] Span),
+
+    #[diag(passes_unexportable_type_in_interface)]
+    TypeInInterface {
+        #[primary_span]
+        span: Span,
+        desc: &'a str,
+        ty: &'a str,
+        #[label]
+        ty_span: Span,
+    },
+
+    #[diag(passes_unexportable_priv_item)]
+    PrivItem {
+        #[primary_span]
+        span: Span,
+        #[note]
+        vis_note: Span,
+        vis_descr: &'a str,
+    },
+
+    #[diag(passes_unexportable_adt_with_private_fields)]
+    AdtWithPrivFields {
+        #[primary_span]
+        span: Span,
+        #[note]
+        vis_note: Span,
+        field_name: &'a str,
+    },
+}
diff --git a/compiler/rustc_passes/src/lang_items.rs b/compiler/rustc_passes/src/lang_items.rs
index 664bd4ad0a2..275714c2d0e 100644
--- a/compiler/rustc_passes/src/lang_items.rs
+++ b/compiler/rustc_passes/src/lang_items.rs
@@ -19,7 +19,8 @@ use rustc_session::cstore::ExternCrate;
 use rustc_span::Span;
 
 use crate::errors::{
-    DuplicateLangItem, IncorrectTarget, LangItemOnIncorrectTarget, UnknownLangItem,
+    DuplicateLangItem, IncorrectCrateType, IncorrectTarget, LangItemOnIncorrectTarget,
+    UnknownLangItem,
 };
 use crate::weak_lang_items;
 
@@ -236,6 +237,10 @@ impl<'ast, 'tcx> LanguageItemCollector<'ast, 'tcx> {
             }
         }
 
+        if self.tcx.crate_types().contains(&rustc_session::config::CrateType::Sdylib) {
+            self.tcx.dcx().emit_err(IncorrectCrateType { span: attr_span });
+        }
+
         self.collect_item(lang_item, item_def_id.to_def_id(), Some(item_span));
     }
 }
diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs
index 424bce9d4d4..001725e2882 100644
--- a/compiler/rustc_passes/src/lib.rs
+++ b/compiler/rustc_passes/src/lib.rs
@@ -19,6 +19,7 @@ use rustc_middle::query::Providers;
 
 pub mod abi_test;
 mod check_attr;
+mod check_export;
 pub mod dead;
 mod debugger_visualizer;
 mod diagnostic_items;
@@ -54,4 +55,5 @@ pub fn provide(providers: &mut Providers) {
     reachable::provide(providers);
     stability::provide(providers);
     upvars::provide(providers);
+    check_export::provide(providers);
 }
diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs
index 321e5729b72..f0e8fa986fe 100644
--- a/compiler/rustc_passes/src/reachable.rs
+++ b/compiler/rustc_passes/src/reachable.rs
@@ -435,10 +435,12 @@ fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
 fn reachable_set(tcx: TyCtxt<'_>, (): ()) -> LocalDefIdSet {
     let effective_visibilities = &tcx.effective_visibilities(());
 
-    let any_library = tcx
-        .crate_types()
-        .iter()
-        .any(|ty| *ty == CrateType::Rlib || *ty == CrateType::Dylib || *ty == CrateType::ProcMacro);
+    let any_library = tcx.crate_types().iter().any(|ty| {
+        *ty == CrateType::Rlib
+            || *ty == CrateType::Dylib
+            || *ty == CrateType::ProcMacro
+            || *ty == CrateType::Sdylib
+    });
     let mut reachable_context = ReachableContext {
         tcx,
         maybe_typeck_results: None,
diff --git a/compiler/rustc_passes/src/weak_lang_items.rs b/compiler/rustc_passes/src/weak_lang_items.rs
index 701f500e4f6..93d164e7d01 100644
--- a/compiler/rustc_passes/src/weak_lang_items.rs
+++ b/compiler/rustc_passes/src/weak_lang_items.rs
@@ -67,7 +67,8 @@ fn verify(tcx: TyCtxt<'_>, items: &lang_items::LanguageItems) {
         | CrateType::ProcMacro
         | CrateType::Cdylib
         | CrateType::Executable
-        | CrateType::Staticlib => true,
+        | CrateType::Staticlib
+        | CrateType::Sdylib => true,
         CrateType::Rlib => false,
     });
     if !needs_check {
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index e2d36f6a4e2..a9d9236d318 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1169,6 +1169,10 @@ impl OutputFilenames {
             .unwrap_or_else(|| OutFileName::Real(self.output_path(flavor)))
     }
 
+    pub fn interface_path(&self) -> PathBuf {
+        self.out_directory.join(format!("lib{}.rs", self.crate_stem))
+    }
+
     /// Gets the output path where a compilation artifact of the given type
     /// should be placed on disk.
     fn output_path(&self, flavor: OutputType) -> PathBuf {
@@ -1452,13 +1456,17 @@ pub enum CrateType {
     Staticlib,
     Cdylib,
     ProcMacro,
+    Sdylib,
 }
 
 impl CrateType {
     pub fn has_metadata(self) -> bool {
         match self {
             CrateType::Rlib | CrateType::Dylib | CrateType::ProcMacro => true,
-            CrateType::Executable | CrateType::Cdylib | CrateType::Staticlib => false,
+            CrateType::Executable
+            | CrateType::Cdylib
+            | CrateType::Staticlib
+            | CrateType::Sdylib => false,
         }
     }
 }
@@ -2818,6 +2826,7 @@ pub fn parse_crate_types_from_list(list_list: Vec<String>) -> Result<Vec<CrateTy
                 "cdylib" => CrateType::Cdylib,
                 "bin" => CrateType::Executable,
                 "proc-macro" => CrateType::ProcMacro,
+                "sdylib" => CrateType::Sdylib,
                 _ => {
                     return Err(format!(
                         "unknown crate type: `{part}`, expected one of: \
@@ -2915,6 +2924,7 @@ impl fmt::Display for CrateType {
             CrateType::Staticlib => "staticlib".fmt(f),
             CrateType::Cdylib => "cdylib".fmt(f),
             CrateType::ProcMacro => "proc-macro".fmt(f),
+            CrateType::Sdylib => "sdylib".fmt(f),
         }
     }
 }
diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs
index c8a5c22ad12..4cfc745dec2 100644
--- a/compiler/rustc_session/src/cstore.rs
+++ b/compiler/rustc_session/src/cstore.rs
@@ -27,6 +27,7 @@ pub struct CrateSource {
     pub dylib: Option<(PathBuf, PathKind)>,
     pub rlib: Option<(PathBuf, PathKind)>,
     pub rmeta: Option<(PathBuf, PathKind)>,
+    pub sdylib_interface: Option<(PathBuf, PathKind)>,
 }
 
 impl CrateSource {
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 5f4695fb184..440e8f808c7 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -2113,6 +2113,8 @@ options! {
         "emit noalias metadata for box (default: yes)"),
     branch_protection: Option<BranchProtection> = (None, parse_branch_protection, [TRACKED],
         "set options for branch target identification and pointer authentication on AArch64"),
+    build_sdylib_interface: bool = (false, parse_bool, [UNTRACKED],
+        "whether the stable interface is being built"),
     cf_protection: CFProtection = (CFProtection::None, parse_cfprotection, [TRACKED],
         "instrument control-flow architecture protection"),
     check_cfg_all_expected: bool = (false, parse_bool, [UNTRACKED],
diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs
index 46dae9144cd..cba70b5bd5d 100644
--- a/compiler/rustc_session/src/output.rs
+++ b/compiler/rustc_session/src/output.rs
@@ -98,7 +98,7 @@ pub fn filename_for_input(
         CrateType::Rlib => {
             OutFileName::Real(outputs.out_directory.join(&format!("lib{libname}.rlib")))
         }
-        CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => {
+        CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib | CrateType::Sdylib => {
             let (prefix, suffix) = (&sess.target.dll_prefix, &sess.target.dll_suffix);
             OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")))
         }
@@ -167,6 +167,7 @@ pub const CRATE_TYPES: &[(Symbol, CrateType)] = &[
     (sym::staticlib, CrateType::Staticlib),
     (sym::proc_dash_macro, CrateType::ProcMacro),
     (sym::bin, CrateType::Executable),
+    (sym::sdylib, CrateType::Sdylib),
 ];
 
 pub fn categorize_crate_type(s: Symbol) -> Option<CrateType> {
@@ -187,6 +188,11 @@ pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<C
         return vec![CrateType::Executable];
     }
 
+    // Shadow `sdylib` crate type in interface build.
+    if session.opts.unstable_opts.build_sdylib_interface {
+        return vec![CrateType::Rlib];
+    }
+
     // Only check command line flags if present. If no types are specified by
     // command line, then reuse the empty `base` Vec to hold the types that
     // will be found in crate attributes.
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 3912c7dc7d6..31d129c3465 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -914,6 +914,7 @@ symbols! {
         explicit_generic_args_with_impl_trait,
         explicit_tail_calls,
         export_name,
+        export_stable,
         expr,
         expr_2021,
         expr_fragment_specifier_2024,
@@ -1878,6 +1879,7 @@ symbols! {
         saturating_add,
         saturating_div,
         saturating_sub,
+        sdylib,
         search_unbox,
         select_unpredictable,
         self_in_typedefs,
diff --git a/compiler/rustc_symbol_mangling/src/export.rs b/compiler/rustc_symbol_mangling/src/export.rs
new file mode 100644
index 00000000000..770401fc8cf
--- /dev/null
+++ b/compiler/rustc_symbol_mangling/src/export.rs
@@ -0,0 +1,181 @@
+use std::assert_matches::debug_assert_matches;
+
+use rustc_abi::IntegerType;
+use rustc_data_structures::stable_hasher::StableHasher;
+use rustc_hashes::Hash128;
+use rustc_hir::def::DefKind;
+use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
+use rustc_span::symbol::{Symbol, sym};
+
+trait AbiHashStable<'tcx> {
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher);
+}
+macro_rules! default_hash_impl {
+    ($($t:ty,)+) => {
+        $(impl<'tcx> AbiHashStable<'tcx> for $t {
+            #[inline]
+            fn abi_hash(&self, _tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+                ::std::hash::Hash::hash(self, hasher);
+            }
+        })*
+    };
+}
+
+default_hash_impl! { i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, }
+
+impl<'tcx> AbiHashStable<'tcx> for bool {
+    #[inline]
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        (if *self { 1u8 } else { 0u8 }).abi_hash(tcx, hasher);
+    }
+}
+
+impl<'tcx> AbiHashStable<'tcx> for str {
+    #[inline]
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        self.as_bytes().abi_hash(tcx, hasher);
+    }
+}
+
+impl<'tcx> AbiHashStable<'tcx> for String {
+    #[inline]
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        self[..].abi_hash(tcx, hasher);
+    }
+}
+
+impl<'tcx> AbiHashStable<'tcx> for Symbol {
+    #[inline]
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        self.as_str().abi_hash(tcx, hasher);
+    }
+}
+
+impl<'tcx, T: AbiHashStable<'tcx>> AbiHashStable<'tcx> for [T] {
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        self.len().abi_hash(tcx, hasher);
+        for item in self {
+            item.abi_hash(tcx, hasher);
+        }
+    }
+}
+
+impl<'tcx> AbiHashStable<'tcx> for Ty<'tcx> {
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        match self.kind() {
+            ty::Bool => sym::bool.abi_hash(tcx, hasher),
+            ty::Char => sym::char.abi_hash(tcx, hasher),
+            ty::Int(int_ty) => int_ty.name_str().abi_hash(tcx, hasher),
+            ty::Uint(uint_ty) => uint_ty.name_str().abi_hash(tcx, hasher),
+            ty::Float(float_ty) => float_ty.name_str().abi_hash(tcx, hasher),
+
+            ty::Adt(adt_def, args) => {
+                adt_def.is_struct().abi_hash(tcx, hasher);
+                adt_def.is_enum().abi_hash(tcx, hasher);
+                adt_def.is_union().abi_hash(tcx, hasher);
+
+                if let Some(align) = adt_def.repr().align {
+                    align.bits().abi_hash(tcx, hasher);
+                }
+
+                if let Some(integer) = adt_def.repr().int {
+                    match integer {
+                        IntegerType::Pointer(sign) => sign.abi_hash(tcx, hasher),
+                        IntegerType::Fixed(integer, sign) => {
+                            integer.int_ty_str().abi_hash(tcx, hasher);
+                            sign.abi_hash(tcx, hasher);
+                        }
+                    }
+                }
+
+                if let Some(pack) = adt_def.repr().pack {
+                    pack.bits().abi_hash(tcx, hasher);
+                }
+
+                adt_def.repr().c().abi_hash(tcx, hasher);
+
+                for variant in adt_def.variants() {
+                    variant.name.abi_hash(tcx, hasher);
+                    for field in &variant.fields {
+                        field.name.abi_hash(tcx, hasher);
+                        let field_ty = tcx.type_of(field.did).instantiate_identity();
+                        field_ty.abi_hash(tcx, hasher);
+                    }
+                }
+                args.abi_hash(tcx, hasher);
+            }
+
+            ty::Tuple(args) if args.len() == 0 => {}
+
+            // FIXME: Not yet supported.
+            ty::Foreign(_)
+            | ty::Ref(_, _, _)
+            | ty::Str
+            | ty::Array(_, _)
+            | ty::Pat(_, _)
+            | ty::Slice(_)
+            | ty::RawPtr(_, _)
+            | ty::FnDef(_, _)
+            | ty::FnPtr(_, _)
+            | ty::Dynamic(_, _, _)
+            | ty::Closure(_, _)
+            | ty::CoroutineClosure(_, _)
+            | ty::Coroutine(_, _)
+            | ty::CoroutineWitness(_, _)
+            | ty::Never
+            | ty::Tuple(_)
+            | ty::Alias(_, _)
+            | ty::Param(_)
+            | ty::Bound(_, _)
+            | ty::Placeholder(_)
+            | ty::Infer(_)
+            | ty::UnsafeBinder(_) => unreachable!(),
+
+            ty::Error(_) => {}
+        }
+    }
+}
+
+impl<'tcx> AbiHashStable<'tcx> for ty::FnSig<'tcx> {
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        for ty in self.inputs_and_output {
+            ty.abi_hash(tcx, hasher);
+        }
+        self.safety.is_safe().abi_hash(tcx, hasher);
+    }
+}
+
+impl<'tcx> AbiHashStable<'tcx> for ty::GenericArg<'tcx> {
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        self.unpack().abi_hash(tcx, hasher);
+    }
+}
+
+impl<'tcx> AbiHashStable<'tcx> for ty::GenericArgKind<'tcx> {
+    fn abi_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
+        match self {
+            ty::GenericArgKind::Type(t) => t.abi_hash(tcx, hasher),
+            ty::GenericArgKind::Lifetime(_) | ty::GenericArgKind::Const(_) => unimplemented!(),
+        }
+    }
+}
+
+pub(crate) fn compute_hash_of_export_fn<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    instance: Instance<'tcx>,
+) -> String {
+    let def_id = instance.def_id();
+    debug_assert_matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn);
+
+    let args = instance.args;
+    let sig_ty = tcx.fn_sig(def_id).instantiate(tcx, args);
+    let sig_ty = tcx.instantiate_bound_regions_with_erased(sig_ty);
+
+    let hash = {
+        let mut hasher = StableHasher::new();
+        sig_ty.abi_hash(tcx, &mut hasher);
+        hasher.finish::<Hash128>()
+    };
+
+    hash.as_u128().to_string()
+}
diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs
index ca8918e06aa..a51d7da878a 100644
--- a/compiler/rustc_symbol_mangling/src/lib.rs
+++ b/compiler/rustc_symbol_mangling/src/lib.rs
@@ -92,6 +92,7 @@
 #![cfg_attr(bootstrap, feature(let_chains))]
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
 #![doc(rust_logo)]
+#![feature(assert_matches)]
 #![feature(rustdoc_internals)]
 // tidy-alphabetical-end
 
@@ -104,6 +105,7 @@ use rustc_middle::ty::{self, Instance, TyCtxt};
 use rustc_session::config::SymbolManglingVersion;
 use tracing::debug;
 
+mod export;
 mod hashed;
 mod legacy;
 mod v0;
@@ -296,12 +298,21 @@ fn compute_symbol_name<'tcx>(
         tcx.symbol_mangling_version(mangling_version_crate)
     };
 
-    let symbol = match mangling_version {
-        SymbolManglingVersion::Legacy => legacy::mangle(tcx, instance, instantiating_crate),
-        SymbolManglingVersion::V0 => v0::mangle(tcx, instance, instantiating_crate),
-        SymbolManglingVersion::Hashed => hashed::mangle(tcx, instance, instantiating_crate, || {
-            v0::mangle(tcx, instance, instantiating_crate)
-        }),
+    let symbol = match tcx.is_exportable(def_id) {
+        true => format!(
+            "{}.{}",
+            v0::mangle(tcx, instance, instantiating_crate, true),
+            export::compute_hash_of_export_fn(tcx, instance)
+        ),
+        false => match mangling_version {
+            SymbolManglingVersion::Legacy => legacy::mangle(tcx, instance, instantiating_crate),
+            SymbolManglingVersion::V0 => v0::mangle(tcx, instance, instantiating_crate, false),
+            SymbolManglingVersion::Hashed => {
+                hashed::mangle(tcx, instance, instantiating_crate, || {
+                    v0::mangle(tcx, instance, instantiating_crate, false)
+                })
+            }
+        },
     };
 
     debug_assert!(
diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs
index f8f2714ee42..ad391d56992 100644
--- a/compiler/rustc_symbol_mangling/src/v0.rs
+++ b/compiler/rustc_symbol_mangling/src/v0.rs
@@ -26,6 +26,7 @@ pub(super) fn mangle<'tcx>(
     tcx: TyCtxt<'tcx>,
     instance: Instance<'tcx>,
     instantiating_crate: Option<CrateNum>,
+    is_exportable: bool,
 ) -> String {
     let def_id = instance.def_id();
     // FIXME(eddyb) this should ideally not be needed.
@@ -35,6 +36,7 @@ pub(super) fn mangle<'tcx>(
     let mut cx: SymbolMangler<'_> = SymbolMangler {
         tcx,
         start_offset: prefix.len(),
+        is_exportable,
         paths: FxHashMap::default(),
         types: FxHashMap::default(),
         consts: FxHashMap::default(),
@@ -93,6 +95,7 @@ pub fn mangle_internal_symbol<'tcx>(tcx: TyCtxt<'tcx>, item_name: &str) -> Strin
     let mut cx: SymbolMangler<'_> = SymbolMangler {
         tcx,
         start_offset: prefix.len(),
+        is_exportable: false,
         paths: FxHashMap::default(),
         types: FxHashMap::default(),
         consts: FxHashMap::default(),
@@ -135,6 +138,7 @@ pub(super) fn mangle_typeid_for_trait_ref<'tcx>(
     let mut cx = SymbolMangler {
         tcx,
         start_offset: 0,
+        is_exportable: false,
         paths: FxHashMap::default(),
         types: FxHashMap::default(),
         consts: FxHashMap::default(),
@@ -163,6 +167,7 @@ struct SymbolMangler<'tcx> {
     tcx: TyCtxt<'tcx>,
     binders: Vec<BinderLevel>,
     out: String,
+    is_exportable: bool,
 
     /// The length of the prefix in `out` (e.g. 2 for `_R`).
     start_offset: usize,
@@ -376,7 +381,14 @@ impl<'tcx> Printer<'tcx> for SymbolMangler<'tcx> {
                 args,
             )?;
         } else {
-            self.push_disambiguator(key.disambiguated_data.disambiguator as u64);
+            let exported_impl_order = self.tcx.stable_order_of_exportable_impls(impl_def_id.krate);
+            let disambiguator = match self.is_exportable {
+                true => exported_impl_order[&impl_def_id] as u64,
+                false => {
+                    exported_impl_order.len() as u64 + key.disambiguated_data.disambiguator as u64
+                }
+            };
+            self.push_disambiguator(disambiguator);
             self.print_def_path(parent_def_id, &[])?;
         }
 
@@ -818,8 +830,10 @@ impl<'tcx> Printer<'tcx> for SymbolMangler<'tcx> {
 
     fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
         self.push("C");
-        let stable_crate_id = self.tcx.def_path_hash(cnum.as_def_id()).stable_crate_id();
-        self.push_disambiguator(stable_crate_id.as_u64());
+        if !self.is_exportable {
+            let stable_crate_id = self.tcx.def_path_hash(cnum.as_def_id()).stable_crate_id();
+            self.push_disambiguator(stable_crate_id.as_u64());
+        }
         let name = self.tcx.crate_name(cnum);
         self.push_ident(name.as_str());
         Ok(())
diff --git a/rustfmt.toml b/rustfmt.toml
index 7c384b876bf..d9857a7e3e7 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -19,6 +19,7 @@ ignore = [
     "/tests/debuginfo/",              # These tests are somewhat sensitive to source code layout.
     "/tests/incremental/",            # These tests are somewhat sensitive to source code layout.
     "/tests/pretty/",                 # These tests are very sensitive to source code layout.
+    "/tests/run-make/export",         # These tests contain syntax errors.
     "/tests/run-make/translation/test.rs", # This test contains syntax errors.
     "/tests/rustdoc/",                # Some have syntax errors, some are whitespace-sensitive.
     "/tests/rustdoc-gui/",            # Some tests are sensitive to source code layout.
diff --git a/tests/run-make/export/compile-interface-error/app.rs b/tests/run-make/export/compile-interface-error/app.rs
new file mode 100644
index 00000000000..f619745a711
--- /dev/null
+++ b/tests/run-make/export/compile-interface-error/app.rs
@@ -0,0 +1,3 @@
+extern crate libr;
+
+fn main() {}
diff --git a/tests/run-make/export/compile-interface-error/liblibr.rs b/tests/run-make/export/compile-interface-error/liblibr.rs
new file mode 100644
index 00000000000..906d8d7be12
--- /dev/null
+++ b/tests/run-make/export/compile-interface-error/liblibr.rs
@@ -0,0 +1,5 @@
+#![feature(export_stable)]
+
+// interface file is broken(priv fn):
+#[export_stable]
+extern "C" fn foo();
diff --git a/tests/run-make/export/compile-interface-error/rmake.rs b/tests/run-make/export/compile-interface-error/rmake.rs
new file mode 100644
index 00000000000..89474e9d4fb
--- /dev/null
+++ b/tests/run-make/export/compile-interface-error/rmake.rs
@@ -0,0 +1,9 @@
+use run_make_support::rustc;
+
+fn main() {
+    // Do not produce the interface, use the broken one.
+    rustc()
+        .input("app.rs")
+        .run_fail()
+        .assert_stderr_contains("couldn't compile interface");
+}
diff --git a/tests/run-make/export/disambiguator/app.rs b/tests/run-make/export/disambiguator/app.rs
new file mode 100644
index 00000000000..27e0e2280e5
--- /dev/null
+++ b/tests/run-make/export/disambiguator/app.rs
@@ -0,0 +1,7 @@
+extern crate libr;
+
+use libr::*;
+
+fn main() {
+    assert_eq!(S::<S2>::foo(), 2);
+}
diff --git a/tests/run-make/export/disambiguator/libr.rs b/tests/run-make/export/disambiguator/libr.rs
new file mode 100644
index 00000000000..b294d5c9e8e
--- /dev/null
+++ b/tests/run-make/export/disambiguator/libr.rs
@@ -0,0 +1,27 @@
+// `S::<S2>::foo` and `S::<S1>::foo` have same `DefPath` modulo disambiguator.
+// `libr.rs` interface may not contain `S::<S1>::foo` as private items aren't
+// exportable. We should make sure that original `S::<S2>::foo` and the one
+// produced during interface generation have same mangled names.
+
+#![feature(export_stable)]
+#![crate_type = "sdylib"]
+
+#[export_stable]
+#[repr(C)]
+pub struct S<T>(pub T);
+
+struct S1;
+pub struct S2;
+
+impl S<S1> {
+    extern "C" fn foo() -> i32 {
+        1
+    }
+}
+
+#[export_stable]
+impl S<S2> {
+    pub extern "C" fn foo() -> i32 {
+        2
+    }
+}
diff --git a/tests/run-make/export/disambiguator/rmake.rs b/tests/run-make/export/disambiguator/rmake.rs
new file mode 100644
index 00000000000..743db1933fb
--- /dev/null
+++ b/tests/run-make/export/disambiguator/rmake.rs
@@ -0,0 +1,12 @@
+use run_make_support::rustc;
+
+fn main() {
+    rustc()
+        .env("RUSTC_FORCE_RUSTC_VERSION", "1")
+        .input("libr.rs")
+        .run();
+    rustc()
+        .env("RUSTC_FORCE_RUSTC_VERSION", "2")
+        .input("app.rs")
+        .run();
+}
diff --git a/tests/run-make/export/extern-opt/app.rs b/tests/run-make/export/extern-opt/app.rs
new file mode 100644
index 00000000000..765c9925d5f
--- /dev/null
+++ b/tests/run-make/export/extern-opt/app.rs
@@ -0,0 +1,6 @@
+extern crate libr;
+use libr::*;
+
+fn main() {
+    assert_eq!(foo(1), 1);
+}
diff --git a/tests/run-make/export/extern-opt/libinterface.rs b/tests/run-make/export/extern-opt/libinterface.rs
new file mode 100644
index 00000000000..313cfbe7d59
--- /dev/null
+++ b/tests/run-make/export/extern-opt/libinterface.rs
@@ -0,0 +1,4 @@
+#![feature(export_stable)]
+
+#[export_stable]
+pub extern "C" fn foo(x: i32) -> i32;
diff --git a/tests/run-make/export/extern-opt/libr.rs b/tests/run-make/export/extern-opt/libr.rs
new file mode 100644
index 00000000000..026ebb4233d
--- /dev/null
+++ b/tests/run-make/export/extern-opt/libr.rs
@@ -0,0 +1,5 @@
+#![feature(export_stable)]
+#![crate_type = "sdylib"]
+
+#[export_stable]
+pub extern "C" fn foo(x: i32) -> i32 { x }
diff --git a/tests/run-make/export/extern-opt/rmake.rs b/tests/run-make/export/extern-opt/rmake.rs
new file mode 100644
index 00000000000..821e2eb2149
--- /dev/null
+++ b/tests/run-make/export/extern-opt/rmake.rs
@@ -0,0 +1,23 @@
+use run_make_support::{rustc, dynamic_lib_name};
+
+fn main() {
+    rustc()
+        .env("RUSTC_FORCE_RUSTC_VERSION", "1")
+        .input("libr.rs")
+        .run();
+
+    rustc()
+        .env("RUSTC_FORCE_RUSTC_VERSION", "2")
+        .input("app.rs")
+        .extern_("libr", "libinterface.rs")
+        .extern_("libr", dynamic_lib_name("libr"))
+        .run();
+
+    rustc()
+        .env("RUSTC_FORCE_RUSTC_VERSION", "2")
+        .input("app.rs")
+        .extern_("libr", "interface.rs") // wrong interface format
+        .extern_("libr", dynamic_lib_name("libr"))
+        .run_fail()
+        .assert_stderr_contains("extern location for libr does not exist");
+}
diff --git a/tests/run-make/export/simple/app.rs b/tests/run-make/export/simple/app.rs
new file mode 100644
index 00000000000..ba34bdd7b56
--- /dev/null
+++ b/tests/run-make/export/simple/app.rs
@@ -0,0 +1,8 @@
+extern crate libr;
+use libr::*;
+
+fn main() {
+    let s = m::S { x: 42 };
+    assert_eq!(m::foo1(s), 42);
+    assert_eq!(m::S::foo2(1), 1);
+}
diff --git a/tests/run-make/export/simple/libr.rs b/tests/run-make/export/simple/libr.rs
new file mode 100644
index 00000000000..e10b76a6e52
--- /dev/null
+++ b/tests/run-make/export/simple/libr.rs
@@ -0,0 +1,22 @@
+#![feature(export_stable)]
+#![crate_type = "sdylib"]
+
+#[export_stable]
+pub mod m {
+    #[repr(C)]
+    pub struct S {
+        pub x: i32,
+    }
+
+    pub extern "C" fn foo1(x: S) -> i32 {
+        x.x
+    }
+
+    pub type Integer = i32;
+
+    impl S {
+        pub extern "C" fn foo2(x: Integer) -> Integer {
+            x
+        }
+    }
+}
diff --git a/tests/run-make/export/simple/rmake.rs b/tests/run-make/export/simple/rmake.rs
new file mode 100644
index 00000000000..743db1933fb
--- /dev/null
+++ b/tests/run-make/export/simple/rmake.rs
@@ -0,0 +1,12 @@
+use run_make_support::rustc;
+
+fn main() {
+    rustc()
+        .env("RUSTC_FORCE_RUSTC_VERSION", "1")
+        .input("libr.rs")
+        .run();
+    rustc()
+        .env("RUSTC_FORCE_RUSTC_VERSION", "2")
+        .input("app.rs")
+        .run();
+}
diff --git a/tests/ui/attributes/export/crate-type-2.rs b/tests/ui/attributes/export/crate-type-2.rs
new file mode 100644
index 00000000000..f0379f6d797
--- /dev/null
+++ b/tests/ui/attributes/export/crate-type-2.rs
@@ -0,0 +1,2 @@
+//@ compile-flags: --crate-type=sdylib
+//~^ ERROR  `sdylib` crate type is unstable
diff --git a/tests/ui/attributes/export/crate-type-2.stderr b/tests/ui/attributes/export/crate-type-2.stderr
new file mode 100644
index 00000000000..7ce6a500113
--- /dev/null
+++ b/tests/ui/attributes/export/crate-type-2.stderr
@@ -0,0 +1,9 @@
+error[E0658]: `sdylib` crate type is unstable
+   |
+   = note: see issue #139939 <https://github.com/rust-lang/rust/issues/139939> for more information
+   = help: add `#![feature(export_stable)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/attributes/export/crate-type.rs b/tests/ui/attributes/export/crate-type.rs
new file mode 100644
index 00000000000..bd092bbb1a1
--- /dev/null
+++ b/tests/ui/attributes/export/crate-type.rs
@@ -0,0 +1,2 @@
+#![crate_type = "sdylib"]
+//~^ ERROR  `sdylib` crate type is unstable
diff --git a/tests/ui/attributes/export/crate-type.stderr b/tests/ui/attributes/export/crate-type.stderr
new file mode 100644
index 00000000000..7ce6a500113
--- /dev/null
+++ b/tests/ui/attributes/export/crate-type.stderr
@@ -0,0 +1,9 @@
+error[E0658]: `sdylib` crate type is unstable
+   |
+   = note: see issue #139939 <https://github.com/rust-lang/rust/issues/139939> for more information
+   = help: add `#![feature(export_stable)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/attributes/export/exportable.rs b/tests/ui/attributes/export/exportable.rs
new file mode 100644
index 00000000000..f592fce88cd
--- /dev/null
+++ b/tests/ui/attributes/export/exportable.rs
@@ -0,0 +1,139 @@
+//@ compile-flags: -Zunstable-options -Csymbol-mangling-version=v0
+
+#![crate_type = "sdylib"]
+#![allow(incomplete_features, improper_ctypes_definitions)]
+#![feature(export_stable)]
+#![feature(inherent_associated_types)]
+
+mod m {
+    #[export_stable]
+    pub struct S;
+    //~^ ERROR private items are not exportable
+
+    pub fn foo() -> i32 { 0 }
+    //~^ ERROR only functions with "C" ABI are exportable
+}
+
+#[export_stable]
+pub use m::foo;
+
+#[export_stable]
+pub mod m1 {
+    #[repr(C)]
+    pub struct S1; // OK, public type with stable repr
+
+    struct S2;
+
+    pub struct S3;
+    //~^ ERROR types with unstable layout are not exportable
+}
+
+pub mod fn_sig {
+    #[export_stable]
+    pub fn foo1() {}
+    //~^ ERROR only functions with "C" ABI are exportable
+
+    #[export_stable]
+    #[repr(C)]
+    pub struct S;
+
+    #[export_stable]
+    pub extern "C" fn foo2(x: S) -> i32 { 0 }
+
+    #[export_stable]
+    pub extern "C" fn foo3(x: Box<S>) -> i32 { 0 }
+    //~^ ERROR function with `#[export_stable]` attribute uses type `Box<fn_sig::S>`, which is not exportable
+}
+
+pub mod impl_item {
+    pub struct S;
+
+    impl S {
+        #[export_stable]
+        pub extern "C" fn foo1(&self) -> i32 { 0 }
+        //~^ ERROR method with `#[export_stable]` attribute uses type `&impl_item::S`, which is not exportable
+
+        #[export_stable]
+        pub extern "C" fn foo2(self) -> i32 { 0 }
+        //~^ ERROR method with `#[export_stable]` attribute uses type `impl_item::S`, which is not exportable
+    }
+
+    pub struct S2<T>(T);
+
+    impl<T> S2<T> {
+        #[export_stable]
+        pub extern "C" fn foo1(&self) {}
+        //~^ ERROR generic functions are not exportable
+    }
+}
+
+pub mod tys {
+    pub trait Trait {
+        type Type;
+    }
+    pub struct S;
+
+    impl Trait for S {
+        type Type = (u32,);
+    }
+
+    #[export_stable]
+    pub extern "C" fn foo1(x: <S as Trait>::Type) -> u32 { x.0 }
+    //~^ ERROR function with `#[export_stable]` attribute uses type `(u32,)`, which is not exportable
+
+    #[export_stable]
+    pub type Type = [i32; 4];
+
+    #[export_stable]
+    pub extern "C" fn foo2(_x: Type) {}
+    //~^ ERROR function with `#[export_stable]` attribute uses type `[i32; 4]`, which is not exportable
+
+    impl S {
+        #[export_stable]
+        pub type Type = extern "C" fn();
+    }
+
+    #[export_stable]
+    pub extern "C" fn foo3(_x: S::Type) {}
+    //~^ ERROR function with `#[export_stable]` attribute uses type `extern "C" fn()`, which is not exportable
+
+    #[export_stable]
+    pub extern "C" fn foo4() -> impl Copy {
+    //~^ ERROR function with `#[export_stable]` attribute uses type `impl Copy`, which is not exportable
+        0
+    }
+}
+
+pub mod privacy {
+    #[export_stable]
+    #[repr(C)]
+    pub struct S1 {
+        pub x: i32
+    }
+
+    #[export_stable]
+    #[repr(C)]
+    pub struct S2 {
+    //~^ ERROR ADT types with private fields are not exportable
+        x: i32
+    }
+
+    #[export_stable]
+    #[repr(i32)]
+    enum E {
+    //~^ ERROR private items are not exportable
+        Variant1 { x: i32 }
+    }
+}
+
+pub mod use_site {
+    #[export_stable]
+    pub trait Trait {}
+    //~^ ERROR trait's are not exportable
+
+    #[export_stable]
+    pub const C: i32 = 0;
+    //~^ ERROR constant's are not exportable
+}
+
+fn main() {}
diff --git a/tests/ui/attributes/export/exportable.stderr b/tests/ui/attributes/export/exportable.stderr
new file mode 100644
index 00000000000..0f6469d35c3
--- /dev/null
+++ b/tests/ui/attributes/export/exportable.stderr
@@ -0,0 +1,130 @@
+error: private items are not exportable
+  --> $DIR/exportable.rs:10:5
+   |
+LL |     pub struct S;
+   |     ^^^^^^^^^^^^
+   |
+note: is only usable at visibility `pub(crate)`
+  --> $DIR/exportable.rs:10:5
+   |
+LL |     pub struct S;
+   |     ^^^^^^^^^^^^
+
+error: private items are not exportable
+  --> $DIR/exportable.rs:123:5
+   |
+LL |     enum E {
+   |     ^^^^^^
+   |
+note: is only usable at visibility `pub(self)`
+  --> $DIR/exportable.rs:123:5
+   |
+LL |     enum E {
+   |     ^^^^^^
+
+error: trait's are not exportable
+  --> $DIR/exportable.rs:131:5
+   |
+LL |     pub trait Trait {}
+   |     ^^^^^^^^^^^^^^^
+
+error: constant's are not exportable
+  --> $DIR/exportable.rs:135:5
+   |
+LL |     pub const C: i32 = 0;
+   |     ^^^^^^^^^^^^^^^^
+
+error: only functions with "C" ABI are exportable
+  --> $DIR/exportable.rs:13:5
+   |
+LL |     pub fn foo() -> i32 { 0 }
+   |     ^^^^^^^^^^^^^^^^^^^
+
+error: types with unstable layout are not exportable
+  --> $DIR/exportable.rs:27:5
+   |
+LL |     pub struct S3;
+   |     ^^^^^^^^^^^^^
+
+error: only functions with "C" ABI are exportable
+  --> $DIR/exportable.rs:33:5
+   |
+LL |     pub fn foo1() {}
+   |     ^^^^^^^^^^^^^
+
+error: function with `#[export_stable]` attribute uses type `Box<fn_sig::S>`, which is not exportable
+  --> $DIR/exportable.rs:44:5
+   |
+LL |     pub extern "C" fn foo3(x: Box<S>) -> i32 { 0 }
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^------^^^^^^^^
+   |                               |
+   |                               not exportable
+
+error: method with `#[export_stable]` attribute uses type `&impl_item::S`, which is not exportable
+  --> $DIR/exportable.rs:53:9
+   |
+LL |         pub extern "C" fn foo1(&self) -> i32 { 0 }
+   |         ^^^^^^^^^^^^^^^^^^^^^^^-----^^^^^^^^
+   |                                |
+   |                                not exportable
+
+error: method with `#[export_stable]` attribute uses type `impl_item::S`, which is not exportable
+  --> $DIR/exportable.rs:57:9
+   |
+LL |         pub extern "C" fn foo2(self) -> i32 { 0 }
+   |         ^^^^^^^^^^^^^^^^^^^^^^^----^^^^^^^^
+   |                                |
+   |                                not exportable
+
+error: generic functions are not exportable
+  --> $DIR/exportable.rs:65:9
+   |
+LL |         pub extern "C" fn foo1(&self) {}
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: function with `#[export_stable]` attribute uses type `(u32,)`, which is not exportable
+  --> $DIR/exportable.rs:81:5
+   |
+LL |     pub extern "C" fn foo1(x: <S as Trait>::Type) -> u32 { x.0 }
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^------------------^^^^^^^^
+   |                               |
+   |                               not exportable
+
+error: function with `#[export_stable]` attribute uses type `[i32; 4]`, which is not exportable
+  --> $DIR/exportable.rs:88:5
+   |
+LL |     pub extern "C" fn foo2(_x: Type) {}
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^----^
+   |                                |
+   |                                not exportable
+
+error: function with `#[export_stable]` attribute uses type `extern "C" fn()`, which is not exportable
+  --> $DIR/exportable.rs:97:5
+   |
+LL |     pub extern "C" fn foo3(_x: S::Type) {}
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^-------^
+   |                                |
+   |                                not exportable
+
+error: function with `#[export_stable]` attribute uses type `impl Copy`, which is not exportable
+  --> $DIR/exportable.rs:101:5
+   |
+LL |     pub extern "C" fn foo4() -> impl Copy {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------
+   |                                 |
+   |                                 not exportable
+
+error: ADT types with private fields are not exportable
+  --> $DIR/exportable.rs:116:5
+   |
+LL |     pub struct S2 {
+   |     ^^^^^^^^^^^^^
+   |
+note: `x` is private
+  --> $DIR/exportable.rs:118:9
+   |
+LL |         x: i32
+   |         ^^^^^^
+
+error: aborting due to 16 previous errors
+
diff --git a/tests/ui/attributes/export/lang-item.rs b/tests/ui/attributes/export/lang-item.rs
new file mode 100644
index 00000000000..b923b41a957
--- /dev/null
+++ b/tests/ui/attributes/export/lang-item.rs
@@ -0,0 +1,8 @@
+#![feature(no_core, lang_items, export_stable)]
+#![allow(incomplete_features)]
+#![crate_type = "sdylib"]
+#![no_core]
+
+#[lang = "sized"]
+//~^ ERROR lang items are not allowed in stable dylibs
+trait Sized {}
diff --git a/tests/ui/attributes/export/lang-item.stderr b/tests/ui/attributes/export/lang-item.stderr
new file mode 100644
index 00000000000..8c0741bdb6f
--- /dev/null
+++ b/tests/ui/attributes/export/lang-item.stderr
@@ -0,0 +1,8 @@
+error: lang items are not allowed in stable dylibs
+  --> $DIR/lang-item.rs:6:1
+   |
+LL | #[lang = "sized"]
+   | ^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/feature-gates/feature-gate-export_stable.rs b/tests/ui/feature-gates/feature-gate-export_stable.rs
new file mode 100644
index 00000000000..5d05fee059b
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-export_stable.rs
@@ -0,0 +1,5 @@
+#![crate_type="lib"]
+
+#[export_stable]
+//~^ ERROR the `#[export_stable]` attribute is an experimental feature
+pub mod a {}
diff --git a/tests/ui/feature-gates/feature-gate-export_stable.stderr b/tests/ui/feature-gates/feature-gate-export_stable.stderr
new file mode 100644
index 00000000000..6beb52a77e5
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-export_stable.stderr
@@ -0,0 +1,13 @@
+error[E0658]: the `#[export_stable]` attribute is an experimental feature
+  --> $DIR/feature-gate-export_stable.rs:3:1
+   |
+LL | #[export_stable]
+   | ^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #139939 <https://github.com/rust-lang/rust/issues/139939> for more information
+   = help: add `#![feature(export_stable)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0658`.