Merge remote-tracking branch 'origin/trunk' into precedence-error

This commit is contained in:
Folkert 2020-04-02 23:29:15 +02:00
commit 604dbf7215
19 changed files with 226 additions and 56 deletions

View File

@ -32,7 +32,9 @@ Create `~/.config/cargo` and add this to it:
```
[build]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
# Link with lld, per https://github.com/rust-lang/rust/issues/39915#issuecomment-538049306
# Use target-cpu=native, per https://deterministic.space/high-performance-rust.html
rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "target-cpu=native"]
```
Then install `lld` version 9 (e.g. with `$ sudo apt-get install lld-9`)

4
Cargo.lock generated
View File

@ -568,6 +568,10 @@ dependencies = [
"roc_types",
]
[[package]]
name = "roc_builtins_bitcode"
version = "0.1.0"
[[package]]
name = "roc_can"
version = "0.1.0"

View File

@ -10,6 +10,7 @@ members = [
"compiler/types",
"compiler/uniq",
"compiler/builtins",
"compiler/builtins/bitcode",
"compiler/constrain",
"compiler/unify",
"compiler/solve",
@ -23,3 +24,7 @@ members = [
"cli"
]
# Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release]
lto = "fat"
codegen-units = 1

View File

@ -1,6 +1,6 @@
#!/bin/bash
mkdir -p $HOME/.cargo
echo -e "[build]\nrustflags = [\"-C\", \"link-arg=-fuse-ld=lld\"]" > $HOME/.cargo/config
echo -e "[build]\nrustflags = [\"-C\", \"link-arg=-fuse-ld=lld\", \"-C\", \"target-cpu=native\"]" > $HOME/.cargo/config
ln -s /usr/bin/lld-8 /usr/local/bin/ld.lld

View File

@ -8,7 +8,9 @@ use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header, get_call_conventions};
use roc_gen::llvm::build::{
build_proc, build_proc_header, get_call_conventions, module_from_builtins,
};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
@ -65,7 +67,7 @@ fn gen(src: &str, target: Triple, dest_filename: &Path) {
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
let context = Context::create();
let module = context.create_module("app");
let module = module_from_builtins(&context, "app");
let builder = context.create_builder();
let fpm = PassManager::create(&module);
@ -74,17 +76,14 @@ fn gen(src: &str, target: Triple, dest_filename: &Path) {
fpm.initialize();
// Compute main_fn_type before moving subs to Env
let pointer_bytes = target.pointer_width().unwrap().bytes() as u32;
let layout = Layout::from_content(&arena, content, &subs, pointer_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
panic!(
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}",
err, subs
)
});
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let ptr_bytes = execution_engine
.get_target_data()
.get_pointer_byte_size(None);
let main_fn_type =
basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false);
let main_fn_name = "$Test.main";
@ -109,7 +108,7 @@ fn gen(src: &str, target: Triple, dest_filename: &Path) {
&mut procs,
home,
&mut ident_ids,
pointer_bytes,
ptr_bytes,
);
// Put this module's ident_ids back in the interns, so we can use them in env.

View File

@ -0,0 +1,13 @@
[package]
name = "roc_builtins_bitcode"
version = "0.1.0"
authors = ["Richard Feldman <richard.t.feldman@gmail.com>"]
repository = "https://github.com/rtfeldman/roc"
readme = "README.md"
edition = "2018"
description = "Generate LLVM bitcode for Roc builtins"
license = "Apache-2.0"
[build]
target-dir="~/code/roc/copmiler/builtins/bitcode/"

View File

@ -0,0 +1,56 @@
# Bitcode for Builtins
Roc's builtins are implemented in the compiler using LLVM only.
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.
Here is the process for doing that.
## Building the bitcode
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
$ cargo rustc --release --lib -- --emit=llvm-bc
```
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.
> 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
> ```
## Importing the bitcode
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),
so we just need to move the `.bc` file from the previous step to the correct
location.
The current `.bc` file is located at:
```
compiler/gen/src/llvm/builtins.bc
```
...so you want to overwrite it with the new `.bc` file in `target/deps/`
Once that's done, `git status` should show that the `builtins.bc` file
has been changed. Commit that change and you're done!

View File

@ -0,0 +1,10 @@
#![crate_type = "lib"]
#![no_std]
// TODO replace this with a normal Inkwell build_cast call - this was just
// used as a proof of concept for getting bitcode importing working!
#[no_mangle]
pub fn i64_to_f64_(num: i64) -> f64 {
num as f64
}

View File

