Merge branch 'trunk' into convert-bitcode-gen-to-c

This commit is contained in:
Richard Feldman 2020-10-25 14:17:17 -04:00 committed by GitHub
commit 21d4f8026e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 360 additions and 266 deletions

6
Cargo.lock generated
View file

@ -2217,6 +2217,7 @@ dependencies = [
"indoc", "indoc",
"inkwell", "inkwell",
"inlinable_string", "inlinable_string",
"libloading",
"maplit", "maplit",
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
@ -2238,7 +2239,7 @@ dependencies = [
"roc_unify", "roc_unify",
"roc_uniq", "roc_uniq",
"target-lexicon", "target-lexicon",
"tokio", "tempfile",
] ]
[[package]] [[package]]
@ -2291,6 +2292,7 @@ dependencies = [
"inkwell", "inkwell",
"inlinable_string", "inlinable_string",
"libc", "libc",
"libloading",
"maplit", "maplit",
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
@ -2425,10 +2427,12 @@ dependencies = [
"inkwell", "inkwell",
"inlinable_string", "inlinable_string",
"libc", "libc",
"libloading",
"maplit", "maplit",
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",
"roc_build",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",

View file

@ -58,6 +58,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] } tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
libc = "0.2" libc = "0.2"
libloading = "0.6"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
# #

View file

