Merge branch 'trunk' of github.com:rtfeldman/roc into str-split

This commit is contained in:
Chad Stearns 2020-09-26 14:54:18 -04:00
commit 5fe6eefa97
32 changed files with 1134 additions and 152 deletions

View file

@ -36,6 +36,8 @@ $ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50f
$ brew pin llvm $ brew pin llvm
``` ```
If that doesn't work and you get a `brew` error `Error: Calling Installation of llvm from a GitHub commit URL is disabled! Use 'brew extract llvm' to stable tap on GitHub instead.` while trying the above solution, you can follow the steps extracting the formula into your private tap (one public version is at `sladwig/tap/llvm`). If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again.
### LLVM installation on Windows ### LLVM installation on Windows
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source

4
Cargo.lock generated
View file

@ -1109,7 +1109,7 @@ dependencies = [
[[package]] [[package]]
name = "inkwell" name = "inkwell"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release1#005de45445d6ddae80f09ed012e6c3dfe674201a" source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release2#d0a1ce5e678cf0f0e62b757760d1019301c603c9"
dependencies = [ dependencies = [
"either", "either",
"inkwell_internals", "inkwell_internals",
@ -1123,7 +1123,7 @@ dependencies = [
[[package]] [[package]]
name = "inkwell_internals" name = "inkwell_internals"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release1#005de45445d6ddae80f09ed012e6c3dfe674201a" source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release2#d0a1ce5e678cf0f0e62b757760d1019301c603c9"
dependencies = [ dependencies = [
"proc-macro2 0.4.30", "proc-macro2 0.4.30",
"quote 0.6.13", "quote 0.6.13",

View file

@ -65,7 +65,7 @@ One of the reasons this editor is coupled with the language itself is to allow p
A trivial example: suppose I'm writing a Roc app for an Arduino platform. I install a platform-specific package for displaying text on a grid of LEDs. Because I've installed this package, at the call site where I call the function to specify the color of the text on the LEDs, my Roc editor displays an inline color picker. As I move a slider around to try out different colors, not only does my code change to reflect that value in realtime, but the physical LEDs in my room change color in realtime as well. As the application author, all I did to get that experience was to install the "text on an LED grid" package, nothing else. A trivial example: suppose I'm writing a Roc app for an Arduino platform. I install a platform-specific package for displaying text on a grid of LEDs. Because I've installed this package, at the call site where I call the function to specify the color of the text on the LEDs, my Roc editor displays an inline color picker. As I move a slider around to try out different colors, not only does my code change to reflect that value in realtime, but the physical LEDs in my room change color in realtime as well. As the application author, all I did to get that experience was to install the "text on an LED grid" package, nothing else.
The goal is for this to be one of the most trivial, bare minimum examples of what the editor experience would be like. Hopefully, people in the future will look back on this example and say "that's so embarassingly basic; why didn't you talk about one of the *actually great* things in the seamless editor plugin ecosystem?" The goal is for this to be one of the most trivial, bare minimum examples of what the editor experience would be like. Hopefully, people in the future will look back on this example and say "that's so embarrassingly basic; why didn't you talk about one of the *actually great* things in the seamless editor plugin ecosystem?"
Finally, some implementation goals: Finally, some implementation goals:

View file

@ -76,7 +76,7 @@ libc = "0.2"
# commit of TheDan64/inkwell, push a new tag which points to the latest commit, # commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# 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.release1" } inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
target-lexicon = "0.10" target-lexicon = "0.10"
[dev-dependencies] [dev-dependencies]

View file

@ -210,6 +210,12 @@ fn build_file(
"host.rs", "host.rs",
"-o", "-o",
binary_path.as_path().to_str().unwrap(), binary_path.as_path().to_str().unwrap(),
// ensure we don't make a position-independent executable
"-C",
"link-arg=-no-pie",
// explicitly link in the c++ stdlib, for exceptions
"-C",
"link-arg=-lc++",
]) ])
.current_dir(cwd) .current_dir(cwd)
.spawn() .spawn()

View file

@ -1,7 +1,8 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::execution_engine::{ExecutionEngine, JitFunction}; use inkwell::execution_engine::ExecutionEngine;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_gen::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::operator::CalledVia; use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -50,20 +51,6 @@ pub unsafe fn jit_to_ast<'a>(
jit_to_ast_help(&env, execution_engine, main_fn_name, layout, content) jit_to_ast_help(&env, execution_engine, main_fn_name, layout, content)
} }
macro_rules! jit_map {
($execution_engine: expr, $main_fn_name: expr, $ty: ty, $transform: expr) => {{
unsafe {
let main: JitFunction<unsafe extern "C" fn() -> $ty> = $execution_engine
.get_function($main_fn_name)
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
$transform(main.call())
}
}};
}
fn jit_to_ast_help<'a>( fn jit_to_ast_help<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
execution_engine: ExecutionEngine, execution_engine: ExecutionEngine,
@ -72,32 +59,30 @@ fn jit_to_ast_help<'a>(
content: &Content, content: &Content,
) -> Expr<'a> { ) -> Expr<'a> {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => run_jit_function!(
jit_map!(execution_engine, main_fn_name, i64, |num| num_to_ast( execution_engine,
env, main_fn_name,
i64_to_ast(env.arena, num), i64,
content |num| num_to_ast(env, i64_to_ast(env.arena, num), content)
)) ),
} Layout::Builtin(Builtin::Float64) => run_jit_function!(
Layout::Builtin(Builtin::Float64) => { execution_engine,
jit_map!(execution_engine, main_fn_name, f64, |num| num_to_ast( main_fn_name,
env, f64,
f64_to_ast(env.arena, num), |num| num_to_ast(env, f64_to_ast(env.arena, num), content)
content ),
)) Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => run_jit_function!(
}
Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => jit_map!(
execution_engine, execution_engine,
main_fn_name, main_fn_name,
&'static str, &'static str,
|string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) } |string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) }
), ),
Layout::Builtin(Builtin::EmptyList) => { Layout::Builtin(Builtin::EmptyList) => {
jit_map!(execution_engine, main_fn_name, &'static str, |_| { run_jit_function!(execution_engine, 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)) => jit_map!( Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
execution_engine, execution_engine,
main_fn_name, main_fn_name,
(*const libc::c_void, usize), (*const libc::c_void, usize),
@ -118,18 +103,26 @@ fn jit_to_ast_help<'a>(
} }
}; };
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
let layout = Layout::Struct(&fields);
match env.ptr_bytes { match env.ptr_bytes {
// 64-bit target (8-byte pointers, 16-byte structs) // 64-bit target (8-byte pointers, 16-byte structs)
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
jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| { run_jit_function!(
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) execution_engine,
}) main_fn_name,
[u8; 8],
|bytes: [u8; 8]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
}
)
} }
16 => { 16 => {
// two eightbytes, returned as-is // two eightbytes, returned as-is
jit_map!( run_jit_function!(
execution_engine, execution_engine,
main_fn_name, main_fn_name,
[u8; 16], [u8; 16],
@ -138,13 +131,13 @@ fn jit_to_ast_help<'a>(
} }
) )
} }
_ => { 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
jit_map!( run_jit_function_dynamic_type!(
execution_engine, execution_engine,
main_fn_name, main_fn_name,
*const u8, 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) }
) )
} }
@ -157,23 +150,33 @@ 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
jit_map!(execution_engine, main_fn_name, [u8; 4], |bytes: [u8; 4]| { run_jit_function!(
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) execution_engine,
}) main_fn_name,
[u8; 4],
|bytes: [u8; 4]| {
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
jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| { run_jit_function!(
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
}
_ => {
// anything more than 2 fourbytes
// the return "value" is a pointer to the result
jit_map!(
execution_engine, execution_engine,
main_fn_name, main_fn_name,
*const u8, [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,
main_fn_name,
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

@ -241,6 +241,15 @@ mod repl_eval {
); );
} }
#[test]
fn four_element_record() {
// if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help
expect_success(
"{ a: 1, b: 2, c: 3, d: 4 }",
"{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }",
);
}
// #[test] // #[test]
// fn multiline_string() { // fn multiline_string() {
// // If a string contains newlines, format it as a multiline string in the output // // If a string contains newlines, format it as a multiline string in the output

View file

@ -26,7 +26,7 @@ This `Expr` representation of the expression is useful for things like:
Since the parser is only concerned with translating String values into Expr values, it will happily translate syntactically valid strings into expressions that won't work at runtime. Since the parser is only concerned with translating String values into Expr values, it will happily translate syntactically valid strings into expressions that won't work at runtime.
For example, parsing will transalte this string: For example, parsing will translate this string:
not "foo", "bar" not "foo", "bar"
@ -87,11 +87,11 @@ The first thing it does is to call `eval` on the right `Expr` values on either s
Since the second call to `eval` will match on another `BinOp`, it's once again going to recursively call `eval` on both of its `Expr` values. Since those are both `Int` values, though, their `eval` calls will return them right away without doing anything else. Since the second call to `eval` will match on another `BinOp`, it's once again going to recursively call `eval` on both of its `Expr` values. Since those are both `Int` values, though, their `eval` calls will return them right away without doing anything else.
Now that it's evaluated the expressions on either side of the `Minus`, `eval` will look at the particular operator being applied to those expressoins (in this case, a minus operator) and check to see if the expressions it was given work with that operation. Now that it's evaluated the expressions on either side of the `Minus`, `eval` will look at the particular operator being applied to those expressions (in this case, a minus operator) and check to see if the expressions it was given work with that operation.
> Remember, this `Expr` value potentially came directly from the parser. `eval` can't be sure any type checking has been done on it! > Remember, this `Expr` value potentially came directly from the parser. `eval` can't be sure any type checking has been done on it!
If `eval` detects a non-numeric `Expr` value (that is, the `Expr` is not `Int` or `Frac`) on either side of the `Mnus`, then it will immediately give an error and halt the evaluation. This sort of runtime type error is common to dynamic languages, and you can think of `eval` as being a dynamic evaluation of Roc code that hasn't necessarily been type-checked. If `eval` detects a non-numeric `Expr` value (that is, the `Expr` is not `Int` or `Frac`) on either side of the `Minus`, then it will immediately give an error and halt the evaluation. This sort of runtime type error is common to dynamic languages, and you can think of `eval` as being a dynamic evaluation of Roc code that hasn't necessarily been type-checked.
Assuming there's no type problem, `eval` can go ahead and run the Rust code of `8 - 3` and store the result in an `Int` expr. Assuming there's no type problem, `eval` can go ahead and run the Rust code of `8 - 3` and store the result in an `Int` expr.

View file

@ -44,7 +44,7 @@ tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded",
# commit of TheDan64/inkwell, push a new tag which points to the latest commit, # commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# 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.release1" } inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
target-lexicon = "0.10" target-lexicon = "0.10"
[dev-dependencies] [dev-dependencies]

View file

@ -273,6 +273,9 @@ pub fn gen(
} }
} }
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
mpm.run_on(module); mpm.run_on(module);
// Verify the module // Verify the module