@ -2,6 +2,7 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::memory_buffer::MemoryBuffer;
use inkwell::module::{Linkage, Module};
use inkwell::passes::PassManager;
use inkwell::types::{BasicTypeEnum, IntType, StructType};
@ -47,6 +48,14 @@ 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);
Module::parse_bitcode_from_buffer(&memory_buffer, ctx)
.unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err))
}
pub fn add_passes(fpm: &PassManager<FunctionValue<'_>>) {
// tail-call elimination is always on
fpm.add_instruction_combining_pass();
@ -1069,6 +1078,31 @@ fn call_with_args<'a, 'ctx, 'env>(
BasicValueEnum::IntValue(int_val)
}
Symbol::NUM_TO_FLOAT => {
// TODO specialize this to be not just for i64!
let builtin_fn_name = "i64_to_f64_";
let fn_val = env
.module
.get_function(builtin_fn_name)
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", builtin_fn_name));
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
for (arg, _layout) in args.iter() {
arg_vals.push(*arg);
}
let call = env
.builder
.build_call(fn_val, arg_vals.into_bump_slice(), "call_builtin");
call.set_call_convention(DEFAULT_CALLING_CONVENTION);
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call for builtin {:?}", symbol))
}
Symbol::FLOAT_EQ => {
debug_assert!(args.len() == 2);

Binary file not shown.

View File

@ -344,4 +344,17 @@ mod gen_builtins {
i64
);
}
#[test]
fn int_to_float() {
assert_evals_to!(
indoc!(
r#"
Num.toFloat 0x9
"#
),
9.0,
f64
);
}
}

View File

@ -10,7 +10,7 @@ macro_rules! assert_llvm_evals_to {
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
let context = Context::create();
let module = context.create_module("app");
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
let builder = context.create_builder();
let fpm = inkwell::passes::PassManager::create(&module);
@ -142,7 +142,7 @@ macro_rules! assert_opt_evals_to {
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
let context = Context::create();
let module = context.create_module("app");
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
let builder = context.create_builder();
let fpm = PassManager::create(&module);

View File

@ -133,6 +133,6 @@ impl std::fmt::Display for BinOp {
Pizza => "|>",
};
write!(f, "({})", as_str)
write!(f, "{}", as_str)
}
}

View File

@ -8,6 +8,7 @@ edition = "2018"
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_load = { path = "../load" }
@ -15,7 +16,6 @@ roc_can = { path = "../can" }
roc_solve = { path = "../solve" }
inlinable_string = "0.1.0"
[dev-dependencies]
roc_constrain = { path = "../constrain" }
roc_builtins = { path = "../builtins" }

View File

@ -1,4 +1,4 @@
use crate::report::ReportText::{Batch, Module, Region, Value};
use crate::report::ReportText::{Batch, BinOp, Module, Region, Value};
use roc_can::expected::{Expected, PExpected};
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
@ -27,6 +27,7 @@ pub struct Palette {
pub line_number: Color,
pub gutter_bar: Color,
pub module_name: Color,
pub binop: Color,
}
#[derive(Copy, Clone)]
@ -52,6 +53,7 @@ pub const TEST_PALETTE: Palette = Palette {
line_number: Color::Cyan,
gutter_bar: Color::Magenta,
module_name: Color::Green,
binop: Color::Green,
};
impl Color {
@ -314,10 +316,21 @@ pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
texts.push(plain_text("\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."));
}
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => {
texts.push(plain_text(&*format!(
"You cannot mix {} and {} without parentheses",
left_bin_op.value, right_bin_op.value
)));
if left_bin_op.value == right_bin_op.value {
texts.push(plain_text("Using more than one "));
texts.push(BinOp(left_bin_op.value));
texts.push(plain_text(
" like this requires parentheses, to clarify how things should be grouped.",
))
} else {
texts.push(plain_text("Using "));
texts.push(BinOp(left_bin_op.value));
texts.push(plain_text(" and "));
texts.push(BinOp(right_bin_op.value));
texts.push(plain_text(
" together requires parentheses, to clarify how they should be grouped.",
))
}
texts.push(Region(region));
}
Problem::UnsupportedPattern(_pattern_type, _region) => {
@ -371,6 +384,8 @@ pub enum ReportText {
/// The documentation for this symbol.
Docs(Symbol),
BinOp(roc_parse::operator::BinOp),
/// Many ReportText that should be concatenated together.
Batch(Vec<ReportText>),
}
@ -575,6 +590,9 @@ impl ReportText {
report_text.render_ci(buf, subs, home, src_lines, interns);
}
}
BinOp(bin_op) => {
buf.push_str(bin_op.to_string().as_str());
}
}
}
@ -716,6 +734,9 @@ impl ReportText {
report_text.render_color_terminal(buf, subs, home, src_lines, interns, palette);
}
}
BinOp(bin_op) => {
buf.push_str(&palette.binop.render(bin_op.to_string().as_str()));
}
_ => panic!("TODO implement more ReportTexts in render color terminal"),
}
}

View File

@ -304,23 +304,22 @@ mod test_reporting {
)
}
// hits a TODO in reporting
// #[test]
// fn report_shadow() {
// report_problem_as(
// indoc!(
// r#"
// i = 1
// #[test]
// fn report_shadow() {
// report_problem_as(
// indoc!(
// r#"
// i = 1
//
// s = \i ->
// i + 1
// s = \i ->
// i + 1
//
// s i
// "#
// ),
// indoc!(r#" "#),
// )
// }
// s i
// "#
// ),
// indoc!(r#" "#),
// )
// }
// #[test]
// fn report_unsupported_top_level_def() {
@ -355,7 +354,7 @@ mod test_reporting {
),
indoc!(
r#"
You cannot mix (!=) and (==) without parentheses
Using != and == together requires parentheses, to clarify how they should be grouped.
3 if selectedId != thisId == adminsId then
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -372,7 +371,7 @@ mod test_reporting {
r#"
if
1
!= 2
== 2
== 3
then
2
@ -383,10 +382,10 @@ mod test_reporting {
),
indoc!(
r#"
You cannot mix (!=) and (==) without parentheses
Using more than one == like this requires parentheses, to clarify how things should be grouped.
2 > 1
3 > != 2
3 > == 2
4 > == 3
"#

View File

@ -9,7 +9,9 @@ mod helpers;
#[cfg(test)]
mod test_solve {
use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut};
use crate::helpers::{
assert_correct_variable_usage, can_expr, infer_expr, with_larger_debug_stack, CanExprOut,
};
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs;
@ -2220,9 +2222,10 @@ mod test_solve {
#[test]
fn quicksort_partition() {
infer_eq_without_problem(
indoc!(
r#"
with_larger_debug_stack(|| {
infer_eq_without_problem(
indoc!(
r#"
swap : Int, Int, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
@ -2261,9 +2264,10 @@ mod test_solve {
partition
"#
),
"Int, Int, List Int -> [ Pair Int (List Int) ]",
);
),
"Int, Int, List Int -> [ Pair Int (List Int) ]",
);
});
}
#[test]

View File

@ -792,7 +792,7 @@ fn write_error_type_help(
}
}
Function(arguments, result) => {
let use_parens = parens != Parens::Unnecessary;
let write_parens = parens != Parens::Unnecessary;
if write_parens {
buf.push(')');
@ -815,10 +815,10 @@ fn write_error_type_help(
buf.push(')');
}
}
Record(fields, ext) {
buf.push('{');
buf.push('}');
write_ext(ext, buf);
Record(fields, ext) => {
buf.push('{');
buf.push('}');
write_type_ext(ext, buf);
}
Infinite => {
@ -836,6 +836,16 @@ pub enum TypeExt {
RigidOpen(Lowercase),
}
fn write_type_ext(ext: TypeExt, buf: &mut String) {
use TypeExt::*;
match ext {
Closed => {}
FlexOpen(lowercase) | RigidOpen(lowercase) => {
buf.push_str(lowercase.as_str());
}
}
}
static THE_LETTER_A: u32 = 'a' as u32;
pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lowercase, u32) {

View File

@ -15,7 +15,7 @@ in our imaginations...so Rube Goldberg it is!)
1. `cd` into `examples/hello-world/`
2. Run `cargo run hello.roc` to compile the Roc source code into a `hello.o` file.
3. Run `gcc -shared hello.o -o libhello_from_roc.so` to generate `libhello_from_roc.so`. (This filename must begin with `lib` and end in `.so` or else `host.rs` won't be able to find it!)
4. Move `libhello_from_roc.so` onto the system library path, e.g. with `sudo mv libhello_from_roc.so /usr/local/lib/`
4. Move `libhello_from_roc.so` onto the system library path, e.g. with `sudo mv libhello_from_roc.so /usr/local/lib/` on macOS, or `sudo mv libhello_from_roc.so /usr/local/lib /usr/lib` on Linux.
5. Run `rustc host.rs -o hello` to generate the `hello` executable.
6. Run `./hello` to see the greeting!