@ -1,5 +1,8 @@
use bumpalo::Bump; use bumpalo::Bump;
use roc_build::{link::link, program}; use roc_build::{
link::{link, rebuild_host, LinkType},
program,
};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
@ -19,8 +22,9 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
pub fn build_file( pub fn build_file(
target: &Triple, target: &Triple,
src_dir: PathBuf, src_dir: PathBuf,
filename: PathBuf, roc_file_path: PathBuf,
opt_level: OptLevel, opt_level: OptLevel,
link_type: LinkType,
) -> Result<PathBuf, LoadingProblem> { ) -> Result<PathBuf, LoadingProblem> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let arena = Bump::new(); let arena = Bump::new();
@ -35,12 +39,12 @@ pub fn build_file(
}; };
let loaded = roc_load::file::load_and_monomorphize( let loaded = roc_load::file::load_and_monomorphize(
&arena, &arena,
filename.clone(), roc_file_path.clone(),
stdlib, stdlib,
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
)?; )?;
let dest_filename = filename.with_file_name("roc_app.o"); let app_o_file = roc_file_path.with_file_name("roc_app.o");
let buf = &mut String::with_capacity(1024); let buf = &mut String::with_capacity(1024);
for (module_id, module_timing) in loaded.timings.iter() { for (module_id, module_timing) in loaded.timings.iter() {
@ -69,12 +73,14 @@ pub fn build_file(
program::gen_from_mono_module( program::gen_from_mono_module(
&arena, &arena,
loaded, loaded,
filename, roc_file_path,
Triple::host(), Triple::host(),
&dest_filename, &app_o_file,
opt_level, opt_level,
); );
println!("\nSuccess! 🎉\n\n\t{}\n", app_o_file.display());
let compilation_end = compilation_start.elapsed().unwrap(); let compilation_end = compilation_start.elapsed().unwrap();
println!( println!(
@ -82,33 +88,37 @@ pub fn build_file(
compilation_end.as_millis() compilation_end.as_millis()
); );
let cwd = dest_filename.parent().unwrap(); let cwd = app_o_file.parent().unwrap();
// Step 2: link the precompiled host and compiled app // Step 2: link the precompiled host and compiled app
let host_input_path = cwd.join("platform").join("host.o"); let host_input_path = cwd.join("platform").join("host.o");
let binary_path = cwd.join("app"); // TODO should be app.exe on Windows let binary_path = cwd.join("app"); // TODO should be app.exe on Windows
// 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.
rebuild_host(host_input_path.as_path());
// TODO try to move as much of this linking as possible to the precompiled // TODO try to move as much of this linking as possible to the precompiled
// host, to minimize the amount of host-application linking required. // host, to minimize the amount of host-application linking required.
let cmd_result = // TODO use lld let (mut child, binary_path) = // TODO use lld
link( link(
target, target,
binary_path.as_path(), binary_path,
host_input_path.as_path(), &[host_input_path.as_path().to_str().unwrap(), app_o_file.as_path().to_str().unwrap()],
dest_filename.as_path(), link_type
) )
.map_err(|_| { .map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn."); todo!("gracefully handle `rustc` failing to spawn.");
})? })?;
.wait()
.map_err(|_| { let cmd_result = child.wait().map_err(|_| {
todo!("gracefully handle error after `rustc` spawned"); todo!("gracefully handle error after `rustc` spawned");
}); });
// Clean up the leftover .o file from the Roc, if possible. // Clean up the leftover .o file from the Roc, if possible.
// (If cleaning it up fails, that's fine. No need to take action.) // (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. // TODO compile the app_o_file to a tmpdir, as an extra precaution.
let _ = fs::remove_file(dest_filename); let _ = fs::remove_file(app_o_file);
// If the cmd errored out, return the Err. // If the cmd errored out, return the Err.
cmd_result?; cmd_result?;

View file

@ -3,6 +3,7 @@ extern crate clap;
use clap::ArgMatches; use clap::ArgMatches;
use clap::{App, Arg}; use clap::{App, Arg};
use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
@ -91,7 +92,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
} }
}); });
let binary_path = build::build_file(target, src_dir, path, opt_level) let binary_path = build::build_file(target, src_dir, path, opt_level, LinkType::Executable)
.expect("TODO gracefully handle build_file failing"); .expect("TODO gracefully handle build_file failing");
if run_after_build { if run_after_build {

View file

@ -1,7 +1,6 @@
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine; use roc_build::link::module_to_dylib;
use inkwell::OptimizationLevel;
use roc_builtins::unique::uniq_stdlib; use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::expected::Expected; use roc_can::expected::Expected;
@ -284,14 +283,6 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
let (module_pass, function_pass) = let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level); roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
// Without calling this, we get a linker error when building this crate
// in --release mode and then trying to eval anything in the repl.
ExecutionEngine::link_in_mc_jit();
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let env = roc_gen::llvm::build::Env { let env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
@ -386,10 +377,12 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
// Uncomment this to see the module's optimized LLVM instruction output: // Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr(); // env.module.print_to_stderr();
let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
let answer = unsafe { let answer = unsafe {
eval::jit_to_ast( eval::jit_to_ast(
&arena, &arena,
execution_engine, lib,
main_fn_name, main_fn_name,
&main_fn_layout, &main_fn_layout,
&content, &content,

View file

@ -1,6 +1,6 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::execution_engine::ExecutionEngine; use libloading::Library;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_gen::{run_jit_function, run_jit_function_dynamic_type}; use roc_gen::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
@ -31,7 +31,7 @@ struct Env<'a, 'env> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub unsafe fn jit_to_ast<'a>( pub unsafe fn jit_to_ast<'a>(
arena: &'a Bump, arena: &'a Bump,
execution_engine: ExecutionEngine, lib: Library,
main_fn_name: &str, main_fn_name: &str,
layout: &Layout<'a>, layout: &Layout<'a>,
content: &Content, content: &Content,
@ -48,42 +48,43 @@ pub unsafe fn jit_to_ast<'a>(
interns, interns,
}; };
jit_to_ast_help(&env, execution_engine, main_fn_name, layout, content) jit_to_ast_help(&env, lib, main_fn_name, layout, content)
} }
fn jit_to_ast_help<'a>( fn jit_to_ast_help<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
execution_engine: ExecutionEngine, lib: Library,
main_fn_name: &str, main_fn_name: &str,
layout: &Layout<'a>, layout: &Layout<'a>,
content: &Content, content: &Content,
) -> Expr<'a> { ) -> Expr<'a> {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => run_jit_function!( Layout::Builtin(Builtin::Int64) => {
execution_engine, run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
main_fn_name, env,
i64, i64_to_ast(env.arena, num),
|num| num_to_ast(env, i64_to_ast(env.arena, num), content) content
), ))
Layout::Builtin(Builtin::Float64) => run_jit_function!( }
execution_engine, Layout::Builtin(Builtin::Float64) => {
main_fn_name, run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast(
f64, env,
|num| num_to_ast(env, f64_to_ast(env.arena, num), content) f64_to_ast(env.arena, num),
), content
Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => run_jit_function!( ))
execution_engine, }
main_fn_name, Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => {
&'static str, run_jit_function!(lib, main_fn_name, &'static str, |string: &'static str| {
|string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) } str_to_ast(env.arena, env.arena.alloc(string))
), })
}
Layout::Builtin(Builtin::EmptyList) => { Layout::Builtin(Builtin::EmptyList) => {
run_jit_function!(execution_engine, main_fn_name, &'static str, |_| { run_jit_function!(lib, main_fn_name, &'static str, |_| {
Expr::List(Vec::new_in(env.arena)) Expr::List(Vec::new_in(env.arena))
}) })
} }
Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!( Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
execution_engine, lib,
main_fn_name, main_fn_name,
(*const libc::c_void, usize), (*const libc::c_void, usize),
|(ptr, len): (*const libc::c_void, usize)| { |(ptr, len): (*const libc::c_void, usize)| {
@ -111,31 +112,21 @@ fn jit_to_ast_help<'a>(
8 => match layout.stack_size(env.ptr_bytes) { 8 => match layout.stack_size(env.ptr_bytes) {
8 => { 8 => {
// just one eightbyte, returned as-is // just one eightbyte, returned as-is
run_jit_function!( run_jit_function!(lib, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
execution_engine,
main_fn_name,
[u8; 8],
|bytes: [u8; 8]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
} })
)
} }
16 => { 16 => {
// two eightbytes, returned as-is // two eightbytes, returned as-is
run_jit_function!( run_jit_function!(lib, main_fn_name, [u8; 16], |bytes: [u8; 16]| {
execution_engine,
main_fn_name,
[u8; 16],
|bytes: [u8; 16]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
} })
)
} }
larger_size => { larger_size => {
// anything more than 2 eightbytes // anything more than 2 eightbytes
// the return "value" is a pointer to the result // the return "value" is a pointer to the result
run_jit_function_dynamic_type!( run_jit_function_dynamic_type!(
execution_engine, lib,
main_fn_name, main_fn_name,
larger_size as usize, larger_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } |bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }
@ -150,31 +141,21 @@ fn jit_to_ast_help<'a>(
match layout.stack_size(env.ptr_bytes) { match layout.stack_size(env.ptr_bytes) {
4 => { 4 => {
// just one fourbyte, returned as-is // just one fourbyte, returned as-is
run_jit_function!( run_jit_function!(lib, main_fn_name, [u8; 4], |bytes: [u8; 4]| {
execution_engine,
main_fn_name,
[u8; 4],
|bytes: [u8; 4]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
} })
)
} }
8 => { 8 => {
// just one fourbyte, returned as-is // just one fourbyte, returned as-is
run_jit_function!( run_jit_function!(lib, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
execution_engine,
main_fn_name,
[u8; 8],
|bytes: [u8; 8]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
} })
)
} }
larger_size => { larger_size => {
// anything more than 2 fourbytes // anything more than 2 fourbytes
// the return "value" is a pointer to the result // the return "value" is a pointer to the result
run_jit_function_dynamic_type!( run_jit_function_dynamic_type!(
execution_engine, lib,
main_fn_name, main_fn_name,
larger_size as usize, larger_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } |bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }

View file

@ -26,7 +26,8 @@ im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0" inlinable_string = "0.1.0"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] } libloading = "0.6"
tempfile = "3.1.0"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
# #
# The reason for this fork is that the way Inkwell is designed, you have to use # The reason for this fork is that the way Inkwell is designed, you have to use

View file

@ -1,35 +1,44 @@
use crate::target;
use crate::target::arch_str; use crate::target::arch_str;
use inkwell::module::Module;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use libloading::{Error, Library};
use roc_gen::llvm::build::OptLevel;
use std::io; use std::io;
use std::path::Path; use std::path::{Path, PathBuf};
use std::process::{Child, Command}; use std::process::{Child, Command};
use target_lexicon::{Architecture, OperatingSystem, Triple}; use target_lexicon::{Architecture, OperatingSystem, Triple};
use tempfile::tempdir;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType {
Executable,
Dylib,
}
/// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"]
pub fn link( pub fn link(
target: &Triple, target: &Triple,
binary_path: &Path, output_path: PathBuf,
host_input_path: &Path, input_paths: &[&str],
dest_filename: &Path, link_type: LinkType,
) -> io::Result<Child> { ) -> io::Result<(Child, PathBuf)> {
// 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.
rebuild_host(host_input_path);
match target { match target {
Triple { Triple {
architecture: Architecture::X86_64, architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux, operating_system: OperatingSystem::Linux,
.. ..
} => link_linux(target, binary_path, host_input_path, dest_filename), } => link_linux(target, output_path, input_paths, link_type),
Triple { Triple {
architecture: Architecture::X86_64, architecture: Architecture::X86_64,
operating_system: OperatingSystem::Darwin, operating_system: OperatingSystem::Darwin,
.. ..
} => link_macos(target, binary_path, host_input_path, dest_filename), } => link_macos(target, output_path, input_paths, link_type),
_ => panic!("TODO gracefully handle unsupported target: {:?}", target), _ => panic!("TODO gracefully handle unsupported target: {:?}", target),
} }
} }
fn rebuild_host(host_input_path: &Path) { pub fn rebuild_host(host_input_path: &Path) {
let c_host_src = host_input_path.with_file_name("host.c"); 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 c_host_dest = host_input_path.with_file_name("c_host.o");
let rust_host_src = host_input_path.with_file_name("host.rs"); let rust_host_src = host_input_path.with_file_name("host.rs");
@ -118,15 +127,16 @@ fn rebuild_host(host_input_path: &Path) {
fn link_linux( fn link_linux(
target: &Triple, target: &Triple,
binary_path: &Path, output_path: PathBuf,
host_input_path: &Path, input_paths: &[&str],
dest_filename: &Path, link_type: LinkType,
) -> io::Result<Child> { ) -> io::Result<(Child, PathBuf)> {
let libcrt_path = if Path::new("/usr/lib/x86_64-linux-gnu").exists() { let libcrt_path = if Path::new("/usr/lib/x86_64-linux-gnu").exists() {
Path::new("/usr/lib/x86_64-linux-gnu") Path::new("/usr/lib/x86_64-linux-gnu")
} else { } else {
Path::new("/usr/lib") Path::new("/usr/lib")
}; };
let libgcc_path = if Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() { let libgcc_path = if Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1") Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1")
} else if Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() { } else if Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
@ -134,8 +144,45 @@ fn link_linux(
} else { } else {
Path::new("/usr/lib/libgcc_s.so.1") Path::new("/usr/lib/libgcc_s.so.1")
}; };
let mut soname;
let (base_args, output_path) = match link_type {
LinkType::Executable => (
// Presumably this S stands for Static, since if we include Scrt1.o
// in the linking for dynamic builds, linking fails.
vec![libcrt_path.join("Scrt1.o").to_str().unwrap().to_string()],
output_path,
),
LinkType::Dylib => {
// TODO: do we acually need the version number on this?
// Do we even need the "-soname" argument?
//
// See https://software.intel.com/content/www/us/en/develop/articles/create-a-unix-including-linux-shared-library.html
soname = output_path.clone();
soname.set_extension("so.1");
let mut output_path = output_path;
output_path.set_extension("so.1.0");
(
// TODO: find a way to avoid using a vec! here - should theoretically be
// able to do this somehow using &[] but the borrow checker isn't having it.
// Also find a way to have these be string slices instead of Strings.
vec![
"-shared".to_string(),
"-soname".to_string(),
soname.as_path().to_str().unwrap().to_string(),
],
output_path,
)
}
};
// NOTE: order of arguments to `ld` matters here! // NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments // The `-l` flags should go after the `.o` arguments
Ok((
Command::new("ld") Command::new("ld")
// Don't allow LD_ env vars to affect this // Don't allow LD_ env vars to affect this
.env_clear() .env_clear()
@ -144,12 +191,11 @@ fn link_linux(
arch_str(target), arch_str(target),
libcrt_path.join("crti.o").to_str().unwrap(), libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(), libcrt_path.join("crtn.o").to_str().unwrap(),
libcrt_path.join("Scrt1.o").to_str().unwrap(), ])
"-dynamic-linker", .args(&base_args)
"/lib64/ld-linux-x86-64.so.2", .args(&["-dynamic-linker", "/lib64/ld-linux-x86-64.so.2"])
// Inputs .args(input_paths)
host_input_path.to_str().unwrap(), // host.o .args(&[
dest_filename.to_str().unwrap(), // app.o
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925 // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
// for discussion and further references // for discussion and further references
"-lc", "-lc",
@ -164,28 +210,43 @@ fn link_linux(
libgcc_path.to_str().unwrap(), libgcc_path.to_str().unwrap(),
// Output // Output
"-o", "-o",
binary_path.to_str().unwrap(), // app output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
]) ])
.spawn() .spawn()?,
output_path,
))
} }
fn link_macos( fn link_macos(
target: &Triple, target: &Triple,
binary_path: &Path, output_path: PathBuf,
host_input_path: &Path, input_paths: &[&str],
dest_filename: &Path, link_type: LinkType,
) -> io::Result<Child> { ) -> io::Result<(Child, PathBuf)> {
let (link_type_arg, output_path) = match link_type {
LinkType::Executable => ("-execute", output_path),
LinkType::Dylib => {
let mut output_path = output_path;
output_path.set_extension("dylib");
("-dylib", output_path)
}
};
Ok((
// NOTE: order of arguments to `ld` matters here! // NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments // The `-l` flags should go after the `.o` arguments
Command::new("ld") Command::new("ld")
// Don't allow LD_ env vars to affect this // Don't allow LD_ env vars to affect this
.env_clear() .env_clear()
.args(&[ .args(&[
link_type_arg,
"-arch", "-arch",
target.architecture.to_string().as_str(), target.architecture.to_string().as_str(),
// Inputs ])
host_input_path.to_str().unwrap(), // host.o .args(input_paths)
dest_filename.to_str().unwrap(), // roc_app.o .args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274 // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references // for discussion and further references
"-lSystem", "-lSystem",
@ -198,7 +259,47 @@ fn link_macos(
"-lc++", // TODO shouldn't we need this? "-lc++", // TODO shouldn't we need this?
// Output // Output
"-o", "-o",
binary_path.to_str().unwrap(), // app output_path.to_str().unwrap(), // app
]) ])
.spawn() .spawn()?,
output_path,
))
}
pub fn module_to_dylib(
module: &Module,
target: &Triple,
opt_level: OptLevel,
) -> Result<Library, Error> {
let dir = tempdir().unwrap();
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let mut app_o_file = file_path;
app_o_file.set_file_name("app.o");
// Emit the .o file using position-indepedent code (PIC) - needed for dylibs
let reloc = RelocMode::PIC;
let model = CodeModel::Default;
let target_machine = target::target_machine(target, opt_level.into(), reloc, model).unwrap();
target_machine
.write_to_file(module, FileType::Object, &app_o_file)
.expect("Writing .o file failed");
// Link app.o into a dylib - e.g. app.so or app.dylib
let (mut child, dylib_path) = link(
&Triple::host(),
app_o_file.clone(),
&[app_o_file.to_str().unwrap()],
LinkType::Dylib,
)
.unwrap();
child.wait().unwrap();
// Load the dylib
let path = dylib_path.as_path().to_str().unwrap();
Library::new(path)
} }

View file

@ -2,7 +2,6 @@ use crate::target;
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::targets::{CodeModel, FileType, RelocMode}; use inkwell::targets::{CodeModel, FileType, RelocMode};
use inkwell::OptimizationLevel;
use roc_gen::layout_id::LayoutIds; use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope}; use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
use roc_load::file::MonomorphizedModule; use roc_load::file::MonomorphizedModule;
@ -16,9 +15,9 @@ use target_lexicon::Triple;
pub fn gen_from_mono_module( pub fn gen_from_mono_module(
arena: &Bump, arena: &Bump,
loaded: MonomorphizedModule, loaded: MonomorphizedModule,
filename: PathBuf, file_path: PathBuf,
target: Triple, target: Triple,
dest_filename: &Path, app_o_file: &Path,
opt_level: OptLevel, opt_level: OptLevel,
) { ) {
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
@ -32,7 +31,7 @@ pub fn gen_from_mono_module(
let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns); let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns);
for problem in loaded.can_problems.into_iter() { for problem in loaded.can_problems.into_iter() {
let report = can_problem(&alloc, filename.clone(), problem); let report = can_problem(&alloc, file_path.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
@ -41,7 +40,7 @@ pub fn gen_from_mono_module(
} }
for problem in loaded.type_problems.into_iter() { for problem in loaded.type_problems.into_iter() {
let report = type_problem(&alloc, filename.clone(), problem); let report = type_problem(&alloc, file_path.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
@ -126,14 +125,11 @@ pub fn gen_from_mono_module(
// Emit the .o file // Emit the .o file
let opt = OptimizationLevel::Aggressive;
let reloc = RelocMode::Default; let reloc = RelocMode::Default;
let model = CodeModel::Default; let model = CodeModel::Default;
let target_machine = target::target_machine(&target, opt, reloc, model).unwrap(); let target_machine = target::target_machine(&target, opt_level.into(), reloc, model).unwrap();
target_machine target_machine
.write_to_file(&env.module, FileType::Object, &dest_filename) .write_to_file(&env.module, FileType::Object, &app_o_file)
.expect("Writing .o file failed"); .expect("Writing .o file failed");
println!("\nSuccess! 🎉\n\n\t{}\n", dest_filename.display());
} }

View file

@ -1,3 +1,4 @@
use crate::builtins;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration}; use crate::def::{canonicalize_defs, sort_can_defs, Declaration};
use crate::env::Env; use crate::env::Env;
use crate::expr::Output; use crate::expr::Output;
@ -259,6 +260,15 @@ pub fn canonicalize_module_defs<'a>(
} }
} }
// Add builtin defs (e.g. List.get) to the module's defs
let builtin_defs = builtins::builtin_defs(var_store);
for (symbol, def) in builtin_defs {
if references.contains(&symbol) {
declarations.push(Declaration::Builtin(def));
}
}
Ok(ModuleOutput { Ok(ModuleOutput {
aliases, aliases,
rigid_variables, rigid_variables,

View file

@ -40,12 +40,15 @@ inlinable_string = "0.1"
# This way, GitHub Actions works and nobody's builds get broken. # This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" } inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
target-lexicon = "0.10" target-lexicon = "0.10"
libloading = "0.6"
[dev-dependencies] [dev-dependencies]
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_load = { path = "../load" } roc_load = { path = "../load" }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
@ -54,4 +57,3 @@ quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2" libc = "0.2"
roc_std = { path = "../../roc_std" }

View file

@ -50,6 +50,15 @@ pub enum OptLevel {
Optimize, Optimize,
} }
impl Into<OptimizationLevel> for OptLevel {
fn into(self) -> OptimizationLevel {
match self {
OptLevel::Normal => OptimizationLevel::None,
OptLevel::Optimize => OptimizationLevel::Aggressive,
}
}
}
#[derive(Default, Debug, Clone, PartialEq)] #[derive(Default, Debug, Clone, PartialEq)]
pub struct Scope<'a, 'ctx> { pub struct Scope<'a, 'ctx> {
symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>, symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>,

View file

@ -28,24 +28,23 @@ impl<T: Sized> Into<Result<T, String>> for RocCallResult<T> {
#[macro_export] #[macro_export]
macro_rules! run_jit_function { macro_rules! run_jit_function {
($execution_engine: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{
let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new(); let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new();
run_jit_function!($execution_engine, $main_fn_name, $ty, $transform, v) run_jit_function!($lib, $main_fn_name, $ty, $transform, v)
}}; }};
($execution_engine: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use roc_gen::run_roc::RocCallResult; use roc_gen::run_roc::RocCallResult;
unsafe { unsafe {
let main: JitFunction<unsafe extern "C" fn() -> RocCallResult<$ty>> = $execution_engine let main: libloading::Symbol<unsafe extern "C" fn() -> RocCallResult<$ty>> = $lib
.get_function($main_fn_name) .get($main_fn_name.as_bytes())
.ok() .ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored"); .expect("errored");
match main.call().into() { match main().into() {
Ok(success) => { Ok(success) => {
// only if there are no exceptions thrown, check for errors // only if there are no exceptions thrown, check for errors
assert_eq!( assert_eq!(
@ -68,26 +67,25 @@ macro_rules! run_jit_function {
/// It explicitly allocates a buffer that the roc main function can write its result into. /// It explicitly allocates a buffer that the roc main function can write its result into.
#[macro_export] #[macro_export]
macro_rules! run_jit_function_dynamic_type { macro_rules! run_jit_function_dynamic_type {
($execution_engine: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{ ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{
let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new(); let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new();
run_jit_function_dynamic_type!($execution_engine, $main_fn_name, $bytes, $transform, v) run_jit_function_dynamic_type!($lib, $main_fn_name, $bytes, $transform, v)
}}; }};
($execution_engine: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{ ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use roc_gen::run_roc::RocCallResult; use roc_gen::run_roc::RocCallResult;
unsafe { unsafe {
let main: JitFunction<unsafe extern "C" fn(*const u8)> = $execution_engine let main: libloading::Symbol<unsafe extern "C" fn(*const u8)> = $lib
.get_function($main_fn_name) .get($main_fn_name.as_bytes())
.ok() .ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored"); .expect("errored");
let layout = std::alloc::Layout::array::<u8>($bytes).unwrap(); let layout = std::alloc::Layout::array::<u8>($bytes).unwrap();
let result = std::alloc::alloc(layout); let result = std::alloc::alloc(layout);
main.call(result); main(result);
let flag = *result; let flag = *result;

View file

@ -691,19 +691,19 @@ mod gen_num {
assert_evals_to!("Num.atan 10", 1.4711276743037345, f64); assert_evals_to!("Num.atan 10", 1.4711276743037345, f64);
} }
#[test] // #[test]
#[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)] // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
fn int_overflow() { // fn int_overflow() {
assert_evals_to!( // assert_evals_to!(
indoc!( // indoc!(
r#" // r#"
9_223_372_036_854_775_807 + 1 // 9_223_372_036_854_775_807 + 1
"# // "#
), // ),
0, // 0,
i64 // i64
); // );
} // }
#[test] #[test]
fn int_add_checked() { fn int_add_checked() {
@ -775,17 +775,17 @@ mod gen_num {
); );
} }
#[test] // #[test]
#[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)] // #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
fn float_overflow() { // fn float_overflow() {
assert_evals_to!( // assert_evals_to!(
indoc!( // indoc!(
r#" // r#"
1.7976931348623157e308 + 1.7976931348623157e308 // 1.7976931348623157e308 + 1.7976931348623157e308
"# // "#
), // ),
0.0, // 0.0,
f64 // f64
); // );
} // }
} }

View file

@ -884,22 +884,22 @@ mod gen_primitives {
); );
} }
#[test] // #[test]
#[should_panic(expected = "Roc failed with message: ")] // #[should_panic(expected = "Roc failed with message: ")]
fn exception() { // fn exception() {
assert_evals_to!( // assert_evals_to!(
indoc!( // indoc!(
r#" // r#"
if True then // if True then
x + z // x + z
else // else
y + z // y + z
"# // "#
), // ),
3, // 3,
i64 // i64
); // );
} // }
#[test] #[test]
fn closure() { fn closure() {

View file

@ -1,3 +1,5 @@
use libloading::Library;
use roc_build::link::module_to_dylib;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
@ -19,12 +21,7 @@ pub fn helper<'a>(
stdlib: roc_builtins::std::StdLib, stdlib: roc_builtins::std::StdLib,
leak: bool, leak: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> ( ) -> (&'static str, Vec<roc_problem::can::Problem>, Library) {
&'static str,
Vec<roc_problem::can::Problem>,
inkwell::execution_engine::ExecutionEngine<'a>,
) {
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope}; use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -166,10 +163,6 @@ pub fn helper<'a>(
let (module_pass, function_pass) = let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level); roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let env = roc_gen::llvm::build::Env { let env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
@ -265,7 +258,10 @@ pub fn helper<'a>(
// Uncomment this to see the module's optimized LLVM instruction output: // Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr(); // env.module.print_to_stderr();
(main_fn_name, errors, execution_engine.clone()) let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
(main_fn_name, errors, lib)
} }
// TODO this is almost all code duplication with assert_llvm_evals_to // TODO this is almost all code duplication with assert_llvm_evals_to
@ -284,7 +280,7 @@ macro_rules! assert_opt_evals_to {
let stdlib = roc_builtins::unique::uniq_stdlib(); let stdlib = roc_builtins::unique::uniq_stdlib();
let (main_fn_name, errors, execution_engine) = let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context); $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| { let transform = |success| {
@ -292,7 +288,7 @@ macro_rules! assert_opt_evals_to {
let given = $transform(success); let given = $transform(success);
assert_eq!(&given, &expected); assert_eq!(&given, &expected);
}; };
run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors) run_jit_function!(lib, main_fn_name, $ty, transform, errors)
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
@ -312,7 +308,7 @@ macro_rules! assert_llvm_evals_to {
let context = Context::create(); let context = Context::create();
let stdlib = roc_builtins::std::standard_stdlib(); let stdlib = roc_builtins::std::standard_stdlib();
let (main_fn_name, errors, execution_engine) = let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context); $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| { let transform = |success| {
@ -320,7 +316,7 @@ macro_rules! assert_llvm_evals_to {
let given = $transform(success); let given = $transform(success);
assert_eq!(&given, &expected); assert_eq!(&given, &expected);
}; };
run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors) run_jit_function!(lib, main_fn_name, $ty, transform, errors)
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {

View file

@ -2020,7 +2020,8 @@ fn parse_and_constrain<'a>(
let module_id = header.module_id; let module_id = header.module_id;
// Generate documentation information // Generate documentation information
// TODO: store timing information? // TODO: store timing information
// TODO: only run this if we're doing a doc gen pass!
let module_docs = crate::docs::generate_module_docs( let module_docs = crate::docs::generate_module_docs(
header.module_name, header.module_name,
&header.exposed_ident_ids, &header.exposed_ident_ids,
@ -2041,17 +2042,7 @@ fn parse_and_constrain<'a>(
); );
let canonicalize_end = SystemTime::now(); let canonicalize_end = SystemTime::now();
let (module, declarations, ident_ids, constraint, problems) = match canonicalized { let (module, declarations, ident_ids, constraint, problems) = match canonicalized {
Ok(mut module_output) => { Ok(module_output) => {
// Add builtin defs (e.g. List.get) to the module's defs
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
let references = &module_output.references;
for (symbol, def) in builtin_defs {
if references.contains(&symbol) {
module_output.declarations.push(Declaration::Builtin(def));
}
}
let constraint = constrain_module(&module_output, module_id, mode, &mut var_store); let constraint = constrain_module(&module_output, module_id, mode, &mut var_store);
// Now that we're done with parsing, canonicalization, and constraint gen, // Now that we're done with parsing, canonicalization, and constraint gen,

View file

@ -25,7 +25,7 @@ roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
tempfile = "3.0.1" tempfile = "3.1.0"
quickcheck = "0.8" quickcheck = "0.8"
quickcheck_macros = "0.8" quickcheck_macros = "0.8"
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }