diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2b119c59f..667c3ef007 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/compiler/builtins/bitcode/README.md b/compiler/builtins/bitcode/README.md index da1dabea44..823c734851 100644 --- a/compiler/builtins/bitcode/README.md +++ b/compiler/builtins/bitcode/README.md @@ -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! diff --git a/compiler/builtins/bitcode/regenerate.sh b/compiler/builtins/bitcode/regenerate.sh new file mode 100755 index 0000000000..f4d7a0cd9a --- /dev/null +++ b/compiler/builtins/bitcode/regenerate.sh @@ -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 diff --git a/compiler/builtins/bitcode/src/lib.rs b/compiler/builtins/bitcode/src/lib.rs index 34fbe4bc18..57dfd48ba8 100644 --- a/compiler/builtins/bitcode/src/lib.rs +++ b/compiler/builtins/bitcode/src/lib.rs @@ -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] @@ -46,202 +48,7 @@ pub fn is_finite_(num: f64) -> bool { f64::is_finite(num) } -/// 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)) -} - #[no_mangle] -#[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 - } + libm::atan(x) } diff --git a/compiler/builtins/bitcode/src/libm.rs b/compiler/builtins/bitcode/src/libm.rs new file mode 100644 index 0000000000..67cc4d942d --- /dev/null +++ b/compiler/builtins/bitcode/src/libm.rs @@ -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 + } +} diff --git a/compiler/gen/src/llvm/builtins.bc b/compiler/gen/src/llvm/builtins.bc index 9518ecae24..9d937909bb 100644 Binary files a/compiler/gen/src/llvm/builtins.bc and b/compiler/gen/src/llvm/builtins.bc differ