View file

@ -36,3 +36,12 @@ pub fn pow_int_(mut base: i64, mut exp: i64) -> i64 {
acc 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)
}

View file

@ -187,6 +187,26 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// addChecked : Num a, Num a -> Result (Num a) [ IntOverflow ]*
let overflow = SolvedType::TagUnion(
vec![(TagName::Global("Overflow".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_type(
Symbol::NUM_ADD_CHECKED,
SolvedType::Func(
vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))],
Box::new(result_type(num_type(flex(TVAR1)), overflow)),
),
);
// addWrap : Int, Int -> Int
add_type(
Symbol::NUM_ADD_WRAP,
SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())),
);
// sub or (-) : Num a, Num a -> Num a // sub or (-) : Num a, Num a -> Num a
add_type( add_type(
Symbol::NUM_SUB, Symbol::NUM_SUB,

View file

@ -274,6 +274,26 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![num_type(u, num), num_type(v, num)], num_type(w, num)) unique_function(vec![num_type(u, num), num_type(v, num)], num_type(w, num))
}); });
// addChecked : Num a, Num a -> Result (Num a) [ IntOverflow ]*
let overflow = SolvedType::TagUnion(
vec![(TagName::Global("Overflow".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_type(Symbol::NUM_ADD_CHECKED, {
let_tvars! { u, v, w, num, result, star };
unique_function(
vec![num_type(u, num), num_type(v, num)],
result_type(result, num_type(w, num), lift(star, overflow)),
)
});
// addWrap : Int, Int -> Int
add_type(Symbol::NUM_ADD_WRAP, {
let_tvars! { u, v, w };
unique_function(vec![int_type(u), int_type(v)], int_type(w))
});
// sub or (-) : Num a, Num a -> Num a // sub or (-) : Num a, Num a -> Num a
add_type(Symbol::NUM_SUB, { add_type(Symbol::NUM_SUB, {
let_tvars! { u, v, w, num }; let_tvars! { u, v, w, num };

View file

@ -68,6 +68,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_KEEP_IF => list_keep_if, Symbol::LIST_KEEP_IF => list_keep_if,
Symbol::LIST_WALK_RIGHT => list_walk_right, Symbol::LIST_WALK_RIGHT => list_walk_right,
Symbol::NUM_ADD => num_add, Symbol::NUM_ADD => num_add,
Symbol::NUM_ADD_CHECKED => num_add_checked,
Symbol::NUM_ADD_WRAP => num_add_wrap,
Symbol::NUM_SUB => num_sub, Symbol::NUM_SUB => num_sub,
Symbol::NUM_MUL => num_mul, Symbol::NUM_MUL => num_mul,
Symbol::NUM_GT => num_gt, Symbol::NUM_GT => num_gt,
@ -238,6 +240,105 @@ fn num_add(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumAdd) num_binop(symbol, var_store, LowLevel::NumAdd)
} }
/// Num.add : Num a, Num a -> Num a
fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumAddWrap)
}
/// Num.add : Num a, Num a -> Num a
fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh();
let num_var_2 = var_store.fresh();
let num_var_3 = var_store.fresh();
let ret_var = var_store.fresh();
let record_var = var_store.fresh();
// let arg_3 = RunLowLevel NumAddChecked arg_1 arg_2
//
// if arg_3.b then
// # overflow
// Err Overflow
// else
// # all is well
// Ok arg_3.a
let cont = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(
// arg_3.b
Access {
record_var,
ext_var: var_store.fresh(),
field: "b".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
},
),
// overflow!
no_region(tag(
"Err",
vec![tag("Overflow", Vec::new(), var_store)],
var_store,
)),
)],
final_else: Box::new(
// all is well
no_region(
// Ok arg_3.a
tag(
"Ok",
vec![
// arg_3.a
Access {
record_var,
ext_var: var_store.fresh(),
field: "a".into(),
field_var: num_var_3,
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
},
],
var_store,
),
),
),
};
// arg_3 = RunLowLevel NumAddChecked arg_1 arg_2
let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)),
loc_expr: no_region(RunLowLevel {
op: LowLevel::NumAddChecked,
args: vec![
(num_var_1, Var(Symbol::ARG_1)),
(num_var_2, Var(Symbol::ARG_2)),
],
ret_var: record_var,
}),
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
};
let body = LetNonRec(
Box::new(def),
Box::new(no_region(cont)),
ret_var,
SendMap::default(),
);
defn(
symbol,
vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
}
/// Num.sub : Num a, Num a -> Num a /// Num.sub : Num a, Num a -> Num a
fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSub) num_binop(symbol, var_store, LowLevel::NumSub)

View file

@ -38,7 +38,7 @@ inlinable_string = "0.1"
# commit of TheDan64/inkwell, push a new tag which points to the latest commit, # commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# 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.release1" } inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
target-lexicon = "0.10" target-lexicon = "0.10"
[dev-dependencies] [dev-dependencies]

View file

@ -13,3 +13,5 @@
pub mod layout_id; pub mod layout_id;
pub mod llvm; pub mod llvm;
pub mod run_roc;

View file

@ -270,6 +270,12 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
LLVM_FLOOR_F64, LLVM_FLOOR_F64,
f64_type.fn_type(&[f64_type.into()], false), f64_type.fn_type(&[f64_type.into()], false),
); );
add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I64, {
let fields = [i64_type.into(), i1_type.into()];
ctx.struct_type(&fields, false)
.fn_type(&[i64_type.into(), i64_type.into()], false)
});
} }
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
@ -282,6 +288,7 @@ static LLVM_COS_F64: &str = "llvm.cos.f64";
static LLVM_POW_F64: &str = "llvm.pow.f64"; static LLVM_POW_F64: &str = "llvm.pow.f64";
static LLVM_CEILING_F64: &str = "llvm.ceil.f64"; static LLVM_CEILING_F64: &str = "llvm.ceil.f64";
static LLVM_FLOOR_F64: &str = "llvm.floor.f64"; static LLVM_FLOOR_F64: &str = "llvm.floor.f64";
static LLVM_SADD_WITH_OVERFLOW_I64: &str = "llvm.sadd.with.overflow.i64";
fn add_intrinsic<'ctx>( fn add_intrinsic<'ctx>(
module: &Module<'ctx>, module: &Module<'ctx>,
@ -353,7 +360,10 @@ enum PassVia {
impl PassVia { impl PassVia {
fn from_layout(ptr_bytes: u32, layout: &Layout<'_>) -> Self { fn from_layout(ptr_bytes: u32, layout: &Layout<'_>) -> Self {
if layout.stack_size(ptr_bytes) > 16 { let stack_size = layout.stack_size(ptr_bytes);
let eightbyte = 8;
if stack_size > 2 * eightbyte {
PassVia::Memory PassVia::Memory
} else { } else {
PassVia::Register PassVia::Register
@ -361,14 +371,14 @@ impl PassVia {
} }
} }
pub fn make_main_function<'a, 'ctx, 'env>( /// entry point to roc code; uses the fastcc calling convention
pub fn build_roc_main<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>, layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>, main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) { ) -> &'a FunctionValue<'ctx> {
use inkwell::types::BasicType; use inkwell::types::BasicType;
use PassVia::*;
let context = env.context; let context = env.context;
let builder = env.builder; let builder = env.builder;
@ -386,7 +396,7 @@ pub fn make_main_function<'a, 'ctx, 'env>(
.module .module
.add_function(roc_main_fn_name, roc_main_fn_type, None); .add_function(roc_main_fn_name, roc_main_fn_type, None);
// our exposed main function adheres to the C calling convention // internal function, use fast calling convention
roc_main_fn.set_call_conventions(FAST_CALL_CONV); roc_main_fn.set_call_conventions(FAST_CALL_CONV);
// Add main's body // Add main's body
@ -403,17 +413,42 @@ pub fn make_main_function<'a, 'ctx, 'env>(
main_body, main_body,
); );
env.arena.alloc(roc_main_fn)
}
pub fn make_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
use inkwell::types::BasicType;
use PassVia::*;
let context = env.context;
let builder = env.builder;
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
// internal main function
let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body);
// build the C calling convention wrapper // build the C calling convention wrapper
let main_fn_name = "$Test.main"; let main_fn_name = "$Test.main";
let register_or_memory = PassVia::from_layout(env.ptr_bytes, layout);
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
let main_return_layout = Layout::Struct(&fields);
let main_return_type = block_of_memory(context, &main_return_layout, env.ptr_bytes);
let register_or_memory = PassVia::from_layout(env.ptr_bytes, &main_return_layout);
let main_fn_type = match register_or_memory { let main_fn_type = match register_or_memory {
Memory => { Memory => {
let return_value_ptr = context.i64_type().ptr_type(AddressSpace::Generic).into(); let return_value_ptr = context.i64_type().ptr_type(AddressSpace::Generic).into();
context.void_type().fn_type(&[return_value_ptr], false) context.void_type().fn_type(&[return_value_ptr], false)
} }
Register => return_type.fn_type(&[], false), Register => main_return_type.fn_type(&[], false),
}; };
// Add main to the module. // Add main to the module.
@ -424,35 +459,157 @@ pub fn make_main_function<'a, 'ctx, 'env>(
// Add main's body // Add main's body
let basic_block = context.append_basic_block(main_fn, "entry"); let basic_block = context.append_basic_block(main_fn, "entry");
let then_block = context.append_basic_block(main_fn, "then_block");
let catch_block = context.append_basic_block(main_fn, "catch_block");
let cont_block = context.append_basic_block(main_fn, "cont_block");
builder.position_at_end(basic_block); builder.position_at_end(basic_block);
let call = builder.build_call(roc_main_fn, &[], "call_roc_main"); let result_alloca = builder.build_alloca(main_return_type, "result");
call.set_call_convention(FAST_CALL_CONV);
let call_result = call.try_as_basic_value().left().unwrap(); // invoke instead of call, so that we can catch any exeptions thrown in Roc code
let call_result = {
let call = builder.build_invoke(roc_main_fn, &[], then_block, catch_block, "call_roc_main");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap()
};
match register_or_memory { // exception handling
Memory => { {
// write the result into the supplied pointer builder.position_at_end(catch_block);
// this is a void function, therefore return None
let ptr_return_type = return_type.ptr_type(AddressSpace::Generic);
let ptr_as_int = main_fn.get_first_param().unwrap(); let landing_pad_type = {
let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into();
let selector_value = context.i32_type().into();
let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr"); context.struct_type(&[exception_ptr, selector_value], false)
};
builder.build_store(ptr.into_pointer_value(), call_result); let info = builder
.build_catch_all_landing_pad(
&landing_pad_type,
&BasicValueEnum::IntValue(context.i8_type().const_zero()),
context.i8_type().ptr_type(AddressSpace::Generic),
"main_landing_pad",
)
.into_struct_value();
builder.build_return(None); let exception_ptr = builder
} .build_extract_value(info, 0, "exception_ptr")
Register => { .unwrap();
// construct a normal return
// values are passed to the caller via registers let thrown = cxa_begin_catch(env, exception_ptr);
builder.build_return(Some(&call_result));
let error_msg = {
let exception_type = u8_ptr;
let ptr = builder.build_bitcast(
thrown,
exception_type.ptr_type(AddressSpace::Generic),
"cast",
);
builder.build_load(ptr.into_pointer_value(), "error_msg")
};
let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false);
let return_value = {
let v1 = return_type.const_zero();
// flag is non-zero, indicating failure
let flag = context.i64_type().const_int(1, false);
let v2 = builder
.build_insert_value(v1, flag, 0, "set_error")
.unwrap();
let v3 = builder
.build_insert_value(v2, error_msg, 1, "set_exception")
.unwrap();
v3
};
// bitcast result alloca so we can store our concrete type { flag, error_msg } in there
let result_alloca_bitcast = builder
.build_bitcast(
result_alloca,
return_type.ptr_type(AddressSpace::Generic),
"result_alloca_bitcast",
)
.into_pointer_value();
// store our return value
builder.build_store(result_alloca_bitcast, return_value);
cxa_end_catch(env);
builder.build_unconditional_branch(cont_block);
}
{
builder.position_at_end(then_block);
let actual_return_type =
basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
let return_type =
context.struct_type(&[context.i64_type().into(), actual_return_type], false);
let return_value = {
let v1 = return_type.const_zero();
let v2 = builder
.build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error")
.unwrap();
let v3 = builder
.build_insert_value(v2, call_result, 1, "set_call_result")
.unwrap();
v3
};
let ptr = builder.build_bitcast(
result_alloca,
return_type.ptr_type(AddressSpace::Generic),
"name",
);
builder.build_store(ptr.into_pointer_value(), return_value);
builder.build_unconditional_branch(cont_block);
}
{
builder.position_at_end(cont_block);
let result = builder.build_load(result_alloca, "result");
match register_or_memory {
Memory => {
// write the result into the supplied pointer
let ptr_return_type = main_return_type.ptr_type(AddressSpace::Generic);
let ptr_as_int = main_fn.get_first_param().unwrap();
let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr");
builder.build_store(ptr.into_pointer_value(), result);
// this is a void function, therefore return None
builder.build_return(None);
}
Register => {
// construct a normal return
// values are passed to the caller via registers
builder.build_return(Some(&result));
}
} }
} }
// MUST set the personality at the very end;
// doing it earlier can cause the personality to be ignored
let personality_func = get_gxx_personality_v0(env);
main_fn.set_personality_function(personality_func);
(main_fn_name, env.arena.alloc(main_fn)) (main_fn_name, env.arena.alloc(main_fn))
} }
@ -790,8 +947,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
debug_assert!(*union_size > 1); debug_assert!(*union_size > 1);
let ptr_size = env.ptr_bytes; let ptr_size = env.ptr_bytes;
let mut filler = tag_layout.stack_size(ptr_size);
let ctx = env.context; let ctx = env.context;
let builder = env.builder; let builder = env.builder;
@ -828,16 +983,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
} else { } else {
field_vals.push(val); field_vals.push(val);
} }
filler -= field_size;
} }
} }
// TODO verify that this is required (better safe than sorry)
if filler > 0 {
field_types.push(env.context.i8_type().array_type(filler).into());
}
// Create the struct_type // Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let mut struct_val = struct_type.const_zero().into(); let mut struct_val = struct_type.const_zero().into();
@ -1385,7 +1533,14 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
build_exp_stmt(env, layout_ids, scope, parent, cont) build_exp_stmt(env, layout_ids, scope, parent, cont)
} }
_ => todo!("unsupported expr {:?}", stmt),
RuntimeError(error_msg) => {
throw_exception(env, error_msg);
// unused value (must return a BasicValue)
let zero = env.context.i64_type().const_zero();
zero.into()
}
} }
} }
@ -1957,7 +2112,7 @@ fn run_low_level<'a, 'ctx, 'env>(
list_join(env, inplace, parent, list, outer_list_layout) list_join(env, inplace, parent, list, outer_list_layout)
} }
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor
| NumToFloat => { | NumToFloat | NumIsFinite => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]); let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2068,7 +2223,7 @@ fn run_low_level<'a, 'ctx, 'env>(
} }
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
| NumDivUnchecked | NumPow | NumPowInt => { | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]); let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2083,6 +2238,7 @@ fn run_low_level<'a, 'ctx, 'env>(
match lhs_builtin { match lhs_builtin {
Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop( Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop(
env, env,
parent,
lhs_arg.into_int_value(), lhs_arg.into_int_value(),
lhs_layout, lhs_layout,
rhs_arg.into_int_value(), rhs_arg.into_int_value(),
@ -2091,6 +2247,7 @@ fn run_low_level<'a, 'ctx, 'env>(
), ),
Float128 | Float64 | Float32 | Float16 => build_float_binop( Float128 | Float64 | Float32 | Float16 => build_float_binop(
env, env,
parent,
lhs_arg.into_float_value(), lhs_arg.into_float_value(),
lhs_layout, lhs_layout,
rhs_arg.into_float_value(), rhs_arg.into_float_value(),
@ -2262,6 +2419,7 @@ where
fn build_int_binop<'a, 'ctx, 'env>( fn build_int_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
lhs: IntValue<'ctx>, lhs: IntValue<'ctx>,
_lhs_layout: &Layout<'a>, _lhs_layout: &Layout<'a>,
rhs: IntValue<'ctx>, rhs: IntValue<'ctx>,
@ -2274,7 +2432,37 @@ fn build_int_binop<'a, 'ctx, 'env>(
let bd = env.builder; let bd = env.builder;
match op { match op {
NumAdd => bd.build_int_add(lhs, rhs, "add_int").into(), NumAdd => {
let context = env.context;
let result = env
.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()])
.into_struct_value();
let add_result = bd.build_extract_value(result, 0, "add_result").unwrap();
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
let condition = bd.build_int_compare(
IntPredicate::EQ,
has_overflowed.into_int_value(),
context.bool_type().const_zero(),
"has_not_overflowed",
);
let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block");
bd.build_conditional_branch(condition, then_block, throw_block);
bd.position_at_end(throw_block);
throw_exception(env, "integer addition overflowed!");
bd.position_at_end(then_block);
add_result
}
NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(),
NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
NumSub => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumSub => bd.build_int_sub(lhs, rhs, "sub_int").into(),
NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(),
NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(),
@ -2311,6 +2499,7 @@ fn call_bitcode_fn<'a, 'ctx, 'env>(
fn build_float_binop<'a, 'ctx, 'env>( fn build_float_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
lhs: FloatValue<'ctx>, lhs: FloatValue<'ctx>,
_lhs_layout: &Layout<'a>, _lhs_layout: &Layout<'a>,
rhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>,
@ -2323,7 +2512,55 @@ fn build_float_binop<'a, 'ctx, 'env>(
let bd = env.builder; let bd = env.builder;
match op { match op {
NumAdd => bd.build_float_add(lhs, rhs, "add_float").into(), NumAdd => {
let builder = env.builder;
let context = env.context;
let result = bd.build_float_add(lhs, rhs, "add_float");
let is_finite =
call_bitcode_fn(NumIsFinite, env, &[result.into()], "is_finite_").into_int_value();
let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block");
builder.build_conditional_branch(is_finite, then_block, throw_block);
builder.position_at_end(throw_block);
throw_exception(env, "float addition overflowed!");
builder.position_at_end(then_block);
result.into()
}
NumAddChecked => {
let context = env.context;
let result = bd.build_float_add(lhs, rhs, "add_float");
let is_finite =
call_bitcode_fn(NumIsFinite, env, &[result.into()], "is_finite_").into_int_value();
let is_infinite = bd.build_not(is_finite, "negate");
let struct_type = context.struct_type(
&[context.f64_type().into(), context.bool_type().into()],
false,
);
let struct_value = {
let v1 = struct_type.const_zero();
let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap();
let v3 = bd
.build_insert_value(v2, is_infinite, 1, "set_is_infinite")
.unwrap();
v3.into_struct_value()
};
struct_value.into()
}
NumAddWrap => unreachable!("wrapping addition is not defined on floats"),
NumSub => bd.build_float_sub(lhs, rhs, "sub_float").into(), NumSub => bd.build_float_sub(lhs, rhs, "sub_float").into(),
NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(), NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(),
NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(), NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(),
@ -2427,8 +2664,260 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
env.context.i64_type(), env.context.i64_type(),
"num_floor", "num_floor",
), ),
NumIsFinite => call_bitcode_fn(NumIsFinite, env, &[arg.into()], "is_finite_"),
_ => { _ => {
unreachable!("Unrecognized int unary operation: {:?}", op); unreachable!("Unrecognized int unary operation: {:?}", op);
} }
} }
} }
fn define_global_str<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
message: &str,
) -> inkwell::values::GlobalValue<'ctx> {
let module = env.module;
// hash the name so we don't re-define existing messages
let name = {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
message.hash(&mut hasher);
let hash = hasher.finish();
format!("_Error_message_{}", hash)
};
match module.get_global(&name) {
Some(current) => current,
None => unsafe { env.builder.build_global_string(message, name.as_str()) },
}
}
fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) {
let context = env.context;
let builder = env.builder;
let info = {
// we represend both void and char pointers with `u8*`
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
// allocate an exception (that can hold a pointer to a string)
let str_ptr_size = env
.context
.i64_type()
.const_int(env.ptr_bytes as u64, false);
let initial = cxa_allocate_exception(env, str_ptr_size);
// define the error message as a global
// (a hash is used such that the same value is not defined repeatedly)
let error_msg_global = define_global_str(env, message);
// cast this to a void pointer
let error_msg_ptr =
builder.build_bitcast(error_msg_global.as_pointer_value(), u8_ptr, "unused");
// store this void pointer in the exception
let exception_type = u8_ptr;
let exception_value = error_msg_ptr;
let temp = builder
.build_bitcast(
initial,
exception_type.ptr_type(AddressSpace::Generic),
"exception_object_str_ptr_ptr",
)
.into_pointer_value();
builder.build_store(temp, exception_value);
initial
};
cxa_throw_exception(env, info);
builder.build_unreachable();
}
fn cxa_allocate_exception<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
exception_size: IntValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let name = "__cxa_allocate_exception";
let module = env.module;
let context = env.context;
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
let function = match module.get_function(&name) {
Some(gvalue) => gvalue,
None => {
// void *__cxa_allocate_exception(size_t thrown_size);
let cxa_allocate_exception = module.add_function(
name,
u8_ptr.fn_type(&[context.i64_type().into()], false),
Some(Linkage::External),
);
cxa_allocate_exception.set_call_conventions(C_CALL_CONV);
cxa_allocate_exception
}
};
let call = env.builder.build_call(
function,
&[exception_size.into()],
"exception_object_void_ptr",
);
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value().left().unwrap()
}
fn cxa_throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, info: BasicValueEnum<'ctx>) {
let name = "__cxa_throw";
let module = env.module;
let context = env.context;
let builder = env.builder;
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
let function = match module.get_function(&name) {
Some(value) => value,
None => {
// void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) );
let cxa_throw = module.add_function(
name,
context
.void_type()
.fn_type(&[u8_ptr.into(), u8_ptr.into(), u8_ptr.into()], false),
Some(Linkage::External),
);
cxa_throw.set_call_conventions(C_CALL_CONV);
cxa_throw
}
};
// global storing the type info of a c++ int (equivalent to `i32` in llvm)
// we just need any valid such value, and arbitrarily use this one
let ztii = match module.get_global("_ZTIi") {
Some(gvalue) => gvalue.as_pointer_value(),
None => {
let ztii = module.add_global(u8_ptr, Some(AddressSpace::Generic), "_ZTIi");
ztii.set_linkage(Linkage::External);
ztii.as_pointer_value()
}
};
let type_info = builder.build_bitcast(ztii, u8_ptr, "cast");
let null: BasicValueEnum = u8_ptr.const_zero().into();
let call = builder.build_call(function, &[info, type_info, null], "throw");
call.set_call_convention(C_CALL_CONV);
}
#[allow(dead_code)]
fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
let name = "__cxa_rethrow";
let module = env.module;
let context = env.context;
let function = match module.get_function(&name) {
Some(gvalue) => gvalue,
None => {
let cxa_rethrow = module.add_function(
name,
context.void_type().fn_type(&[], false),
Some(Linkage::External),
);
cxa_rethrow.set_call_conventions(C_CALL_CONV);
cxa_rethrow
}
};
let call = env.builder.build_call(function, &[], "never_used");
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value().left().unwrap()
}
fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> {
let name = "__cxa_rethrow";
let module = env.module;
let context = env.context;
match module.get_function(&name) {
Some(gvalue) => gvalue,
None => {
let personality_func = module.add_function(
"__gxx_personality_v0",
context.i64_type().fn_type(&[], false),
Some(Linkage::External),
);
personality_func.set_call_conventions(C_CALL_CONV);
personality_func
}
}
}
fn cxa_end_catch<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) {
let name = "__cxa_end_catch";
let module = env.module;
let context = env.context;
let function = match module.get_function(&name) {
Some(gvalue) => gvalue,
None => {
let cxa_end_catch = module.add_function(
name,
context.void_type().fn_type(&[], false),
Some(Linkage::External),
);
cxa_end_catch.set_call_conventions(C_CALL_CONV);
cxa_end_catch
}
};
let call = env.builder.build_call(function, &[], "never_used");
call.set_call_convention(C_CALL_CONV);
}
fn cxa_begin_catch<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
exception_ptr: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let name = "__cxa_begin_catch";
let module = env.module;
let context = env.context;
let function = match module.get_function(&name) {
Some(gvalue) => gvalue,
None => {
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
let cxa_begin_catch = module.add_function(
"__cxa_begin_catch",
u8_ptr.fn_type(&[u8_ptr.into()], false),
Some(Linkage::External),
);
cxa_begin_catch.set_call_conventions(C_CALL_CONV);
cxa_begin_catch
}
};
let call = env
.builder
.build_call(function, &[exception_ptr], "exception_payload_ptr");
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value().left().unwrap()
}

View file

@ -1570,6 +1570,26 @@ pub fn list_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) ->
) )
} }
pub fn load_list<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
ptr_type: PointerType<'ctx>,
) -> (IntValue<'ctx>, PointerValue<'ctx>) {
let ptr_as_int = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap()
.into_int_value();
let ptr = builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr");
let length = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len")
.unwrap()
.into_int_value();
(length, ptr)
}
pub fn load_list_ptr<'ctx>( pub fn load_list_ptr<'ctx>(
builder: &Builder<'ctx>, builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>, wrapper_struct: StructValue<'ctx>,

Binary file not shown.

View file

@ -100,7 +100,7 @@ pub fn decrement_refcount_layout<'a, 'ctx, 'env>(
#[inline(always)] #[inline(always)]
fn decrement_refcount_builtin<'a, 'ctx, 'env>( fn decrement_refcount_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
@ -109,14 +109,37 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>(
use Builtin::*; use Builtin::*;
match builtin { match builtin {
List(MemoryMode::Refcounted, element_layout) => { List(memory_mode, element_layout) => {
let wrapper_struct = value.into_struct_value();
if element_layout.contains_refcounted() { if element_layout.contains_refcounted() {
// TODO decrement all values use crate::llvm::build_list::{incrementing_elem_loop, load_list};
use inkwell::types::BasicType;
let ptr_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, wrapper_struct, ptr_type);
let loop_fn = |_index, element| {
decrement_refcount_layout(env, parent, layout_ids, element, element_layout);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"dec_index",
loop_fn,
);
} }
build_dec_list(env, layout_ids, layout, value.into_struct_value());
} if let MemoryMode::Refcounted = memory_mode {
List(MemoryMode::Unique, _element_layout) => { build_inc_list(env, layout_ids, layout, wrapper_struct);
// do nothing }
build_dec_list(env, layout_ids, layout, wrapper_struct);
} }
Set(element_layout) => { Set(element_layout) => {
if element_layout.contains_refcounted() { if element_layout.contains_refcounted() {
@ -158,7 +181,7 @@ pub fn increment_refcount_layout<'a, 'ctx, 'env>(
#[inline(always)] #[inline(always)]
fn increment_refcount_builtin<'a, 'ctx, 'env>( fn increment_refcount_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
@ -167,15 +190,36 @@ fn increment_refcount_builtin<'a, 'ctx, 'env>(
use Builtin::*; use Builtin::*;
match builtin { match builtin {
List(MemoryMode::Refcounted, element_layout) => { List(memory_mode, element_layout) => {
if element_layout.contains_refcounted() {
// TODO decrement all values
}
let wrapper_struct = value.into_struct_value(); let wrapper_struct = value.into_struct_value();
build_inc_list(env, layout_ids, layout, wrapper_struct); if element_layout.contains_refcounted() {
} use crate::llvm::build_list::{incrementing_elem_loop, load_list};
List(MemoryMode::Unique, _element_layout) => { use inkwell::types::BasicType;
// do nothing
let ptr_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, wrapper_struct, ptr_type);
let loop_fn = |_index, element| {
increment_refcount_layout(env, parent, layout_ids, element, element_layout);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"inc_index",
loop_fn,
);
}
if let MemoryMode::Refcounted = memory_mode {
build_inc_list(env, layout_ids, layout, wrapper_struct);
}
} }
Set(element_layout) => { Set(element_layout) => {
if element_layout.contains_refcounted() { if element_layout.contains_refcounted() {

124
compiler/gen/src/run_roc.rs Normal file
View file

@ -0,0 +1,124 @@
use std::ffi::CString;
use std::os::raw::c_char;
#[repr(C)]
union Payload<T: Copy> {
success: T,
failure: *mut c_char,
}
#[repr(C)]
pub struct RocCallResult<T: Copy> {
pub flag: u64,
payload: Payload<T>,
}
impl<T: Copy> Into<Result<T, String>> for RocCallResult<T> {
fn into(self) -> Result<T, String> {
if self.flag == 0 {
Ok(unsafe { self.payload.success })
} else {
Err(unsafe {
let raw = CString::from_raw(self.payload.failure);
let result = format!("{:?}", raw);
// make sure rust does not try to free the Roc string
std::mem::forget(raw);
result
})
}
}
}
#[macro_export]
macro_rules! run_jit_function {
($execution_engine: 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)
}};
($execution_engine: 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)
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
match main.call().into() {
Ok(success) => {
// only if there are no exceptions thrown, check for errors
assert_eq!(
$errors,
std::vec::Vec::new(),
"Encountered errors: {:?}",
$errors
);
$transform(success)
}
Err(error_msg) => panic!("Roc failed with message: {}", error_msg),
}
}
}};
}
/// In the repl, we don't know the type that is returned; it it's large enough to not fit in 2
/// registers (i.e. size bigger than 16 bytes on 64-bit systems), then we use this macro.
/// 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) => {{
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)
}};
($execution_engine: 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)
.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);
let flag = *result;
if flag == 0 {
$transform(result.offset(8) as *const u8)
} else {
use std::ffi::CString;
use std::os::raw::c_char;
// first field is a char pointer (to the error message)
// read value, and transmute to a pointer
let ptr_as_int = *(result as *const u64).offset(1);
let ptr = std::mem::transmute::<u64, *mut c_char>(ptr_as_int);
// make CString (null-terminated)
let raw = CString::from_raw(ptr);
let result = format!("{:?}", raw);
// make sure rust doesn't try to free the Roc constant string
std::mem::forget(raw);
eprintln!("{}", result);
panic!("Roc hit an error");
}
}
}};
}

View file

@ -663,12 +663,14 @@ mod gen_list {
#[test] #[test]
fn list_concat_large() { fn list_concat_large() {
// these values produce mono ASTs so large that with_larger_debug_stack(|| {
// it can cause a stack overflow. This has been solved // these values produce mono ASTs so large that
// for current code, but may become a problem again in the future. // it can cause a stack overflow. This has been solved
assert_concat_worked(150, 150); // for current code, but may become a problem again in the future.
assert_concat_worked(129, 350); assert_concat_worked(150, 150);
assert_concat_worked(350, 129); assert_concat_worked(129, 350);
assert_concat_worked(350, 129);
})
} }
#[test] #[test]

View file

@ -685,4 +685,102 @@ mod gen_num {
fn pow_int() { fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64); assert_evals_to!("Num.powInt 2 3", 8, 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() {
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 1 2 is
Ok v -> v
_ -> -1
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 9_223_372_036_854_775_807 1 is
Err Overflow -> -1
Ok v -> v
"#
),
-1,
i64
);
}
#[test]
fn int_add_wrap() {
assert_evals_to!(
indoc!(
r#"
Num.addWrap 9_223_372_036_854_775_807 1
"#
),
std::i64::MIN,
i64
);
}
#[test]
fn float_add_checked_pass() {
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 1.0 0.0 is
Ok v -> v
Err Overflow -> -1.0
"#
),
1.0,
f64
);
}
#[test]
fn float_add_checked_fail() {
assert_evals_to!(
indoc!(
r#"
when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is
Err Overflow -> -1
Ok v -> v
"#
),
-1.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

@ -806,4 +806,21 @@ mod gen_primitives {
i64 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
);
}
} }

