Merge remote-tracking branch 'remote/main' into upgrade-llvm-zig

This commit is contained in:
Luke Boswell 2024-11-13 09:02:20 +11:00
commit dc3aa06d61
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
661 changed files with 14834 additions and 10530 deletions

View file

@ -42,29 +42,6 @@ pub fn link(
}
}
/// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj"
pub fn legacy_host_file(target: Target, platform_main_roc: &Path) -> PathBuf {
let lib_ext = target.static_library_file_ext();
let file_name = roc_linker::preprocessed_host_filename(target)
.replace(roc_linker::PRECOMPILED_HOST_EXT, lib_ext);
let lib_path = platform_main_roc.with_file_name(file_name);
let default_host_path: PathBuf = platform_main_roc
.with_file_name("libhost")
.with_extension(lib_ext);
if lib_path.exists() {
lib_path
} else if default_host_path.exists() {
default_host_path
} else {
let obj_ext = target.object_file_ext();
lib_path.with_extension(obj_ext)
}
}
// Attempts to find a file that is stored relative to the roc executable.
// Since roc is built in target/debug/roc, we may need to drop that path to find the file.
// This is used to avoid depending on the current working directory.
@ -427,6 +404,7 @@ pub fn rebuild_host(
};
let host_dest = if matches!(target.architecture(), Architecture::Wasm32) {
// TODO verify this is corect, how do we do get here with OptLevel::Development
if matches!(opt_level, OptLevel::Development) {
platform_main_roc.with_extension("o")
} else {
@ -437,7 +415,7 @@ pub fn rebuild_host(
.with_file_name("dynhost")
.with_extension(executable_extension)
} else {
legacy_host_file(target, platform_main_roc)
platform_main_roc.with_file_name(target.prebuilt_static_object())
};
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
@ -1314,9 +1292,9 @@ pub fn llvm_module_to_dylib(
unsafe { Library::new(path) }
}
pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &Path) {
pub fn preprocess_host_wasm32(host_input_path: &Path, host_output_path: &Path) {
let host_input = host_input_path.to_str().unwrap();
let output_file = preprocessed_host_path.to_str().unwrap();
let output_file = host_output_path.to_str().unwrap();
/*
Notes:
@ -1362,9 +1340,11 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_counter: usize) {
let command_string = stringify_command(&command, false);
let cmd_str = &command_string;
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRINT_BUILD_COMMANDS, {
print_command_str(cmd_str);
});
let cmd_output = command.output().unwrap();
let max_flaky_fail_count = 10;

View file

@ -1,6 +1,5 @@
use crate::link::{
legacy_host_file, link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy,
};
use crate::link::{link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy};
use bumpalo::collections::CollectIn;
use bumpalo::Bump;
use inkwell::memory_buffer::MemoryBuffer;
use roc_error_macros::internal_error;
@ -27,6 +26,7 @@ use std::{
#[cfg(feature = "target-wasm32")]
use roc_collections::all::MutSet;
use roc_target::SurgicalHostArtifacts;
pub const DEFAULT_ROC_FILENAME: &str = "main.roc";
@ -96,7 +96,7 @@ pub fn gen_from_mono_module<'a>(
roc_file_path: &Path,
target: Target,
code_gen_options: CodeGenOptions,
preprocessed_host_path: &Path,
built_host_opt: &BuiltHostOpt,
wasm_dev_stack_bytes: Option<u32>,
) -> GenFromMono<'a> {
let path = roc_file_path;
@ -106,19 +106,27 @@ pub fn gen_from_mono_module<'a>(
let opt = code_gen_options.opt_level;
match code_gen_options.backend {
CodeGenBackend::Wasm => gen_from_mono_module_dev(
arena,
loaded,
target,
preprocessed_host_path,
wasm_dev_stack_bytes,
AssemblyBackendMode::Binary, // dummy value, unused in practice
),
CodeGenBackend::Wasm => {
assert_ne!(
*built_host_opt,
BuiltHostOpt::None,
"Wasm backend needs a built host."
);
gen_from_mono_module_dev(
arena,
loaded,
target,
built_host_opt,
wasm_dev_stack_bytes,
AssemblyBackendMode::Binary, // dummy value, unused in practice
)
}
CodeGenBackend::Assembly(backend_mode) => gen_from_mono_module_dev(
arena,
loaded,
target,
preprocessed_host_path,
built_host_opt,
wasm_dev_stack_bytes,
backend_mode,
),
@ -226,11 +234,16 @@ fn gen_from_mono_module_llvm<'a>(
exposed_to_host,
platform_path: _,
} => {
// TODO support multiple of these!
debug_assert_eq!(exposed_to_host.len(), 1);
let (symbol, layout) = exposed_to_host[0];
let entry_points: bumpalo::collections::Vec<_> = exposed_to_host
.iter()
.map(|(fn_name, symbol, layout)| SingleEntryPoint {
name: fn_name,
symbol: *symbol,
layout: *layout,
})
.collect_in(arena);
roc_mono::ir::EntryPoint::Single(SingleEntryPoint { symbol, layout })
roc_mono::ir::EntryPoint::Program(entry_points.into_bump_slice())
}
EntryPoint::Test => roc_mono::ir::EntryPoint::Expects { symbols: &[] },
};
@ -435,43 +448,59 @@ fn gen_from_mono_module_llvm<'a>(
)
}
#[cfg(feature = "target-wasm32")]
fn gen_from_mono_module_dev<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
target: Target,
preprocessed_host_path: &Path,
built_host_opt: &BuiltHostOpt,
wasm_dev_stack_bytes: Option<u32>,
backend_mode: AssemblyBackendMode,
#[allow(unused_variables)] backend_mode: AssemblyBackendMode,
) -> GenFromMono<'a> {
match target.architecture() {
Architecture::Wasm32 => gen_from_mono_module_dev_wasm32(
arena,
loaded,
preprocessed_host_path,
wasm_dev_stack_bytes,
),
Architecture::X86_64 | Architecture::Aarch64 => {
gen_from_mono_module_dev_assembly(arena, loaded, target, backend_mode)
}
_ => todo!(),
}
}
match (built_host_opt, target.architecture()) {
(BuiltHostOpt::Additive(host_path), Architecture::Wasm32) => {
#[cfg(feature = "target-wasm32")]
{
gen_from_mono_module_dev_wasm32(arena, loaded, host_path, wasm_dev_stack_bytes)
}
#[cfg(not(feature = "target-wasm32"))]
pub fn gen_from_mono_module_dev<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
target: Target,
_host_input_path: &Path,
_wasm_dev_stack_bytes: Option<u32>,
backend_mode: AssemblyBackendMode,
) -> GenFromMono<'a> {
match target.architecture() {
Architecture::X86_64 | Architecture::Aarch64 => {
gen_from_mono_module_dev_assembly(arena, loaded, target, backend_mode)
#[cfg(not(feature = "target-wasm32"))]
{
internal_error!("Compiler was not built with feature 'target-wasm32'.");
}
}
(BuiltHostOpt::None, Architecture::Wasm32) => {
internal_error!("Cannot compile wasm32 without a host on the dev compiler backend.")
}
(BuiltHostOpt::Legacy(host_path), Architecture::Wasm32) => internal_error!(
"Unsupported host files found for use with wasm32 dev compiler backend:\n {}",
host_path.display()
),
(
BuiltHostOpt::Surgical(SurgicalHostArtifacts {
preprocessed_host, ..
}),
Architecture::Wasm32,
) => internal_error!(
"Unsupported host files found for use with wasm32 dev compiler backend:\n {}",
preprocessed_host.display()
),
(_, Architecture::X86_64 | Architecture::Aarch64) => {
#[cfg(not(feature = "target-wasm32"))]
{
gen_from_mono_module_dev_assembly(arena, loaded, target, backend_mode)
}
#[cfg(feature = "target-wasm32")]
{
internal_error!("Compiler was not built with feature 'target-wasm32'.")
}
}
(_, Architecture::Aarch32) => {
internal_error!("Dev compiler backend does not support 32 bit ARM architectures")
}
(_, Architecture::X86_32) => {
internal_error!("Dev compiler backend does not support 32 bit x86 architectures")
}
_ => todo!(),
}
}
@ -479,7 +508,7 @@ pub fn gen_from_mono_module_dev<'a>(
fn gen_from_mono_module_dev_wasm32<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
preprocessed_host_path: &Path,
built_host_path: &Path,
wasm_dev_stack_bytes: Option<u32>,
) -> GenFromMono<'a> {
let all_code_gen_start = Instant::now();
@ -505,17 +534,17 @@ fn gen_from_mono_module_dev_wasm32<'a>(
stack_bytes: wasm_dev_stack_bytes.unwrap_or(roc_gen_wasm::Env::DEFAULT_STACK_BYTES),
};
let host_bytes = std::fs::read(preprocessed_host_path).unwrap_or_else(|_| {
let host_bytes = std::fs::read(built_host_path).unwrap_or_else(|_| {
internal_error!(
"Failed to read host object file {}! Try omitting --prebuilt-platform",
preprocessed_host_path.display()
built_host_path.display()
)
});
let host_module = roc_gen_wasm::parse_host(arena, &host_bytes).unwrap_or_else(|e| {
internal_error!(
"I ran into a problem with the host object file, {} at offset 0x{:x}:\n{}",
preprocessed_host_path.display(),
built_host_path.display(),
e.offset,
e.message
)
@ -549,6 +578,7 @@ fn gen_from_mono_module_dev_wasm32<'a>(
)
}
#[allow(dead_code)]
fn gen_from_mono_module_dev_assembly<'a>(
arena: &'a bumpalo::Bump,
loaded: MonomorphizedModule<'a>,
@ -725,7 +755,8 @@ pub fn build_file<'a>(
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
build_host: bool,
suppress_build_host_warning: bool,
wasm_dev_stack_bytes: Option<u32>,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
@ -733,7 +764,6 @@ pub fn build_file<'a>(
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
// Step 1: compile the app and generate the .o file
let loaded = roc_load::load_and_monomorphize(
arena,
app_module_path.clone(),
@ -751,7 +781,8 @@ pub fn build_file<'a>(
emit_timings,
link_type,
linking_strategy,
prebuilt_requested,
build_host,
suppress_build_host_warning,
wasm_dev_stack_bytes,
loaded,
compilation_start,
@ -759,6 +790,64 @@ pub fn build_file<'a>(
)
}
#[derive(Debug, PartialEq, Eq)]
/// Opt because of possible None value
// Advice: do not try to wrap this in an Option, that would require cloning in build_loaded_file.
pub enum BuiltHostOpt {
Additive(PathBuf),
Legacy(PathBuf),
// SurgicalHostArtifacts contains metadata, preprocessed_host
Surgical(SurgicalHostArtifacts),
None,
}
fn build_and_preprocess_host(
code_gen_options: CodeGenOptions,
dll_stub_symbols: Vec<String>,
emit_timings: bool,
linking_strategy: LinkingStrategy,
platform_main_roc: &Path,
preprocessed_host_path: &Path,
target: Target,
) -> BuiltHostOpt {
let rebuild_thread = match linking_strategy {
LinkingStrategy::Additive => spawn_wasm32_host_build_thread(
code_gen_options.opt_level,
target,
platform_main_roc.to_owned(),
preprocessed_host_path.to_owned(),
),
LinkingStrategy::Surgical => {
let preprocessed_path =
platform_main_roc.with_file_name(target.prebuilt_surgical_host());
let metadata_path = platform_main_roc.with_file_name(target.metadata_file_name());
spawn_surgical_host_build_thread(
code_gen_options.opt_level,
target,
platform_main_roc.to_owned(),
dll_stub_symbols,
preprocessed_path,
preprocessed_host_path.to_owned(),
metadata_path,
)
}
LinkingStrategy::Legacy => spawn_legacy_host_build_thread(
code_gen_options.opt_level,
target,
platform_main_roc.to_owned(),
),
};
let (rebuild_duration, path) = rebuild_thread.join().expect("Failed to build host.");
if emit_timings {
println!(
"Finished rebuilding the platform host in {} ms\n",
rebuild_duration
);
}
path
}
#[allow(clippy::too_many_arguments)]
fn build_loaded_file<'a>(
arena: &'a Bump,
@ -767,121 +856,59 @@ fn build_loaded_file<'a>(
code_gen_options: CodeGenOptions,
emit_timings: bool,
link_type: LinkType,
mut linking_strategy: LinkingStrategy,
prebuilt_requested: bool,
linking_strategy: LinkingStrategy,
build_host_requested: bool,
suppress_build_host_warning: bool,
wasm_dev_stack_bytes: Option<u32>,
loaded: roc_load::MonomorphizedModule<'a>,
compilation_start: Instant,
out_path: Option<&Path>,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let platform_main_roc = match &loaded.entry_point {
// get the platform path from the app header
let platform_main_roc_path = match &loaded.entry_point {
EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
_ => unreachable!(),
};
// For example, if we're loading the platform from a URL, it's automatically prebuilt
// even if the --prebuilt-platform CLI flag wasn't set.
let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
let output_exe_path = get_exe_path(
out_path,
app_module_path.as_path(),
target,
linking_strategy,
link_type,
);
if is_platform_prebuilt && linking_strategy == LinkingStrategy::Surgical {
// Fallback to legacy linking if the preprocessed host file does not exist, but a legacy host does exist.
let preprocessed_host_path =
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target));
let legacy_host_path = legacy_host_file(target, &platform_main_roc);
if !preprocessed_host_path.exists() && legacy_host_path.exists() {
linking_strategy = LinkingStrategy::Legacy;
}
}
let dll_stub_symbols =
roc_linker::ExposedSymbols::from_exposed_to_host(&loaded.interns, &loaded.exposed_to_host);
// the preprocessed host is stored beside the platform's main.roc
let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
if target == Target::Wasm32 {
// when compiling a wasm application, we implicitly assume here that the host is in zig
// and has a file called "host.zig"
platform_main_roc.with_file_name("host.zig")
let built_host_opt =
// Not sure if this is correct for all calls with LinkType::Dylib...
if link_type == LinkType::Dylib || target == Target::Wasm32 {
BuiltHostOpt::None
} else {
legacy_host_file(target, &platform_main_roc)
}
} else {
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target))
};
let prebuilt_host = determine_built_host_path(&platform_main_roc_path, target, build_host_requested, link_type, linking_strategy, suppress_build_host_warning);
let output_exe_path = match out_path {
Some(path) => {
// true iff the path ends with a directory separator,
// e.g. '/' on UNIX, '/' or '\\' on Windows
let ends_with_sep = {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
path.as_os_str().as_bytes().ends_with(&[b'/'])
match prebuilt_host {
BuiltHostOpt::None => {
build_and_preprocess_host(
code_gen_options,
dll_stub_symbols,
emit_timings,
linking_strategy,
&platform_main_roc_path,
&output_exe_path,
target,
)
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
let last = path.as_os_str().encode_wide().last();
last == Some(0x002f)// UTF-16 slash
|| last == Some(0x005c) // UTF-16 backslash
BuiltHostOpt::Surgical(ref surgical_artifacts) => {
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&surgical_artifacts.preprocessed_host, output_exe_path.as_path()).unwrap();
prebuilt_host
}
};
// If you specified a path that ends in in a directory separator, then
// use that directory, but use the app module's filename for the filename.
if ends_with_sep {
let filename = app_module_path.file_name().unwrap_or_default();
with_output_extension(&path.join(filename), target, linking_strategy, link_type)
} else {
path.to_path_buf()
other => other
}
}
None => with_output_extension(&app_module_path, target, linking_strategy, link_type),
};
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
None
} else if is_platform_prebuilt {
if !preprocessed_host_path.exists() {
invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path);
std::process::exit(1);
}
if linking_strategy == LinkingStrategy::Surgical {
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
None
} else {
// TODO this should probably be moved before load_and_monomorphize.
// To do this we will need to preprocess files just for their exported symbols.
// Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get prebuilt platforms from there.
let dll_stub_symbols = roc_linker::ExposedSymbols::from_exposed_to_host(
&loaded.interns,
&loaded.exposed_to_host,
);
let join_handle = spawn_rebuild_thread(
code_gen_options.opt_level,
linking_strategy,
platform_main_roc.clone(),
preprocessed_host_path.clone(),
output_exe_path.clone(),
target,
dll_stub_symbols,
);
Some(join_handle)
};
};
let buf = &mut String::with_capacity(1024);
@ -914,31 +941,13 @@ fn build_loaded_file<'a>(
let problems = report_problems_monomorphized(&mut loaded);
let loaded = loaded;
let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread
.join()
.expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
}
None
} else {
Some(rebuild_thread)
}
} else {
None
};
let (roc_app_bytes, code_gen_timing, expect_metadata) = gen_from_mono_module(
arena,
loaded,
&app_module_path,
target,
code_gen_options,
&preprocessed_host_path,
&built_host_opt,
wasm_dev_stack_bytes,
);
@ -971,24 +980,18 @@ fn build_loaded_file<'a>(
);
}
if let Some(thread) = opt_rebuild_timing {
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
}
}
// Step 2: link the prebuilt platform and compiled app
// link the prebuilt platform and compiled app
let link_start = Instant::now();
match (linking_strategy, link_type) {
(LinkingStrategy::Surgical, _) => {
let metadata_file = platform_main_roc_path.with_file_name(target.metadata_file_name());
roc_linker::link_preprocessed_host(
target,
&platform_main_roc,
&roc_app_bytes,
&output_exe_path,
metadata_file,
);
}
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
@ -1013,36 +1016,57 @@ fn build_loaded_file<'a>(
std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
let builtins_host_tempfile = roc_bitcode::host_tempfile()
.expect("failed to write host builtins object to tempfile");
let mut inputs = vec![app_o_file.to_str().unwrap()];
if !matches!(link_type, LinkType::Dylib | LinkType::None) {
// the host has been compiled into a .o or .obj file
inputs.push(preprocessed_host_path.as_path().to_str().unwrap());
let mut host_path = String::new();
match built_host_opt {
BuiltHostOpt::Legacy(p) => {
host_path.push_str(&p.to_string_lossy());
inputs.push(&host_path);
}
BuiltHostOpt::None => {
// In case of link_type == LinkType::Dylib or target == Target::Wasm32
// When compiling a Dylib there is no host, such as when generating glue using `roc glue`.
if target == Target::Wasm32 {
let wasm_host_zig: PathBuf =
platform_main_roc_path.with_file_name("host.zig");
assert!(
wasm_host_zig.exists(),
"No host.zig file found at {} when building wasm32 target.",
wasm_host_zig.display()
);
host_path.push_str(&wasm_host_zig.to_string_lossy());
inputs.push(&host_path);
}
}
other => {
panic!("Unexpected variant of built_host_opt in combination with `LinkingStrategy::Legacy`: {:?}", other);
}
}
let builtins_host_tempfile = roc_bitcode::host_tempfile()
.expect("failed to write host builtins object to tempfile");
if matches!(code_gen_options.backend, CodeGenBackend::Assembly(_)) {
inputs.push(builtins_host_tempfile.path().to_str().unwrap());
}
let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type)
.map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
.map_err(|_| todo!("linker failed to spawn."))?;
let exit_status = child
.wait()
.map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
.map_err(|_| todo!("linker error after spawning"))?;
// Extend the lifetime of the tempfile so it doesn't get dropped
// (and thus deleted) before the child process is done using it!
let _ = builtins_host_tempfile;
if !exit_status.success() {
todo!(
"gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
exit_status.code()
);
todo!("linker failed with exit code {:?}", exit_status.code());
}
}
}
@ -1063,124 +1087,291 @@ fn build_loaded_file<'a>(
})
}
fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) {
let prefix = if prebuilt_requested {
"Because I was run with --prebuilt-platform, "
} else {
""
};
fn determine_built_host_path(
platform_main_roc_path: &Path,
target: Target,
build_host_requested: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
suppress_build_host_warning: bool,
) -> BuiltHostOpt {
if build_host_requested {
if !suppress_build_host_warning {
// TODO
//report_rebuilding_existing_host(&preprocessed_host.to_string_lossy());
unimplemented!()
}
let preprocessed_host_path_str = preprocessed_host_path.to_string_lossy();
let extra_err_msg = if preprocessed_host_path_str.ends_with(".rh") {
"\n\n\tNote: If the platform does have an .rh1 file but no .rh file, it's because it's been built with an older version of roc. Contact the author to release a new build of the platform using a roc release newer than March 21 2023.\n"
match link_type {
LinkType::Executable => BuiltHostOpt::None,
LinkType::Dylib => {
eprintln!("You asked me to build the host, but I don't know how to rebuild a host for a dynamic library.");
std::process::exit(1);
}
LinkType::None => {
eprintln!("You asked me to build the host, but I don't know how to rebuild a host for an unlinked object.");
std::process::exit(1);
}
}
} else {
""
};
match linking_strategy {
LinkingStrategy::Legacy => {
let legacy_host_path_res = target.find_legacy_host(platform_main_roc_path);
match legacy_host_path_res {
Ok(legacy_host_path) => BuiltHostOpt::Legacy(legacy_host_path),
Err(err_msg) => {
eprintln!("Legacy linking failed: {}", err_msg);
#[cfg(target_os = "linux")]
eprintln!(
"\n TIP: Maybe try surgical linking with the flag --linker=surgical"
);
std::process::exit(1);
}
}
}
LinkingStrategy::Surgical => {
let surgical_artifacts = target.find_surgical_host(platform_main_roc_path);
match surgical_artifacts {
Ok(surgical_artifacts) => BuiltHostOpt::Surgical(surgical_artifacts),
Err(paths_str) => {
// TODO improve error message
eprintln!(
"LinkingStrategy was set to Surgical (default), but \
I tried to find the surgical host at any of these paths {} but it does not exist.",
paths_str
);
std::process::exit(1);
}
}
}
LinkingStrategy::Additive => {
unimplemented!()
}
}
}
}
/// Get outut path for the executable.
///
/// If you specified a path that ends in in a directory separator, then
/// use that directory, but use the app module's filename for the filename.
fn get_exe_path(
out_path: Option<&Path>,
app_module_path: &Path,
target: Target,
linking_strategy: LinkingStrategy,
link_type: LinkType,
) -> PathBuf {
match out_path {
Some(path) => {
// true iff the path ends with a directory separator,
// e.g. '/' on UNIX, '/' or '\\' on Windows
let ends_with_sep = {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
path.as_os_str().as_bytes().ends_with(&[b'/'])
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
let last = path.as_os_str().encode_wide().last();
last == Some(0x002f)// UTF-16 slash
|| last == Some(0x005c) // UTF-16 backslash
}
};
if ends_with_sep {
let filename = app_module_path.file_name().unwrap_or_default();
with_output_extension(&path.join(filename), target, linking_strategy, link_type)
} else {
path.to_path_buf()
}
}
None => with_output_extension(app_module_path, target, linking_strategy, link_type),
}
}
#[allow(dead_code)]
fn report_rebuilding_existing_host(host_path: &str) {
eprintln!(
indoc::indoc!(
r#"
{}I was expecting this file to exist:
WARNING: I found an existing compiled host at:
{}
However, it was not there!{}
However, the --build-host flag was set! I will rebuild the host and overwrite the existing file.
If you have the platform's source code locally, you may be able to generate it by re-running this command omitting --prebuilt-platform
Remove the --build-host flag to use the existing host and silence this warning.
Rebuilding hosts using the roc compiler is deprecated and will be removed in a future version.
"#
),
prefix,
preprocessed_host_path.to_string_lossy(),
extra_err_msg
host_path,
);
}
#[allow(clippy::too_many_arguments)]
fn spawn_rebuild_thread(
#[allow(dead_code)]
fn report_rebuilding_missing_host(host_path: &str) {
eprintln!(
indoc::indoc!(
r#"
WARNING: I was expecting a prebuilt host to exist at:
{}
However, it was not there! I will rebuild the host and write it to that location.
Rebuilding hosts using the roc compiler is deprecated and will be removed in a future version.
"#
),
host_path,
);
}
#[allow(dead_code)]
fn report_missing_prebuilt_host(msg: &str) {
eprintln!(
indoc::indoc!(
r#"
I was expecting a prebuilt host to exist:
{}
However, it was not there!
If you have the platform's source code locally, you may be able to generate it by using a build script.
"#
),
msg
);
}
#[allow(dead_code)]
fn report_refusing_to_rebuild_host(host_path: &str) {
eprintln!(
indoc::indoc!(
r#"
I found a prebuilt host for this platform, but you requested to rebuild it:
{}
Remove the `--build-host` flag to use the prebuilt host.
The `--build-host` flag is deprecated and will be removed in a future release.
"#
),
host_path,
);
}
fn spawn_wasm32_host_build_thread(
opt_level: OptLevel,
linking_strategy: LinkingStrategy,
platform_main_roc: PathBuf,
preprocessed_host_path: PathBuf,
output_exe_path: PathBuf,
target: Target,
dll_stub_symbols: Vec<String>,
) -> std::thread::JoinHandle<u128> {
platform_main_roc: PathBuf,
output_path: PathBuf,
) -> std::thread::JoinHandle<(u128, BuiltHostOpt)> {
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Rebuilding platform...");
eprintln!("🔨 Building host ...");
let rebuild_host_start = Instant::now();
let start = Instant::now();
match linking_strategy {
LinkingStrategy::Additive => {
let host_dest = rebuild_host(opt_level, target, platform_main_roc.as_path(), None);
let host_dest = rebuild_host(opt_level, target, platform_main_roc.as_path(), None);
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
}
LinkingStrategy::Surgical => {
build_and_preprocess_host_lowlevel(
opt_level,
target,
platform_main_roc.as_path(),
preprocessed_host_path.as_path(),
&dll_stub_symbols,
);
preprocess_host_wasm32(host_dest.as_path(), &output_path);
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
}
LinkingStrategy::Legacy => {
rebuild_host(opt_level, target, platform_main_roc.as_path(), None);
}
}
rebuild_host_start.elapsed().as_millis()
(
start.elapsed().as_millis(),
BuiltHostOpt::Additive(output_path),
)
})
}
pub fn build_and_preprocess_host(
/// Note this will copy the preprocessed host to the executable location
/// where the surgical linker will modify that copy in-place.
fn spawn_surgical_host_build_thread(
opt_level: OptLevel,
target: Target,
platform_main_roc: &Path,
preprocessed_host_path: &Path,
exposed_symbols: roc_linker::ExposedSymbols,
) {
let stub_dll_symbols = exposed_symbols.stub_dll_symbols();
platform_main_roc: PathBuf,
dll_stub_symbols: Vec<String>,
preprocessed_path: PathBuf,
output_exe_path: PathBuf,
metadata_path: PathBuf,
) -> std::thread::JoinHandle<(u128, BuiltHostOpt)> {
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Building host ...");
build_and_preprocess_host_lowlevel(
opt_level,
target,
platform_main_roc,
preprocessed_host_path,
&stub_dll_symbols,
)
let start = Instant::now();
let stub_lib = roc_linker::generate_stub_lib_from_loaded(
target,
platform_main_roc.as_path(),
dll_stub_symbols.as_slice(),
);
debug_assert!(stub_lib.exists());
let host_exe = rebuild_host(
opt_level,
target,
platform_main_roc.as_path(),
Some(&stub_lib),
);
roc_linker::preprocess_host(
target,
host_exe.as_path(),
metadata_path.as_path(),
preprocessed_path.as_path(),
&stub_lib,
false,
false,
);
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_path, &output_exe_path).unwrap();
(
start.elapsed().as_millis(),
BuiltHostOpt::Surgical(SurgicalHostArtifacts {
metadata: metadata_path,
preprocessed_host: preprocessed_path,
}),
)
})
}
fn build_and_preprocess_host_lowlevel(
// Note the output host will be
fn spawn_legacy_host_build_thread(
opt_level: OptLevel,
target: Target,
platform_main_roc: &Path,
_preprocessed_host_path: &Path,
stub_dll_symbols: &[String],
) {
let stub_lib =
roc_linker::generate_stub_lib_from_loaded(target, platform_main_roc, stub_dll_symbols);
platform_main_roc: PathBuf,
) -> std::thread::JoinHandle<(u128, BuiltHostOpt)> {
std::thread::spawn(move || {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Building host ...");
debug_assert!(stub_lib.exists());
let start = Instant::now();
let host_dest = rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib));
let host_dest = rebuild_host(opt_level, target, platform_main_roc.as_path(), None);
roc_linker::preprocess_host(
target,
host_dest.as_path(),
platform_main_roc,
&stub_lib,
false,
false,
)
(start.elapsed().as_millis(), BuiltHostOpt::Legacy(host_dest))
})
}
#[allow(clippy::too_many_arguments)]
@ -1266,7 +1457,7 @@ pub fn build_str_test<'a>(
arena: &'a Bump,
app_module_path: &Path,
app_module_source: &'a str,
assume_prebuild: bool,
build_host_requested: bool,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let target = target_lexicon::Triple::host().into();
@ -1303,6 +1494,9 @@ pub fn build_str_test<'a>(
)
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
// we are in a test, so we don't need to provide a warning about rebuilding the host
let suppress_build_host_warning = true;
build_loaded_file(
arena,
target,
@ -1311,7 +1505,8 @@ pub fn build_str_test<'a>(
emit_timings,
link_type,
linking_strategy,
assume_prebuild,
build_host_requested,
suppress_build_host_warning,
wasm_dev_stack_bytes,
loaded,
compilation_start,