about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_attr_parsing/messages.ftl8
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs69
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs6
-rw-r--r--compiler/rustc_attr_parsing/src/session_diagnostics.rs28
-rw-r--r--compiler/rustc_codegen_llvm/src/base.rs24
-rw-r--r--compiler/rustc_codegen_llvm/src/common.rs14
-rw-r--r--compiler/rustc_codegen_llvm/src/consts.rs226
-rw-r--r--compiler/rustc_codegen_llvm/src/context.rs81
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm/ffi.rs1
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm/mod.rs4
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs6
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs8
-rw-r--r--compiler/rustc_hir/src/attrs/data_structures.rs6
-rw-r--r--compiler/rustc_hir/src/attrs/encode_cross_crate.rs2
-rw-r--r--compiler/rustc_middle/src/middle/codegen_fn_attrs.rs6
-rw-r--r--compiler/rustc_passes/src/check_attr.rs2
-rw-r--r--compiler/rustc_span/src/symbol.rs2
-rw-r--r--library/core/src/lib.rs1
-rw-r--r--library/core/src/os/darwin/mod.rs19
-rw-r--r--library/core/src/os/darwin/objc.rs113
-rw-r--r--library/core/src/os/mod.rs24
-rw-r--r--library/std/src/os/darwin/mod.rs2
-rw-r--r--library/std/src/os/darwin/objc.rs13
-rw-r--r--src/tools/tidy/src/pal.rs1
-rw-r--r--tests/codegen-llvm/auxiliary/darwin_objc_aux.rs27
-rw-r--r--tests/codegen-llvm/darwin-no-objc.rs52
-rw-r--r--tests/codegen-llvm/darwin-objc-abi-v1.rs100
-rw-r--r--tests/codegen-llvm/darwin-objc-abi-v2.rs185
-rw-r--r--tests/codegen-llvm/darwin-objc-cross-crate.rs58
-rw-r--r--tests/ui/README.md6
-rw-r--r--tests/ui/darwin-objc/darwin-objc-bad-arg.rs36
-rw-r--r--tests/ui/darwin-objc/darwin-objc-bad-arg.stderr50
-rw-r--r--tests/ui/darwin-objc/darwin-objc-bad-const.rs17
-rw-r--r--tests/ui/darwin-objc/darwin-objc-bad-const.stderr19
-rw-r--r--tests/ui/darwin-objc/darwin-objc-bad-ref.rs31
-rw-r--r--tests/ui/darwin-objc/darwin-objc-bad-ref.stderr39
-rw-r--r--tests/ui/darwin-objc/darwin-objc-class-selector.rs31
-rw-r--r--tests/ui/darwin-objc/darwin-objc-class.rs39
-rw-r--r--tests/ui/darwin-objc/darwin-objc-selector.rs36
39 files changed, 1381 insertions, 11 deletions
diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl
index 839a5d23c3b..81ec17077c1 100644
--- a/compiler/rustc_attr_parsing/messages.ftl
+++ b/compiler/rustc_attr_parsing/messages.ftl
@@ -122,6 +122,14 @@ attr_parsing_null_on_export = `export_name` may not contain null characters
 
 attr_parsing_null_on_link_section = `link_section` may not contain null characters
 
+attr_parsing_null_on_objc_class = `objc::class!` may not contain null characters
+
+attr_parsing_null_on_objc_selector = `objc::selector!` may not contain null characters
+
+attr_parsing_objc_class_expected_string_literal = `objc::class!` expected a string literal
+
+attr_parsing_objc_selector_expected_string_literal = `objc::selector!` expected a string literal
+
 attr_parsing_repr_ident =
     meta item in `repr` must be an identifier
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
index d5d51f2e79a..262b8213977 100644
--- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
@@ -2,7 +2,10 @@ use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, SanitizerSet, UsedBy};
 use rustc_session::parse::feature_err;
 
 use super::prelude::*;
-use crate::session_diagnostics::{NakedFunctionIncompatibleAttribute, NullOnExport};
+use crate::session_diagnostics::{
+    NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector,
+    ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral,
+};
 
 pub(crate) struct OptimizeParser;
 
@@ -150,6 +153,70 @@ impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
     }
 }
 
+pub(crate) struct ObjcClassParser;
+
+impl<S: Stage> SingleAttributeParser<S> for ObjcClassParser {
+    const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_class];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "ClassName");
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(nv) = args.name_value() else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+        let Some(classname) = nv.value_as_str() else {
+            // `#[rustc_objc_class = ...]` is expected to be used as an implementatioin detail
+            // inside a standard library macro, but `cx.expected_string_literal` exposes too much.
+            // Use a custom error message instead.
+            cx.emit_err(ObjcClassExpectedStringLiteral { span: nv.value_span });
+            return None;
+        };
+        if classname.as_str().contains('\0') {
+            // `#[rustc_objc_class = ...]` will be converted to a null-terminated string,
+            // so it may not contain any null characters.
+            cx.emit_err(NullOnObjcClass { span: nv.value_span });
+            return None;
+        }
+        Some(AttributeKind::ObjcClass { classname, span: cx.attr_span })
+    }
+}
+
+pub(crate) struct ObjcSelectorParser;
+
+impl<S: Stage> SingleAttributeParser<S> for ObjcSelectorParser {
+    const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_selector];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const ALLOWED_TARGETS: AllowedTargets =
+        AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
+    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "methodName");
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(nv) = args.name_value() else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+        let Some(methname) = nv.value_as_str() else {
+            // `#[rustc_objc_selector = ...]` is expected to be used as an implementatioin detail
+            // inside a standard library macro, but `cx.expected_string_literal` exposes too much.
+            // Use a custom error message instead.
+            cx.emit_err(ObjcSelectorExpectedStringLiteral { span: nv.value_span });
+            return None;
+        };
+        if methname.as_str().contains('\0') {
+            // `#[rustc_objc_selector = ...]` will be converted to a null-terminated string,
+            // so it may not contain any null characters.
+            cx.emit_err(NullOnObjcSelector { span: nv.value_span });
+            return None;
+        }
+        Some(AttributeKind::ObjcSelector { methname, span: cx.attr_span })
+    }
+}
+
 #[derive(Default)]
 pub(crate) struct NakedParser {
     span: Option<Span>,
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index d7998048be5..58b13292484 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -20,8 +20,8 @@ use crate::attributes::allow_unstable::{
 use crate::attributes::body::CoroutineParser;
 use crate::attributes::codegen_attrs::{
     ColdParser, CoverageParser, ExportNameParser, ForceTargetFeatureParser, NakedParser,
-    NoMangleParser, OptimizeParser, SanitizeParser, TargetFeatureParser, TrackCallerParser,
-    UsedParser,
+    NoMangleParser, ObjcClassParser, ObjcSelectorParser, OptimizeParser, SanitizeParser,
+    TargetFeatureParser, TrackCallerParser, UsedParser,
 };
 use crate::attributes::confusables::ConfusablesParser;
 use crate::attributes::crate_level::{
@@ -185,6 +185,8 @@ attribute_parsers!(
         Single<LinkageParser>,
         Single<MoveSizeLimitParser>,
         Single<MustUseParser>,
+        Single<ObjcClassParser>,
+        Single<ObjcSelectorParser>,
         Single<OptimizeParser>,
         Single<PathAttributeParser>,
         Single<PatternComplexityLimitParser>,
diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
index 32ea9005a97..2c2b14c8a68 100644
--- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs
+++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
@@ -460,6 +460,34 @@ pub(crate) struct NullOnLinkSection {
 }
 
 #[derive(Diagnostic)]
+#[diag(attr_parsing_null_on_objc_class)]
+pub(crate) struct NullOnObjcClass {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_null_on_objc_selector)]
+pub(crate) struct NullOnObjcSelector {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_objc_class_expected_string_literal)]
+pub(crate) struct ObjcClassExpectedStringLiteral {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(attr_parsing_objc_selector_expected_string_literal)]
+pub(crate) struct ObjcSelectorExpectedStringLiteral {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
 #[diag(attr_parsing_stability_outside_std, code = E0734)]
 pub(crate) struct StabilityOutsideStd {
     #[primary_span]
diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs
index 9cc5d8dbc21..978134cc32b 100644
--- a/compiler/rustc_codegen_llvm/src/base.rs
+++ b/compiler/rustc_codegen_llvm/src/base.rs
@@ -109,18 +109,36 @@ pub(crate) fn compile_codegen_unit(
                 attributes::apply_to_llfn(entry, llvm::AttributePlace::Function, &attrs);
             }
 
+            // Define Objective-C module info and module flags. Note, the module info will
+            // also be added to the `llvm.compiler.used` variable, created later.
+            //
+            // These are only necessary when we need the linker to do its Objective-C-specific
+            // magic. We could theoretically do it unconditionally, but at a slight cost to linker
+            // performance in the common case where it's unnecessary.
+            if !cx.objc_classrefs.borrow().is_empty() || !cx.objc_selrefs.borrow().is_empty() {
+                if cx.objc_abi_version() == 1 {
+                    cx.define_objc_module_info();
+                }
+                cx.add_objc_module_flags();
+            }
+
             // Finalize code coverage by injecting the coverage map. Note, the coverage map will
             // also be added to the `llvm.compiler.used` variable, created next.
             if cx.sess().instrument_coverage() {
                 cx.coverageinfo_finalize();
             }
 
-            // Create the llvm.used and llvm.compiler.used variables.
+            // Create the llvm.used variable.
             if !cx.used_statics.is_empty() {
                 cx.create_used_variable_impl(c"llvm.used", &cx.used_statics);
             }
-            if !cx.compiler_used_statics.is_empty() {
-                cx.create_used_variable_impl(c"llvm.compiler.used", &cx.compiler_used_statics);
+
+            // Create the llvm.compiler.used variable.
+            {
+                let compiler_used_statics = cx.compiler_used_statics.borrow();
+                if !compiler_used_statics.is_empty() {
+                    cx.create_used_variable_impl(c"llvm.compiler.used", &compiler_used_statics);
+                }
             }
 
             // Run replace-all-uses-with for statics that need it. This must
diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs
index 11b79a7fe68..aa2df46329f 100644
--- a/compiler/rustc_codegen_llvm/src/common.rs
+++ b/compiler/rustc_codegen_llvm/src/common.rs
@@ -108,6 +108,10 @@ impl<'ll, CX: Borrow<SCx<'ll>>> GenericCx<'ll, CX> {
         bytes_in_context(self.llcx(), bytes)
     }
 
+    pub(crate) fn null_terminate_const_bytes(&self, bytes: &[u8]) -> &'ll Value {
+        null_terminate_bytes_in_context(self.llcx(), bytes)
+    }
+
     pub(crate) fn const_get_elt(&self, v: &'ll Value, idx: u64) -> &'ll Value {
         unsafe {
             let idx = c_uint::try_from(idx).expect("LLVMGetAggregateElement index overflow");
@@ -381,6 +385,16 @@ pub(crate) fn bytes_in_context<'ll>(llcx: &'ll llvm::Context, bytes: &[u8]) -> &
     }
 }
 
+pub(crate) fn null_terminate_bytes_in_context<'ll>(
+    llcx: &'ll llvm::Context,
+    bytes: &[u8],
+) -> &'ll Value {
+    unsafe {
+        let ptr = bytes.as_ptr() as *const c_char;
+        llvm::LLVMConstStringInContext2(llcx, ptr, bytes.len(), FALSE)
+    }
+}
+
 pub(crate) fn named_struct<'ll>(ty: &'ll Type, elts: &[&'ll Value]) -> &'ll Value {
     let len = c_uint::try_from(elts.len()).expect("LLVMConstStructInContext elements len overflow");
     unsafe { llvm::LLVMConstNamedStruct(ty, elts.as_ptr(), len) }
diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs
index dc9bb743560..a110ecbb75d 100644
--- a/compiler/rustc_codegen_llvm/src/consts.rs
+++ b/compiler/rustc_codegen_llvm/src/consts.rs
@@ -16,6 +16,7 @@ use rustc_middle::mir::mono::MonoItem;
 use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf};
 use rustc_middle::ty::{self, Instance};
 use rustc_middle::{bug, span_bug};
+use rustc_span::Symbol;
 use tracing::{debug, instrument, trace};
 
 use crate::common::CodegenCx;
@@ -331,6 +332,10 @@ impl<'ll> CodegenCx<'ll, '_> {
             }
 
             g
+        } else if let Some(classname) = fn_attrs.objc_class {
+            self.get_objc_classref(classname)
+        } else if let Some(methname) = fn_attrs.objc_selector {
+            self.get_objc_selref(methname)
         } else {
             check_and_apply_linkage(self, fn_attrs, llty, sym, def_id)
         };
@@ -543,8 +548,225 @@ impl<'ll> CodegenCx<'ll, '_> {
 
     /// Add a global value to a list to be stored in the `llvm.compiler.used` variable,
     /// an array of ptr.
-    pub(crate) fn add_compiler_used_global(&mut self, global: &'ll Value) {
-        self.compiler_used_statics.push(global);
+    pub(crate) fn add_compiler_used_global(&self, global: &'ll Value) {
+        self.compiler_used_statics.borrow_mut().push(global);
+    }
+
+    // We do our best here to match what Clang does when compiling Objective-C natively.
+    // See Clang's `CGObjCCommonMac::CreateCStringLiteral`:
+    // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L4134
+    fn define_objc_classname(&self, classname: &str) -> &'ll Value {
+        assert_eq!(self.objc_abi_version(), 1);
+
+        let llval = self.null_terminate_const_bytes(classname.as_bytes());
+        let llty = self.val_ty(llval);
+        let sym = self.generate_local_symbol_name("OBJC_CLASS_NAME_");
+        let g = self.define_global(&sym, llty).unwrap_or_else(|| {
+            bug!("symbol `{}` is already defined", sym);
+        });
+        set_global_alignment(self, g, self.tcx.data_layout.i8_align.abi);
+        llvm::set_initializer(g, llval);
+        llvm::set_linkage(g, llvm::Linkage::PrivateLinkage);
+        llvm::set_section(g, c"__TEXT,__cstring,cstring_literals");
+        llvm::LLVMSetGlobalConstant(g, llvm::TRUE);
+        llvm::LLVMSetUnnamedAddress(g, llvm::UnnamedAddr::Global);
+        self.add_compiler_used_global(g);
+
+        g
+    }
+
+    // We do our best here to match what Clang does when compiling Objective-C natively.
+    // See Clang's `ObjCNonFragileABITypesHelper`:
+    // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L6052
+    fn get_objc_class_t(&self) -> &'ll Type {
+        if let Some(class_t) = self.objc_class_t.get() {
+            return class_t;
+        }
+
+        assert_eq!(self.objc_abi_version(), 2);
+
+        // struct _class_t {
+        //     struct _class_t* isa;
+        //     struct _class_t* const superclass;
+        //     void* cache;
+        //     IMP* vtable;
+        //     struct class_ro_t* ro;
+        // }
+
+        let class_t = self.type_named_struct("struct._class_t");
+        let els = [self.type_ptr(); 5];
+        let packed = false;
+        self.set_struct_body(class_t, &els, packed);
+
+        self.objc_class_t.set(Some(class_t));
+        class_t
+    }
+
+    // We do our best here to match what Clang does when compiling Objective-C natively. We
+    // deduplicate references within a CGU, but we need a reference definition in each referencing
+    // CGU. All attempts at using external references to a single reference definition result in
+    // linker errors.
+    fn get_objc_classref(&self, classname: Symbol) -> &'ll Value {
+        let mut classrefs = self.objc_classrefs.borrow_mut();
+        if let Some(classref) = classrefs.get(&classname).copied() {
+            return classref;
+        }
+
+        let g = match self.objc_abi_version() {
+            1 => {
+                // See Clang's `CGObjCMac::EmitClassRefFromId`:
+                // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L5205
+                let llval = self.define_objc_classname(classname.as_str());
+                let llty = self.type_ptr();
+                let sym = self.generate_local_symbol_name("OBJC_CLASS_REFERENCES_");
+                let g = self.define_global(&sym, llty).unwrap_or_else(|| {
+                    bug!("symbol `{}` is already defined", sym);
+                });
+                set_global_alignment(self, g, self.tcx.data_layout.pointer_align().abi);
+                llvm::set_initializer(g, llval);
+                llvm::set_linkage(g, llvm::Linkage::PrivateLinkage);
+                llvm::set_section(g, c"__OBJC,__cls_refs,literal_pointers,no_dead_strip");
+                self.add_compiler_used_global(g);
+                g
+            }
+            2 => {
+                // See Clang's `CGObjCNonFragileABIMac::EmitClassRefFromId`:
+                // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L7423
+                let llval = {
+                    let extern_sym = format!("OBJC_CLASS_$_{}", classname.as_str());
+                    let extern_llty = self.get_objc_class_t();
+                    self.declare_global(&extern_sym, extern_llty)
+                };
+                let llty = self.type_ptr();
+                let sym = self.generate_local_symbol_name("OBJC_CLASSLIST_REFERENCES_$_");
+                let g = self.define_global(&sym, llty).unwrap_or_else(|| {
+                    bug!("symbol `{}` is already defined", sym);
+                });
+                set_global_alignment(self, g, self.tcx.data_layout.pointer_align().abi);
+                llvm::set_initializer(g, llval);
+                llvm::set_linkage(g, llvm::Linkage::InternalLinkage);
+                llvm::set_section(g, c"__DATA,__objc_classrefs,regular,no_dead_strip");
+                self.add_compiler_used_global(g);
+                g
+            }
+            _ => unreachable!(),
+        };
+
+        classrefs.insert(classname, g);
+        g
+    }
+
+    // We do our best here to match what Clang does when compiling Objective-C natively. We
+    // deduplicate references within a CGU, but we need a reference definition in each referencing
+    // CGU. All attempts at using external references to a single reference definition result in
+    // linker errors.
+    //
+    // Newer versions of Apple Clang generate calls to `@"objc_msgSend$methname"` selector stub
+    // functions. We don't currently do that. The code we generate is closer to what Apple Clang
+    // generates with the `-fno-objc-msgsend-selector-stubs` option.
+    fn get_objc_selref(&self, methname: Symbol) -> &'ll Value {
+        let mut selrefs = self.objc_selrefs.borrow_mut();
+        if let Some(selref) = selrefs.get(&methname).copied() {
+            return selref;
+        }
+
+        let abi_version = self.objc_abi_version();
+
+        // See Clang's `CGObjCCommonMac::CreateCStringLiteral`:
+        // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L4134
+        let methname_llval = self.null_terminate_const_bytes(methname.as_str().as_bytes());
+        let methname_llty = self.val_ty(methname_llval);
+        let methname_sym = self.generate_local_symbol_name("OBJC_METH_VAR_NAME_");
+        let methname_g = self.define_global(&methname_sym, methname_llty).unwrap_or_else(|| {
+            bug!("symbol `{}` is already defined", methname_sym);
+        });
+        set_global_alignment(self, methname_g, self.tcx.data_layout.i8_align.abi);
+        llvm::set_initializer(methname_g, methname_llval);
+        llvm::set_linkage(methname_g, llvm::Linkage::PrivateLinkage);
+        llvm::set_section(
+            methname_g,
+            match abi_version {
+                1 => c"__TEXT,__cstring,cstring_literals",
+                2 => c"__TEXT,__objc_methname,cstring_literals",
+                _ => unreachable!(),
+            },
+        );
+        llvm::LLVMSetGlobalConstant(methname_g, llvm::TRUE);
+        llvm::LLVMSetUnnamedAddress(methname_g, llvm::UnnamedAddr::Global);
+        self.add_compiler_used_global(methname_g);
+
+        // See Clang's `CGObjCMac::EmitSelectorAddr`:
+        // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L5243
+        // And Clang's `CGObjCNonFragileABIMac::EmitSelectorAddr`:
+        // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L7586
+        let selref_llval = methname_g;
+        let selref_llty = self.type_ptr();
+        let selref_sym = self.generate_local_symbol_name("OBJC_SELECTOR_REFERENCES_");
+        let selref_g = self.define_global(&selref_sym, selref_llty).unwrap_or_else(|| {
+            bug!("symbol `{}` is already defined", selref_sym);
+        });
+        set_global_alignment(self, selref_g, self.tcx.data_layout.pointer_align().abi);
+        llvm::set_initializer(selref_g, selref_llval);
+        llvm::set_externally_initialized(selref_g, true);
+        llvm::set_linkage(
+            selref_g,
+            match abi_version {
+                1 => llvm::Linkage::PrivateLinkage,
+                2 => llvm::Linkage::InternalLinkage,
+                _ => unreachable!(),
+            },
+        );
+        llvm::set_section(
+            selref_g,
+            match abi_version {
+                1 => c"__OBJC,__message_refs,literal_pointers,no_dead_strip",
+                2 => c"__DATA,__objc_selrefs,literal_pointers,no_dead_strip",
+                _ => unreachable!(),
+            },
+        );
+        self.add_compiler_used_global(selref_g);
+
+        selrefs.insert(methname, selref_g);
+        selref_g
+    }
+
+    // We do our best here to match what Clang does when compiling Objective-C natively.
+    // See Clang's `ObjCTypesHelper`:
+    // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L5936
+    // And Clang's `CGObjCMac::EmitModuleInfo`:
+    // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L5151
+    pub(crate) fn define_objc_module_info(&mut self) {
+        assert_eq!(self.objc_abi_version(), 1);
+
+        // struct _objc_module {
+        //     long version;                // Hardcoded to 7 in Clang.
+        //     long size;                   // sizeof(struct _objc_module)
+        //     char* name;                  // Hardcoded to classname "" in Clang.
+        //     struct _objc_symtab* symtab; // Null without class or category definitions.
+        //  }
+
+        let llty = self.type_named_struct("struct._objc_module");
+        let i32_llty = self.type_i32();
+        let ptr_llty = self.type_ptr();
+        let packed = false;
+        self.set_struct_body(llty, &[i32_llty, i32_llty, ptr_llty, ptr_llty], packed);
+
+        let version = self.const_uint(i32_llty, 7);
+        let size = self.const_uint(i32_llty, 16);
+        let name = self.define_objc_classname("");
+        let symtab = self.const_null(ptr_llty);
+        let llval = crate::common::named_struct(llty, &[version, size, name, symtab]);
+
+        let sym = "OBJC_MODULES";
+        let g = self.define_global(&sym, llty).unwrap_or_else(|| {
+            bug!("symbol `{}` is already defined", sym);
+        });
+        set_global_alignment(self, g, self.tcx.data_layout.pointer_align().abi);
+        llvm::set_initializer(g, llval);
+        llvm::set_linkage(g, llvm::Linkage::PrivateLinkage);
+        llvm::set_section(g, c"__OBJC,__module_info,regular,no_dead_strip");
+
+        self.add_compiler_used_global(g);
     }
 }
 
diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs
index 5f4385c9c6a..4a8ea11a3a8 100644
--- a/compiler/rustc_codegen_llvm/src/context.rs
+++ b/compiler/rustc_codegen_llvm/src/context.rs
@@ -26,7 +26,7 @@ use rustc_session::config::{
     BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, FunctionReturn, PAuthKey, PacRet,
 };
 use rustc_span::source_map::Spanned;
-use rustc_span::{DUMMY_SP, Span};
+use rustc_span::{DUMMY_SP, Span, Symbol};
 use rustc_symbol_mangling::mangle_internal_symbol;
 use rustc_target::spec::{HasTargetSpec, RelocModel, SmallDataThresholdSupport, Target, TlsModel};
 use smallvec::SmallVec;
@@ -119,7 +119,7 @@ pub(crate) struct FullCx<'ll, 'tcx> {
 
     /// Statics that will be placed in the llvm.compiler.used variable
     /// See <https://llvm.org/docs/LangRef.html#the-llvm-compiler-used-global-variable> for details
-    pub compiler_used_statics: Vec<&'ll Value>,
+    pub compiler_used_statics: RefCell<Vec<&'ll Value>>,
 
     /// Mapping of non-scalar types to llvm types.
     pub type_lowering: RefCell<FxHashMap<(Ty<'tcx>, Option<VariantIdx>), &'ll Type>>,
@@ -146,6 +146,15 @@ pub(crate) struct FullCx<'ll, 'tcx> {
     /// `global_asm!` needs to be able to find this new global so that it can
     /// compute the correct mangled symbol name to insert into the asm.
     pub renamed_statics: RefCell<FxHashMap<DefId, &'ll Value>>,
+
+    /// Cached Objective-C class type
+    pub objc_class_t: Cell<Option<&'ll Type>>,
+
+    /// Cache of Objective-C class references
+    pub objc_classrefs: RefCell<FxHashMap<Symbol, &'ll Value>>,
+
+    /// Cache of Objective-C selector references
+    pub objc_selrefs: RefCell<FxHashMap<Symbol, &'ll Value>>,
 }
 
 fn to_llvm_tls_model(tls_model: TlsModel) -> llvm::ThreadLocalMode {
@@ -615,7 +624,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
                 const_globals: Default::default(),
                 statics_to_rauw: RefCell::new(Vec::new()),
                 used_statics: Vec::new(),
-                compiler_used_statics: Vec::new(),
+                compiler_used_statics: Default::default(),
                 type_lowering: Default::default(),
                 scalar_lltypes: Default::default(),
                 coverage_cx,
@@ -626,6 +635,9 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
                 intrinsics: Default::default(),
                 local_gen_sym_counter: Cell::new(0),
                 renamed_statics: Default::default(),
+                objc_class_t: Cell::new(None),
+                objc_classrefs: Default::default(),
+                objc_selrefs: Default::default(),
             },
             PhantomData,
         )
@@ -650,6 +662,69 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
         llvm::set_linkage(g, llvm::Linkage::AppendingLinkage);
         llvm::set_section(g, c"llvm.metadata");
     }
