mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Merge remote-tracking branch 'remote/main' into upgrade-llvm-zig
This commit is contained in:
commit
dc3aa06d61
661 changed files with 14834 additions and 10530 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue