Merge branch 'trunk' into feature/docs-read-parse-file

This commit is contained in:
Pablo Hirafuji 2020-09-27 19:41:47 -03:00
commit d68d8b4347
39 changed files with 1438 additions and 173 deletions

View File

@ -9,6 +9,9 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Verify compiler/builtin/bitcode/regenerate.sh was run if necessary
run: pushd compiler/builtins/bitcode && ./regenerate.sh && git diff --exit-code ../../gen/src/llvm/builtins.bc && popd
- name: Install LLVM
run: sudo ./ci/install-llvm.sh 10

View File

@ -1,9 +1,9 @@
# Building the Roc compiler from source
## Installing LLVM
## Installing LLVM and libc++abi
To build the compiler, you need a particular version of LLVM installed on your system.
To build the compiler, you need both `libc++abi` and a particular version of LLVM installed on your system. Some systems may already have `libc++abi` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `apt-get install libc++abi-dev`.)
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.
@ -36,6 +36,8 @@ $ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50f
$ 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
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

@ -1112,7 +1112,7 @@ dependencies = [
[[package]]
name = "inkwell"
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 = [
"either",
"inkwell_internals",
@ -1126,7 +1126,7 @@ dependencies = [
[[package]]
name = "inkwell_internals"
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 = [
"proc-macro2 0.4.30",
"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.
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:

View File

@ -76,7 +76,7 @@ libc = "0.2"
# 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`.
# 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"
[dev-dependencies]

View File

@ -210,6 +210,12 @@ fn build_file(
"host.rs",
"-o",
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)
.spawn()

View File

@ -1,7 +1,8 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use inkwell::execution_engine::{ExecutionEngine, JitFunction};
use inkwell::execution_engine::ExecutionEngine;
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::operator::CalledVia;
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)
}
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>(
env: &Env<'a, '_>,
execution_engine: ExecutionEngine,
@ -72,32 +59,30 @@ fn jit_to_ast_help<'a>(
content: &Content,
) -> Expr<'a> {
match layout {
Layout::Builtin(Builtin::Int64) => {
jit_map!(execution_engine, main_fn_name, i64, |num| num_to_ast(
env,
i64_to_ast(env.arena, num),
content
))
}
Layout::Builtin(Builtin::Float64) => {
jit_map!(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) => jit_map!(
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) => {
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))
})
}
Layout::Builtin(Builtin::List(_, elem_layout)) => jit_map!(
Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
execution_engine,
main_fn_name,
(*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 {
// 64-bit target (8-byte pointers, 16-byte structs)
8 => match layout.stack_size(env.ptr_bytes) {
8 => {
// just one eightbyte, returned as-is
jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
run_jit_function!(
execution_engine,
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
jit_map!(
run_jit_function!(
execution_engine,
main_fn_name,
[u8; 16],
@ -138,13 +131,13 @@ fn jit_to_ast_help<'a>(
}
)
}
_ => {
larger_size => {
// anything more than 2 eightbytes
// the return "value" is a pointer to the result
jit_map!(
run_jit_function_dynamic_type!(
execution_engine,
main_fn_name,
*const u8,
larger_size as usize,
|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) {
4 => {
// just one fourbyte, returned as-is
jit_map!(execution_engine, main_fn_name, [u8; 4], |bytes: [u8; 4]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
run_jit_function!(
execution_engine,
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
jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
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!(
run_jit_function!(
execution_engine,
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) }
)
}

View File

@ -1,4 +1,4 @@
#[macro_use]
// #[macro_use]
extern crate pretty_assertions;
extern crate bumpalo;
@ -20,7 +20,9 @@ mod cli_run {
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]);
assert_eq!(&out.stderr, "");
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(&out.stdout.ends_with("Hello, World!!!!!!!!!!!!!\n"));
assert!(out.status.success());
}
@ -33,7 +35,9 @@ mod cli_run {
"--optimize",
]);
assert_eq!(&out.stderr, "");
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(&out
.stdout
.ends_with("[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n"));

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]
// fn multiline_string() {
// // 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.
For example, parsing will transalte this string:
For example, parsing will translate this string:
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.
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!
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.

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,
# 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.
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"
[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);
// Verify the module

View File

@ -17,42 +17,37 @@ The source we'll use to generate the bitcode is in `src/lib.rs` in this director
To generate the bitcode, `cd` into `compiler/builtins/bitcode/` and run:
```bash
$ cargo rustc --release --lib -- --emit=llvm-bc
$ ./regenerate.sh
```
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
> ```
>
> 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.
## 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.
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
```
...so you want to overwrite it with the new `.bc` file in `target/deps/`
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!

View File

@ -0,0 +1,19 @@
#!/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

@ -4,6 +4,8 @@
#![crate_type = "lib"]
#![no_std]
mod libm;
/// 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]
@ -36,3 +38,17 @@ pub fn pow_int_(mut base: i64, mut exp: i64) -> i64 {
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

@ -0,0 +1,199 @@
/// 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

@ -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
add_type(
Symbol::NUM_SUB,
@ -436,6 +456,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
SolvedType::Func(vec![float_type()], Box::new(int_type())),
);
// atan : Float -> Float
add_type(
Symbol::NUM_ATAN,
SolvedType::Func(vec![float_type()], Box::new(float_type())),
);
// Bool module
// and : Bool, Bool -> Bool

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))
});
// 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
add_type(Symbol::NUM_SUB, {
let_tvars! { u, v, w, num };
@ -475,6 +495,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![float_type(star1)], int_type(star2))
});
// atan : Float -> Float
add_type(Symbol::NUM_ATAN, {
let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], float_type(star2))
});
// Bool module
// isEq or (==) : Attr * a, Attr * a -> Attr * Bool

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_WALK_RIGHT => list_walk_right,
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_MUL => num_mul,
Symbol::NUM_GT => num_gt,
@ -95,6 +97,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_CEILING => num_ceiling,
Symbol::NUM_POW_INT => num_pow_int,
Symbol::NUM_FLOOR => num_floor,
Symbol::NUM_ATAN => num_atan,
}
}
@ -238,6 +241,105 @@ fn num_add(symbol: Symbol, var_store: &mut VarStore) -> Def {
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
fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSub)
@ -664,6 +766,26 @@ fn num_floor(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.atan : Float -> Float
fn num_atan(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_float_var = var_store.fresh();
let ret_float_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumAtan,
args: vec![(arg_float_var, Var(Symbol::ARG_1))],
ret_var: ret_float_var,
};
defn(
symbol,
vec![(arg_float_var, Symbol::ARG_1)],
var_store,
body,
ret_float_var,
)
}
/// List.isEmpty : List * -> Bool
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();

