Merge remote-tracking branch 'origin/trunk' into fix-alias-inference

This commit is contained in:
Folkert 2020-10-29 14:40:55 +01:00
commit a0c8915678
44 changed files with 822 additions and 736 deletions

View File

@ -55,10 +55,16 @@ jobs:
name: cargo test
with:
command: test
args: --no-fail-fast
- uses: actions-rs/cargo@v1
name: cargo test -- --ignored
continue-on-error: true
with:
command: test
args: -- --ignored
- uses: actions-rs/cargo@v1
name: cargo test --release
with:
command: test
args: --release --no-fail-fast
args: --release

View File

@ -1,21 +1,37 @@
# Building the Roc compiler from source
## Installing LLVM, valgrind, libunwind, and libc++-dev
## Installing LLVM, Zig, valgrind, libunwind, and libc++-dev
To build the compiler, you need these installed:
* `libunwind` (macOS should already have this one installed)
* `libc++-dev`
* a particular version of LLVM
* a particular version of Zig (see below)
* a particular version of LLVM (see below)
To run the test suite (via `cargo test`), you additionally need to install:
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)]
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) macOS systems
should already have `libunwind`, but other systems will need to install it
(e.g. with `sudo apt-get install libunwind-dev`).
### libunwind & libc++-dev
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be donw with `sudo apt-get install libunwind-dev`).
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.)
### Zig
We use a specific version of Zig, a build off the the commit `0088efc4b`. The latest tagged version of Zig, 0.6.0, doesn't include the feature to emit LLVM ir, which is a core feature of how we use Zig. To download this specific version, you can use the following links:
* [linux](https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz)
* [macOS](https://ziglang.org/builds/zig-macos-x86_64-0.6.0+0088efc4b.tar.xz)
Alternatively, any recent master branch build should work. To install the latest master branch build you can use:
* `brew install zig --HEAD` (on macos)
* `snap install zig --classic --edge` (on ubunutu)
Once 0.7.0 is released, we'll switch back to installing the tagged releases and this process will get easier.
### LLVM
To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need.

33
Cargo.lock generated
View File

@ -2217,6 +2217,7 @@ dependencies = [
"indoc",
"inkwell",
"inlinable_string",
"libloading",
"maplit",
"pretty_assertions",
"quickcheck",
@ -2238,7 +2239,7 @@ dependencies = [
"roc_unify",
"roc_uniq",
"target-lexicon",
"tokio",
"tempfile",
]
[[package]]
@ -2257,10 +2258,6 @@ dependencies = [
"roc_types",
]
[[package]]
name = "roc_builtins_bitcode"
version = "0.1.0"
[[package]]
name = "roc_can"
version = "0.1.0"
@ -2295,6 +2292,7 @@ dependencies = [
"inkwell",
"inlinable_string",
"libc",
"libloading",
"maplit",
"pretty_assertions",
"quickcheck",
@ -2320,6 +2318,7 @@ dependencies = [
"roc_uniq",
"serde",
"serde-xml-rs",
"serial_test",
"strip-ansi-escapes",
"target-lexicon",
"tempfile",
@ -2429,10 +2428,12 @@ dependencies = [
"inkwell",
"inlinable_string",
"libc",
"libloading",
"maplit",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
@ -2804,6 +2805,28 @@ dependencies = [
"serde",
]
[[package]]
name = "serial_test"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b15f74add9a9d4a3eb2bf739c9a427d266d3895b53d992c3a7c234fec2ff1f1"
dependencies = [
"lazy_static",
"parking_lot 0.10.2",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65f59259be9fc1bf677d06cc1456e97756004a1a5a577480f71430bd7c17ba33"
dependencies = [
"proc-macro2 1.0.21",
"quote 1.0.7",
"syn 1.0.40",
]
[[package]]
name = "sha-1"
version = "0.8.2"

View File

@ -10,7 +10,6 @@ members = [
"compiler/types",
"compiler/uniq",
"compiler/builtins",
"compiler/builtins/bitcode",
"compiler/constrain",
"compiler/unify",
"compiler/solve",

View File

@ -60,3 +60,8 @@ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
add-apt-repository "${REPO_NAME}"
apt-get update
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev valgrind
# install zig - can't use apt-get since we require at least a specific commit later then the most recent tag (0.6.0)
wget -c https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz --no-check-certificate
tar -xf zig-linux-x86_64-0.6.0+0088efc4b.tar.xz
ln -s "$PWD/zig-linux-x86_64-0.6.0+0088efc4b/zig" /usr/local/bin/zig

View File

@ -58,6 +58,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
libc = "0.2"
libloading = "0.6"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
@ -88,4 +89,5 @@ quickcheck_macros = "0.8"
strip-ansi-escapes = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"
serial_test = "0.5"
tempfile = "3.1.0"

View File

@ -1,5 +1,8 @@
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_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
@ -19,8 +22,9 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
pub fn build_file(
target: &Triple,
src_dir: PathBuf,
filename: PathBuf,
roc_file_path: PathBuf,
opt_level: OptLevel,
link_type: LinkType,
) -> Result<PathBuf, LoadingProblem> {
let compilation_start = SystemTime::now();
let arena = Bump::new();
@ -35,12 +39,12 @@ pub fn build_file(
};
let loaded = roc_load::file::load_and_monomorphize(
&arena,
filename.clone(),
roc_file_path.clone(),
stdlib,
src_dir.as_path(),
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);
for (module_id, module_timing) in loaded.timings.iter() {
@ -69,12 +73,14 @@ pub fn build_file(
program::gen_from_mono_module(
&arena,
loaded,
filename,
roc_file_path,
Triple::host(),
&dest_filename,
&app_o_file,
opt_level,
);
println!("\nSuccess! 🎉\n\n\t{}\n", app_o_file.display());
let compilation_end = compilation_start.elapsed().unwrap();
println!(
@ -82,33 +88,37 @@ pub fn build_file(
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
let host_input_path = cwd.join("platform").join("host.o");
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
// 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(
target,
binary_path.as_path(),
host_input_path.as_path(),
dest_filename.as_path(),
binary_path,
&[host_input_path.as_path().to_str().unwrap(), app_o_file.as_path().to_str().unwrap()],
link_type
)
.map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn.");
})?
.wait()
.map_err(|_| {
todo!("gracefully handle error after `rustc` spawned");
});
})?;
let cmd_result = child.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);
// TODO compile the app_o_file to a tmpdir, as an extra precaution.
let _ = fs::remove_file(app_o_file);
// If the cmd errored out, return the Err.
cmd_result?;

View File

@ -3,6 +3,7 @@ extern crate clap;
use clap::ArgMatches;
use clap::{App, Arg};
use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel;
use std::io;
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");
if run_after_build {

View File

@ -1,7 +1,6 @@
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::OptimizationLevel;
use roc_build::link::module_to_dylib;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint;
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) =
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
let env = roc_gen::llvm::build::Env {
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:
// 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 {
eval::jit_to_ast(
&arena,
execution_engine,
lib,
main_fn_name,
&main_fn_layout,
&content,

View File

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

View File

@ -11,120 +11,175 @@ mod helpers;
#[cfg(test)]
mod cli_run {
use crate::helpers::{example_file, extract_valgrind_errors, run_roc, run_with_valgrind, Out};
use crate::helpers::{
example_file, extract_valgrind_errors, run_cmd, run_roc, run_with_valgrind,
};
use serial_test::serial;
fn check_output(
folder: &str,
file: &str,
flags: &[&str],
expected_ending: &str,
use_valgrind: bool,
) {
let compile_out = run_roc(
&[
&["build", example_file(folder, file).to_str().unwrap()],
flags,
]
.concat(),
);
if !compile_out.stderr.is_empty() {
panic!(compile_out.stderr);
}
assert!(compile_out.status.success());
let out = if use_valgrind {
let (valgrind_out, raw_xml) =
run_with_valgrind(&[example_file(folder, "app").to_str().unwrap()]);
let memory_errors = extract_valgrind_errors(&raw_xml);
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
}
valgrind_out
} else {
run_cmd(example_file(folder, "app").to_str().unwrap(), &[])
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?}",
expected_ending, out
);
}
assert!(out.status.success());
}
#[test]
#[serial(hello_world)]
fn run_hello_world() {
fn check_hello_world_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(out.status.success());
let (valgrind_out, raw_xml) =
run_with_valgrind(&[example_file("hello-world", "app").to_str().unwrap()]);
let ending = "Hello, World!!!!!!!!!!!!!\n";
if !&valgrind_out.stdout.ends_with(ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
);
}
let memory_errors = extract_valgrind_errors(&raw_xml);
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
}
assert!(valgrind_out.status.success());
}
check_hello_world_output(run_roc(&[
"build",
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]));
check_hello_world_output(run_roc(&[
"build",
"--optimize",
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]));
check_output(
"hello-world",
"Hello.roc",
&[],
"Hello, World!!!!!!!!!!!!!\n",
true,
);
}
#[test]
#[serial(hello_world)]
fn run_hello_world_optimized() {
check_output(
"hello-world",
"Hello.roc",
&[],
"Hello, World!!!!!!!!!!!!!\n",
true,
);
}
#[test]
#[serial(quicksort)]
fn run_quicksort() {
fn check_quicksort_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(out.status.success());
let (valgrind_out, raw_xml) =
run_with_valgrind(&[example_file("quicksort", "app").to_str().unwrap()]);
let ending = "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n";
if !&valgrind_out.stdout.ends_with(ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
);
}
let memory_errors = extract_valgrind_errors(&raw_xml);
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
}
assert!(valgrind_out.status.success());
}
// TODO: Uncomment this once we are correctly freeing the RocList even when in dev build.
/*
check_quicksort_output(run_roc(&[
"build",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
]));
*/
check_quicksort_output(run_roc(&[
"build",
"--optimize",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
]));
check_output(
"quicksort",
"Quicksort.roc",
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
);
}
#[test]
#[serial(quicksort)]
fn run_quicksort_optimized() {
check_output(
"quicksort",
"Quicksort.roc",
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
);
}
#[test]
#[serial(quicksort)]
// TODO: Stop ignoring this test once we are correctly freeing the RocList even when in dev build.
#[ignore]
fn run_quicksort_valgrind() {
check_output(
"quicksort",
"Quicksort.roc",
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
);
}
#[test]
#[serial(quicksort)]
// TODO: Stop ignoring this test once valgrind supports AVX512.
#[ignore]
fn run_quicksort_optimized_valgrind() {
check_output(
"quicksort",
"Quicksort.roc",
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
);
}
#[test]
#[serial(multi_module)]
fn run_multi_module() {
fn check_muti_module_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(out.status.success());
check_output(
"multi-module",
"Quicksort.roc",
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
);
}
let (valgrind_out, raw_xml) =
run_with_valgrind(&[example_file("multi-module", "app").to_str().unwrap()]);
let ending = "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n";
if !&valgrind_out.stdout.ends_with(ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
);
}
let memory_errors = extract_valgrind_errors(&raw_xml);
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
}
assert!(valgrind_out.status.success());
}
#[test]
#[serial(multi_module)]
fn run_multi_module_optimized() {
check_output(
"multi-module",
"Quicksort.roc",
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
);
}
// TODO: Uncomment this once we are correctly freeing the RocList even when in dev build.
/*
check_muti_module_output(run_roc(&[
"run",
example_file("multi-module", "Quicksort.roc")
.to_str()
.unwrap(),
]));
*/
check_muti_module_output(run_roc(&[
"run",
example_file("multi-module", "Quicksort.roc")
.to_str()
.unwrap(),
"--optimize",
]));
#[test]
#[serial(multi_module)]
// TODO: Stop ignoring this test once we are correctly freeing the RocList even when in dev build.
#[ignore]
fn run_multi_module_valgrind() {
check_output(
"multi-module",
"Quicksort.roc",
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
);
}
#[test]
#[serial(multi_module)]
// TODO: Stop ignoring this test once valgrind supports AVX512.
#[ignore]
fn run_multi_module_optimized_valgrind() {
check_output(
"multi-module",
"Quicksort.roc",
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
);
}
}

