From e55fba8aebd71fcb551f6f1ca627837ac4dceb33 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 26 Sep 2020 23:31:44 -0400 Subject: [PATCH 1/4] Extract regenerate.sh script --- compiler/builtins/bitcode/README.md | 23 +++++++++-------------- compiler/builtins/bitcode/regenerate.sh | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 14 deletions(-) create mode 100755 compiler/builtins/bitcode/regenerate.sh 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 From 6759908ee2460c7223e43ffee795142a4d11ae3b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 26 Sep 2020 23:52:11 -0400 Subject: [PATCH 2/4] Verify on CI that regenerate.sh was run --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) 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 From 2626be4e394078d44cd96fefdde137b87c15277d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 26 Sep 2020 23:57:40 -0400 Subject: [PATCH 3/4] Extract libm.rs into its own module --- compiler/builtins/bitcode/src/lib.rs | 199 +------------------------- compiler/builtins/bitcode/src/libm.rs | 199 ++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 196 deletions(-) create mode 100644 compiler/builtins/bitcode/src/libm.rs 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 + } +} From 47b1d45739a1a28fa96e1675b2dce8860d2ddf2d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 26 Sep 2020 23:59:10 -0400 Subject: [PATCH 4/4] Check in builtins.bc --- compiler/gen/src/llvm/builtins.bc | Bin 4084 -> 4068 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/compiler/gen/src/llvm/builtins.bc b/compiler/gen/src/llvm/builtins.bc index 9518ecae24f93ba196c6154ec162d15a62993978..9d937909bb15d9fe9ba8e486611d6c38ca2e5e92 100644 GIT binary patch delta 1536 zcmXw3drVVT96oI?Z5b_GN(+~vqi}H#C0LOv6IWY7s1vmyLRqYROo2wB1p$p&c1ycr zos-+GN-##PMEA%3SadwZ2Xn~4E>WX&0t$xb#xh5-QGxB=jGfEmB;Wac=R1$zcTP@z zui_`;-D=kUt6OdCNQVan0097WvHo&5WMbz%PuZK*-ZB6%D6$~{2sfxhcv=Jjti>Vh ztRQn`!IeZQ;4djQj(bq5(Ut=s4MP_9roPjJdX8|koD(cgi=T6r%@?QIl`C-mDNil; zJNB&1lgO(t`#pia;gt1d@_)=q{zCS;whOUk&9~m>e}fEiML;y>e_f7s>qq)4fqm3p zQ!WXU@)gso?MW=2MjeH2FS&{iO!)szRErGDc$FV1bVJ#R^tC*6LFb=Mp#KgPsI2;* zP^8`LLHPyCXJu*PL%^BFdbBU9*Fw3-H|~v60OT#d@Kaf=%N{y3p3tCOR!-bLUvg#| z(kVNvtRdt57d8!FyPRW|1|#@8bnrb$K=^mi4)e zr|PfkG;$K(;@T23as*$okuK>!jty8zX!97iROmA_UNqXr>KB?AXdlPawR- z>?us{4tok77~z;1j)G>`H_L6Fer1ZA)4_1P!5+ScbdBA$<@!Y0t1Z*w;bC z=p|)}kCVQVaF1~XF%J!mtfTgOA6IXDQMVcqqg77M-H^WE3*NX!qx1T-C{~X7)Ec0gV_ zx|5Xe159Ojt}xYR)l#k}=%xT(TT0Ncb(AketTIq8ov@L_&IJeLGkAXioj#4%7tkHk zczd?Goz&(BCpt*&>B`57X*|b(s7_L@N8;PhD3?mI=>g@6m2CQ+av3F?c2YhSrm{{c zs=~9;;cFyzHaO5ajdM%sHkgPd?nAOXZ3%>KYIYiL%$X;yk=labR2!)+3eL2W80_20 z!Q$Xt8#$OC9JxjAu}31c0lZ|(zIJjL^4S31xSP(H!SBL5!Sv;lg$BxXQbLSSF0*6- znm8>XE>NyB5`v&y!Yz!&omBoLxDC|)P_fENxqd?lctgKBB4J)cTt)9p;_EetDuBB)e}qT<`kxyUj4vLvS~+fsFmD<#M3X${;zSGDiBXa<-Fv4TQ&BOg#hTzyJK2OKJXV-C_gE$rRYLd}tz;7!98!$q+(LRo055{C zcn*5Zi}j8A6;<49^jDa#DERyqIb0Ws)XdkH}6X7JhF^o;=iE#v`w&VY2m z_rD)Ylusy3M|<`Do8;j6;6QZ%k1nKdOym7I^R47yrnNZo)lern-?}i=xsy_u@_R1$ z6((g5F`_VS?9s!okQ3QMOey}aJpf#k0ubY23AgL#*(?;W1wdkFg(;l|_>n5%TnLP* z;b`I)>`qR)b)yY&AX@KM08V`QNz%(J;v-nf+*)5{(Y-ZKPTgO3=Rovj3k&QQ005T& z5$9AolZU}x&vjwk8m6FOZ|dA(hC?uA0*By1Fl}j`;e1a>_%0peVHE@u7hnTv0LUO` zb}vOn=O+W8hFUblQe4u1fLbU7)0j+*&Kz*lb3Z(OJv@+cdx^1TJb2LZX_|a7$Ythr qY5Ho5z61uV<*?pdQLQ&0(wCSk%+>q#p4|}*5oz+2+&7EiZtx%PEkdUN delta 1579 zcmXw34NMbf82;{hv{&0J*IF4^7zNy>8)0p&Q05OkED@c8Q*7w-n{R9 z-+kkeFC*=#$o}T-R#fb9kN{u+05|fty>4r$(=m)DXK6*N0YCw;!T@}7LibE3_XD7S z3pN*k@NWB#@EbVJCM}u|)MUIiq-X)Gw`4qdGv&PT2moye4nQbWRc~LAI`TLNIKnD! zOBbgR#WPek?YbCzz+vQGMY&3ch*wiM6BW31TJdlzen%Hut(dNHV^-a()-L=!7Rr+V z$*6bQ6z0*7JSzn@pSQvkB9P--CO)*SL3q1USCYFbo5{g3@0;jUi6Melx_L`Bvp+g; zb00aM?d3)X{^biPi@ux0+D(oCFC(9;R7=aiNj1`+C%tQ)t39{We2yF}jIt7=V%d?{ zxijv~FIf^?0kE#VQ~N;JS3-)gg?M~apq;`75$Qurw{jr^fE+mh!WI**LXb9xlOIVN zq;3mGme&`{QDPo~Cv&>Xs~Un@uwE@OD(Fel-Lbqv&j;HT#g-MaLT=tE*?G0R>LB+W zevPc2Wi?}vH7sb=xWu;6XpqWrZhzzh%3f(O4_WOtME>)AG3PR%6t!BnsOBJQ>=@1A zs4nDNzB6>th>@j+2A^bub?*R3!wVaHi3P+snqVy(UR=5~Rw%k^3E!G{yWklpVROO? zFX?Q}r-R-LKU(<29adpB&a%T zw7c{~`6g?RMk*x_ZzbQdQcuLa7SCEIXLUXB5^njg5^~mHDIoirD6$39Y2YRq;5VO9 z2Q;P~o^wn)Bqg8r?wf0kB9eY+9Pp3b{+hqXM4N1RGIEO9Vm#cdXWIS{)26YRf6vd&4IVM&dag6?#s@ws$?;r&UXUWMN|z1!pWi-4%%?RD z`X}2fi1|7hB)3e&{C(!Ee~c^6x)KxU%ny3BillareWt~zrV&JpO&`Z~0GOMvq%5@TP=3t>_t?JRUQ}U$f4Itk}^y#p6fvvdYbvz zKl@*hkTP6^M|hSHQufB*qc>OkKJHD%y<#DCBXih4+jfbdY39D4X%!P)XPF0nW+suO zEZ$iy=BAHDVqz@exL8L%ak<3wE@uDwK&AQ1UcL9nCW5}0896mrUXtvgM;g?DBPD4b zdh(GvkYGNVTq$d?KMvUzsL0Ojf5CL7&+QopT_Dx*R7_`27C`+sCVXnJA{cH00GH$d zgh789>)VaUuYxF5buAf{+>1N1i9}ky7Jz%;6^f835Q0@UM4&xsfY|_)$^qa)uM&~b z)o2e#)4j=x)nW(y+pXMY_z!+{=9?>-^~BKLYd@9wM{e%xH%t^Uou>}3N%;cg3js)n z?@BoJ7WQ6ZVTWM**a?9^9Do3tCBlbZ!K%={hhVQLH$)5s4}!JjILrACO0<_&LG}Rz z>)Go7r~$}^JP2ahFY89Au~nBntK+c%d=9k`h-JU5k3)^^iZ!-_g*vdn1mvs^HOHK2 z)oKcIIOYu7d`@w(Id_9{(ZxI74~|gj52dLTht0WqV?|MMc~ME3-dI$gYtGA87vvbr mlnI53c$HGKUa45GNz7Gg3gS~7+k)%F8