Merge branch 'top-level-thunks' into sized-functions-inference

This commit is contained in:
Folkert 2020-10-14 21:58:53 +02:00
commit de8ad07f2e
84 changed files with 5060 additions and 2287 deletions

View File

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

View File

@ -1,9 +1,21 @@
# 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.

196
Cargo.lock generated
View File

@ -16,6 +16,21 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "aho-corasick"
version = "0.7.13"
@ -120,6 +135,20 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "bitflags"
version = "1.2.1"
@ -279,6 +308,15 @@ dependencies = [
"bitflags",
]
[[package]]
name = "cloudabi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
dependencies = [
"bitflags",
]
[[package]]
name = "cocoa"
version = "0.20.2"
@ -634,6 +672,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]]
name = "fnv"
version = "1.0.7"
@ -829,7 +873,7 @@ dependencies = [
"gfx-hal",
"libloading",
"log",
"parking_lot",
"parking_lot 0.10.2",
"range-alloc",
"raw-window-handle",
"smallvec",
@ -885,7 +929,7 @@ dependencies = [
"log",
"metal",
"objc",
"parking_lot",
"parking_lot 0.10.2",
"range-alloc",
"raw-window-handle",
"smallvec",
@ -947,6 +991,12 @@ dependencies = [
"slab",
]
[[package]]
name = "gimli"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
[[package]]
name = "glyph_brush"
version = "0.7.0"
@ -1119,7 +1169,7 @@ dependencies = [
"libc",
"llvm-sys",
"once_cell",
"parking_lot",
"parking_lot 0.10.2",
"regex",
]
@ -1259,6 +1309,15 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "lock_api"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.11"
@ -1328,6 +1387,16 @@ dependencies = [
"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]]
name = "mio"
version = "0.6.22"
@ -1519,6 +1588,12 @@ dependencies = [
"cc",
]
[[package]]
name = "object"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
[[package]]
name = "once_cell"
version = "1.4.1"
@ -1561,8 +1636,19 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [
"lock_api",
"parking_lot_core",
"lock_api 0.3.4",
"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]]
@ -1572,13 +1658,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [
"cfg-if",
"cloudabi",
"cloudabi 0.0.3",
"libc",
"redox_syscall",
"smallvec",
"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]]
name = "peek-poke"
version = "0.2.0"
@ -1650,6 +1754,16 @@ dependencies = [
"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]]
name = "pin-project"
version = "0.4.23"
@ -1965,7 +2079,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"cloudabi 0.0.3",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
@ -2203,6 +2317,8 @@ dependencies = [
"roc_types",
"roc_unify",
"roc_uniq",
"serde",
"serde-xml-rs",
"strip-ansi-escapes",
"target-lexicon",
"tokio",
@ -2319,6 +2435,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
@ -2343,6 +2460,7 @@ dependencies = [
"inlinable_string",
"maplit",
"num_cpus",
"parking_lot 0.11.0",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
@ -2351,6 +2469,7 @@ dependencies = [
"roc_collections",
"roc_constrain",
"roc_module",
"roc_mono",
"roc_parse",
"roc_problem",
"roc_region",
@ -2394,6 +2513,7 @@ dependencies = [
"roc_solve",
"roc_types",
"roc_unify",
"ven_ena",
"ven_pretty",
]
@ -2548,6 +2668,12 @@ dependencies = [
"roc_types",
]
[[package]]
name = "rustc-demangle"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2610b7f643d18c87dff3b489950269617e6601a51f1f05aa5daefee36f64f0b"
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -2624,6 +2750,21 @@ name = "serde"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "serde_cbor"
@ -2755,7 +2896,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0a4829a5c591dc24a944a736d6b1e4053e51339a79fd5d4702c4c999a9c45e"
dependencies = [
"lock_api",
"lock_api 0.3.4",
]
[[package]]
@ -2856,6 +2997,37 @@ dependencies = [
"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]]
name = "thread_local"
version = "1.0.1"
@ -3183,7 +3355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5dece29f3cd403aabf4056595eabe4b9af56b8bfae12445f097cf8666a41829"
dependencies = [
"arrayvec",
"parking_lot",
"parking_lot 0.10.2",
"raw-window-handle",
"smallvec",
"wgpu-core",
@ -3210,7 +3382,7 @@ dependencies = [
"gfx-hal",
"gfx-memory",
"log",
"parking_lot",
"parking_lot 0.10.2",
"peek-poke",
"smallvec",
"vec_map",
@ -3227,7 +3399,7 @@ dependencies = [
"lazy_static",
"libc",
"objc",
"parking_lot",
"parking_lot 0.10.2",
"raw-window-handle",
"wgpu-core",
"wgpu-types",
@ -3320,7 +3492,7 @@ dependencies = [
"ndk-glue",
"ndk-sys",
"objc",
"parking_lot",
"parking_lot 0.10.2",
"percent-encoding",
"raw-window-handle",
"smithay-client-toolkit",

View File

@ -59,4 +59,4 @@ esac
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
add-apt-repository "${REPO_NAME}"
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_macros = "0.8"
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_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
);
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]
extern crate clap;
use bumpalo::Bump;
use clap::ArgMatches;
use clap::{App, Arg};
use roc_build::program::gen;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::io;
use std::path::Path;
use std::process;
use std::process::Command;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
pub mod build;
pub mod repl;
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 opt_level = if matches.is_present(FLAG_OPTIMIZE) {
OptLevel::Optimize
@ -78,7 +74,7 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
// Spawn the root task
let path = path.canonicalize().unwrap_or_else(|err| {
use ErrorKind::*;
use io::ErrorKind::*;
match err.kind() {
NotFound => {
@ -95,8 +91,8 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
}
});
let binary_path =
build_file(src_dir, path, opt_level).expect("TODO gracefully handle build_file failing");
let binary_path = build::build_file(target, src_dir, path, opt_level)
.expect("TODO gracefully handle build_file failing");
if run_after_build {
// Run the compiled app
@ -109,123 +105,3 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
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 std::io;
use std::path::Path;
use target_lexicon::Triple;
fn main() -> io::Result<()> {
let matches = build_app().get_matches();
match matches.subcommand_name() {
None => roc_editor::launch(&[]),
Some("build") => build(matches.subcommand_matches("build").unwrap(), false),
Some("run") => build(matches.subcommand_matches("run").unwrap(), true),
Some("build") => build(
&Triple::host(),
matches.subcommand_matches("build").unwrap(),
false,
),
Some("run") => build(
&Triple::host(),
matches.subcommand_matches("run").unwrap(),
true,
),
Some("repl") => repl::main(),
Some("edit") => {
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::module::{constrain_imported_values, load_builtin_aliases, Import};
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_module::ident::Ident;
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::blankspace::space0_before;
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 std::hash::Hash;
use std::io::{self, Write};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked;
use target_lexicon::Triple;
@ -158,29 +155,71 @@ pub fn repl_home() -> ModuleId {
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> {
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 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.
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
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
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
// Used for reporting where an error came from.
//
// TODO: maybe Reporting should have this be an Option?
let path = PathBuf::new();
let total_problems = can_problems.len() + type_problems.len();
let mut lines = Vec::with_capacity(error_count);
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 module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
let builder = context.create_builder();
// pretty-print the expr type string for later.
name_all_type_vars(var, &mut subs);
debug_assert_eq!(exposed_to_host.len(), 1);
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 (_, 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) =
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
.create_jit_execution_engine(OptimizationLevel::None)
.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();
// 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,
builder: &builder,
context: &context,
@ -230,97 +301,70 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
module,
ptr_bytes,
leak: false,
// important! we don't want any procedures to get the C calling convention
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 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 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);
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
let mut headers = Vec::with_capacity(procedures.len());
// 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 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);
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));
}
// Build each proc using its header info.
for (proc, fn_val) in headers {
// NOTE: This is here to be uncommented in case verification fails.
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
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) {
function_pass.run_on(&fn_val);
} else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
eprintln!(
"\n\nFunction {:?} failed LLVM verification in 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();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in 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::make_main_function(
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
&env,
&mut layout_ids,
&main_ret_layout,
&main_body,
main_fn_symbol,
&main_fn_layout,
);
// 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,
execution_engine,
main_fn_name,
&main_ret_layout,
&main_fn_layout,
&content,
&env.interns,
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_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)]
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]
fn run_hello_world() {
let out = run_roc(&[
"run",
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]);
fn check_hello_world_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(&out.stdout.ends_with("Hello, World!!!!!!!!!!!!!\n"));
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]
fn run_quicksort() {
let out = run_roc(&[
"run",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
"--optimize",
]);
fn check_quicksort_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(&out
.stdout
.ends_with("[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n"));
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;
use roc_cli::repl::{INSTRUCTIONS, PROMPT, WELCOME_MESSAGE};
use serde::Deserialize;
use serde_xml_rs::from_str;
use std::env;
use std::io::Write;
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)]
pub fn example_dir(dir_name: &str) -> PathBuf {
let mut path = env::current_exe().ok().unwrap();

View File

@ -1,12 +1,17 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
mod helpers;
#[cfg(test)]
mod repl_eval {
use crate::helpers;
const ERROR_MESSAGE_START: char = '─';
fn expect_success(input: &str, expected: &str) {
let out = helpers::repl_eval(input);
@ -15,6 +20,28 @@ mod repl_eval {
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]
fn literal_0() {
expect_success("0", "0 : Num *");
@ -256,13 +283,46 @@ mod repl_eval {
// 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]
// fn list_of_3_field_records() {
// expect_success(
// "[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
// "[ { foo: 4.1, bar: 2, baz: 0x3 } ] : List { foo : Float, bar : Num *, baz : Int }",
// );
// fn mono_problem() {
// // can't produce a mono error (non-exhaustive pattern) yet
// }
}

View File

@ -10,4 +10,6 @@
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod link;
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 inkwell::context::Context;
use inkwell::targets::{
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple,
};
use inkwell::targets::{CodeModel, FileType, RelocMode};
use inkwell::OptimizationLevel;
use roc_collections::all::default_hasher;
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel};
use roc_load::file::LoadedModule;
use roc_mono::ir::{Env, PartialProc, Procs};
use roc_mono::layout::{Layout, LayoutCache};
use std::collections::HashSet;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
use roc_load::file::MonomorphizedModule;
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 this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
#[allow(clippy::cognitive_complexity)]
pub fn gen(
pub fn gen_from_mono_module(
arena: &Bump,
mut loaded: LoadedModule,
loaded: MonomorphizedModule,
filename: PathBuf,
target: Triple,
dest_filename: &Path,
@ -54,14 +49,6 @@ pub fn gen(
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
let context = Context::create();
@ -71,157 +58,8 @@ pub fn gen(
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
let mut env = roc_gen::llvm::build::Env {
let env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: &context,
@ -229,36 +67,27 @@ pub fn gen(
module,
ptr_bytes,
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
let mut headers = {
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);
let mut headers = Vec::with_capacity(loaded.procedures.len());
// 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.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);
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));
}
@ -268,11 +97,13 @@ pub fn gen(
// (This approach means we don't have to defensively clone name here.)
//
// 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) {
fpm.run_on(&fn_val);
} else {
// fn_val.print_to_stderr();
// env.module.print_to_stderr();
// NOTE: If this fails, uncomment the above println to debug.
panic!(
"Non-main function failed LLVM verification. Uncomment the above println to debug!"
@ -295,80 +126,10 @@ pub fn gen(
// 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 reloc = RelocMode::Default;
let model = CodeModel::Default;
// 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();
let target_machine = target::target_machine(&target, opt, reloc, model).unwrap();
target_machine
.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;
/// TODO replace this with a normal Inkwell build_cast call - this was just
/// used as a proof of concept for getting bitcode importing working!
/// TODO this is no longer used. Feel free to delete it the next time
/// we need to rebuild builtins.bc!
#[no_mangle]
pub fn i64_to_f64_(num: i64) -> f64 {
num as f64

View File

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

View File

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

View File

@ -126,6 +126,7 @@ pub enum Expr {
},
/// field accessor as a function, e.g. (.foo) expr
Accessor {
function_var: Variable,
record_var: Variable,
closure_var: Variable,
ext_var: Variable,
@ -550,6 +551,7 @@ pub fn canonicalize_expr<'a>(
}
ast::Expr::AccessorFunction(field) => (
Accessor {
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_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::env::Env;
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,
Output::default(),
var_store,
@ -149,17 +148,12 @@ pub fn canonicalize_module_defs<'a>(
}
// 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);
}
// Add defs for any referenced builtins.
for (symbol, def) in builtin_defs(var_store) {
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{
defs.can_defs_by_symbol.insert(symbol, def);
}
}
// NOTE previously we inserted builtin defs into the list of defs here
// this is now done later, in file.rs.
match sort_can_defs(&mut env, defs, Output::default()) {
(Ok(declarations), output) => {
@ -250,6 +244,11 @@ pub fn canonicalize_module_defs<'a>(
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 {
aliases,
rigid_variables,

View File

@ -675,6 +675,7 @@ pub fn constrain_expr(
)
}
Accessor {
function_var,
field,
record_var,
closure_var,
@ -701,16 +702,19 @@ pub fn constrain_expr(
region,
);
exists(
vec![*record_var, *closure_var, field_var, ext_var],
And(vec![
Eq(
Type::Function(
let function_type = Type::Function(
vec![record_type],
Box::new(Type::Variable(*closure_var)),
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,
region,
),

View File

@ -174,7 +174,11 @@ pub struct FreeVars {
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::*;
match solved_type {

View File

@ -67,6 +67,7 @@ pub fn constrain_decls(
// perform usage analysis on the whole file
let mut var_usage = VarUsage::default();
for decl in decls.iter().rev() {
// NOTE: rigids are empty because they are not shared between top-level definitions
match decl {
@ -1445,6 +1446,7 @@ pub fn constrain_expr(
}
Accessor {
function_var,
field,
record_var,
closure_var,
@ -1490,6 +1492,7 @@ pub fn constrain_expr(
exists(
vec![
*record_var,
*function_var,
*closure_var,
*field_var,
*ext_var,
@ -1497,7 +1500,16 @@ pub fn constrain_expr(
field_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,

View File

@ -44,6 +44,7 @@ target-lexicon = "0.10"
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_load = { path = "../load" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
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"
// and a LayoutId of 1.
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 roc_collections::all::{ImMap, MutSet};
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::layout::{Builtin, Layout, MemoryMode};
use target_lexicon::CallingConvention;
@ -53,6 +53,7 @@ pub enum OptLevel {
#[derive(Default, Debug, Clone, PartialEq)]
pub struct Scope<'a, '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>])>,
}
@ -63,23 +64,23 @@ impl<'a, 'ctx> Scope<'a, 'ctx> {
pub fn insert(&mut self, symbol: Symbol, value: (Layout<'a>, PointerValue<'ctx>)) {
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) {
self.symbols.remove(symbol);
}
/*
fn get_join_point(&self, symbol: &JoinPointId) -> Option<&PhiValue<'ctx>> {
self.join_points.get(symbol)
pub fn retain_top_level_thunks_for_module(&mut self, module_id: ModuleId) {
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> {
@ -416,26 +417,47 @@ pub fn build_roc_main<'a, 'ctx, 'env>(
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>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
// 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 PassVia::*;
let context = env.context;
let builder = env.builder;
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
// internal main function
let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body);
// build the C calling convention wrapper
let main_fn_name = "$Test.main";
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
let main_return_layout = Layout::Struct(&fields);
@ -1136,18 +1158,33 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
list_literal(env, inplace, scope, elem_layout, elems)
}
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.try_as_basic_value().left().unwrap()
}
None => {
// this is a function pointer, store it
let fn_name = layout_ids
.get(*symbol, layout)
.to_symbol_string(*symbol, &env.interns);
let ptr = env
.module
.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_pointer_value();
BasicValueEnum::PointerValue(ptr)
}
}
}
RuntimeErrorFunction(_) => todo!(),
}
}
@ -1511,16 +1548,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
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)
}
Dec(symbol, cont) => {
@ -1836,6 +1863,7 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
pub fn build_proc<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mut scope: Scope<'a, 'ctx>,
proc: roc_mono::ir::Proc<'a>,
fn_val: FunctionValue<'ctx>,
) {
@ -1848,8 +1876,6 @@ pub fn build_proc<'a, 'ctx, 'env>(
builder.position_at_end(entry);
let mut scope = Scope::default();
// Add args to scope
for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) {
set_name(arg_val, arg_symbol.ident_string(&env.interns));
@ -1899,15 +1925,16 @@ fn call_with_args<'a, 'ctx, 'env>(
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = fn_name.as_str();
let fn_val = env
.module
.get_function(fn_name.as_str())
.unwrap_or_else(|| {
let fn_val = env.module.get_function(fn_name).unwrap_or_else(|| {
if symbol.is_builtin() {
panic!("Unrecognized builtin function: {:?}", symbol)
panic!("Unrecognized builtin function: {:?}", fn_name)
} else {
panic!("Unrecognized non-builtin function: {:?}", symbol)
panic!(
"Unrecognized non-builtin function: {:?} {:?}",
fn_name, layout
)
}
});
@ -2619,8 +2646,13 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
))
}
NumToFloat => {
// TODO specialize this to be not just for i64!
call_bitcode_fn(NumToFloat, env, &[arg.into()], "i64_to_f64_")
// This is an Int, so we need to convert it.
bd.build_cast(
InstructionOpcode::SIToFP,
arg,
env.context.f64_type(),
"i64_to_f64",
)
}
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);

View File

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

View File

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

View File

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

View File

@ -678,15 +678,58 @@ mod gen_records {
}
#[test]
fn just_to_be_sure() {
fn accessor() {
assert_evals_to!(
indoc!(
r#"
{ a: 1, b : 2, c : 3 }
.foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
"#
),
[1, 2, 3],
[i64; 3]
7,
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!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
when 2 is
2 if False -> 0
_ -> 42
main {}
wrapper {}
"#
),
42,
@ -473,12 +473,12 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
when 2 is
2 if True -> 42
_ -> 0
main {}
wrapper {}
"#
),
42,
@ -491,12 +491,12 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
when 2 is
_ if False -> 0
_ -> 42
main {}
wrapper {}
"#
),
42,
@ -674,7 +674,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
x : [ Red, White, Blue ]
x = Blue
@ -686,7 +686,7 @@ mod gen_tags {
y
main {}
wrapper {}
"#
),
3.1,
@ -699,7 +699,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
y =
when 1 + 2 is
3 -> 3
@ -708,7 +708,7 @@ mod gen_tags {
y
main {}
wrapper {}
"#
),
3,

View File

@ -1,9 +1,22 @@
use roc_collections::all::MutSet;
use roc_types::subs::Subs;
use roc_collections::all::{MutMap, MutSet};
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,
src: &str,
stdlib: roc_builtins::std::StdLib,
leak: bool,
context: &'a inkwell::context::Context,
) -> (
@ -11,26 +24,62 @@ pub fn helper_without_uniqueness<'a>(
Vec<roc_problem::can::Problem>,
inkwell::execution_engine::ExecutionEngine<'a>,
) {
use crate::helpers::{can_expr, infer_expr, CanExprOut};
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_mono::layout::Layout;
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
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 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
let errors = problems
let errors = can_problems
.into_iter()
.filter(|problem| {
use roc_problem::can::Problem::*;
@ -43,15 +92,18 @@ pub fn helper_without_uniqueness<'a>(
})
.collect::<Vec<roc_problem::can::Problem>>();
let subs = Subs::new(var_store.into());
let mut unify_problems = Vec::new();
let (content, mut subs, solve_env) = infer_expr(subs, &mut unify_problems, &constraint, var);
assert_eq!(
unify_problems,
type_problems,
Vec::new(),
"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");
@ -66,19 +118,12 @@ pub fn helper_without_uniqueness<'a>(
let (module_pass, function_pass) =
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
.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 {
let env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context,
@ -86,85 +131,71 @@ pub fn helper_without_uniqueness<'a>(
module,
ptr_bytes,
leak,
// important! we don't want any procedures to get the C calling convention
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,
};
// infer the size of any closures
roc_mono::closures::infer_closure_size(&loc_expr.value, mono_env.subs, &solve_env);
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);
let mut headers = Vec::with_capacity(procedures.len());
// 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 mut scope = Scope::default();
for ((symbol, layout), proc) in procedures.drain() {
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));
}
// Build each proc using its header info.
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) {
function_pass.run_on(&fn_val);
} else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
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();
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::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
&env,
&mut layout_ids,
main_fn_symbol,
&main_fn_layout,
);
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -188,180 +219,6 @@ pub fn helper_without_uniqueness<'a>(
(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, solve_env) = 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,
interns,
module,
ptr_bytes,
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,
};
// infer the size of any closures
roc_mono::closures::infer_closure_size(&loc_expr.value, mono_env.subs, &solve_env);
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
// the only difference is that this calls uniq_expr instead of can_expr.
// Should extract the common logic into test helpers.
@ -376,11 +233,17 @@ macro_rules! assert_opt_evals_to {
let context = Context::create();
let (main_fn_name, execution_engine) =
$crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context);
let stdlib = roc_builtins::unique::uniq_stdlib();
let transform = |success| assert_eq!($transform(success), $expected);
run_jit_function!(execution_engine, main_fn_name, $ty, transform)
let (main_fn_name, errors, execution_engine) =
$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) => {
@ -398,9 +261,10 @@ macro_rules! assert_llvm_evals_to {
let arena = Bump::new();
let context = Context::create();
let stdlib = roc_builtins::std::standard_stdlib();
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 expected = $expected;
@ -417,29 +281,20 @@ macro_rules! assert_llvm_evals_to {
#[macro_export]
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.
// These each rebuild everything from scratch, starting with
// parsing the source, so that there's no chance their passing
// 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);
}

View File

@ -3,32 +3,6 @@ extern crate bumpalo;
#[macro_use]
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
/// run out of stack space in debug builds (but don't in --release builds)
#[allow(dead_code)]
@ -68,249 +42,3 @@ where
{
run_test()
}
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<roc_solve::solve::TypeError>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs, solve::Env) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: SendMap::default(),
};
let (solved, env) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner(), env)
}
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_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1"
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
crossbeam = "0.7"
num_cpus = "1"

View File

@ -5,6 +5,7 @@ use crossbeam::channel::{bounded, Sender};
use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread;
use inlinable_string::InlinableString;
use parking_lot::Mutex;
use roc_builtins::std::{Mode, StdLib};
use roc_can::constraint::Constraint;
use roc_can::def::Declaration;
@ -32,7 +33,7 @@ use std::io;
use std::iter;
use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use std::time::{Duration, SystemTime};
/// Filename extension for normal Roc modules
@ -349,8 +350,14 @@ pub fn load(
let arena = Bump::new();
// Reserve one CPU for the main thread, and let all the others be eligible
// to spawn workers.
let num_workers = num_cpus::get() - 1;
// to spawn workers. We use .max(2) to enforce that we always
// 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);
@ -878,8 +885,7 @@ fn finish<'a>(
let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
.into_inner()
.expect("Unwrapping mutex for module_ids");
.into_inner();
let interns = Interns {
module_ids,
@ -1079,10 +1085,8 @@ fn send_header<'a>(
let ident_ids = {
// 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 ident_ids_by_module = (*ident_ids_by_module).lock().expect(
"Failed to acquire lock for interning ident IDs, presumably because a thread panicked.",
);
let mut module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).lock();
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 ident_ids_by_module = (*ident_ids_by_module).lock().expect(
"Failed to acquire lock for interning ident IDs, presumably because a thread panicked.",
);
let ident_ids_by_module = (*ident_ids_by_module).lock();
// Populate dep_idents with each of their IdentIds,
// which we'll need during canonicalization to translate
@ -1277,9 +1279,11 @@ impl<'a> BuildTask<'a> {
waiting_for_solve.insert(module_id, solve_needed);
let module_ids = {
(*module_ids).lock().expect("Failed to acquire lock for obtaining module IDs, presumably because a thread panicked.").clone()
};
// Clone the module_ids we'll need for canonicalization.
// 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,
// 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)]
mod test_load {
use crate::helpers::fixtures_dir;
use bumpalo::Bump;
use inlinable_string::InlinableString;
use roc_can::def::Declaration::*;
use roc_can::def::Def;
use roc_collections::all::MutMap;
use roc_constrain::module::SubsByModule;
use roc_load::file::{load, LoadedModule};
use roc_load::file::LoadedModule;
use roc_module::symbol::{Interns, ModuleId};
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs;
@ -34,9 +35,11 @@ mod test_load {
) -> LoadedModule {
let src_dir = fixtures_dir().join(dir_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,
&roc_builtins::std::standard_stdlib(),
roc_builtins::std::standard_stdlib(),
src_dir.as_path(),
subs_by_module,
);
@ -128,9 +131,11 @@ mod test_load {
let subs_by_module = MutMap::default();
let src_dir = fixtures_dir().join("interface_with_deps");
let filename = src_dir.join("Primary.roc");
let loaded = load(
let arena = Bump::new();
let loaded = roc_load::file::load_and_typecheck(
&arena,
filename,
&roc_builtins::std::standard_stdlib(),
roc_builtins::std::standard_stdlib(),
src_dir.as_path(),
subs_by_module,
);

View File

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

View File

@ -288,6 +288,7 @@ impl fmt::Debug for ModuleId {
.lock()
.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) {
Some(str_ref) => write!(f, "{}", str_ref.clone()),
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.
@ -376,7 +380,7 @@ pub struct IdentId(u32);
///
/// Each module name is stored twice, for faster lookups.
/// 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 {
by_ident: MutMap<InlinableString, IdentId>,

View File

@ -17,6 +17,7 @@ roc_solve = { path = "../solve" }
roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.2", features = ["collections"] }
ven_ena = { path = "../../vendor/ena" }
[dev-dependencies]
roc_constrain = { path = "../constrain" }

View File

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

View File

@ -1143,7 +1143,7 @@ fn test_to_equality<'a>(
// TODO procs and layout are currently unused, but potentially required
// for defining optional fields?
// if not, do remove
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments, clippy::needless_collect)]
fn decide_to_branching<'a>(
env: &mut Env<'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
let nr = (tests.len() as i64) - 1 + (guard.is_some() as i64);
let arena = env.arena;
let accum_symbols = std::iter::once(true_symbol)
.chain((0..nr).map(|_| env.unique_symbol()))
.rev()
@ -1268,15 +1271,14 @@ fn decide_to_branching<'a>(
let accum = accum_it.next().unwrap();
let test_symbol = env.unique_symbol();
let and_expr =
Expr::RunLowLevel(LowLevel::And, env.arena.alloc([test_symbol, accum]));
let and_expr = Expr::RunLowLevel(LowLevel::And, arena.alloc([test_symbol, accum]));
// write to the branching symbol
cond = Stmt::Let(
current_symbol,
and_expr,
Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond),
arena.alloc(cond),
);
// calculate the guard value
@ -1287,9 +1289,9 @@ fn decide_to_branching<'a>(
};
cond = Stmt::Join {
id,
parameters: env.arena.alloc([param]),
remainder: env.arena.alloc(stmt),
continuation: env.arena.alloc(cond),
parameters: arena.alloc([param]),
remainder: arena.alloc(stmt),
continuation: arena.alloc(cond),
};
// 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 = Expr::RunLowLevel(
LowLevel::Eq,
bumpalo::vec![in env.arena; lhs, rhs].into_bump_slice(),
bumpalo::vec![in arena; lhs, rhs].into_bump_slice(),
);
let and_expr =
Expr::RunLowLevel(LowLevel::And, env.arena.alloc([test_symbol, accum]));
let and_expr = Expr::RunLowLevel(LowLevel::And, arena.alloc([test_symbol, accum]));
// write to the branching symbol
cond = Stmt::Let(
current_symbol,
and_expr,
Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond),
arena.alloc(cond),
);
// write to the test symbol
@ -1320,11 +1321,11 @@ fn decide_to_branching<'a>(
test_symbol,
test,
Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond),
arena.alloc(cond),
);
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;
@ -1334,7 +1335,7 @@ fn decide_to_branching<'a>(
true_symbol,
Expr::Literal(Literal::Bool(true)),
Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond),
arena.alloc(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.
#[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> {
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> {
@ -244,16 +286,60 @@ impl<'a> LayoutCache<'a> {
// Store things according to the root Variable, to avoid duplicate work.
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 {
arena,
subs,
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
.entry(var)
.or_insert_with(|| Layout::from_var(&mut env, var))
.clone()
.update_value(cached_var, |existing| existing.value = cached_layout);
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
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_content =
subs.get_without_compacting(uniqueness_var).content;
@ -406,15 +492,19 @@ fn layout_from_flat_type<'a>(
))
}
Record(fields, ext_var) => {
debug_assert!(ext_var_is_empty_record(subs, ext_var));
// Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena);
sorted_fields.extend(fields.into_iter());
for tuple in fields {
sorted_fields.push(tuple);
// extract any values from the ext_var
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));
// Determine the layouts of the fields, maintaining sort order
@ -781,22 +871,6 @@ fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool {
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> {
use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*;

View File

@ -875,7 +875,8 @@ mod test_mono {
indoc!(
r#"
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)]
mod test_reporting {
use crate::helpers::test_home;
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
use bumpalo::Bump;
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::{Procs, Stmt};
use roc_mono::layout::LayoutCache;
use roc_reporting::report::{
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,
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::subs::Subs;
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 {
let mut filename = PathBuf::new();
@ -87,6 +87,7 @@ mod test_reporting {
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
let mut layout_cache = LayoutCache::default();
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
@ -94,7 +95,8 @@ mod test_reporting {
home,
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))

View File

@ -497,22 +497,35 @@ fn solve(
let visit_mark = young_mark.next();
let final_mark = visit_mark.next();
debug_assert!({
debug_assert_eq!(
{
let offenders = next_pools
.get(next_rank)
.iter()
.filter(|var| {
subs.get_without_compacting(roc_types::subs::Variable::clone(
var,
))
.rank
.into_usize()
> next_rank.into_usize()
})
.collect::<Vec<&roc_types::subs::Variable>>();
let current = subs.get_without_compacting(
roc_types::subs::Variable::clone(var),
);
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
generalize(subs, young_mark, visit_mark, next_rank, next_pools);
@ -596,6 +609,16 @@ fn type_to_var(
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(
subs: &mut Subs,
rank: Rank,
@ -792,10 +815,10 @@ fn check_for_infinite_type(
) {
let var = loc_var.value;
let is_uniq_infer = match subs.get(var).content {
Content::Alias(Symbol::ATTR_ATTR, _, _) => true,
_ => false,
};
let is_uniq_infer = matches!(
subs.get(var).content,
Content::Alias(Symbol::ATTR_ATTR, _, _)
);
while let Some((recursive, chain)) = subs.occurs(var) {
let description = subs.get(recursive);
@ -1210,6 +1233,184 @@ fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable])
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 {
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 {
match subs.get_without_compacting(var).content {
Content::Structure(FlatType::Boolean(Bool::Shared)) => true,
_ => false,
}
matches!(
subs.get_without_compacting(var).content,
Content::Structure(FlatType::Boolean(Bool::Shared))
)
}
/// Given the Subs
@ -163,10 +163,7 @@ impl Bool {
}
pub fn is_unique(&self, subs: &Subs) -> bool {
match self.simplify(subs) {
Shared => false,
_ => true,
}
!matches!(self.simplify(subs), Shared)
}
pub fn variables(&self) -> SendSet<Variable> {

View File

@ -1,6 +1,7 @@
use crate::boolean_algebra;
use crate::subs::{FlatType, Subs, VarId, Variable};
use crate::types::{Problem, RecordField, Type};
use roc_collections::all::MutSet;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
@ -15,13 +16,17 @@ impl<T> Solved<T> {
&self.0
}
pub fn inner_mut(&mut self) -> &'_ mut T {
&mut self.0
}
pub fn into_inner(self) -> T {
self.0
}
}
/// 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 {
/// A function. The types of its arguments, then the type of its return value.
Func(Vec<SolvedType>, Box<SolvedType>, Box<SolvedType>),
@ -55,7 +60,7 @@ pub enum SolvedType {
Error,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SolvedBool {
SolvedShared,
SolvedContainer(VarId, Vec<VarId>),
@ -68,10 +73,13 @@ impl SolvedBool {
match boolean {
Bool::Shared => SolvedBool::SolvedShared,
Bool::Container(cvar, mvars) => {
debug_assert!(matches!(
subs.get_without_compacting(*cvar).content,
crate::subs::Content::FlexVar(_)
));
match subs.get_without_compacting(*cvar).content {
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(
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::*;
// 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 {
FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)),
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) => {
let mut new_args = Vec::with_capacity(args.len());
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))
}
@ -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::*;
match flat_type {
Apply(symbol, args) => {
let mut new_args = Vec::with_capacity(args.len());
for var in args {
new_args.push(Self::from_var(subs, var));
for var in args.iter().copied() {
new_args.push(Self::from_var_help(subs, recursion_vars, var));
}
SolvedType::Apply(symbol, new_args)
@ -232,11 +255,11 @@ impl SolvedType {
let mut new_args = Vec::with_capacity(args.len());
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 closure = Self::from_var(subs, closure);
let ret = Self::from_var_help(subs, recursion_vars, ret);
let closure = Self::from_var_help(subs, recursion_vars, closure);
SolvedType::Func(new_args, Box::new(closure), Box::new(ret))
}
@ -247,15 +270,15 @@ impl SolvedType {
use RecordField::*;
let solved_type = match field {
Optional(var) => Optional(Self::from_var(subs, var)),
Required(var) => Required(Self::from_var(subs, var)),
Demanded(var) => Demanded(Self::from_var(subs, var)),
Optional(var) => Optional(Self::from_var_help(subs, recursion_vars, var)),
Required(var) => Required(Self::from_var_help(subs, recursion_vars, var)),
Demanded(var) => Demanded(Self::from_var_help(subs, recursion_vars, var)),
};
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 {
fields: new_fields,
@ -269,30 +292,32 @@ impl SolvedType {
let mut new_args = Vec::with_capacity(args.len());
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));
}
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))
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
recursion_vars.insert(subs, rec_var);
let mut new_tags = Vec::with_capacity(tags.len());
for (tag_name, args) in tags {
let mut new_args = Vec::with_capacity(args.len());
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));
}
let ext = Self::from_var(subs, ext_var);
let ext = Self::from_var_help(subs, recursion_vars, ext_var);
SolvedType::RecursiveTagUnion(
VarId::from_var(rec_var, subs),
@ -314,3 +339,20 @@ pub struct BuiltinAlias {
pub vars: Vec<Located<Lowercase>>,
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

@ -73,13 +73,16 @@ impl VarStore {
}
pub fn new_from_subs(subs: &Subs) -> Self {
// TODO why -2, are we not overwriting something here?
let next_var = (subs.utable.len() - 1) as u32;
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 {
// Increment the counter and return the value it had before it was incremented.
let answer = self.next;
@ -549,10 +552,10 @@ pub enum Content {
impl Content {
#[inline(always)]
pub fn is_number(&self) -> bool {
match &self {
Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)) => true,
_ => false,
}
matches!(
&self,
Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _))
)
}
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
/// - Optional: introduced by pattern matches and annotations.
/// Can unify with Required, but not with Demanded
#[derive(PartialEq, Eq, Clone)]
#[derive(PartialEq, Eq, Clone, Hash)]
pub enum RecordField<T> {
Optional(T),
Required(T),
@ -989,7 +989,7 @@ pub struct Alias {
pub typ: Type,
}
#[derive(PartialEq, Eq, Debug, Clone)]
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum Problem {
CanonicalizationProblem,
CircularType(Symbol, ErrorType, Region),
@ -1015,7 +1015,7 @@ pub enum Mismatch {
CanonicalizationProblem,
}
#[derive(PartialEq, Eq, Clone)]
#[derive(PartialEq, Eq, Clone, Hash)]
pub enum ErrorType {
Infinite,
Type(Symbol, Vec<ErrorType>),
@ -1360,7 +1360,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 {
Closed,
FlexOpen(Lowercase),

View File

@ -31,21 +31,11 @@ macro_rules! mismatch {
println!("");
}
vec![Mismatch::TypeMismatch]
}};
($msg:expr,) => {{
if cfg!(debug_assertions) {
println!(
"Mismatch in {} Line {} Column {}",
file!(),
line!(),
column!()
);
println!($msg);
println!("");
}
vec![Mismatch::TypeMismatch]
mismatch!($msg)
}};
($msg:expr, $($arg:tt)*) => {{
if cfg!(debug_assertions) {
@ -681,7 +671,7 @@ fn unify_shared_tags(
merge(subs, ctx, Structure(flat_type))
} 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(_, _, _) => {
// Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same.
mismatch!()
mismatch!("Rigid with {:?}", &other)
}
Error => {
// 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/)
* [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
* [Scrivner](https://www.literatureandlatte.com/scrivener/overview) writing app for novelists, screenwriters, and more

4
examples/.gitignore vendored
View File

@ -1,3 +1,3 @@
app
*.o
*.a
host.o
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 []
quicksort = \originalList ->
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp = \list, low, high ->
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;
#[link(name = "roc_app", kind = "static")]
extern "C" {
#[allow(improper_ctypes)]
#[link_name = "quicksort#1"]
#[link_name = "quicksort_1"]
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 mut nums = Vec::with_capacity(NUM_NUMS);
for index in 0..nums.capacity() {
let num = index as i64 % 12345;
let num = index as i64 % 123;
nums.push(num);
}
@ -39,4 +41,7 @@ pub fn main() {
// this pointer would segfault/cause badness. Therefore, we
// leak it for now
Box::leak(answer);
// Exit code
0
}

View File

@ -71,7 +71,7 @@ impl<T> RocList<T> {
let value = *self.get_storage_ptr();
// 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),
Less => Some(Storage::Refcounted(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;
}
}
}
}
}
}