mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
428 lines
13 KiB
Rust
428 lines
13 KiB
Rust
use bumpalo::Bump;
|
|
use roc_build::{
|
|
link::{link, rebuild_host, LinkType},
|
|
program::{self, Problems},
|
|
};
|
|
use roc_builtins::bitcode;
|
|
use roc_load::LoadingProblem;
|
|
use roc_mono::ir::OptLevel;
|
|
use roc_reporting::report::RenderTarget;
|
|
use roc_target::TargetInfo;
|
|
use std::path::PathBuf;
|
|
use std::time::{Duration, SystemTime};
|
|
use target_lexicon::Triple;
|
|
use tempfile::Builder;
|
|
|
|
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
|
|
buf.push_str(&format!(
|
|
" {:9.3} ms {}\n",
|
|
duration.as_secs_f64() * 1000.0,
|
|
label,
|
|
));
|
|
}
|
|
|
|
pub struct BuiltFile {
|
|
pub binary_path: PathBuf,
|
|
pub problems: Problems,
|
|
pub total_time: Duration,
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn build_file<'a>(
|
|
arena: &'a Bump,
|
|
target: &Triple,
|
|
src_dir: PathBuf,
|
|
roc_file_path: PathBuf,
|
|
opt_level: OptLevel,
|
|
emit_debug_info: bool,
|
|
emit_timings: bool,
|
|
link_type: LinkType,
|
|
surgically_link: bool,
|
|
precompiled: bool,
|
|
target_valgrind: bool,
|
|
) -> Result<BuiltFile, LoadingProblem<'a>> {
|
|
let compilation_start = SystemTime::now();
|
|
let target_info = TargetInfo::from(target);
|
|
|
|
// Step 1: compile the app and generate the .o file
|
|
let subs_by_module = Default::default();
|
|
|
|
let loaded = roc_load::load_and_monomorphize(
|
|
arena,
|
|
roc_file_path.clone(),
|
|
src_dir.as_path(),
|
|
subs_by_module,
|
|
target_info,
|
|
// TODO: expose this from CLI?
|
|
RenderTarget::ColorTerminal,
|
|
)?;
|
|
|
|
use target_lexicon::Architecture;
|
|
let emit_wasm = matches!(target.architecture, Architecture::Wasm32);
|
|
|
|
// TODO wasm host extension should be something else ideally
|
|
// .bc does not seem to work because
|
|
//
|
|
// > Non-Emscripten WebAssembly hasn't implemented __builtin_return_address
|
|
//
|
|
// and zig does not currently emit `.a` webassembly static libraries
|
|
let host_extension = if emit_wasm { "zig" } else { "o" };
|
|
let app_extension = if emit_wasm { "bc" } else { "o" };
|
|
|
|
let cwd = roc_file_path.parent().unwrap();
|
|
let mut binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
|
|
|
|
if emit_wasm {
|
|
binary_path.set_extension("wasm");
|
|
}
|
|
|
|
let mut host_input_path = PathBuf::from(cwd);
|
|
let path_to_platform = loaded.platform_path.clone();
|
|
host_input_path.push(&*path_to_platform);
|
|
host_input_path.push("host");
|
|
host_input_path.set_extension(host_extension);
|
|
|
|
// 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 precompiled hosts from there.
|
|
|
|
let exposed_values = loaded
|
|
.exposed_to_host
|
|
.values
|
|
.keys()
|
|
.map(|x| x.as_str(&loaded.interns).to_string())
|
|
.collect();
|
|
|
|
let exposed_closure_types = loaded
|
|
.exposed_to_host
|
|
.closure_types
|
|
.iter()
|
|
.map(|x| x.as_str(&loaded.interns).to_string())
|
|
.collect();
|
|
|
|
let rebuild_thread = spawn_rebuild_thread(
|
|
opt_level,
|
|
surgically_link,
|
|
precompiled,
|
|
host_input_path.clone(),
|
|
binary_path.clone(),
|
|
target,
|
|
exposed_values,
|
|
exposed_closure_types,
|
|
target_valgrind,
|
|
);
|
|
|
|
// TODO try to move as much of this linking as possible to the precompiled
|
|
// host, to minimize the amount of host-application linking required.
|
|
let app_o_file = Builder::new()
|
|
.prefix("roc_app")
|
|
.suffix(&format!(".{}", app_extension))
|
|
.tempfile()
|
|
.map_err(|err| {
|
|
todo!("TODO Gracefully handle tempfile creation error {:?}", err);
|
|
})?;
|
|
let app_o_file = app_o_file.path();
|
|
let buf = &mut String::with_capacity(1024);
|
|
|
|
let mut it = loaded.timings.iter().peekable();
|
|
while let Some((module_id, module_timing)) = it.next() {
|
|
let module_name = loaded.interns.module_name(*module_id);
|
|
|
|
buf.push_str(" ");
|
|
|
|
if module_name.is_empty() {
|
|
// the App module
|
|
buf.push_str("Application Module");
|
|
} else {
|
|
buf.push_str(module_name);
|
|
}
|
|
|
|
buf.push('\n');
|
|
|
|
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
|
|
report_timing(buf, "Parse header", module_timing.parse_header);
|
|
report_timing(buf, "Parse body", module_timing.parse_body);
|
|
report_timing(buf, "Canonicalize", module_timing.canonicalize);
|
|
report_timing(buf, "Constrain", module_timing.constrain);
|
|
report_timing(buf, "Solve", module_timing.solve);
|
|
report_timing(
|
|
buf,
|
|
"Find Specializations",
|
|
module_timing.find_specializations,
|
|
);
|
|
report_timing(
|
|
buf,
|
|
"Make Specializations",
|
|
module_timing.make_specializations,
|
|
);
|
|
report_timing(buf, "Other", module_timing.other());
|
|
buf.push('\n');
|
|
report_timing(buf, "Total", module_timing.total());
|
|
|
|
if it.peek().is_some() {
|
|
buf.push('\n');
|
|
}
|
|
}
|
|
|
|
// This only needs to be mutable for report_problems. This can't be done
|
|
// inside a nested scope without causing a borrow error!
|
|
let mut loaded = loaded;
|
|
let problems = program::report_problems_monomorphized(&mut loaded);
|
|
let loaded = loaded;
|
|
|
|
let code_gen_timing = program::gen_from_mono_module(
|
|
arena,
|
|
loaded,
|
|
&roc_file_path,
|
|
target,
|
|
app_o_file,
|
|
opt_level,
|
|
emit_debug_info,
|
|
);
|
|
|
|
buf.push('\n');
|
|
buf.push_str(" ");
|
|
buf.push_str("Code Generation");
|
|
buf.push('\n');
|
|
|
|
report_timing(
|
|
buf,
|
|
"Generate Assembly from Mono IR",
|
|
code_gen_timing.code_gen,
|
|
);
|
|
report_timing(buf, "Emit .o file", code_gen_timing.emit_o_file);
|
|
|
|
let compilation_end = compilation_start.elapsed().unwrap();
|
|
|
|
let size = std::fs::metadata(&app_o_file)
|
|
.unwrap_or_else(|err| {
|
|
panic!(
|
|
"Could not open {:?} - which was supposed to have been generated. Error: {:?}",
|
|
app_o_file, err
|
|
);
|
|
})
|
|
.len();
|
|
|
|
if emit_timings {
|
|
println!(
|
|
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
|
|
buf
|
|
);
|
|
|
|
println!(
|
|
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
|
|
compilation_end.as_millis(),
|
|
size,
|
|
);
|
|
}
|
|
|
|
let rebuild_duration = rebuild_thread.join().unwrap();
|
|
if emit_timings && !precompiled {
|
|
println!(
|
|
"Finished rebuilding and preprocessing the host in {} ms\n",
|
|
rebuild_duration
|
|
);
|
|
}
|
|
|
|
// Step 2: link the precompiled host and compiled app
|
|
let link_start = SystemTime::now();
|
|
let problems = if surgically_link {
|
|
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
|
|
.map_err(|err| {
|
|
todo!(
|
|
"gracefully handle failing to surgically link with error: {:?}",
|
|
err
|
|
);
|
|
})?;
|
|
problems
|
|
} else if matches!(link_type, LinkType::None) {
|
|
// Just copy the object file to the output folder.
|
|
binary_path.set_extension(app_extension);
|
|
std::fs::copy(app_o_file, &binary_path).unwrap();
|
|
problems
|
|
} else {
|
|
let mut inputs = vec![
|
|
host_input_path.as_path().to_str().unwrap(),
|
|
app_o_file.to_str().unwrap(),
|
|
];
|
|
if matches!(opt_level, OptLevel::Development) {
|
|
inputs.push(bitcode::BUILTINS_HOST_OBJ_PATH);
|
|
}
|
|
|
|
let (mut child, _) = // TODO use lld
|
|
link(
|
|
target,
|
|
binary_path.clone(),
|
|
&inputs,
|
|
link_type
|
|
)
|
|
.map_err(|_| {
|
|
todo!("gracefully handle `ld` failing to spawn.");
|
|
})?;
|
|
|
|
let exit_status = child.wait().map_err(|_| {
|
|
todo!("gracefully handle error after `ld` spawned");
|
|
})?;
|
|
|
|
if exit_status.success() {
|
|
problems
|
|
} else {
|
|
let mut problems = problems;
|
|
|
|
// Add an error for `ld` failing
|
|
problems.errors += 1;
|
|
|
|
problems
|
|
}
|
|
};
|
|
let linking_time = link_start.elapsed().unwrap();
|
|
|
|
if emit_timings {
|
|
println!("Finished linking in {} ms\n", linking_time.as_millis());
|
|
}
|
|
|
|
let total_time = compilation_start.elapsed().unwrap();
|
|
|
|
Ok(BuiltFile {
|
|
binary_path,
|
|
problems,
|
|
total_time,
|
|
})
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn spawn_rebuild_thread(
|
|
opt_level: OptLevel,
|
|
surgically_link: bool,
|
|
precompiled: bool,
|
|
host_input_path: PathBuf,
|
|
binary_path: PathBuf,
|
|
target: &Triple,
|
|
exported_symbols: Vec<String>,
|
|
exported_closure_types: Vec<String>,
|
|
target_valgrind: bool,
|
|
) -> std::thread::JoinHandle<u128> {
|
|
let thread_local_target = target.clone();
|
|
std::thread::spawn(move || {
|
|
if !precompiled {
|
|
println!("🔨 Rebuilding host...");
|
|
}
|
|
|
|
let rebuild_host_start = SystemTime::now();
|
|
if !precompiled {
|
|
if surgically_link {
|
|
roc_linker::build_and_preprocess_host(
|
|
opt_level,
|
|
&thread_local_target,
|
|
host_input_path.as_path(),
|
|
exported_symbols,
|
|
exported_closure_types,
|
|
target_valgrind,
|
|
)
|
|
.unwrap();
|
|
} else {
|
|
rebuild_host(
|
|
opt_level,
|
|
&thread_local_target,
|
|
host_input_path.as_path(),
|
|
None,
|
|
target_valgrind,
|
|
);
|
|
}
|
|
}
|
|
if surgically_link {
|
|
// Copy preprocessed host to executable location.
|
|
let prehost = host_input_path.with_file_name("preprocessedhost");
|
|
std::fs::copy(prehost, binary_path.as_path()).unwrap();
|
|
}
|
|
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
|
|
|
|
rebuild_host_end.as_millis()
|
|
})
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn check_file(
|
|
arena: &Bump,
|
|
src_dir: PathBuf,
|
|
roc_file_path: PathBuf,
|
|
emit_timings: bool,
|
|
) -> Result<(program::Problems, Duration), LoadingProblem> {
|
|
let compilation_start = SystemTime::now();
|
|
|
|
// only used for generating errors. We don't do code generation, so hardcoding should be fine
|
|
// we need monomorphization for when exhaustiveness checking
|
|
let target_info = TargetInfo::default_x86_64();
|
|
|
|
// Step 1: compile the app and generate the .o file
|
|
let subs_by_module = Default::default();
|
|
|
|
let mut loaded = roc_load::load_and_typecheck(
|
|
arena,
|
|
roc_file_path,
|
|
src_dir.as_path(),
|
|
subs_by_module,
|
|
target_info,
|
|
// TODO: expose this from CLI?
|
|
RenderTarget::ColorTerminal,
|
|
)?;
|
|
|
|
let buf = &mut String::with_capacity(1024);
|
|
|
|
let mut it = loaded.timings.iter().peekable();
|
|
while let Some((module_id, module_timing)) = it.next() {
|
|
let module_name = loaded.interns.module_name(*module_id);
|
|
|
|
buf.push_str(" ");
|
|
|
|
if module_name.is_empty() {
|
|
// the App module
|
|
buf.push_str("Application Module");
|
|
} else {
|
|
buf.push_str(module_name);
|
|
}
|
|
|
|
buf.push('\n');
|
|
|
|
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
|
|
report_timing(buf, "Parse header", module_timing.parse_header);
|
|
report_timing(buf, "Parse body", module_timing.parse_body);
|
|
report_timing(buf, "Canonicalize", module_timing.canonicalize);
|
|
report_timing(buf, "Constrain", module_timing.constrain);
|
|
report_timing(buf, "Solve", module_timing.solve);
|
|
report_timing(
|
|
buf,
|
|
"Find Specializations",
|
|
module_timing.find_specializations,
|
|
);
|
|
report_timing(
|
|
buf,
|
|
"Make Specializations",
|
|
module_timing.make_specializations,
|
|
);
|
|
report_timing(buf, "Other", module_timing.other());
|
|
buf.push('\n');
|
|
report_timing(buf, "Total", module_timing.total());
|
|
|
|
if it.peek().is_some() {
|
|
buf.push('\n');
|
|
}
|
|
}
|
|
|
|
let compilation_end = compilation_start.elapsed().unwrap();
|
|
|
|
if emit_timings {
|
|
println!(
|
|
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
|
|
buf
|
|
);
|
|
|
|
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
|
|
}
|
|
|
|
Ok((
|
|
program::report_problems_typechecked(&mut loaded),
|
|
compilation_end,
|
|
))
|
|
}
|