From de44e8191a08758401b399877162702052a8767d Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 8 Sep 2025 23:12:13 +0200 Subject: [PATCH 1/3] refactor: Merge collate_raw_dylibs_windows and collate_raw_dylibs_elf --- .../src/back/link/raw_dylib.rs | 57 +++++-------------- 1 file changed, 13 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs index 9f42991d4c06a..c4a68920ca4f3 100644 --- a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs +++ b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs @@ -23,7 +23,7 @@ use crate::{NativeLib, common, errors}; /// then the CodegenResults value contains one NativeLib instance for each block. However, the /// linker appears to expect only a single import library for each library used, so we need to /// collate the symbols together by library name before generating the import libraries. -fn collate_raw_dylibs_windows<'a>( +fn collate_raw_dylibs<'a>( sess: &Session, used_libraries: impl IntoIterator, ) -> Vec<(String, Vec)> { @@ -32,14 +32,21 @@ fn collate_raw_dylibs_windows<'a>( for lib in used_libraries { if lib.kind == NativeLibKind::RawDylib { - let ext = if lib.verbatim { "" } else { ".dll" }; - let name = format!("{}{}", lib.name, ext); + let name = if lib.verbatim { + lib.name.as_str().to_owned() + } else { + let prefix = sess.target.dll_prefix.as_ref(); + let suffix = sess.target.dll_suffix.as_ref(); + format!("{prefix}{}{suffix}", lib.name) + }; let imports = dylib_table.entry(name.clone()).or_default(); for import in &lib.dll_imports { if let Some(old_import) = imports.insert(import.name, import) { // FIXME: when we add support for ordinals, figure out if we need to do anything // if we have two DllImport values with the same name but different ordinals. - if import.calling_convention != old_import.calling_convention { + if sess.target.is_like_windows + && import.calling_convention != old_import.calling_convention + { sess.dcx().emit_err(errors::MultipleExternalFuncDecl { span: import.span, function: import.name, @@ -66,7 +73,7 @@ pub(super) fn create_raw_dylib_dll_import_libs<'a>( tmpdir: &Path, is_direct_dependency: bool, ) -> Vec { - collate_raw_dylibs_windows(sess, used_libraries) + collate_raw_dylibs(sess, used_libraries) .into_iter() .map(|(raw_dylib_name, raw_dylib_imports)| { let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" }; @@ -119,50 +126,12 @@ pub(super) fn create_raw_dylib_dll_import_libs<'a>( .collect() } -/// Extract all symbols defined in raw-dylib libraries, collated by library name. -/// -/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library, -/// then the CodegenResults value contains one NativeLib instance for each block. However, the -/// linker appears to expect only a single import library for each library used, so we need to -/// collate the symbols together by library name before generating the import libraries. -fn collate_raw_dylibs_elf<'a>( - sess: &Session, - used_libraries: impl IntoIterator, -) -> Vec<(String, Vec)> { - // Use index maps to preserve original order of imports and libraries. - let mut dylib_table = FxIndexMap::>::default(); - - for lib in used_libraries { - if lib.kind == NativeLibKind::RawDylib { - let filename = if lib.verbatim { - lib.name.as_str().to_owned() - } else { - let ext = sess.target.dll_suffix.as_ref(); - let prefix = sess.target.dll_prefix.as_ref(); - format!("{prefix}{}{ext}", lib.name) - }; - - let imports = dylib_table.entry(filename.clone()).or_default(); - for import in &lib.dll_imports { - imports.insert(import.name, import); - } - } - } - sess.dcx().abort_if_errors(); - dylib_table - .into_iter() - .map(|(name, imports)| { - (name, imports.into_iter().map(|(_, import)| import.clone()).collect()) - }) - .collect() -} - pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>( sess: &Session, used_libraries: impl IntoIterator, raw_dylib_so_dir: &Path, ) -> Vec { - collate_raw_dylibs_elf(sess, used_libraries) + collate_raw_dylibs(sess, used_libraries) .into_iter() .map(|(load_filename, raw_dylib_imports)| { use std::hash::Hash; From 92fa367e08396c0fb5ffbaee42ce110c4fe99d8f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 9 Sep 2025 03:42:59 +0200 Subject: [PATCH 2/3] Add raw_dylib_macho feature gate --- compiler/rustc_attr_parsing/messages.ftl | 6 ++ .../src/attributes/link_attrs.rs | 67 ++++++++++++++----- .../src/session_diagnostics.rs | 7 ++ compiler/rustc_feature/src/unstable.rs | 4 +- compiler/rustc_span/src/symbol.rs | 1 + .../feature-gate-raw-dylib-macho.rs | 10 +++ .../feature-gate-raw-dylib-macho.stderr | 12 ++++ .../ui/linkage-attr/raw-dylib/macho/empty.rs | 10 +++ .../windows/raw-dylib-windows-only.rs | 6 +- 9 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-raw-dylib-macho.rs create mode 100644 tests/ui/feature-gates/feature-gate-raw-dylib-macho.stderr create mode 100644 tests/ui/linkage-attr/raw-dylib/macho/empty.rs diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 7fa1293463cc5..e3265a0ddaa9b 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -242,6 +242,12 @@ attr_parsing_raw_dylib_no_nul = attr_parsing_raw_dylib_elf_unstable = link kind `raw-dylib` is unstable on ELF platforms +attr_parsing_raw_dylib_macho_unstable = + link kind `raw-dylib` is unstable on Mach-O platforms + +attr_parsing_raw_dylib_macho_use_verbatim = + link kind `raw-dylib` should use the `+verbatim` linkage modifier + attr_parsing_raw_dylib_only_windows = link kind `raw-dylib` is only supported on Windows targets diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index d4942e56f429d..14dc2486af4ed 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -13,8 +13,8 @@ use crate::fluent_generated; use crate::session_diagnostics::{ AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, LinkFrameworkApple, LinkOrdinalOutOfRange, - LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibNoNul, RawDylibOnlyWindows, - WholeArchiveNeedsStatic, + LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibMachoUseVerbatim, + RawDylibNoNul, RawDylibOnlyWindows, WholeArchiveNeedsStatic, }; pub(crate) struct LinkNameParser; @@ -218,6 +218,17 @@ impl CombineAttributeParser for LinkParser { cx.emit_err(RawDylibNoNul { span: name_span }); } + if sess.target.binary_format == BinaryFormat::MachO + && kind == Some(NativeLibKind::RawDylib) + && verbatim != Some(true) + { + // It is possible for us to emit a non-absolute path like `libSystem.dylib` in the + // binary and have that work as well, though it's unclear what the use-case of that + // would be, and it might lead to people accidentally specifying too "lax" linking. + // So let's disallow it for now. + cx.emit_err(RawDylibMachoUseVerbatim { span: cx.attr_span }); + } + result = Some(LinkEntry { span: cx.attr_span, kind: kind.unwrap_or(NativeLibKind::Unspecified), @@ -286,22 +297,42 @@ impl LinkParser { NativeLibKind::Framework { as_needed: None } } sym::raw_dash_dylib => { - if sess.target.is_like_windows { - // raw-dylib is stable and working on Windows - } else if sess.target.binary_format == BinaryFormat::Elf && features.raw_dylib_elf() - { - // raw-dylib is unstable on ELF, but the user opted in - } else if sess.target.binary_format == BinaryFormat::Elf && sess.is_nightly_build() - { - feature_err( - sess, - sym::raw_dylib_elf, - nv.value_span, - fluent_generated::attr_parsing_raw_dylib_elf_unstable, - ) - .emit(); - } else { - cx.emit_err(RawDylibOnlyWindows { span: nv.value_span }); + match sess.target.binary_format { + _ if sess.target.is_like_windows => { + // raw-dylib is stable and working on Windows + } + + BinaryFormat::Elf if features.raw_dylib_elf() => { + // raw-dylib is unstable on ELF, but the user opted in + } + BinaryFormat::Elf if sess.is_nightly_build() => { + // Incomplete, so don't recommend if not nightly. + feature_err( + sess, + sym::raw_dylib_elf, + nv.value_span, + fluent_generated::attr_parsing_raw_dylib_elf_unstable, + ) + .emit(); + } + + BinaryFormat::MachO if features.raw_dylib_macho() => { + // raw-dylib is unstable on Mach-O, but the user opted in + } + BinaryFormat::MachO if sess.is_nightly_build() => { + // Incomplete, so don't recommend if not nightly. + feature_err( + sess, + sym::raw_dylib_macho, + nv.value_span, + fluent_generated::attr_parsing_raw_dylib_macho_unstable, + ) + .emit(); + } + + _ => { + cx.emit_err(RawDylibOnlyWindows { span: nv.value_span }); + } } NativeLibKind::RawDylib diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index a9dee23bf6a3b..69abee46bb46b 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -881,6 +881,13 @@ pub(crate) struct RawDylibOnlyWindows { pub span: Span, } +#[derive(Diagnostic)] +#[diag(attr_parsing_raw_dylib_macho_use_verbatim)] +pub(crate) struct RawDylibMachoUseVerbatim { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(attr_parsing_invalid_link_modifier)] pub(crate) struct InvalidLinkModifier { diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 4f35bf63a1a43..d3faed14affda 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -611,8 +611,10 @@ declare_features! ( (unstable, postfix_match, "1.79.0", Some(121618)), /// Allows macro attributes on expressions, statements and non-inline modules. (unstable, proc_macro_hygiene, "1.30.0", Some(54727)), - /// Allows the use of raw-dylibs on ELF platforms + /// Allows the use of raw-dylibs on ELF platforms. (incomplete, raw_dylib_elf, "1.87.0", Some(135694)), + /// Allows the use of raw-dylibs on Mach-O platforms. + (incomplete, raw_dylib_macho, "CURRENT_RUSTC_VERSION", Some(146356)), (unstable, reborrow, "CURRENT_RUSTC_VERSION", Some(145612)), /// Makes `&` and `&mut` patterns eat only one layer of references in Rust 2024. (incomplete, ref_pat_eat_one_layer_2024, "1.79.0", Some(123076)), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e5108d8b7e921..53bc4419f5b94 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1751,6 +1751,7 @@ symbols! { raw_dash_dylib: "raw-dylib", raw_dylib, raw_dylib_elf, + raw_dylib_macho, raw_eq, raw_identifiers, raw_ref_op, diff --git a/tests/ui/feature-gates/feature-gate-raw-dylib-macho.rs b/tests/ui/feature-gates/feature-gate-raw-dylib-macho.rs new file mode 100644 index 0000000000000..976e05c7ebef9 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-raw-dylib-macho.rs @@ -0,0 +1,10 @@ +//@ only-apple +//@ needs-dynamic-linking + +#[link(name = "uwu", kind = "raw-dylib", modifiers = "+verbatim")] +//~^ ERROR: link kind `raw-dylib` is unstable on Mach-O platforms +unsafe extern "C" { + safe fn kawaii(); +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-raw-dylib-macho.stderr b/tests/ui/feature-gates/feature-gate-raw-dylib-macho.stderr new file mode 100644 index 0000000000000..a01013be60d71 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-raw-dylib-macho.stderr @@ -0,0 +1,12 @@ +error[E0658]: link kind `raw-dylib` is unstable on Mach-O platforms + --> $DIR/feature-gate-raw-dylib-macho.rs:4:29 + | +LL | #[link(name = "uwu", kind = "raw-dylib", modifiers = "+verbatim")] + | ^^^^^^^^^^^ + | + = help: add `#![feature(raw_dylib_macho)]` 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/linkage-attr/raw-dylib/macho/empty.rs b/tests/ui/linkage-attr/raw-dylib/macho/empty.rs new file mode 100644 index 0000000000000..1771fbc746118 --- /dev/null +++ b/tests/ui/linkage-attr/raw-dylib/macho/empty.rs @@ -0,0 +1,10 @@ +//@ only-apple +//@ build-pass + +#![allow(incomplete_features)] +#![feature(raw_dylib_macho)] + +#[link(name = "hack", kind = "raw-dylib", modifiers = "+verbatim")] +unsafe extern "C" {} + +fn main() {} diff --git a/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.rs b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.rs index 935c59b5aaa5e..812f016d44ef9 100644 --- a/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.rs +++ b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.rs @@ -1,9 +1,13 @@ -//@ revisions: elf notelf +//@ revisions: elf notelf macho //@ [elf] only-elf +//@ [macho] only-apple //@ [notelf] ignore-windows //@ [notelf] ignore-elf +//@ [notelf] ignore-apple //@ compile-flags: --crate-type lib #[link(name = "foo", kind = "raw-dylib")] //[notelf]~^ ERROR: link kind `raw-dylib` is only supported on Windows targets //[elf]~^^ ERROR: link kind `raw-dylib` is unstable on ELF platforms +//[macho]~^^^ ERROR: link kind `raw-dylib` is unstable on Mach-O platforms +//[macho]~^^^^ ERROR: link kind `raw-dylib` should use the `+verbatim` linkage modifier extern "C" {} From 13a1d492c6b88481b330db8502be623b8eba1872 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 9 Sep 2025 03:44:17 +0200 Subject: [PATCH 3/3] Hackily implement raw_dylib_macho using LLVM's TextAPI --- compiler/rustc_codegen_ssa/src/back/link.rs | 17 ++- .../src/back/link/raw_dylib.rs | 126 ++++++++++++++++++ .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 53 ++++++++ .../src/language-features/raw-dylib-macho.md | 74 ++++++++++ .../raw-dylib/macho/Empty.sdk/.gitkeep | 0 tests/ui/linkage-attr/raw-dylib/macho/cf.rs | 21 +++ .../ui/linkage-attr/raw-dylib/macho/system.rs | 49 +++++++ .../raw-dylib-windows-only.macho.stderr | 18 +++ 8 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 src/doc/unstable-book/src/language-features/raw-dylib-macho.md create mode 100644 tests/ui/linkage-attr/raw-dylib/macho/Empty.sdk/.gitkeep create mode 100644 tests/ui/linkage-attr/raw-dylib/macho/cf.rs create mode 100644 tests/ui/linkage-attr/raw-dylib/macho/system.rs create mode 100644 tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.macho.stderr diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 48b01ea2df197..5c7c08b491519 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -386,7 +386,8 @@ fn link_rlib<'a>( // On Windows, we add the raw-dylib import libraries to the rlibs already. // But on ELF, this is not possible, as a shared object cannot be a member of a static library. - // Instead, we add all raw-dylibs to the final link on ELF. + // Similarly on Mach-O, `.tbd` files cannot be members of static libraries. + // Instead, we add all raw-dylibs to the final link on ELF/Mach-O. if sess.target.is_like_windows { for output_path in raw_dylib::create_raw_dylib_dll_import_libs( sess, @@ -2357,6 +2358,14 @@ fn linker_with_args( ) { cmd.add_object(&output_path); } + } else if sess.target.is_like_darwin { + for link_path in raw_dylib::create_raw_dylib_macho_tapi( + sess, + codegen_results.crate_info.used_libraries.iter(), + tmpdir, + ) { + cmd.link_dylib_by_path(&link_path, true); + } } else { for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects( sess, @@ -2404,6 +2413,12 @@ fn linker_with_args( ) { cmd.add_object(&output_path); } + } else if sess.target.is_like_darwin { + for link_path in + raw_dylib::create_raw_dylib_macho_tapi(sess, native_libraries_from_nonstatics, tmpdir) + { + cmd.link_dylib_by_path(&link_path, true); + } } else { for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects( sess, diff --git a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs index c4a68920ca4f3..03db832f02318 100644 --- a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs +++ b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs @@ -1,5 +1,7 @@ +use std::ffi::CString; use std::fs; use std::io::{BufWriter, Write}; +use std::mem::ManuallyDrop; use std::path::{Path, PathBuf}; use rustc_abi::Endian; @@ -11,6 +13,7 @@ use rustc_hir::attrs::NativeLibKind; use rustc_session::Session; use rustc_session::cstore::DllImport; use rustc_span::Symbol; +use rustc_target::spec::apple::OSVersion; use crate::back::archive::ImportLibraryItem; use crate::back::link::ArchiveBuilderBuilder; @@ -126,6 +129,129 @@ pub(super) fn create_raw_dylib_dll_import_libs<'a>( .collect() } +/// Mach-O linkers support the TAPI/TBD (TextAPI / Text-based Stubs) format as an alternative to +/// a full dynamic library. +/// +/// TODO. +pub(super) fn create_raw_dylib_macho_tapi<'a>( + sess: &Session, + used_libraries: impl IntoIterator, + tmpdir: &Path, +) -> impl Iterator { + collate_raw_dylibs(sess, used_libraries).into_iter().map(|(install_name, raw_dylib_imports)| { + // TODO: Do this properly + let path = tmpdir.join(Path::new(&install_name).file_stem().unwrap()).with_extension("tbd"); + + let exports: Vec<_> = raw_dylib_imports + .iter() + .map(|import| { + let name = if let Some(name) = import.name.as_str().strip_prefix("\x01") { + name.to_string() + } else { + format!("_{}", import.name.as_str()) + }; + LLVMRustMachOTbdExport { + // TODO + name: ManuallyDrop::new(CString::new(name).unwrap()).as_ptr(), + flags: if import.is_fn { SymbolFlags::Text } else { SymbolFlags::Data }, + kind: EncodeKind::GlobalSymbol, + } + }) + .collect(); + + unsafe { + macho_tbd_write( + &path, + &sess.target.llvm_target, + &install_name, + OSVersion::new(1, 0, 0), + OSVersion::new(1, 0, 0), + &exports, + ) + .unwrap() + }; + + path + }) +} + +bitflags::bitflags! { + #[repr(transparent)] + #[derive(Copy, Clone)] + pub(crate) struct SymbolFlags: u8 { + const None = 0; + const ThreadLocalValue = 1 << 0; + const WeakDefined = 1 << 1; + const WeakReferenced = 1 << 2; + const Undefined = 1 << 3; + const Rexported = 1 << 4; + const Data = 1 << 5; + const Text = 1 << 6; + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[repr(u8)] +#[allow(unused)] +pub(crate) enum EncodeKind { + GlobalSymbol = 0, + ObjectiveCClass = 1, + ObjectiveCClassEHType = 2, + ObjectiveCInstanceVariable = 3, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub(crate) struct LLVMRustMachOTbdExport { + pub(crate) name: *const std::ffi::c_char, + pub(crate) flags: SymbolFlags, + pub(crate) kind: EncodeKind, +} + +unsafe extern "C" { + pub(crate) fn LLVMRustMachoTbdWrite( + path: *const std::ffi::c_char, + llvm_target_name: *const std::ffi::c_char, + install_name: *const std::ffi::c_char, + current_version: u32, + compatibility_version: u32, + exports: *const LLVMRustMachOTbdExport, + num_exports: libc::size_t, + ) -> u8; +} + +unsafe fn macho_tbd_write( + path: &Path, + llvm_target_name: &str, + install_name: &str, + current_version: OSVersion, + compatibility_version: OSVersion, + exports: &[LLVMRustMachOTbdExport], +) -> Result<(), ()> { + let path = CString::new(path.to_str().unwrap()).unwrap(); + let llvm_target_name = CString::new(llvm_target_name).unwrap(); + let install_name = CString::new(install_name).unwrap(); + + fn pack_version(OSVersion { major, minor, patch }: OSVersion) -> u32 { + let (major, minor, patch) = (major as u32, minor as u32, patch as u32); + (major << 16) | (minor << 8) | patch + } + + let result = unsafe { + LLVMRustMachoTbdWrite( + path.as_ptr(), + llvm_target_name.as_ptr(), + install_name.as_ptr(), + pack_version(current_version), + pack_version(compatibility_version), + exports.as_ptr(), + exports.len(), + ) + }; + + if result == 0 { Ok(()) } else { Err(()) } +} + pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>( sess: &Session, used_libraries: impl IntoIterator, diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 361a5f765510f..8143deaee5e6c 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -35,6 +35,10 @@ #include "llvm/Support/Signals.h" #include "llvm/Support/Timer.h" #include "llvm/Support/ToolOutputFile.h" +#include "llvm/TextAPI/Architecture.h" +#include "llvm/TextAPI/InterfaceFile.h" +#include "llvm/TextAPI/Platform.h" +#include "llvm/TextAPI/TextAPIWriter.h" #include // for raw `write` in the bad-alloc handler @@ -1945,3 +1949,52 @@ extern "C" void LLVMRustSetNoSanitizeHWAddress(LLVMValueRef Global) { MD.NoHWAddress = true; GV.setSanitizerMetadata(MD); } + +struct LLVMRustMachOTbdExport { + char *name; + MachO::SymbolFlags flags; + MachO::EncodeKind kind; +}; + +extern "C" uint8_t LLVMRustMachoTbdWrite( + const char *Path, + const char *LLVMTargetName, + const char *InstallName, + uint32_t CurrentVersion, + uint32_t CompatibilityVersion, + const LLVMRustMachOTbdExport *Exports, + size_t NumExports) { + MachO::InterfaceFile File; + File.setFileType(MachO::FileType::TBD_V1); // TODO + + File.setInstallName(StringRef(InstallName)); + File.setCurrentVersion(MachO::PackedVersion(CurrentVersion)); + File.setCompatibilityVersion(MachO::PackedVersion(CompatibilityVersion)); + // File.setTwoLevelNamespace(); + + // auto Arch = MachO::getArchitectureFromName(Arch); + // auto Platform = MachO::getPlatformFromName(PlatPlatform); + MachO::Target Target{Triple(LLVMTargetName)}; + File.addTarget(Target); + + for (size_t i = 0; i < NumExports; i++) { + File.addSymbol(Exports[i].kind, StringRef(Exports[i].name), Target, Exports[i].flags); + } + + std::string ErrorInfo; + std::error_code EC; + raw_fd_ostream OS(Path, EC, sys::fs::CD_CreateAlways); + if (EC) + ErrorInfo = EC.message(); + if (ErrorInfo != "") { + LLVMRustSetLastError(ErrorInfo.c_str()); + return -1; + } + + if (Error Err = MachO::TextAPIWriter::writeToStream(OS, File)) { + LLVMRustSetLastError(toString(std::move(Err)).c_str()); + return -1; + } + + return 0; +} diff --git a/src/doc/unstable-book/src/language-features/raw-dylib-macho.md b/src/doc/unstable-book/src/language-features/raw-dylib-macho.md new file mode 100644 index 0000000000000..730a51709eb36 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/raw-dylib-macho.md @@ -0,0 +1,74 @@ +# `raw_dylib_macho` + +The tracking issue for this feature is: [#146356] + +[#146356]: https://github.com/rust-lang/rust/issues/146356 + +------------------------ + +The `raw_dylib_macho` feature enables support for Mach-O/Darwin/Apple platforms +when using the `raw-dylib` linkage kind. + +The `+verbatim` modifier currently must be set (though this restriction may be +lifted in the future). + +```rust +//! Link to CoreFoundation without having to have the linker stubs available. +#![feature(raw_dylib_macho)] +#![cfg(target_vendor = "apple")] + +#[link( + name = "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", + kind = "raw-dylib", + modifiers = "+verbatim", +)] +unsafe extern "C" { + // Example function. + safe fn CFRunLoopGetTypeID() -> core::ffi::c_ulong; +} + +fn main() { + let _ = CFRunLoopGetTypeID(); +} +``` + +```rust +//! Weakly link to a symbol in libSystem.dylib that has been introduced +//! later than macOS 10.12 (which might mean that host tooling will have +//! trouble linking if it doesn't have the newer linker stubs available). +#![feature(raw_dylib_macho)] +#![feature(linkage)] +#![cfg(target_vendor = "apple")] + +use std::ffi::{c_int, c_void}; + +#[link(name = "/usr/lib/libSystem.B.dylib", kind = "raw-dylib", modifiers = "+verbatim")] +unsafe extern "C" { + #[linkage = "extern_weak"] + safe static os_sync_wait_on_address: Option c_int>; +} + +fn main() { + if let Some(_wait_on_address) = os_sync_wait_on_address { + // Use new symbol + } else { + // Fallback implementation + } +} +``` + +```rust,no_run +//! Link to a library relative to where the current binary will be installed, +//! without having that library available. +#![feature(raw_dylib_macho)] +#![cfg(target_vendor = "apple")] + +#[link(name = "@executable_path/libfoo.dylib", kind = "raw-dylib", modifiers = "+verbatim")] +unsafe extern "C" { + safe fn foo(); +} + +fn main() { + foo(); +} +``` diff --git a/tests/ui/linkage-attr/raw-dylib/macho/Empty.sdk/.gitkeep b/tests/ui/linkage-attr/raw-dylib/macho/Empty.sdk/.gitkeep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/ui/linkage-attr/raw-dylib/macho/cf.rs b/tests/ui/linkage-attr/raw-dylib/macho/cf.rs new file mode 100644 index 0000000000000..15a5bbb5c6791 --- /dev/null +++ b/tests/ui/linkage-attr/raw-dylib/macho/cf.rs @@ -0,0 +1,21 @@ +//! Link to CoreFoundation without having to have the linker stubs available. + +//@ only-apple +//@ run-pass + +#![allow(incomplete_features)] +#![feature(raw_dylib_macho)] + +#[link( + name = "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", + kind = "raw-dylib", + modifiers = "+verbatim" +)] +unsafe extern "C" { + // Example function. + safe fn CFRunLoopGetTypeID() -> core::ffi::c_ulong; +} + +fn main() { + let _ = CFRunLoopGetTypeID(); +} diff --git a/tests/ui/linkage-attr/raw-dylib/macho/system.rs b/tests/ui/linkage-attr/raw-dylib/macho/system.rs new file mode 100644 index 0000000000000..972f7fd52f412 --- /dev/null +++ b/tests/ui/linkage-attr/raw-dylib/macho/system.rs @@ -0,0 +1,49 @@ +//! Check that we can link and run a `no_std` binary without. + +//@ only-apple +//@ run-pass +//@ compile-flags: -Cpanic=abort +//@ compile-flags: -Clinker=ld -Clink-arg=-syslibroot -Clink-arg=./Empty.sdk + +#![allow(incomplete_features)] +#![feature(raw_dylib_macho)] +#![feature(lang_items)] +#![no_std] +#![no_main] + +use core::ffi::{c_char, c_int}; +use core::panic::PanicInfo; + +#[link( + name = "/usr/lib/libSystem.B.dylib", + kind = "raw-dylib", + modifiers = "+verbatim", + // current_version: 1351, + // compatibility_version: 1, +)] +#[allow(unused)] +unsafe extern "C" { + #[link_name = "\x01dyld_stub_binder"] + unsafe fn dyld_stub_binder(); +} + +#[panic_handler] +fn panic_handler(_info: &PanicInfo<'_>) -> ! { + loop {} +} + +#[lang = "eh_personality"] +extern "C" fn rust_eh_personality( + _version: i32, + _actions: i32, + _exception_class: u64, + _exception_object: *mut (), + _context: *mut (), +) -> i32 { + loop {} +} + +#[no_mangle] +extern "C" fn main(_argc: c_int, _argv: *const *const c_char) -> c_int { + 0 +} diff --git a/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.macho.stderr b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.macho.stderr new file mode 100644 index 0000000000000..eb4ccbbb6b9fa --- /dev/null +++ b/tests/ui/linkage-attr/raw-dylib/windows/raw-dylib-windows-only.macho.stderr @@ -0,0 +1,18 @@ +error[E0658]: link kind `raw-dylib` is unstable on Mach-O platforms + --> $DIR/raw-dylib-windows-only.rs:8:29 + | +LL | #[link(name = "foo", kind = "raw-dylib")] + | ^^^^^^^^^^^ + | + = help: add `#![feature(raw_dylib_macho)]` 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: link kind `raw-dylib` should use the `+verbatim` linkage modifier + --> $DIR/raw-dylib-windows-only.rs:8:1 + | +LL | #[link(name = "foo", kind = "raw-dylib")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`.