View file

@ -664,6 +664,19 @@ mod gen_records {
); );
} }
#[test]
fn return_nested_record() {
assert_evals_to!(
indoc!(
r#"
{ flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
"#
),
(0x0, (6.28, 3.14, 0.1)),
(i64, (f64, f64, f64))
);
}
#[test] #[test]
fn just_to_be_sure() { fn just_to_be_sure() {
assert_evals_to!( assert_evals_to!(

View file

@ -6,7 +6,11 @@ pub fn helper_without_uniqueness<'a>(
src: &str, src: &str,
leak: bool, leak: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) { ) -> (
&'static str,
Vec<roc_problem::can::Problem>,
inkwell::execution_engine::ExecutionEngine<'a>,
) {
use crate::helpers::{can_expr, infer_expr, CanExprOut}; use crate::helpers::{can_expr, infer_expr, CanExprOut};
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header}; use roc_gen::llvm::build::{build_proc, build_proc_header};
@ -24,6 +28,8 @@ pub fn helper_without_uniqueness<'a>(
problems, problems,
.. ..
} = can_expr(src); } = can_expr(src);
// don't panic based on the errors here, so we can test that RuntimeError generates the correct code
let errors = problems let errors = problems
.into_iter() .into_iter()
.filter(|problem| { .filter(|problem| {
@ -37,8 +43,6 @@ pub fn helper_without_uniqueness<'a>(
}) })
.collect::<Vec<roc_problem::can::Problem>>(); .collect::<Vec<roc_problem::can::Problem>>();
assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors);
let subs = Subs::new(var_store.into()); let subs = Subs::new(var_store.into());
let mut unify_problems = Vec::new(); let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
@ -178,7 +182,7 @@ pub fn helper_without_uniqueness<'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, execution_engine.clone()) (main_fn_name, errors, execution_engine.clone())
} }
pub fn helper_with_uniqueness<'a>( pub fn helper_with_uniqueness<'a>(
@ -360,7 +364,7 @@ macro_rules! assert_opt_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::JitFunction; use roc_gen::run_jit_function;
let arena = Bump::new(); let arena = Bump::new();
@ -369,15 +373,8 @@ macro_rules! assert_opt_evals_to {
let (main_fn_name, execution_engine) = let (main_fn_name, execution_engine) =
$crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context); $crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context);
unsafe { let transform = |success| assert_eq!($transform(success), $expected);
let main: JitFunction<unsafe extern "C" fn() -> $ty> = execution_engine run_jit_function!(execution_engine, main_fn_name, $ty, transform)
.get_function(main_fn_name)
.ok()
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
.expect("errored");
assert_eq!($transform(main.call()), $expected);
}
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
@ -390,24 +387,17 @@ macro_rules! assert_llvm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::JitFunction; use roc_gen::run_jit_function;
let arena = Bump::new(); let arena = Bump::new();
let context = Context::create(); let context = Context::create();
let (main_fn_name, execution_engine) = let (main_fn_name, errors, execution_engine) =
$crate::helpers::eval::helper_without_uniqueness(&arena, $src, $leak, &context); $crate::helpers::eval::helper_without_uniqueness(&arena, $src, $leak, &context);
unsafe { let transform = |success| assert_eq!($transform(success), $expected);
let main: JitFunction<unsafe extern "C" fn() -> $ty> = execution_engine run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors)
.get_function(main_fn_name)
.ok()
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
.expect("errored");
assert_eq!($transform(main.call()), $expected);
}
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {

View file

@ -21,6 +21,8 @@ pub enum LowLevel {
ListKeepIf, ListKeepIf,
ListWalkRight, ListWalkRight,
NumAdd, NumAdd,
NumAddWrap,
NumAddChecked,
NumSub, NumSub,
NumMul, NumMul,
NumGt, NumGt,
@ -41,6 +43,7 @@ pub enum LowLevel {
NumCeiling, NumCeiling,
NumPowInt, NumPowInt,
NumFloor, NumFloor,
NumIsFinite,
Eq, Eq,
NotEq, NotEq,
And, And,

View file

@ -644,6 +644,8 @@ define_builtins! {
39 NUM_CEILING: "ceiling" 39 NUM_CEILING: "ceiling"
40 NUM_POW_INT: "powInt" 40 NUM_POW_INT: "powInt"
41 NUM_FLOOR: "floor" 41 NUM_FLOOR: "floor"
42 NUM_ADD_WRAP: "addWrap"
43 NUM_ADD_CHECKED: "addChecked"
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -521,12 +521,11 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]), ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]),
ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
Eq | NotEq | And | Or | NumAdd | NumSub | NumMul | NumGt | NumGte | NumLt | NumLte Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt => { | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow
arena.alloc_slice_copy(&[irrelevant, irrelevant]) | NumPowInt => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
}
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
| NumToFloat | Not => arena.alloc_slice_copy(&[irrelevant]), | NumToFloat | Not | NumIsFinite => arena.alloc_slice_copy(&[irrelevant]),
} }
} }

