Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions compiler/rustc_attr_parsing/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
67 changes: 49 additions & 18 deletions compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -218,6 +218,17 @@ impl<S: Stage> CombineAttributeParser<S> 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),
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 16 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
167 changes: 131 additions & 36 deletions compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,6 +13,7 @@
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;
Expand All @@ -23,7 +26,7 @@
/// 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<Item = &'a NativeLib>,
) -> Vec<(String, Vec<DllImport>)> {
Expand All @@ -32,14 +35,21 @@

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,
Expand All @@ -66,7 +76,7 @@
tmpdir: &Path,
is_direct_dependency: bool,
) -> Vec<PathBuf> {
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" };
Expand Down Expand Up @@ -119,50 +129,135 @@
.collect()
}

/// Extract all symbols defined in raw-dylib libraries, collated by library name.
/// Mach-O linkers support the TAPI/TBD (TextAPI / Text-based Stubs) format as an alternative to
/// a full dynamic library.
///
/// 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>(
/// TODO.

Check failure on line 135 in compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs

View workflow job for this annotation

GitHub Actions / PR - tidy

TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME

Check failure on line 135 in compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs

View workflow job for this annotation

GitHub Actions / PR - aarch64-gnu-llvm-19-2

TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME
pub(super) fn create_raw_dylib_macho_tapi<'a>(
sess: &Session,
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
) -> Vec<(String, Vec<DllImport>)> {
// Use index maps to preserve original order of imports and libraries.
let mut dylib_table = FxIndexMap::<String, FxIndexMap<Symbol, &DllImport>>::default();
tmpdir: &Path,
) -> impl Iterator<Item = PathBuf> {
collate_raw_dylibs(sess, used_libraries).into_iter().map(|(install_name, raw_dylib_imports)| {
// TODO: Do this properly

Check failure on line 142 in compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs

View workflow job for this annotation

GitHub Actions / PR - tidy

TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME

Check failure on line 142 in compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs

View workflow job for this annotation

GitHub Actions / PR - aarch64-gnu-llvm-19-2

TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME
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

Check failure on line 154 in compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs

View workflow job for this annotation

GitHub Actions / PR - tidy

TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME

Check failure on line 154 in compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs

View workflow job for this annotation

GitHub Actions / PR - aarch64-gnu-llvm-19-2

TODO is used for tasks that should be done before merging a PR; If you want to leave a message in the codebase use FIXME
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
})
}

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)
};
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;
}
}

let imports = dylib_table.entry(filename.clone()).or_default();
for import in &lib.dll_imports {
imports.insert(import.name, import);
}
}
#[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
}
sess.dcx().abort_if_errors();
dylib_table
.into_iter()
.map(|(name, imports)| {
(name, imports.into_iter().map(|(_, import)| import.clone()).collect())
})
.collect()

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<Item = &'a NativeLib>,
raw_dylib_so_dir: &Path,
) -> Vec<String> {
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;
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
Loading
Loading