View File

@ -38,7 +38,7 @@ inlinable_string = "0.1"
# 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`.
# 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"
[dev-dependencies]

View File

@ -13,3 +13,5 @@
pub mod layout_id;
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,
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";
@ -282,6 +288,7 @@ static LLVM_COS_F64: &str = "llvm.cos.f64";
static LLVM_POW_F64: &str = "llvm.pow.f64";
static LLVM_CEILING_F64: &str = "llvm.ceil.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>(
module: &Module<'ctx>,
@ -353,7 +360,10 @@ enum PassVia {
impl PassVia {
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
} else {
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>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
) -> &'a FunctionValue<'ctx> {
use inkwell::types::BasicType;
use PassVia::*;
let context = env.context;
let builder = env.builder;
@ -386,7 +396,7 @@ pub fn make_main_function<'a, 'ctx, 'env>(
.module
.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);
// Add main's body
@ -403,17 +413,42 @@ pub fn make_main_function<'a, 'ctx, 'env>(
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
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 {
Memory => {
let return_value_ptr = context.i64_type().ptr_type(AddressSpace::Generic).into();
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.
@ -424,35 +459,157 @@ pub fn make_main_function<'a, 'ctx, 'env>(
// Add main's body
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);
let call = builder.build_call(roc_main_fn, &[], "call_roc_main");
call.set_call_convention(FAST_CALL_CONV);
let result_alloca = builder.build_alloca(main_return_type, "result");
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 {
Memory => {
// write the result into the supplied pointer
// this is a void function, therefore return None
let ptr_return_type = return_type.ptr_type(AddressSpace::Generic);
// exception handling
{
builder.position_at_end(catch_block);
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);
}
Register => {
// construct a normal return
// values are passed to the caller via registers
builder.build_return(Some(&call_result));
let exception_ptr = builder
.build_extract_value(info, 0, "exception_ptr")
.unwrap();
let thrown = cxa_begin_catch(env, exception_ptr);
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))
}
@ -790,8 +947,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
debug_assert!(*union_size > 1);
let ptr_size = env.ptr_bytes;
let mut filler = tag_layout.stack_size(ptr_size);
let ctx = env.context;
let builder = env.builder;
@ -828,16 +983,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
} else {
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
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
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)
}
_ => 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()
}
}
}
@ -1951,7 +2106,7 @@ fn run_low_level<'a, 'ctx, 'env>(
list_join(env, inplace, parent, list, outer_list_layout)
}
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor
| NumToFloat => {
| NumToFloat | NumIsFinite | NumAtan => {
debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2062,7 +2217,7 @@ fn run_low_level<'a, 'ctx, 'env>(
}
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
| NumDivUnchecked | NumPow | NumPowInt => {
| NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt => {
debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2077,6 +2232,7 @@ fn run_low_level<'a, 'ctx, 'env>(
match lhs_builtin {
Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop(
env,
parent,
lhs_arg.into_int_value(),
lhs_layout,
rhs_arg.into_int_value(),
@ -2085,6 +2241,7 @@ fn run_low_level<'a, 'ctx, 'env>(
),
Float128 | Float64 | Float32 | Float16 => build_float_binop(
env,
parent,
lhs_arg.into_float_value(),
lhs_layout,
rhs_arg.into_float_value(),
@ -2256,6 +2413,7 @@ where
fn build_int_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
lhs: IntValue<'ctx>,
_lhs_layout: &Layout<'a>,
rhs: IntValue<'ctx>,
@ -2268,7 +2426,37 @@ fn build_int_binop<'a, 'ctx, 'env>(
let bd = env.builder;
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(),
NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(),
NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(),
@ -2305,6 +2493,7 @@ fn call_bitcode_fn<'a, 'ctx, 'env>(
fn build_float_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
lhs: FloatValue<'ctx>,
_lhs_layout: &Layout<'a>,
rhs: FloatValue<'ctx>,
@ -2317,7 +2506,55 @@ fn build_float_binop<'a, 'ctx, 'env>(
let bd = env.builder;
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(),
NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(),
NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(),
@ -2421,8 +2658,261 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
env.context.i64_type(),
"num_floor",
),
NumIsFinite => call_bitcode_fn(NumIsFinite, env, &[arg.into()], "is_finite_"),
NumAtan => call_bitcode_fn(NumAtan, env, &[arg.into()], "atan_"),
_ => {
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>(
builder: &Builder<'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)]
fn decrement_refcount_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
@ -109,14 +109,37 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>(
use 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() {
// 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());
}
List(MemoryMode::Unique, _element_layout) => {
// do nothing
if let MemoryMode::Refcounted = memory_mode {
build_inc_list(env, layout_ids, layout, wrapper_struct);
}
build_dec_list(env, layout_ids, layout, wrapper_struct);
}
Set(element_layout) => {
if element_layout.contains_refcounted() {
@ -158,7 +181,7 @@ pub fn increment_refcount_layout<'a, 'ctx, 'env>(
#[inline(always)]
fn increment_refcount_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
@ -167,15 +190,36 @@ fn increment_refcount_builtin<'a, 'ctx, 'env>(
use Builtin::*;
match builtin {
List(MemoryMode::Refcounted, element_layout) => {
if element_layout.contains_refcounted() {
// TODO decrement all values
}
List(memory_mode, element_layout) => {
let wrapper_struct = value.into_struct_value();
build_inc_list(env, layout_ids, layout, wrapper_struct);
}
List(MemoryMode::Unique, _element_layout) => {
// do nothing
if element_layout.contains_refcounted() {
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| {
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) => {
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]
fn list_concat_large() {
// these values produce mono ASTs so large that
// it can cause a stack overflow. This has been solved
// for current code, but may become a problem again in the future.
assert_concat_worked(150, 150);
assert_concat_worked(129, 350);
assert_concat_worked(350, 129);
with_larger_debug_stack(|| {
// these values produce mono ASTs so large that
// it can cause a stack overflow. This has been solved
// for current code, but may become a problem again in the future.
assert_concat_worked(150, 150);
assert_concat_worked(129, 350);
assert_concat_worked(350, 129);
})
}
#[test]

View File

@ -685,4 +685,107 @@ mod gen_num {
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
#[test]
fn atan() {
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]
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
);
}
#[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]
fn just_to_be_sure() {
assert_evals_to!(

View File

@ -6,7 +6,11 @@ pub fn helper_without_uniqueness<'a>(
src: &str,
leak: bool,
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 inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header};
@ -24,6 +28,8 @@ pub fn helper_without_uniqueness<'a>(
problems,
..
} = can_expr(src);
// don't panic based on the errors here, so we can test that RuntimeError generates the correct code
let errors = problems
.into_iter()
.filter(|problem| {
@ -37,8 +43,6 @@ pub fn helper_without_uniqueness<'a>(
})
.collect::<Vec<roc_problem::can::Problem>>();
assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors);
let subs = Subs::new(var_store.into());
let mut unify_problems = Vec::new();
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:
// env.module.print_to_stderr();
(main_fn_name, execution_engine.clone())
(main_fn_name, errors, execution_engine.clone())
}
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) => {
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use roc_gen::run_jit_function;
let arena = Bump::new();
@ -369,15 +373,8 @@ macro_rules! assert_opt_evals_to {
let (main_fn_name, execution_engine) =
$crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context);
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");
assert_eq!($transform(main.call()), $expected);
}
let transform = |success| assert_eq!($transform(success), $expected);
run_jit_function!(execution_engine, main_fn_name, $ty, transform)
};
($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) => {
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use roc_gen::run_jit_function;
let arena = Bump::new();
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);
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");
assert_eq!($transform(main.call()), $expected);
}
let transform = |success| assert_eq!($transform(success), $expected);
run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors)
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {

View File

@ -20,6 +20,8 @@ pub enum LowLevel {
ListKeepIf,
ListWalkRight,
NumAdd,
NumAddWrap,
NumAddChecked,
NumSub,
NumMul,
NumGt,
@ -40,6 +42,8 @@ pub enum LowLevel {
NumCeiling,
NumPowInt,
NumFloor,
NumIsFinite,
NumAtan,
Eq,
NotEq,
And,

View File

@ -644,6 +644,9 @@ define_builtins! {
39 NUM_CEILING: "ceiling"
40 NUM_POW_INT: "powInt"
41 NUM_FLOOR: "floor"
42 NUM_ADD_WRAP: "addWrap"
43 NUM_ADD_CHECKED: "addChecked"
44 NUM_ATAN: "atan"
}
2 BOOL: "Bool" => {
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]),
ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
Eq | NotEq | And | Or | NumAdd | NumSub | NumMul | NumGt | NumGte | NumLt | NumLte
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt => {
arena.alloc_slice_copy(&[irrelevant, irrelevant])
}
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt
| NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow
| NumPowInt => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
| NumToFloat | Not => arena.alloc_slice_copy(&[irrelevant]),
| NumToFloat | Not | NumIsFinite | NumAtan => arena.alloc_slice_copy(&[irrelevant]),
}
}

View File

@ -2430,6 +2430,18 @@ mod solve_expr {
);
}
#[test]
fn atan() {
infer_eq_without_problem(
indoc!(
r#"
Num.atan
"#
),
"Float -> Float",
);
}
#[test]
fn reconstruct_path() {
infer_eq_without_problem(

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,
# 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.
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"
winit = "0.22"
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/)
* 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
* [How to Visualize Data Structures in VS Code](https://addyosmani.com/blog/visualize-data-structures-vscode/)
### 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))
* [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
* [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:
```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`.

View File

@ -11,9 +11,9 @@ let
in { pkgs ? pinnedPkgs }:
let
isOsX = builtins.currentSystem == "x86_64-darwin";
isMacOS = builtins.currentSystem == "x86_64-darwin";
darwin-frameworks =
if isOsX then
if isMacOS then
with pkgs.darwin.apple_sdk.frameworks; [
AppKit
CoreFoundation
@ -26,6 +26,7 @@ let
else
[ ];
llvm = pkgs.llvm_10;
lld = pkgs.lld_10; # this should match llvm's version
inputs =
[
pkgs.rustup
@ -35,6 +36,8 @@ let
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
];
in pkgs.mkShell {
buildInputs = inputs ++ darwin-frameworks;