View File

@ -15,6 +15,7 @@ use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use tempfile::NamedTempFile;
#[derive(Debug)]
pub struct Out {
pub stdout: String,
pub stderr: String,
@ -131,6 +132,9 @@ enum ValgrindField {
Args(ValgrindDummyStruct),
Error(ValgrindError),
Status(ValgrindDummyStruct),
Stack(ValgrindDummyStruct),
#[serde(rename = "fatal_signal")]
FatalSignal(ValgrindDummyStruct),
ErrorCounts(ValgrindDummyStruct),
SuppCounts(ValgrindDummyStruct),
}

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!
bumpalo = { version = "3.2", features = ["collections"] }
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.
#
# 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 inkwell::module::Module;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use libloading::{Error, Library};
use roc_gen::llvm::build::OptLevel;
use std::io;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::{Child, Command};
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(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
// 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);
output_path: PathBuf,
input_paths: &[&str],
link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
match target {
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
..
} => link_linux(target, binary_path, host_input_path, dest_filename),
} => link_linux(target, output_path, input_paths, link_type),
Triple {
architecture: Architecture::X86_64,
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),
}
}
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_dest = host_input_path.with_file_name("c_host.o");
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(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
output_path: PathBuf,
input_paths: &[&str],
link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
let libcrt_path = if Path::new("/usr/lib/x86_64-linux-gnu").exists() {
Path::new("/usr/lib/x86_64-linux-gnu")
} else {
Path::new("/usr/lib")
};
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")
} else if Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
@ -134,71 +144,162 @@ fn link_linux(
} else {
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!
// The `-l` flags should go after the `.o` arguments
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
"-arch",
arch_str(target),
libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(),
libcrt_path.join("Scrt1.o").to_str().unwrap(),
"-dynamic-linker",
"/lib64/ld-linux-x86-64.so.2",
// Inputs
host_input_path.to_str().unwrap(), // host.o
dest_filename.to_str().unwrap(), // app.o
// 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++",
"-lunwind",
libgcc_path.to_str().unwrap(),
// Output
"-o",
binary_path.to_str().unwrap(), // app
])
.spawn()
Ok((
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
"-arch",
arch_str(target),
libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(),
])
.args(&base_args)
.args(&["-dynamic-linker", "/lib64/ld-linux-x86-64.so.2"])
.args(input_paths)
.args(&[
// 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++",
"-lunwind",
libgcc_path.to_str().unwrap(),
// Output
"-o",
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
])
.spawn()?,
output_path,
))
}
fn link_macos(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
"-arch",
target.architecture.to_string().as_str(),
// Inputs
host_input_path.to_str().unwrap(), // host.o
dest_filename.to_str().unwrap(), // roc_app.o
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references
"-lSystem",
"-lresolv",
"-lpthread",
// "-lrt", // TODO shouldn't we need this?
// "-lc_nonshared", // 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
"-lc++", // TODO shouldn't we need this?
// Output
"-o",
binary_path.to_str().unwrap(), // app
])
.spawn()
output_path: PathBuf,
input_paths: &[&str],
link_type: LinkType,
) -> 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!
// The `-l` flags should go after the `.o` arguments
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
link_type_arg,
"-arch",
target.architecture.to_string().as_str(),
])
.args(input_paths)
.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references
"-lSystem",
"-lresolv",
"-lpthread",
// "-lrt", // TODO shouldn't we need this?
// "-lc_nonshared", // 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
"-lc++", // TODO shouldn't we need this?
// Output
"-o",
output_path.to_str().unwrap(), // app
])
.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 inkwell::context::Context;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use inkwell::OptimizationLevel;
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
use roc_load::file::MonomorphizedModule;
@ -16,9 +15,9 @@ use target_lexicon::Triple;
pub fn gen_from_mono_module(
arena: &Bump,
loaded: MonomorphizedModule,
filename: PathBuf,
file_path: PathBuf,
target: Triple,
dest_filename: &Path,
app_o_file: &Path,
opt_level: OptLevel,
) {
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);
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();
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() {
let report = type_problem(&alloc, filename.clone(), problem);
let report = type_problem(&alloc, file_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
@ -126,14 +125,11 @@ pub fn gen_from_mono_module(
// Emit the .o file
let opt = OptimizationLevel::Aggressive;
let reloc = RelocMode::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
.write_to_file(&env.module, FileType::Object, &dest_filename)
.write_to_file(&env.module, FileType::Object, &app_o_file)
.expect("Writing .o file failed");
println!("\nSuccess! 🎉\n\n\t{}\n", dest_filename.display());
}

View File

@ -68,7 +68,7 @@ pub fn target_machine(
Target::from_name(arch).unwrap().create_target_machine(
&TargetTriple::create(target_triple_str(target)),
arch,
"+avx2", // TODO this string was used uncritically from an example, and should be reexamined
"", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features.
opt,
reloc,
model,

2
compiler/builtins/bitcode/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
zig-cache
src/zig-cache

View File

@ -1,9 +0,0 @@
[package]
name = "roc_builtins_bitcode"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
repository = "https://github.com/rtfeldman/roc"
readme = "README.md"
edition = "2018"
description = "Generate LLVM bitcode for Roc builtins"
license = "Apache-2.0"

View File

@ -5,52 +5,19 @@ When their implementations are simple enough (e.g. addition), they
can be implemented directly in Inkwell.
When their implementations are complex enough, it's nicer to
implement them in a higher-level language like Rust, compile the
result to LLVM bitcode, and import that bitcode into the compiler.
implement them in a higher-level language like Zig, then compile
the result to LLVM bitcode, and import that bitcode into the compiler.
Here is the process for doing that.
Compiling the bitcode happens automatically in a Rust build script at `compiler/builtins/build.rs`.
Then `builtins/src/bitcode/rs` staticlly imports the compiled bitcode for use in the compiler.
## Building the bitcode
You can find the compiled bitcode in `target/debug/build/roc_builtins-[some random characters]/out/builtins.bc`.
There will be two directories like `roc_builtins-[some random characters]`, look for the one that has an
`out` directory as a child.
The source we'll use to generate the bitcode is in `src/lib.rs` in this directory.
To generate the bitcode, `cd` into `compiler/builtins/bitcode/` and run:
```bash
$ ./regenerate.sh
```
> If you want to take a look at the human-readable LLVM IR rather than the
> bitcode, run this instead and look for a `.ll` file instead of a `.bc` file:
>
> ```bash
> $ cargo rustc --release --lib -- --emit=llvm-ir
> ```
>
> Then look in the root `roc` source directory under `target/release/deps/` for a file
> with a name like `roc_builtins_bitcode-8da0901c58a73ebf.bc` - except
> probably with a different hash before the `.bc`. If there's more than one
> `*.bc` file in that directory, delete the whole `deps/` directory and re-run
> the `cargo rustc` command above to regenerate it.
**Note**: In order to be able to address the bitcode functions by name, they need to be defined with the `#[no_mangle]` attribute.
The bitcode is a bunch of bytes that aren't particularly human-readable.
Since Roc is designed to be distributed as a single binary, these bytes
need to be included in the raw source somewhere.
The `llvm/src/build.rs` file statically imports these raw bytes
using the [`include_bytes!` macro](https://doc.rust-lang.org/std/macro.include_bytes.html).
The current `.bc` file is located at:
```
compiler/gen/src/llvm/builtins.bc
```
The script will automatically replace this `.bc` file with the new one.
Once that's done, `git status` should show that the `builtins.bc` file
has been changed. Commit that change and you're done!
> The bitcode is a bunch of bytes that aren't particularly human-readable.
> If you want to take a look at the human-readable LLVM IR, look at
> `target/debug/build/roc_builtins-[some random characters]/out/builtins.ll`
## Calling bitcode functions

View File

@ -1,19 +0,0 @@
#!/bin/bash
set -euxo pipefail
# Clear out any existing output files. Sometimes if these are there, rustc
# doesn't generate the .bc file - or we can end up with more than one .bc
rm -rf ../../../target/release/deps/
# Regenerate the .bc file
cargo rustc --release --lib -- --emit=llvm-bc
bc_files=$(ls ../../../target/release/deps/*.bc | wc -l)
if [[ $bc_files != 1 ]]; then
echo "More than one .bc file was emitted somehow."
exit 1;
fi
cp ../../../target/release/deps/*.bc ../../gen/src/llvm/builtins.bc

View File

@ -1,54 +0,0 @@
// NOTE: Editing this file on its own does nothing! The procedure for
// incorporating changes here is in this crate' README.
#![crate_type = "lib"]
#![no_std]
mod libm;
/// TODO this is no longer used. Feel free to delete it the next time
/// we need to rebuild builtins.bc!
#[no_mangle]
pub fn i64_to_f64_(num: i64) -> f64 {
num as f64
}
/// Adapted from Rust's core::num module, by the Rust core team,
/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
///
/// Thank you, Rust core team!
#[no_mangle]
pub fn pow_int_(mut base: i64, mut exp: i64) -> i64 {
let mut acc = 1;
while exp > 1 {
if (exp & 1) == 1 {
acc *= base;
}
exp /= 2;
base *= base;
}
// Deal with the final bit of the exponent separately, since
// squaring the base afterwards is not necessary and may cause a
// needless overflow.
if exp == 1 {
acc *= base;
}
acc
}
/// Adapted from Rust's core::num module, by the Rust core team,
/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
///
/// Thank you, Rust core team!
#[no_mangle]
pub fn is_finite_(num: f64) -> bool {
f64::is_finite(num)
}
#[no_mangle]
pub fn atan_(x: f64) -> f64 {
libm::atan(x)
}

View File

@ -1,199 +0,0 @@
/// Adapted from Rust's libm module, by the Rust core team,
/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
/// https://github.com/rust-lang/libm/blob/master/LICENSE-APACHE
///
/// Thank you, Rust core team!
// From https://github.com/rust-lang/libm/blob/master/src/math/mod.rs
#[cfg(not(debug_assertions))]
macro_rules! i {
($array:expr, $index:expr) => {
unsafe { *$array.get_unchecked($index) }
};
($array:expr, $index:expr, = , $rhs:expr) => {
unsafe {
*$array.get_unchecked_mut($index) = $rhs;
}
};
($array:expr, $index:expr, += , $rhs:expr) => {
unsafe {
*$array.get_unchecked_mut($index) += $rhs;
}
};
($array:expr, $index:expr, -= , $rhs:expr) => {
unsafe {
*$array.get_unchecked_mut($index) -= $rhs;
}
};
($array:expr, $index:expr, &= , $rhs:expr) => {
unsafe {
*$array.get_unchecked_mut($index) &= $rhs;
}
};
($array:expr, $index:expr, == , $rhs:expr) => {
unsafe { *$array.get_unchecked_mut($index) == $rhs }
};
}
#[cfg(debug_assertions)]
macro_rules! i {
($array:expr, $index:expr) => {
*$array.get($index).unwrap()
};
($array:expr, $index:expr, = , $rhs:expr) => {
*$array.get_mut($index).unwrap() = $rhs;
};
($array:expr, $index:expr, -= , $rhs:expr) => {
*$array.get_mut($index).unwrap() -= $rhs;
};
($array:expr, $index:expr, += , $rhs:expr) => {
*$array.get_mut($index).unwrap() += $rhs;
};
($array:expr, $index:expr, &= , $rhs:expr) => {
*$array.get_mut($index).unwrap() &= $rhs;
};
($array:expr, $index:expr, == , $rhs:expr) => {
*$array.get_mut($index).unwrap() == $rhs
};
}
macro_rules! llvm_intrinsically_optimized {
(#[cfg($($clause:tt)*)] $e:expr) => {
#[cfg(all(feature = "unstable", $($clause)*))]
{
if true { // thwart the dead code lint
$e
}
}
};
}
macro_rules! force_eval {
($e:expr) => {
unsafe {
::core::ptr::read_volatile(&$e);
}
};
}
// From https://github.com/rust-lang/libm/blob/master/src/math/atan.rs
// Clippy fails CI if we don't include overrides below. Since this is copied
// straight from the libm crate, I figure they had a reason to include
// the extra percision, so we just silence this warning.
#[allow(clippy::excessive_precision)]
const ATANHI: [f64; 4] = [
4.63647609000806093515e-01, /* atan(0.5)hi 0x3FDDAC67, 0x0561BB4F */
7.85398163397448278999e-01, /* atan(1.0)hi 0x3FE921FB, 0x54442D18 */
9.82793723247329054082e-01, /* atan(1.5)hi 0x3FEF730B, 0xD281F69B */
1.57079632679489655800e+00, /* atan(inf)hi 0x3FF921FB, 0x54442D18 */
];
#[allow(clippy::excessive_precision)]
const ATANLO: [f64; 4] = [
2.26987774529616870924e-17, /* atan(0.5)lo 0x3C7A2B7F, 0x222F65E2 */
3.06161699786838301793e-17, /* atan(1.0)lo 0x3C81A626, 0x33145C07 */
1.39033110312309984516e-17, /* atan(1.5)lo 0x3C700788, 0x7AF0CBBD */
6.12323399573676603587e-17, /* atan(inf)lo 0x3C91A626, 0x33145C07 */
];
#[allow(clippy::excessive_precision)]
const AT: [f64; 11] = [
3.33333333333329318027e-01, /* 0x3FD55555, 0x5555550D */
-1.99999999998764832476e-01, /* 0xBFC99999, 0x9998EBC4 */
1.42857142725034663711e-01, /* 0x3FC24924, 0x920083FF */
-1.11111104054623557880e-01, /* 0xBFBC71C6, 0xFE231671 */
9.09088713343650656196e-02, /* 0x3FB745CD, 0xC54C206E */
-7.69187620504482999495e-02, /* 0xBFB3B0F2, 0xAF749A6D */
6.66107313738753120669e-02, /* 0x3FB10D66, 0xA0D03D51 */
-5.83357013379057348645e-02, /* 0xBFADDE2D, 0x52DEFD9A */
4.97687799461593236017e-02, /* 0x3FA97B4B, 0x24760DEB */
-3.65315727442169155270e-02, /* 0xBFA2B444, 0x2C6A6C2F */
1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */
];
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fabs(x: f64) -> f64 {
// On wasm32 we know that LLVM's intrinsic will compile to an optimized
// `f64.abs` native instruction, so we can leverage this for both code size
// and speed.
llvm_intrinsically_optimized! {
#[cfg(target_arch = "wasm32")] {
return unsafe { ::core::intrinsics::fabsf64(x) }
}
}
f64::from_bits(x.to_bits() & (u64::MAX / 2))
}
#[inline(always)]
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn atan(x: f64) -> f64 {
let mut x = x;
let mut ix = (x.to_bits() >> 32) as u32;
let sign = ix >> 31;
ix &= 0x7fff_ffff;
if ix >= 0x4410_0000 {
if x.is_nan() {
return x;
}
let z = ATANHI[3] + f64::from_bits(0x0380_0000); // 0x1p-120f
return if sign != 0 { -z } else { z };
}
let id = if ix < 0x3fdc_0000 {
/* |x| < 0.4375 */
if ix < 0x3e40_0000 {
/* |x| < 2^-27 */
if ix < 0x0010_0000 {
/* raise underflow for subnormal x */
force_eval!(x as f32);
}
return x;
}
-1
} else {
x = fabs(x);
if ix < 0x3ff30000 {
/* |x| < 1.1875 */
if ix < 0x3fe60000 {
/* 7/16 <= |x| < 11/16 */
x = (2. * x - 1.) / (2. + x);
0
} else {
/* 11/16 <= |x| < 19/16 */
x = (x - 1.) / (x + 1.);
1
}
} else if ix < 0x40038000 {
/* |x| < 2.4375 */
x = (x - 1.5) / (1. + 1.5 * x);
2
} else {
/* 2.4375 <= |x| < 2^66 */
x = -1. / x;
3
}
};
let z = x * x;
let w = z * z;
/* break sum from i=0 to 10 AT[i]z**(i+1) into odd and even poly */
let s1 = z * (AT[0] + w * (AT[2] + w * (AT[4] + w * (AT[6] + w * (AT[8] + w * AT[10])))));
let s2 = w * (AT[1] + w * (AT[3] + w * (AT[5] + w * (AT[7] + w * AT[9]))));
if id < 0 {
return x - x * (s1 + s2);
}
let z = i!(ATANHI, id as usize) - (x * (s1 + s2) - i!(ATANLO, id as usize) - x);
if sign != 0 {
-z
} else {
z
}
}

View File

@ -0,0 +1,14 @@
const std = @import("std");
const math = std.math;
export fn atan_(num: f64) f64 {
return math.atan(num);
}
export fn is_finite_(num: f64) bool {
return math.isFinite(num);
}
export fn pow_int_(base: i64, exp: i64) i64 {
return math.pow(i64, base, exp);
}

View File

@ -0,0 +1,70 @@
use std::convert::AsRef;
use std::env;
use std::ffi::OsStr;
use std::path::Path;
use std::process::Command;
use std::str;
fn run_command<S, I>(command: &str, args: I)
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let output_result = Command::new(OsStr::new(&command)).args(args).output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command),
};
panic!("{} failed: {}", command, error_str);
}
},
Err(reason) => panic!("{} failed: {}", command, reason),
}
}
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
// "." is relative to where "build.rs" is
let src_path = Path::new(".").join("bitcode").join("src").join("main.zig");
let src_path_str = src_path.to_str().expect("Invalid src path");
println!("Building main.zig from: {}", src_path_str);
let zig_cache_dir = Path::new(&out_dir).join("zig-cache");
let zig_cache_dir_str = zig_cache_dir.to_str().expect("Invalid zig cache dir");
println!("Setting zig cache to: {}", zig_cache_dir_str);
let dest_ll_path = Path::new(&out_dir).join("builtins.ll");
let dest_ll = dest_ll_path.to_str().expect("Invalid dest ir path");
let emit_ir_arg = "-femit-llvm-ir=".to_owned() + dest_ll;
println!("Compiling zig llvm-ir to: {}", dest_ll);
run_command(
"zig",
&[
"build-obj",
src_path_str,
&emit_ir_arg,
"-fno-emit-bin",
"--strip",
"-O",
"ReleaseFast",
"--cache-dir",
zig_cache_dir_str,
],
);
let dest_bc_path = Path::new(&out_dir).join("builtins.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling bitcode to: {}", dest_bc);
run_command("llvm-as-10", &[dest_ll, "-o", dest_bc]);
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={}", src_path_str);
println!("cargo:rustc-env=BUILTINS_BC={}", dest_bc);
}