View file

@ -46,7 +46,7 @@ tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded",
# commit of TheDan64/inkwell, push a new tag which points to the latest commit, # commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# 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.release1" } inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
target-lexicon = "0.10" target-lexicon = "0.10"
winit = "0.22" winit = "0.22"
wgpu = "0.5" wgpu = "0.5"

View file

@ -17,6 +17,7 @@ These are potentially inspirational resources for the editor's design.
* [Unity game engine](https://unity.com/) * [Unity game engine](https://unity.com/)
* Scripts can expose values as text inputs, sliders, checkboxes, etc or even generate custom graphical inputs * Scripts can expose values as text inputs, sliders, checkboxes, etc or even generate custom graphical inputs
* Drag-n-drop game objects and component into script interfaces * Drag-n-drop game objects and component into script interfaces
* [How to Visualize Data Structures in VS Code](https://addyosmani.com/blog/visualize-data-structures-vscode/)
### Live Interactivity ### Live Interactivity
@ -27,6 +28,11 @@ These are potentially inspirational resources for the editor's design.
* [Xi](https://xi-editor.io/) modern text editor with concurrent editing (related to [Druid](https://github.com/linebender/druid)) * [Xi](https://xi-editor.io/) modern text editor with concurrent editing (related to [Druid](https://github.com/linebender/druid))
* [Self](https://selflanguage.org/) programming language * [Self](https://selflanguage.org/) programming language
### Debugging
* [VS code debug visualization](https://marketplace.visualstudio.com/items?itemName=hediet.debug-visualizer)
* [Algorithm visualization for javascript](https://algorithm-visualizer.org)
### Structured Editing ### Structured Editing
* [Deuce](http://ravichugh.github.io/sketch-n-sketch/) (videos on the right) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/) and others * [Deuce](http://ravichugh.github.io/sketch-n-sketch/) (videos on the right) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/) and others

View file

@ -682,7 +682,7 @@ has the fields `x` and `y`.
In Roc, you can do this like so: In Roc, you can do this like so:
```elm ```elm
table { height = 800, width = 600 } table { height: 800, width: 600 }
``` ```
...and the `table` function will fill in its default values for `x` and `y`. ...and the `table` function will fill in its default values for `x` and `y`.