code to link LLVM IR

This commit is contained in:
Folkert 2021-05-25 22:44:39 +02:00
parent c8018a12fc
commit 2b13bdfd52
3 changed files with 133 additions and 34 deletions

View File

@ -44,7 +44,7 @@ pub fn build_file<'a>(
let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
// Step 1: compile the app and generate the .o file
// Step 1: compile the app
let subs_by_module = MutMap::default();
// Release builds use uniqueness optimizations
@ -64,6 +64,51 @@ pub fn build_file<'a>(
)?;
let path_to_platform = loaded.platform_path.clone();
// Step 2: build the host
let cwd = roc_file_path.parent().unwrap();
let mut host_input_path = PathBuf::from(cwd);
host_input_path.push(&*path_to_platform);
use roc_build::link::HostBuildMode;
let host_build_mode = match opt_level {
OptLevel::Normal => {
if true {
host_input_path.push("host.o");
HostBuildMode::ObjectFile
} else {
host_input_path.push("host.bc");
HostBuildMode::LLVMBitcode
}
}
OptLevel::Optimize => {
if false {
host_input_path.push("host.o");
HostBuildMode::ObjectFile
} else {
host_input_path.push("host.bc");
HostBuildMode::LLVMBitcode
}
}
};
// TODO 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 rebuild_host_start = SystemTime::now();
rebuild_host(host_input_path.as_path(), host_build_mode);
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
if emit_debug_info {
println!(
"Finished rebuilding the host in {} ms\n",
rebuild_host_end.as_millis()
);
}
dbg!("built host");
// step 3: generate the .o file
let app_o_file = Builder::new()
.prefix("roc_app")
.suffix(".o")
@ -114,7 +159,6 @@ pub fn build_file<'a>(
}
}
let cwd = roc_file_path.parent().unwrap();
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
let code_gen_timing = program::gen_from_mono_module(
&arena,
@ -122,9 +166,14 @@ pub fn build_file<'a>(
&roc_file_path,
Triple::host(),
&app_o_file,
match host_build_mode {
HostBuildMode::ObjectFile => None,
HostBuildMode::LLVMBitcode => Some(host_input_path.as_path()),
},
opt_level,
emit_debug_info,
);
dbg!("code gen done");
buf.push('\n');
buf.push_str(" ");
@ -158,38 +207,35 @@ pub fn build_file<'a>(
);
}
// Step 2: link the precompiled host and compiled app
let mut host_input_path = PathBuf::from(cwd);
host_input_path.push(&*path_to_platform);
host_input_path.push("host.o");
// TODO 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 rebuild_host_start = SystemTime::now();
rebuild_host(host_input_path.as_path());
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
if emit_debug_info {
println!(
"Finished rebuilding the host in {} ms\n",
rebuild_host_end.as_millis()
);
}
// Step 3: link them together
// 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 link_start = SystemTime::now();
let (mut child, binary_path) = // TODO use lld
link(
target,
binary_path,
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()],
link_type
)
.map_err(|_| {
let (mut child, binary_path) = {
// TODO use lld
let linked = match host_build_mode {
HostBuildMode::ObjectFile => link(
target,
binary_path,
&[
dbg!(host_input_path.as_path().to_str().unwrap()),
app_o_file.to_str().unwrap(),
],
link_type,
),
HostBuildMode::LLVMBitcode => link(
target,
binary_path,
&[app_o_file.to_str().unwrap()],
link_type,
),
};
linked.map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn.");
})?;
})?
};
let cmd_result = child.wait().map_err(|_| {
todo!("gracefully handle error after `rustc` spawned");

View File

@ -63,14 +63,18 @@ fn build_zig_host(
zig_host_src: &str,
zig_str_path: &str,
) -> Output {
// "zig" "build-obj" "/home/folkertdev/roc/roc/examples/quicksort/platform/host.zig" "-femit-bin=/home/folkertdev/roc/roc/examples/quicksort/platform/host.o" "--pkg-begin" "str" "compiler/builtins/bitcode/src/str.zig" "--pkg-end" "-fcompiler-rt" "--library" "c"
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/quicksort/platform/host.ll",
Command::new("zig")
.env_clear()
.env("PATH", env_path)
.env("HOME", env_home)
.args(&[
"build-obj",
"build-lib",
zig_host_src,
emit_bin,
"-O",
"ReleaseFast",
"--pkg-begin",
"str",
zig_str_path,
@ -158,7 +162,13 @@ fn build_zig_host(
.unwrap()
}
pub fn rebuild_host(host_input_path: &Path) {
#[derive(Clone, Copy)]
pub enum HostBuildMode {
ObjectFile,
LLVMBitcode,
}
pub fn rebuild_host(host_input_path: &Path, host_build_mode: HostBuildMode) {
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");
@ -172,7 +182,17 @@ pub fn rebuild_host(host_input_path: &Path) {
if zig_host_src.exists() {
// Compile host.zig
let emit_bin = format!("-femit-bin={}", host_dest.to_str().unwrap());
let zig_host_dest = match host_build_mode {
HostBuildMode::LLVMBitcode => host_input_path.with_file_name("host.ll"),
HostBuildMode::ObjectFile => host_input_path.with_file_name("host.o"),
};
let emit_bin = match host_build_mode {
HostBuildMode::LLVMBitcode => {
format!("-femit-llvm-ir={}", zig_host_dest.to_str().unwrap())
}
HostBuildMode::ObjectFile => format!("-femit-bin={}", zig_host_dest.to_str().unwrap()),
};
let zig_str_path = find_zig_str_path();
@ -193,6 +213,19 @@ pub fn rebuild_host(host_input_path: &Path) {
zig_str_path.to_str().unwrap(),
),
);
if let HostBuildMode::LLVMBitcode = host_build_mode {
let bc = host_input_path.with_file_name("host.bc");
let output = Command::new("llvm-as")
.env_clear()
.env("PATH", &env_path)
.args(&[zig_host_dest.to_str().unwrap(), "-o", bc.to_str().unwrap()])
.output()
.unwrap();
validate_output("host.zig", "llvm-as", output);
}
} else {
// Compile host.c
let output = Command::new("clang")

View File

@ -19,13 +19,14 @@ pub struct CodeGenTiming {
// TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::cognitive_complexity, clippy::clippy::too_many_arguments)]
pub fn gen_from_mono_module(
arena: &Bump,
mut loaded: MonomorphizedModule,
roc_file_path: &Path,
target: Triple,
app_o_file: &Path,
host_llvm_ir: Option<&Path>,
opt_level: OptLevel,
emit_debug_info: bool,
) -> CodeGenTiming {
@ -191,6 +192,25 @@ pub fn gen_from_mono_module(
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
if let Some(host_llvm_ir) = host_llvm_ir {
let host_module =
inkwell::module::Module::parse_bitcode_from_path(host_llvm_ir, &context).unwrap();
// host_module.link_in_module(module.clone()).unwrap();
module.link_in_module(host_module).unwrap();
// Verify the module
if let Err(errors) = env.module.verify() {
// write the ll code to a file, so we can modify it
env.module.print_to_file(&app_ll_file).unwrap();
panic!(
"😱 LLVM errors linking in the HOST MODULE; I wrote the full LLVM IR to {:?}\n\n {:?}",
app_ll_file, errors,
);
}
}
mpm.run_on(module);
// Verify the module