mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 21:39:07 +00:00
415 lines
12 KiB
Rust
415 lines
12 KiB
Rust
use memmap2::{Mmap, MmapMut};
|
|
use object::Object;
|
|
use roc_build::link::{rebuild_host, LinkType};
|
|
use roc_error_macros::internal_error;
|
|
use roc_mono::ir::OptLevel;
|
|
use std::cmp::Ordering;
|
|
use std::mem;
|
|
use std::path::Path;
|
|
use target_lexicon::Triple;
|
|
|
|
mod elf;
|
|
mod macho;
|
|
mod pe;
|
|
|
|
mod generate_dylib;
|
|
mod metadata;
|
|
|
|
pub fn supported(link_type: LinkType, target: &Triple) -> bool {
|
|
if let LinkType::Executable = link_type {
|
|
match target {
|
|
Triple {
|
|
architecture: target_lexicon::Architecture::X86_64,
|
|
operating_system: target_lexicon::OperatingSystem::Linux,
|
|
binary_format: target_lexicon::BinaryFormat::Elf,
|
|
..
|
|
} => true,
|
|
|
|
// macho support is incomplete
|
|
Triple {
|
|
operating_system: target_lexicon::OperatingSystem::Darwin,
|
|
binary_format: target_lexicon::BinaryFormat::Macho,
|
|
..
|
|
} => false,
|
|
|
|
Triple {
|
|
architecture: target_lexicon::Architecture::X86_64,
|
|
operating_system: target_lexicon::OperatingSystem::Windows,
|
|
binary_format: target_lexicon::BinaryFormat::Coff,
|
|
..
|
|
} => true,
|
|
|
|
_ => false,
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn build_and_preprocess_host(
|
|
opt_level: OptLevel,
|
|
target: &Triple,
|
|
host_input_path: &Path,
|
|
preprocessed_host_path: &Path,
|
|
exposed_to_host: Vec<String>,
|
|
exported_closure_types: Vec<String>,
|
|
) {
|
|
let dummy_lib = if let target_lexicon::OperatingSystem::Windows = target.operating_system {
|
|
host_input_path.with_file_name("libapp.obj")
|
|
} else {
|
|
host_input_path.with_file_name("libapp.so")
|
|
};
|
|
|
|
let dynhost = if let target_lexicon::OperatingSystem::Windows = target.operating_system {
|
|
host_input_path.with_file_name("dynhost.exe")
|
|
} else {
|
|
host_input_path.with_file_name("dynhost")
|
|
};
|
|
|
|
generate_dynamic_lib(target, exposed_to_host, exported_closure_types, &dummy_lib);
|
|
rebuild_host(opt_level, target, host_input_path, Some(&dummy_lib));
|
|
let metadata = host_input_path.with_file_name("metadata");
|
|
// let prehost = host_input_path.with_file_name("preprocessedhost");
|
|
|
|
preprocess(
|
|
target,
|
|
&dynhost,
|
|
&metadata,
|
|
preprocessed_host_path,
|
|
&dummy_lib,
|
|
false,
|
|
false,
|
|
)
|
|
}
|
|
|
|
pub fn link_preprocessed_host(
|
|
target: &Triple,
|
|
host_input_path: &Path,
|
|
roc_app_bytes: &[u8],
|
|
binary_path: &Path,
|
|
) {
|
|
let metadata = host_input_path.with_file_name("metadata");
|
|
surgery(roc_app_bytes, &metadata, binary_path, false, false, target)
|
|
}
|
|
|
|
fn generate_dynamic_lib(
|
|
target: &Triple,
|
|
exposed_to_host: Vec<String>,
|
|
exported_closure_types: Vec<String>,
|
|
dummy_lib_path: &Path,
|
|
) {
|
|
let mut custom_names = Vec::new();
|
|
|
|
for sym in exposed_to_host {
|
|
custom_names.extend([
|
|
format!("roc__{}_1_exposed", sym),
|
|
format!("roc__{}_1_exposed_generic", sym),
|
|
format!("roc__{}_size", sym),
|
|
]);
|
|
|
|
for closure_type in &exported_closure_types {
|
|
custom_names.extend([
|
|
format!("roc__{}_1_{}_caller", sym, closure_type),
|
|
format!("roc__{}_1_{}_size", sym, closure_type),
|
|
format!("roc__{}_1_{}_result_size", sym, closure_type),
|
|
]);
|
|
}
|
|
}
|
|
|
|
// on windows (PE) binary search is used on the symbols,
|
|
// so they must be in alphabetical order
|
|
custom_names.sort_unstable();
|
|
|
|
if !dummy_lib_is_up_to_date(target, dummy_lib_path, &custom_names) {
|
|
let bytes = crate::generate_dylib::generate(target, &custom_names)
|
|
.unwrap_or_else(|e| internal_error!("{e}"));
|
|
|
|
std::fs::write(dummy_lib_path, &bytes).unwrap_or_else(|e| internal_error!("{e}"))
|
|
}
|
|
}
|
|
|
|
fn object_matches_target<'a>(target: &Triple, object: &object::File<'a, &'a [u8]>) -> bool {
|
|
use target_lexicon::{Architecture as TLA, OperatingSystem as TLO};
|
|
|
|
match target.architecture {
|
|
TLA::X86_64 => {
|
|
if object.architecture() != object::Architecture::X86_64 {
|
|
return false;
|
|
}
|
|
|
|
let target_format = match target.operating_system {
|
|
TLO::Linux => object::BinaryFormat::Elf,
|
|
TLO::Windows => object::BinaryFormat::Pe,
|
|
_ => todo!("surgical linker does not support target {:?}", target),
|
|
};
|
|
|
|
object.format() == target_format
|
|
}
|
|
TLA::Aarch64(_) => object.architecture() == object::Architecture::Aarch64,
|
|
_ => todo!("surgical linker does not support target {:?}", target),
|
|
}
|
|
}
|
|
|
|
/// Checks whether the dummy `.dll/.so` is up to date, in other words that it exports exactly the
|
|
/// symbols that it is supposed to export, and is built for the right target. If this is the case,
|
|
/// we can skip rebuildingthe dummy lib.
|
|
fn dummy_lib_is_up_to_date(
|
|
target: &Triple,
|
|
dummy_lib_path: &Path,
|
|
custom_names: &[String],
|
|
) -> bool {
|
|
if !std::path::Path::exists(dummy_lib_path) {
|
|
return false;
|
|
}
|
|
|
|
let dummy_lib = open_mmap(dummy_lib_path);
|
|
let object = object::File::parse(&*dummy_lib).unwrap();
|
|
|
|
// the user may have been cross-compiling.
|
|
// The dynhost on disk must match our current target
|
|
if !object_matches_target(target, &object) {
|
|
return false;
|
|
}
|
|
|
|
// we made this dynhost file. For the file to be the same as what we'd generate,
|
|
// we need all symbols to be there and in the correct order
|
|
let dynamic_symbols: Vec<_> = object.exports().unwrap();
|
|
|
|
let it1 = dynamic_symbols.iter().map(|e| e.name());
|
|
let it2 = custom_names.iter().map(|s| s.as_bytes());
|
|
|
|
it1.eq(it2)
|
|
}
|
|
|
|
/// Constructs a `metadata::Metadata` from a host executable binary, and writes it to disk
|
|
fn preprocess(
|
|
target: &Triple,
|
|
host_exe_path: &Path,
|
|
metadata_path: &Path,
|
|
preprocessed_path: &Path,
|
|
shared_lib: &Path,
|
|
verbose: bool,
|
|
time: bool,
|
|
) {
|
|
if verbose {
|
|
println!("Targeting: {}", target);
|
|
}
|
|
|
|
let endianness = target
|
|
.endianness()
|
|
.unwrap_or(target_lexicon::Endianness::Little);
|
|
|
|
match target.binary_format {
|
|
target_lexicon::BinaryFormat::Elf => {
|
|
crate::elf::preprocess_elf(
|
|
endianness,
|
|
host_exe_path,
|
|
metadata_path,
|
|
preprocessed_path,
|
|
shared_lib,
|
|
verbose,
|
|
time,
|
|
);
|
|
}
|
|
|
|
target_lexicon::BinaryFormat::Macho => {
|
|
crate::macho::preprocess_macho(
|
|
target,
|
|
host_exe_path,
|
|
metadata_path,
|
|
preprocessed_path,
|
|
shared_lib,
|
|
verbose,
|
|
time,
|
|
);
|
|
}
|
|
|
|
target_lexicon::BinaryFormat::Coff => {
|
|
crate::pe::preprocess_windows(
|
|
host_exe_path,
|
|
metadata_path,
|
|
preprocessed_path,
|
|
verbose,
|
|
time,
|
|
)
|
|
.unwrap_or_else(|e| internal_error!("{}", e));
|
|
}
|
|
|
|
target_lexicon::BinaryFormat::Wasm => {
|
|
todo!("Roc does not yet support web assembly hosts!");
|
|
}
|
|
target_lexicon::BinaryFormat::Unknown => {
|
|
internal_error!("Roc does not support unknown host binary formats!");
|
|
}
|
|
other => {
|
|
internal_error!(
|
|
concat!(
|
|
r"Roc does not yet support the {:?} binary format. ",
|
|
r"Please file a bug report for this, describing what operating system you were targeting!"
|
|
),
|
|
other,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn surgery(
|
|
roc_app_bytes: &[u8],
|
|
metadata_path: &Path,
|
|
executable_path: &Path,
|
|
verbose: bool,
|
|
time: bool,
|
|
target: &Triple,
|
|
) {
|
|
match target.binary_format {
|
|
target_lexicon::BinaryFormat::Elf => {
|
|
crate::elf::surgery_elf(roc_app_bytes, metadata_path, executable_path, verbose, time);
|
|
}
|
|
|
|
target_lexicon::BinaryFormat::Macho => {
|
|
crate::macho::surgery_macho(
|
|
roc_app_bytes,
|
|
metadata_path,
|
|
executable_path,
|
|
verbose,
|
|
time,
|
|
);
|
|
}
|
|
|
|
target_lexicon::BinaryFormat::Coff => {
|
|
crate::pe::surgery_pe(executable_path, metadata_path, roc_app_bytes);
|
|
}
|
|
|
|
target_lexicon::BinaryFormat::Wasm => {
|
|
todo!("Roc does not yet support web assembly hosts!");
|
|
}
|
|
target_lexicon::BinaryFormat::Unknown => {
|
|
internal_error!("Roc does not support unknown host binary formats!");
|
|
}
|
|
other => {
|
|
internal_error!(
|
|
concat!(
|
|
r"Roc does not yet support the {:?} binary format. ",
|
|
r"Please file a bug report for this, describing what operating system you were targeting!"
|
|
),
|
|
other,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn align_by_constraint(offset: usize, constraint: usize) -> usize {
|
|
if offset % constraint == 0 {
|
|
offset
|
|
} else {
|
|
offset + constraint - (offset % constraint)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn align_to_offset_by_constraint(
|
|
current_offset: usize,
|
|
target_offset: usize,
|
|
constraint: usize,
|
|
) -> usize {
|
|
let target_remainder = target_offset % constraint;
|
|
let current_remainder = current_offset % constraint;
|
|
match target_remainder.cmp(¤t_remainder) {
|
|
Ordering::Greater => current_offset + (target_remainder - current_remainder),
|
|
Ordering::Less => current_offset + ((target_remainder + constraint) - current_remainder),
|
|
Ordering::Equal => current_offset,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn load_struct_inplace<T>(bytes: &[u8], offset: usize) -> &T {
|
|
&load_structs_inplace(bytes, offset, 1)[0]
|
|
}
|
|
|
|
pub(crate) fn load_struct_inplace_mut<T>(bytes: &mut [u8], offset: usize) -> &mut T {
|
|
&mut load_structs_inplace_mut(bytes, offset, 1)[0]
|
|
}
|
|
|
|
pub(crate) fn load_structs_inplace<T>(bytes: &[u8], offset: usize, count: usize) -> &[T] {
|
|
let (head, body, tail) =
|
|
unsafe { bytes[offset..offset + count * mem::size_of::<T>()].align_to::<T>() };
|
|
assert!(head.is_empty(), "Data was not aligned");
|
|
assert_eq!(count, body.len(), "Failed to load all structs");
|
|
assert!(tail.is_empty(), "End of data was not aligned");
|
|
body
|
|
}
|
|
|
|
pub(crate) fn load_structs_inplace_mut<T>(
|
|
bytes: &mut [u8],
|
|
offset: usize,
|
|
count: usize,
|
|
) -> &mut [T] {
|
|
let (head, body, tail) =
|
|
unsafe { bytes[offset..offset + count * mem::size_of::<T>()].align_to_mut::<T>() };
|
|
assert!(head.is_empty(), "Data was not aligned");
|
|
assert_eq!(count, body.len(), "Failed to load all structs");
|
|
assert!(tail.is_empty(), "End of data was not aligned");
|
|
body
|
|
}
|
|
|
|
pub(crate) fn open_mmap(path: &Path) -> Mmap {
|
|
let in_file = std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.open(path)
|
|
.unwrap_or_else(|e| internal_error!("{e}"));
|
|
|
|
unsafe { Mmap::map(&in_file).unwrap_or_else(|e| internal_error!("{e}")) }
|
|
}
|
|
|
|
pub(crate) fn open_mmap_mut(path: &Path, length: usize) -> MmapMut {
|
|
let out_file = std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.open(path)
|
|
.unwrap_or_else(|e| internal_error!("{e}"));
|
|
out_file
|
|
.set_len(length as u64)
|
|
.unwrap_or_else(|e| internal_error!("{e}"));
|
|
|
|
unsafe { MmapMut::map_mut(&out_file).unwrap_or_else(|e| internal_error!("{e}")) }
|
|
}
|
|
|
|
/// # dbg_hex
|
|
/// display dbg result in hexadecimal `{:#x?}` format.
|
|
///
|
|
/// # usage
|
|
/// Replace `dbg!()` with `dbg_hex!()`
|
|
///
|
|
/// # example
|
|
/// ```rust, no_run
|
|
/// use dbg_hex::dbg_hex;
|
|
/// dbg_hex!(0x16 + 0x16);
|
|
/// ```
|
|
///
|
|
/// output
|
|
/// ```text
|
|
/// [src/lib.rs:38] 0x16 + 0x16 = 0x2c
|
|
/// ```
|
|
#[macro_export]
|
|
macro_rules! dbg_hex {
|
|
// NOTE: We cannot use `concat!` to make a static string as a format argument
|
|
// of `eprintln!` because `file!` could contain a `{` or
|
|
// `$val` expression could be a block (`{ .. }`), in which case the `eprintln!`
|
|
// will be malformed.
|
|
() => {
|
|
eprintln!("[{}:{}]", file!(), line!());
|
|
};
|
|
($val:expr $(,)?) => {
|
|
// Use of `match` here is intentional because it affects the lifetimes
|
|
// of temporaries - https://stackoverflow.com/a/48732525/1063961
|
|
match $val {
|
|
tmp => {
|
|
eprintln!("[{}:{}] {} = {:#x?}",
|
|
file!(), line!(), stringify!($val), &tmp);
|
|
tmp
|
|
}
|
|
}
|
|
};
|
|
($($val:expr),+ $(,)?) => {
|
|
($($crate::dbg_hex!($val)),+,)
|
|
};
|
|
}
|