use std::path::{Path, PathBuf};
use rucc_tuple::{Abi, Arch, DataModel, Endian, Env, ObjectFormat, Os, TargetTuple};
use crate::layout::Sysroot;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LinkMode {
#[default]
Static,
StaticPie,
Dynamic,
DynamicNoPie,
Shared,
}
impl LinkMode {
#[must_use]
pub const fn is_dynamic(self) -> bool {
matches!(self, LinkMode::Dynamic | LinkMode::DynamicNoPie | LinkMode::Shared)
}
#[must_use]
pub const fn is_pie(self) -> bool {
matches!(self, LinkMode::StaticPie | LinkMode::Dynamic | LinkMode::Shared)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Libc {
None,
Archive,
Stub,
Import,
}
#[must_use]
pub fn libc(target: TargetTuple) -> Libc {
match (target.os(), target.env()) {
(Os::None, _) => Libc::None,
_ if target.object_format() == ObjectFormat::Coff => Libc::Import,
(_, Env::Musl) => Libc::Archive,
_ => Libc::Stub,
}
}
pub const BUILTINS: &str = "librucc_builtins.a";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LinkLine {
pub start: Vec<PathBuf>,
pub libraries: Vec<PathBuf>,
pub end: Vec<PathBuf>,
}
impl LinkLine {
#[must_use]
pub fn for_target(sysroot: &Sysroot, mode: LinkMode) -> Self {
match libc(sysroot.target()) {
Libc::None => LinkLine::freestanding(sysroot),
Libc::Archive => LinkLine::musl(sysroot, mode),
Libc::Stub => LinkLine::glibc(sysroot, mode),
Libc::Import => LinkLine::mingw(sysroot, mode),
}
}
#[must_use]
pub fn freestanding(sysroot: &Sysroot) -> Self {
LinkLine {
start: Vec::new(),
libraries: vec![sysroot.lib().join(BUILTINS)],
end: Vec::new(),
}
}
#[must_use]
pub fn musl(sysroot: &Sysroot, mode: LinkMode) -> Self {
let lib = sysroot.lib();
LinkLine {
start: start_files(&lib, mode),
libraries: vec![lib.join("libc.a"), lib.join(BUILTINS)],
end: vec![lib.join("crtn.o")],
}
}
#[must_use]
pub fn glibc(sysroot: &Sysroot, mode: LinkMode) -> Self {
let lib = sysroot.lib();
LinkLine {
start: start_files(&lib, mode),
libraries: vec![lib.join("libc.so"), lib.join(BUILTINS)],
end: vec![lib.join("crtn.o")],
}
}
#[must_use]
pub fn mingw(sysroot: &Sysroot, mode: LinkMode) -> Self {
let lib = sysroot.lib();
let start = match mode {
LinkMode::Shared => "dllcrt2.o",
_ => "crt2.o",
};
let libraries = [
"libmingw32.a",
"libmoldname.a",
"libmingwex.a",
"libmsvcrt.a",
"libadvapi32.a",
"libshell32.a",
"libuser32.a",
"libkernel32.a",
BUILTINS,
];
LinkLine {
start: vec![lib.join(start)],
libraries: libraries.iter().map(|name| lib.join(name)).collect(),
end: Vec::new(),
}
}
#[must_use]
pub fn with_objects(&self, objects: &[PathBuf]) -> Vec<PathBuf> {
let mut all = self.start.clone();
all.extend_from_slice(objects);
all.extend(self.libraries.iter().cloned());
all.extend(self.end.iter().cloned());
all
}
}
fn start_files(lib: &Path, mode: LinkMode) -> Vec<PathBuf> {
let first = match mode {
LinkMode::Static | LinkMode::DynamicNoPie => Some("crt1.o"),
LinkMode::StaticPie => Some("rcrt1.o"),
LinkMode::Dynamic => Some("Scrt1.o"),
LinkMode::Shared => None,
};
first.map(|name| lib.join(name)).into_iter().chain([lib.join("crti.o")]).collect()
}
#[must_use]
pub fn loader(target: TargetTuple) -> Option<&'static str> {
match (target.os(), target.env()) {
(Os::Linux, Env::Musl) => Some(musl_loader(target)),
(Os::Linux, Env::Gnu) => Some(glibc_loader(target)),
(Os::Linux, Env::Android) => Some(match target.pointer_width() {
64 => "/system/bin/linker64",
_ => "/system/bin/linker",
}),
_ => None,
}
}
#[must_use]
pub fn musl_loader(target: TargetTuple) -> &'static str {
match target.arch() {
Arch::X86_64 => match target.data_model() {
DataModel::Ilp32On64 => "/lib/ld-musl-x32.so.1",
_ => "/lib/ld-musl-x86_64.so.1",
},
Arch::X86 => "/lib/ld-musl-i386.so.1",
Arch::Aarch64 | Arch::Arm64Ec => "/lib/ld-musl-aarch64.so.1",
Arch::Arm => match target.resolved_abi() {
Abi::DoubleFloat => "/lib/ld-musl-armhf.so.1",
_ => "/lib/ld-musl-arm.so.1",
},
Arch::Riscv64 => "/lib/ld-musl-riscv64.so.1",
Arch::Riscv32 => "/lib/ld-musl-riscv32.so.1",
Arch::S390x => "/lib/ld-musl-s390x.so.1",
Arch::PowerPc64 => match target.endian() {
Endian::Little => "/lib/ld-musl-powerpc64le.so.1",
Endian::Big => "/lib/ld-musl-powerpc64.so.1",
},
Arch::LoongArch64 => "/lib/ld-musl-loongarch64.so.1",
Arch::Wasm32 => "/lib/ld-musl-none.so.1",
}
}
#[must_use]
pub fn glibc_loader(target: TargetTuple) -> &'static str {
let narrow = target.data_model() == DataModel::Ilp32On64;
let hard = matches!(target.resolved_abi(), Abi::DoubleFloat);
match target.arch() {
Arch::X86_64 if narrow => "/libx32/ld-linux-x32.so.2",
Arch::X86_64 => "/lib64/ld-linux-x86-64.so.2",
Arch::X86 => "/lib/ld-linux.so.2",
Arch::Aarch64 | Arch::Arm64Ec => match (target.endian(), narrow) {
(Endian::Little, false) => "/lib/ld-linux-aarch64.so.1",
(Endian::Little, true) => "/lib/ld-linux-aarch64_ilp32.so.1",
(Endian::Big, false) => "/lib/ld-linux-aarch64_be.so.1",
(Endian::Big, true) => "/lib/ld-linux-aarch64_be_ilp32.so.1",
},
Arch::Arm if hard => "/lib/ld-linux-armhf.so.3",
Arch::Arm => "/lib/ld-linux.so.3",
Arch::Riscv64 if hard => "/lib/ld-linux-riscv64-lp64d.so.1",
Arch::Riscv64 => "/lib/ld-linux-riscv64-lp64.so.1",
Arch::Riscv32 if hard => "/lib/ld-linux-riscv32-ilp32d.so.1",
Arch::Riscv32 => "/lib/ld-linux-riscv32-ilp32.so.1",
Arch::S390x => "/lib/ld64.so.1",
Arch::PowerPc64 => "/lib64/ld64.so.2",
Arch::LoongArch64 if hard => "/lib64/ld-linux-loongarch-lp64d.so.1",
Arch::LoongArch64 => "/lib64/ld-linux-loongarch-lp64s.so.1",
Arch::Wasm32 => "/lib/ld-linux-wasm32.so.1",
}
}