Extract target triple and linking logic

This commit is contained in:
Richard Feldman 2020-10-01 22:14:04 -04:00
parent a108544fa8
commit 26dfa01205
7 changed files with 282 additions and 203 deletions

View file

@ -1,21 +1,16 @@
#[macro_use]
extern crate clap;
use bumpalo::Bump;
use clap::ArgMatches;
use clap::{App, Arg};
use roc_build::program::gen;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::fs;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::io;
use std::path::Path;
use std::process;
use std::process::Command;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
pub mod build;
pub mod repl;
pub static FLAG_OPTIMIZE: &str = "optimize";
@ -67,7 +62,7 @@ pub fn build_app<'a>() -> App<'a> {
)
}
pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
OptLevel::Optimize
@ -79,7 +74,7 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
// Spawn the root task
let path = path.canonicalize().unwrap_or_else(|err| {
use ErrorKind::*;
use io::ErrorKind::*;
match err.kind() {
NotFound => {
@ -96,8 +91,8 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
}
});
let binary_path =
build_file(src_dir, path, opt_level).expect("TODO gracefully handle build_file failing");
let binary_path = build::build_file(target, src_dir, path, opt_level)
.expect("TODO gracefully handle build_file failing");
if run_after_build {
// Run the compiled app
@ -110,131 +105,3 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
Ok(())
}
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
buf.push_str(&format!(
" {:.3} ms {}\n",
duration.as_secs_f64() * 1000.0,
label,
));
}
fn build_file(
src_dir: PathBuf,
filename: PathBuf,
opt_level: OptLevel,
) -> Result<PathBuf, LoadingProblem> {
let compilation_start = SystemTime::now();
let arena = Bump::new();
// Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default();
// Release builds use uniqueness optimizations
let stdlib = match opt_level {
OptLevel::Normal => roc_builtins::std::standard_stdlib(),
OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(),
};
let loaded =
roc_load::file::load(filename.clone(), &stdlib, src_dir.as_path(), subs_by_module)?;
let dest_filename = filename.with_file_name("roc_app.o");
let buf = &mut String::with_capacity(1024);
for (module_id, module_timing) in loaded.timings.iter() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
buf.push_str(module_name);
buf.push_str("\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, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
}
println!(
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
buf
);
gen(
&arena,
loaded,
filename,
Triple::host(),
&dest_filename,
opt_level,
);
let compilation_end = compilation_start.elapsed().unwrap();
println!(
"Finished compilation and code gen in {} ms\n",
compilation_end.as_millis()
);
let cwd = dest_filename.parent().unwrap();
// Step 2: link the precompiled host and compiled app
let arch = "x86_64"; // TODO determine this based on target
let target_triple = "x86_64-unknown-linux-gnu";
let host_input_path = cwd
.join("platform")
.join("host")
.join(target_triple)
.join("host.o");
let binary_path = cwd.join("app"); // TODO should be app.exe on Windows
// 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 cmd_result = Command::new("ld") // TODO use lld
.args(&[
"-arch",
arch,
"/usr/lib/x86_64-linux-gnu/crti.o",
"/usr/lib/x86_64-linux-gnu/crtn.o",
"/usr/lib/x86_64-linux-gnu/Scrt1.o",
"-dynamic-linker",
"/lib64/ld-linux-x86-64.so.2",
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
// for discussion and further references
"-lc",
"-lm",
"-lpthread",
"-ldl",
"-lrt",
"-lutil",
"-lc_nonshared",
// "-lc++", // TODO shouldn't we need this?
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
// "-lunwind", // TODO will eventually need this, see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
"-o",
binary_path.as_path().to_str().unwrap(), // app
host_input_path.as_path().to_str().unwrap(), // host.o
dest_filename.as_path().to_str().unwrap(), // roc_app.o
])
.spawn()
.map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn.");
})?
.wait()
.map_err(|_| {
todo!("gracefully handle error after `rustc` spawned");
});
// Clean up the leftover .o file from the Roc, if possible.
// (If cleaning it up fails, that's fine. No need to take action.)
// TODO compile the dest_filename to a tmpdir, as an extra precaution.
let _ = fs::remove_file(dest_filename);
// If the cmd errored out, return the Err.
cmd_result?;
Ok(binary_path)
}