View File

@ -0,0 +1,18 @@
use std::fs::File;
use std::io::prelude::Read;
use std::vec::Vec;
pub fn get_bytes() -> Vec<u8> {
// In the build script for the builtins module, we compile the builtins bitcode and set
// BUILTINS_BC to the path to the compiled output.
let path: &'static str = env!(
"BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?"
);
let mut builtins_bitcode = File::open(path).expect("Unable to find builtins bitcode source");
let mut buffer = Vec::new();
builtins_bitcode
.read_to_end(&mut buffer)
.expect("Unable to read builtins bitcode");
buffer
}

View File

@ -10,5 +10,6 @@
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod bitcode;
pub mod std;
pub mod unique;

View File

@ -450,7 +450,7 @@ pub fn canonicalize_expr<'a>(
let mut bound_by_argument_patterns = MutSet::default();
for loc_pattern in loc_arg_patterns.into_iter() {
for loc_pattern in loc_arg_patterns.iter() {
let (new_output, can_arg) = canonicalize_pattern(
env,
var_store,

View File

@ -1,3 +1,4 @@
use crate::builtins;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration};
use crate::env::Env;
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 {
aliases,
rigid_variables,

View File

@ -133,7 +133,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
Defs(defs, loc_ret) | Nested(Defs(defs, loc_ret)) => {
let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena);
for loc_def in defs.into_iter() {
for loc_def in defs.iter() {
let loc_def = Located {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
@ -167,7 +167,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr));
let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
for branch in branches.into_iter() {
for branch in branches.iter() {
let desugared = desugar_expr(arena, &branch.value);
let mut alternatives = Vec::with_capacity_in(branch.patterns.len(), arena);

View File

@ -4,7 +4,7 @@ use crate::pattern::fmt_pattern;
use crate::spaces::{
add_spaces, fmt_comments_only, fmt_condition_spaces, fmt_spaces, newline, INDENT,
};
use bumpalo::collections::{String, Vec};
use bumpalo::collections::String;
use roc_module::operator::{self, BinOp};
use roc_parse::ast::StrSegment;
use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch};

View File

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

View File

@ -29,6 +29,7 @@ use inkwell::values::{
};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
use roc_builtins::bitcode;
use roc_collections::all::{ImMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -50,6 +51,15 @@ pub enum OptLevel {
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)]
pub struct Scope<'a, 'ctx> {
symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>,
@ -172,8 +182,9 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
}
pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Module<'ctx> {
let memory_buffer =
MemoryBuffer::create_from_memory_range(include_bytes!("builtins.bc"), module_name);
let bitcode_bytes = bitcode::get_bytes();
let memory_buffer = MemoryBuffer::create_from_memory_range(&bitcode_bytes, module_name);
let module = Module::parse_bitcode_from_buffer(&memory_buffer, ctx)
.unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err));

Binary file not shown.

View File

@ -28,24 +28,23 @@ impl<T: Sized> Into<Result<T, String>> for RocCallResult<T> {
#[macro_export]
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();
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::execution_engine::JitFunction;
use roc_gen::run_roc::RocCallResult;
unsafe {
let main: JitFunction<unsafe extern "C" fn() -> RocCallResult<$ty>> = $execution_engine
.get_function($main_fn_name)
let main: libloading::Symbol<unsafe extern "C" fn() -> RocCallResult<$ty>> = $lib
.get($main_fn_name.as_bytes())
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
match main.call().into() {
match main().into() {
Ok(success) => {
// only if there are no exceptions thrown, check for errors
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.
#[macro_export]
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();
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::execution_engine::JitFunction;
use roc_gen::run_roc::RocCallResult;
unsafe {
let main: JitFunction<unsafe extern "C" fn(*const u8)> = $execution_engine
.get_function($main_fn_name)
let main: libloading::Symbol<unsafe extern "C" fn(*const u8)> = $lib
.get($main_fn_name.as_bytes())
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
let layout = std::alloc::Layout::array::<u8>($bytes).unwrap();
let result = std::alloc::alloc(layout);
main.call(result);
main(result);
let flag = *result;

View File

@ -68,7 +68,7 @@ mod gen_num {
indoc!(
r#"
limitedNegate = \num ->
x =
x =
if num == 1 then
-1
else if num == -1 then
@ -482,7 +482,7 @@ mod gen_num {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
wrapper = \{} ->
when 10 is
x if x == 5 -> 0
_ -> 42
@ -500,7 +500,7 @@ mod gen_num {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
wrapper = \{} ->
when 10 is
x if x == 10 -> 42
_ -> 0
@ -691,19 +691,19 @@ mod gen_num {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
#[test]
#[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
fn int_overflow() {
assert_evals_to!(
indoc!(
r#"
9_223_372_036_854_775_807 + 1
"#
),
0,
i64
);
}
// #[test]
// #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
// fn int_overflow() {
// assert_evals_to!(
// indoc!(
// r#"
// 9_223_372_036_854_775_807 + 1
// "#
// ),
// 0,
// i64
// );
// }
#[test]
fn int_add_checked() {
@ -750,7 +750,7 @@ mod gen_num {
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 1.0 0.0 is
when Num.addChecked 1.0 0.0 is
Ok v -> v
Err Overflow -> -1.0
"#
@ -775,17 +775,17 @@ mod gen_num {
);
}
#[test]
#[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
fn float_overflow() {
assert_evals_to!(
indoc!(
r#"
1.7976931348623157e308 + 1.7976931348623157e308
"#
),
0.0,
f64
);
}
// #[test]
// #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
// fn float_overflow() {
// assert_evals_to!(
// indoc!(
// r#"
// 1.7976931348623157e308 + 1.7976931348623157e308
// "#
// ),
// 0.0,
// f64
// );
// }
}

View File

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

View File

@ -1,3 +1,5 @@
use libloading::Library;
use roc_build::link::module_to_dylib;
use roc_collections::all::{MutMap, MutSet};
fn promote_expr_to_module(src: &str) -> String {
@ -19,12 +21,7 @@ pub fn helper<'a>(
stdlib: roc_builtins::std::StdLib,
leak: bool,
context: &'a inkwell::context::Context,
) -> (
&'static str,
Vec<roc_problem::can::Problem>,
inkwell::execution_engine::ExecutionEngine<'a>,
) {
use inkwell::OptimizationLevel;
) -> (&'static str, Vec<roc_problem::can::Problem>, Library) {
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
use std::path::{Path, PathBuf};
@ -166,10 +163,6 @@ pub fn helper<'a>(
let (module_pass, function_pass) =
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
let env = roc_gen::llvm::build::Env {
arena: &arena,
@ -265,7 +258,10 @@ pub fn helper<'a>(
// Uncomment this to see the module's optimized LLVM instruction output:
// 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
@ -284,7 +280,7 @@ macro_rules! assert_opt_evals_to {
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);
let transform = |success| {
@ -292,7 +288,7 @@ macro_rules! assert_opt_evals_to {
let given = $transform(success);
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) => {
@ -312,7 +308,7 @@ macro_rules! assert_llvm_evals_to {
let context = Context::create();
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);
let transform = |success| {
@ -320,7 +316,7 @@ macro_rules! assert_llvm_evals_to {
let given = $transform(success);
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) => {

View File

@ -2260,7 +2260,8 @@ fn parse_and_constrain<'a>(
let module_id = header.module_id;
// 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(
header.module_name,
&header.exposed_ident_ids,
@ -2272,7 +2273,7 @@ fn parse_and_constrain<'a>(
let mut var_store = VarStore::default();
let canonicalized = canonicalize_module_defs(
&arena,
parsed_defs,
&parsed_defs,
module_id,
module_ids,
header.exposed_ident_ids,
@ -2283,17 +2284,7 @@ fn parse_and_constrain<'a>(
);
let canonicalize_end = SystemTime::now();
let (module, declarations, ident_ids, constraint, problems) = match canonicalized {
Ok(mut 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));
}
}
Ok(module_output) => {
let constraint = constrain_module(&module_output, module_id, mode, &mut var_store);
// Now that we're done with parsing, canonicalization, and constraint gen,

View File

@ -13,6 +13,9 @@ mod helpers;
#[cfg(test)]
mod test_mono {
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use roc_mono::ir::Proc;
use roc_mono::layout::Layout;
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n");
@ -27,15 +30,6 @@ mod test_mono {
buffer
}
// NOTE because the Show instance of module names is different in --release mode,
// these tests would all fail. In the future, when we do interesting optimizations,
// we'll likely want some tests for --release too.
#[cfg(not(debug_assertions))]
fn compiles_to_ir(_src: &str, _expected: &str) {
// just do nothing
}
#[cfg(debug_assertions)]
fn compiles_to_ir(src: &str, expected: &str) {
use bumpalo::Bump;
use std::path::{Path, PathBuf};
@ -84,12 +78,22 @@ mod test_mono {
println!("Ignoring {} canonicalization problems", can_problems.len());
}
assert!(type_problems.is_empty());
assert!(mono_problems.is_empty());
assert_eq!(type_problems, Vec::new());
assert_eq!(mono_problems, Vec::new());
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap();
verify_procedures(expected, procedures, main_fn_symbol);
}
#[cfg(debug_assertions)]
fn verify_procedures(
expected: &str,
procedures: MutMap<(Symbol, Layout<'_>), Proc<'_>>,
main_fn_symbol: Symbol,
) {
let index = procedures
.keys()
.position(|(s, _)| *s == main_fn_symbol)
@ -120,6 +124,18 @@ mod test_mono {
}
}
// NOTE because the Show instance of module names is different in --release mode,
// these tests would all fail. In the future, when we do interesting optimizations,
// we'll likely want some tests for --release too.
#[cfg(not(debug_assertions))]
fn verify_procedures(
_expected: &str,
_procedures: MutMap<(Symbol, Layout<'_>), Proc<'_>>,
_main_fn_symbol: Symbol,
) {
// Do nothing
}
#[test]
fn ir_int_literal() {
compiles_to_ir(
@ -154,7 +170,6 @@ mod test_mono {
)
}
#[test]
#[test]
fn ir_when_maybe() {
compiles_to_ir(

View File

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

2
nix/bin/llvm-as-10 Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
llvm-as "$@"

36
nix/zig.nix Normal file
View File

@ -0,0 +1,36 @@
{ pkgs, isMacOS }:
# We require at least specific commit of Zig after the latest tagged
# release (0.6.0), so we just download the binaries for that commit
let
version = "0.6.0+0088efc4b";
osName =
if isMacOS
then "macos"
else "linux";
archiveName = "zig-${osName}-x86_64-${version}";
sha256 =
if isMacOS
then "665c1a7f472cfc5e0715f0ddf6ff8409fb749ac91cbbae68c443b4a37ebd058e"
else "bab70ae3bd0af538022bc3ef50d8f34fa8dceac39ba7d9e5d528eee7e6d5a1cf";
in
pkgs.stdenv.mkDerivation {
pname = "zig";
version = version;
buildInputs = [ pkgs.gzip ];
src = pkgs.fetchurl {
inherit sha256;
name = "${archiveName}.tar.xz";
url = "https://ziglang.org/builds/${archiveName}.tar.xz";
};
phases = [ "installPhase" ];
installPhase = ''
mkdir -p $out/bin
tar -xf $src
cp ${archiveName}/zig $out/zig
cp -r ${archiveName}/lib $out/lib
ln -s "$out/zig" "$out/bin/zig"
chmod +x $out/bin/zig
'';
}

28
nix/zls.nix Normal file
View File

@ -0,0 +1,28 @@
{ pkgs, zig }:
# As of 2020-10-25, building zls is not available on Nix. For some reason,
# this hangs on `zig build`. I'll try to figure it our later.
let
rev = "e8c20351d85da8eb4bf22480045b994007284d69";
in
pkgs.stdenv.mkDerivation {
pname = "zig-language-server";
version = rev;
src = pkgs.fetchgit {
inherit rev;
fetchSubmodules = true;
url = "https://github.com/zigtools/zls.git";
sha256 = "06g8gml1g0fmvcfysy93bd1hb64vjd2v12x3kgxz58kmk5z0168y";
};
phases = [ "buildPhase" "installPhase" ];
buildInputs = [ zig ];
buildPhase = ''
zig build
'';
installPhase = ''
mkdir -p $out/bin
cp ./zig-cache/bin/zls $out/bin/zls
chmod +x $out/bin/zls
'';
}

View File

@ -1,17 +1,19 @@
let
{ }:
with {
# Look here for information about how pin version of nixpkgs
# → https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs
pinnedPkgs = import (builtins.fetchGit {
name = "nixpkgs-20.03";
url = "https://github.com/nixos/nixpkgs/";
ref = "refs/heads/release-20.03";
pkgs = import (builtins.fetchGit {
name = "nixpkgs-2020-10-24";
url = "https://github.com/nixos/nixpkgs-channels/";
ref = "refs/heads/nixpkgs-unstable";
rev = "502845c3e31ef3de0e424f3fcb09217df2ce6df6";
}) { };
# This allows overriding pkgs by passing `--arg pkgs ...`
in { pkgs ? pinnedPkgs }:
isMacOS = builtins.currentSystem == "x86_64-darwin";
};
let
isMacOS = builtins.currentSystem == "x86_64-darwin";
darwin-frameworks =
if isMacOS then
with pkgs.darwin.apple_sdk.frameworks; [
@ -27,20 +29,33 @@ let
[ ];
llvm = pkgs.llvm_10;
lld = pkgs.lld_10; # this should match llvm's version
zig = import ./nix/zig.nix { inherit pkgs isMacOS; };
inputs =
[
# build libraries
pkgs.rustup
pkgs.cargo
llvm
# libraries for llvm
pkgs.valgrind
zig
# llb deps
pkgs.libffi
pkgs.libxml2
pkgs.zlib
# faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker
lld
# dev tools
pkgs.rust-analyzer
# (import ./nix/zls.nix { inherit pkgs zig; })
pkgs.ccls
];
in pkgs.mkShell {
buildInputs = inputs ++ darwin-frameworks;
LLVM_SYS_100_PREFIX = "${llvm}";
# Aliases don't work cross shell, so we do this
shellHook = ''
export PATH="$PATH:$PWD/nix/bin"
'';
}