mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-02 19:32:17 +00:00

- rework strGraphemes() to use a mutable slice and keep track of just `last_codepoint_len`. - add zig tests for empty string, ascii, utf8, ascii+utf8+emoji
1392 lines
44 KiB
Rust
1392 lines
44 KiB
Rust
use crate::target::{arch_str, target_zig_str};
|
|
use libloading::{Error, Library};
|
|
use roc_builtins::bitcode;
|
|
use roc_error_macros::internal_error;
|
|
use roc_mono::ir::OptLevel;
|
|
use roc_utils::get_lib_path;
|
|
use std::collections::HashMap;
|
|
use std::env;
|
|
use std::io;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{self, Child, Command, Output};
|
|
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
|
use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH};
|
|
|
|
fn zig_executable() -> String {
|
|
match std::env::var("ROC_ZIG") {
|
|
Ok(path) => path,
|
|
Err(_) => "zig".into(),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub enum LinkType {
|
|
// These numbers correspond to the --lib and --no-link flags
|
|
Executable = 0,
|
|
Dylib = 1,
|
|
None = 2,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub enum LinkingStrategy {
|
|
/// Compile app and host object files, then use a linker like lld or wasm-ld
|
|
Legacy,
|
|
/// Compile app and host object files, then use the Roc surgical linker
|
|
Surgical,
|
|
/// Initialise the backend from a host object file, then add the app to it. No linker needed.
|
|
Additive,
|
|
}
|
|
|
|
/// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"]
|
|
pub fn link(
|
|
target: &Triple,
|
|
output_path: PathBuf,
|
|
input_paths: &[&str],
|
|
link_type: LinkType,
|
|
) -> io::Result<(Child, PathBuf)> {
|
|
match target {
|
|
Triple {
|
|
architecture: Architecture::Wasm32,
|
|
..
|
|
} => link_wasm32(target, output_path, input_paths, link_type),
|
|
Triple {
|
|
operating_system: OperatingSystem::Linux,
|
|
..
|
|
} => link_linux(target, output_path, input_paths, link_type),
|
|
Triple {
|
|
operating_system: OperatingSystem::Darwin,
|
|
..
|
|
} => link_macos(target, output_path, input_paths, link_type),
|
|
Triple {
|
|
operating_system: OperatingSystem::Windows,
|
|
..
|
|
} => link_windows(target, output_path, input_paths, link_type),
|
|
_ => internal_error!("TODO gracefully handle unsupported target: {:?}", target),
|
|
}
|
|
}
|
|
|
|
fn find_zig_str_path() -> PathBuf {
|
|
// First try using the lib path relative to the executable location.
|
|
let lib_path_opt = get_lib_path();
|
|
|
|
if let Some(lib_path) = lib_path_opt {
|
|
let zig_str_path = lib_path.join("str.zig");
|
|
|
|
if std::path::Path::exists(&zig_str_path) {
|
|
return zig_str_path;
|
|
}
|
|
}
|
|
|
|
let zig_str_path = PathBuf::from("crates/compiler/builtins/bitcode/src/str.zig");
|
|
|
|
if std::path::Path::exists(&zig_str_path) {
|
|
return zig_str_path;
|
|
}
|
|
|
|
// when running the tests, we start in the /cli directory
|
|
let zig_str_path = PathBuf::from("../compiler/builtins/bitcode/src/str.zig");
|
|
if std::path::Path::exists(&zig_str_path) {
|
|
return zig_str_path;
|
|
}
|
|
|
|
internal_error!("cannot find `str.zig`. Check the source code in find_zig_str_path() to show all the paths I tried.")
|
|
}
|
|
|
|
fn find_wasi_libc_path() -> PathBuf {
|
|
// Environment variable defined in wasi-libc-sys/build.rs
|
|
let wasi_libc_pathbuf = PathBuf::from(WASI_LIBC_PATH);
|
|
if std::path::Path::exists(&wasi_libc_pathbuf) {
|
|
return wasi_libc_pathbuf;
|
|
}
|
|
|
|
internal_error!("cannot find `wasi-libc.a`")
|
|
}
|
|
|
|
#[cfg(all(unix, not(target_os = "macos")))]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn build_zig_host_native(
|
|
env_path: &str,
|
|
env_home: &str,
|
|
emit_bin: &str,
|
|
zig_host_src: &str,
|
|
zig_str_path: &str,
|
|
target: &str,
|
|
opt_level: OptLevel,
|
|
shared_lib_path: Option<&Path>,
|
|
) -> Output {
|
|
let mut command = Command::new(&zig_executable());
|
|
command
|
|
.env_clear()
|
|
.env("PATH", env_path)
|
|
.env("HOME", env_home);
|
|
|
|
if let Some(shared_lib_path) = shared_lib_path {
|
|
// with LLVM, the builtins are already part of the roc app,
|
|
// but with the dev backend, they are missing. To minimize work,
|
|
// we link them as part of the host executable
|
|
let builtins_obj = if target.contains("windows") {
|
|
bitcode::get_builtins_windows_obj_path()
|
|
} else {
|
|
bitcode::get_builtins_host_obj_path()
|
|
};
|
|
|
|
command.args(&[
|
|
"build-exe",
|
|
"-fPIE",
|
|
"-rdynamic", // make sure roc_alloc and friends are exposed
|
|
shared_lib_path.to_str().unwrap(),
|
|
&builtins_obj,
|
|
]);
|
|
} else {
|
|
command.args(&["build-obj", "-fPIC"]);
|
|
}
|
|
|
|
command.args(&[
|
|
zig_host_src,
|
|
emit_bin,
|
|
"--pkg-begin",
|
|
"str",
|
|
zig_str_path,
|
|
"--pkg-end",
|
|
// include libc
|
|
"-lc",
|
|
// cross-compile?
|
|
"-target",
|
|
target,
|
|
]);
|
|
|
|
// some examples need the compiler-rt in the app object file.
|
|
// but including it on windows causes weird crashes, at least
|
|
// when we use zig 0.9. It looks like zig 0.10 is going to fix
|
|
// this problem for us, so this is a temporary workaround
|
|
if !target.contains("windows") {
|
|
command.args(&[
|
|
// include the zig runtime
|
|
"-fcompiler-rt",
|
|
]);
|
|
}
|
|
|
|
// valgrind does not yet support avx512 instructions, see #1963.
|
|
if env::var("NO_AVX512").is_ok() {
|
|
command.args(&["-mcpu", "x86_64"]);
|
|
}
|
|
|
|
if matches!(opt_level, OptLevel::Optimize) {
|
|
command.args(&["-O", "ReleaseSafe"]);
|
|
} else if matches!(opt_level, OptLevel::Size) {
|
|
command.args(&["-O", "ReleaseSmall"]);
|
|
}
|
|
|
|
command.output().unwrap()
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn build_zig_host_native(
|
|
env_path: &str,
|
|
env_home: &str,
|
|
emit_bin: &str,
|
|
zig_host_src: &str,
|
|
zig_str_path: &str,
|
|
target: &str,
|
|
opt_level: OptLevel,
|
|
shared_lib_path: Option<&Path>,
|
|
) -> Output {
|
|
let mut command = Command::new(&zig_executable());
|
|
command
|
|
.env_clear()
|
|
.env("PATH", env_path)
|
|
.env("HOME", env_home);
|
|
|
|
if let Some(shared_lib_path) = shared_lib_path {
|
|
command.args(&[
|
|
"build-exe",
|
|
"-fPIE",
|
|
shared_lib_path.to_str().unwrap(),
|
|
&bitcode::get_builtins_host_obj_path(),
|
|
]);
|
|
} else {
|
|
command.args(&["build-obj", "-fPIC"]);
|
|
}
|
|
|
|
command.args(&[
|
|
zig_host_src,
|
|
emit_bin,
|
|
"--pkg-begin",
|
|
"str",
|
|
zig_str_path,
|
|
"--pkg-end",
|
|
// include the zig runtime
|
|
// "-fcompiler-rt", compiler-rt causes segfaults on windows; investigate why
|
|
// include libc
|
|
"--library",
|
|
"c",
|
|
// cross-compile?
|
|
"-target",
|
|
target,
|
|
]);
|
|
|
|
if matches!(opt_level, OptLevel::Optimize) {
|
|
command.args(&["-O", "ReleaseSafe"]);
|
|
} else if matches!(opt_level, OptLevel::Size) {
|
|
command.args(&["-O", "ReleaseSmall"]);
|
|
}
|
|
|
|
command.output().unwrap()
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn build_zig_host_native(
|
|
env_path: &str,
|
|
env_home: &str,
|
|
emit_bin: &str,
|
|
zig_host_src: &str,
|
|
zig_str_path: &str,
|
|
_target: &str,
|
|
opt_level: OptLevel,
|
|
shared_lib_path: Option<&Path>,
|
|
// For compatibility with the non-macOS def above. Keep these in sync.
|
|
) -> Output {
|
|
use serde_json::Value;
|
|
|
|
// Run `zig env` to find the location of zig's std/ directory
|
|
let zig_env_output = Command::new(&zig_executable())
|
|
.args(&["env"])
|
|
.output()
|
|
.unwrap();
|
|
|
|
let zig_env_json = if zig_env_output.status.success() {
|
|
std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| {
|
|
internal_error!(
|
|
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
|
|
utf8_err
|
|
);
|
|
})
|
|
} else {
|
|
match std::str::from_utf8(&zig_env_output.stderr) {
|
|
Ok(stderr) => internal_error!("`zig env` failed - stderr output was: {:?}", stderr),
|
|
Err(utf8_err) => internal_error!(
|
|
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
|
|
utf8_err
|
|
),
|
|
}
|
|
};
|
|
|
|
let mut zig_compiler_rt_path = match serde_json::from_str(zig_env_json) {
|
|
Ok(Value::Object(map)) => match map.get("std_dir") {
|
|
Some(Value::String(std_dir)) => PathBuf::from(Path::new(std_dir)),
|
|
_ => {
|
|
internal_error!("Expected JSON containing a `std_dir` String field from `zig env`, but got: {:?}", zig_env_json);
|
|
}
|
|
},
|
|
_ => {
|
|
internal_error!(
|
|
"Expected JSON containing a `std_dir` field from `zig env`, but got: {:?}",
|
|
zig_env_json
|
|
);
|
|
}
|
|
};
|
|
|
|
zig_compiler_rt_path.push("special");
|
|
zig_compiler_rt_path.push("compiler_rt.zig");
|
|
|
|
let mut command = Command::new(&zig_executable());
|
|
command
|
|
.env_clear()
|
|
.env("PATH", &env_path)
|
|
.env("HOME", &env_home);
|
|
if let Some(shared_lib_path) = shared_lib_path {
|
|
command.args(&[
|
|
"build-exe",
|
|
"-fPIE",
|
|
shared_lib_path.to_str().unwrap(),
|
|
&bitcode::get_builtins_host_obj_path(),
|
|
]);
|
|
} else {
|
|
command.args(&["build-obj", "-fPIC"]);
|
|
}
|
|
command.args(&[
|
|
zig_host_src,
|
|
emit_bin,
|
|
"--pkg-begin",
|
|
"str",
|
|
zig_str_path,
|
|
"--pkg-end",
|
|
// include the zig runtime
|
|
"--pkg-begin",
|
|
"compiler_rt",
|
|
zig_compiler_rt_path.to_str().unwrap(),
|
|
"--pkg-end",
|
|
// include libc
|
|
"--library",
|
|
"c",
|
|
]);
|
|
if matches!(opt_level, OptLevel::Optimize) {
|
|
command.args(&["-O", "ReleaseSafe"]);
|
|
} else if matches!(opt_level, OptLevel::Size) {
|
|
command.args(&["-O", "ReleaseSmall"]);
|
|
}
|
|
command.output().unwrap()
|
|
}
|
|
|
|
pub fn build_zig_host_wasm32(
|
|
env_path: &str,
|
|
env_home: &str,
|
|
emit_bin: &str,
|
|
zig_host_src: &str,
|
|
zig_str_path: &str,
|
|
opt_level: OptLevel,
|
|
shared_lib_path: Option<&Path>,
|
|
) -> Output {
|
|
if shared_lib_path.is_some() {
|
|
unimplemented!("Linking a shared library to wasm not yet implemented");
|
|
}
|
|
|
|
let zig_target = if matches!(opt_level, OptLevel::Development) {
|
|
"wasm32-wasi"
|
|
} else {
|
|
// For LLVM backend wasm we are emitting a .bc file anyway so this target is OK
|
|
"i386-linux-musl"
|
|
};
|
|
|
|
// NOTE currently just to get compiler warnings if the host code is invalid.
|
|
// the produced artifact is not used
|
|
//
|
|
// NOTE we're emitting LLVM IR here (again, it is not actually used)
|
|
//
|
|
// we'd like to compile with `-target wasm32-wasi` but that is blocked on
|
|
//
|
|
// https://github.com/ziglang/zig/issues/9414
|
|
let mut command = Command::new(&zig_executable());
|
|
let args = &[
|
|
"build-obj",
|
|
zig_host_src,
|
|
emit_bin,
|
|
"--pkg-begin",
|
|
"str",
|
|
zig_str_path,
|
|
"--pkg-end",
|
|
// include the zig runtime
|
|
// "-fcompiler-rt",
|
|
// include libc
|
|
"--library",
|
|
"c",
|
|
"-target",
|
|
zig_target,
|
|
// "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli_testing_examples/benchmarks/platform/host.ll",
|
|
"-fPIC",
|
|
"--strip",
|
|
];
|
|
|
|
command
|
|
.env_clear()
|
|
.env("PATH", env_path)
|
|
.env("HOME", env_home)
|
|
.args(args);
|
|
|
|
if matches!(opt_level, OptLevel::Optimize) {
|
|
command.args(&["-O", "ReleaseSafe"]);
|
|
} else if matches!(opt_level, OptLevel::Size) {
|
|
command.args(&["-O", "ReleaseSmall"]);
|
|
}
|
|
command.output().unwrap()
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn build_c_host_native(
|
|
target: &Triple,
|
|
env_path: &str,
|
|
env_home: &str,
|
|
env_cpath: &str,
|
|
dest: &str,
|
|
sources: &[&str],
|
|
opt_level: OptLevel,
|
|
shared_lib_path: Option<&Path>,
|
|
) -> Output {
|
|
let mut command = Command::new("clang");
|
|
command
|
|
.env_clear()
|
|
.env("PATH", &env_path)
|
|
.env("CPATH", &env_cpath)
|
|
.env("HOME", &env_home)
|
|
.args(sources)
|
|
.args(&["-o", dest]);
|
|
if let Some(shared_lib_path) = shared_lib_path {
|
|
match target.operating_system {
|
|
OperatingSystem::Windows => {
|
|
// just use zig as a C compiler
|
|
|
|
// I think we only ever have one C source file in practice
|
|
assert_eq!(sources.len(), 1);
|
|
|
|
return build_zig_host_native(
|
|
env_path,
|
|
env_home,
|
|
&format!("-femit-bin={}", dest),
|
|
sources[0],
|
|
find_zig_str_path().to_str().unwrap(),
|
|
"x86_64-windows-gnu",
|
|
opt_level,
|
|
Some(shared_lib_path),
|
|
);
|
|
}
|
|
_ => {
|
|
command.args(&[
|
|
shared_lib_path.to_str().unwrap(),
|
|
// This line is commented out because
|
|
// @bhansconnect: With the addition of Str.graphemes, always
|
|
// linking the built-ins led to a surgical linker bug for
|
|
// optimized builds. Disabling until it is needed for dev
|
|
// builds.
|
|
// &bitcode::get_builtins_host_obj_path(),
|
|
"-fPIE",
|
|
"-pie",
|
|
"-lm",
|
|
"-lpthread",
|
|
"-ldl",
|
|
"-lrt",
|
|
"-lutil",
|
|
]);
|
|
}
|
|
}
|
|
} else {
|
|
command.args(&["-fPIC", "-c"]);
|
|
}
|
|
if matches!(opt_level, OptLevel::Optimize) {
|
|
command.arg("-O3");
|
|
} else if matches!(opt_level, OptLevel::Size) {
|
|
command.arg("-Os");
|
|
}
|
|
command.output().unwrap()
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn build_swift_host_native(
|
|
env_path: &str,
|
|
env_home: &str,
|
|
dest: &str,
|
|
sources: &[&str],
|
|
opt_level: OptLevel,
|
|
shared_lib_path: Option<&Path>,
|
|
objc_header_path: Option<&str>,
|
|
arch: Architecture,
|
|
) -> Output {
|
|
if shared_lib_path.is_some() {
|
|
unimplemented!("Linking a shared library to Swift not yet implemented");
|
|
}
|
|
|
|
let mut command = Command::new("arch");
|
|
command
|
|
.env_clear()
|
|
.env("PATH", &env_path)
|
|
.env("HOME", &env_home);
|
|
|
|
match arch {
|
|
Architecture::Aarch64(_) => command.arg("-arm64"),
|
|
_ => command.arg(format!("-{}", arch)),
|
|
};
|
|
|
|
command
|
|
.arg("xcrun") // xcrun helps swiftc to find the right header files
|
|
.arg("swiftc")
|
|
.args(sources)
|
|
.arg("-emit-object")
|
|
.arg("-parse-as-library")
|
|
.args(&["-o", dest]);
|
|
|
|
if let Some(objc_header) = objc_header_path {
|
|
command.args(&["-import-objc-header", objc_header]);
|
|
}
|
|
|
|
if matches!(opt_level, OptLevel::Optimize) {
|
|
command.arg("-O");
|
|
} else if matches!(opt_level, OptLevel::Size) {
|
|
command.arg("-Osize");
|
|
}
|
|
|
|
command.output().unwrap()
|
|
}
|
|
|
|
pub fn rebuild_host(
|
|
opt_level: OptLevel,
|
|
target: &Triple,
|
|
host_input_path: &Path,
|
|
shared_lib_path: Option<&Path>,
|
|
) -> PathBuf {
|
|
let c_host_src = host_input_path.with_file_name("host.c");
|
|
let c_host_dest = host_input_path.with_file_name("c_host.o");
|
|
let zig_host_src = host_input_path.with_file_name("host.zig");
|
|
let rust_host_src = host_input_path.with_file_name("host.rs");
|
|
let rust_host_dest = host_input_path.with_file_name("rust_host.o");
|
|
let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
|
|
let swift_host_src = host_input_path.with_file_name("host.swift");
|
|
let swift_host_header_src = host_input_path.with_file_name("host.h");
|
|
|
|
let host_dest = if matches!(target.architecture, Architecture::Wasm32) {
|
|
if matches!(opt_level, OptLevel::Development) {
|
|
host_input_path.with_file_name("host.o")
|
|
} else {
|
|
host_input_path.with_file_name("host.bc")
|
|
}
|
|
} else {
|
|
let os = roc_target::OperatingSystem::from(target.operating_system);
|
|
|
|
if shared_lib_path.is_some() {
|
|
let extension = match os {
|
|
roc_target::OperatingSystem::Windows => "exe",
|
|
roc_target::OperatingSystem::Unix => "",
|
|
roc_target::OperatingSystem::Wasi => "",
|
|
};
|
|
|
|
host_input_path
|
|
.with_file_name("dynhost")
|
|
.with_extension(extension)
|
|
} else {
|
|
let extension = match os {
|
|
roc_target::OperatingSystem::Windows => "obj",
|
|
roc_target::OperatingSystem::Unix => "o",
|
|
roc_target::OperatingSystem::Wasi => "o",
|
|
};
|
|
|
|
host_input_path
|
|
.with_file_name("host")
|
|
.with_extension(extension)
|
|
}
|
|
};
|
|
|
|
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
|
let env_home = env::var("HOME").unwrap_or_else(|_| "".to_string());
|
|
let env_cpath = env::var("CPATH").unwrap_or_else(|_| "".to_string());
|
|
|
|
if zig_host_src.exists() {
|
|
// Compile host.zig
|
|
|
|
let zig_str_path = find_zig_str_path();
|
|
|
|
debug_assert!(
|
|
std::path::Path::exists(&zig_str_path),
|
|
"Cannot find str.zig, looking at {:?}",
|
|
&zig_str_path
|
|
);
|
|
|
|
let output = match target.architecture {
|
|
Architecture::Wasm32 => {
|
|
let emit_bin = if matches!(opt_level, OptLevel::Development) {
|
|
format!("-femit-bin={}", host_dest.to_str().unwrap())
|
|
} else {
|
|
format!("-femit-llvm-ir={}", host_dest.to_str().unwrap())
|
|
};
|
|
build_zig_host_wasm32(
|
|
&env_path,
|
|
&env_home,
|
|
&emit_bin,
|
|
zig_host_src.to_str().unwrap(),
|
|
zig_str_path.to_str().unwrap(),
|
|
opt_level,
|
|
shared_lib_path,
|
|
)
|
|
}
|
|
Architecture::X86_64 => {
|
|
let emit_bin = format!("-femit-bin={}", host_dest.to_str().unwrap());
|
|
|
|
let target = match target.operating_system {
|
|
OperatingSystem::Windows => "x86_64-windows-gnu",
|
|
_ => "native",
|
|
};
|
|
|
|
build_zig_host_native(
|
|
&env_path,
|
|
&env_home,
|
|
&emit_bin,
|
|
zig_host_src.to_str().unwrap(),
|
|
zig_str_path.to_str().unwrap(),
|
|
target,
|
|
opt_level,
|
|
shared_lib_path,
|
|
)
|
|
}
|
|
Architecture::X86_32(_) => {
|
|
let emit_bin = format!("-femit-bin={}", host_dest.to_str().unwrap());
|
|
build_zig_host_native(
|
|
&env_path,
|
|
&env_home,
|
|
&emit_bin,
|
|
zig_host_src.to_str().unwrap(),
|
|
zig_str_path.to_str().unwrap(),
|
|
"i386-linux-musl",
|
|
opt_level,
|
|
shared_lib_path,
|
|
)
|
|
}
|
|
|
|
Architecture::Aarch64(_) => {
|
|
let emit_bin = format!("-femit-bin={}", host_dest.to_str().unwrap());
|
|
build_zig_host_native(
|
|
&env_path,
|
|
&env_home,
|
|
&emit_bin,
|
|
zig_host_src.to_str().unwrap(),
|
|
zig_str_path.to_str().unwrap(),
|
|
target_zig_str(target),
|
|
opt_level,
|
|
shared_lib_path,
|
|
)
|
|
}
|
|
_ => internal_error!("Unsupported architecture {:?}", target.architecture),
|
|
};
|
|
|
|
validate_output("host.zig", &zig_executable(), output)
|
|
} else if cargo_host_src.exists() {
|
|
// Compile and link Cargo.toml, if it exists
|
|
let cargo_dir = host_input_path.parent().unwrap();
|
|
|
|
let cargo_out_dir = cargo_dir.join("target").join(
|
|
if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
|
|
"release"
|
|
} else {
|
|
"debug"
|
|
},
|
|
);
|
|
|
|
let mut command = Command::new("cargo");
|
|
command.arg("build").current_dir(cargo_dir);
|
|
// Rust doesn't expose size without editing the cargo.toml. Instead just use release.
|
|
if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
|
|
command.arg("--release");
|
|
}
|
|
let source_file = if shared_lib_path.is_some() {
|
|
command.env("RUSTFLAGS", "-C link-dead-code");
|
|
command.args(&["--bin", "host"]);
|
|
"src/main.rs"
|
|
} else {
|
|
command.arg("--lib");
|
|
"src/lib.rs"
|
|
};
|
|
let output = command.output().unwrap();
|
|
|
|
validate_output(source_file, "cargo build", output);
|
|
|
|
if shared_lib_path.is_some() {
|
|
// For surgical linking, just copy the dynamically linked rust app.
|
|
std::fs::copy(cargo_out_dir.join("host"), &host_dest).unwrap();
|
|
} else {
|
|
// Cargo hosts depend on a c wrapper for the api. Compile host.c as well.
|
|
|
|
let output = build_c_host_native(
|
|
target,
|
|
&env_path,
|
|
&env_home,
|
|
&env_cpath,
|
|
c_host_dest.to_str().unwrap(),
|
|
&[c_host_src.to_str().unwrap()],
|
|
opt_level,
|
|
shared_lib_path,
|
|
);
|
|
validate_output("host.c", "clang", output);
|
|
|
|
let output = Command::new("ld")
|
|
.env_clear()
|
|
.env("PATH", &env_path)
|
|
.args(&[
|
|
"-r",
|
|
"-L",
|
|
cargo_out_dir.to_str().unwrap(),
|
|
c_host_dest.to_str().unwrap(),
|
|
"-lhost",
|
|
"-o",
|
|
host_dest.to_str().unwrap(),
|
|
])
|
|
.output()
|
|
.unwrap();
|
|
validate_output("c_host.o", "ld", output);
|
|
|
|
// Clean up c_host.o
|
|
if c_host_dest.exists() {
|
|
std::fs::remove_file(c_host_dest).unwrap();
|
|
}
|
|
}
|
|
} else if rust_host_src.exists() {
|
|
// Compile and link host.rs, if it exists
|
|
let mut command = Command::new("rustc");
|
|
command.args(&[
|
|
rust_host_src.to_str().unwrap(),
|
|
"-o",
|
|
rust_host_dest.to_str().unwrap(),
|
|
]);
|
|
if matches!(opt_level, OptLevel::Optimize) {
|
|
command.arg("-O");
|
|
} else if matches!(opt_level, OptLevel::Size) {
|
|
command.arg("-C opt-level=s");
|
|
}
|
|
let output = command.output().unwrap();
|
|
|
|
validate_output("host.rs", "rustc", output);
|
|
|
|
// Rust hosts depend on a c wrapper for the api. Compile host.c as well.
|
|
if shared_lib_path.is_some() {
|
|
// If compiling to executable, let c deal with linking as well.
|
|
let output = build_c_host_native(
|
|
target,
|
|
&env_path,
|
|
&env_home,
|
|
&env_cpath,
|
|
host_dest.to_str().unwrap(),
|
|
&[
|
|
c_host_src.to_str().unwrap(),
|
|
rust_host_dest.to_str().unwrap(),
|
|
],
|
|
opt_level,
|
|
shared_lib_path,
|
|
);
|
|
validate_output("host.c", "clang", output);
|
|
} else {
|
|
let output = build_c_host_native(
|
|
target,
|
|
&env_path,
|
|
&env_home,
|
|
&env_cpath,
|
|
c_host_dest.to_str().unwrap(),
|
|
&[c_host_src.to_str().unwrap()],
|
|
opt_level,
|
|
shared_lib_path,
|
|
);
|
|
|
|
validate_output("host.c", "clang", output);
|
|
let output = Command::new("ld")
|
|
.env_clear()
|
|
.env("PATH", &env_path)
|
|
.args(&[
|
|
"-r",
|
|
c_host_dest.to_str().unwrap(),
|
|
rust_host_dest.to_str().unwrap(),
|
|
"-o",
|
|
host_dest.to_str().unwrap(),
|
|
])
|
|
.output()
|
|
.unwrap();
|
|
|
|
validate_output("rust_host.o", "ld", output);
|
|
}
|
|
|
|
// Clean up rust_host.o and c_host.o
|
|
if c_host_dest.exists() {
|
|
std::fs::remove_file(c_host_dest).unwrap();
|
|
}
|
|
if rust_host_dest.exists() {
|
|
std::fs::remove_file(rust_host_dest).unwrap();
|
|
}
|
|
} else if c_host_src.exists() {
|
|
// Compile host.c, if it exists
|
|
let output = build_c_host_native(
|
|
target,
|
|
&env_path,
|
|
&env_home,
|
|
&env_cpath,
|
|
host_dest.to_str().unwrap(),
|
|
&[c_host_src.to_str().unwrap()],
|
|
opt_level,
|
|
shared_lib_path,
|
|
);
|
|
validate_output("host.c", "clang", output);
|
|
} else if swift_host_src.exists() {
|
|
// Compile host.swift, if it exists
|
|
let output = build_swift_host_native(
|
|
&env_path,
|
|
&env_home,
|
|
host_dest.to_str().unwrap(),
|
|
&[swift_host_src.to_str().unwrap()],
|
|
opt_level,
|
|
shared_lib_path,
|
|
swift_host_header_src
|
|
.exists()
|
|
.then(|| swift_host_header_src.to_str().unwrap()),
|
|
target.architecture,
|
|
);
|
|
validate_output("host.swift", "swiftc", output);
|
|
}
|
|
|
|
host_dest
|
|
}
|
|
|
|
fn nix_path_opt() -> Option<String> {
|
|
env::var_os("NIX_GLIBC_PATH").map(|path| path.into_string().unwrap())
|
|
}
|
|
|
|
fn library_path<const N: usize>(segments: [&str; N]) -> Option<PathBuf> {
|
|
let mut guess_path = PathBuf::new();
|
|
for s in segments {
|
|
guess_path.push(s);
|
|
}
|
|
if guess_path.exists() {
|
|
Some(guess_path)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Given a list of library directories and the name of a library, find the 1st match
|
|
///
|
|
/// The provided list of library directories should be in the form of a list of
|
|
/// directories, where each directory is represented by a series of path segments, like
|
|
///
|
|
/// ["/usr", "lib"]
|
|
///
|
|
/// Each directory will be checked for a file with the provided filename, and the first
|
|
/// match will be returned.
|
|
///
|
|
/// If there are no matches, [`None`] will be returned.
|
|
fn look_for_library(lib_dirs: &[&[&str]], lib_filename: &str) -> Option<PathBuf> {
|
|
lib_dirs
|
|
.iter()
|
|
.map(|lib_dir| {
|
|
lib_dir.iter().fold(PathBuf::new(), |mut path, segment| {
|
|
path.push(segment);
|
|
path
|
|
})
|
|
})
|
|
.map(|mut path| {
|
|
path.push(lib_filename);
|
|
path
|
|
})
|
|
.find(|path| path.exists())
|
|
}
|
|
|
|
fn link_linux(
|
|
target: &Triple,
|
|
output_path: PathBuf,
|
|
input_paths: &[&str],
|
|
link_type: LinkType,
|
|
) -> io::Result<(Child, PathBuf)> {
|
|
let architecture = format!("{}-linux-gnu", target.architecture);
|
|
|
|
// Command::new("cp")
|
|
// .args(&[input_paths[0], "/home/folkertdev/roc/wasm/host.o"])
|
|
// .output()
|
|
// .unwrap();
|
|
//
|
|
// Command::new("cp")
|
|
// .args(&[input_paths[1], "/home/folkertdev/roc/wasm/app.o"])
|
|
// .output()
|
|
// .unwrap();
|
|
|
|
if let Architecture::X86_32(_) = target.architecture {
|
|
return Ok((
|
|
Command::new(&zig_executable())
|
|
.args(&["build-exe"])
|
|
.args(input_paths)
|
|
.args(&[
|
|
"-target",
|
|
"i386-linux-musl",
|
|
"-lc",
|
|
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
|
])
|
|
.spawn()?,
|
|
output_path,
|
|
));
|
|
}
|
|
|
|
// Some things we'll need to build a list of dirs to check for libraries
|
|
let maybe_nix_path = nix_path_opt();
|
|
let usr_lib_arch = ["/usr", "lib", &architecture];
|
|
let lib_arch = ["/lib", &architecture];
|
|
let nix_path_segments;
|
|
let lib_dirs_if_nix: [&[&str]; 5];
|
|
let lib_dirs_if_nonix: [&[&str]; 4];
|
|
|
|
// Build the aformentioned list
|
|
let lib_dirs: &[&[&str]] =
|
|
// give preference to nix_path if it's defined, this prevents bugs
|
|
if let Some(nix_path) = &maybe_nix_path {
|
|
nix_path_segments = [nix_path.as_str()];
|
|
lib_dirs_if_nix = [
|
|
&nix_path_segments,
|
|
&usr_lib_arch,
|
|
&lib_arch,
|
|
&["/usr", "lib"],
|
|
&["/usr", "lib64"],
|
|
];
|
|
&lib_dirs_if_nix
|
|
} else {
|
|
lib_dirs_if_nonix = [
|
|
&usr_lib_arch,
|
|
&lib_arch,
|
|
&["/usr", "lib"],
|
|
&["/usr", "lib64"],
|
|
];
|
|
&lib_dirs_if_nonix
|
|
};
|
|
|
|
// Look for the libraries we'll need
|
|
|
|
let libgcc_name = "libgcc_s.so.1";
|
|
let libgcc_path = look_for_library(lib_dirs, libgcc_name);
|
|
|
|
let crti_name = "crti.o";
|
|
let crti_path = look_for_library(lib_dirs, crti_name);
|
|
|
|
let crtn_name = "crtn.o";
|
|
let crtn_path = look_for_library(lib_dirs, crtn_name);
|
|
|
|
let scrt1_name = "Scrt1.o";
|
|
let scrt1_path = look_for_library(lib_dirs, scrt1_name);
|
|
|
|
// Unwrap all the paths at once so we can inform the user of all missing libs at once
|
|
let (libgcc_path, crti_path, crtn_path, scrt1_path) =
|
|
match (libgcc_path, crti_path, crtn_path, scrt1_path) {
|
|
(Some(libgcc), Some(crti), Some(crtn), Some(scrt1)) => (libgcc, crti, crtn, scrt1),
|
|
(maybe_gcc, maybe_crti, maybe_crtn, maybe_scrt1) => {
|
|
if maybe_gcc.is_none() {
|
|
eprintln!("Couldn't find libgcc_s.so.1!");
|
|
eprintln!("You may need to install libgcc\n");
|
|
}
|
|
if maybe_crti.is_none() | maybe_crtn.is_none() | maybe_scrt1.is_none() {
|
|
eprintln!("Couldn't find the glibc development files!");
|
|
eprintln!("We need the objects crti.o, crtn.o, and Scrt1.o");
|
|
eprintln!("You may need to install the glibc development package");
|
|
eprintln!("(probably called glibc-dev or glibc-devel)\n");
|
|
}
|
|
|
|
let dirs = lib_dirs
|
|
.iter()
|
|
.map(|segments| segments.join("/"))
|
|
.collect::<Vec<String>>()
|
|
.join("\n");
|
|
eprintln!("We looked in the following directories:\n{}", dirs);
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
let ld_linux = match target.architecture {
|
|
Architecture::X86_64 => {
|
|
// give preference to nix_path if it's defined, this prevents bugs
|
|
if let Some(nix_path) = nix_path_opt() {
|
|
library_path([&nix_path, "ld-linux-x86-64.so.2"])
|
|
} else {
|
|
library_path(["/lib64", "ld-linux-x86-64.so.2"])
|
|
}
|
|
}
|
|
Architecture::Aarch64(_) => library_path(["/lib", "ld-linux-aarch64.so.1"]),
|
|
_ => internal_error!(
|
|
"TODO gracefully handle unsupported linux architecture: {:?}",
|
|
target.architecture
|
|
),
|
|
};
|
|
let ld_linux = ld_linux.unwrap();
|
|
let ld_linux = ld_linux.to_str().unwrap();
|
|
|
|
let mut soname;
|
|
let (base_args, output_path) = match link_type {
|
|
LinkType::Executable => (
|
|
// Presumably this S stands for Static, since if we include Scrt1.o
|
|
// in the linking for dynamic builds, linking fails.
|
|
vec![scrt1_path.to_string_lossy().into_owned()],
|
|
output_path,
|
|
),
|
|
LinkType::Dylib => {
|
|
// TODO: do we actually need the version number on this?
|
|
// Do we even need the "-soname" argument?
|
|
//
|
|
// See https://software.intel.com/content/www/us/en/develop/articles/create-a-unix-including-linux-shared-library.html
|
|
|
|
soname = output_path.clone();
|
|
soname.set_extension("so.1");
|
|
|
|
let mut output_path = output_path;
|
|
|
|
output_path.set_extension("so.1.0");
|
|
|
|
(
|
|
// TODO: find a way to avoid using a vec! here - should theoretically be
|
|
// able to do this somehow using &[] but the borrow checker isn't having it.
|
|
// Also find a way to have these be string slices instead of Strings.
|
|
vec![
|
|
"-shared".to_string(),
|
|
"-soname".to_string(),
|
|
soname.as_path().to_str().unwrap().to_string(),
|
|
],
|
|
output_path,
|
|
)
|
|
}
|
|
LinkType::None => internal_error!("link_linux should not be called with link type of none"),
|
|
};
|
|
|
|
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
|
|
|
// NOTE: order of arguments to `ld` matters here!
|
|
// The `-l` flags should go after the `.o` arguments
|
|
|
|
let mut command = Command::new("ld");
|
|
|
|
command
|
|
// Don't allow LD_ env vars to affect this
|
|
.env_clear()
|
|
.env("PATH", &env_path)
|
|
// Keep NIX_ env vars
|
|
.envs(
|
|
env::vars()
|
|
.filter(|&(ref k, _)| k.starts_with("NIX_"))
|
|
.collect::<HashMap<String, String>>(),
|
|
)
|
|
.args(&[
|
|
"--gc-sections",
|
|
"--eh-frame-hdr",
|
|
"-A",
|
|
arch_str(target),
|
|
"-pie",
|
|
&*crti_path.to_string_lossy(),
|
|
&*crtn_path.to_string_lossy(),
|
|
])
|
|
.args(&base_args)
|
|
.args(&["-dynamic-linker", ld_linux])
|
|
.args(input_paths)
|
|
// ld.lld requires this argument, and does not accept --arch
|
|
// .args(&["-L/usr/lib/x86_64-linux-gnu"])
|
|
.args(&[
|
|
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496365925
|
|
// for discussion and further references
|
|
"-lc",
|
|
"-lm",
|
|
"-lpthread",
|
|
"-ldl",
|
|
"-lrt",
|
|
"-lutil",
|
|
"-lc_nonshared",
|
|
libgcc_path.to_str().unwrap(),
|
|
// Output
|
|
"-o",
|
|
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
|
|
]);
|
|
|
|
let output = command.spawn()?;
|
|
|
|
Ok((output, output_path))
|
|
}
|
|
|
|
fn link_macos(
|
|
target: &Triple,
|
|
output_path: PathBuf,
|
|
input_paths: &[&str],
|
|
link_type: LinkType,
|
|
) -> io::Result<(Child, PathBuf)> {
|
|
let (link_type_arg, output_path) = match link_type {
|
|
LinkType::Executable => ("-execute", output_path),
|
|
LinkType::Dylib => {
|
|
let mut output_path = output_path;
|
|
|
|
output_path.set_extension("dylib");
|
|
|
|
("-dylib", output_path)
|
|
}
|
|
LinkType::None => internal_error!("link_macos should not be called with link type of none"),
|
|
};
|
|
|
|
let arch = match target.architecture {
|
|
Architecture::Aarch64(_) => "arm64".to_string(),
|
|
_ => target.architecture.to_string(),
|
|
};
|
|
|
|
let mut ld_command = Command::new("ld");
|
|
|
|
ld_command
|
|
// NOTE: order of arguments to `ld` matters here!
|
|
// The `-l` flags should go after the `.o` arguments
|
|
// Don't allow LD_ env vars to affect this
|
|
.env_clear()
|
|
.args(&[
|
|
// NOTE: we don't do --gc-sections on macOS because the default
|
|
// macOS linker doesn't support it, but it's a performance
|
|
// optimization, so if we ever switch to a different linker,
|
|
// we'd like to re-enable it on macOS!
|
|
// "--gc-sections",
|
|
link_type_arg,
|
|
"-arch",
|
|
&arch,
|
|
"-macos_version_min",
|
|
&get_macos_version(),
|
|
])
|
|
.args(input_paths);
|
|
|
|
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
|
|
if Path::new(sdk_path).exists() {
|
|
ld_command.arg(format!("-L{}", sdk_path));
|
|
ld_command.arg(format!("-L{}/swift", sdk_path));
|
|
};
|
|
|
|
let roc_link_flags = match env::var("ROC_LINK_FLAGS") {
|
|
Ok(flags) => {
|
|
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
|
|
|
|
flags
|
|
}
|
|
Err(_) => "".to_string(),
|
|
};
|
|
for roc_link_flag in roc_link_flags.split_whitespace() {
|
|
ld_command.arg(roc_link_flag);
|
|
}
|
|
|
|
ld_command.args(&[
|
|
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496392274
|
|
// for discussion and further references
|
|
"-lSystem",
|
|
"-lresolv",
|
|
"-lpthread",
|
|
// This `-F PATH` flag is needed for `-framework` flags to work
|
|
"-F",
|
|
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/",
|
|
// These frameworks are needed for GUI examples to work
|
|
"-framework",
|
|
"AudioUnit",
|
|
"-framework",
|
|
"Cocoa",
|
|
"-framework",
|
|
"CoreAudio",
|
|
"-framework",
|
|
"CoreVideo",
|
|
"-framework",
|
|
"IOKit",
|
|
"-framework",
|
|
"Metal",
|
|
"-framework",
|
|
"QuartzCore",
|
|
// "-lrt", // TODO shouldn't we need this?
|
|
// "-lc_nonshared", // TODO shouldn't we need this?
|
|
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/roc-lang/roc/pull/554#discussion_r496370840
|
|
"-framework",
|
|
"Security",
|
|
// Output
|
|
"-o",
|
|
output_path.to_str().unwrap(), // app
|
|
]);
|
|
|
|
let mut ld_child = ld_command.spawn()?;
|
|
|
|
match target.architecture {
|
|
Architecture::Aarch64(_) => {
|
|
ld_child.wait()?;
|
|
let codesign_child = Command::new("codesign")
|
|
.args(&["-s", "-", output_path.to_str().unwrap()])
|
|
.spawn()?;
|
|
|
|
Ok((codesign_child, output_path))
|
|
}
|
|
_ => Ok((ld_child, output_path)),
|
|
}
|
|
}
|
|
|
|
fn get_macos_version() -> String {
|
|
let cmd_stdout = Command::new("sw_vers")
|
|
.arg("-productVersion")
|
|
.output()
|
|
.expect("Failed to execute command 'sw_vers -productVersion'")
|
|
.stdout;
|
|
|
|
let full_version_string = String::from_utf8(cmd_stdout)
|
|
.expect("Failed to convert output of command 'sw_vers -productVersion' into a utf8 string");
|
|
|
|
full_version_string
|
|
.trim_end()
|
|
.split('.')
|
|
.take(2)
|
|
.collect::<Vec<&str>>()
|
|
.join(".")
|
|
}
|
|
|
|
fn link_wasm32(
|
|
_target: &Triple,
|
|
output_path: PathBuf,
|
|
input_paths: &[&str],
|
|
_link_type: LinkType,
|
|
) -> io::Result<(Child, PathBuf)> {
|
|
let zig_str_path = find_zig_str_path();
|
|
let wasi_libc_path = find_wasi_libc_path();
|
|
|
|
let child = Command::new(&zig_executable())
|
|
// .env_clear()
|
|
// .env("PATH", &env_path)
|
|
.args(&["build-exe"])
|
|
.args(input_paths)
|
|
.args([
|
|
// include wasi libc
|
|
// using `-lc` is broken in zig 8 (and early 9) in combination with ReleaseSmall
|
|
wasi_libc_path.to_str().unwrap(),
|
|
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
|
"-target",
|
|
"wasm32-wasi-musl",
|
|
"--pkg-begin",
|
|
"str",
|
|
zig_str_path.to_str().unwrap(),
|
|
"--pkg-end",
|
|
"--strip",
|
|
"-O",
|
|
"ReleaseSmall",
|
|
// useful for debugging
|
|
// "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli_testing_examples/benchmarks/platform/host.ll",
|
|
])
|
|
.spawn()?;
|
|
|
|
Ok((child, output_path))
|
|
}
|
|
|
|
fn link_windows(
|
|
_target: &Triple,
|
|
output_path: PathBuf,
|
|
input_paths: &[&str],
|
|
link_type: LinkType,
|
|
) -> io::Result<(Child, PathBuf)> {
|
|
let zig_str_path = find_zig_str_path();
|
|
|
|
match link_type {
|
|
LinkType::Dylib => {
|
|
let child = Command::new(&zig_executable())
|
|
.args(&["build-lib"])
|
|
.args(input_paths)
|
|
.args([
|
|
"-lc",
|
|
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
|
"-target",
|
|
"native",
|
|
"--pkg-begin",
|
|
"str",
|
|
zig_str_path.to_str().unwrap(),
|
|
"--pkg-end",
|
|
"--strip",
|
|
"-O",
|
|
"Debug",
|
|
"-dynamic",
|
|
])
|
|
.spawn()?;
|
|
|
|
Ok((child, output_path))
|
|
}
|
|
LinkType::Executable => {
|
|
let child = Command::new(&zig_executable())
|
|
.args(&["build-exe"])
|
|
.args(input_paths)
|
|
.args([
|
|
"-target",
|
|
"x86_64-windows-gnu",
|
|
"--subsystem",
|
|
"console",
|
|
"-lc",
|
|
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
|
])
|
|
.spawn()?;
|
|
|
|
Ok((child, output_path))
|
|
}
|
|
LinkType::None => todo!(),
|
|
}
|
|
}
|
|
|
|
pub fn llvm_module_to_dylib(
|
|
module: &inkwell::module::Module,
|
|
target: &Triple,
|
|
opt_level: OptLevel,
|
|
) -> Result<Library, Error> {
|
|
use crate::target::{self, convert_opt_level};
|
|
use inkwell::targets::{FileType, RelocMode};
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let filename = PathBuf::from("Test.roc");
|
|
let file_path = dir.path().join(filename);
|
|
let mut app_o_file = file_path;
|
|
|
|
app_o_file.set_file_name("app.o");
|
|
|
|
// Emit the .o file using position-independent code (PIC) - needed for dylibs
|
|
let reloc = RelocMode::PIC;
|
|
let target_machine =
|
|
target::target_machine(target, convert_opt_level(opt_level), reloc).unwrap();
|
|
|
|
target_machine
|
|
.write_to_file(module, FileType::Object, &app_o_file)
|
|
.expect("Writing .o file failed");
|
|
|
|
// Link app.o into a dylib - e.g. app.so or app.dylib
|
|
let (mut child, dylib_path) = link(
|
|
&Triple::host(),
|
|
app_o_file.clone(),
|
|
&[app_o_file.to_str().unwrap()],
|
|
LinkType::Dylib,
|
|
)
|
|
.unwrap();
|
|
|
|
child.wait().unwrap();
|
|
|
|
// Load the dylib
|
|
let path = dylib_path.as_path().to_str().unwrap();
|
|
|
|
if matches!(target.architecture, Architecture::Aarch64(_)) {
|
|
// On AArch64 darwin machines, calling `ldopen` on Roc-generated libs from multiple threads
|
|
// sometimes fails with
|
|
// cannot dlopen until fork() handlers have completed
|
|
// This may be due to codesigning. In any case, spinning until we are able to dlopen seems
|
|
// to be okay.
|
|
loop {
|
|
match unsafe { Library::new(path) } {
|
|
Ok(lib) => return Ok(lib),
|
|
Err(Error::DlOpen { .. }) => continue,
|
|
Err(other) => return Err(other),
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe { Library::new(path) }
|
|
}
|
|
|
|
pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &Path) {
|
|
let host_input = host_input_path.to_str().unwrap();
|
|
let output_file = preprocessed_host_path.to_str().unwrap();
|
|
|
|
/*
|
|
Notes:
|
|
zig build-obj just gives you back the first input file, doesn't combine them!
|
|
zig build-lib works but doesn't emit relocations, even with --emit-relocs (bug?)
|
|
(gen_wasm needs relocs for host-to-app calls and stack size adjustment)
|
|
zig wasm-ld is a wrapper around wasm-ld and gives us maximum flexiblity
|
|
(but seems to be an unofficial API)
|
|
*/
|
|
|
|
let mut command = Command::new(&zig_executable());
|
|
let args = &[
|
|
"wasm-ld",
|
|
&bitcode::get_builtins_wasm32_obj_path(),
|
|
host_input,
|
|
WASI_LIBC_PATH,
|
|
WASI_COMPILER_RT_PATH, // builtins need __multi3, __udivti3, __fixdfti
|
|
"-o",
|
|
output_file,
|
|
"--export-all",
|
|
"--no-entry",
|
|
"--import-undefined",
|
|
"--relocatable",
|
|
];
|
|
|
|
command.args(args);
|
|
|
|
// println!("\npreprocess_host_wasm32");
|
|
// println!("zig {}\n", args.join(" "));
|
|
|
|
let output = command.output().unwrap();
|
|
validate_output(output_file, "zig", output)
|
|
}
|
|
|
|
fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
|
|
if !output.status.success() {
|
|
match std::str::from_utf8(&output.stderr) {
|
|
Ok(stderr) => internal_error!(
|
|
"Failed to rebuild {} - stderr of the `{}` command was:\n{}",
|
|
file_name,
|
|
cmd_name,
|
|
stderr
|
|
),
|
|
Err(utf8_err) => internal_error!(
|
|
"Failed to rebuild {} - stderr of the `{}` command was invalid utf8 ({:?})",
|
|
file_name,
|
|
cmd_name,
|
|
utf8_err
|
|
),
|
|
}
|
|
}
|
|
}
|