+
+    /// The Objective-C ABI that is used.
+    ///
+    /// This corresponds to the `-fobjc-abi-version=` flag in Clang / GCC.
+    pub(crate) fn objc_abi_version(&self) -> u32 {
+        assert!(self.tcx.sess.target.is_like_darwin);
+        if self.tcx.sess.target.arch == "x86" && self.tcx.sess.target.os == "macos" {
+            // 32-bit x86 macOS uses ABI version 1 (a.k.a. the "fragile ABI").
+            1
+        } else {
+            // All other Darwin-like targets we support use ABI version 2
+            // (a.k.a the "non-fragile ABI").
+            2
+        }
+    }
+
+    // We do our best here to match what Clang does when compiling Objective-C natively.
+    // See Clang's `CGObjCCommonMac::EmitImageInfo`:
+    // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.8/clang/lib/CodeGen/CGObjCMac.cpp#L5085
+    pub(crate) fn add_objc_module_flags(&self) {
+        let abi_version = self.objc_abi_version();
+
+        llvm::add_module_flag_u32(
+            self.llmod,
+            llvm::ModuleFlagMergeBehavior::Error,
+            "Objective-C Version",
+            abi_version,
+        );
+
+        llvm::add_module_flag_u32(
+            self.llmod,
+            llvm::ModuleFlagMergeBehavior::Error,
+            "Objective-C Image Info Version",
+            0,
+        );
+
+        llvm::add_module_flag_str(
+            self.llmod,
+            llvm::ModuleFlagMergeBehavior::Error,
+            "Objective-C Image Info Section",
+            match abi_version {
+                1 => "__OBJC,__image_info,regular",
+                2 => "__DATA,__objc_imageinfo,regular,no_dead_strip",
+                _ => unreachable!(),
+            },
+        );
+
+        if self.tcx.sess.target.env == "sim" {
+            llvm::add_module_flag_u32(
+                self.llmod,
+                llvm::ModuleFlagMergeBehavior::Error,
+                "Objective-C Is Simulated",
+                1 << 5,
+            );
+        }
+
+        llvm::add_module_flag_u32(
+            self.llmod,
+            llvm::ModuleFlagMergeBehavior::Error,
+            "Objective-C Class Properties",
+            1 << 6,
+        );
+    }
 }
 impl<'ll> SimpleCx<'ll> {
     pub(crate) fn get_type_of_global(&self, val: &'ll Value) -> &'ll Type {
diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
index 0679f55ab7f..e125aba6afc 100644
--- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs
@@ -1237,6 +1237,7 @@ unsafe extern "C" {
     pub(crate) safe fn LLVMSetGlobalConstant(GlobalVar: &Value, IsConstant: Bool);
     pub(crate) safe fn LLVMSetTailCall(CallInst: &Value, IsTailCall: Bool);
     pub(crate) safe fn LLVMSetTailCallKind(CallInst: &Value, kind: TailCallKind);
+    pub(crate) safe fn LLVMSetExternallyInitialized(GlobalVar: &Value, IsExtInit: Bool);
 
     // Operations on attributes
     pub(crate) fn LLVMCreateStringAttribute(
diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs
index d6974e22c85..1115d82fa85 100644
--- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs
@@ -258,6 +258,10 @@ pub(crate) fn set_alignment(llglobal: &Value, align: Align) {
     }
 }
 
+pub(crate) fn set_externally_initialized(llglobal: &Value, is_ext_init: bool) {
+    LLVMSetExternallyInitialized(llglobal, is_ext_init.to_llvm_bool());
+}
+
 /// Get the `name`d comdat from `llmod` and assign it to `llglobal`.
 ///
 /// Inserts the comdat into `llmod` if it does not exist.
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index dc500c363f4..32ae810ecc8 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -296,6 +296,12 @@ fn process_builtin_attrs(
                 AttributeKind::Sanitize { span, .. } => {
                     interesting_spans.sanitize = Some(*span);
                 }
+                AttributeKind::ObjcClass { classname, .. } => {
+                    codegen_fn_attrs.objc_class = Some(*classname);
+                }
+                AttributeKind::ObjcSelector { methname, .. } => {
+                    codegen_fn_attrs.objc_selector = Some(*methname);
+                }
                 _ => {}
             }
         }
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 129ab7eccb5..99d6e93faa9 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -1057,6 +1057,14 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         rustc_std_internal_symbol, Normal, template!(Word), WarnFollowing,
         EncodeCrossCrate::No,
     ),
+    rustc_attr!(
+        rustc_objc_class, Normal, template!(NameValueStr: "ClassName"), ErrorPreceding,
+        EncodeCrossCrate::No,
+    ),
+    rustc_attr!(
+        rustc_objc_selector, Normal, template!(NameValueStr: "methodName"), ErrorPreceding,
+        EncodeCrossCrate::No,
+    ),
 
     // ==========================================================================
     // Internal attributes, Macro related:
diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs
index ea11a99efbc..04b144abd03 100644
--- a/compiler/rustc_hir/src/attrs/data_structures.rs
+++ b/compiler/rustc_hir/src/attrs/data_structures.rs
@@ -594,6 +594,12 @@ pub enum AttributeKind {
     /// Represents `#[non_exhaustive]`
     NonExhaustive(Span),
 
+    /// Represents `#[rustc_objc_class]`
+    ObjcClass { classname: Symbol, span: Span },
+
+    /// Represents `#[rustc_objc_selector]`
+    ObjcSelector { methname: Symbol, span: Span },
+
     /// Represents `#[optimize(size|speed)]`
     Optimize(OptimizeAttr, Span),
 
diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
index 55521c15854..cb4feeb05f1 100644
--- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
+++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
@@ -69,6 +69,8 @@ impl AttributeKind {
             NoMangle(..) => Yes, // Needed for rustdoc
             NoStd(..) => No,
             NonExhaustive(..) => Yes, // Needed for rustdoc
+            ObjcClass { .. } => No,
+            ObjcSelector { .. } => No,
             Optimize(..) => No,
             ParenSugar(..) => No,
             PassByValue(..) => Yes,
diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
index 8b4503073b0..f0d96c6ac89 100644
--- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
+++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
@@ -69,6 +69,10 @@ pub struct CodegenFnAttrs {
     /// The `#[patchable_function_entry(...)]` attribute. Indicates how many nops should be around
     /// the function entry.
     pub patchable_function_entry: Option<PatchableFunctionEntry>,
+    /// The `#[rustc_objc_class = "..."]` attribute.
+    pub objc_class: Option<Symbol>,
+    /// The `#[rustc_objc_selector = "..."]` attribute.
+    pub objc_selector: Option<Symbol>,
 }
 
 #[derive(Copy, Clone, Debug, TyEncodable, TyDecodable, HashStable, PartialEq, Eq)]
@@ -185,6 +189,8 @@ impl CodegenFnAttrs {
             instruction_set: None,
             alignment: None,
             patchable_function_entry: None,
+            objc_class: None,
+            objc_selector: None,
         }
     }
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 2562d2e0b83..60f575cb844 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -276,6 +276,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                     | AttributeKind::PatternComplexityLimit { .. }
                     | AttributeKind::NoCore { .. }
                     | AttributeKind::NoStd { .. }
+                    | AttributeKind::ObjcClass { .. }
+                    | AttributeKind::ObjcSelector { .. }
                 ) => { /* do nothing  */ }
                 Attribute::Unparsed(attr_item) => {
                     style = Some(attr_item.style);
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index cdb0b5b58da..668e80a26b4 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1917,6 +1917,8 @@ symbols! {
         rustc_no_mir_inline,
         rustc_nonnull_optimization_guaranteed,
         rustc_nounwind,
+        rustc_objc_class,
+        rustc_objc_selector,
         rustc_object_lifetime_default,
         rustc_on_unimplemented,
         rustc_outlives,
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 86a68e18b0a..5d52bfb1b12 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -313,6 +313,7 @@ pub mod io;
 pub mod iter;
 pub mod net;
 pub mod option;
+pub mod os;
 pub mod panic;
 pub mod panicking;
 #[unstable(feature = "pattern_type_macro", issue = "123646")]
diff --git a/library/core/src/os/darwin/mod.rs b/library/core/src/os/darwin/mod.rs
new file mode 100644
index 00000000000..8426d82b8ce
--- /dev/null
+++ b/library/core/src/os/darwin/mod.rs
@@ -0,0 +1,19 @@
+//! Platform-specific extensions to `core` for Darwin / Apple platforms.
+//!
+//! This is available on the following operating systems:
+//! - macOS
+//! - iOS
+//! - tvOS
+//! - watchOS
+//! - visionOS
+//!
+//! Note: This module is called "Darwin" as that's the name of the underlying
+//! core OS of the above operating systems, but it should not be confused with
+//! the `-darwin` suffix in the `x86_64-apple-darwin` and
+//! `aarch64-apple-darwin` target names, which are mostly named that way for
+//! legacy reasons.
+
+#![unstable(feature = "darwin_objc", issue = "145496")]
+#![doc(cfg(target_vendor = "apple"))]
+
+pub mod objc;
diff --git a/library/core/src/os/darwin/objc.rs b/library/core/src/os/darwin/objc.rs
new file mode 100644
index 00000000000..928cb54e82c
--- /dev/null
+++ b/library/core/src/os/darwin/objc.rs
@@ -0,0 +1,113 @@
+//! Defines types and macros for Objective-C interoperability.
+
+#![unstable(feature = "darwin_objc", issue = "145496")]
+#![allow(nonstandard_style)]
+
+use crate::fmt;
+
+/// Equivalent to Objective-C’s `struct objc_class` type.
+#[cfg_attr(not(doc), repr(u8))] // An implementation detail we don't want to show up in rustdoc
+pub enum objc_class {
+    #[unstable(
+        feature = "objc_class_variant",
+        reason = "temporary implementation detail",
+        issue = "none"
+    )]
+    #[doc(hidden)]
+    __variant1,
+    #[unstable(
+        feature = "objc_class_variant",
+        reason = "temporary implementation detail",
+        issue = "none"
+    )]
+    #[doc(hidden)]
+    __variant2,
+}
+
+impl fmt::Debug for objc_class {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("objc_class").finish()
+    }
+}
+
+/// Equivalent to Objective-C’s `struct objc_selector` type.
+#[cfg_attr(not(doc), repr(u8))] // An implementation detail we don't want to show up in rustdoc
+pub enum objc_selector {
+    #[unstable(
+        feature = "objc_selector_variant",
+        reason = "temporary implementation detail",
+        issue = "none"
+    )]
+    #[doc(hidden)]
+    __variant1,
+    #[unstable(
+        feature = "objc_selector_variant",
+        reason = "temporary implementation detail",
+        issue = "none"
+    )]
+    #[doc(hidden)]
+    __variant2,
+}
+
+impl fmt::Debug for objc_selector {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("objc_selector").finish()
+    }
+}
+
+/// Equivalent to Objective-C’s `Class` type.
+pub type Class = *mut objc_class;
+
+/// Equivalent to Objective-C’s `SEL` type.
+pub type SEL = *mut objc_selector;
+
+/// Gets a reference to an Objective-C class.
+///
+/// This macro will yield an expression of type [`Class`] for the given class name string literal.
+///
+/// # Example
+///
+/// ```no_run
+/// #![feature(darwin_objc)]
+/// use core::os::darwin::objc;
+///
+/// let string_class = objc::class!("NSString");
+/// ```
+#[allow_internal_unstable(rustc_attrs)]
+pub macro class($classname:expr) {{
+    // Since static Objective-C class references actually end up with multiple definitions
+    // across dylib boundaries, we only expose the value of the static and don't provide a way to
+    // get the address of or a reference to the static.
+    unsafe extern "C" {
+        #[rustc_objc_class = $classname]
+        safe static VAL: $crate::os::darwin::objc::Class;
+    }
+    VAL
+}}
+
+/// Gets a reference to an Objective-C selector.
+///
+/// This macro will yield an expression of type [`SEL`] for the given method name string literal.
+///
+/// It is similar to Objective-C’s `@selector` directive.
+///
+/// # Examples
+///
+/// ```no_run
+/// #![feature(darwin_objc)]
+/// use core::os::darwin::objc;
+///
+/// let alloc_sel = objc::selector!("alloc");
+/// let init_sel = objc::selector!("initWithCString:encoding:");
+/// ```
+#[allow_internal_unstable(rustc_attrs)]
+pub macro selector($methname:expr) {{
+    // Since static Objective-C selector references actually end up with multiple definitions
+    // across dylib boundaries, we only expose the value of the static and don't provide a way to
+    // get the address of or a reference to the static.
+    unsafe extern "C" {
+        #[rustc_objc_selector = $methname]
+        safe static VAL: $crate::os::darwin::objc::SEL;
+    }
+    VAL
+}}
diff --git a/library/core/src/os/mod.rs b/library/core/src/os/mod.rs
new file mode 100644
index 00000000000..897f59f530e
--- /dev/null
+++ b/library/core/src/os/mod.rs
@@ -0,0 +1,24 @@
+//! OS-specific functionality.
+
+#![unstable(feature = "darwin_objc", issue = "145496")]
+
+#[cfg(all(
+    doc,
+    any(
+        all(target_arch = "wasm32", not(target_os = "wasi")),
+        all(target_vendor = "fortanix", target_env = "sgx")
+    )
+))]
+#[unstable(issue = "none", feature = "std_internals")]
+pub mod darwin {}
+
+// darwin
+#[cfg(not(all(
+    doc,
+    any(
+        all(target_arch = "wasm32", not(target_os = "wasi")),
+        all(target_vendor = "fortanix", target_env = "sgx")
+    )
+)))]
+#[cfg(any(target_vendor = "apple", doc))]
+pub mod darwin;
diff --git a/library/std/src/os/darwin/mod.rs b/library/std/src/os/darwin/mod.rs
index 3b1bd974fa3..ff184f477fb 100644
--- a/library/std/src/os/darwin/mod.rs
+++ b/library/std/src/os/darwin/mod.rs
@@ -17,6 +17,8 @@
 #![doc(cfg(target_vendor = "apple"))]
 
 pub mod fs;
+pub mod objc;
+
 // deprecated, but used for public reexport under `std::os::unix::raw`, as
 // well as `std::os::macos`/`std::os::ios`, because those modules precede the
 // decision to remove these.
diff --git a/library/std/src/os/darwin/objc.rs b/library/std/src/os/darwin/objc.rs
new file mode 100644
index 00000000000..a4b31fee7c5
--- /dev/null
+++ b/library/std/src/os/darwin/objc.rs
@@ -0,0 +1,13 @@
+//! Defines types and macros for Objective-C interoperability.
+//!
+//! This module re-exports all the items in [`core::os::darwin::objc`].
+//!
+//! [`core::os::darwin::objc`]: ../../../../core/os/darwin/objc/index.html "mod core::os::darwin::objc"
+
+#![unstable(feature = "darwin_objc", issue = "145496")]
+
+// We can't generate an intra-doc link for this automatically since `core::os::darwin` isn't
+// compiled into `core` on every platform even though it's documented on every platform.
+// We just link to it directly in the module documentation above instead.
+#[cfg(not(doc))]
+pub use core::os::darwin::objc::*;
diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs
index 5b8b44429bb..991ad55809c 100644
--- a/src/tools/tidy/src/pal.rs
+++ b/src/tools/tidy/src/pal.rs
@@ -53,6 +53,7 @@ const EXCEPTION_PATHS: &[&str] = &[
     // core::ffi contains platform-specific type and linkage configuration
     "library/core/src/ffi/mod.rs",
     "library/core/src/ffi/primitives.rs",
+    "library/core/src/os", // Platform-specific public interfaces
     "library/std/src/sys", // Platform-specific code for std lives here.
     "library/std/src/os",  // Platform-specific public interfaces
     // Temporary `std` exceptions
diff --git a/tests/codegen-llvm/auxiliary/darwin_objc_aux.rs b/tests/codegen-llvm/auxiliary/darwin_objc_aux.rs
new file mode 100644
index 00000000000..3c35d003c8a
--- /dev/null
+++ b/tests/codegen-llvm/auxiliary/darwin_objc_aux.rs
@@ -0,0 +1,27 @@
+#![crate_type = "lib"]
+#![feature(darwin_objc)]
+
+use std::os::darwin::objc;
+
+#[link(name = "Foundation", kind = "framework")]
+unsafe extern "C" {}
+
+#[inline(always)]
+pub fn inline_get_object_class() -> objc::Class {
+    objc::class!("NSObject")
+}
+
+#[inline(always)]
+pub fn inline_get_alloc_selector() -> objc::SEL {
+    objc::selector!("alloc")
+}
+
+#[inline(never)]
+pub fn never_inline_get_string_class() -> objc::Class {
+    objc::class!("NSString")
+}
+
+#[inline(never)]
+pub fn never_inline_get_init_selector() -> objc::SEL {
+    objc::selector!("init")
+}
diff --git a/tests/codegen-llvm/darwin-no-objc.rs b/tests/codegen-llvm/darwin-no-objc.rs
new file mode 100644
index 00000000000..fda3671fb6d
--- /dev/null
+++ b/tests/codegen-llvm/darwin-no-objc.rs
@@ -0,0 +1,52 @@
+// Test that we don't generate Objective-C definitions or image info unnecessarily.
+
+//@ add-core-stubs
+//@ revisions: i686_apple_darwin
+//@ [i686_apple_darwin] compile-flags: --target i686-apple-darwin
+//@ [i686_apple_darwin] needs-llvm-components: x86
+//@ revisions: x86_64_macos
+//@ [x86_64_macos] compile-flags: --target x86_64-apple-darwin
+//@ [x86_64_macos] needs-llvm-components: x86
+//@ revisions: aarch64_macos
+//@ [aarch64_macos] compile-flags: --target aarch64-apple-darwin
+//@ [aarch64_macos] needs-llvm-components: aarch64
+//@ revisions: i386_ios
+//@ [i386_ios] compile-flags: --target i386-apple-ios
+//@ [i386_ios] needs-llvm-components: x86
+//@ revisions: x86_64_ios
+//@ [x86_64_ios] compile-flags: --target x86_64-apple-ios
+//@ [x86_64_ios] needs-llvm-components: x86
+//@ revisions: armv7s_ios
+//@ [armv7s_ios] compile-flags: --target armv7s-apple-ios
+//@ [armv7s_ios] needs-llvm-components: arm
+//@ revisions: aarch64_ios
+//@ [aarch64_ios] compile-flags: --target aarch64-apple-ios
+//@ [aarch64_ios] needs-llvm-components: aarch64
+//@ revisions: aarch64_ios_sim
+//@ [aarch64_ios_sim] compile-flags: --target aarch64-apple-ios-sim
+//@ [aarch64_ios_sim] needs-llvm-components: aarch64
+
+#![crate_type = "lib"]
+#![feature(no_core, lang_items)]
+#![no_core]
+
+extern crate minicore;
+use minicore::*;
+
+#[no_mangle]
+pub fn foo() {}
+
+// CHECK-NOT: %struct._class_t
+// CHECK-NOT: %struct._objc_module
+// CHECK-NOT: @OBJC_CLASS_NAME_
+// CHECK-NOT: @"OBJC_CLASS_$_{{[0-9A-Z_a-z]+}}"
+// CHECK-NOT: @"OBJC_CLASSLIST_REFERENCES_$_.{{[0-9]+}}"
+// CHECK-NOT: @OBJC_METH_VAR_NAME_
+// CHECK-NOT: @OBJC_SELECTOR_REFERENCES_
+// CHECK-NOT: @OBJC_MODULES
+
+// CHECK-NOT: !"Objective-C Version"
+// CHECK-NOT: !"Objective-C Image Info Version"
+// CHECK-NOT: !"Objective-C Image Info Section"
+// CHECK-NOT: !"Objective-C Is Simulated"
+// CHECK-NOT: !"Objective-C Class Properties"
diff --git a/tests/codegen-llvm/darwin-objc-abi-v1.rs b/tests/codegen-llvm/darwin-objc-abi-v1.rs
new file mode 100644
index 00000000000..0fc1de9332a
--- /dev/null
+++ b/tests/codegen-llvm/darwin-objc-abi-v1.rs
@@ -0,0 +1,100 @@
+// ignore-tidy-linelength
+//@ add-core-stubs
+//@ revisions: i686_apple_darwin
+//@ [i686_apple_darwin] compile-flags: --target i686-apple-darwin
+//@ [i686_apple_darwin] needs-llvm-components: x86
+
+#![crate_type = "lib"]
+#![feature(no_core, lang_items, rustc_attrs)]
+#![no_core]
+
+extern crate minicore;
+use minicore::*;
+
+#[no_mangle]
+pub fn get_class() -> *mut () {
+    unsafe extern "C" {
+        #[rustc_objc_class = "MyClass"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_class_again() -> *mut () {
+    // Codegen should de-duplicate this class with the one from get_class above.
+    unsafe extern "C" {
+        #[rustc_objc_class = "MyClass"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_selector() -> *mut () {
+    unsafe extern "C" {
+        #[rustc_objc_selector = "myMethod"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_selector_again() -> *mut () {
+    // Codegen should de-duplicate this selector with the one from get_selector above.
+    unsafe extern "C" {
+        #[rustc_objc_selector = "myMethod"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_other_class() -> *mut () {
+    unsafe extern "C" {
+        #[rustc_objc_class = "OtherClass"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_other_selector() -> *mut () {
+    unsafe extern "C" {
+        #[rustc_objc_selector = "otherMethod"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+// CHECK: %struct._objc_module = type { i32, i32, ptr, ptr }
+
+// CHECK: @OBJC_CLASS_NAME_.{{[0-9]+}} = private unnamed_addr constant [8 x i8] c"MyClass\00", section "__TEXT,__cstring,cstring_literals", align 1
+// CHECK: @OBJC_CLASS_REFERENCES_.{{[0-9]+}} = private global ptr @OBJC_CLASS_NAME_.{{[0-9]+}}, section "__OBJC,__cls_refs,literal_pointers,no_dead_strip", align 4
+// CHECK-NOT: @OBJC_CLASS_NAME_
+// CHECK-NOT: @OBJC_CLASS_REFERENCES_
+
+// CHECK: @OBJC_METH_VAR_NAME_.{{[0-9]+}} = private unnamed_addr constant [9 x i8] c"myMethod\00", section "__TEXT,__cstring,cstring_literals", align 1
+// CHECK: @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}} = private externally_initialized global ptr @OBJC_METH_VAR_NAME_.{{[0-9]+}}, section "__OBJC,__message_refs,literal_pointers,no_dead_strip", align 4
+// CHECK-NOT: @OBJC_METH_VAR_NAME_
+// CHECK-NOT: @OBJC_SELECTOR_REFERENCES_
+
+// CHECK: @OBJC_CLASS_NAME_.{{[0-9]+}} = private unnamed_addr constant [11 x i8] c"OtherClass\00", section "__TEXT,__cstring,cstring_literals", align 1
+// CHECK: @OBJC_CLASS_REFERENCES_.{{[0-9]+}} = private global ptr @OBJC_CLASS_NAME_.{{[0-9]+}}, section "__OBJC,__cls_refs,literal_pointers,no_dead_strip", align 4
+
+// CHECK: @OBJC_METH_VAR_NAME_.{{[0-9]+}} = private unnamed_addr constant [12 x i8] c"otherMethod\00", section "__TEXT,__cstring,cstring_literals", align 1
+// CHECK: @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}} = private externally_initialized global ptr @OBJC_METH_VAR_NAME_.{{[0-9]+}}, section "__OBJC,__message_refs,literal_pointers,no_dead_strip", align 4
+
+// CHECK: @OBJC_CLASS_NAME_.{{[0-9]+}} = private unnamed_addr constant [1 x i8] zeroinitializer, section "__TEXT,__cstring,cstring_literals", align 1
+// CHECK: @OBJC_MODULES = private global %struct._objc_module { i32 7, i32 16, ptr @OBJC_CLASS_NAME_.{{[0-9]+}}, ptr null }, section "__OBJC,__module_info,regular,no_dead_strip", align 4
+
+// CHECK: load ptr, ptr @OBJC_CLASS_REFERENCES_.{{[0-9]+}}, align 4
+// CHECK: load ptr, ptr @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}}, align 4
+// CHECK: load ptr, ptr @OBJC_CLASS_REFERENCES_.{{[0-9]+}}, align 4
+// CHECK: load ptr, ptr @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}}, align 4
+
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Version", i32 1}
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Image Info Version", i32 0}
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Image Info Section", !"__OBJC,__image_info,regular"}
+// CHECK-NOT: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Class Properties", i32 64}
diff --git a/tests/codegen-llvm/darwin-objc-abi-v2.rs b/tests/codegen-llvm/darwin-objc-abi-v2.rs
new file mode 100644
index 00000000000..f142371d582
--- /dev/null
+++ b/tests/codegen-llvm/darwin-objc-abi-v2.rs
@@ -0,0 +1,185 @@
+// ignore-tidy-linelength
+//@ add-core-stubs
+//@ revisions: x86_64_macos
+//@ [x86_64_macos] compile-flags: --target x86_64-apple-darwin
+//@ [x86_64_macos] needs-llvm-components: x86
+//@ revisions: aarch64_macos
+//@ [aarch64_macos] compile-flags: --target aarch64-apple-darwin
+//@ [aarch64_macos] needs-llvm-components: aarch64
+//@ revisions: i386_ios
+//@ [i386_ios] compile-flags: --target i386-apple-ios
+//@ [i386_ios] needs-llvm-components: x86
+//@ revisions: x86_64_ios
+//@ [x86_64_ios] compile-flags: --target x86_64-apple-ios
+//@ [x86_64_ios] needs-llvm-components: x86
+//@ revisions: armv7s_ios
+//@ [armv7s_ios] compile-flags: --target armv7s-apple-ios
+//@ [armv7s_ios] needs-llvm-components: arm
+//@ revisions: aarch64_ios
+//@ [aarch64_ios] compile-flags: --target aarch64-apple-ios
+//@ [aarch64_ios] needs-llvm-components: aarch64
+//@ revisions: aarch64_ios_sim
+//@ [aarch64_ios_sim] compile-flags: --target aarch64-apple-ios-sim
+//@ [aarch64_ios_sim] needs-llvm-components: aarch64
+
+#![crate_type = "lib"]
+#![feature(no_core, lang_items, rustc_attrs)]
+#![no_core]
+
+extern crate minicore;
+use minicore::*;
+
+#[no_mangle]
+pub fn get_class() -> *mut () {
+    unsafe extern "C" {
+        #[rustc_objc_class = "MyClass"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_class_again() -> *mut () {
+    // Codegen should de-duplicate this class with the one from get_class above.
+    unsafe extern "C" {
+        #[rustc_objc_class = "MyClass"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_selector() -> *mut () {
+    unsafe extern "C" {
+        #[rustc_objc_selector = "myMethod"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_selector_again() -> *mut () {
+    // Codegen should de-duplicate this selector with the one from get_selector above.
+    unsafe extern "C" {
+        #[rustc_objc_selector = "myMethod"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_other_class() -> *mut () {
+    unsafe extern "C" {
+        #[rustc_objc_class = "OtherClass"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+#[no_mangle]
+pub fn get_other_selector() -> *mut () {
+    unsafe extern "C" {
+        #[rustc_objc_selector = "otherMethod"]
+        safe static VAL: *mut ();
+    }
+    VAL
+}
+
+// CHECK: %struct._class_t = type { ptr, ptr, ptr, ptr, ptr }
+
+// CHECK: @"OBJC_CLASS_$_MyClass" = external global %struct._class_t
+// CHECK: @"OBJC_CLASSLIST_REFERENCES_$_.{{[0-9]+}}" = internal global ptr @"OBJC_CLASS_$_MyClass", section "__DATA,__objc_classrefs,regular,no_dead_strip",
+// x86_64_macos-SAME: align 8
+// aarch64_macos-SAME: align 8
+// i386_ios-SAME: align 4
+// x86_64_ios-SAME: align 8
+// armv7s_ios-SAME: align 4
+// aarch64_ios-SAME: align 8
+// aarch64_ios_sim-SAME: align 8
+// CHECK-NOT: @"OBJC_CLASS_$_MyClass"
+// CHECK-NOT: @"OBJC_CLASSLIST_REFERENCES_$_
+
+// CHECK: @OBJC_METH_VAR_NAME_.{{[0-9]+}} = private unnamed_addr constant [9 x i8] c"myMethod\00", section "__TEXT,__objc_methname,cstring_literals", align 1
+// CHECK: @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}} = internal externally_initialized global ptr @OBJC_METH_VAR_NAME_.{{[0-9]+}}, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip",
+// x86_64_macos-SAME: align 8
+// aarch64_macos-SAME: align 8
+// i386_ios-SAME: align 4
+// x86_64_ios-SAME: align 8
+// armv7s_ios-SAME: align 4
+// aarch64_ios-SAME: align 8
+// aarch64_ios_sim-SAME: align 8
+// CHECK-NOT: @OBJC_METH_VAR_NAME_
+// CHECK-NOT: @OBJC_SELECTOR_REFERENCES_
+
+// CHECK: @"OBJC_CLASS_$_OtherClass" = external global %struct._class_t
+// CHECK: @"OBJC_CLASSLIST_REFERENCES_$_.{{[0-9]+}}" = internal global ptr @"OBJC_CLASS_$_OtherClass", section "__DATA,__objc_classrefs,regular,no_dead_strip",
+// x86_64_macos-SAME: align 8
+// aarch64_macos-SAME: align 8
+// i386_ios-SAME: align 4
+// x86_64_ios-SAME: align 8
+// armv7s_ios-SAME: align 4
+// aarch64_ios-SAME: align 8
+// aarch64_ios_sim-SAME: align 8
+
+// CHECK: @OBJC_METH_VAR_NAME_.{{[0-9]+}} = private unnamed_addr constant [12 x i8] c"otherMethod\00", section "__TEXT,__objc_methname,cstring_literals", align 1
+// CHECK: @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}} = internal externally_initialized global ptr @OBJC_METH_VAR_NAME_.{{[0-9]+}}, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip",
+// x86_64_macos-SAME: align 8
+// aarch64_macos-SAME: align 8
+// i386_ios-SAME: align 4
+// x86_64_ios-SAME: align 8
+// armv7s_ios-SAME: align 4
+// aarch64_ios-SAME: align 8
+// aarch64_ios_sim-SAME: align 8
+
+// CHECK-NOT: @OBJC_CLASS_NAME_
+// CHECK-NOT: @OBJC_MODULES
+
+// CHECK: load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$_.{{[0-9]+}}",
+// x86_64_macos-SAME: align 8
+// aarch64_macos-SAME: align 8
+// i386_ios-SAME: align 4
+// x86_64_ios-SAME: align 8
+// armv7s_ios-SAME: align 4
+// aarch64_ios-SAME: align 8
+// aarch64_ios_sim-SAME: align 8
+
+// CHECK: load ptr, ptr @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}},
+// x86_64_macos-SAME: align 8
+// aarch64_macos-SAME: align 8
+// i386_ios-SAME: align 4
+// x86_64_ios-SAME: align 8
+// armv7s_ios-SAME: align 4
+// aarch64_ios-SAME: align 8
+// aarch64_ios_sim-SAME: align 8
+
+// CHECK: load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$_.{{[0-9]+}}",
+// x86_64_macos-SAME: align 8
+// aarch64_macos-SAME: align 8
+// i386_ios-SAME: align 4
+// x86_64_ios-SAME: align 8
+// armv7s_ios-SAME: align 4
+// aarch64_ios-SAME: align 8
+// aarch64_ios_sim-SAME: align 8
+
+// CHECK: load ptr, ptr @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}},
+// x86_64_macos-SAME: align 8
+// aarch64_macos-SAME: align 8
+// i386_ios-SAME: align 4
+// x86_64_ios-SAME: align 8
+// armv7s_ios-SAME: align 4
+// aarch64_ios-SAME: align 8
+// aarch64_ios_sim-SAME: align 8
+
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Version", i32 2}
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Image Info Version", i32 0}
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
+
+// x86_64_macos-NOT: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+// aarch64_macos-NOT: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+// i386_ios: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+// x86_64_ios: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+// armv7s_ios-NOT: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+// aarch64_ios-NOT: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+// aarch64_ios_sim: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Class Properties", i32 64}
diff --git a/tests/codegen-llvm/darwin-objc-cross-crate.rs b/tests/codegen-llvm/darwin-objc-cross-crate.rs
new file mode 100644
index 00000000000..74ad9a27346
--- /dev/null
+++ b/tests/codegen-llvm/darwin-objc-cross-crate.rs
@@ -0,0 +1,58 @@
+// Test that Objective-C class and selector references inlined across crates
+// get defined in this CGU but non-inline references don't.
+
+// ignore-tidy-linelength
+//@ aux-build: darwin_objc_aux.rs
+//@ revisions: x86_64_macos aarch64_macos
+//@ [x86_64_macos] only-x86_64-apple-darwin
+//@ [aarch64_macos] only-aarch64-apple-darwin
+
+#![crate_type = "lib"]
+#![feature(darwin_objc)]
+
+use std::os::darwin::objc;
+
+extern crate darwin_objc_aux as aux;
+
+#[no_mangle]
+pub fn get_object_class() -> objc::Class {
+    aux::inline_get_object_class()
+}
+
+#[no_mangle]
+pub fn get_alloc_selector() -> objc::SEL {
+    aux::inline_get_alloc_selector()
+}
+
+#[no_mangle]
+pub fn get_string_class() -> objc::Class {
+    aux::never_inline_get_string_class()
+}
+
+#[no_mangle]
+pub fn get_init_selector() -> objc::SEL {
+    aux::never_inline_get_init_selector()
+}
+
+// CHECK: %struct._class_t = type { ptr, ptr, ptr, ptr, ptr }
+
+// CHECK: @"OBJC_CLASS_$_NSObject" = external global %struct._class_t
+// CHECK: @"OBJC_CLASSLIST_REFERENCES_$_.{{[0-9]+}}" = internal global ptr @"OBJC_CLASS_$_NSObject", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
+
+// CHECK: @OBJC_METH_VAR_NAME_.{{[0-9]+}} = private unnamed_addr constant [6 x i8] c"alloc\00", section "__TEXT,__objc_methname,cstring_literals", align 1
+// CHECK: @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}} = internal externally_initialized global ptr @OBJC_METH_VAR_NAME_.{{[0-9]+}}, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip", align 8
+
+// CHECK-NOT: @"OBJC_CLASS_$_NSString" = external global %struct._class_t
+// CHECK-NOT: @"OBJC_CLASSLIST_REFERENCES_$_.{{[0-9]+}}" = internal global ptr @"OBJC_CLASS_$_NSString"
+
+// CHECK-NOT: @OBJC_METH_VAR_NAME_.{{[0-9]+}} = private unnamed_addr constant [5 x i8] c"init\00"
+// CHECK-NOT: @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}} = internal externally_initialized global ptr @OBJC_METH_VAR_NAME_.{{[0-9]+}}
+
+// CHECK: load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$_.{{[0-9]+}}", align 8
+// CHECK: load ptr, ptr @OBJC_SELECTOR_REFERENCES_.{{[0-9]+}}, align 8
+
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Version", i32 2}
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Image Info Version", i32 0}
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Image Info Section", !"__DATA,__objc_imageinfo,regular,no_dead_strip"}
+// CHECK-NOT: !{{[0-9]+}} = !{i32 1, !"Objective-C Is Simulated", i32 32}
+// CHECK: !{{[0-9]+}} = !{i32 1, !"Objective-C Class Properties", i32 64}
diff --git a/tests/ui/README.md b/tests/ui/README.md
index 66c1bb905a7..3b28ef694dd 100644
--- a/tests/ui/README.md
+++ b/tests/ui/README.md
@@ -350,6 +350,12 @@ Tests for FFI with C varargs (`va_list`).
 
 Tests for detection and handling of cyclic trait dependencies.
 
+## `tests/ui/darwin-objc/`: Darwin Objective-C
+
+Tests exercising `#![feature(darwin_objc)]`.
+
+See [Tracking Issue for `darwin_objc` #145496](https://github.com/rust-lang/rust/issues/145496).
+
 ## `tests/ui/dataflow_const_prop/`
 
 Contains a single regression test for const prop in `SwitchInt` pass crashing when `ptr2int` transmute is involved.
diff --git a/tests/ui/darwin-objc/darwin-objc-bad-arg.rs b/tests/ui/darwin-objc/darwin-objc-bad-arg.rs
new file mode 100644
index 00000000000..70eb83aa052
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-bad-arg.rs
@@ -0,0 +1,36 @@
+// Test that `objc::class!` and `objc::selector!` only take string literals.
+
+//@ edition: 2024
+//@ only-apple
+
+#![feature(darwin_objc)]
+
+use std::os::darwin::objc;
+
+pub fn main() {
+    let s = "NSObject";
+    objc::class!(s);
+    //~^ ERROR attribute value must be a literal
+
+    objc::class!(NSObject);
+    //~^ ERROR attribute value must be a literal
+
+    objc::class!(123);
+    //~^ ERROR `objc::class!` expected a string literal
+
+    objc::class!("NSObject\0");
+    //~^ ERROR `objc::class!` may not contain null characters
+
+    let s = "alloc";
+    objc::selector!(s);
+    //~^ ERROR attribute value must be a literal
+
+    objc::selector!(alloc);
+    //~^ ERROR attribute value must be a literal
+
+    objc::selector!(123);
+    //~^ ERROR `objc::selector!` expected a string literal
+
+    objc::selector!("alloc\0");
+    //~^ ERROR `objc::selector!` may not contain null characters
+}
diff --git a/tests/ui/darwin-objc/darwin-objc-bad-arg.stderr b/tests/ui/darwin-objc/darwin-objc-bad-arg.stderr
new file mode 100644
index 00000000000..99eeb27e30c
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-bad-arg.stderr
@@ -0,0 +1,50 @@
+error: attribute value must be a literal
+  --> $DIR/darwin-objc-bad-arg.rs:12:18
+   |
+LL |     objc::class!(s);
+   |                  ^
+
+error: attribute value must be a literal
+  --> $DIR/darwin-objc-bad-arg.rs:15:18
+   |
+LL |     objc::class!(NSObject);
+   |                  ^^^^^^^^
+
+error: `objc::class!` expected a string literal
+  --> $DIR/darwin-objc-bad-arg.rs:18:18
+   |
+LL |     objc::class!(123);
+   |                  ^^^
+
+error: `objc::class!` may not contain null characters
+  --> $DIR/darwin-objc-bad-arg.rs:21:18
+   |
+LL |     objc::class!("NSObject\0");
+   |                  ^^^^^^^^^^^^
+
+error: attribute value must be a literal
+  --> $DIR/darwin-objc-bad-arg.rs:25:21
+   |
+LL |     objc::selector!(s);
+   |                     ^
+
+error: attribute value must be a literal
+  --> $DIR/darwin-objc-bad-arg.rs:28:21
+   |
+LL |     objc::selector!(alloc);
+   |                     ^^^^^
+
+error: `objc::selector!` expected a string literal
+  --> $DIR/darwin-objc-bad-arg.rs:31:21
+   |
+LL |     objc::selector!(123);
+   |                     ^^^
+
+error: `objc::selector!` may not contain null characters
+  --> $DIR/darwin-objc-bad-arg.rs:34:21
+   |
+LL |     objc::selector!("alloc\0");
+   |                     ^^^^^^^^^
+
+error: aborting due to 8 previous errors
+
diff --git a/tests/ui/darwin-objc/darwin-objc-bad-const.rs b/tests/ui/darwin-objc/darwin-objc-bad-const.rs
new file mode 100644
index 00000000000..ccf28697b48
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-bad-const.rs
@@ -0,0 +1,17 @@
+// Test that `objc::class!` and `objc::selector!` aren't `const` expressions.
+// The system gives them their final values at dynamic load time.
+
+//@ edition: 2024
+//@ only-apple
+
+#![feature(darwin_objc)]
+
+use std::os::darwin::objc;
+
+pub const CLASS: objc::Class = objc::class!("NSObject");
+//~^ ERROR cannot access extern static `CLASS::VAL` [E0080]
+
+pub const SELECTOR: objc::SEL = objc::selector!("alloc");
+//~^ ERROR cannot access extern static `SELECTOR::VAL` [E0080]
+
+pub fn main() {}
diff --git a/tests/ui/darwin-objc/darwin-objc-bad-const.stderr b/tests/ui/darwin-objc/darwin-objc-bad-const.stderr
new file mode 100644
index 00000000000..43d0b7d761f
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-bad-const.stderr
@@ -0,0 +1,19 @@
+error[E0080]: cannot access extern static `CLASS::VAL`
+  --> $DIR/darwin-objc-bad-const.rs:11:32
+   |
+LL | pub const CLASS: objc::Class = objc::class!("NSObject");
+   |                                ^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `CLASS` failed here
+   |
+   = note: this error originates in the macro `objc::class` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0080]: cannot access extern static `SELECTOR::VAL`
+  --> $DIR/darwin-objc-bad-const.rs:14:33
+   |
+LL | pub const SELECTOR: objc::SEL = objc::selector!("alloc");
+   |                                 ^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `SELECTOR` failed here
+   |
+   = note: this error originates in the macro `objc::selector` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/darwin-objc/darwin-objc-bad-ref.rs b/tests/ui/darwin-objc/darwin-objc-bad-ref.rs
new file mode 100644
index 00000000000..fb1437366dd
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-bad-ref.rs
@@ -0,0 +1,31 @@
+// Test that `objc::class!` and `objc::selector!` can't be returned by reference.
+// A single instance may have multiple addresses (e.g. across dylib boundaries).
+
+//@ edition: 2024
+//@ only-apple
+
+#![feature(darwin_objc)]
+
+use std::os::darwin::objc;
+
+pub fn class_ref<'a>() -> &'a objc::Class {
+    &objc::class!("NSObject")
+    //~^ ERROR cannot return reference to temporary value [E0515]
+}
+
+pub fn class_ref_static() -> &'static objc::Class {
+    &objc::class!("NSObject")
+    //~^ ERROR cannot return reference to temporary value [E0515]
+}
+
+pub fn selector_ref<'a>() -> &'a objc::SEL {
+    &objc::selector!("alloc")
+    //~^ ERROR cannot return reference to temporary value [E0515]
+}
+
+pub fn selector_ref_static() -> &'static objc::SEL {
+    &objc::selector!("alloc")
+    //~^ ERROR cannot return reference to temporary value [E0515]
+}
+
+pub fn main() {}
diff --git a/tests/ui/darwin-objc/darwin-objc-bad-ref.stderr b/tests/ui/darwin-objc/darwin-objc-bad-ref.stderr
new file mode 100644
index 00000000000..b09e6a75c84
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-bad-ref.stderr
@@ -0,0 +1,39 @@
+error[E0515]: cannot return reference to temporary value
+  --> $DIR/darwin-objc-bad-ref.rs:12:5
+   |
+LL |     &objc::class!("NSObject")
+   |     ^------------------------
+   |     ||
+   |     |temporary value created here
+   |     returns a reference to data owned by the current function
+
+error[E0515]: cannot return reference to temporary value
+  --> $DIR/darwin-objc-bad-ref.rs:17:5
+   |
+LL |     &objc::class!("NSObject")
+   |     ^------------------------
+   |     ||
+   |     |temporary value created here
+   |     returns a reference to data owned by the current function
+
+error[E0515]: cannot return reference to temporary value
+  --> $DIR/darwin-objc-bad-ref.rs:22:5
+   |
+LL |     &objc::selector!("alloc")
+   |     ^------------------------
+   |     ||
+   |     |temporary value created here
+   |     returns a reference to data owned by the current function
+
+error[E0515]: cannot return reference to temporary value
+  --> $DIR/darwin-objc-bad-ref.rs:27:5
+   |
+LL |     &objc::selector!("alloc")
+   |     ^------------------------
+   |     ||
+   |     |temporary value created here
+   |     returns a reference to data owned by the current function
+
+error: aborting due to 4 previous errors
+
+For more information about this error, try `rustc --explain E0515`.
diff --git a/tests/ui/darwin-objc/darwin-objc-class-selector.rs b/tests/ui/darwin-objc/darwin-objc-class-selector.rs
new file mode 100644
index 00000000000..b9a2fc3634f
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-class-selector.rs
@@ -0,0 +1,31 @@
+// Call `[NSObject class]` using `objc::class!` and `objc::selector!`.
+
+//@ edition: 2024
+//@ only-apple
+//@ run-pass
+
+#![feature(darwin_objc)]
+
+use std::mem::transmute;
+use std::os::darwin::objc;
+
+#[link(name = "Foundation", kind = "framework")]
+unsafe extern "C" {}
+
+#[link(name = "objc", kind = "dylib")]
+unsafe extern "C" {
+    unsafe fn objc_msgSend();
+}
+
+fn main() {
+    let msg_send_fn = unsafe {
+        transmute::<
+            unsafe extern "C" fn(),
+            unsafe extern "C" fn(objc::Class, objc::SEL) -> objc::Class,
+        >(objc_msgSend)
+    };
+    let static_sel = objc::selector!("class");
+    let static_class = objc::class!("NSObject");
+    let runtime_class = unsafe { msg_send_fn(static_class, static_sel) };
+    assert_eq!(static_class, runtime_class);
+}
diff --git a/tests/ui/darwin-objc/darwin-objc-class.rs b/tests/ui/darwin-objc/darwin-objc-class.rs
new file mode 100644
index 00000000000..851149d8726
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-class.rs
@@ -0,0 +1,39 @@
+// Test that `objc::class!` returns the same thing as `objc_lookUpClass`.
+
+//@ edition: 2024
+//@ only-apple
+//@ run-pass
+
+#![feature(darwin_objc)]
+
+use std::ffi::c_char;
+use std::os::darwin::objc;
+
+#[link(name = "Foundation", kind = "framework")]
+unsafe extern "C" {}
+
+#[link(name = "objc")]
+unsafe extern "C" {
+    fn objc_lookUpClass(methname: *const c_char) -> objc::Class;
+}
+
+fn get_object_class() -> objc::Class {
+    objc::class!("NSObject")
+}
+
+fn lookup_object_class() -> objc::Class {
+    unsafe { objc_lookUpClass(c"NSObject".as_ptr()) }
+}
+
+fn get_string_class() -> objc::Class {
+    objc::class!("NSString")
+}
+
+fn lookup_string_class() -> objc::Class {
+    unsafe { objc_lookUpClass(c"NSString".as_ptr()) }
+}
+
+fn main() {
+    assert_eq!(get_object_class(), lookup_object_class());
+    assert_eq!(get_string_class(), lookup_string_class());
+}
diff --git a/tests/ui/darwin-objc/darwin-objc-selector.rs b/tests/ui/darwin-objc/darwin-objc-selector.rs
new file mode 100644
index 00000000000..008ae4c4ca4
--- /dev/null
+++ b/tests/ui/darwin-objc/darwin-objc-selector.rs
@@ -0,0 +1,36 @@
+// Test that `objc::selector!` returns the same thing as `sel_registerName`.
+
+//@ edition: 2024
+//@ only-apple
+//@ run-pass
+
+#![feature(darwin_objc)]
+
+use std::ffi::c_char;
+use std::os::darwin::objc;
+
+#[link(name = "objc")]
+unsafe extern "C" {
+    fn sel_registerName(methname: *const c_char) -> objc::SEL;
+}
+
+fn get_alloc_selector() -> objc::SEL {
+    objc::selector!("alloc")
+}
+
+fn register_alloc_selector() -> objc::SEL {
+    unsafe { sel_registerName(c"alloc".as_ptr()) }
+}
+
+fn get_init_selector() -> objc::SEL {
+    objc::selector!("initWithCString:encoding:")
+}
+
+fn register_init_selector() -> objc::SEL {
+    unsafe { sel_registerName(c"initWithCString:encoding:".as_ptr()) }
+}
+
+fn main() {
+    assert_eq!(get_alloc_selector(), register_alloc_selector());
+    assert_eq!(get_init_selector(), register_init_selector());
+}