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

This commit is contained in:
Chad Stearns 2020-10-15 05:26:33 -04:00
commit 415a37a891
86 changed files with 5156 additions and 2360 deletions

View file

@ -6,14 +6,15 @@ jobs:
test: test:
name: fmt, clippy, test, test --release name: fmt, clippy, test, test --release
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Verify compiler/builtin/bitcode/regenerate.sh was run if necessary - 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 run: pushd compiler/builtins/bitcode && ./regenerate.sh && git diff --exit-code ../../gen/src/llvm/builtins.bc && popd
- name: Install LLVM - name: Install CI Libraries
run: sudo ./ci/install-llvm.sh 10 run: sudo ./ci/install-ci-libraries.sh 10
- name: Enable LLD - name: Enable LLD
run: sudo ./ci/enable-lld.sh run: sudo ./ci/enable-lld.sh

View file

@ -1,9 +1,21 @@
# Building the Roc compiler from source # Building the Roc compiler from source
## Installing LLVM and libc++abi ## Installing LLVM, valgrind, libunwind, and libc++-dev
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 build the compiler, you need these installed:
* `libunwind` (macOS should already have this one installed)
* `libc++-dev`
* a particular version of LLVM
To run the test suite (via `cargo test`), you additionally need to install:
* [`valgrind`](https://www.valgrind.org/)
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) macOS systems
should already have `libunwind`, but other systems will need to install it
(e.g. with `sudo apt-get install libunwind-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. 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.

196
Cargo.lock generated
View file

@ -16,6 +16,21 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2692800d602527d2b8fea50036119c37df74ab565b10e285706a3dcec0ec3e16" checksum = "2692800d602527d2b8fea50036119c37df74ab565b10e285706a3dcec0ec3e16"
[[package]]
name = "addr2line"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.13" version = "0.7.13"
@ -120,6 +135,20 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f813291114c186a042350e787af10c26534601062603d888be110f59f85ef8fa"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
@ -279,6 +308,15 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "cloudabi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.20.2" version = "0.20.2"
@ -634,6 +672,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -829,7 +873,7 @@ dependencies = [
"gfx-hal", "gfx-hal",
"libloading", "libloading",
"log", "log",
"parking_lot", "parking_lot 0.10.2",
"range-alloc", "range-alloc",
"raw-window-handle", "raw-window-handle",
"smallvec", "smallvec",
@ -885,7 +929,7 @@ dependencies = [
"log", "log",
"metal", "metal",
"objc", "objc",
"parking_lot", "parking_lot 0.10.2",
"range-alloc", "range-alloc",
"raw-window-handle", "raw-window-handle",
"smallvec", "smallvec",
@ -947,6 +991,12 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "gimli"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
[[package]] [[package]]
name = "glyph_brush" name = "glyph_brush"
version = "0.7.0" version = "0.7.0"
@ -1119,7 +1169,7 @@ dependencies = [
"libc", "libc",
"llvm-sys", "llvm-sys",
"once_cell", "once_cell",
"parking_lot", "parking_lot 0.10.2",
"regex", "regex",
] ]
@ -1259,6 +1309,15 @@ dependencies = [
"scopeguard", "scopeguard",
] ]
[[package]]
name = "lock_api"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
dependencies = [
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.11" version = "0.4.11"
@ -1328,6 +1387,16 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "miniz_oxide"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
dependencies = [
"adler",
"autocfg 1.0.1",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.6.22" version = "0.6.22"
@ -1519,6 +1588,12 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "object"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.4.1" version = "1.4.1"
@ -1561,8 +1636,19 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [ dependencies = [
"lock_api", "lock_api 0.3.4",
"parking_lot_core", "parking_lot_core 0.7.2",
]
[[package]]
name = "parking_lot"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
dependencies = [
"instant",
"lock_api 0.4.1",
"parking_lot_core 0.8.0",
] ]
[[package]] [[package]]
@ -1572,13 +1658,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cloudabi", "cloudabi 0.0.3",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "parking_lot_core"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
dependencies = [
"backtrace",
"cfg-if",
"cloudabi 0.1.0",
"instant",
"libc",
"petgraph",
"redox_syscall",
"smallvec",
"thread-id",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "peek-poke" name = "peek-poke"
version = "0.2.0" version = "0.2.0"
@ -1650,6 +1754,16 @@ dependencies = [
"sha-1", "sha-1",
] ]
[[package]]
name = "petgraph"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "0.4.23" version = "0.4.23"
@ -1965,7 +2079,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [ dependencies = [
"cloudabi", "cloudabi 0.0.3",
"fuchsia-cprng", "fuchsia-cprng",
"libc", "libc",
"rand_core 0.4.2", "rand_core 0.4.2",
@ -2206,6 +2320,8 @@ dependencies = [
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"roc_uniq", "roc_uniq",
"serde",
"serde-xml-rs",
"strip-ansi-escapes", "strip-ansi-escapes",
"target-lexicon", "target-lexicon",
"tokio", "tokio",
@ -2322,6 +2438,7 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
@ -2346,6 +2463,7 @@ dependencies = [
"inlinable_string", "inlinable_string",
"maplit", "maplit",
"num_cpus", "num_cpus",
"parking_lot 0.11.0",
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",
@ -2354,6 +2472,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_module", "roc_module",
"roc_mono",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
@ -2397,6 +2516,7 @@ dependencies = [
"roc_solve", "roc_solve",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"ven_ena",
"ven_pretty", "ven_pretty",
] ]
@ -2551,6 +2671,12 @@ dependencies = [
"roc_types", "roc_types",
] ]
[[package]]
name = "rustc-demangle"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2610b7f643d18c87dff3b489950269617e6601a51f1f05aa5daefee36f64f0b"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"
@ -2627,6 +2753,21 @@ name = "serde"
version = "1.0.116" version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-xml-rs"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efe415925cf3d0bbb2fc47d09b56ce03eef51c5d56846468a39bcc293c7a846c"
dependencies = [
"log",
"serde",
"thiserror",
"xml-rs",
]
[[package]] [[package]]
name = "serde_cbor" name = "serde_cbor"
@ -2758,7 +2899,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0a4829a5c591dc24a944a736d6b1e4053e51339a79fd5d4702c4c999a9c45e" checksum = "fd0a4829a5c591dc24a944a736d6b1e4053e51339a79fd5d4702c4c999a9c45e"
dependencies = [ dependencies = [
"lock_api", "lock_api 0.3.4",
] ]
[[package]] [[package]]
@ -2859,6 +3000,37 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "thiserror"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
dependencies = [
"proc-macro2 1.0.21",
"quote 1.0.7",
"syn 1.0.40",
]
[[package]]
name = "thread-id"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
dependencies = [
"libc",
"redox_syscall",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.0.1" version = "1.0.1"
@ -3186,7 +3358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5dece29f3cd403aabf4056595eabe4b9af56b8bfae12445f097cf8666a41829" checksum = "b5dece29f3cd403aabf4056595eabe4b9af56b8bfae12445f097cf8666a41829"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"parking_lot", "parking_lot 0.10.2",
"raw-window-handle", "raw-window-handle",
"smallvec", "smallvec",
"wgpu-core", "wgpu-core",
@ -3213,7 +3385,7 @@ dependencies = [
"gfx-hal", "gfx-hal",
"gfx-memory", "gfx-memory",
"log", "log",
"parking_lot", "parking_lot 0.10.2",
"peek-poke", "peek-poke",
"smallvec", "smallvec",
"vec_map", "vec_map",
@ -3230,7 +3402,7 @@ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
"objc", "objc",
"parking_lot", "parking_lot 0.10.2",
"raw-window-handle", "raw-window-handle",
"wgpu-core", "wgpu-core",
"wgpu-types", "wgpu-types",
@ -3323,7 +3495,7 @@ dependencies = [
"ndk-glue", "ndk-glue",
"ndk-sys", "ndk-sys",
"objc", "objc",
"parking_lot", "parking_lot 0.10.2",
"percent-encoding", "percent-encoding",
"raw-window-handle", "raw-window-handle",
"smithay-client-toolkit", "smithay-client-toolkit",

View file

@ -59,4 +59,4 @@ esac
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
add-apt-repository "${REPO_NAME}" add-apt-repository "${REPO_NAME}"
apt-get update apt-get update
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev valgrind

View file

@ -86,3 +86,5 @@ indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"
quickcheck_macros = "0.8" quickcheck_macros = "0.8"
strip-ansi-escapes = "0.1" strip-ansi-escapes = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"

117
cli/src/build.rs Normal file
View file

@ -0,0 +1,117 @@
use bumpalo::Bump;
use roc_build::{link::link, program};
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::fs;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
buf.push_str(&format!(
" {:.3} ms {}\n",
duration.as_secs_f64() * 1000.0,
label,
));
}
pub fn build_file(
target: &Triple,
src_dir: PathBuf,
filename: PathBuf,
opt_level: OptLevel,
) -> Result<PathBuf, LoadingProblem> {
let compilation_start = SystemTime::now();
let arena = Bump::new();
// Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default();
// Release builds use uniqueness optimizations
let stdlib = match opt_level {
OptLevel::Normal => roc_builtins::std::standard_stdlib(),
OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(),
};
let loaded = roc_load::file::load_and_monomorphize(
&arena,
filename.clone(),
stdlib,
src_dir.as_path(),
subs_by_module,
)?;
let dest_filename = filename.with_file_name("roc_app.o");
let buf = &mut String::with_capacity(1024);
for (module_id, module_timing) in loaded.timings.iter() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
buf.push_str(module_name);
buf.push('\n');
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
report_timing(buf, "Parse header", module_timing.parse_header);
report_timing(buf, "Parse body", module_timing.parse_body);
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
}
println!(
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
buf
);
program::gen_from_mono_module(
&arena,
loaded,
filename,
Triple::host(),
&dest_filename,
opt_level,
);
let compilation_end = compilation_start.elapsed().unwrap();
println!(
"Finished compilation and code gen in {} ms\n",
compilation_end.as_millis()
);
let cwd = dest_filename.parent().unwrap();
// Step 2: link the precompiled host and compiled app
let host_input_path = cwd.join("platform").join("host.o");
let binary_path = cwd.join("app"); // TODO should be app.exe on Windows
// TODO try to move as much of this linking as possible to the precompiled
// host, to minimize the amount of host-application linking required.
let cmd_result = // TODO use lld
link(
target,
binary_path.as_path(),
host_input_path.as_path(),
dest_filename.as_path(),
)
.map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn.");
})?
.wait()
.map_err(|_| {
todo!("gracefully handle error after `rustc` spawned");
});
// Clean up the leftover .o file from the Roc, if possible.
// (If cleaning it up fails, that's fine. No need to take action.)
// TODO compile the dest_filename to a tmpdir, as an extra precaution.
let _ = fs::remove_file(dest_filename);
// If the cmd errored out, return the Err.
cmd_result?;
Ok(binary_path)
}

View file

@ -1,20 +1,16 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use bumpalo::Bump;
use clap::ArgMatches; use clap::ArgMatches;
use clap::{App, Arg}; use clap::{App, Arg};
use roc_build::program::gen;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem; use std::io;
use std::io::{self, ErrorKind}; use std::path::Path;
use std::path::{Path, PathBuf};
use std::process; use std::process;
use std::process::Command; use std::process::Command;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple; use target_lexicon::Triple;
pub mod build;
pub mod repl; pub mod repl;
pub static FLAG_OPTIMIZE: &str = "optimize"; pub static FLAG_OPTIMIZE: &str = "optimize";
@ -66,7 +62,7 @@ pub fn build_app<'a>() -> App<'a> {
) )
} }
pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
let filename = matches.value_of(FLAG_ROC_FILE).unwrap(); let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
let opt_level = if matches.is_present(FLAG_OPTIMIZE) { let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
OptLevel::Optimize OptLevel::Optimize
@ -78,7 +74,7 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
// Spawn the root task // Spawn the root task
let path = path.canonicalize().unwrap_or_else(|err| { let path = path.canonicalize().unwrap_or_else(|err| {
use ErrorKind::*; use io::ErrorKind::*;
match err.kind() { match err.kind() {
NotFound => { NotFound => {
@ -95,8 +91,8 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
} }
}); });
let binary_path = let binary_path = build::build_file(target, src_dir, path, opt_level)
build_file(src_dir, path, opt_level).expect("TODO gracefully handle build_file failing"); .expect("TODO gracefully handle build_file failing");
if run_after_build { if run_after_build {
// Run the compiled app // Run the compiled app
@ -109,123 +105,3 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
Ok(()) Ok(())
} }
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
buf.push_str(&format!(
" {:.3} ms {}\n",
duration.as_secs_f64() * 1000.0,
label,
));
}
fn build_file(
src_dir: PathBuf,
filename: PathBuf,
opt_level: OptLevel,
) -> Result<PathBuf, LoadingProblem> {
let compilation_start = SystemTime::now();
let arena = Bump::new();
// Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default();
// Release builds use uniqueness optimizations
let stdlib = match opt_level {
OptLevel::Normal => roc_builtins::std::standard_stdlib(),
OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(),
};
let loaded =
roc_load::file::load(filename.clone(), &stdlib, src_dir.as_path(), subs_by_module)?;
let dest_filename = filename.with_extension("o");
let buf = &mut String::with_capacity(1024);
for (module_id, module_timing) in loaded.timings.iter() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
buf.push_str(module_name);
buf.push_str("\n");
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
report_timing(buf, "Parse header", module_timing.parse_header);
report_timing(buf, "Parse body", module_timing.parse_body);
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
}
println!(
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
buf
);
gen(
&arena,
loaded,
filename,
Triple::host(),
&dest_filename,
opt_level,
);
let compilation_end = compilation_start.elapsed().unwrap();
println!(
"Finished compilation and code gen in {} ms\n",
compilation_end.as_millis()
);
let cwd = dest_filename.parent().unwrap();
let lib_path = dest_filename.with_file_name("libroc_app.a");
// Step 2: turn the .o file into a .a static library
Command::new("ar") // TODO on Windows, use `link`
.args(&[
"rcs",
lib_path.to_str().unwrap(),
dest_filename.to_str().unwrap(),
])
.spawn()
.map_err(|_| {
todo!("gracefully handle `ar` failing to spawn.");
})?
.wait()
.map_err(|_| {
todo!("gracefully handle error after `ar` spawned");
})?;
// Step 3: have rustc compile the host and link in the .a file
let binary_path = cwd.join("app");
Command::new("rustc")
.args(&[
"-L",
".",
"--crate-type",
"bin",
"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()
.map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn.");
})?
.wait()
.map_err(|_| {
todo!("gracefully handle error after `rustc` spawned");
})?;
Ok(binary_path)
}

View file

@ -1,14 +1,23 @@
use roc_cli::{build, build_app, repl, DIRECTORY_OR_FILES}; use roc_cli::{build, build_app, repl, DIRECTORY_OR_FILES};
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use target_lexicon::Triple;
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
let matches = build_app().get_matches(); let matches = build_app().get_matches();
match matches.subcommand_name() { match matches.subcommand_name() {
None => roc_editor::launch(&[]), None => roc_editor::launch(&[]),
Some("build") => build(matches.subcommand_matches("build").unwrap(), false), Some("build") => build(
Some("run") => build(matches.subcommand_matches("run").unwrap(), true), &Triple::host(),
matches.subcommand_matches("build").unwrap(),
false,
),
Some("run") => build(
&Triple::host(),
matches.subcommand_matches("run").unwrap(),
true,
),
Some("repl") => repl::main(), Some("repl") => repl::main(),
Some("edit") => { Some("edit") => {
match matches match matches

View file

@ -12,12 +12,9 @@ use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap, SendSet};
use roc_constrain::expr::constrain_expr; use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_fmt::annotation::{Formattable, Newlines, Parens}; use roc_fmt::annotation::{Formattable, Newlines, Parens};
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel}; use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_mono::ir::Procs;
use roc_mono::layout::{Layout, LayoutCache};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, FailReason, Parser, State}; use roc_parse::parser::{loc, Fail, FailReason, Parser, State};
@ -29,7 +26,7 @@ use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type; use roc_types::types::Type;
use std::hash::Hash; use std::hash::Hash;
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
use target_lexicon::Triple; use target_lexicon::Triple;
@ -158,29 +155,71 @@ pub fn repl_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"REPL".into()) ModuleIds::default().get_or_insert(&"REPL".into())
} }
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Repl provides [ replOutput ] imports []\n\nreplOutput =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> { fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
// Look up the types and expressions of the `provided` values
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let arena = Bump::new(); let arena = Bump::new();
let CanExprOut {
loc_expr,
var_store,
var,
constraint,
home,
interns,
problems: can_problems,
..
} = can_expr(src)?; // IMPORTANT: we must bail out here if there were UTF-8 errors!
let subs = Subs::new(var_store.into());
let mut type_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut type_problems, &constraint, var);
// SAFETY: we've already verified that this is valid UTF-8 during parsing. // SAFETY: we've already verified that this is valid UTF-8 during parsing.
let src_lines: Vec<&str> = unsafe { from_utf8_unchecked(src).split('\n').collect() }; let src_str: &str = unsafe { from_utf8_unchecked(src) };
let stdlib = roc_builtins::std::standard_stdlib();
let stdlib_mode = stdlib.mode;
let filename = PathBuf::from("REPL.roc");
let src_dir = Path::new("fake/test/path");
let module_src = promote_expr_to_module(src_str);
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
&arena,
filename,
&module_src,
stdlib,
src_dir,
exposed_types,
);
let loaded = loaded.expect("failed to load module");
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
can_problems,
type_problems,
mono_problems,
mut procedures,
interns,
exposed_to_host,
mut subs,
module_id: home,
..
} = loaded;
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
if error_count > 0 {
// There were problems; report them and return.
let src_lines: Vec<&str> = module_src.split('\n').collect();
// Used for reporting where an error came from.
//
// TODO: maybe Reporting should have this be an Option?
let path = PathBuf::new();
// Report problems // Report problems
let palette = DEFAULT_PALETTE; let palette = DEFAULT_PALETTE;
@ -188,31 +227,63 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
// Report parsing and canonicalization problems // Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, &interns);
// Used for reporting where an error came from. let mut lines = Vec::with_capacity(error_count);
//
// TODO: maybe Reporting should have this be an Option?
let path = PathBuf::new();
let total_problems = can_problems.len() + type_problems.len();
if total_problems == 0 { for problem in can_problems.into_iter() {
let report = can_problem(&alloc, path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
for problem in type_problems.into_iter() {
let report = type_problem(&alloc, path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
for problem in mono_problems.into_iter() {
let report = mono_problem(&alloc, path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
Ok(ReplOutput::Problems(lines))
} else {
let context = Context::create(); let context = Context::create();
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app")); let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
let builder = context.create_builder(); let builder = context.create_builder();
// pretty-print the expr type string for later. debug_assert_eq!(exposed_to_host.len(), 1);
name_all_type_vars(var, &mut subs); let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var;
// pretty-print the expr type string for later.
name_all_type_vars(main_fn_var, &mut subs);
let content = subs.get(main_fn_var).content;
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
let (_, main_fn_layout) = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.unwrap()
.clone();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(module);
let (module_pass, function_pass) = let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level); roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let main_ret_layout = Layout::new(&arena, content.clone(), &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in test: could not convert Content to main_layout. Err was {:?}",
err
)
});
let execution_engine = module let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None) .create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test"); .expect("Error creating JIT execution engine for test");
@ -222,7 +293,7 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
ExecutionEngine::link_in_mc_jit(); ExecutionEngine::link_in_mc_jit();
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env { let env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
builder: &builder, builder: &builder,
context: &context, context: &context,
@ -230,97 +301,70 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
module, module,
ptr_bytes, ptr_bytes,
leak: false, leak: false,
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(), exposed_to_host: MutSet::default(),
}; };
let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
let mut mono_problems = Vec::new(); let mut headers = Vec::with_capacity(procedures.len());
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
};
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
let param_map = roc_mono::borrow::ParamMap::default();
let main_body = roc_mono::inc_dec::visit_declaration(
mono_env.arena,
mono_env.arena.alloc(param_map),
mono_env.arena.alloc(main_body),
);
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
None => 0,
};
Vec::with_capacity(num_headers)
};
let mut layout_cache = LayoutCache::default();
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(
procs.runtime_errors,
roc_collections::all::MutMap::default()
);
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
let main_body = roc_mono::inc_dec::visit_declaration(
mono_env.arena,
param_map,
mono_env.arena.alloc(main_body),
);
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for ((symbol, layout), proc) in procs.drain() { let mut scope = roc_gen::llvm::build::Scope::default();
for ((symbol, layout), proc) in procedures.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, layout, fn_val);
}
headers.push((proc, fn_val)); headers.push((proc, fn_val));
} }
// Build each proc using its header info. // Build each proc using its header info.
for (proc, fn_val) in headers { for (proc, fn_val) in headers {
// NOTE: This is here to be uncommented in case verification fails. let mut current_scope = scope.clone();
// (This approach means we don't have to defensively clone name here.)
// // only have top-level thunks for this proc's module in scope
// println!("\n\nBuilding and then verifying function {}\n\n", name); // this retain is not needed for correctness, but will cause less confusion when debugging
build_proc(&env, &mut layout_ids, proc, fn_val); let home = proc.name.module_id();
current_scope.retain_top_level_thunks_for_module(home);
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
if fn_val.verify(true) { if fn_val.verify(true) {
function_pass.run_on(&fn_val); function_pass.run_on(&fn_val);
} else { } else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
eprintln!( eprintln!(
"\n\nFunction {:?} failed LLVM verification in build. Its content was:\n", "\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
fn_val.get_name().to_str().unwrap() fn_val.get_name().to_str().unwrap(),
mode,
); );
fn_val.print_to_stderr(); fn_val.print_to_stderr();
panic!( panic!(
"The preceding code was from {:?}, which failed LLVM verification in build.", "The preceding code was from {:?}, which failed LLVM verification in {} build.",
fn_val.get_name().to_str().unwrap() fn_val.get_name().to_str().unwrap(),
mode,
); );
} }
} }
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
let (main_fn_name, main_fn) = roc_gen::llvm::build::make_main_function(
&env, &env,
&mut layout_ids, &mut layout_ids,
&main_ret_layout, main_fn_symbol,
&main_body, &main_fn_layout,
); );
// Uncomment this to see the module's un-optimized LLVM instruction output: // Uncomment this to see the module's un-optimized LLVM instruction output:
@ -347,7 +391,7 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
&arena, &arena,
execution_engine, execution_engine,
main_fn_name, main_fn_name,
&main_ret_layout, &main_fn_layout,
&content, &content,
&env.interns, &env.interns,
home, home,
@ -363,29 +407,6 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
expr: expr.into_bump_str().to_string(), expr: expr.into_bump_str().to_string(),
expr_type: expr_type_str, expr_type: expr_type_str,
}) })
} else {
// There were problems; report them and return.
let mut lines = Vec::with_capacity(total_problems);
for problem in can_problems.into_iter() {
let report = can_problem(&alloc, path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
for problem in type_problems.into_iter() {
let report = type_problem(&alloc, path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
Ok(ReplOutput::Problems(lines))
} }
} }

View file

@ -11,36 +11,119 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod cli_run { mod cli_run {
use crate::helpers::{example_file, run_roc}; use crate::helpers::{example_file, extract_valgrind_errors, run_roc, run_with_valgrind, Out};
#[test] #[test]
fn run_hello_world() { fn run_hello_world() {
let out = run_roc(&[ fn check_hello_world_output(out: Out) {
"run",
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]);
if !out.stderr.is_empty() { if !out.stderr.is_empty() {
panic!(out.stderr); panic!(out.stderr);
} }
assert!(&out.stdout.ends_with("Hello, World!!!!!!!!!!!!!\n"));
assert!(out.status.success()); assert!(out.status.success());
let valgrind_out =
run_with_valgrind(&[example_file("hello-world", "app").to_str().unwrap()]);
assert!(valgrind_out.status.success());
let ending = "Hello, World!!!!!!!!!!!!!\n";
if !&valgrind_out.stdout.ends_with(ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
);
}
let memory_errors = extract_valgrind_errors(&valgrind_out.stderr);
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
}
}
check_hello_world_output(run_roc(&[
"build",
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]));
check_hello_world_output(run_roc(&[
"build",
"--optimize",
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]));
} }
#[test] #[test]
fn run_quicksort() { fn run_quicksort() {
let out = run_roc(&[ fn check_quicksort_output(out: Out) {
"run",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
"--optimize",
]);
if !out.stderr.is_empty() { if !out.stderr.is_empty() {
panic!(out.stderr); 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"));
assert!(out.status.success()); assert!(out.status.success());
let valgrind_out =
run_with_valgrind(&[example_file("quicksort", "app").to_str().unwrap()]);
assert!(valgrind_out.status.success());
let ending = "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n";
if !&valgrind_out.stdout.ends_with(ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
);
}
let memory_errors = extract_valgrind_errors(&valgrind_out.stderr);
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
}
}
// TODO: Uncomment this once we are correctly freeing the RocList even when in dev build.
/*
check_quicksort_output(run_roc(&[
"build",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
]));
*/
check_quicksort_output(run_roc(&[
"build",
"--optimize",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
]));
}
#[test]
fn run_multi_module() {
fn check_muti_module_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(out.status.success());
let valgrind_out =
run_with_valgrind(&[example_file("multi-module", "app").to_str().unwrap()]);
assert!(valgrind_out.status.success());
let ending = "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n";
if !&valgrind_out.stdout.ends_with(ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
);
}
let memory_errors = extract_valgrind_errors(&valgrind_out.stderr);
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
}
}
// TODO: Uncomment this once we are correctly freeing the RocList even when in dev build.
/*
check_muti_module_output(run_roc(&[
"run",
example_file("multi-module", "Quicksort.roc")
.to_str()
.unwrap(),
]));
*/
check_muti_module_output(run_roc(&[
"run",
example_file("multi-module", "Quicksort.roc")
.to_str()
.unwrap(),
"--optimize",
]));
} }
} }

View file

@ -5,6 +5,8 @@ extern crate roc_load;
extern crate roc_module; extern crate roc_module;
use roc_cli::repl::{INSTRUCTIONS, PROMPT, WELCOME_MESSAGE}; use roc_cli::repl::{INSTRUCTIONS, PROMPT, WELCOME_MESSAGE};
use serde::Deserialize;
use serde_xml_rs::from_str;
use std::env; use std::env;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
@ -54,6 +56,86 @@ pub fn run_roc(args: &[&str]) -> Out {
} }
} }
#[allow(dead_code)]
pub fn run_with_valgrind(args: &[&str]) -> Out {
//TODO: figure out if there is a better way to get the valgrind executable.
let mut cmd = Command::new("valgrind");
cmd.arg("--tool=memcheck");
cmd.arg("--xml=yes");
cmd.arg("--xml-fd=2");
for arg in args {
cmd.arg(arg);
}
let output = cmd
.output()
.expect("failed to execute compiled `valgrind` binary in CLI test");
Out {
stdout: String::from_utf8(output.stdout).unwrap(),
stderr: String::from_utf8(output.stderr).unwrap(),
status: output.status,
}
}
#[derive(Debug, Deserialize)]
struct ValgrindOutput {
#[serde(rename = "$value")]
pub fields: Vec<ValgrindField>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
enum ValgrindField {
ProtocolVersion(isize),
ProtocolTool(String),
Preamble(ValgrindDummyStruct),
Pid(isize),
PPid(isize),
Tool(String),
Args(ValgrindDummyStruct),
Error(ValgrindError),
Status(ValgrindDummyStruct),
ErrorCounts(ValgrindDummyStruct),
SuppCounts(ValgrindDummyStruct),
}
#[derive(Debug, Deserialize)]
struct ValgrindDummyStruct {}
#[derive(Debug, Deserialize, Clone)]
pub struct ValgrindError {
kind: String,
#[serde(default)]
what: Option<String>,
#[serde(default)]
xwhat: Option<ValgrindErrorXWhat>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ValgrindErrorXWhat {
text: String,
#[serde(default)]
leakedbytes: Option<isize>,
#[serde(default)]
leakedblocks: Option<isize>,
}
#[allow(dead_code)]
pub fn extract_valgrind_errors(xml: &str) -> Vec<ValgrindError> {
let parsed_xml: ValgrindOutput =
from_str(xml).expect("failed to parse the `valgrind` xml output");
parsed_xml
.fields
.iter()
.filter_map(|field| match field {
ValgrindField::Error(err) => Some(err.clone()),
_ => None,
})
.collect()
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn example_dir(dir_name: &str) -> PathBuf { pub fn example_dir(dir_name: &str) -> PathBuf {
let mut path = env::current_exe().ok().unwrap(); let mut path = env::current_exe().ok().unwrap();

View file

@ -1,12 +1,17 @@
#[macro_use] #[macro_use]
extern crate pretty_assertions; extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
mod helpers; mod helpers;
#[cfg(test)] #[cfg(test)]
mod repl_eval { mod repl_eval {
use crate::helpers; use crate::helpers;
const ERROR_MESSAGE_START: char = '─';
fn expect_success(input: &str, expected: &str) { fn expect_success(input: &str, expected: &str) {
let out = helpers::repl_eval(input); let out = helpers::repl_eval(input);
@ -15,6 +20,28 @@ mod repl_eval {
assert!(out.status.success()); assert!(out.status.success());
} }
fn expect_failure(input: &str, expected: &str) {
let out = helpers::repl_eval(input);
// there may be some other stuff printed (e.g. unification errors)
// so skip till the header of the first error
match out.stdout.find(ERROR_MESSAGE_START) {
Some(index) => {
assert_eq!(&out.stderr, "");
assert_eq!(&out.stdout[index..], expected);
assert!(out.status.success());
}
None => {
assert_eq!(&out.stderr, "");
assert!(out.status.success());
panic!(
"I expected a failure, but there is no error message in stdout:\n\n{}",
&out.stdout
);
}
}
}
#[test] #[test]
fn literal_0() { fn literal_0() {
expect_success("0", "0 : Num *"); expect_success("0", "0 : Num *");
@ -256,13 +283,46 @@ mod repl_eval {
// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\""); // expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\"");
// } // }
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done #[test]
fn list_of_3_field_records() {
expect_success(
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int, foo : Float }",
);
}
#[test]
fn type_problem() {
expect_failure(
"1 + \"\"",
indoc!(
r#"
TYPE MISMATCH
The 2nd argument to add is not what I expect:
4 1 + ""
^^
This argument is a string of type:
Str
But add needs the 2nd argument to be:
Num a
"#
),
);
}
// #[test]
// fn parse_problem() {
// // can't find something that won't parse currently
// }
// //
// #[test] // #[test]
// fn list_of_3_field_records() { // fn mono_problem() {
// expect_success( // // can't produce a mono error (non-exhaustive pattern) yet
// "[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
// "[ { foo: 4.1, bar: 2, baz: 0x3 } ] : List { foo : Float, bar : Num *, baz : Int }",
// );
// } // }
} }

View file

@ -10,4 +10,6 @@
// and encouraging shortcuts here creates bad incentives. I would rather temporarily // and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs. // re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod link;
pub mod program; pub mod program;
pub mod target;

204
compiler/build/src/link.rs Normal file
View file

@ -0,0 +1,204 @@
use crate::target::arch_str;
use std::io;
use std::path::Path;
use std::process::{Child, Command};
use target_lexicon::{Architecture, OperatingSystem, Triple};
pub fn link(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
// TODO we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there.
rebuild_host(host_input_path);
match target {
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
..
} => link_linux(target, binary_path, host_input_path, dest_filename),
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Darwin,
..
} => link_macos(target, binary_path, host_input_path, dest_filename),
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
}
}
fn rebuild_host(host_input_path: &Path) {
let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o");
let rust_host_src = host_input_path.with_file_name("host.rs");
let rust_host_dest = host_input_path.with_file_name("rust_host.o");
let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
let host_dest = host_input_path.with_file_name("host.o");
// Compile host.c
Command::new("clang")
.env_clear()
.args(&[
"-c",
c_host_src.to_str().unwrap(),
"-o",
c_host_dest.to_str().unwrap(),
])
.output()
.unwrap();
if cargo_host_src.exists() {
// Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap();
let libhost_dir = cargo_dir.join("target").join("release");
Command::new("cargo")
.args(&["build", "--release"])
.current_dir(cargo_dir)
.output()
.unwrap();
Command::new("ld")
.env_clear()
.args(&[
"-r",
"-L",
libhost_dir.to_str().unwrap(),
c_host_dest.to_str().unwrap(),
"-lhost",
"-o",
host_dest.to_str().unwrap(),
])
.output()
.unwrap();
} else if rust_host_src.exists() {
// Compile and link host.rs, if it exists
Command::new("rustc")
.args(&[
rust_host_src.to_str().unwrap(),
"-o",
rust_host_dest.to_str().unwrap(),
])
.output()
.unwrap();
Command::new("ld")
.env_clear()
.args(&[
"-r",
c_host_dest.to_str().unwrap(),
rust_host_dest.to_str().unwrap(),
"-o",
host_dest.to_str().unwrap(),
])
.output()
.unwrap();
// Clean up rust_host.o
Command::new("rm")
.env_clear()
.args(&[
"-f",
rust_host_dest.to_str().unwrap(),
c_host_dest.to_str().unwrap(),
])
.output()
.unwrap();
} else {
// Clean up rust_host.o
Command::new("mv")
.env_clear()
.args(&[c_host_dest, host_dest])
.output()
.unwrap();
}
}
fn link_linux(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
let libcrt_path = if Path::new("/usr/lib/x86_64-linux-gnu").exists() {
Path::new("/usr/lib/x86_64-linux-gnu")
} else {
Path::new("/usr/lib")
};
let libgcc_path = if Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1")
} else if Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1")
} else {
Path::new("/usr/lib/libgcc_s.so.1")
};
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
"-arch",
arch_str(target),
libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(),
libcrt_path.join("Scrt1.o").to_str().unwrap(),
"-dynamic-linker",
"/lib64/ld-linux-x86-64.so.2",
// Inputs
host_input_path.to_str().unwrap(), // host.o
dest_filename.to_str().unwrap(), // app.o
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
// for discussion and further references
"-lc",
"-lm",
"-lpthread",
"-ldl",
"-lrt",
"-lutil",
"-lc_nonshared",
"-lc++",
"-lunwind",
libgcc_path.to_str().unwrap(),
// Output
"-o",
binary_path.to_str().unwrap(), // app
])
.spawn()
}
fn link_macos(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
"-arch",
target.architecture.to_string().as_str(),
// Inputs
host_input_path.to_str().unwrap(), // host.o
dest_filename.to_str().unwrap(), // roc_app.o
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references
"-lSystem",
"-lresolv",
"-lpthread",
// "-lrt", // TODO shouldn't we need this?
// "-lc_nonshared", // TODO shouldn't we need this?
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
// "-lunwind", // TODO will eventually need this, see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
"-lc++", // TODO shouldn't we need this?
// Output
"-o",
binary_path.to_str().unwrap(), // app
])
.spawn()
}

View file

@ -1,26 +1,21 @@
use crate::target;
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::targets::{ use inkwell::targets::{CodeModel, FileType, RelocMode};
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple,
};
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_collections::all::default_hasher;
use roc_gen::layout_id::LayoutIds; use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel}; use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
use roc_load::file::LoadedModule; use roc_load::file::MonomorphizedModule;
use roc_mono::ir::{Env, PartialProc, Procs};
use roc_mono::layout::{Layout, LayoutCache};
use std::collections::HashSet;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; use target_lexicon::Triple;
// TODO how should imported modules factor into this? What if those use builtins too? // TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions // TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend. // TODO make this polymorphic in the llvm functions so it can be reused for another backend.
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn gen( pub fn gen_from_mono_module(
arena: &Bump, arena: &Bump,
mut loaded: LoadedModule, loaded: MonomorphizedModule,
filename: PathBuf, filename: PathBuf,
target: Triple, target: Triple,
dest_filename: &Path, dest_filename: &Path,
@ -54,14 +49,6 @@ pub fn gen(
println!("\n{}\n", buf); println!("\n{}\n", buf);
} }
// Look up the types and expressions of the `provided` values
let mut decls_by_id = loaded.declarations_by_id;
let home_decls = decls_by_id
.remove(&loaded.module_id)
.expect("Root module ID not found in loaded declarations_by_id");
let mut subs = loaded.solved.into_inner();
// Generate the binary // Generate the binary
let context = Context::create(); let context = Context::create();
@ -71,157 +58,8 @@ pub fn gen(
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let mut exposed_to_host =
HashSet::with_capacity_and_hasher(loaded.exposed_vars_by_symbol.len(), default_hasher());
for (symbol, _) in loaded.exposed_vars_by_symbol {
exposed_to_host.insert(symbol);
}
let mut ident_ids = loaded.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = LayoutIds::default();
let mut procs = Procs::default();
let mut mono_problems = std::vec::Vec::new();
let mut layout_cache = LayoutCache::default();
let mut mono_env = Env {
arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
};
// Add modules' decls to Procs
for (_, mut decls) in decls_by_id
.drain()
.chain(std::iter::once((loaded.module_id, home_decls)))
{
for decl in decls.drain(..) {
use roc_can::def::Declaration::*;
use roc_can::expr::Expr::*;
use roc_can::pattern::Pattern::*;
match decl {
Declare(def) | Builtin(def) => match def.loc_pattern.value {
Identifier(symbol) => {
match def.loc_expr.value {
Closure {
function_type: annotation,
return_type: ret_var,
recursive: recursivity,
arguments: loc_args,
loc_body: boxed_body,
..
} => {
let is_tail_recursive =
matches!(recursivity, roc_can::expr::Recursive::TailRecursive);
let loc_body = *boxed_body;
// If this is an exposed symbol, we need to
// register it as such. Otherwise, since it
// never gets called by Roc code, it will never
// get specialized!
if exposed_to_host.contains(&symbol) {
let mut pattern_vars =
bumpalo::collections::Vec::with_capacity_in(
loc_args.len(),
arena,
);
for (var, _) in loc_args.iter() {
pattern_vars.push(*var);
}
let layout = layout_cache.from_var(mono_env.arena, annotation, mono_env.subs).unwrap_or_else(|err|
todo!("TODO gracefully handle the situation where we expose a function to the host which doesn't have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", err)
);
procs.insert_exposed(
symbol,
layout,
pattern_vars, //: Vec<'a, Variable>,
annotation,
ret_var,
);
}
procs.insert_named(
&mut mono_env,
&mut layout_cache,
symbol,
annotation,
loc_args,
loc_body,
is_tail_recursive,
ret_var,
);
}
body => {
let annotation = def.expr_var;
let proc = PartialProc {
annotation,
// This is a 0-arity thunk, so it has no arguments.
pattern_symbols: bumpalo::collections::Vec::new_in(
mono_env.arena,
),
is_self_recursive: false,
body,
};
// If this is an exposed symbol, we need to
// register it as such. Otherwise, since it
// never gets called by Roc code, it will never
// get specialized!
if exposed_to_host.contains(&symbol) {
let pattern_vars = bumpalo::collections::Vec::new_in(arena);
let ret_layout = layout_cache.from_var(mono_env.arena, annotation, mono_env.subs).unwrap_or_else(|err|
todo!("TODO gracefully handle the situation where we expose a function to the host which doesn't have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", err)
);
let layout =
Layout::FunctionPointer(&[], arena.alloc(ret_layout));
procs.insert_exposed(
symbol,
layout,
pattern_vars,
// It seems brittle that we're passing
// annotation twice - especially since
// in both cases we're giving the
// annotation to the top-level value,
// not the thunk function it will code
// gen to. It seems to work, but that
// may only be because at present we
// only use the function annotation
// variable during specialization, and
// exposed values are never specialized
// because they must be monomorphic.
annotation,
annotation,
);
}
procs.partial_procs.insert(symbol, proc);
procs.module_thunks.insert(symbol);
}
};
}
other => {
todo!("TODO gracefully handle Declare({:?})", other);
}
},
DeclareRec(_defs) => {
todo!("TODO support DeclareRec");
}
InvalidCycle(_loc_idents, _regions) => {
todo!("TODO handle InvalidCycle");
}
}
}
}
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env { let env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
builder: &builder, builder: &builder,
context: &context, context: &context,
@ -229,36 +67,27 @@ pub fn gen(
module, module,
ptr_bytes, ptr_bytes,
leak: false, leak: false,
exposed_to_host, exposed_to_host: loaded.exposed_to_host.keys().copied().collect(),
}; };
// Populate Procs further and get the low-level Expr from the canonical Expr // Populate Procs further and get the low-level Expr from the canonical Expr
let mut headers = { let mut headers = Vec::with_capacity(loaded.procedures.len());
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
None => 0,
};
Vec::with_capacity(num_headers)
};
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(
procs.runtime_errors,
roc_collections::all::MutMap::default()
);
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for ((symbol, layout), proc) in procs.get_specialized_procs(arena) { let mut layout_ids = LayoutIds::default();
let mut scope = Scope::default();
for ((symbol, layout), proc) in loaded.procedures {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, layout, fn_val);
}
headers.push((proc, fn_val)); headers.push((proc, fn_val));
} }
@ -268,11 +97,13 @@ pub fn gen(
// (This approach means we don't have to defensively clone name here.) // (This approach means we don't have to defensively clone name here.)
// //
// println!("\n\nBuilding and then verifying function {:?}\n\n", proc); // println!("\n\nBuilding and then verifying function {:?}\n\n", proc);
build_proc(&env, &mut layout_ids, proc, fn_val); build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
if fn_val.verify(true) { if fn_val.verify(true) {
fpm.run_on(&fn_val); fpm.run_on(&fn_val);
} else { } else {
// fn_val.print_to_stderr();
// env.module.print_to_stderr();
// NOTE: If this fails, uncomment the above println to debug. // NOTE: If this fails, uncomment the above println to debug.
panic!( panic!(
"Non-main function failed LLVM verification. Uncomment the above println to debug!" "Non-main function failed LLVM verification. Uncomment the above println to debug!"
@ -295,80 +126,10 @@ pub fn gen(
// Emit the .o file // Emit the .o file
// NOTE: arch_str is *not* the same as the beginning of the magic target triple
// string! For example, if it's "x86-64" here, the magic target triple string
// will begin with "x86_64" (with an underscore) instead.
let arch_str = match target.architecture {
Architecture::X86_64 => {
Target::initialize_x86(&InitializationConfig::default());
"x86-64"
}
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
// NOTE: why not enable arm and wasm by default?
//
// We had some trouble getting them to link properly. This may be resolved in the
// future, or maybe it was just some weird configuration on one machine.
Target::initialize_arm(&InitializationConfig::default());
"arm"
}
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
Target::initialize_webassembly(&InitializationConfig::default());
"wasm32"
}
_ => panic!(
"TODO gracefully handle unsupported target architecture: {:?}",
target.architecture
),
};
let opt = OptimizationLevel::Aggressive; let opt = OptimizationLevel::Aggressive;
let reloc = RelocMode::Default; let reloc = RelocMode::Default;
let model = CodeModel::Default; let model = CodeModel::Default;
let target_machine = target::target_machine(&target, opt, reloc, model).unwrap();
// Best guide I've found on how to determine these magic strings:
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
let target_triple_str = match target {
Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Linux,
..
} => "x86_64-unknown-linux-gnu",
Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Pc,
operating_system: OperatingSystem::Linux,
..
} => "x86_64-pc-linux-gnu",
Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Darwin,
..
} => "x86_64-unknown-darwin10",
Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Apple,
operating_system: OperatingSystem::Darwin,
..
} => "x86_64-apple-darwin10",
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
};
let target_machine = Target::from_name(arch_str)
.unwrap()
.create_target_machine(
&TargetTriple::create(target_triple_str),
arch_str,
"+avx2", // TODO this string was used uncritically from an example, and should be reexamined
opt,
reloc,
model,
)
.unwrap();
target_machine target_machine
.write_to_file(&env.module, FileType::Object, &dest_filename) .write_to_file(&env.module, FileType::Object, &dest_filename)

View file

@ -0,0 +1,76 @@
use inkwell::targets::{
CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple,
};
use inkwell::OptimizationLevel;
use target_lexicon::{Architecture, OperatingSystem, Triple};
pub fn target_triple_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings:
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
match target {
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
..
} => "x86_64-unknown-linux-gnu",
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Darwin,
..
} => "x86_64-unknown-darwin10",
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
}
}
/// NOTE: arch_str is *not* the same as the beginning of the magic target triple
/// string! For example, if it's "x86-64" here, the magic target triple string
/// will begin with "x86_64" (with an underscore) instead.
pub fn arch_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings:
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
match target.architecture {
Architecture::X86_64 => {
Target::initialize_x86(&InitializationConfig::default());
"x86-64"
}
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
// NOTE: why not enable arm and wasm by default?
//
// We had some trouble getting them to link properly. This may be resolved in the
// future, or maybe it was just some weird configuration on one machine.
Target::initialize_arm(&InitializationConfig::default());
"arm"
}
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
Target::initialize_webassembly(&InitializationConfig::default());
"wasm32"
}
_ => panic!(
"TODO gracefully handle unsupported target architecture: {:?}",
target.architecture
),
}
}
pub fn target_machine(
target: &Triple,
opt: OptimizationLevel,
reloc: RelocMode,
model: CodeModel,
) -> Option<TargetMachine> {
let arch = arch_str(target);
Target::from_name(arch).unwrap().create_target_machine(
&TargetTriple::create(target_triple_str(target)),
arch,
"+avx2", // TODO this string was used uncritically from an example, and should be reexamined
opt,
reloc,
model,
)
}

View file

@ -6,8 +6,8 @@
mod libm; mod libm;
/// TODO replace this with a normal Inkwell build_cast call - this was just /// TODO this is no longer used. Feel free to delete it the next time
/// used as a proof of concept for getting bitcode importing working! /// we need to rebuild builtins.bc!
#[no_mangle] #[no_mangle]
pub fn i64_to_f64_(num: i64) -> f64 { pub fn i64_to_f64_(num: i64) -> f64 {
num as f64 num as f64

View file

@ -12,6 +12,7 @@ pub enum Mode {
Uniqueness, Uniqueness,
} }
#[derive(Debug, Clone)]
pub struct StdLib { pub struct StdLib {
pub mode: Mode, pub mode: Mode,
pub types: MutMap<Symbol, (SolvedType, Region)>, pub types: MutMap<Symbol, (SolvedType, Region)>,

View file

@ -25,7 +25,7 @@ pub struct Env<'a> {
pub tailcallable_symbol: Option<Symbol>, pub tailcallable_symbol: Option<Symbol>,
/// Symbols which were referenced by qualified lookups. /// Symbols which were referenced by qualified lookups.
pub referenced_symbols: MutSet<Symbol>, pub qualified_lookups: MutSet<Symbol>,
pub ident_ids: IdentIds, pub ident_ids: IdentIds,
pub exposed_ident_ids: IdentIds, pub exposed_ident_ids: IdentIds,
@ -46,7 +46,7 @@ impl<'a> Env<'a> {
exposed_ident_ids, exposed_ident_ids,
problems: Vec::new(), problems: Vec::new(),
closures: MutMap::default(), closures: MutMap::default(),
referenced_symbols: MutSet::default(), qualified_lookups: MutSet::default(),
tailcallable_symbol: None, tailcallable_symbol: None,
} }
} }
@ -77,7 +77,7 @@ impl<'a> Env<'a> {
Some(ident_id) => { Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id); let symbol = Symbol::new(module_id, *ident_id);
self.referenced_symbols.insert(symbol); self.qualified_lookups.insert(symbol);
Ok(symbol) Ok(symbol)
} }
@ -101,7 +101,7 @@ impl<'a> Env<'a> {
Some(ident_id) => { Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id); let symbol = Symbol::new(module_id, *ident_id);
self.referenced_symbols.insert(symbol); self.qualified_lookups.insert(symbol);
Ok(symbol) Ok(symbol)
} }

View file

@ -126,6 +126,7 @@ pub enum Expr {
}, },
/// field accessor as a function, e.g. (.foo) expr /// field accessor as a function, e.g. (.foo) expr
Accessor { Accessor {
function_var: Variable,
record_var: Variable, record_var: Variable,
closure_var: Variable, closure_var: Variable,
ext_var: Variable, ext_var: Variable,
@ -550,6 +551,7 @@ pub fn canonicalize_expr<'a>(
} }
ast::Expr::AccessorFunction(field) => ( ast::Expr::AccessorFunction(field) => (
Accessor { Accessor {
function_var: var_store.fresh(),
record_var: var_store.fresh(), record_var: var_store.fresh(),
ext_var: var_store.fresh(), ext_var: var_store.fresh(),
closure_var: var_store.fresh(), closure_var: var_store.fresh(),

View file

@ -1,4 +1,3 @@
use crate::builtins::builtin_defs;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration}; use crate::def::{canonicalize_defs, sort_can_defs, Declaration};
use crate::env::Env; use crate::env::Env;
use crate::expr::Output; use crate::expr::Output;
@ -115,7 +114,7 @@ pub fn canonicalize_module_defs<'a>(
} }
} }
let (mut defs, _scope, output, symbols_introduced) = canonicalize_defs( let (defs, _scope, output, symbols_introduced) = canonicalize_defs(
&mut env, &mut env,
Output::default(), Output::default(),
var_store, var_store,
@ -149,17 +148,12 @@ pub fn canonicalize_module_defs<'a>(
} }
// Gather up all the symbols that were referenced from other modules. // Gather up all the symbols that were referenced from other modules.
for symbol in env.referenced_symbols.iter() { for symbol in env.qualified_lookups.iter() {
references.insert(*symbol); references.insert(*symbol);
} }
// Add defs for any referenced builtins. // NOTE previously we inserted builtin defs into the list of defs here
for (symbol, def) in builtin_defs(var_store) { // this is now done later, in file.rs.
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{
defs.can_defs_by_symbol.insert(symbol, def);
}
}
match sort_can_defs(&mut env, defs, Output::default()) { match sort_can_defs(&mut env, defs, Output::default()) {
(Ok(declarations), output) => { (Ok(declarations), output) => {
@ -250,6 +244,11 @@ pub fn canonicalize_module_defs<'a>(
references.insert(symbol); references.insert(symbol);
} }
// Gather up all the symbols that were referenced from other modules.
for symbol in env.qualified_lookups.iter() {
references.insert(*symbol);
}
Ok(ModuleOutput { Ok(ModuleOutput {
aliases, aliases,
rigid_variables, rigid_variables,

View file

@ -675,6 +675,7 @@ pub fn constrain_expr(
) )
} }
Accessor { Accessor {
function_var,
field, field,
record_var, record_var,
closure_var, closure_var,
@ -701,16 +702,19 @@ pub fn constrain_expr(
region, region,
); );
exists( let function_type = Type::Function(
vec![*record_var, *closure_var, field_var, ext_var],
And(vec![
Eq(
Type::Function(
vec![record_type], vec![record_type],
Box::new(Type::Variable(*closure_var)), Box::new(Type::Variable(*closure_var)),
Box::new(field_type), Box::new(field_type),
), );
expected,
exists(
vec![*record_var, *function_var, *closure_var, field_var, ext_var],
And(vec![
Eq(function_type.clone(), expected, category.clone(), region),
Eq(
function_type,
NoExpectation(Variable(*function_var)),
category, category,
region, region,
), ),

View file

@ -174,7 +174,11 @@ pub struct FreeVars {
pub wildcards: Vec<Variable>, pub wildcards: Vec<Variable>,
} }
fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut VarStore) -> Type { pub fn to_type(
solved_type: &SolvedType,
free_vars: &mut FreeVars,
var_store: &mut VarStore,
) -> Type {
use roc_types::solved_types::SolvedType::*; use roc_types::solved_types::SolvedType::*;
match solved_type { match solved_type {

View file

@ -67,6 +67,7 @@ pub fn constrain_decls(
// perform usage analysis on the whole file // perform usage analysis on the whole file
let mut var_usage = VarUsage::default(); let mut var_usage = VarUsage::default();
for decl in decls.iter().rev() { for decl in decls.iter().rev() {
// NOTE: rigids are empty because they are not shared between top-level definitions // NOTE: rigids are empty because they are not shared between top-level definitions
match decl { match decl {
@ -1445,6 +1446,7 @@ pub fn constrain_expr(
} }
Accessor { Accessor {
function_var,
field, field,
record_var, record_var,
closure_var, closure_var,
@ -1490,6 +1492,7 @@ pub fn constrain_expr(
exists( exists(
vec![ vec![
*record_var, *record_var,
*function_var,
*closure_var, *closure_var,
*field_var, *field_var,
*ext_var, *ext_var,
@ -1497,7 +1500,16 @@ pub fn constrain_expr(
field_uniq_var, field_uniq_var,
record_uniq_var, record_uniq_var,
], ],
And(vec![Eq(fn_type, expected, category, region), record_con]), And(vec![
Eq(fn_type.clone(), expected, category.clone(), region),
Eq(
fn_type,
Expected::NoExpectation(Variable(*function_var)),
category,
region,
),
record_con,
]),
) )
} }
RuntimeError(_) => True, RuntimeError(_) => True,

View file

@ -44,6 +44,7 @@ target-lexicon = "0.10"
[dev-dependencies] [dev-dependencies]
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_load = { path = "../load" }
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"

View file

@ -10,7 +10,7 @@ impl LayoutId {
// Returns something like "foo#1" when given a symbol that interns to "foo" // Returns something like "foo#1" when given a symbol that interns to "foo"
// and a LayoutId of 1. // and a LayoutId of 1.
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String { pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
format!("{}#{}", symbol.ident_string(interns), self.0) format!("{}_{}", symbol.ident_string(interns), self.0)
} }
} }

View file

@ -31,7 +31,7 @@ use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate}; use inkwell::{AddressSpace, IntPredicate};
use roc_collections::all::{ImMap, MutSet}; use roc_collections::all::{ImMap, MutSet};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{JoinPointId, Wrapped}; use roc_mono::ir::{JoinPointId, Wrapped};
use roc_mono::layout::{Builtin, Layout, MemoryMode}; use roc_mono::layout::{Builtin, Layout, MemoryMode};
use target_lexicon::CallingConvention; use target_lexicon::CallingConvention;
@ -53,6 +53,7 @@ pub enum OptLevel {
#[derive(Default, Debug, Clone, PartialEq)] #[derive(Default, Debug, Clone, PartialEq)]
pub struct Scope<'a, 'ctx> { pub struct Scope<'a, 'ctx> {
symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>, symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>,
pub top_level_thunks: ImMap<Symbol, (Layout<'a>, FunctionValue<'ctx>)>,
join_points: ImMap<JoinPointId, (BasicBlock<'ctx>, &'a [PointerValue<'ctx>])>, join_points: ImMap<JoinPointId, (BasicBlock<'ctx>, &'a [PointerValue<'ctx>])>,
} }
@ -63,23 +64,23 @@ impl<'a, 'ctx> Scope<'a, 'ctx> {
pub fn insert(&mut self, symbol: Symbol, value: (Layout<'a>, PointerValue<'ctx>)) { pub fn insert(&mut self, symbol: Symbol, value: (Layout<'a>, PointerValue<'ctx>)) {
self.symbols.insert(symbol, value); self.symbols.insert(symbol, value);
} }
pub fn insert_top_level_thunk(
&mut self,
symbol: Symbol,
layout: Layout<'a>,
function_value: FunctionValue<'ctx>,
) {
self.top_level_thunks
.insert(symbol, (layout, function_value));
}
fn remove(&mut self, symbol: &Symbol) { fn remove(&mut self, symbol: &Symbol) {
self.symbols.remove(symbol); self.symbols.remove(symbol);
} }
/*
fn get_join_point(&self, symbol: &JoinPointId) -> Option<&PhiValue<'ctx>> { pub fn retain_top_level_thunks_for_module(&mut self, module_id: ModuleId) {
self.join_points.get(symbol) self.top_level_thunks
.retain(|s, _| s.module_id() == module_id);
} }
fn remove_join_point(&mut self, symbol: &JoinPointId) {
self.join_points.remove(symbol);
}
fn get_mut_join_point(&mut self, symbol: &JoinPointId) -> Option<&mut PhiValue<'ctx>> {
self.join_points.get_mut(symbol)
}
fn insert_join_point(&mut self, symbol: JoinPointId, value: PhiValue<'ctx>) {
self.join_points.insert(symbol, value);
}
*/
} }
pub struct Env<'a, 'ctx, 'env> { pub struct Env<'a, 'ctx, 'env> {
@ -321,6 +322,7 @@ pub fn construct_optimization_passes<'a>(
pmb.set_optimization_level(OptimizationLevel::None); pmb.set_optimization_level(OptimizationLevel::None);
} }
OptLevel::Optimize => { OptLevel::Optimize => {
pmb.set_optimization_level(OptimizationLevel::Aggressive);
// this threshold seems to do what we want // this threshold seems to do what we want
pmb.set_inliner_with_threshold(275); pmb.set_inliner_with_threshold(275);
@ -416,26 +418,47 @@ pub fn build_roc_main<'a, 'ctx, 'env>(
env.arena.alloc(roc_main_fn) env.arena.alloc(roc_main_fn)
} }
pub fn promote_to_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
symbol: Symbol,
layout: &Layout<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let wrapped = env.module.get_function(&fn_name).unwrap();
make_main_function_help(env, layout, wrapped)
}
pub fn make_main_function<'a, 'ctx, 'env>( pub fn make_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>, layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>, main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) { ) -> (&'static str, &'a FunctionValue<'ctx>) {
// internal main function
let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body);
make_main_function_help(env, layout, roc_main_fn)
}
fn make_main_function_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
roc_main_fn: FunctionValue<'ctx>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
// build the C calling convention wrapper
use inkwell::types::BasicType; use inkwell::types::BasicType;
use PassVia::*; use PassVia::*;
let context = env.context; let context = env.context;
let builder = env.builder; 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 main_fn_name = "$Test.main";
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()]; let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
let main_return_layout = Layout::Struct(&fields); let main_return_layout = Layout::Struct(&fields);
@ -1136,18 +1159,35 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
list_literal(env, inplace, scope, elem_layout, elems) list_literal(env, inplace, scope, elem_layout, elems)
} }
FunctionPointer(symbol, layout) => { FunctionPointer(symbol, layout) => {
match scope.top_level_thunks.get(symbol) {
Some((_layout, function_value)) => {
// this is a 0-argument thunk, evaluate it!
let call =
env.builder
.build_call(*function_value, &[], "evaluate_top_level_thunk");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap()
}
None => {
// this is a function pointer, store it
let fn_name = layout_ids let fn_name = layout_ids
.get(*symbol, layout) .get(*symbol, layout)
.to_symbol_string(*symbol, &env.interns); .to_symbol_string(*symbol, &env.interns);
let ptr = env let ptr = env
.module .module
.get_function(fn_name.as_str()) .get_function(fn_name.as_str())
.unwrap_or_else(|| panic!("Could not get pointer to unknown function {:?}", symbol)) .unwrap_or_else(|| {
panic!("Could not get pointer to unknown function {:?}", symbol)
})
.as_global_value() .as_global_value()
.as_pointer_value(); .as_pointer_value();
BasicValueEnum::PointerValue(ptr) BasicValueEnum::PointerValue(ptr)
} }
}
}
RuntimeErrorFunction(_) => todo!(), RuntimeErrorFunction(_) => todo!(),
} }
} }
@ -1511,16 +1551,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
increment_refcount_layout(env, parent, layout_ids, value, &layout); increment_refcount_layout(env, parent, layout_ids, value, &layout);
} }
/*
match layout {
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => {
increment_refcount_list(env, parent, value.into_struct_value());
build_exp_stmt(env, layout_ids, scope, parent, cont)
}
_ => build_exp_stmt(env, layout_ids, scope, parent, cont),
}
*/
build_exp_stmt(env, layout_ids, scope, parent, cont) build_exp_stmt(env, layout_ids, scope, parent, cont)
} }
Dec(symbol, cont) => { Dec(symbol, cont) => {
@ -1836,6 +1866,7 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
pub fn build_proc<'a, 'ctx, 'env>( pub fn build_proc<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>, env: &'a Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
mut scope: Scope<'a, 'ctx>,
proc: roc_mono::ir::Proc<'a>, proc: roc_mono::ir::Proc<'a>,
fn_val: FunctionValue<'ctx>, fn_val: FunctionValue<'ctx>,
) { ) {
@ -1848,8 +1879,6 @@ pub fn build_proc<'a, 'ctx, 'env>(
builder.position_at_end(entry); builder.position_at_end(entry);
let mut scope = Scope::default();
// Add args to scope // Add args to scope
for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) { for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) {
set_name(arg_val, arg_symbol.ident_string(&env.interns)); set_name(arg_val, arg_symbol.ident_string(&env.interns));
@ -1899,15 +1928,16 @@ fn call_with_args<'a, 'ctx, 'env>(
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let fn_name = fn_name.as_str();
let fn_val = env let fn_val = env.module.get_function(fn_name).unwrap_or_else(|| {
.module
.get_function(fn_name.as_str())
.unwrap_or_else(|| {
if symbol.is_builtin() { if symbol.is_builtin() {
panic!("Unrecognized builtin function: {:?}", symbol) panic!("Unrecognized builtin function: {:?}", fn_name)
} else { } else {
panic!("Unrecognized non-builtin function: {:?}", symbol) panic!(
"Unrecognized non-builtin function: {:?} {:?}",
fn_name, layout
)
} }
}); });
@ -2626,8 +2656,13 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
)) ))
} }
NumToFloat => { NumToFloat => {
// TODO specialize this to be not just for i64! // This is an Int, so we need to convert it.
call_bitcode_fn(NumToFloat, env, &[arg.into()], "i64_to_f64_") bd.build_cast(
InstructionOpcode::SIToFP,
arg,
env.context.f64_type(),
"i64_to_f64",
)
} }
_ => { _ => {
unreachable!("Unrecognized int unary operation: {:?}", op); unreachable!("Unrecognized int unary operation: {:?}", op);

View file

@ -1019,8 +1019,7 @@ mod gen_list {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \shared -> wrapper = \shared ->
# This should not mutate the original # This should not mutate the original
x = x =
when List.get (List.set shared 1 7.7) 1 is when List.get (List.set shared 1 7.7) 1 is
@ -1034,7 +1033,7 @@ mod gen_list {
{ x, y } { x, y }
main [ 2.1, 4.3 ] wrapper [ 2.1, 4.3 ]
"# "#
), ),
(7.7, 4.3), (7.7, 4.3),
@ -1047,7 +1046,6 @@ mod gen_list {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} ->
shared = [ 2, 4 ] shared = [ 2, 4 ]
# This List.set is out of bounds, and should have no effect # This List.set is out of bounds, and should have no effect
@ -1062,8 +1060,6 @@ mod gen_list {
Err _ -> 0 Err _ -> 0
{ x, y } { x, y }
main {}
"# "#
), ),
(4, 4), (4, 4),
@ -1149,6 +1145,9 @@ mod gen_list {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Quicksort provides [ main ] imports []
swap : Int, Int, List a -> List a swap : Int, Int, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
@ -1159,6 +1158,8 @@ mod gen_list {
_ -> _ ->
[] []
main =
swap 0 1 [ 1, 2 ] swap 0 1 [ 1, 2 ]
"# "#
), ),

View file

@ -482,12 +482,12 @@ mod gen_num {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
when 10 is when 10 is
x if x == 5 -> 0 x if x == 5 -> 0
_ -> 42 _ -> 42
main {} wrapper {}
"# "#
), ),
42, 42,
@ -500,12 +500,12 @@ mod gen_num {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
when 10 is when 10 is
x if x == 10 -> 42 x if x == 10 -> 42
_ -> 0 _ -> 0
main {} wrapper {}
"# "#
), ),
42, 42,

View file

@ -276,10 +276,10 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
(\a -> a) 5 (\a -> a) 5
main {} wrapper {}
"# "#
), ),
5, 5,
@ -292,14 +292,14 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
alwaysFloatIdentity : Int -> (Float -> Float) alwaysFloatIdentity : Int -> (Float -> Float)
alwaysFloatIdentity = \num -> alwaysFloatIdentity = \num ->
(\a -> a) (\a -> a)
(alwaysFloatIdentity 2) 3.14 (alwaysFloatIdentity 2) 3.14
main {} wrapper {}
"# "#
), ),
3.14, 3.14,
@ -402,8 +402,9 @@ mod gen_primitives {
i64 i64
); );
} }
#[test] #[test]
fn gen_nested_defs() { fn gen_nested_defs_old() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -443,6 +444,28 @@ mod gen_primitives {
); );
} }
#[test]
fn let_x_in_x() {
assert_evals_to!(
indoc!(
r#"
x = 5
answer =
1337
unused =
nested = 17
nested
answer
"#
),
1337,
i64
);
}
#[test] #[test]
fn factorial() { fn factorial() {
assert_evals_to!( assert_evals_to!(
@ -505,11 +528,31 @@ mod gen_primitives {
); );
} }
#[test]
fn top_level_constant() {
assert_evals_to!(
indoc!(
r#"
app LinkedListLen0 provides [ main ] imports []
pi = 3.1415
main =
pi + pi
"#
),
3.1415 + 3.1415,
f64
);
}
#[test] #[test]
fn linked_list_len_0() { fn linked_list_len_0() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app LinkedListLen0 provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
nil : LinkedList Int nil : LinkedList Int
@ -522,13 +565,12 @@ mod gen_primitives {
Cons _ rest -> 1 + length rest Cons _ rest -> 1 + length rest
main =
length nil length nil
"# "#
), ),
0, 0,
i64, i64
|x| x,
false
); );
} }
@ -537,6 +579,8 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app LinkedListLenTwice0 provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
nil : LinkedList Int nil : LinkedList Int
@ -548,13 +592,12 @@ mod gen_primitives {
Nil -> 0 Nil -> 0
Cons _ rest -> 1 + length rest Cons _ rest -> 1 + length rest
main =
length nil + length nil length nil + length nil
"# "#
), ),
0, 0,
i64, i64
|x| x,
false
); );
} }
@ -563,6 +606,8 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
one : LinkedList Int one : LinkedList Int
@ -574,14 +619,12 @@ mod gen_primitives {
Nil -> 0 Nil -> 0
Cons _ rest -> 1 + length rest Cons _ rest -> 1 + length rest
main =
length one length one
"# "#
), ),
1, 1,
i64, i64
|x| x,
false
); );
} }
@ -590,6 +633,8 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
one : LinkedList Int one : LinkedList Int
@ -601,14 +646,12 @@ mod gen_primitives {
Nil -> 0 Nil -> 0
Cons _ rest -> 1 + length rest Cons _ rest -> 1 + length rest
main =
length one + length one length one + length one
"# "#
), ),
2, 2,
i64, i64
|x| x,
false
); );
} }
@ -617,6 +660,8 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList Int three : LinkedList Int
@ -629,52 +674,83 @@ mod gen_primitives {
Cons _ rest -> 1 + length rest Cons _ rest -> 1 + length rest
main =
length three length three
"# "#
), ),
3, 3,
i64,
|x| x,
false
);
}
#[test]
fn linked_list_sum() {
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList Int
three = Cons 3 (Cons 2 (Cons 1 Nil))
sum : LinkedList a -> Int
sum = \list ->
when list is
Nil -> 0
Cons x rest -> x + sum rest
sum three
"#
),
3 + 2 + 1,
i64 i64
); );
} }
#[test] #[test]
fn linked_list_map() { fn linked_list_sum_num_a() {
// `f` is not actually a function, so the call to it fails currently
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app Test provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList Int three : LinkedList Int
three = Cons 3 (Cons 2 (Cons 1 Nil)) three = Cons 3 (Cons 2 (Cons 1 Nil))
sum : LinkedList a -> Int
sum : LinkedList (Num a) -> Num a
sum = \list ->
when list is
Nil -> 0
Cons x rest -> x + sum rest
main =
sum three
"#
),
3 + 2 + 1,
i64
)
}
#[test]
fn linked_list_sum_int() {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ]
zero : LinkedList Int
zero = Nil
sum : LinkedList Int -> Int
sum = \list ->
when list is
Nil -> 0
Cons x rest -> x + sum rest
main =
sum zero
"#
),
0,
i64
)
}
#[test]
fn linked_list_map() {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList Int
three = Cons 3 (Cons 2 (Cons 1 Nil))
sum : LinkedList (Num a) -> Num a
sum = \list -> sum = \list ->
when list is when list is
Nil -> 0 Nil -> 0
@ -686,6 +762,7 @@ mod gen_primitives {
Nil -> Nil Nil -> Nil
Cons x rest -> Cons (f x) (map f rest) Cons x rest -> Cons (f x) (map f rest)
main =
sum (map (\_ -> 1) three) sum (map (\_ -> 1) three)
"# "#
), ),

View file

@ -678,15 +678,58 @@ mod gen_records {
} }
#[test] #[test]
fn just_to_be_sure() { fn accessor() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
{ a: 1, b : 2, c : 3 } .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
"# "#
), ),
[1, 2, 3], 7,
[i64; 3] i64
);
}
#[test]
fn accessor_single_element_record() {
assert_evals_to!(
indoc!(
r#"
.foo { foo: 4 }
"#
),
4,
i64
);
}
#[test]
fn update_record() {
assert_evals_to!(
indoc!(
r#"
rec = { foo: 42, bar: 6.28 }
{ rec & foo: rec.foo + 1 }
"#
),
(6.28, 43),
(f64, i64)
);
}
#[test]
fn update_single_element_record() {
assert_evals_to!(
indoc!(
r#"
rec = { foo: 42}
{ rec & foo: rec.foo + 1 }
"#
),
43,
i64
); );
} }
} }

View file

@ -455,12 +455,12 @@ mod gen_tags {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
when 2 is when 2 is
2 if False -> 0 2 if False -> 0
_ -> 42 _ -> 42
main {} wrapper {}
"# "#
), ),
42, 42,
@ -473,12 +473,12 @@ mod gen_tags {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
when 2 is when 2 is
2 if True -> 42 2 if True -> 42
_ -> 0 _ -> 0
main {} wrapper {}
"# "#
), ),
42, 42,
@ -491,12 +491,12 @@ mod gen_tags {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
when 2 is when 2 is
_ if False -> 0 _ if False -> 0
_ -> 42 _ -> 42
main {} wrapper {}
"# "#
), ),
42, 42,
@ -674,7 +674,7 @@ mod gen_tags {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
x : [ Red, White, Blue ] x : [ Red, White, Blue ]
x = Blue x = Blue
@ -686,7 +686,7 @@ mod gen_tags {
y y
main {} wrapper {}
"# "#
), ),
3.1, 3.1,
@ -699,7 +699,7 @@ mod gen_tags {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
main = \{} -> wrapper = \{} ->
y = y =
when 1 + 2 is when 1 + 2 is
3 -> 3 3 -> 3
@ -708,7 +708,7 @@ mod gen_tags {
y y
main {} wrapper {}
"# "#
), ),
3, 3,

View file

@ -1,9 +1,22 @@
use roc_collections::all::MutSet; use roc_collections::all::{MutMap, MutSet};
use roc_types::subs::Subs;
pub fn helper_without_uniqueness<'a>( fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
pub fn helper<'a>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
src: &str, src: &str,
stdlib: roc_builtins::std::StdLib,
leak: bool, leak: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> ( ) -> (
@ -11,26 +24,62 @@ pub fn helper_without_uniqueness<'a>(
Vec<roc_problem::can::Problem>, Vec<roc_problem::can::Problem>,
inkwell::execution_engine::ExecutionEngine<'a>, inkwell::execution_engine::ExecutionEngine<'a>,
) { ) {
use crate::helpers::{can_expr, infer_expr, CanExprOut};
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header}; use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
use roc_mono::layout::Layout; use std::path::{Path, PathBuf};
let stdlib_mode = stdlib.mode;
let filename = PathBuf::from("Test.roc");
let src_dir = Path::new("fake/test/path");
let module_src;
let temp;
if src.starts_with("app") {
// this is already a module
module_src = src;
} else {
// this is an expression, promote it to a module
temp = promote_expr_to_module(src);
module_src = &temp;
}
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
arena,
filename,
&module_src,
stdlib,
src_dir,
exposed_types,
);
let loaded = loaded.expect("failed to load module");
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
can_problems,
type_problems,
mono_problems,
mut procedures,
interns,
exposed_to_host,
..
} = loaded;
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap();
let (_, main_fn_layout) = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.unwrap()
.clone();
let target = target_lexicon::Triple::host(); let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let CanExprOut {
loc_expr,
var_store,
var,
constraint,
home,
interns,
problems,
..
} = can_expr(src);
// don't panic based on the errors here, so we can test that RuntimeError generates the correct code // don't panic based on the errors here, so we can test that RuntimeError generates the correct code
let errors = problems let errors = can_problems
.into_iter() .into_iter()
.filter(|problem| { .filter(|problem| {
use roc_problem::can::Problem::*; use roc_problem::can::Problem::*;
@ -43,15 +92,18 @@ pub fn helper_without_uniqueness<'a>(
}) })
.collect::<Vec<roc_problem::can::Problem>>(); .collect::<Vec<roc_problem::can::Problem>>();
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);
assert_eq!( assert_eq!(
unify_problems, type_problems,
Vec::new(), Vec::new(),
"Encountered type mismatches: {:?}", "Encountered type mismatches: {:?}",
unify_problems type_problems,
);
assert_eq!(
mono_problems,
Vec::new(),
"Encountered monomorphization errors: {:?}",
mono_problems,
); );
let module = roc_gen::llvm::build::module_from_builtins(context, "app"); let module = roc_gen::llvm::build::module_from_builtins(context, "app");
@ -66,102 +118,84 @@ pub fn helper_without_uniqueness<'a>(
let (module_pass, function_pass) = let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level); roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}",
err
)
});
let execution_engine = module let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None) .create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test"); .expect("Error creating JIT execution engine for test");
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env { let env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
builder: &builder, builder: &builder,
context: context, context,
interns, interns,
module, module,
ptr_bytes, ptr_bytes,
leak: leak, leak,
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(), exposed_to_host: MutSet::default(),
}; };
let mut procs = roc_mono::ir::Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default(); let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
let mut headers = Vec::with_capacity(procedures.len());
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
};
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
None => 0,
};
Vec::with_capacity(num_headers)
};
let mut layout_cache = roc_mono::layout::LayoutCache::default();
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(
procs.runtime_errors,
roc_collections::all::MutMap::default()
);
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
let main_body = roc_mono::inc_dec::visit_declaration(
mono_env.arena,
param_map,
mono_env.arena.alloc(main_body),
);
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for ((symbol, layout), proc) in procs.drain() { let mut scope = Scope::default();
for ((symbol, layout), proc) in procedures.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, layout, fn_val);
}
headers.push((proc, fn_val)); headers.push((proc, fn_val));
} }
// Build each proc using its header info. // Build each proc using its header info.
for (proc, fn_val) in headers { for (proc, fn_val) in headers {
build_proc(&env, &mut layout_ids, proc, fn_val); let mut current_scope = scope.clone();
// only have top-level thunks for this proc's module in scope
// this retain is not needed for correctness, but will cause less confusion when debugging
let home = proc.name.module_id();
current_scope.retain_top_level_thunks_for_module(home);
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
if fn_val.verify(true) { if fn_val.verify(true) {
function_pass.run_on(&fn_val); function_pass.run_on(&fn_val);
} else { } else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
eprintln!( eprintln!(
"\n\nFunction {:?} failed LLVM verification in NON-OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap() "\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
fn_val.get_name().to_str().unwrap(),
mode,
); );
fn_val.print_to_stderr(); fn_val.print_to_stderr();
panic!( panic!(
"The preceding code was from {:?}, which failed LLVM verification in NON-OPTIMIZED build.", fn_val.get_name().to_str().unwrap() "The preceding code was from {:?}, which failed LLVM verification in {} build.",
fn_val.get_name().to_str().unwrap(),
mode,
); );
} }
} }
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
let (main_fn_name, main_fn) = &env,
roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body); &mut layout_ids,
main_fn_symbol,
&main_fn_layout,
);
// Uncomment this to see the module's un-optimized LLVM instruction output: // Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr(); // env.module.print_to_stderr();
@ -185,177 +219,6 @@ pub fn helper_without_uniqueness<'a>(
(main_fn_name, errors, execution_engine.clone()) (main_fn_name, errors, execution_engine.clone())
} }
pub fn helper_with_uniqueness<'a>(
arena: &'a bumpalo::Bump,
src: &str,
leak: bool,
context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
use crate::helpers::{infer_expr, uniq_expr};
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_mono::layout::Layout;
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let (loc_expr, _output, problems, subs, var, constraint, home, interns) = uniq_expr(src);
let errors = problems
.into_iter()
.filter(|problem| {
use roc_problem::can::Problem::*;
// Ignore "unused" problems
match problem {
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false,
_ => true,
}
})
.collect::<Vec<roc_problem::can::Problem>>();
assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors);
let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
assert_eq!(
unify_problems,
Vec::new(),
"Encountered one or more type mismatches: {:?}",
unify_problems
);
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(context, "app"));
let builder = context.create_builder();
let opt_level = if cfg!(debug_assertions) {
roc_gen::llvm::build::OptLevel::Normal
} else {
roc_gen::llvm::build::OptLevel::Optimize
};
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}",
err
)
});
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: context,
interns,
module,
ptr_bytes,
leak: leak,
exposed_to_host: MutSet::default(),
};
let mut procs = roc_mono::ir::Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
};
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
None => 0,
};
Vec::with_capacity(num_headers)
};
let mut layout_cache = roc_mono::layout::LayoutCache::default();
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(
procs.runtime_errors,
roc_collections::all::MutMap::default()
);
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
let main_body = roc_mono::inc_dec::visit_declaration(
mono_env.arena,
param_map,
mono_env.arena.alloc(main_body),
);
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for ((symbol, layout), proc) in procs.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val));
}
// Build each proc using its header info.
for (proc, fn_val) in headers {
build_proc(&env, &mut layout_ids, proc, fn_val);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
} else {
eprintln!(
"\n\nFunction {:?} failed LLVM verification in OPTIMIZED build. Its content was:\n",
fn_val.get_name().to_str().unwrap()
);
fn_val.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in OPTIMIZED build.", fn_val.get_name().to_str().unwrap()
);
}
}
let (main_fn_name, main_fn) =
roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
// you're in the version with uniqueness!
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
fpm.run_on(&main_fn);
} else {
panic!("main function {} failed LLVM verification in OPTIMIZED build. Uncomment nearby statements to see more details.", main_fn_name);
}
mpm.run_on(module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
(main_fn_name, execution_engine)
}
// TODO this is almost all code duplication with assert_llvm_evals_to // TODO this is almost all code duplication with assert_llvm_evals_to
// the only difference is that this calls uniq_expr instead of can_expr. // the only difference is that this calls uniq_expr instead of can_expr.
// Should extract the common logic into test helpers. // Should extract the common logic into test helpers.
@ -370,11 +233,17 @@ macro_rules! assert_opt_evals_to {
let context = Context::create(); let context = Context::create();
let (main_fn_name, execution_engine) = let stdlib = roc_builtins::unique::uniq_stdlib();
$crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context);
let transform = |success| assert_eq!($transform(success), $expected); let (main_fn_name, errors, execution_engine) =
run_jit_function!(execution_engine, main_fn_name, $ty, transform) $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| {
let expected = $expected;
let given = $transform(success);
assert_eq!(&given, &expected);
};
run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors)
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
@ -392,9 +261,10 @@ macro_rules! assert_llvm_evals_to {
let arena = Bump::new(); let arena = Bump::new();
let context = Context::create(); let context = Context::create();
let stdlib = roc_builtins::std::standard_stdlib();
let (main_fn_name, errors, execution_engine) = let (main_fn_name, errors, execution_engine) =
$crate::helpers::eval::helper_without_uniqueness(&arena, $src, $leak, &context); $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| { let transform = |success| {
let expected = $expected; let expected = $expected;
@ -411,29 +281,20 @@ macro_rules! assert_llvm_evals_to {
#[macro_export] #[macro_export]
macro_rules! assert_evals_to { macro_rules! assert_evals_to {
($src:expr, $expected:expr, $ty:ty) => { ($src:expr, $expected:expr, $ty:ty) => {{
assert_evals_to!($src, $expected, $ty, (|val| val));
}};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument.
{
assert_evals_to!($src, $expected, $ty, $transform, true);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
// Run un-optimized tests, and then optimized tests, in separate scopes. // Run un-optimized tests, and then optimized tests, in separate scopes.
// These each rebuild everything from scratch, starting with // These each rebuild everything from scratch, starting with
// parsing the source, so that there's no chance their passing // parsing the source, so that there's no chance their passing
// or failing depends on leftover state from the previous one. // or failing depends on leftover state from the previous one.
{
assert_llvm_evals_to!($src, $expected, $ty, (|val| val));
}
{
assert_opt_evals_to!($src, $expected, $ty, (|val| val));
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument.
{
assert_llvm_evals_to!($src, $expected, $ty, $transform);
}
{
assert_opt_evals_to!($src, $expected, $ty, $transform);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
// Same as above, except with an additional transformation argument.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak); assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
} }

View file

@ -3,32 +3,6 @@ extern crate bumpalo;
#[macro_use] #[macro_use]
pub mod eval; pub mod eval;
use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint;
use roc_can::env::Env;
use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator;
use roc_can::scope::Scope;
use roc_collections::all::{ImMap, MutMap, SendMap};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_solve::solve;
use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type;
pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
}
/// Used in the with_larger_debug_stack() function, for tests that otherwise /// Used in the with_larger_debug_stack() function, for tests that otherwise
/// run out of stack space in debug builds (but don't in --release builds) /// run out of stack space in debug builds (but don't in --release builds)
#[allow(dead_code)] #[allow(dead_code)]
@ -68,249 +42,3 @@ where
{ {
run_test() run_test()
} }
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<roc_solve::solve::TypeError>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: SendMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
}
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str)
}
pub fn uniq_expr(
expr_str: &str,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
}
pub fn uniq_expr_with(
arena: &Bump,
expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let home = test_home();
let CanExprOut {
loc_expr,
output,
problems,
var_store: mut old_var_store,
var,
interns,
..
} = can_expr_with(arena, home, expr_str);
// double check
let mut var_store = VarStore::new(old_var_store.fresh());
let expected2 = Expected::NoExpectation(Type::Variable(var));
let constraint = roc_constrain::uniq::constrain_declaration(
home,
&mut var_store,
Region::zero(),
&loc_expr,
declared_idents,
expected2,
);
let stdlib = uniq_stdlib();
let types = stdlib.types;
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
// load builtin values
// TODO what to do with those rigids?
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
// load builtin types
let mut constraint = load_builtin_aliases(stdlib.aliases, constraint, &mut var_store);
constraint.instantiate_aliases(&mut var_store);
let subs2 = Subs::new(var_store.into());
(
loc_expr, output, problems, subs2, var, constraint, home, interns,
)
}
pub struct CanExprOut {
pub loc_expr: Located<Expr>,
pub output: Output,
pub problems: Vec<Problem>,
pub home: ModuleId,
pub interns: Interns,
pub var_store: VarStore,
pub var: Variable,
pub constraint: Constraint,
}
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e
)
});
let mut var_store = VarStore::default();
let var = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(var));
let module_ids = ModuleIds::default();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar_expr(arena, &loc_expr);
let mut scope = Scope::new(home);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr(
&mut env,
&mut var_store,
&mut scope,
Region::zero(),
&loc_expr.value,
);
// Add builtin defs (e.g. List.get) directly to the canonical Expr,
// since we aren't using modules here.
let mut with_builtins = loc_expr.value;
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
for (symbol, def) in builtin_defs {
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{
with_builtins = Expr::LetNonRec(
Box::new(def),
Box::new(Located {
region: Region::zero(),
value: with_builtins,
}),
var_store.fresh(),
SendMap::default(),
);
}
}
let loc_expr = Located {
region: loc_expr.region,
value: with_builtins,
};
let constraint = constrain_expr(
&roc_constrain::expr::Env {
rigids: ImMap::default(),
home,
},
loc_expr.region,
&loc_expr.value,
expected,
);
let types = roc_builtins::std::types();
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
// load builtin values
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
// TODO determine what to do with those rigids
// for var in introduced_rigids {
// output.ftv.insert(var, format!("internal_{:?}", var).into());
// }
//load builtin types
let mut constraint =
load_builtin_aliases(roc_builtins::std::aliases(), constraint, &mut var_store);
constraint.instantiate_aliases(&mut var_store);
let mut all_ident_ids = MutMap::default();
// When pretty printing types, we may need the exposed builtins,
// so include them in the Interns we'll ultimately return.
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
all_ident_ids.insert(module_id, ident_ids);
}
all_ident_ids.insert(home, env.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids,
};
CanExprOut {
loc_expr,
output,
problems: env.problems,
home: env.home,
var_store,
interns,
var,
constraint,
}
}

View file

@ -17,8 +17,10 @@ roc_problem = { path = "../problem" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
crossbeam = "0.7" crossbeam = "0.7"
num_cpus = "1" num_cpus = "1"

View file

@ -5,6 +5,7 @@ use crossbeam::channel::{bounded, Sender};
use crossbeam::deque::{Injector, Stealer, Worker}; use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread; use crossbeam::thread;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use parking_lot::Mutex;
use roc_builtins::std::{Mode, StdLib}; use roc_builtins::std::{Mode, StdLib};
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::def::Declaration; use roc_can::def::Declaration;
@ -32,7 +33,7 @@ use std::io;
use std::iter; use std::iter;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
/// Filename extension for normal Roc modules /// Filename extension for normal Roc modules
@ -349,8 +350,14 @@ pub fn load(
let arena = Bump::new(); let arena = Bump::new();
// Reserve one CPU for the main thread, and let all the others be eligible // Reserve one CPU for the main thread, and let all the others be eligible
// to spawn workers. // to spawn workers. We use .max(2) to enforce that we always
let num_workers = num_cpus::get() - 1; // end up with at least 1 worker - since (.max(2) - 1) will
// always return a number that's at least 1. Using
// .max(2) on the initial number of CPUs instead of
// doing .max(1) on the entire expression guards against
// num_cpus returning 0, while also avoiding wrapping
// unsigned subtraction overflow.
let num_workers = num_cpus::get().max(2) - 1;
let mut worker_arenas = bumpalo::collections::Vec::with_capacity_in(num_workers, &arena); let mut worker_arenas = bumpalo::collections::Vec::with_capacity_in(num_workers, &arena);
@ -878,8 +885,7 @@ fn finish<'a>(
let module_ids = Arc::try_unwrap(state.arc_modules) let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids")) .unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
.into_inner() .into_inner();
.expect("Unwrapping mutex for module_ids");
let interns = Interns { let interns = Interns {
module_ids, module_ids,
@ -1079,10 +1085,8 @@ fn send_header<'a>(
let ident_ids = { let ident_ids = {
// Lock just long enough to perform the minimal operations necessary. // Lock just long enough to perform the minimal operations necessary.
let mut module_ids = (*module_ids).lock().expect("Failed to acquire lock for interning module IDs, presumably because a thread panicked."); let mut module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).lock().expect( let mut ident_ids_by_module = (*ident_ids_by_module).lock();
"Failed to acquire lock for interning ident IDs, presumably because a thread panicked.",
);
home = module_ids.get_or_insert(&declared_name.as_inline_str()); home = module_ids.get_or_insert(&declared_name.as_inline_str());
@ -1243,9 +1247,7 @@ impl<'a> BuildTask<'a> {
let mut dep_idents: IdentIdsByModule = IdentIds::exposed_builtins(num_deps); let mut dep_idents: IdentIdsByModule = IdentIds::exposed_builtins(num_deps);
{ {
let ident_ids_by_module = (*ident_ids_by_module).lock().expect( let ident_ids_by_module = (*ident_ids_by_module).lock();
"Failed to acquire lock for interning ident IDs, presumably because a thread panicked.",
);
// Populate dep_idents with each of their IdentIds, // Populate dep_idents with each of their IdentIds,
// which we'll need during canonicalization to translate // which we'll need during canonicalization to translate
@ -1277,9 +1279,11 @@ impl<'a> BuildTask<'a> {
waiting_for_solve.insert(module_id, solve_needed); waiting_for_solve.insert(module_id, solve_needed);
let module_ids = { // Clone the module_ids we'll need for canonicalization.
(*module_ids).lock().expect("Failed to acquire lock for obtaining module IDs, presumably because a thread panicked.").clone() // This should be small, and cloning it should be quick.
}; // We release the lock as soon as we're done cloning, so we don't have
// to lock the global module_ids while canonicalizing any given module.
let module_ids = { (*module_ids).lock().clone() };
// Now that we have waiting_for_solve populated, continue parsing, // Now that we have waiting_for_solve populated, continue parsing,
// canonicalizing, and constraining the module. // canonicalizing, and constraining the module.

File diff suppressed because it is too large Load diff

View file

@ -14,12 +14,13 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_load { mod test_load {
use crate::helpers::fixtures_dir; use crate::helpers::fixtures_dir;
use bumpalo::Bump;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_can::def::Declaration::*; use roc_can::def::Declaration::*;
use roc_can::def::Def; use roc_can::def::Def;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_constrain::module::SubsByModule; use roc_constrain::module::SubsByModule;
use roc_load::file::{load, LoadedModule}; use roc_load::file::LoadedModule;
use roc_module::symbol::{Interns, ModuleId}; use roc_module::symbol::{Interns, ModuleId};
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs; use roc_types::subs::Subs;
@ -34,9 +35,11 @@ mod test_load {
) -> LoadedModule { ) -> LoadedModule {
let src_dir = fixtures_dir().join(dir_name); let src_dir = fixtures_dir().join(dir_name);
let filename = src_dir.join(format!("{}.roc", module_name)); let filename = src_dir.join(format!("{}.roc", module_name));
let loaded = load( let arena = Bump::new();
let loaded = roc_load::file::load_and_typecheck(
&arena,
filename, filename,
&roc_builtins::std::standard_stdlib(), roc_builtins::std::standard_stdlib(),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
); );
@ -128,9 +131,11 @@ mod test_load {
let subs_by_module = MutMap::default(); let subs_by_module = MutMap::default();
let src_dir = fixtures_dir().join("interface_with_deps"); let src_dir = fixtures_dir().join("interface_with_deps");
let filename = src_dir.join("Primary.roc"); let filename = src_dir.join("Primary.roc");
let loaded = load( let arena = Bump::new();
let loaded = roc_load::file::load_and_typecheck(
&arena,
filename, filename,
&roc_builtins::std::standard_stdlib(), roc_builtins::std::standard_stdlib(),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
); );

View file

@ -14,13 +14,14 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_uniq_load { mod test_uniq_load {
use crate::helpers::fixtures_dir; use crate::helpers::fixtures_dir;
use bumpalo::Bump;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_builtins::unique; use roc_builtins::unique;
use roc_can::def::Declaration::*; use roc_can::def::Declaration::*;
use roc_can::def::Def; use roc_can::def::Def;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_constrain::module::SubsByModule; use roc_constrain::module::SubsByModule;
use roc_load::file::{load, LoadedModule}; use roc_load::file::LoadedModule;
use roc_module::symbol::{Interns, ModuleId}; use roc_module::symbol::{Interns, ModuleId};
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs; use roc_types::subs::Subs;
@ -33,11 +34,13 @@ mod test_uniq_load {
module_name: &str, module_name: &str,
subs_by_module: SubsByModule, subs_by_module: SubsByModule,
) -> LoadedModule { ) -> LoadedModule {
let arena = Bump::new();
let src_dir = fixtures_dir().join(dir_name); let src_dir = fixtures_dir().join(dir_name);
let filename = src_dir.join(format!("{}.roc", module_name)); let filename = src_dir.join(format!("{}.roc", module_name));
let loaded = load( let loaded = roc_load::file::load_and_typecheck(
&arena,
filename, filename,
&unique::uniq_stdlib(), unique::uniq_stdlib(),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
); );
@ -126,12 +129,14 @@ mod test_uniq_load {
#[test] #[test]
fn interface_with_deps() { fn interface_with_deps() {
let arena = Bump::new();
let subs_by_module = MutMap::default(); let subs_by_module = MutMap::default();
let src_dir = fixtures_dir().join("interface_with_deps"); let src_dir = fixtures_dir().join("interface_with_deps");
let filename = src_dir.join("Primary.roc"); let filename = src_dir.join("Primary.roc");
let loaded = load( let loaded = roc_load::file::load_and_typecheck(
&arena,
filename, filename,
&roc_builtins::std::standard_stdlib(), roc_builtins::std::standard_stdlib(),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
); );

View file

@ -288,6 +288,7 @@ impl fmt::Debug for ModuleId {
.lock() .lock()
.expect("Failed to acquire lock for Debug reading from DEBUG_MODULE_ID_NAMES, presumably because a thread panicked."); .expect("Failed to acquire lock for Debug reading from DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
if PRETTY_PRINT_DEBUG_SYMBOLS {
match names.get(&self.0) { match names.get(&self.0) {
Some(str_ref) => write!(f, "{}", str_ref.clone()), Some(str_ref) => write!(f, "{}", str_ref.clone()),
None => { None => {
@ -297,6 +298,9 @@ impl fmt::Debug for ModuleId {
); );
} }
} }
} else {
write!(f, "{}", self.0)
}
} }
/// In relese builds, all we have access to is the number, so only display that. /// In relese builds, all we have access to is the number, so only display that.
@ -376,7 +380,7 @@ pub struct IdentId(u32);
/// ///
/// Each module name is stored twice, for faster lookups. /// Each module name is stored twice, for faster lookups.
/// Since these are interned strings, this shouldn't result in many total allocations in practice. /// Since these are interned strings, this shouldn't result in many total allocations in practice.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IdentIds { pub struct IdentIds {
by_ident: MutMap<InlinableString, IdentId>, by_ident: MutMap<InlinableString, IdentId>,

View file

@ -12,9 +12,12 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_constrain = { path = "../constrain" }
roc_solve = { path = "../solve" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
ven_ena = { path = "../../vendor/ena" }
[dev-dependencies] [dev-dependencies]
roc_constrain = { path = "../constrain" } roc_constrain = { path = "../constrain" }

View file

@ -337,6 +337,7 @@ impl<'a> BorrowInfState<'a> {
self.own_var(*x); self.own_var(*x);
} }
} }
FunctionCall { FunctionCall {
call_type, call_type,
args, args,

View file

@ -1143,7 +1143,7 @@ fn test_to_equality<'a>(
// TODO procs and layout are currently unused, but potentially required // TODO procs and layout are currently unused, but potentially required
// for defining optional fields? // for defining optional fields?
// if not, do remove // if not, do remove
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments, clippy::needless_collect)]
fn decide_to_branching<'a>( fn decide_to_branching<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
@ -1256,6 +1256,9 @@ fn decide_to_branching<'a>(
// TODO There must be some way to remove this iterator/loop // TODO There must be some way to remove this iterator/loop
let nr = (tests.len() as i64) - 1 + (guard.is_some() as i64); let nr = (tests.len() as i64) - 1 + (guard.is_some() as i64);
let arena = env.arena;
let accum_symbols = std::iter::once(true_symbol) let accum_symbols = std::iter::once(true_symbol)
.chain((0..nr).map(|_| env.unique_symbol())) .chain((0..nr).map(|_| env.unique_symbol()))
.rev() .rev()
@ -1268,15 +1271,14 @@ fn decide_to_branching<'a>(
let accum = accum_it.next().unwrap(); let accum = accum_it.next().unwrap();
let test_symbol = env.unique_symbol(); let test_symbol = env.unique_symbol();
let and_expr = let and_expr = Expr::RunLowLevel(LowLevel::And, arena.alloc([test_symbol, accum]));
Expr::RunLowLevel(LowLevel::And, env.arena.alloc([test_symbol, accum]));
// write to the branching symbol // write to the branching symbol
cond = Stmt::Let( cond = Stmt::Let(
current_symbol, current_symbol,
and_expr, and_expr,
Layout::Builtin(Builtin::Int1), Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond), arena.alloc(cond),
); );
// calculate the guard value // calculate the guard value
@ -1287,9 +1289,9 @@ fn decide_to_branching<'a>(
}; };
cond = Stmt::Join { cond = Stmt::Join {
id, id,
parameters: env.arena.alloc([param]), parameters: arena.alloc([param]),
remainder: env.arena.alloc(stmt), remainder: arena.alloc(stmt),
continuation: env.arena.alloc(cond), continuation: arena.alloc(cond),
}; };
// load all the variables (the guard might need them); // load all the variables (the guard might need them);
@ -1301,18 +1303,17 @@ fn decide_to_branching<'a>(
let test_symbol = env.unique_symbol(); let test_symbol = env.unique_symbol();
let test = Expr::RunLowLevel( let test = Expr::RunLowLevel(
LowLevel::Eq, LowLevel::Eq,
bumpalo::vec![in env.arena; lhs, rhs].into_bump_slice(), bumpalo::vec![in arena; lhs, rhs].into_bump_slice(),
); );
let and_expr = let and_expr = Expr::RunLowLevel(LowLevel::And, arena.alloc([test_symbol, accum]));
Expr::RunLowLevel(LowLevel::And, env.arena.alloc([test_symbol, accum]));
// write to the branching symbol // write to the branching symbol
cond = Stmt::Let( cond = Stmt::Let(
current_symbol, current_symbol,
and_expr, and_expr,
Layout::Builtin(Builtin::Int1), Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond), arena.alloc(cond),
); );
// write to the test symbol // write to the test symbol
@ -1320,11 +1321,11 @@ fn decide_to_branching<'a>(
test_symbol, test_symbol,
test, test,
Layout::Builtin(Builtin::Int1), Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond), arena.alloc(cond),
); );
for (symbol, layout, expr) in new_stores.into_iter() { for (symbol, layout, expr) in new_stores.into_iter() {
cond = Stmt::Let(symbol, expr, layout, env.arena.alloc(cond)); cond = Stmt::Let(symbol, expr, layout, arena.alloc(cond));
} }
current_symbol = accum; current_symbol = accum;
@ -1334,7 +1335,7 @@ fn decide_to_branching<'a>(
true_symbol, true_symbol,
Expr::Literal(Literal::Bool(true)), Expr::Literal(Literal::Bool(true)),
Layout::Builtin(Builtin::Int1), Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond), arena.alloc(cond),
); );
cond cond

File diff suppressed because it is too large Load diff

View file

@ -226,9 +226,51 @@ impl<'a> Layout<'a> {
} }
/// Avoid recomputing Layout from Variable multiple times. /// Avoid recomputing Layout from Variable multiple times.
#[derive(Default)] /// We use `ena` for easy snapshots and rollbacks of the cache.
/// During specialization, a type variable `a` can be specialized to different layouts,
/// e.g. `identity : a -> a` could be specialized to `Bool -> Bool` or `Str -> Str`.
/// Therefore in general it's invalid to store a map from variables to layouts
/// But if we're careful when to invalidate certain keys, we still get some benefit
#[derive(Default, Debug)]
pub struct LayoutCache<'a> { pub struct LayoutCache<'a> {
layouts: MutMap<Variable, Result<Layout<'a>, LayoutProblem>>, layouts: ven_ena::unify::UnificationTable<ven_ena::unify::InPlace<CachedVariable<'a>>>,
}
#[derive(Debug, Clone)]
pub enum CachedLayout<'a> {
Cached(Layout<'a>),
NotCached,
Problem(LayoutProblem),
}
/// Must wrap so we can define a specific UnifyKey instance
/// PhantomData so we can store the 'a lifetime, which is needed to implement the UnifyKey trait,
/// specifically so we can use `type Value = CachedLayout<'a>`
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CachedVariable<'a>(Variable, std::marker::PhantomData<&'a ()>);
impl<'a> CachedVariable<'a> {
fn new(var: Variable) -> Self {
CachedVariable(var, std::marker::PhantomData)
}
}
// use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
impl<'a> ven_ena::unify::UnifyKey for CachedVariable<'a> {
type Value = CachedLayout<'a>;
fn index(&self) -> u32 {
self.0.index()
}
fn from_index(index: u32) -> Self {
CachedVariable(Variable::from_index(index), std::marker::PhantomData)
}
fn tag() -> &'static str {
"CachedVariable"
}
} }
impl<'a> LayoutCache<'a> { impl<'a> LayoutCache<'a> {
@ -244,16 +286,60 @@ impl<'a> LayoutCache<'a> {
// Store things according to the root Variable, to avoid duplicate work. // Store things according to the root Variable, to avoid duplicate work.
let var = subs.get_root_key_without_compacting(var); let var = subs.get_root_key_without_compacting(var);
let cached_var = CachedVariable::new(var);
self.expand_to_fit(cached_var);
use CachedLayout::*;
match self.layouts.probe_value(cached_var) {
Cached(result) => Ok(result),
Problem(problem) => Err(problem),
NotCached => {
let mut env = Env { let mut env = Env {
arena, arena,
subs, subs,
seen: MutSet::default(), seen: MutSet::default(),
}; };
let result = Layout::from_var(&mut env, var);
let cached_layout = match &result {
Ok(layout) => Cached(layout.clone()),
Err(problem) => Problem(problem.clone()),
};
self.layouts self.layouts
.entry(var) .update_value(cached_var, |existing| existing.value = cached_layout);
.or_insert_with(|| Layout::from_var(&mut env, var))
.clone() result
}
}
}
fn expand_to_fit(&mut self, var: CachedVariable<'a>) {
use ven_ena::unify::UnifyKey;
let required = (var.index() as isize) - (self.layouts.len() as isize) + 1;
if required > 0 {
self.layouts.reserve(required as usize);
for _ in 0..required {
self.layouts.new_key(CachedLayout::NotCached);
}
}
}
pub fn snapshot(
&mut self,
) -> ven_ena::unify::Snapshot<ven_ena::unify::InPlace<CachedVariable<'a>>> {
self.layouts.snapshot()
}
pub fn rollback_to(
&mut self,
snapshot: ven_ena::unify::Snapshot<ven_ena::unify::InPlace<CachedVariable<'a>>>,
) {
self.layouts.rollback_to(snapshot)
} }
} }
@ -370,7 +456,7 @@ fn layout_from_flat_type<'a>(
// correct the memory mode of unique lists // correct the memory mode of unique lists
match Layout::from_var(env, wrapped_var)? { match Layout::from_var(env, wrapped_var)? {
Layout::Builtin(Builtin::List(_, elem_layout)) => { Layout::Builtin(Builtin::List(_ignored, elem_layout)) => {
let uniqueness_var = args[0]; let uniqueness_var = args[0];
let uniqueness_content = let uniqueness_content =
subs.get_without_compacting(uniqueness_var).content; subs.get_without_compacting(uniqueness_var).content;
@ -406,15 +492,19 @@ fn layout_from_flat_type<'a>(
)) ))
} }
Record(fields, ext_var) => { Record(fields, ext_var) => {
debug_assert!(ext_var_is_empty_record(subs, ext_var));
// Sort the fields by label // Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena); let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena);
sorted_fields.extend(fields.into_iter());
for tuple in fields { // extract any values from the ext_var
sorted_fields.push(tuple); let mut fields_map = MutMap::default();
match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut fields_map) {
Ok(()) | Err((_, Content::FlexVar(_))) => {}
Err(_) => unreachable!("this would have been a type error"),
} }
sorted_fields.extend(fields_map.into_iter());
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
// Determine the layouts of the fields, maintaining sort order // Determine the layouts of the fields, maintaining sort order
@ -781,22 +871,6 @@ fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool {
unreachable!(); unreachable!();
} }
#[cfg(debug_assertions)]
fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
// the ext_var is empty
let mut ext_fields = MutMap::default();
match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) {
Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(),
Err((_, content)) => panic!("invalid content in ext_var: {:?}", content),
}
}
#[cfg(not(debug_assertions))]
fn ext_var_is_empty_record(_: &Subs, _: Variable) -> bool {
// This should only ever be used in debug_assert! macros
unreachable!();
}
fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutProblem> { fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutProblem> {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;

View file

@ -875,7 +875,8 @@ mod test_mono {
indoc!( indoc!(
r#" r#"
let Test.0 = 5i64; let Test.0 = 5i64;
ret Test.0; let Test.2 = 3i64;
ret Test.2;
"# "#
), ),
); );
@ -1920,4 +1921,149 @@ mod test_mono {
), ),
) )
} }
#[test]
fn rigids() {
compiles_to_ir(
indoc!(
r#"
swap : Int, Int, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
foo = atJ
list
|> List.set i foo
|> List.set j atI
_ ->
[]
swap 0 0 [0x1]
"#
),
indoc!(
r#"
procedure List.3 (#Attr.2, #Attr.3):
let Test.43 = lowlevel ListLen #Attr.2;
let Test.39 = lowlevel NumLt #Attr.3 Test.43;
if Test.39 then
let Test.41 = 1i64;
let Test.42 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.40 = Ok Test.41 Test.42;
ret Test.40;
else
let Test.37 = 0i64;
let Test.38 = Struct {};
let Test.36 = Err Test.37 Test.38;
ret Test.36;
procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
let Test.19 = lowlevel ListLen #Attr.2;
let Test.17 = lowlevel NumLt #Attr.3 Test.19;
if Test.17 then
let Test.18 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4;
ret Test.18;
else
ret #Attr.2;
procedure Test.0 (Test.2, Test.3, Test.4):
let Test.34 = CallByName List.3 Test.4 Test.2;
let Test.35 = CallByName List.3 Test.4 Test.3;
let Test.13 = Struct {Test.34, Test.35};
let Test.24 = true;
let Test.26 = 1i64;
let Test.25 = Index 0 Test.13;
let Test.27 = Index 0 Test.25;
let Test.33 = lowlevel Eq Test.26 Test.27;
let Test.31 = lowlevel And Test.33 Test.24;
let Test.29 = 1i64;
let Test.28 = Index 1 Test.13;
let Test.30 = Index 0 Test.28;
let Test.32 = lowlevel Eq Test.29 Test.30;
let Test.23 = lowlevel And Test.32 Test.31;
if Test.23 then
let Test.21 = Index 0 Test.13;
let Test.5 = Index 1 Test.21;
let Test.20 = Index 1 Test.13;
let Test.6 = Index 1 Test.20;
let Test.15 = CallByName List.4 Test.4 Test.2 Test.6;
let Test.14 = CallByName List.4 Test.15 Test.3 Test.5;
ret Test.14;
else
dec Test.4;
let Test.22 = Array [];
ret Test.22;
let Test.9 = 0i64;
let Test.10 = 0i64;
let Test.12 = 1i64;
let Test.11 = Array [Test.12];
let Test.8 = CallByName Test.0 Test.9 Test.10 Test.11;
ret Test.8;
"#
),
)
}
#[test]
fn let_x_in_x() {
compiles_to_ir(
indoc!(
r#"
x = 5
answer =
1337
unused =
nested = 17
nested
answer
"#
),
indoc!(
r#"
let Test.1 = 1337i64;
let Test.0 = 5i64;
let Test.3 = 17i64;
ret Test.1;
"#
),
)
}
#[test]
fn let_x_in_x_indirect() {
compiles_to_ir(
indoc!(
r#"
x = 5
answer =
1337
unused =
nested = 17
i = 1
nested
answer
"#
),
indoc!(
r#"
let Test.1 = 1337i64;
let Test.0 = 5i64;
let Test.3 = 17i64;
let Test.4 = 1i64;
ret Test.1;
"#
),
)
}
} }

View file

@ -10,21 +10,21 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_reporting { mod test_reporting {
use crate::helpers::test_home; use crate::helpers::test_home;
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::symbol::{Interns, ModuleId}; use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::{Procs, Stmt}; use roc_mono::ir::{Procs, Stmt};
use roc_mono::layout::LayoutCache;
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE, can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE, CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
WHITE_CODE, YELLOW_CODE, WHITE_CODE, YELLOW_CODE,
}; };
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
use roc_solve::solve;
use roc_types::pretty_print::name_all_type_vars; use roc_types::pretty_print::name_all_type_vars;
use roc_types::subs::Subs; use roc_types::subs::Subs;
use std::path::PathBuf; use std::path::PathBuf;
// use roc_region::all;
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
use roc_solve::solve;
fn filename_from_string(str: &str) -> PathBuf { fn filename_from_string(str: &str) -> PathBuf {
let mut filename = PathBuf::new(); let mut filename = PathBuf::new();
@ -87,6 +87,7 @@ mod test_reporting {
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr // Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mut layout_cache = LayoutCache::default();
let mut mono_env = roc_mono::ir::Env { let mut mono_env = roc_mono::ir::Env {
arena: &arena, arena: &arena,
subs: &mut subs, subs: &mut subs,
@ -94,7 +95,8 @@ mod test_reporting {
home, home,
ident_ids: &mut ident_ids, ident_ids: &mut ident_ids,
}; };
let _mono_expr = Stmt::new(&mut mono_env, loc_expr.value, &mut procs); let _mono_expr =
Stmt::new(&mut mono_env, loc_expr.value, &mut procs, &mut layout_cache);
} }
Ok((unify_problems, can_problems, mono_problems, home, interns)) Ok((unify_problems, can_problems, mono_problems, home, interns))

View file

@ -89,13 +89,7 @@ impl Default for Pools {
impl Pools { impl Pools {
pub fn new(num_pools: usize) -> Self { pub fn new(num_pools: usize) -> Self {
let mut pools = Vec::with_capacity(num_pools); Pools(vec![Vec::new(); num_pools])
for _ in 0..num_pools {
pools.push(Vec::new());
}
Pools(pools)
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
@ -469,22 +463,35 @@ fn solve(
let visit_mark = young_mark.next(); let visit_mark = young_mark.next();
let final_mark = visit_mark.next(); let final_mark = visit_mark.next();
debug_assert!({ debug_assert_eq!(
{
let offenders = next_pools let offenders = next_pools
.get(next_rank) .get(next_rank)
.iter() .iter()
.filter(|var| { .filter(|var| {
subs.get_without_compacting(roc_types::subs::Variable::clone( let current = subs.get_without_compacting(
var, roc_types::subs::Variable::clone(var),
)) );
.rank
.into_usize()
> next_rank.into_usize()
})
.collect::<Vec<&roc_types::subs::Variable>>();
offenders.is_empty() current.rank.into_usize() > next_rank.into_usize()
}); })
.collect::<Vec<_>>();
let result = offenders.len();
if result > 0 {
dbg!(
&subs,
&offenders,
&let_con.def_types,
&let_con.def_aliases
);
}
result
},
0
);
// pop pool // pop pool
generalize(subs, young_mark, visit_mark, next_rank, next_pools); generalize(subs, young_mark, visit_mark, next_rank, next_pools);
@ -568,6 +575,16 @@ fn type_to_var(
type_to_variable(subs, rank, pools, cached, typ) type_to_variable(subs, rank, pools, cached, typ)
} }
/// Abusing existing functions for our purposes
/// this is to put a solved type back into subs
pub fn insert_type_into_subs(subs: &mut Subs, typ: &Type) -> Variable {
let rank = Rank::NONE;
let mut pools = Pools::default();
let mut cached = MutMap::default();
type_to_variable(subs, rank, &mut pools, &mut cached, typ)
}
fn type_to_variable( fn type_to_variable(
subs: &mut Subs, subs: &mut Subs,
rank: Rank, rank: Rank,
@ -764,10 +781,10 @@ fn check_for_infinite_type(
) { ) {
let var = loc_var.value; let var = loc_var.value;
let is_uniq_infer = match subs.get(var).content { let is_uniq_infer = matches!(
Content::Alias(Symbol::ATTR_ATTR, _, _) => true, subs.get(var).content,
_ => false, Content::Alias(Symbol::ATTR_ATTR, _, _)
}; );
while let Some((recursive, chain)) = subs.occurs(var) { while let Some((recursive, chain)) = subs.occurs(var) {
let description = subs.get(recursive); let description = subs.get(recursive);
@ -1182,6 +1199,184 @@ fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable])
pool.extend(vars); pool.extend(vars);
} }
/// Function that converts rigids variables to flex variables
/// this is used during the monomorphization process
pub fn instantiate_rigids(subs: &mut Subs, var: Variable) {
let rank = Rank::NONE;
let mut pools = Pools::default();
instantiate_rigids_help(subs, rank, &mut pools, var);
}
fn instantiate_rigids_help(
subs: &mut Subs,
max_rank: Rank,
pools: &mut Pools,
var: Variable,
) -> Variable {
use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*;
let desc = subs.get_without_compacting(var);
if let Some(copy) = desc.copy.into_variable() {
return copy;
}
let make_descriptor = |content| Descriptor {
content,
rank: max_rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
};
let content = desc.content;
let copy = var;
pools.get_mut(max_rank).push(copy);
// Link the original variable to the new variable. This lets us
// avoid making multiple copies of the variable we are instantiating.
//
// Need to do this before recursively copying to avoid looping.
subs.set(
var,
Descriptor {
content: content.clone(),
rank: desc.rank,
mark: Mark::NONE,
copy: copy.into(),
},
);
// Now we recursively copy the content of the variable.
// We have already marked the variable as copied, so we
// will not repeat this work or crawl this variable again.
match content {
Structure(flat_type) => {
let new_flat_type = match flat_type {
Apply(symbol, args) => {
let args = args
.into_iter()
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
.collect();
Apply(symbol, args)
}
Func(arg_vars, closure_var, ret_var) => {
let new_ret_var = instantiate_rigids_help(subs, max_rank, pools, ret_var);
let new_closure_var =
instantiate_rigids_help(subs, max_rank, pools, closure_var);
let arg_vars = arg_vars
.into_iter()
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
.collect();
Func(arg_vars, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Record(fields, ext_var) => {
let mut new_fields = MutMap::default();
for (label, field) in fields {
use RecordField::*;
let new_field = match field {
Demanded(var) => {
Demanded(instantiate_rigids_help(subs, max_rank, pools, var))
}
Required(var) => {
Required(instantiate_rigids_help(subs, max_rank, pools, var))
}
Optional(var) => {
Optional(instantiate_rigids_help(subs, max_rank, pools, var))
}
};
new_fields.insert(label, new_field);
}
Record(
new_fields,
instantiate_rigids_help(subs, max_rank, pools, ext_var),
)
}
TagUnion(tags, ext_var) => {
let mut new_tags = MutMap::default();
for (tag, vars) in tags {
let new_vars: Vec<Variable> = vars
.into_iter()
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
.collect();
new_tags.insert(tag, new_vars);
}
TagUnion(
new_tags,
instantiate_rigids_help(subs, max_rank, pools, ext_var),
)
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut new_tags = MutMap::default();
let new_rec_var = instantiate_rigids_help(subs, max_rank, pools, rec_var);
for (tag, vars) in tags {
let new_vars: Vec<Variable> = vars
.into_iter()
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
.collect();
new_tags.insert(tag, new_vars);
}
RecursiveTagUnion(
new_rec_var,
new_tags,
instantiate_rigids_help(subs, max_rank, pools, ext_var),
)
}
Boolean(b) => {
let mut mapper = |var| instantiate_rigids_help(subs, max_rank, pools, var);
Boolean(b.map_variables(&mut mapper))
}
};
subs.set(copy, make_descriptor(Structure(new_flat_type)));
copy
}
FlexVar(_) | Error => copy,
RigidVar(name) => {
subs.set(copy, make_descriptor(FlexVar(Some(name))));
copy
}
Alias(symbol, args, real_type_var) => {
let new_args = args
.into_iter()
.map(|(name, var)| (name, instantiate_rigids_help(subs, max_rank, pools, var)))
.collect();
let new_real_type_var = instantiate_rigids_help(subs, max_rank, pools, real_type_var);
let new_content = Alias(symbol, new_args, new_real_type_var);
subs.set(copy, make_descriptor(new_content));
copy
}
}
}
fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable {
let copy = deep_copy_var_help(subs, rank, pools, var); let copy = deep_copy_var_help(subs, rank, pools, var);

View file

@ -52,10 +52,10 @@ pub enum Bool {
} }
pub fn var_is_shared(subs: &Subs, var: Variable) -> bool { pub fn var_is_shared(subs: &Subs, var: Variable) -> bool {
match subs.get_without_compacting(var).content { matches!(
Content::Structure(FlatType::Boolean(Bool::Shared)) => true, subs.get_without_compacting(var).content,
_ => false, Content::Structure(FlatType::Boolean(Bool::Shared))
} )
} }
/// Given the Subs /// Given the Subs
@ -163,10 +163,7 @@ impl Bool {
} }
pub fn is_unique(&self, subs: &Subs) -> bool { pub fn is_unique(&self, subs: &Subs) -> bool {
match self.simplify(subs) { !matches!(self.simplify(subs), Shared)
Shared => false,
_ => true,
}
} }
pub fn variables(&self) -> SendSet<Variable> { pub fn variables(&self) -> SendSet<Variable> {

View file

@ -676,7 +676,7 @@ fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens
let combined = buffers.join(" | "); let combined = buffers.join(" | ");
buf.push_str("("); buf.push('(');
write_content( write_content(
env, env,
subs.get_without_compacting(cvar).content, subs.get_without_compacting(cvar).content,
@ -686,7 +686,7 @@ fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens
); );
buf.push_str(" | "); buf.push_str(" | ");
buf.push_str(&combined); buf.push_str(&combined);
buf.push_str(")"); buf.push(')');
} }
} }
} }
@ -716,7 +716,7 @@ fn write_apply(
let mut default_case = |subs, content| { let mut default_case = |subs, content| {
if write_parens { if write_parens {
buf.push_str("("); buf.push('(');
} }
write_content(env, content, subs, &mut arg_param, Parens::InTypeParam); write_content(env, content, subs, &mut arg_param, Parens::InTypeParam);
@ -724,7 +724,7 @@ fn write_apply(
buf.push_str(&arg_param); buf.push_str(&arg_param);
if write_parens { if write_parens {
buf.push_str(")"); buf.push(')');
} }
}; };
@ -762,13 +762,13 @@ fn write_apply(
} }
_ => { _ => {
if write_parens { if write_parens {
buf.push_str("("); buf.push('(');
} }
write_symbol(env, symbol, buf); write_symbol(env, symbol, buf);
for arg in args { for arg in args {
buf.push_str(" "); buf.push(' ');
write_content( write_content(
env, env,
subs.get_without_compacting(arg).content, subs.get_without_compacting(arg).content,
@ -779,7 +779,7 @@ fn write_apply(
} }
if write_parens { if write_parens {
buf.push_str(")"); buf.push(')');
} }
} }
} }
@ -797,7 +797,7 @@ fn write_fn(
let use_parens = parens != Parens::Unnecessary; let use_parens = parens != Parens::Unnecessary;
if use_parens { if use_parens {
buf.push_str("("); buf.push('(');
} }
for arg in args { for arg in args {
@ -826,7 +826,7 @@ fn write_fn(
); );
if use_parens { if use_parens {
buf.push_str(")"); buf.push(')');
} }
} }

View file

@ -1,6 +1,7 @@
use crate::boolean_algebra; use crate::boolean_algebra;
use crate::subs::{FlatType, Subs, VarId, Variable}; use crate::subs::{FlatType, Subs, VarId, Variable};
use crate::types::{Problem, RecordField, Type}; use crate::types::{Problem, RecordField, Type};
use roc_collections::all::MutSet;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -15,13 +16,17 @@ impl<T> Solved<T> {
&self.0 &self.0
} }
pub fn inner_mut(&mut self) -> &'_ mut T {
&mut self.0
}
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0 self.0
} }
} }
/// This is a fully solved type, with no Variables remaining in it. /// This is a fully solved type, with no Variables remaining in it.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SolvedType { pub enum SolvedType {
/// A function. The types of its arguments, then the type of its return value. /// A function. The types of its arguments, then the type of its return value.
Func(Vec<SolvedType>, Box<SolvedType>, Box<SolvedType>), Func(Vec<SolvedType>, Box<SolvedType>, Box<SolvedType>),
@ -55,7 +60,7 @@ pub enum SolvedType {
Error, Error,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SolvedBool { pub enum SolvedBool {
SolvedShared, SolvedShared,
SolvedContainer(VarId, Vec<VarId>), SolvedContainer(VarId, Vec<VarId>),
@ -68,10 +73,13 @@ impl SolvedBool {
match boolean { match boolean {
Bool::Shared => SolvedBool::SolvedShared, Bool::Shared => SolvedBool::SolvedShared,
Bool::Container(cvar, mvars) => { Bool::Container(cvar, mvars) => {
debug_assert!(matches!( match subs.get_without_compacting(*cvar).content {
subs.get_without_compacting(*cvar).content, crate::subs::Content::FlexVar(_) => {}
crate::subs::Content::FlexVar(_) crate::subs::Content::Structure(FlatType::Boolean(Bool::Shared)) => {
)); return SolvedBool::SolvedShared;
}
other => panic!("Container var is not flex but {:?}", other),
}
SolvedBool::SolvedContainer( SolvedBool::SolvedContainer(
VarId::from_var(*cvar, subs), VarId::from_var(*cvar, subs),
@ -193,21 +201,32 @@ impl SolvedType {
} }
} }
fn from_var(subs: &Subs, var: Variable) -> Self { pub fn from_var(subs: &Subs, var: Variable) -> Self {
let mut seen = RecursionVars::default();
Self::from_var_help(subs, &mut seen, var)
}
fn from_var_help(subs: &Subs, recursion_vars: &mut RecursionVars, var: Variable) -> Self {
use crate::subs::Content::*; use crate::subs::Content::*;
// if this is a recursion var we've seen before, just generate a Flex
// (not doing so would have this function loop forever)
if recursion_vars.contains(subs, var) {
return SolvedType::Flex(VarId::from_var(var, subs));
}
match subs.get_without_compacting(var).content { match subs.get_without_compacting(var).content {
FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)), FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)),
RigidVar(name) => SolvedType::Rigid(name), RigidVar(name) => SolvedType::Rigid(name),
Structure(flat_type) => Self::from_flat_type(subs, flat_type), Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type),
Alias(symbol, args, actual_var) => { Alias(symbol, args, actual_var) => {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for (arg_name, arg_var) in args { for (arg_name, arg_var) in args {
new_args.push((arg_name, Self::from_var(subs, arg_var))); new_args.push((arg_name, Self::from_var_help(subs, recursion_vars, arg_var)));
} }
let aliased_to = Self::from_var(subs, actual_var); let aliased_to = Self::from_var_help(subs, recursion_vars, actual_var);
SolvedType::Alias(symbol, new_args, Box::new(aliased_to)) SolvedType::Alias(symbol, new_args, Box::new(aliased_to))
} }
@ -215,15 +234,19 @@ impl SolvedType {
} }
} }
fn from_flat_type(subs: &Subs, flat_type: FlatType) -> Self { fn from_flat_type(
subs: &Subs,
recursion_vars: &mut RecursionVars,
flat_type: FlatType,
) -> Self {
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
match flat_type { match flat_type {
Apply(symbol, args) => { Apply(symbol, args) => {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for var in args { for var in args.iter().copied() {
new_args.push(Self::from_var(subs, var)); new_args.push(Self::from_var_help(subs, recursion_vars, var));
} }
SolvedType::Apply(symbol, new_args) SolvedType::Apply(symbol, new_args)
@ -232,11 +255,11 @@ impl SolvedType {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for var in args { for var in args {
new_args.push(Self::from_var(subs, var)); new_args.push(Self::from_var_help(subs, recursion_vars, var));
} }
let ret = Self::from_var(subs, ret); let ret = Self::from_var_help(subs, recursion_vars, ret);
let closure = Self::from_var(subs, closure); let closure = Self::from_var_help(subs, recursion_vars, closure);
SolvedType::Func(new_args, Box::new(closure), Box::new(ret)) SolvedType::Func(new_args, Box::new(closure), Box::new(ret))
} }
@ -247,15 +270,15 @@ impl SolvedType {
use RecordField::*; use RecordField::*;
let solved_type = match field { let solved_type = match field {
Optional(var) => Optional(Self::from_var(subs, var)), Optional(var) => Optional(Self::from_var_help(subs, recursion_vars, var)),
Required(var) => Required(Self::from_var(subs, var)), Required(var) => Required(Self::from_var_help(subs, recursion_vars, var)),
Demanded(var) => Demanded(Self::from_var(subs, var)), Demanded(var) => Demanded(Self::from_var_help(subs, recursion_vars, var)),
}; };
new_fields.push((label, solved_type)); new_fields.push((label, solved_type));
} }
let ext = Self::from_var(subs, ext_var); let ext = Self::from_var_help(subs, recursion_vars, ext_var);
SolvedType::Record { SolvedType::Record {
fields: new_fields, fields: new_fields,
@ -269,30 +292,32 @@ impl SolvedType {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for var in args { for var in args {
new_args.push(Self::from_var(subs, var)); new_args.push(Self::from_var_help(subs, recursion_vars, var));
} }
new_tags.push((tag_name, new_args)); new_tags.push((tag_name, new_args));
} }
let ext = Self::from_var(subs, ext_var); let ext = Self::from_var_help(subs, recursion_vars, ext_var);
SolvedType::TagUnion(new_tags, Box::new(ext)) SolvedType::TagUnion(new_tags, Box::new(ext))
} }
RecursiveTagUnion(rec_var, tags, ext_var) => { RecursiveTagUnion(rec_var, tags, ext_var) => {
recursion_vars.insert(subs, rec_var);
let mut new_tags = Vec::with_capacity(tags.len()); let mut new_tags = Vec::with_capacity(tags.len());
for (tag_name, args) in tags { for (tag_name, args) in tags {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for var in args { for var in args {
new_args.push(Self::from_var(subs, var)); new_args.push(Self::from_var_help(subs, recursion_vars, var));
} }
new_tags.push((tag_name, new_args)); new_tags.push((tag_name, new_args));
} }
let ext = Self::from_var(subs, ext_var); let ext = Self::from_var_help(subs, recursion_vars, ext_var);
SolvedType::RecursiveTagUnion( SolvedType::RecursiveTagUnion(
VarId::from_var(rec_var, subs), VarId::from_var(rec_var, subs),
@ -314,3 +339,20 @@ pub struct BuiltinAlias {
pub vars: Vec<Located<Lowercase>>, pub vars: Vec<Located<Lowercase>>,
pub typ: SolvedType, pub typ: SolvedType,
} }
#[derive(Default)]
struct RecursionVars(MutSet<Variable>);
impl RecursionVars {
fn contains(&self, subs: &Subs, var: Variable) -> bool {
let var = subs.get_root_key_without_compacting(var);
self.0.contains(&var)
}
fn insert(&mut self, subs: &Subs, var: Variable) {
let var = subs.get_root_key_without_compacting(var);
self.0.insert(var);
}
}

View file

@ -72,6 +72,17 @@ impl VarStore {
VarStore { next: next_var.0 } VarStore { next: next_var.0 }
} }
pub fn new_from_subs(subs: &Subs) -> Self {
let next_var = (subs.utable.len()) as u32;
debug_assert!(next_var >= Variable::FIRST_USER_SPACE_VAR.0);
VarStore { next: next_var }
}
pub fn peek(&mut self) -> u32 {
self.next
}
pub fn fresh(&mut self) -> Variable { pub fn fresh(&mut self) -> Variable {
// Increment the counter and return the value it had before it was incremented. // Increment the counter and return the value it had before it was incremented.
let answer = self.next; let answer = self.next;
@ -163,6 +174,10 @@ impl Variable {
pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self { pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self {
Variable(v) Variable(v)
} }
pub fn index(&self) -> u32 {
self.0
}
} }
impl Into<OptVariable> for Variable { impl Into<OptVariable> for Variable {
@ -255,6 +270,12 @@ impl Subs {
subs subs
} }
pub fn extend_by(&mut self, entries: usize) {
for _ in 0..entries {
self.utable.new_key(flex_var_descriptor());
}
}
pub fn fresh(&mut self, value: Descriptor) -> Variable { pub fn fresh(&mut self, value: Descriptor) -> Variable {
self.utable.new_key(value) self.utable.new_key(value)
} }
@ -531,10 +552,10 @@ pub enum Content {
impl Content { impl Content {
#[inline(always)] #[inline(always)]
pub fn is_number(&self) -> bool { pub fn is_number(&self) -> bool {
match &self { matches!(
Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)) => true, &self,
_ => false, Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _))
} )
} }
pub fn is_unique(&self, subs: &Subs) -> bool { pub fn is_unique(&self, subs: &Subs) -> bool {

View file

@ -22,7 +22,7 @@ pub const TYPE_FLOATINGPOINT: &str = "FloatingPoint";
/// Can unify with Optional and Demanded /// Can unify with Optional and Demanded
/// - Optional: introduced by pattern matches and annotations. /// - Optional: introduced by pattern matches and annotations.
/// Can unify with Required, but not with Demanded /// Can unify with Required, but not with Demanded
#[derive(PartialEq, Eq, Clone)] #[derive(PartialEq, Eq, Clone, Hash)]
pub enum RecordField<T> { pub enum RecordField<T> {
Optional(T), Optional(T),
Required(T), Required(T),
@ -988,7 +988,7 @@ pub struct Alias {
pub typ: Type, pub typ: Type,
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum Problem { pub enum Problem {
CanonicalizationProblem, CanonicalizationProblem,
CircularType(Symbol, ErrorType, Region), CircularType(Symbol, ErrorType, Region),
@ -1014,7 +1014,7 @@ pub enum Mismatch {
CanonicalizationProblem, CanonicalizationProblem,
} }
#[derive(PartialEq, Eq, Clone)] #[derive(PartialEq, Eq, Clone, Hash)]
pub enum ErrorType { pub enum ErrorType {
Infinite, Infinite,
Type(Symbol, Vec<ErrorType>), Type(Symbol, Vec<ErrorType>),
@ -1063,7 +1063,7 @@ fn write_error_type_help(
match error_type { match error_type {
Infinite => buf.push_str(""), Infinite => buf.push_str(""),
Error => buf.push_str("?"), Error => buf.push('?'),
FlexVar(name) => buf.push_str(name.as_str()), FlexVar(name) => buf.push_str(name.as_str()),
RigidVar(name) => buf.push_str(name.as_str()), RigidVar(name) => buf.push_str(name.as_str()),
Type(symbol, arguments) => { Type(symbol, arguments) => {
@ -1181,7 +1181,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
match error_type { match error_type {
Infinite => buf.push_str(""), Infinite => buf.push_str(""),
Error => buf.push_str("?"), Error => buf.push('?'),
FlexVar(name) => buf.push_str(name.as_str()), FlexVar(name) => buf.push_str(name.as_str()),
RigidVar(name) => buf.push_str(name.as_str()), RigidVar(name) => buf.push_str(name.as_str()),
Type(symbol, arguments) => { Type(symbol, arguments) => {
@ -1316,7 +1316,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
while let Some((tag, args)) = it.next() { while let Some((tag, args)) = it.next() {
buf.push_str(&format!("{:?}", tag)); buf.push_str(&format!("{:?}", tag));
for arg in args { for arg in args {
buf.push_str(" "); buf.push(' ');
write_debug_error_type_help(arg, buf, Parens::InTypeParam); write_debug_error_type_help(arg, buf, Parens::InTypeParam);
} }
@ -1335,7 +1335,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
while let Some((tag, args)) = it.next() { while let Some((tag, args)) = it.next() {
buf.push_str(&format!("{:?}", tag)); buf.push_str(&format!("{:?}", tag));
for arg in args { for arg in args {
buf.push_str(" "); buf.push(' ');
write_debug_error_type_help(arg, buf, Parens::Unnecessary); write_debug_error_type_help(arg, buf, Parens::Unnecessary);
} }
@ -1359,7 +1359,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
} }
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum TypeExt { pub enum TypeExt {
Closed, Closed,
FlexOpen(Lowercase), FlexOpen(Lowercase),

View file

@ -31,21 +31,11 @@ macro_rules! mismatch {
println!(""); println!("");
} }
vec![Mismatch::TypeMismatch] vec![Mismatch::TypeMismatch]
}}; }};
($msg:expr,) => {{ ($msg:expr,) => {{
if cfg!(debug_assertions) { mismatch!($msg)
println!(
"Mismatch in {} Line {} Column {}",
file!(),
line!(),
column!()
);
println!($msg);
println!("");
}
vec![Mismatch::TypeMismatch]
}}; }};
($msg:expr, $($arg:tt)*) => {{ ($msg:expr, $($arg:tt)*) => {{
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
@ -133,12 +123,12 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
// NOTE: names are generated here (when creating an error type) and that modifies names // NOTE: names are generated here (when creating an error type) and that modifies names
// generated by pretty_print.rs. So many test will fail with changes in variable names when // generated by pretty_print.rs. So many test will fail with changes in variable names when
// this block runs. // this block runs.
let (type1, _problems1) = subs.var_to_error_type(ctx.first); // let (type1, _problems1) = subs.var_to_error_type(ctx.first);
let (type2, _problems2) = subs.var_to_error_type(ctx.second); // let (type2, _problems2) = subs.var_to_error_type(ctx.second);
println!("\n --------------- \n"); // println!("\n --------------- \n");
dbg!(ctx.first, type1); // dbg!(ctx.first, type1);
println!("\n --- \n"); // println!("\n --- \n");
dbg!(ctx.second, type2); // dbg!(ctx.second, type2);
println!("\n --------------- \n"); println!("\n --------------- \n");
println!( println!(
"{:?} {:?} ~ {:?} {:?}", "{:?} {:?} ~ {:?} {:?}",
@ -681,7 +671,7 @@ fn unify_shared_tags(
merge(subs, ctx, Structure(flat_type)) merge(subs, ctx, Structure(flat_type))
} else { } else {
mismatch!() mismatch!("Problem with Tag Union")
} }
} }
@ -911,7 +901,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
RigidVar(_) | Structure(_) | Alias(_, _, _) => { RigidVar(_) | Structure(_) | Alias(_, _, _) => {
// Type mismatch! Rigid can only unify with flex, even if the // Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same. // rigid names are the same.
mismatch!() mismatch!("Rigid with {:?}", &other)
} }
Error => { Error => {
// Error propagates. // Error propagates.

View file

@ -47,6 +47,8 @@ These are potentially inspirational resources for the editor's design.
* [Unreal Engine 4](https://www.unrealengine.com/en-US/) * [Unreal Engine 4](https://www.unrealengine.com/en-US/)
* [Blueprints](https://docs.unrealengine.com/en-US/Engine/Blueprints/index.html) visual scripting (not suggesting visual scripting for Roc) * [Blueprints](https://docs.unrealengine.com/en-US/Engine/Blueprints/index.html) visual scripting (not suggesting visual scripting for Roc)
* [Live Programing](https://www.microsoft.com/en-us/research/project/live-programming/?from=http%3A%2F%2Fresearch.microsoft.com%2Fen-us%2Fprojects%2Fliveprogramming%2Ftypography.aspx#!publications) by [Microsoft Research] it contains many interesting research papers.
### Non-Code Related Inspiration ### Non-Code Related Inspiration
* [Scrivner](https://www.literatureandlatte.com/scrivener/overview) writing app for novelists, screenwriters, and more * [Scrivner](https://www.literatureandlatte.com/scrivener/overview) writing app for novelists, screenwriters, and more

View file

@ -13,42 +13,42 @@ pub fn handle_text_input(
} }
match virtual_keycode { match virtual_keycode {
Key1 | Numpad1 => text_state.push_str("1"), Key1 | Numpad1 => text_state.push('1'),
Key2 | Numpad2 => text_state.push_str("2"), Key2 | Numpad2 => text_state.push('2'),
Key3 | Numpad3 => text_state.push_str("3"), Key3 | Numpad3 => text_state.push('3'),
Key4 | Numpad4 => text_state.push_str("4"), Key4 | Numpad4 => text_state.push('4'),
Key5 | Numpad5 => text_state.push_str("5"), Key5 | Numpad5 => text_state.push('5'),
Key6 | Numpad6 => text_state.push_str("6"), Key6 | Numpad6 => text_state.push('6'),
Key7 | Numpad7 => text_state.push_str("7"), Key7 | Numpad7 => text_state.push('7'),
Key8 | Numpad8 => text_state.push_str("8"), Key8 | Numpad8 => text_state.push('8'),
Key9 | Numpad9 => text_state.push_str("9"), Key9 | Numpad9 => text_state.push('9'),
Key0 | Numpad0 => text_state.push_str("0"), Key0 | Numpad0 => text_state.push('0'),
A => text_state.push_str("a"), A => text_state.push('a'),
B => text_state.push_str("b"), B => text_state.push('b'),
C => text_state.push_str("c"), C => text_state.push('c'),
D => text_state.push_str("d"), D => text_state.push('d'),
E => text_state.push_str("e"), E => text_state.push('e'),
F => text_state.push_str("f"), F => text_state.push('f'),
G => text_state.push_str("g"), G => text_state.push('g'),
H => text_state.push_str("h"), H => text_state.push('h'),
I => text_state.push_str("i"), I => text_state.push('i'),
J => text_state.push_str("j"), J => text_state.push('j'),
K => text_state.push_str("k"), K => text_state.push('k'),
L => text_state.push_str("l"), L => text_state.push('l'),
M => text_state.push_str("m"), M => text_state.push('m'),
N => text_state.push_str("n"), N => text_state.push('n'),
O => text_state.push_str("o"), O => text_state.push('o'),
P => text_state.push_str("p"), P => text_state.push('p'),
Q => text_state.push_str("q"), Q => text_state.push('q'),
R => text_state.push_str("r"), R => text_state.push('r'),
S => text_state.push_str("s"), S => text_state.push('s'),
T => text_state.push_str("t"), T => text_state.push('t'),
U => text_state.push_str("u"), U => text_state.push('u'),
V => text_state.push_str("v"), V => text_state.push('v'),
W => text_state.push_str("w"), W => text_state.push('w'),
X => text_state.push_str("x"), X => text_state.push('x'),
Y => text_state.push_str("y"), Y => text_state.push('y'),
Z => text_state.push_str("z"), Z => text_state.push('z'),
Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15 Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15
| F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause | F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause
| Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose | Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose
@ -65,55 +65,55 @@ pub fn handle_text_input(
text_state.pop(); text_state.pop();
} }
Return | NumpadEnter => { Return | NumpadEnter => {
text_state.push_str("\n"); text_state.push('\n');
} }
Space => { Space => {
text_state.push_str(" "); text_state.push(' ');
} }
Comma | NumpadComma => { Comma | NumpadComma => {
text_state.push_str(","); text_state.push(',');
} }
Add => { Add => {
text_state.push_str("+"); text_state.push('+');
} }
Apostrophe => { Apostrophe => {
text_state.push_str("'"); text_state.push('\'');
} }
At => { At => {
text_state.push_str("@"); text_state.push('@');
} }
Backslash => { Backslash => {
text_state.push_str("\\"); text_state.push('\\');
} }
Colon => { Colon => {
text_state.push_str(":"); text_state.push(':');
} }
Period | Decimal => { Period | Decimal => {
text_state.push_str("."); text_state.push('.');
} }
Equals | NumpadEquals => { Equals | NumpadEquals => {
text_state.push_str("="); text_state.push('=');
} }
Grave => { Grave => {
text_state.push_str("`"); text_state.push('`');
} }
Minus | Subtract => { Minus | Subtract => {
text_state.push_str("-"); text_state.push('-');
} }
Multiply => { Multiply => {
text_state.push_str("*"); text_state.push('*');
} }
Semicolon => { Semicolon => {
text_state.push_str(";"); text_state.push(';');
} }
Slash | Divide => { Slash | Divide => {
text_state.push_str("/"); text_state.push('/');
} }
Underline => { Underline => {
text_state.push_str("_"); text_state.push('_');
} }
Yen => { Yen => {
text_state.push_str("¥"); text_state.push('¥');
} }
Copy => { Copy => {
todo!("copy"); todo!("copy");

4
examples/.gitignore vendored
View file

@ -1,3 +1,3 @@
app app
*.o host.o
*.a c_host.o

View file

@ -1,14 +0,0 @@
use std::ffi::CStr;
use std::os::raw::c_char;
#[link(name = "roc_app", kind = "static")]
extern "C" {
#[link_name = "main#1"]
fn str_from_roc() -> *const c_char;
}
pub fn main() {
let c_str = unsafe { CStr::from_ptr(str_from_roc()) };
println!("Roc says: {}", c_str.to_str().unwrap());
}

23
examples/hello-world/platform/Cargo.lock generated Normal file
View file

@ -0,0 +1,23 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "host"
version = "0.1.0"
dependencies = [
"roc_std 0.1.0",
]
[[package]]
name = "libc"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"

View file

@ -0,0 +1,13 @@
[package]
name = "host"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
[workspace]

View file

@ -0,0 +1,8 @@
# Rebuilding the host from source
Run `build.sh` to manually rebuild this platform's host.
Note that the compiler currently has its own logic for rebuilding these hosts
(in `link.rs`). It's hardcoded for now, but the long-term goal is that
hosts will be precompiled by platform authors and distributed in packages,
at which point only package authors will need to think about rebuilding hosts.

View file

@ -0,0 +1,12 @@
#!/bin/bash
# compile c_host.o and rust_host.o
clang -c host.c -o c_host.o
rustc host.rs -o rust_host.o
# link them together into host.o
ld -r c_host.o rust_host.o -o host.o
# clean up
rm -f c_host.o
rm -f rust_host.o

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View file

@ -0,0 +1,18 @@
use roc_std::RocStr;
use std::str;
extern "C" {
#[link_name = "main_1"]
fn main() -> RocStr;
}
#[no_mangle]
pub fn rust_main() -> isize {
println!(
"Roc says: {}",
str::from_utf8(unsafe { main().as_slice() }).unwrap()
);
// Exit code
0
}

View file

@ -0,0 +1,49 @@
app Quicksort
provides [ quicksort ]
imports [ Utils.{swap} ]
quicksort : List Int -> List Int
quicksort = \originalList ->
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
Pair partitionIndex partitioned ->
partitioned
|> quicksortHelp low (partitionIndex - 1)
|> quicksortHelp (partitionIndex + 1) high
else
list
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
when partitionHelp (low - 1) low initialList high pivot is
Pair newI newList ->
Pair (newI + 1) (Utils.swap (newI + 1) high newList)
Err _ ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
partitionHelp (i + 1) (j + 1) (Utils.swap (i + 1) j list) high pivot
else
partitionHelp i (j + 1) list high pivot
Err _ ->
Pair i list
else
Pair i list
n = List.len originalList
quicksortHelp originalList 0 (n - 1)

View file

@ -0,0 +1,12 @@
interface Utils exposes [ swap ] imports []
swap : Int, Int, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
list
|> List.set i atJ
|> List.set j atI
_ ->
[]

View file

@ -0,0 +1,23 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "host"
version = "0.1.0"
dependencies = [
"roc_std 0.1.0",
]
[[package]]
name = "libc"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"

View file

@ -0,0 +1,13 @@
[package]
name = "host"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
[workspace]

View file

@ -0,0 +1,8 @@
# Rebuilding the host from source
Run `build.sh` to manually rebuild this platform's host.
Note that the compiler currently has its own logic for rebuilding these hosts
(in `link.rs`). It's hardcoded for now, but the long-term goal is that
hosts will be precompiled by platform authors and distributed in packages,
at which point only package authors will need to think about rebuilding hosts.

View file

@ -0,0 +1,12 @@
#!/bin/bash
# compile c_host.o and rust_host.o
clang -c host.c -o c_host.o
rustc host.rs -o rust_host.o
# link them together into host.o
ld -r c_host.o rust_host.o -o host.o
# clean up
rm -f c_host.o
rm -f rust_host.o

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View file

@ -0,0 +1,40 @@
use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "quicksort_1"]
fn quicksort(list: RocList<i64>) -> RocList<i64>;
}
const NUM_NUMS: usize = 10_000;
#[no_mangle]
pub fn rust_main() -> isize {
let nums: RocList<i64> = {
let mut nums = Vec::with_capacity(NUM_NUMS);
for index in 0..nums.capacity() {
let num = index as i64 % 123;
nums.push(num);
}
RocList::from_slice(&nums)
};
println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now();
let answer = unsafe { quicksort(nums) };
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
&answer.as_slice()[0..20]
);
// Exit code
0
}

View file

@ -1,6 +1,7 @@
app Quicksort provides [ quicksort ] imports [] app Quicksort provides [ quicksort ] imports []
quicksort = \originalList -> quicksort = \originalList ->
quicksortHelp : List (Num a), Int, Int -> List (Num a) quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp = \list, low, high -> quicksortHelp = \list, low, high ->
if low < high then if low < high then

23
examples/quicksort/platform/Cargo.lock generated Normal file
View file

@ -0,0 +1,23 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "host"
version = "0.1.0"
dependencies = [
"roc_std 0.1.0",
]
[[package]]
name = "libc"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"

View file

@ -0,0 +1,13 @@
[package]
name = "host"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
[workspace]

View file

@ -0,0 +1,8 @@
# Rebuilding the host from source
Run `build.sh` to manually rebuild this platform's host.
Note that the compiler currently has its own logic for rebuilding these hosts
(in `link.rs`). It's hardcoded for now, but the long-term goal is that
hosts will be precompiled by platform authors and distributed in packages,
at which point only package authors will need to think about rebuilding hosts.

View file

@ -0,0 +1,12 @@
#!/bin/bash
# compile c_host.o and rust_host.o
clang -c host.c -o c_host.o
rustc host.rs -o rust_host.o
# link them together into host.o
ld -r c_host.o rust_host.o -o host.o
# clean up
rm -f c_host.o
rm -f rust_host.o

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View file

@ -0,0 +1,40 @@
use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "quicksort_1"]
fn quicksort(list: RocList<i64>) -> RocList<i64>;
}
const NUM_NUMS: usize = 10_000;
#[no_mangle]
pub fn rust_main() -> isize {
let nums: RocList<i64> = {
let mut nums = Vec::with_capacity(NUM_NUMS);
for index in 0..nums.capacity() {
let num = index as i64 % 123;
nums.push(num);
}
RocList::from_slice(&nums)
};
println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now();
let answer = unsafe { quicksort(nums) };
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
&answer.as_slice()[0..20]
);
// Exit code
0
}

View file

@ -1,47 +0,0 @@
use std::time::SystemTime;
#[link(name = "roc_app", kind = "static")]
extern "C" {
#[allow(improper_ctypes)]
#[link_name = "quicksort#1"]
fn quicksort(list: &[i64]) -> Box<[i64]>;
}
const NUM_NUMS: usize = 1_000_000;
pub fn main() {
let nums = {
let mut nums = Vec::with_capacity(NUM_NUMS + 1);
// give this list refcount 1
nums.push((std::usize::MAX - 1) as i64);
for index in 1..nums.capacity() {
let num = index as i64 % 12345;
nums.push(num);
}
nums
};
println!("Running Roc shared quicksort");
let start_time = SystemTime::now();
let answer = unsafe { quicksort(&nums[1..]) };
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
// NOTE index 0 is the refcount!
&answer[1..20]
);
// the pointer is to the first _element_ of the list,
// but the refcount precedes it. Thus calling free() on
// this pointer would segfault/cause badness. Therefore, we
// leak it for now
Box::leak(answer);
}

View file

@ -0,0 +1,49 @@
# Rebuilding the host from source
Here are the current steps to rebuild this host. These
steps can likely be moved into a `build.rs` script after
turning `host.rs` into a `cargo` project, but that hasn't
been attempted yet.
## Compile the Rust and C sources
Currently this host has both a `host.rs` and a `host.c`.
This is only because we haven't figured out a way to convince
Rust to emit a `.o` file that doesn't define a `main` entrypoint,
but which is capable of being linked into one later.
As a workaround, we have `host.rs` expose a function called
`rust_main` instead of `main`, and all `host.c` does is provide
an actual `main` which imports and then calls `rust_main` from
the compiled `host.rs`. It's not the most elegant workaround,
but [asking on `users.rust-lang.org`](https://users.rust-lang.org/t/error-when-compiling-linking-with-o-files/49635/4)
didn't turn up any nicer approaches. Maybe they're out there though!
To make this workaround happen, we need to compile both `host.rs`
and `host.c`. First, `cd` into `platform/host/src/` and then run:
```
$ clang -c host.c -o c_host.o
$ rustc host.rs -o rust_host.o
```
Now we should have `c_host.o` and `rust_host.o` in the curent directory.
## Link together the `.o` files
Next, combine `c_host.o` and `rust_host.o` into `host.o` using `ld -r` like so:
```
$ ld -r c_host.o rust_host.o -o host.o
```
Move `host.o` into the appropriate `platform/` subdirectory
based on your architecture and operating system. For example,
on macOS, you'd move `host.o` into the `platform/host/x86_64-unknown-darwin10/` directory.
## All done!
Congratulations! You now have an updated host.
It's now fine to delete `c_host.o` and `rust_host.o`,
since they were only needed to produce `host.o`.

View file

@ -0,0 +1,12 @@
#!/bin/bash
# compile c_host.o and rust_host.o
clang -c host.c -o c_host.o
rustc host.rs -o rust_host.o
# link them together into host.o
ld -r c_host.o rust_host.o -o host.o
# clean up
rm -f c_host.o
rm -f rust_host.o

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View file

@ -1,20 +1,22 @@
#![crate_type = "staticlib"]
use std::time::SystemTime; use std::time::SystemTime;
#[link(name = "roc_app", kind = "static")]
extern "C" { extern "C" {
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link_name = "quicksort#1"] #[link_name = "quicksort_1"]
fn quicksort(list: Box<[i64]>) -> Box<[i64]>; fn quicksort(list: Box<[i64]>) -> Box<[i64]>;
} }
const NUM_NUMS: usize = 1_000_000; const NUM_NUMS: usize = 10_000;
pub fn main() { #[no_mangle]
pub fn rust_main() -> isize {
let nums: Box<[i64]> = { let nums: Box<[i64]> = {
let mut nums = Vec::with_capacity(NUM_NUMS); let mut nums = Vec::with_capacity(NUM_NUMS);
for index in 0..nums.capacity() { for index in 0..nums.capacity() {
let num = index as i64 % 12345; let num = index as i64 % 123;
nums.push(num); nums.push(num);
} }
@ -39,4 +41,7 @@ pub fn main() {
// this pointer would segfault/cause badness. Therefore, we // this pointer would segfault/cause badness. Therefore, we
// leak it for now // leak it for now
Box::leak(answer); Box::leak(answer);
// Exit code
0
} }

View file

@ -71,7 +71,7 @@ impl<T> RocList<T> {
let value = *self.get_storage_ptr(); let value = *self.get_storage_ptr();
// NOTE doesn't work with elements of 16 or more bytes // NOTE doesn't work with elements of 16 or more bytes
match usize::cmp(&0, &value) { match isize::cmp(&(value as isize), &0) {
Equal => Some(Storage::ReadOnly), Equal => Some(Storage::ReadOnly),
Less => Some(Storage::Refcounted(value)), Less => Some(Storage::Refcounted(value)),
Greater => Some(Storage::Capacity(value)), Greater => Some(Storage::Capacity(value)),
@ -214,3 +214,209 @@ impl<T> Drop for RocList<T> {
} }
} }
} }
#[repr(C)]
pub struct RocStr {
elements: *mut u8,
length: usize,
}
impl RocStr {
pub fn len(&self) -> usize {
if self.is_small_str() {
let bytes = self.length.to_ne_bytes();
let last_byte = bytes[bytes.len() - 1];
(last_byte ^ 0b1000_0000) as usize
} else {
self.length
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_small_str(&self) -> bool {
(self.length as isize) < 0
}
pub fn empty() -> Self {
RocStr {
// The first bit of length is 1 to specify small str.
length: 0,
elements: core::ptr::null_mut(),
}
}
pub fn get(&self, index: usize) -> Option<&u8> {
if index < self.len() {
Some(unsafe {
let raw = if self.is_small_str() {
self.get_small_str_ptr().add(index)
} else {
self.elements.add(index)
};
&*raw
})
} else {
None
}
}
pub fn storage(&self) -> Option<Storage> {
use core::cmp::Ordering::*;
if self.is_small_str() || self.length == 0 {
return None;
}
unsafe {
let value = *self.get_storage_ptr();
// NOTE doesn't work with elements of 16 or more bytes
match isize::cmp(&(value as isize), &0) {
Equal => Some(Storage::ReadOnly),
Less => Some(Storage::Refcounted(value)),
Greater => Some(Storage::Capacity(value)),
}
}
}
fn get_storage_ptr(&self) -> *const usize {
let ptr = self.elements as *const usize;
unsafe { ptr.offset(-1) }
}
fn get_storage_ptr_mut(&mut self) -> *mut usize {
self.get_storage_ptr() as *mut usize
}
fn get_element_ptr(elements: *const u8) -> *const usize {
let elem_alignment = core::mem::align_of::<u8>();
let ptr = elements as *const usize;
unsafe {
if elem_alignment <= core::mem::align_of::<usize>() {
ptr.offset(1)
} else {
// If elements have an alignment bigger than usize (e.g. an i128),
// we will have necessarily allocated two usize slots worth of
// space for the storage value (with the first usize slot being
// padding for alignment's sake), and we need to skip past both.
ptr.offset(2)
}
}
}
fn get_small_str_ptr(&self) -> *const u8 {
(self as *const RocStr).cast()
}
fn get_small_str_ptr_mut(&mut self) -> *mut u8 {
(self as *mut RocStr).cast()
}
pub fn from_slice_with_capacity(slice: &[u8], capacity: usize) -> RocStr {
assert!(slice.len() <= capacity);
if capacity < core::mem::size_of::<RocStr>() {
let mut rocstr = RocStr::empty();
let target_ptr = rocstr.get_small_str_ptr_mut();
let source_ptr = slice.as_ptr() as *const u8;
for index in 0..(slice.len() as isize) {
unsafe {
*target_ptr.offset(index) = *source_ptr.offset(index);
}
}
// Write length and small string bit to last byte of length.
let mut bytes = rocstr.length.to_ne_bytes();
bytes[bytes.len() - 1] = capacity as u8 ^ 0b1000_0000;
rocstr.length = usize::from_ne_bytes(bytes);
rocstr
} else {
let ptr = slice.as_ptr();
let element_bytes = capacity;
let num_bytes = core::mem::size_of::<usize>() + element_bytes;
let elements = unsafe {
let raw_ptr = libc::malloc(num_bytes);
// write the capacity
let capacity_ptr = raw_ptr as *mut usize;
*capacity_ptr = capacity;
let raw_ptr = Self::get_element_ptr(raw_ptr as *mut u8);
{
// NOTE: using a memcpy here causes weird issues
let target_ptr = raw_ptr as *mut u8;
let source_ptr = ptr as *const u8;
let length = slice.len() as isize;
for index in 0..length {
*target_ptr.offset(index) = *source_ptr.offset(index);
}
}
raw_ptr as *mut u8
};
RocStr {
length: slice.len(),
elements,
}
}
}
pub fn from_slice(slice: &[u8]) -> RocStr {
Self::from_slice_with_capacity(slice, slice.len())
}
pub fn as_slice(&self) -> &[u8] {
if self.is_small_str() {
unsafe { core::slice::from_raw_parts(self.get_small_str_ptr(), self.len()) }
} else {
unsafe { core::slice::from_raw_parts(self.elements, self.length) }
}
}
}
impl fmt::Debug for RocStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// RocStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] }
f.debug_struct("RocStr")
.field("is_small_str", &self.is_small_str())
.field("storage", &self.storage())
.field("elements", &self.as_slice())
.finish()
}
}
impl PartialEq for RocStr {
fn eq(&self, other: &Self) -> bool {
self.as_slice() == other.as_slice()
}
}
impl Eq for RocStr {}
impl Drop for RocStr {
fn drop(&mut self) {
if !self.is_small_str() {
use Storage::*;
match self.storage() {
None | Some(ReadOnly) => {}
Some(Capacity(_)) | Some(Refcounted(REFCOUNT_1)) => unsafe {
libc::free(self.get_storage_ptr() as *mut libc::c_void);
},
Some(Refcounted(rc)) => {
let sptr = self.get_storage_ptr_mut();
unsafe {
*sptr = rc - 1;
}
}
}
}
}
}