Skip to content

Commit 13a1d49

Browse files
committed
Hackily implement raw_dylib_macho using LLVM's TextAPI
1 parent 92fa367 commit 13a1d49

File tree

8 files changed

+357
-1
lines changed

8 files changed

+357
-1
lines changed

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,8 @@ fn link_rlib<'a>(
386386

387387
// On Windows, we add the raw-dylib import libraries to the rlibs already.
388388
// But on ELF, this is not possible, as a shared object cannot be a member of a static library.
389-
// Instead, we add all raw-dylibs to the final link on ELF.
389+
// Similarly on Mach-O, `.tbd` files cannot be members of static libraries.
390+
// Instead, we add all raw-dylibs to the final link on ELF/Mach-O.
390391
if sess.target.is_like_windows {
391392
for output_path in raw_dylib::create_raw_dylib_dll_import_libs(
392393
sess,
@@ -2357,6 +2358,14 @@ fn linker_with_args(
23572358
) {
23582359
cmd.add_object(&output_path);
23592360
}
2361+
} else if sess.target.is_like_darwin {
2362+
for link_path in raw_dylib::create_raw_dylib_macho_tapi(
2363+
sess,
2364+
codegen_results.crate_info.used_libraries.iter(),
2365+
tmpdir,
2366+
) {
2367+
cmd.link_dylib_by_path(&link_path, true);
2368+
}
23602369
} else {
23612370
for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects(
23622371
sess,
@@ -2404,6 +2413,12 @@ fn linker_with_args(
24042413
) {
24052414
cmd.add_object(&output_path);
24062415
}
2416+
} else if sess.target.is_like_darwin {
2417+
for link_path in
2418+
raw_dylib::create_raw_dylib_macho_tapi(sess, native_libraries_from_nonstatics, tmpdir)
2419+
{
2420+
cmd.link_dylib_by_path(&link_path, true);
2421+
}
24072422
} else {
24082423
for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects(
24092424
sess,

compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use std::ffi::CString;
12
use std::fs;
23
use std::io::{BufWriter, Write};
4+
use std::mem::ManuallyDrop;
35
use std::path::{Path, PathBuf};
46

57
use rustc_abi::Endian;
@@ -11,6 +13,7 @@ use rustc_hir::attrs::NativeLibKind;
1113
use rustc_session::Session;
1214
use rustc_session::cstore::DllImport;
1315
use rustc_span::Symbol;
16+
use rustc_target::spec::apple::OSVersion;
1417

1518
use crate::back::archive::ImportLibraryItem;
1619
use crate::back::link::ArchiveBuilderBuilder;
@@ -126,6 +129,129 @@ pub(super) fn create_raw_dylib_dll_import_libs<'a>(
126129
.collect()
127130
}
128131

132+
/// Mach-O linkers support the TAPI/TBD (TextAPI / Text-based Stubs) format as an alternative to
133+
/// a full dynamic library.
134+
///
135+
/// TODO.
136+
pub(super) fn create_raw_dylib_macho_tapi<'a>(
137+
sess: &Session,
138+
used_libraries: impl IntoIterator<Item = &'a NativeLib>,
139+
tmpdir: &Path,
140+
) -> impl Iterator<Item = PathBuf> {
141+
collate_raw_dylibs(sess, used_libraries).into_iter().map(|(install_name, raw_dylib_imports)| {
142+
// TODO: Do this properly
143+
let path = tmpdir.join(Path::new(&install_name).file_stem().unwrap()).with_extension("tbd");
144+
145+
let exports: Vec<_> = raw_dylib_imports
146+
.iter()
147+
.map(|import| {
148+
let name = if let Some(name) = import.name.as_str().strip_prefix("\x01") {
149+
name.to_string()
150+
} else {
151+
format!("_{}", import.name.as_str())
152+
};
153+
LLVMRustMachOTbdExport {
154+
// TODO
155+
name: ManuallyDrop::new(CString::new(name).unwrap()).as_ptr(),
156+
flags: if import.is_fn { SymbolFlags::Text } else { SymbolFlags::Data },
157+
kind: EncodeKind::GlobalSymbol,
158+
}
159+
})
160+
.collect();
161+
162+
unsafe {
163+
macho_tbd_write(
164+
&path,
165+
&sess.target.llvm_target,
166+
&install_name,
167+
OSVersion::new(1, 0, 0),
168+
OSVersion::new(1, 0, 0),
169+
&exports,
170+
)
171+
.unwrap()
172+
};
173+
174+
path
175+
})
176+
}
177+
178+
bitflags::bitflags! {
179+
#[repr(transparent)]
180+
#[derive(Copy, Clone)]
181+
pub(crate) struct SymbolFlags: u8 {
182+
const None = 0;
183+
const ThreadLocalValue = 1 << 0;
184+
const WeakDefined = 1 << 1;
185+
const WeakReferenced = 1 << 2;
186+
const Undefined = 1 << 3;
187+
const Rexported = 1 << 4;
188+
const Data = 1 << 5;
189+
const Text = 1 << 6;
190+
}
191+
}
192+
193+
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
194+
#[repr(u8)]
195+
#[allow(unused)]
196+
pub(crate) enum EncodeKind {
197+
GlobalSymbol = 0,
198+
ObjectiveCClass = 1,
199+
ObjectiveCClassEHType = 2,
200+
ObjectiveCInstanceVariable = 3,
201+
}
202+
203+
#[derive(Copy, Clone)]
204+
#[repr(C)]
205+
pub(crate) struct LLVMRustMachOTbdExport {
206+
pub(crate) name: *const std::ffi::c_char,
207+
pub(crate) flags: SymbolFlags,
208+
pub(crate) kind: EncodeKind,
209+
}
210+
211+
unsafe extern "C" {
212+
pub(crate) fn LLVMRustMachoTbdWrite(
213+
path: *const std::ffi::c_char,
214+
llvm_target_name: *const std::ffi::c_char,
215+
install_name: *const std::ffi::c_char,
216+
current_version: u32,
217+
compatibility_version: u32,
218+
exports: *const LLVMRustMachOTbdExport,
219+
num_exports: libc::size_t,
220+
) -> u8;
221+
}
222+
223+
unsafe fn macho_tbd_write(
224+
path: &Path,
225+
llvm_target_name: &str,
226+
install_name: &str,
227+
current_version: OSVersion,
228+
compatibility_version: OSVersion,
229+
exports: &[LLVMRustMachOTbdExport],
230+
) -> Result<(), ()> {
231+
let path = CString::new(path.to_str().unwrap()).unwrap();
232+
let llvm_target_name = CString::new(llvm_target_name).unwrap();
233+
let install_name = CString::new(install_name).unwrap();
234+
235+
fn pack_version(OSVersion { major, minor, patch }: OSVersion) -> u32 {
236+
let (major, minor, patch) = (major as u32, minor as u32, patch as u32);
237+
(major << 16) | (minor << 8) | patch
238+
}
239+
240+
let result = unsafe {
241+
LLVMRustMachoTbdWrite(
242+
path.as_ptr(),
243+
llvm_target_name.as_ptr(),
244+
install_name.as_ptr(),
245+
pack_version(current_version),
246+
pack_version(compatibility_version),
247+
exports.as_ptr(),
248+
exports.len(),
249+
)
250+
};
251+
252+
if result == 0 { Ok(()) } else { Err(()) }
253+
}
254+
129255
pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>(
130256
sess: &Session,
131257
used_libraries: impl IntoIterator<Item = &'a NativeLib>,

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
#include "llvm/Support/Signals.h"
3636
#include "llvm/Support/Timer.h"
3737
#include "llvm/Support/ToolOutputFile.h"
38+
#include "llvm/TextAPI/Architecture.h"
39+
#include "llvm/TextAPI/InterfaceFile.h"
40+
#include "llvm/TextAPI/Platform.h"
41+
#include "llvm/TextAPI/TextAPIWriter.h"
3842
#include <iostream>
3943

4044
// for raw `write` in the bad-alloc handler
@@ -1945,3 +1949,52 @@ extern "C" void LLVMRustSetNoSanitizeHWAddress(LLVMValueRef Global) {
19451949
MD.NoHWAddress = true;
19461950
GV.setSanitizerMetadata(MD);
19471951
}
1952+
1953+
struct LLVMRustMachOTbdExport {
1954+
char *name;
1955+
MachO::SymbolFlags flags;
1956+
MachO::EncodeKind kind;
1957+
};
1958+
1959+
extern "C" uint8_t LLVMRustMachoTbdWrite(
1960+
const char *Path,
1961+
const char *LLVMTargetName,
1962+
const char *InstallName,
1963+
uint32_t CurrentVersion,
1964+
uint32_t CompatibilityVersion,
1965+
const LLVMRustMachOTbdExport *Exports,
1966+
size_t NumExports) {
1967+
MachO::InterfaceFile File;
1968+
File.setFileType(MachO::FileType::TBD_V1); // TODO
1969+
1970+
File.setInstallName(StringRef(InstallName));
1971+
File.setCurrentVersion(MachO::PackedVersion(CurrentVersion));
1972+
File.setCompatibilityVersion(MachO::PackedVersion(CompatibilityVersion));
1973+
// File.setTwoLevelNamespace();
1974+
1975+
// auto Arch = MachO::getArchitectureFromName(Arch);
1976+
// auto Platform = MachO::getPlatformFromName(PlatPlatform);
1977+
MachO::Target Target{Triple(LLVMTargetName)};
1978+
File.addTarget(Target);
1979+
1980+
for (size_t i = 0; i < NumExports; i++) {
1981+
File.addSymbol(Exports[i].kind, StringRef(Exports[i].name), Target, Exports[i].flags);
1982+
}
1983+
1984+
std::string ErrorInfo;
1985+
std::error_code EC;
1986+
raw_fd_ostream OS(Path, EC, sys::fs::CD_CreateAlways);
1987+
if (EC)
1988+
ErrorInfo = EC.message();
1989+
if (ErrorInfo != "") {
1990+
LLVMRustSetLastError(ErrorInfo.c_str());
1991+
return -1;
1992+
}
1993+
1994+
if (Error Err = MachO::TextAPIWriter::writeToStream(OS, File)) {
1995+
LLVMRustSetLastError(toString(std::move(Err)).c_str());
1996+
return -1;
1997+
}
1998+
1999+
return 0;
2000+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# `raw_dylib_macho`
2+
3+
The tracking issue for this feature is: [#146356]
4+
5+
[#146356]: https://github.com/rust-lang/rust/issues/146356
6+
7+
------------------------
8+
9+
The `raw_dylib_macho` feature enables support for Mach-O/Darwin/Apple platforms
10+
when using the `raw-dylib` linkage kind.
11+
12+
The `+verbatim` modifier currently must be set (though this restriction may be
13+
lifted in the future).
14+
15+
```rust
16+
//! Link to CoreFoundation without having to have the linker stubs available.
17+
#![feature(raw_dylib_macho)]
18+
#![cfg(target_vendor = "apple")]
19+
20+
#[link(
21+
name = "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
22+
kind = "raw-dylib",
23+
modifiers = "+verbatim",
24+
)]
25+
unsafe extern "C" {
26+
// Example function.
27+
safe fn CFRunLoopGetTypeID() -> core::ffi::c_ulong;
28+
}
29+
30+
fn main() {
31+
let _ = CFRunLoopGetTypeID();
32+
}
33+
```
34+
35+
```rust
36+
//! Weakly link to a symbol in libSystem.dylib that has been introduced
37+
//! later than macOS 10.12 (which might mean that host tooling will have
38+
//! trouble linking if it doesn't have the newer linker stubs available).
39+
#![feature(raw_dylib_macho)]
40+
#![feature(linkage)]
41+
#![cfg(target_vendor = "apple")]
42+
43+
use std::ffi::{c_int, c_void};
44+
45+
#[link(name = "/usr/lib/libSystem.B.dylib", kind = "raw-dylib", modifiers = "+verbatim")]
46+
unsafe extern "C" {
47+
#[linkage = "extern_weak"]
48+
safe static os_sync_wait_on_address: Option<unsafe extern "C" fn(*mut c_void, u64, usize, u32) -> c_int>;
49+
}
50+
51+
fn main() {
52+
if let Some(_wait_on_address) = os_sync_wait_on_address {
53+
// Use new symbol
54+
} else {
55+
// Fallback implementation
56+
}
57+
}
58+
```
59+
60+
```rust,no_run
61+
//! Link to a library relative to where the current binary will be installed,
62+
//! without having that library available.
63+
#![feature(raw_dylib_macho)]
64+
#![cfg(target_vendor = "apple")]
65+
66+
#[link(name = "@executable_path/libfoo.dylib", kind = "raw-dylib", modifiers = "+verbatim")]
67+
unsafe extern "C" {
68+
safe fn foo();
69+
}
70+
71+
fn main() {
72+
foo();
73+
}
74+
```

tests/ui/linkage-attr/raw-dylib/macho/Empty.sdk/.gitkeep

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//! Link to CoreFoundation without having to have the linker stubs available.
2+
3+
//@ only-apple
4+
//@ run-pass
5+
6+
#![allow(incomplete_features)]
7+
#![feature(raw_dylib_macho)]
8+
9+
#[link(
10+
name = "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
11+
kind = "raw-dylib",
12+
modifiers = "+verbatim"
13+
)]
14+
unsafe extern "C" {
15+
// Example function.
16+
safe fn CFRunLoopGetTypeID() -> core::ffi::c_ulong;
17+
}
18+
19+
fn main() {
20+
let _ = CFRunLoopGetTypeID();
21+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//! Check that we can link and run a `no_std` binary without.
2+
3+
//@ only-apple
4+
//@ run-pass
5+
//@ compile-flags: -Cpanic=abort
6+
//@ compile-flags: -Clinker=ld -Clink-arg=-syslibroot -Clink-arg=./Empty.sdk
7+
8+
#![allow(incomplete_features)]
9+
#![feature(raw_dylib_macho)]
10+
#![feature(lang_items)]
11+
#![no_std]
12+
#![no_main]
13+
14+
use core::ffi::{c_char, c_int};
15+
use core::panic::PanicInfo;
16+
17+
#[link(
18+
name = "/usr/lib/libSystem.B.dylib",
19+
kind = "raw-dylib",
20+
modifiers = "+verbatim",
21+
// current_version: 1351,
22+
// compatibility_version: 1,
23+
)]
24+
#[allow(unused)]
25+
unsafe extern "C" {
26+
#[link_name = "\x01dyld_stub_binder"]
27+
unsafe fn dyld_stub_binder();
28+
}
29+
30+
#[panic_handler]
31+
fn panic_handler(_info: &PanicInfo<'_>) -> ! {
32+
loop {}
33+
}
34+
35+
#[lang = "eh_personality"]
36+
extern "C" fn rust_eh_personality(
37+
_version: i32,
38+
_actions: i32,
39+
_exception_class: u64,
40+
_exception_object: *mut (),
41+
_context: *mut (),
42+
) -> i32 {
43+
loop {}
44+
}
45+
46+
#[no_mangle]
47+
extern "C" fn main(_argc: c_int, _argv: *const *const c_char) -> c_int {
48+
0
49+
}

0 commit comments

Comments
 (0)