Merge branch 'trunk' into remove-dict-hash-test-only

This commit is contained in:
satotake 2021-11-07 21:43:04 +09:00 committed by GitHub
commit ad50f4a8f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
148 changed files with 6856 additions and 5566 deletions

View file

@ -43,3 +43,8 @@ Locria Cyber <locriacyber@noreply.users.github.com>
Matthias Beyer <mail@beyermatthias.de>
Tim Whiting <tim@whitings.org>
Logan Lowder <logan.lowder@logikcull.com>
Joshua Warner <joshuawarner32@gmail.com>
Luiz Carlos L. G. de Oliveira <luizcarlos1405@gmail.com>
Oleksii Skidan <al.skidan@gmail.com>
Martin Janiczek <martin@janiczek.cz>
Eric Newbury <enewbury@users.noreply.github.com>

View file

@ -82,8 +82,6 @@ There are also alternative installation options at http://releases.llvm.org/down
## Using Nix
:exclamation: **Our Nix setup is not yet working on MacOS, you'll have to install manually for now** :exclamation:
### Install
Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command.
@ -120,10 +118,11 @@ You should be in a repl now. Have fun!
### Extra tips
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly!
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly!
### Editor
The editor is a WIP and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on.
`cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below.
#### Nvidia GPU

1975
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,7 @@ members = [
"compiler/gen_wasm",
"compiler/build",
"compiler/arena_pool",
"compiler/test_dev",
"compiler/test_gen",
"compiler/test_wasm",
"vendor/ena",

View file

@ -79,11 +79,11 @@ test-rust:
# not pre-compiling the host can cause race conditions
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release && sccache --show-stats
cargo test --release --features with_sound && sccache --show-stats
# run i386 (32-bit linux) cli tests
RUN echo "4" | cargo run --release -- --backend=x86_32 examples/benchmarks/NQueens.roc
RUN echo "4" | cargo run --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
cargo test --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
verify-no-git-changes:
FROM +test-rust
@ -109,7 +109,7 @@ build-nightly-release:
# version.txt is used by the CLI: roc --version
RUN printf "nightly pre-release, built from commit " > version.txt
RUN git log --pretty=format:'%h' -n 1 >> version.txt
RUN cargo build --release
RUN cargo build --features with_sound --release
RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc
SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz

View file

@ -10,7 +10,7 @@ If you're curious about where the language's name and logo came from,
## State of Roc
Roc is not ready for production yet. You are likely to encounter bugs. Publishing packages or documentation is not yet supported.
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C, Swift and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
## Getting started
@ -64,7 +64,7 @@ By using systems-level programming languages like C and C++, platform authors sa
Roc is designed to make the "systems-level platform, higher-level application" experience as nice as possible.
* **Application** authors code exclusively in Roc. It's a language designed for nice ergonomics. The syntax resembles Ruby or CoffeeScript, and it has a fast compiler with full type inference.
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, Swift or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, Swift and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
Every Roc application is built on top of exactly one Roc platform. There is no such thing as a Roc application that runs without a platform, and there is no default platform. You must choose one!

View file

@ -18,12 +18,11 @@ roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify"}
roc_load = { path = "../compiler/load" }
arraystring = "0.3.0"
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"
page_size = "0.4"
snafu = { version = "0.6", features = ["backtraces"] }
bumpalo = { version = "3.8.0", features = ["collections"] }
libc = "0.2.106"
page_size = "0.4.2"
snafu = { version = "0.6.10", features = ["backtraces"] }
ven_graph = { path = "../vendor/pathfinding" }
indoc = "1.0"
[dev-dependencies]
pretty_assertions = "0.6"
indoc = "1.0.3"

View file

@ -9,7 +9,7 @@ use crate::lang::core::ast::ASTNodeId;
#[snafu(visibility(pub))]
pub enum ASTError {
#[snafu(display(
"ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .",
"ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expecting `Some(ExprId)` .",
ast_node_id
))]
ASTNodeIdWithoutExprId {

View file

@ -464,7 +464,7 @@ fn canonicalize_pending_def<'a>(
env.tailcallable_symbol = Some(*defined_symbol);
};
// regiser the name of this closure, to make sure the closure won't capture it's own name
// register the name of this closure, to make sure the closure won't capture it's own name
if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
(&env.pool[loc_can_pattern], &loc_expr.value)
{
@ -632,7 +632,7 @@ fn canonicalize_pending_def<'a>(
env.tailcallable_symbol = Some(*defined_symbol);
};
// regiser the name of this closure, to make sure the closure won't capture it's own name
// register the name of this closure, to make sure the closure won't capture it's own name
if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
(&env.pool[loc_can_pattern], &loc_expr.value)
{

View file

@ -108,7 +108,7 @@ pub enum Expr2 {
},
Closure {
args: PoolVec<(Variable, NodeId<Pattern2>)>, // 8B
uniq_symbol: Symbol, // 8B This is a globally uniqe symbol for the closure
uniq_symbol: Symbol, // 8B This is a globally unique symbol for the closure
body_id: ExprId, // 4B
function_type: Variable, // 4B
recursive: Recursive, // 1B

View file

@ -454,7 +454,7 @@ pub fn expr_to_expr2<'a>(
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
// we end up with weird conclusions like the expression (\x -> x + 1)
// references the (nonexistant) local variable x!
// references the (nonexistent) local variable x!
output.references.lookups.remove(&sub_symbol);
}
}

View file

@ -5,7 +5,7 @@ use roc_types::subs::Variable;
#[derive(Clone, Debug, PartialEq, Default)]
pub struct IntroducedVariables {
// Rigids must be unique within a type annoation.
// Rigids must be unique within a type annotation.
// E.g. in `identity : a -> a`, there should only be one
// variable (a rigid one, with name "a").
// Hence `rigids : Map<Lowercase, Variable>`

View file

@ -162,7 +162,7 @@ impl Scope {
let alias = Alias {
actual,
/// We know that builtin aliases have no hiddden variables (e.g. in closures)
/// We know that builtin aliases have no hidden variables (e.g. in closures)
hidden_variables: PoolVec::empty(pool),
targs: variables,
};

View file

@ -10,8 +10,9 @@
///
/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied.
/// This is important for performance.
use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use std::any::type_name;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;
use std::ptr::null;

View file

@ -1,6 +1,6 @@
use super::pool::{NodeId, Pool, NODE_BYTES};
use super::shallow_clone::ShallowClone;
use libc::c_void;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;

View file

@ -1,8 +1,8 @@
use super::pool::{NodeId, Pool, NODE_BYTES};
use super::shallow_clone::ShallowClone;
use libc::c_void;
use std::any::type_name;
use std::cmp::Ordering;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;

View file

@ -15,25 +15,31 @@ test = false
bench = false
[features]
default = ["target-x86", "llvm", "editor"]
wasm32-cli-run = []
i386-cli-run = []
default = ["target-aarch64", "target-x86_64", "target-wasm32", "llvm", "editor"]
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
i386-cli-run = ["target-x86"]
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
llvm = ["inkwell", "roc_gen_llvm", "roc_build/llvm"]
editor = ["roc_editor"]
target-x86 = []
run-wasm32 = ["wasmer", "wasmer-wasi"]
# arm and wasm give linker errors on some platforms
target-arm = []
target-webassembly = []
# Compiling for a different platform than the host can cause linker errors.
target-arm = ["roc_build/target-arm"]
target-aarch64 = ["roc_build/target-aarch64"]
target-x86 = ["roc_build/target-x86"]
target-x86_64 = ["roc_build/target-x86_64"]
target-wasm32 = ["roc_build/target-wasm32"]
target-all = [
"target-x86",
"target-aarch64",
"target-arm",
"target-webassembly"
"target-x86",
"target-x86_64",
"target-wasm32"
]
@ -58,36 +64,33 @@ roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
clap = "= 3.0.0-beta.1"
const_format = "0.2"
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
const_format = "0.2.22"
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2"
libloading = "0.6"
im = "15.0.0"
im-rc = "15.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
mimalloc = { version = "0.1.26", default-features = false }
inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"
tempfile = "3.1.0"
tempfile = "3.2.0"
wasmer = "2.0.0"
wasmer-wasi = "2.0.0"
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer-wasi = { version = "2.0.0", optional = true }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
serial_test = "0.5"
tempfile = "3.1.0"
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer-wasi = "2.0.0"
pretty_assertions = "1.0.0"
indoc = "1.0.3"
serial_test = "0.5.1"
tempfile = "3.2.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "cli_utils" }
[[bench]]
name = "time_bench"
harness = false

View file

@ -14,11 +14,10 @@ roc_cli = { path = "../../cli" }
roc_collections = { path = "../../compiler/collections" }
roc_load = { path = "../../compiler/load" }
roc_module = { path = "../../compiler/module" }
bumpalo = { version = "3.6.1", features = ["collections"] }
bumpalo = { version = "3.8.0", features = ["collections"] }
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
inlinable_string = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"
strip-ansi-escapes = "0.1"
tempfile = "3.1.0"
serde = { version = "1.0.130", features = ["derive"] }
serde-xml-rs = "0.5.1"
strip-ansi-escapes = "0.1.1"
tempfile = "3.2.0"
rlimit = "0.6.2"

View file

@ -3,7 +3,6 @@ use roc_build::{
link::{link, rebuild_host, LinkType},
program,
};
#[cfg(feature = "llvm")]
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
@ -12,7 +11,6 @@ use roc_mono::ir::OptLevel;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
#[cfg(feature = "llvm")]
use tempfile::Builder;
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
@ -45,7 +43,6 @@ pub struct BuiltFile {
pub total_time: Duration,
}
#[cfg(feature = "llvm")]
#[allow(clippy::too_many_arguments)]
pub fn build_file<'a>(
arena: &'a Bump,
@ -179,8 +176,7 @@ pub fn build_file<'a>(
program::report_problems_monomorphized(&mut loaded);
let loaded = loaded;
let code_gen_timing = match opt_level {
OptLevel::Normal | OptLevel::Optimize => program::gen_from_mono_module_llvm(
let code_gen_timing = program::gen_from_mono_module(
arena,
loaded,
&roc_file_path,
@ -188,11 +184,7 @@ pub fn build_file<'a>(
app_o_file,
opt_level,
emit_debug_info,
),
OptLevel::Development => {
program::gen_from_mono_module_dev(arena, loaded, target, app_o_file)
}
};
);
buf.push('\n');
buf.push_str(" ");

View file

@ -44,24 +44,24 @@ pub fn build_app<'a>() -> App<'a> {
.subcommand(App::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::with_name(ROC_FILE)
Arg::new(ROC_FILE)
.about("The .roc file to build")
.required(true),
)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEV)
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::with_name(FLAG_BACKEND)
Arg::new(FLAG_BACKEND)
.long(FLAG_BACKEND)
.about("Choose a different backend")
// .requires(BACKEND)
@ -70,31 +70,31 @@ pub fn build_app<'a>() -> App<'a> {
.required(false),
)
.arg(
Arg::with_name(FLAG_LIB)
Arg::new(FLAG_LIB)
.long(FLAG_LIB)
.about("Build a C library instead of an executable.")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::with_name(FLAG_TIME)
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(FLAG_LINK)
Arg::new(FLAG_LINK)
.long(FLAG_LINK)
.about("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::with_name(FLAG_PRECOMPILED)
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
@ -106,23 +106,23 @@ pub fn build_app<'a>() -> App<'a> {
.subcommand(App::new(CMD_CHECK)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::with_name(FLAG_TIME)
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
Arg::new(ROC_FILE)
.about("The .roc file of an app to run")
.required(true),
)
)
.subcommand(
App::new(CMD_DOCS)
.about("Generate documentation for Roc modules")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.about("Generate documentation for Roc modules (Work In Progress)")
.arg(Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.multiple_values(true)
.required(false)
.about("The directory or files to build documentation for")
@ -130,45 +130,45 @@ pub fn build_app<'a>() -> App<'a> {
)
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::with_name(FLAG_DEV)
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::with_name(FLAG_TIME)
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(FLAG_LINK)
Arg::new(FLAG_LINK)
.long(FLAG_LINK)
.about("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::with_name(FLAG_PRECOMPILED)
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
)
.arg(
Arg::with_name(FLAG_BACKEND)
Arg::new(FLAG_BACKEND)
.long(FLAG_BACKEND)
.about("Choose a different backend")
// .requires(BACKEND)
@ -177,23 +177,23 @@ pub fn build_app<'a>() -> App<'a> {
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
Arg::new(ROC_FILE)
.about("The .roc file of an app to build and run")
.required(false),
)
.arg(
Arg::with_name(ARGS_FOR_APP)
Arg::new(ARGS_FOR_APP)
.about("Arguments to pass into the app being run")
.requires(ROC_FILE)
.multiple(true),
.multiple_values(true),
);
if cfg!(feature = "editor") {
app.subcommand(
App::new(CMD_EDIT).about("Launch the Roc editor").arg(
Arg::with_name(DIRECTORY_OR_FILES)
Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.multiple_values(true)
.required(false)
.about("(optional) The directory or files to open on launch."),
),
@ -217,7 +217,6 @@ pub enum BuildConfig {
BuildAndRun { roc_file_arg_index: usize },
}
#[cfg(feature = "llvm")]
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use build::build_file;
use std::str::FromStr;
@ -412,6 +411,7 @@ fn roc_run(cmd: &mut Command) -> io::Result<i32> {
}
}
#[cfg(feature = "run-wasm32")]
fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
use wasmer::{Instance, Module, Store};
@ -442,6 +442,11 @@ fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
}
}
#[cfg(not(feature = "run-wasm32"))]
fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
println!("Running wasm files not support");
}
enum Backend {
Host,
X86_32,

View file

@ -13,14 +13,8 @@ static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
use std::ffi::{OsStr, OsString};
#[cfg(feature = "llvm")]
use roc_cli::build;
#[cfg(not(feature = "llvm"))]
fn build(_matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> {
panic!("Building without LLVM is not currently supported.");
}
fn main() -> io::Result<()> {
let matches = build_app().get_matches();

View file

@ -191,6 +191,12 @@ mod cli_run {
eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename);
return;
}
"hello-swift" => {
if cfg!(not(target_os = "macos")) {
eprintln!("WARNING: skipping testing example {} because it only works on MacOS.", example.filename);
return;
}
}
_ => {}
}
@ -283,6 +289,14 @@ mod cli_run {
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
hello_swift:"hello-swift" => Example {
filename: "Hello.roc",
executable_filename: "hello-swift",
stdin: &[],
input_file: None,
expected_ending:"Hello Swift, meet Roc\n",
use_valgrind: true,
},
hello_web:"hello-web" => Example {
filename: "Hello.roc",
executable_filename: "hello-web",

View file

@ -10,10 +10,8 @@ description = "Our own markup language for Roc code. Used by the editor and (soo
roc_ast = { path = "../ast" }
roc_module = { path = "../compiler/module" }
roc_utils = { path = "../utils" }
serde = { version = "1.0.123", features = ["derive"] }
palette = "0.5"
snafu = { version = "0.6", features = ["backtraces"] }
bumpalo = { version = "3.2", features = ["collections"] }
serde = { version = "1.0.130", features = ["derive"] }
palette = "0.6.0"
snafu = { version = "0.6.10", features = ["backtraces"] }
bumpalo = { version = "3.8.0", features = ["collections"] }
itertools = "0.10.1"
[dev-dependencies]

View file

@ -1,4 +1,4 @@
use palette::{Hsv, LinSrgb};
use palette::{FromColor, Hsv, Srgb};
pub type RgbaTup = (f32, f32, f32, f32);
pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0);
@ -12,7 +12,7 @@ pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup {
}
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup {
let rgb = LinSrgb::from(Hsv::new(
let rgb = Srgb::from_color(Hsv::new(
hue as f32,
(saturation as f32) / 100.0,
(brightness as f32) / 100.0,

View file

@ -6,6 +6,3 @@ license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
edition = "2018"
description = "A CLI for Roc"
[dev-dependencies]
pretty_assertions = "0.5.1"

View file

@ -20,32 +20,29 @@ roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm" }
roc_gen_dev = { path = "../gen_dev" }
roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../reporting" }
roc_std = { path = "../../roc_std" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
im = "15.0.0"
im-rc = "15.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
tempfile = "3.2.0"
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
[target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.69"
[features]
default = ["llvm", "target-webassembly", "target-aarch64"]
default = ["llvm", "target-aarch64", "target-x86_64", "target-wasm32"]
target-arm = []
target-aarch64 = []
target-webassembly = []
target-aarch64 = ["roc_gen_dev/target-aarch64"]
target-x86 = []
target-x86_64 = ["roc_gen_dev/target-x86_64"]
target-wasm32 = ["roc_gen_wasm"]
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
llvm = ["inkwell", "roc_gen_llvm"]

View file

@ -300,6 +300,40 @@ pub fn build_c_host_native(
command.output().unwrap()
}
pub fn build_swift_host_native(
env_path: &str,
env_home: &str,
dest: &str,
sources: &[&str],
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
objc_header_path: Option<&str>,
) -> Output {
if shared_lib_path.is_some() {
unimplemented!("Linking a shared library to Swift not yet implemented");
}
let mut command = Command::new("swiftc");
command
.env_clear()
.env("PATH", &env_path)
.env("HOME", &env_home)
.args(sources)
.arg("-emit-object")
.arg("-parse-as-library")
.args(&["-o", dest]);
if let Some(objc_header) = objc_header_path {
command.args(&["-import-objc-header", objc_header]);
}
if matches!(opt_level, OptLevel::Optimize) {
command.arg("-O");
}
command.output().unwrap()
}
pub fn rebuild_host(
opt_level: OptLevel,
target: &Triple,
@ -312,6 +346,9 @@ pub fn rebuild_host(
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 swift_host_src = host_input_path.with_file_name("host.swift");
let swift_host_header_src = host_input_path.with_file_name("host.h");
let host_dest_native = host_input_path.with_file_name(if shared_lib_path.is_some() {
"dynhost"
} else {
@ -540,6 +577,20 @@ pub fn rebuild_host(
shared_lib_path,
);
validate_output("host.c", "clang", output);
} else if swift_host_src.exists() {
// Compile host.swift, if it exists
let output = build_swift_host_native(
&env_path,
&env_home,
host_dest_native.to_str().unwrap(),
&[swift_host_src.to_str().unwrap()],
opt_level,
shared_lib_path,
swift_host_header_src
.exists()
.then(|| swift_host_header_src.to_str().unwrap()),
);
validate_output("host.swift", "swiftc", output);
}
}
@ -690,7 +741,7 @@ fn link_linux(
.args(&[
"--gc-sections",
"--eh-frame-hdr",
"--arch",
"-A",
arch_str(target),
"-pie",
libcrt_path.join("crti.o").to_str().unwrap(),
@ -738,22 +789,14 @@ fn link_macos(
}
};
// This path only exists on macOS Big Sur, and it causes ld errors
// on Catalina if it's specified with -L, so we replace it with a
// redundant -lSystem if the directory isn't there.
let big_sur_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
let big_sur_fix = if Path::new(big_sur_path).exists() {
format!("-L{}", big_sur_path)
} else {
String::from("-lSystem")
};
let arch = match target.architecture {
Architecture::Aarch64(_) => "arm64".to_string(),
_ => target.architecture.to_string(),
};
let mut ld_child = Command::new("ld")
let mut ld_command = Command::new("ld");
ld_command
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
// Don't allow LD_ env vars to affect this
@ -767,12 +810,20 @@ fn link_macos(
link_type_arg,
"-arch",
&arch,
"-macos_version_min",
&get_macos_version(),
])
.args(input_paths)
.args(&[
.args(input_paths);
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
if Path::new(sdk_path).exists() {
ld_command.arg(format!("-L{}", sdk_path));
ld_command.arg(format!("-L{}/swift", sdk_path));
};
ld_command.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references
&big_sur_fix,
"-lSystem",
"-lresolv",
"-lpthread",
@ -784,22 +835,40 @@ fn link_macos(
// Output
"-o",
output_path.to_str().unwrap(), // app
])
.spawn()?;
]);
let mut ld_child = ld_command.spawn()?;
match target.architecture {
Architecture::Aarch64(_) => {
ld_child.wait()?;
let child = Command::new("codesign")
let codesign_child = Command::new("codesign")
.args(&["-s", "-", output_path.to_str().unwrap()])
.spawn()?;
Ok((child, output_path))
Ok((codesign_child, output_path))
}
_ => Ok((ld_child, output_path)),
}
}
fn get_macos_version() -> String {
let cmd_stdout = Command::new("sw_vers")
.arg("-productVersion")
.output()
.expect("Failed to execute command 'sw_vers -productVersion'")
.stdout;
let full_version_string = String::from_utf8(cmd_stdout)
.expect("Failed to convert output of command 'sw_vers -productVersion' into a utf8 string");
full_version_string
.split('.')
.take(2)
.collect::<Vec<&str>>()
.join(".")
}
fn link_wasm32(
_target: &Triple,
output_path: PathBuf,
@ -876,7 +945,7 @@ pub fn module_to_dylib(
// Load the dylib
let path = dylib_path.as_path().to_str().unwrap();
Library::new(path)
unsafe { Library::new(path) }
}
fn validate_output(file_name: &str, cmd_name: &str, output: Output) {

View file

@ -4,12 +4,13 @@ use roc_gen_llvm::llvm::build::module_from_builtins;
pub use roc_gen_llvm::llvm::build::FunctionIterator;
use roc_load::file::{LoadedModule, MonomorphizedModule};
use roc_module::symbol::{Interns, ModuleId};
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use std::path::{Path, PathBuf};
use std::time::Duration;
use roc_collections::all::{MutMap, MutSet};
use roc_collections::all::MutMap;
#[cfg(feature = "target-wasm32")]
use roc_collections::all::MutSet;
#[derive(Debug, Clone, Copy, Default)]
pub struct CodeGenTiming {
@ -19,6 +20,7 @@ pub struct CodeGenTiming {
// TODO: If modules besides this one start needing to know which version of
// llvm we're using, consider moving me somewhere else.
#[cfg(feature = "llvm")]
const LLVM_VERSION: &str = "12";
// TODO instead of finding exhaustiveness problems in monomorphization, find
@ -171,6 +173,50 @@ fn report_problems_help(
problems_reported
}
#[cfg(not(feature = "llvm"))]
pub fn gen_from_mono_module(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
_roc_file_path: &Path,
target: &target_lexicon::Triple,
app_o_file: &Path,
opt_level: OptLevel,
_emit_debug_info: bool,
) -> CodeGenTiming {
match opt_level {
OptLevel::Optimize => {
todo!("Return this error message in a better way: optimized builds not supported without llvm backend");
}
OptLevel::Normal | OptLevel::Development => {
gen_from_mono_module_dev(arena, loaded, target, app_o_file)
}
}
}
#[cfg(feature = "llvm")]
pub fn gen_from_mono_module(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
roc_file_path: &Path,
target: &target_lexicon::Triple,
app_o_file: &Path,
opt_level: OptLevel,
emit_debug_info: bool,
) -> CodeGenTiming {
match opt_level {
OptLevel::Normal | OptLevel::Optimize => gen_from_mono_module_llvm(
arena,
loaded,
roc_file_path,
target,
app_o_file,
opt_level,
emit_debug_info,
),
OptLevel::Development => gen_from_mono_module_dev(arena, loaded, target, app_o_file),
}
}
// 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.
@ -404,7 +450,7 @@ pub fn gen_from_mono_module_llvm(
emit_o_file,
}
}
#[cfg(feature = "target-wasm32")]
pub fn gen_from_mono_module_dev(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
@ -415,13 +461,31 @@ pub fn gen_from_mono_module_dev(
match target.architecture {
Architecture::Wasm32 => gen_from_mono_module_dev_wasm32(arena, loaded, app_o_file),
Architecture::X86_64 => {
Architecture::X86_64 | Architecture::Aarch64(_) => {
gen_from_mono_module_dev_assembly(arena, loaded, target, app_o_file)
}
_ => todo!(),
}
}
#[cfg(not(feature = "target-wasm32"))]
pub fn gen_from_mono_module_dev(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,
target: &target_lexicon::Triple,
app_o_file: &Path,
) -> CodeGenTiming {
use target_lexicon::Architecture;
match target.architecture {
Architecture::X86_64 | Architecture::Aarch64(_) => {
gen_from_mono_module_dev_assembly(arena, loaded, target, app_o_file)
}
_ => todo!(),
}
}
#[cfg(feature = "target-wasm32")]
fn gen_from_mono_module_dev_wasm32(
arena: &bumpalo::Bump,
loaded: MonomorphizedModule,

View file

@ -48,7 +48,9 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
#[cfg(feature = "llvm")]
pub fn init_arch(target: &Triple) {
match target.architecture {
Architecture::X86_64 | Architecture::X86_32(_) => {
Architecture::X86_64 | Architecture::X86_32(_)
if cfg!(any(feature = "target-x86", feature = "target-x86_64")) =>
{
Target::initialize_x86(&InitializationConfig::default());
}
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => {
@ -57,7 +59,7 @@ pub fn init_arch(target: &Triple) {
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
Target::initialize_arm(&InitializationConfig::default());
}
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
Architecture::Wasm32 if cfg!(feature = "target-wasm32") => {
Target::initialize_webassembly(&InitializationConfig::default());
}
_ => panic!(
@ -75,8 +77,8 @@ pub fn arch_str(target: &Triple) -> &'static str {
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
match target.architecture {
Architecture::X86_64 => "x86-64",
Architecture::X86_32(_) => "x86",
Architecture::X86_64 if cfg!(feature = "target-x86_64") => "x86-64",
Architecture::X86_32(_) if cfg!(feature = "target-x86") => "x86",
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => "aarch64",
Architecture::Arm(_) if cfg!(feature = "target-arm") => "arm",
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => "wasm32",

View file

@ -10,9 +10,3 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -42,7 +42,7 @@ fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
```
In these builtin definitions you will need to allocate for and list the arguments. For `List.repeat`, the arguments are the `elem_var` and the `len_var`. So in both the `body` and `defn` we list these arguments in a vector, with the `Symobl::ARG_1` and` Symvol::ARG_2` designating which argument is which.
In these builtin definitions you will need to allocate for and list the arguments. For `List.repeat`, the arguments are the `elem_var` and the `len_var`. So in both the `body` and `defn` we list these arguments in a vector, with the `Symbol::ARG_1` and` Symvol::ARG_2` designating which argument is which.
Since `List.repeat` is implemented entirely as low level functions, its `body` is a `RunLowLevel`, and the `op` is `LowLevel::ListRepeat`. Lets talk about `LowLevel` in the next section.
@ -60,11 +60,11 @@ Its one thing to actually write these functions, its _another_ thing to let the
## Specifying how we pass args to the function
### builtins/mono/src/borrow.rs
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
## Testing it
### solve/tests/solve_expr.rs
To make sure that Roc is properly inferring the type of the new builtin, add a test to this file simlar to:
To make sure that Roc is properly inferring the type of the new builtin, add a test to this file similar to:
```
#[test]
fn atan() {

View file

@ -6,4 +6,4 @@ set -euxo pipefail
zig build test
# fmt every zig
find src/*.zig -type f -print0 | xargs -n 1 -0 zig fmt --check || (echo "zig fmt --check FAILED! Check the previuous lines to see which files were improperly formatted." && exit 1)
find src/*.zig -type f -print0 | xargs -n 1 -0 zig fmt --check || (echo "zig fmt --check FAILED! Check the previous lines to see which files were improperly formatted." && exit 1)

View file

@ -188,7 +188,7 @@ test "" {
//
// Thank you Zig Contributors!
// Export it as weak incase it is alreadly linked in by something else.
// Export it as weak incase it is already linked in by something else.
comptime {
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
}

View file

@ -264,7 +264,7 @@ pub const RocStr = extern struct {
}
// Returns (@sizeOf(RocStr) - 1) for small strings and the empty string.
// Returns 0 for refcounted stirngs and immortal strings.
// Returns 0 for refcounted strings and immortal strings.
// Returns the stored capacity value for all other strings.
pub fn capacity(self: RocStr) usize {
const length = self.len();

View file

@ -15,7 +15,7 @@ extern fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen
// This should never be passed a null pointer.
extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void;
// Signals to the host that the program has paniced
// Signals to the host that the program has panicked
extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void;
comptime {
@ -50,7 +50,7 @@ fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
_ = c_ptr;
_ = tag_id;
@panic("Roc paniced");
@panic("Roc panicked");
}
pub fn alloc(size: usize, alignment: u32) [*]u8 {
@ -70,7 +70,7 @@ pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
}
// indirection because otherwise zig creats an alias to the panic function which our LLVM code
// indirection because otherwise zig creates an alias to the panic function which our LLVM code
// does not know how to deal with
pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
_ = c_ptr;

View file

@ -384,7 +384,7 @@ oks : List (Result elem *) -> List elem
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
## of memory as the original, even if its length decreases. This is becase it
## of memory as the original, even if its length decreases. This is because it
## can't know in advance exactly how much space it will need, and if it guesses a
## length that's too low, it would have to re-allocate.
##

View file

@ -395,7 +395,7 @@ Nat : Int [ @Natural ]
##
## A common use for #Nat is to store the length ("len" for short) of a
## collection like #List, #Set, or #Map. 64-bit systems can represent longer
## lists in memory than 32-bit sytems can, which is why the length of a list
## lists in memory than 32-bit systems can, which is why the length of a list
## is represented as a #Nat in Roc.
##
## If any operation would result in an #Int that is either too big

View file

@ -979,6 +979,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))),
);
// dropFirst : List elem -> List elem
add_top_level_function_type!(
Symbol::LIST_DROP_FIRST,
vec![list_type(flex(TVAR1))],
Box::new(list_type(flex(TVAR1))),
);
// swap : List elem, Nat, Nat -> List elem
add_top_level_function_type!(
Symbol::LIST_SWAP,

View file

@ -14,13 +14,10 @@ roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
ven_graph = { path = "../../vendor/pathfinding" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
im = "15.0.0"
im-rc = "15.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
pretty_assertions = "1.0.0"
indoc = "1.0.3"

View file

@ -20,7 +20,7 @@ pub struct Annotation {
pub struct IntroducedVariables {
// NOTE on rigids
//
// Rigids must be unique within a type annoation.
// Rigids must be unique within a type annotation.
// E.g. in `identity : a -> a`, there should only be one
// variable (a rigid one, with name "a").
// Hence `rigids : ImMap<Lowercase, Variable>`

View file

@ -1,5 +1,5 @@
use crate::def::Def;
use crate::expr::Expr::*;
use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Recursive, WhenBranch};
use crate::pattern::Pattern;
use roc_collections::all::SendMap;
@ -92,6 +92,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_MAP4 => list_map4,
LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at,
LIST_DROP_FIRST => list_drop_first,
LIST_DROP_LAST => list_drop_last,
LIST_SWAP => list_swap,
LIST_MAP_WITH_INDEX => list_map_with_index,
@ -2049,6 +2050,30 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
let num_var = Variable::NAT;
let num_precision_var = Variable::NATURAL;
let body = RunLowLevel {
op: LowLevel::ListDropAt,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(index_var, int(num_var, num_precision_var, 0)),
],
ret_var: list_var,
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
list_var,
)
}
/// List.dropLast: List elem -> List elem
fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -2930,7 +2955,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
CalledVia::Space,
);
let wrapper = Closure {
let wrapper = Closure(ClosureData {
function_type: wrapper_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -2944,7 +2969,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
(Variable::EMPTY_RECORD, no_region(Pattern::Underscore)),
],
loc_body: Box::new(no_region(call_func)),
};
});
let body = RunLowLevel {
op: LowLevel::DictWalk,
@ -3959,7 +3984,7 @@ fn defn_help(
.map(|(var, symbol)| (var, no_region(Identifier(symbol))))
.collect();
Closure {
Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -3969,7 +3994,7 @@ fn defn_help(
recursive: Recursive::NotRecursive,
arguments: closure_args,
loc_body: Box::new(no_region(body)),
}
})
}
#[inline(always)]

View file

@ -1,6 +1,7 @@
use crate::annotation::canonicalize_annotation;
use crate::annotation::IntroducedVariables;
use crate::env::Env;
use crate::expr::ClosureData;
use crate::expr::Expr::{self, *};
use crate::expr::{
canonicalize_expr, local_successors, references_from_call, references_from_local, Output,
@ -670,10 +671,10 @@ fn group_to_declaration(
let mut new_def = can_def.clone();
// Determine recursivity of closures that are not tail-recursive
if let Closure {
if let Closure(ClosureData {
recursive: recursive @ Recursive::NotRecursive,
..
} = &mut new_def.loc_expr.value
}) = &mut new_def.loc_expr.value
{
*recursive = closure_recursivity(*symbol, closures);
}
@ -698,10 +699,10 @@ fn group_to_declaration(
let mut new_def = can_def.clone();
// Determine recursivity of closures that are not tail-recursive
if let Closure {
if let Closure(ClosureData {
recursive: recursive @ Recursive::NotRecursive,
..
} = &mut new_def.loc_expr.value
}) = &mut new_def.loc_expr.value
{
*recursive = closure_recursivity(symbol, closures);
}
@ -787,7 +788,7 @@ fn canonicalize_pending_def<'a>(
output.references.referenced_aliases.insert(symbol);
}
aliases.extend(ann.aliases.iter().cloned());
aliases.extend(ann.aliases.clone());
output.introduced_variables.union(&ann.introduced_variables);
@ -838,7 +839,7 @@ fn canonicalize_pending_def<'a>(
};
Located {
value: Closure {
value: Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -848,7 +849,7 @@ fn canonicalize_pending_def<'a>(
recursive: Recursive::NotRecursive,
arguments: underscores,
loc_body: Box::new(body_expr),
},
}),
region: loc_ann.region,
}
};
@ -972,7 +973,7 @@ fn canonicalize_pending_def<'a>(
env.tailcallable_symbol = Some(*defined_symbol);
};
// regiser the name of this closure, to make sure the closure won't capture it's own name
// register the name of this closure, to make sure the closure won't capture it's own name
if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
(&loc_can_pattern.value, &loc_expr.value)
{
@ -1001,7 +1002,7 @@ fn canonicalize_pending_def<'a>(
if let (
&ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol),
&Closure {
&Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1011,7 +1012,7 @@ fn canonicalize_pending_def<'a>(
loc_body: ref body,
ref captured_symbols,
..
},
}),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
@ -1049,7 +1050,7 @@ fn canonicalize_pending_def<'a>(
});
// renamed_closure_def = Some(&defined_symbol);
loc_can_expr.value = Closure {
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1059,7 +1060,7 @@ fn canonicalize_pending_def<'a>(
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
};
});
}
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
@ -1119,7 +1120,7 @@ fn canonicalize_pending_def<'a>(
vars_by_symbol.insert(*defined_symbol, expr_var);
};
// regiser the name of this closure, to make sure the closure won't capture it's own name
// register the name of this closure, to make sure the closure won't capture it's own name
if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
(&loc_can_pattern.value, &loc_expr.value)
{
@ -1144,7 +1145,7 @@ fn canonicalize_pending_def<'a>(
if let (
&ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol),
&Closure {
&Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1154,7 +1155,7 @@ fn canonicalize_pending_def<'a>(
loc_body: ref body,
ref captured_symbols,
..
},
}),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
@ -1191,7 +1192,7 @@ fn canonicalize_pending_def<'a>(
refs.lookups = refs.lookups.without(defined_symbol);
});
loc_can_expr.value = Closure {
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1201,7 +1202,7 @@ fn canonicalize_pending_def<'a>(
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
};
});
}
// Store the referenced locals in the refs_by_symbol map, so we can later figure out

View file

@ -102,17 +102,7 @@ pub enum Expr {
ret_var: Variable,
},
Closure {
function_type: Variable,
closure_type: Variable,
closure_ext_var: Variable,
return_type: Variable,
name: Symbol,
captured_symbols: Vec<(Symbol, Variable)>,
recursive: Recursive,
arguments: Vec<(Variable, Located<Pattern>)>,
loc_body: Box<Located<Expr>>,
},
Closure(ClosureData),
// Product Types
Record {
@ -173,6 +163,18 @@ pub enum Expr {
// Compiles, but will crash if reached
RuntimeError(RuntimeError),
}
#[derive(Clone, Debug, PartialEq)]
pub struct ClosureData {
pub function_type: Variable,
pub closure_type: Variable,
pub closure_ext_var: Variable,
pub return_type: Variable,
pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub recursive: Recursive,
pub arguments: Vec<(Variable, Located<Pattern>)>,
pub loc_body: Box<Located<Expr>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Field {
@ -550,7 +552,7 @@ pub fn canonicalize_expr<'a>(
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
// we end up with weird conclusions like the expression (\x -> x + 1)
// references the (nonexistant) local variable x!
// references the (nonexistent) local variable x!
output.references.lookups.remove(sub_symbol);
}
}
@ -572,7 +574,7 @@ pub fn canonicalize_expr<'a>(
}
(
Closure {
Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -582,7 +584,7 @@ pub fn canonicalize_expr<'a>(
recursive: Recursive::NotRecursive,
arguments: can_args,
loc_body: Box::new(loc_body_expr),
},
}),
output,
)
}
@ -1403,7 +1405,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
LetNonRec(Box::new(def), Box::new(loc_expr), var)
}
Closure {
Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1413,14 +1415,14 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
captured_symbols,
arguments,
loc_body,
} => {
}) => {
let loc_expr = *loc_body;
let loc_expr = Located {
value: inline_calls(var_store, scope, loc_expr.value),
region: loc_expr.region,
};
Closure {
Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1430,7 +1432,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
captured_symbols,
arguments,
loc_body: Box::new(loc_expr),
}
})
}
Record { record_var, fields } => {
@ -1492,12 +1494,12 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
loc_expr:
Located {
value:
Closure {
Closure(ClosureData {
recursive,
arguments: params,
loc_body: boxed_body,
..
},
}),
..
},
..
@ -1681,7 +1683,7 @@ fn flatten_str_lines<'a>(
(desugar_str_segments(var_store, segments), output)
}
/// Resolve stirng interpolations by desugaring a sequence of StrSegments
/// Resolve string interpolations by desugaring a sequence of StrSegments
/// into nested calls to Str.concat
fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) -> Expr {
use StrSegment::*;

View file

@ -1,6 +1,6 @@
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::env::Env;
use crate::expr::{Expr, Output};
use crate::expr::{ClosureData, Expr, Output};
use crate::operator::desugar_def;
use crate::pattern::Pattern;
use crate::scope::Scope;
@ -416,13 +416,13 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
Closure {
Closure(ClosureData {
captured_symbols,
name,
arguments,
loc_body,
..
} => {
}) => {
captured_symbols.retain(|(s, _)| !no_capture_symbols.contains(s));
captured_symbols.retain(|(s, _)| s != name);
@ -502,7 +502,7 @@ fn fix_values_captured_in_closure_expr(
| Update {
updates: fields, ..
} => {
for field in fields.iter_mut() {
for (_, field) in fields.iter_mut() {
fix_values_captured_in_closure_expr(&mut field.loc_expr.value, no_capture_symbols);
}
}

View file

@ -63,11 +63,11 @@ impl Scope {
}
}
pub fn idents(&self) -> impl Iterator<Item = &(Ident, (Symbol, Region))> {
pub fn idents(&self) -> impl Iterator<Item = (&Ident, &(Symbol, Region))> {
self.idents.iter()
}
pub fn symbols(&self) -> impl Iterator<Item = &(Symbol, Region)> {
pub fn symbols(&self) -> impl Iterator<Item = (&Symbol, &Region)> {
self.symbols.iter()
}

View file

@ -15,7 +15,7 @@ mod test_can {
use crate::helpers::{can_expr_with, test_home, CanExprOut};
use bumpalo::Bump;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::Recursive;
use roc_can::expr::{ClosureData, Recursive};
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region;
use std::{f64, i64};
@ -654,10 +654,10 @@ mod test_can {
match expr {
LetRec(assignments, body, _) => {
match &assignments.get(i).map(|def| &def.loc_expr.value) {
Some(Closure {
Some(Closure(ClosureData {
recursive: recursion,
..
}) => recursion.clone(),
})) => recursion.clone(),
Some(other) => {
panic!("assignment at {} is not a closure, but a {:?}", i, other)
}
@ -676,10 +676,10 @@ mod test_can {
get_closure(&body.value, i - 1)
} else {
match &def.loc_expr.value {
Closure {
Closure(ClosureData {
recursive: recursion,
..
} => recursion.clone(),
}) => recursion.clone(),
other => {
panic!("assignment at {} is not a closure, but a {:?}", i, other)
}

View file

@ -6,8 +6,8 @@ license = "UPL-1.0"
edition = "2018"
[dependencies]
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
wyhash = "0.3"
bumpalo = { version = "3.6.1", features = ["collections"] }
im = "15.0.0"
im-rc = "15.0.0"
wyhash = "0.5.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }

View file

@ -13,10 +13,3 @@ roc_parse = { path = "../parse" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_builtins = { path = "../builtins" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -7,7 +7,7 @@ use roc_can::def::{Declaration, Def};
use roc_can::expected::Expected::{self, *};
use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{Field, WhenBranch};
use roc_can::expr::{ClosureData, Field, WhenBranch};
use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, Index, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName};
@ -333,7 +333,7 @@ pub fn constrain_expr(
// make lookup constraint to lookup this symbol's type in the environment
Lookup(*symbol, expected, region)
}
Closure {
Closure(ClosureData {
function_type: fn_var,
closure_type: closure_var,
closure_ext_var,
@ -343,7 +343,7 @@ pub fn constrain_expr(
captured_symbols,
name,
..
} => {
}) => {
// NOTE defs are treated somewhere else!
let loc_body_expr = &**boxed;
@ -1203,7 +1203,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
// instead of the more generic "something is wrong with the body of `f`"
match (&def.loc_expr.value, &signature) {
(
Closure {
Closure(ClosureData {
function_type: fn_var,
closure_type: closure_var,
closure_ext_var,
@ -1213,7 +1213,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
loc_body,
name,
..
},
}),
Type::Function(arg_types, signature_closure_type, ret_type),
) => {
// NOTE if we ever have problems with the closure, the ignored `_closure_type`
@ -1561,7 +1561,7 @@ pub fn rec_defs_help(
// instead of the more generic "something is wrong with the body of `f`"
match (&def.loc_expr.value, &signature) {
(
Closure {
Closure(ClosureData {
function_type: fn_var,
closure_type: closure_var,
closure_ext_var,
@ -1571,7 +1571,7 @@ pub fn rec_defs_help(
loc_body,
name,
..
},
}),
Type::Function(arg_types, _closure_type, ret_type),
) => {
// NOTE if we ever have trouble with closure type unification, the ignored

View file

@ -10,13 +10,10 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
im = "15.0.0"
im-rc = "15.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
pretty_assertions = "1.0.0"
indoc = "1.0.3"

View file

@ -9,38 +9,29 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_load = { path = "../load" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
im = "15.0.0"
im-rc = "15.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
target-lexicon = "0.12.2"
libloading = "0.6"
object = { version = "0.24", features = ["write"] }
# TODO: Deal with the update of object to 0.27.
# It looks like it breaks linking the generated objects.
# Probably just need to specify an extra field that used to be implicit or something.
# When fixed also update the version of object in the linker.
object = { version = "0.26.2", features = ["write"] }
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"
tempfile = "3.1.0"
itertools = "0.9"
bumpalo = { version = "3.8.0", features = ["collections"] }
[features]
target-aarch64 = ["roc_build/target-aarch64"]
target-aarch64 = []
target-x86_64 = []

View file

@ -53,7 +53,7 @@ Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob
### CallConv
[CallConv](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is the abstaction over caling conventions.
[CallConv](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is the abstraction over calling conventions.
It deals with register and stack specific information related to passing and returning arguments.
Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/x86_64.rs).
@ -74,7 +74,7 @@ This is the general procedure I follow with some helpful links:
1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/tests/helpers/eval.rs) and run the test.
It should fail and print out the mono ir for this test case.
Seeing the actual mono ir tends to be very helpful for complex additions.
1. Generally it will fail in one of the match statments in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.
Add the correct pattern matching and likely new function for your new builtin.
This will break the compile until you add the same function to places that implement the trait,
like [Backend64Bit](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs).

View file

@ -553,7 +553,7 @@ impl<
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
ret_jumps.push((jmp_location, jmp_offset));
// Overwite the original jne with the correct offset.
// Overwrite the original jne with the correct offset.
let end_offset = self.buf.len();
let jne_offset = end_offset - start_offset;
ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32);
@ -664,7 +664,7 @@ impl<
},
}));
// Overwite the original jump with the correct offset.
// Overwrite the original jump with the correct offset.
let mut tmp = bumpalo::vec![in self.env.arena];
self.update_jmp_imm32_offset(
&mut tmp,
@ -1296,7 +1296,7 @@ impl<
match val {
Some(SymbolStorage::GeneralReg(reg)) => {
let offset = self.claim_stack_size(8)?;
// For base addresssing, use the negative offset - 8.
// For base addressing, use the negative offset - 8.
ASM::mov_base32_reg64(&mut self.buf, offset, reg);
self.symbol_storage_map.insert(
*sym,
@ -1310,7 +1310,7 @@ impl<
}
Some(SymbolStorage::FloatReg(reg)) => {
let offset = self.claim_stack_size(8)?;
// For base addresssing, use the negative offset.
// For base addressing, use the negative offset.
ASM::mov_base32_freg64(&mut self.buf, offset, reg);
self.symbol_storage_map.insert(
*sym,

View file

@ -82,7 +82,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
X86_64GeneralReg::RAX,
X86_64GeneralReg::RCX,
X86_64GeneralReg::RDX,
// Don't use stack pionter: X86_64GeneralReg::RSP,
// Don't use stack pointer: X86_64GeneralReg::RSP,
X86_64GeneralReg::RSI,
X86_64GeneralReg::RDI,
X86_64GeneralReg::R8,
@ -258,7 +258,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
Layout::Struct(&[]) => {}
x => {
return Err(format!(
"Loading args with layout {:?} not yet implementd",
"Loading args with layout {:?} not yet implemented",
x
));
}
@ -464,7 +464,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
// The regs we want to use first should be at the end of this vec.
// We will use pop to get which reg to use next
// Don't use stack pionter: X86_64GeneralReg::RSP,
// Don't use stack pointer: X86_64GeneralReg::RSP,
// Don't use frame pointer: X86_64GeneralReg::RBP,
// Use callee saved regs last.
@ -602,7 +602,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Struct(&[]) => {}
x => {
return Err(format!(
"Loading args with layout {:?} not yet implementd",
"Loading args with layout {:?} not yet implemented",
x
));
}

View file

@ -68,7 +68,7 @@ where
/// finalize does any setup and cleanup that should happen around the procedure.
/// finalize does setup because things like stack size and jump locations are not know until the function is written.
/// For example, this can store the frame pionter and setup stack space.
/// For example, this can store the frame pointer and setup stack space.
/// finalize is run at the end of build_proc when all internal code is finalized.
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String>;

View file

@ -28,7 +28,7 @@ pub fn build_module<'a>(
architecture: TargetArch::X86_64,
binary_format: TargetBF::Elf,
..
} => {
} if cfg!(feature = "target-x86_64") => {
let backend: Backend64Bit<
x86_64::X86_64GeneralReg,
x86_64::X86_64FloatReg,
@ -46,7 +46,7 @@ pub fn build_module<'a>(
architecture: TargetArch::X86_64,
binary_format: TargetBF::Macho,
..
} => {
} if cfg!(feature = "target-x86_64") => {
let backend: Backend64Bit<
x86_64::X86_64GeneralReg,
x86_64::X86_64FloatReg,
@ -68,7 +68,7 @@ pub fn build_module<'a>(
architecture: TargetArch::Aarch64(_),
binary_format: TargetBF::Elf,
..
} => {
} if cfg!(feature = "target-aarch64") => {
let backend: Backend64Bit<
aarch64::AArch64GeneralReg,
aarch64::AArch64FloatReg,

View file

@ -1,875 +0,0 @@
#[macro_use]
extern crate indoc;
#[macro_use]
mod helpers;
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_num {
#[test]
fn i64_values() {
assert_evals_to!("0", 0, i64);
assert_evals_to!("-0", 0, i64);
assert_evals_to!("-1", -1, i64);
assert_evals_to!("1", 1, i64);
assert_evals_to!("9_000_000_000_000", 9_000_000_000_000, i64);
assert_evals_to!("-9_000_000_000_000", -9_000_000_000_000, i64);
assert_evals_to!("0b1010", 0b1010, i64);
assert_evals_to!("0o17", 0o17, i64);
assert_evals_to!("0x1000_0000_0000_0000", 0x1000_0000_0000_0000, i64);
}
#[test]
fn f64_values() {
assert_evals_to!("0.0", 0.0, f64);
assert_evals_to!("-0.0", 0.0, f64);
assert_evals_to!("1.0", 1.0, f64);
assert_evals_to!("-1.0", -1.0, f64);
assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64);
assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64);
assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64);
}
#[test]
fn gen_add_i64() {
assert_evals_to!(
indoc!(
r#"
1 + 2 + 3
"#
),
6,
i64
);
}
#[test]
fn gen_add_f64() {
assert_evals_to!(
indoc!(
r#"
1.1 + 2.4 + 3
"#
),
6.5,
f64
);
}
#[test]
fn gen_sub_i64() {
assert_evals_to!(
indoc!(
r#"
1 - 2 - 3
"#
),
-4,
i64
);
}
#[test]
fn gen_mul_i64() {
assert_evals_to!(
indoc!(
r#"
2 * 4 * 6
"#
),
48,
i64
);
}
#[test]
fn i64_force_stack() {
// This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64.
assert_evals_to!(
indoc!(
r#"
a = 0
b = 1
c = 2
d = 3
e = 4
f = 5
g = 6
h = 7
i = 8
j = 9
k = 10
l = 11
m = 12
n = 13
o = 14
p = 15
q = 16
r = 17
s = 18
t = 19
u = 20
v = 21
w = 22
x = 23
y = 24
z = 25
aa = 26
ab = 27
ac = 28
ad = 29
ae = 30
af = 31
ag = 32
a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag
"#
),
528,
i64
);
}
#[test]
fn i64_abs() {
assert_evals_to!("Num.abs -6", 6, i64);
assert_evals_to!("Num.abs 7", 7, i64);
assert_evals_to!("Num.abs 0", 0, i64);
assert_evals_to!("Num.abs -0", 0, i64);
assert_evals_to!("Num.abs -1", 1, i64);
assert_evals_to!("Num.abs 1", 1, i64);
assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64);
assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64);
}
#[test]
fn gen_int_eq() {
assert_evals_to!(
indoc!(
r#"
4 == 4
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
3 == 4
"#
),
false,
bool
);
}
#[test]
fn gen_basic_fn() {
assert_evals_to!(
indoc!(
r#"
always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64)
always42 = \_ -> 42
always42 5
"#
),
42,
i64
);
}
#[test]
fn gen_wrap_add_nums() {
assert_evals_to!(
indoc!(
r#"
add2 = \num1, num2 -> num1 + num2
add2 4 5
"#
),
9,
i64
);
}
#[test]
fn gen_wrap_add_nums_force_stack() {
assert_evals_to!(
indoc!(
r#"
add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9
add9 1 2 3 4 5 6 7 8 9
"#
),
45,
i64
);
}
#[test]
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
#[test]
fn acos() {
assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64);
}
#[test]
fn asin() {
assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64);
}
#[test]
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
#[test]
fn gen_if_fn() {
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
x =
if num == 1 then
-1
else if num == -1 then
1
else
num
x
limitedNegate 1
"#
),
-1,
i64
);
}
#[test]
fn gen_fib_fn() {
assert_evals_to!(
indoc!(
r#"
fib = \n ->
if n == 0 then
0
else if n == 1 then
1
else
(fib (n - 1)) + (fib (n - 2))
fib 10
"#
),
55,
i64
);
}
#[test]
fn gen_fast_fib_fn() {
assert_evals_to!(
indoc!(
r#"
fib = \n, a, b ->
if n == 0 then
a
else
fib (n - 1) b (a + b)
fib 10 0 1
"#
),
55,
i64
);
}
#[test]
fn f64_abs() {
assert_evals_to!("Num.abs -4.7", 4.7, f64);
assert_evals_to!("Num.abs 5.8", 5.8, f64);
}
#[test]
fn f64_round() {
assert_evals_to!("Num.round 3.6", 4, i64);
assert_evals_to!("Num.round 3.4", 3, i64);
assert_evals_to!("Num.round 2.5", 3, i64);
assert_evals_to!("Num.round -2.3", -2, i64);
assert_evals_to!("Num.round -2.5", -3, i64);
}
// #[test]
// fn f64_sqrt() {
// // FIXME this works with normal types, but fails when checking uniqueness types
// assert_evals_to!(
// indoc!(
// r#"
// when Num.sqrt 100 is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// 10.0,
// f64
// );
// }
// #[test]
// fn gen_float_eq() {
// assert_evals_to!(
// indoc!(
// r#"
// 1.0 == 1.0
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn gen_div_f64() {
// // FIXME this works with normal types, but fails when checking uniqueness types
// assert_evals_to!(
// indoc!(
// r#"
// when 48 / 2 is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// 24.0,
// f64
// );
// }
// #[test]
// fn gen_int_neq() {
// assert_evals_to!(
// indoc!(
// r#"
// 4 != 5
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn gen_wrap_int_neq() {
// assert_evals_to!(
// indoc!(
// r#"
// wrappedNotEq : a, a -> Bool
// wrappedNotEq = \num1, num2 ->
// num1 != num2
// wrappedNotEq 2 3
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn gen_sub_f64() {
// assert_evals_to!(
// indoc!(
// r#"
// 1.5 - 2.4 - 3
// "#
// ),
// -3.9,
// f64
// );
// }
// #[test]
// fn gen_div_i64() {
// assert_evals_to!(
// indoc!(
// r#"
// when 1000 // 10 is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// 100,
// i64
// );
// }
// #[test]
// fn gen_div_by_zero_i64() {
// assert_evals_to!(
// indoc!(
// r#"
// when 1000 // 0 is
// Err DivByZero -> 99
// _ -> -24
// "#
// ),
// 99,
// i64
// );
// }
// #[test]
// fn gen_rem_i64() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.rem 8 3 is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// 2,
// i64
// );
// }
// #[test]
// fn gen_rem_div_by_zero_i64() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.rem 8 0 is
// Err DivByZero -> 4
// Ok _ -> -23
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn gen_is_zero_i64() {
// assert_evals_to!("Num.isZero 0", true, bool);
// assert_evals_to!("Num.isZero 1", false, bool);
// }
// #[test]
// fn gen_is_positive_i64() {
// assert_evals_to!("Num.isPositive 0", false, bool);
// assert_evals_to!("Num.isPositive 1", true, bool);
// assert_evals_to!("Num.isPositive -5", false, bool);
// }
// #[test]
// fn gen_is_negative_i64() {
// assert_evals_to!("Num.isNegative 0", false, bool);
// assert_evals_to!("Num.isNegative 3", false, bool);
// assert_evals_to!("Num.isNegative -2", true, bool);
// }
// #[test]
// fn gen_is_positive_f64() {
// assert_evals_to!("Num.isPositive 0.0", false, bool);
// assert_evals_to!("Num.isPositive 4.7", true, bool);
// assert_evals_to!("Num.isPositive -8.5", false, bool);
// }
// #[test]
// fn gen_is_negative_f64() {
// assert_evals_to!("Num.isNegative 0.0", false, bool);
// assert_evals_to!("Num.isNegative 9.9", false, bool);
// assert_evals_to!("Num.isNegative -4.4", true, bool);
// }
// #[test]
// fn gen_is_zero_f64() {
// assert_evals_to!("Num.isZero 0", true, bool);
// assert_evals_to!("Num.isZero 0_0", true, bool);
// assert_evals_to!("Num.isZero 0.0", true, bool);
// assert_evals_to!("Num.isZero 1", false, bool);
// }
// #[test]
// fn gen_is_odd() {
// assert_evals_to!("Num.isOdd 4", false, bool);
// assert_evals_to!("Num.isOdd 5", true, bool);
// }
// #[test]
// fn gen_is_even() {
// assert_evals_to!("Num.isEven 6", true, bool);
// assert_evals_to!("Num.isEven 7", false, bool);
// }
// #[test]
// fn sin() {
// assert_evals_to!("Num.sin 0", 0.0, f64);
// assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64);
// }
// #[test]
// fn cos() {
// assert_evals_to!("Num.cos 0", 1.0, f64);
// assert_evals_to!("Num.cos 3.14159265359", -1.0, f64);
// }
// #[test]
// fn tan() {
// assert_evals_to!("Num.tan 0", 0.0, f64);
// assert_evals_to!("Num.tan 1", 1.557407724654902, f64);
// }
// #[test]
// fn lt_i64() {
// assert_evals_to!("1 < 2", true, bool);
// assert_evals_to!("1 < 1", false, bool);
// assert_evals_to!("2 < 1", false, bool);
// assert_evals_to!("0 < 0", false, bool);
// }
// #[test]
// fn lte_i64() {
// assert_evals_to!("1 <= 1", true, bool);
// assert_evals_to!("2 <= 1", false, bool);
// assert_evals_to!("1 <= 2", true, bool);
// assert_evals_to!("0 <= 0", true, bool);
// }
// #[test]
// fn gt_i64() {
// assert_evals_to!("2 > 1", true, bool);
// assert_evals_to!("2 > 2", false, bool);
// assert_evals_to!("1 > 1", false, bool);
// assert_evals_to!("0 > 0", false, bool);
// }
// #[test]
// fn gte_i64() {
// assert_evals_to!("1 >= 1", true, bool);
// assert_evals_to!("1 >= 2", false, bool);
// assert_evals_to!("2 >= 1", true, bool);
// assert_evals_to!("0 >= 0", true, bool);
// }
// #[test]
// fn lt_f64() {
// assert_evals_to!("1.1 < 1.2", true, bool);
// assert_evals_to!("1.1 < 1.1", false, bool);
// assert_evals_to!("1.2 < 1.1", false, bool);
// assert_evals_to!("0.0 < 0.0", false, bool);
// }
// #[test]
// fn lte_f64() {
// assert_evals_to!("1.1 <= 1.1", true, bool);
// assert_evals_to!("1.2 <= 1.1", false, bool);
// assert_evals_to!("1.1 <= 1.2", true, bool);
// assert_evals_to!("0.0 <= 0.0", true, bool);
// }
// #[test]
// fn gt_f64() {
// assert_evals_to!("2.2 > 1.1", true, bool);
// assert_evals_to!("2.2 > 2.2", false, bool);
// assert_evals_to!("1.1 > 2.2", false, bool);
// assert_evals_to!("0.0 > 0.0", false, bool);
// }
// #[test]
// fn gte_f64() {
// assert_evals_to!("1.1 >= 1.1", true, bool);
// assert_evals_to!("1.1 >= 1.2", false, bool);
// assert_evals_to!("1.2 >= 1.1", true, bool);
// assert_evals_to!("0.0 >= 0.0", true, bool);
// }
#[test]
fn gen_order_of_arithmetic_ops() {
assert_evals_to!(
indoc!(
r#"
1 + 3 * 7 - 2
"#
),
20,
i64
);
}
// #[test]
// fn gen_order_of_arithmetic_ops_complex_float() {
// assert_evals_to!(
// indoc!(
// r#"
// 3 - 48 * 2.0
// "#
// ),
// -93.0,
// f64
// );
// }
#[test]
fn if_guard_bind_variable_false() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 10 is
x if x == 5 -> 0
_ -> 42
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn if_guard_bind_variable_true() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 10 is
x if x == 10 -> 42
_ -> 0
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn tail_call_elimination() {
assert_evals_to!(
indoc!(
r#"
sum = \n, accum ->
when n is
0 -> accum
_ -> sum (n - 1) (n + accum)
sum 1_000_000 0
"#
),
500000500000,
i64
);
}
// #[test]
// fn int_negate() {
// assert_evals_to!("Num.neg 123", -123, i64);
// }
// #[test]
// fn gen_wrap_int_neg() {
// assert_evals_to!(
// indoc!(
// r#"
// wrappedNeg = \num -> -num
// wrappedNeg 3
// "#
// ),
// -3,
// i64
// );
// }
// #[test]
// fn int_to_float() {
// assert_evals_to!("Num.toFloat 0x9", 9.0, f64);
// }
// #[test]
// fn num_to_float() {
// assert_evals_to!("Num.toFloat 9", 9.0, f64);
// }
// #[test]
// fn float_to_float() {
// assert_evals_to!("Num.toFloat 0.5", 0.5, f64);
// }
// #[test]
// fn int_compare() {
// assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder);
// assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder);
// assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder);
// }
// #[test]
// fn float_compare() {
// assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder);
// assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder);
// assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder);
// }
// #[test]
// fn pow() {
// assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64);
// }
// #[test]
// fn ceiling() {
// assert_evals_to!("Num.ceiling 1.1", 2, i64);
// }
// #[test]
// fn floor() {
// assert_evals_to!("Num.floor 1.9", 1, i64);
// }
// // #[test]
// // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
// // fn int_overflow() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // 9_223_372_036_854_775_807 + 1
// // "#
// // ),
// // 0,
// // i64
// // );
// // }
// #[test]
// fn int_add_checked() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.addChecked 1 2 is
// Ok v -> v
// _ -> -1
// "#
// ),
// 3,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// when Num.addChecked 9_223_372_036_854_775_807 1 is
// Err Overflow -> -1
// Ok v -> v
// "#
// ),
// -1,
// i64
// );
// }
// #[test]
// fn int_add_wrap() {
// assert_evals_to!(
// indoc!(
// r#"
// Num.addWrap 9_223_372_036_854_775_807 1
// "#
// ),
// std::i64::MIN,
// i64
// );
// }
// #[test]
// fn float_add_checked_pass() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.addChecked 1.0 0.0 is
// Ok v -> v
// Err Overflow -> -1.0
// "#
// ),
// 1.0,
// f64
// );
// }
// #[test]
// fn float_add_checked_fail() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is
// Err Overflow -> -1
// Ok v -> v
// "#
// ),
// -1.0,
// f64
// );
// }
// // #[test]
// // #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
// // fn float_overflow() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // 1.7976931348623157e308 + 1.7976931348623157e308
// // "#
// // ),
// // 0.0,
// // f64
// // );
// // }
// #[test]
// fn max_i128() {
// assert_evals_to!(
// indoc!(
// r#"
// Num.maxI128
// "#
// ),
// i128::MAX,
// i128
// );
// }
// #[test]
// fn num_max_int() {
// assert_evals_to!(
// indoc!(
// r#"
// Num.maxInt
// "#
// ),
// i64::MAX,
// i64
// );
// }
// #[test]
// fn num_min_int() {
// assert_evals_to!(
// indoc!(
// r#"
// Num.minInt
// "#
// ),
// i64::MIN,
// i64
// );
// }
}

View file

@ -1,937 +0,0 @@
#[macro_use]
extern crate indoc;
#[macro_use]
mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_records {
#[test]
fn basic_record() {
assert_evals_to!(
indoc!(
r#"
{ y: 17, x: 15, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn nested_record() {
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
"#
),
12,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
"#
),
2,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn f64_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.x
"#
),
15.1,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.y
"#
),
17.2,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.z
"#
),
19.3,
f64
);
}
// #[test]
// fn fn_record() {
// assert_evals_to!(
// indoc!(
// r#"
// getRec = \x -> { y: 17, x, z: 19 }
// (getRec 15).x
// "#
// ),
// 15,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.y
// "#
// ),
// 17,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z
// "#
// ),
// 19,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z + rec.x
// "#
// ),
// 34,
// i64
// );
// }
#[test]
fn def_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17, x: 15, z: 19 }
rec.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.z
"#
),
19,
i64
);
}
#[test]
fn when_on_record() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2 } is
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn when_record_with_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: var } -> var + 3
"#
),
5,
i64
);
}
#[test]
fn let_with_record_pattern() {
assert_evals_to!(
indoc!(
r#"
{ x } = { x: 0x2, y: 3.14 }
x
"#
),
2,
i64
);
}
#[test]
fn record_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: 0x4 } -> 5
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn twice_record_access() {
assert_evals_to!(
indoc!(
r#"
x = {a: 0x2, b: 0x3 }
x.a + x.b
"#
),
5,
i64
);
}
#[test]
fn empty_record() {
assert_evals_to!(
indoc!(
r#"
v = {}
v
"#
),
(),
()
);
}
#[test]
fn i64_record1_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3 }
"#
),
3,
i64
);
}
// #[test]
// fn i64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// (3, 5),
// (i64, i64)
// );
// }
// // #[test]
// // fn i64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3, y: 5, z: 17 }
// // "#
// // ),
// // (3, 5, 17),
// // (i64, i64, i64)
// // );
// // }
// #[test]
// fn f64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3.1, y: 5.1 }
// "#
// ),
// (3.1, 5.1),
// (f64, f64)
// );
// }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// // #[test]
// // fn bool_record4_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // record : { a : Bool, b : Bool, c : Bool, d : Bool }
// // record = { a: True, b: True, c : True, d : Bool }
// // record
// // "#
// // ),
// // (true, false, false, true),
// // (bool, bool, bool, bool)
// // );
// // }
// #[test]
// fn i64_record1_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3 }
// "#
// ),
// 3,
// i64
// );
// }
// // #[test]
// // fn i64_record9_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
// // "#
// // ),
// // (3, 5, 17, 1, 9, 12, 13, 14, 15),
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64)
// // );
// // }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// #[test]
// fn bool_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// x : Bool
// x = True
// x
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn optional_field_when_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// main =
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_function_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \{ x ? 10, y } -> x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default_nested() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_singleton_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x : 4 } is
// { x ? 3 } -> x
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn optional_field_empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { } is
// { x ? 3 } -> x
// "#
// ),
// 3,
// i64
// );
// }
// #[test]
// fn return_record_2() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// [3, 5],
// [i64; 2]
// );
// }
// #[test]
// fn return_record_3() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5, z: 4 }
// "#
// ),
// (3, 5, 4),
// (i64, i64, i64)
// );
// }
// #[test]
// fn return_record_4() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2 }
// "#
// ),
// [3, 5, 4, 2],
// [i64; 4]
// );
// }
// #[test]
// fn return_record_5() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1 }
// "#
// ),
// [3, 5, 4, 2, 1],
// [i64; 5]
// );
// }
// #[test]
// fn return_record_6() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
// "#
// ),
// [3, 5, 4, 2, 1, 7],
// [i64; 6]
// );
// }
// #[test]
// fn return_record_7() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
// "#
// ),
// [3, 5, 4, 2, 1, 7, 8],
// [i64; 7]
// );
// }
// #[test]
// fn return_record_float_int() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3.14, b: 0x1 }
// "#
// ),
// (3.14, 0x1),
// (f64, i64)
// );
// }
// #[test]
// fn return_record_int_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 0x1, b: 3.14 }
// "#
// ),
// (0x1, 3.14),
// (i64, f64)
// );
// }
// #[test]
// fn return_record_float_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 6.28, b: 3.14 }
// "#
// ),
// (6.28, 3.14),
// (f64, f64)
// );
// }
// #[test]
// fn return_record_float_float_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 6.28, b: 3.14, c: 0.1 }
// "#
// ),
// (6.28, 3.14, 0.1),
// (f64, f64, f64)
// );
// }
// #[test]
// fn return_nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
// "#
// ),
// (0x0, (6.28, 3.14, 0.1)),
// (i64, (f64, f64, f64))
// );
// }
// #[test]
// fn accessor() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 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 }
// { rec & foo: rec.foo + 1 }
// "#
// ),
// (6, 43),
// (i64, i64)
// );
// }
#[test]
fn update_single_element_record() {
assert_evals_to!(
indoc!(
r#"
rec = { foo: 42}
{ rec & foo: rec.foo + 1 }
"#
),
43,
i64
);
}
// #[test]
// fn booleans_in_record() {
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
// (true, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
// (false, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
// (true, false),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
// (false, false),
// (bool, bool)
// );
// }
// #[test]
// fn alignment_in_record() {
// assert_evals_to!(
// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
// (32i64, true, 2u8),
// (i64, bool, u8)
// );
// }
// #[test]
// fn blue_and_present() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue, y: 7 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn blue_and_absent() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue }
// "#
// ),
// 3,
// i64
// );
// }
}

View file

@ -1,954 +0,0 @@
// #[macro_use]
// extern crate indoc;
#[macro_use]
mod helpers;
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_str {
// use roc_std::{RocList, RocStr};
// #[test]
// fn str_split_bigger_delimiter_small_str() {
// assert_evals_to!(
// indoc!(
// r#"
// List.len (Str.split "hello" "JJJJ there")
// "#
// ),
// 1,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// when List.first (Str.split "JJJ" "JJJJ there") is
// Ok str ->
// Str.countGraphemes str
// _ ->
// -1
// "#
// ),
// 3,
// i64
// );
// }
// #[test]
// fn str_split_str_concat_repeated() {
// assert_evals_to!(
// indoc!(
// r#"
// when List.first (Str.split "JJJJJ" "JJJJ there") is
// Ok str ->
// str
// |> Str.concat str
// |> Str.concat str
// |> Str.concat str
// |> Str.concat str
// _ ->
// "Not Str!"
// "#
// ),
// RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"),
// RocStr
// );
// }
// #[test]
// fn str_split_small_str_bigger_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// when
// List.first
// (Str.split "JJJ" "0123456789abcdefghi")
// is
// Ok str -> str
// _ -> ""
// "#
// ),
// RocStr::from_slice(b"JJJ"),
// RocStr
// );
// }
// #[test]
// fn str_split_big_str_small_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"01234567789abcdefghi"),
// RocStr::from_slice(b"01234567789abcdefghi")
// ]),
// RocList<RocStr>
// );
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"01234567789abcdefghi "),
// RocStr::from_slice(b" 01234567789abcdefghi")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_small_str_small_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "J!J!J" "!"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"J"),
// RocStr::from_slice(b"J"),
// RocStr::from_slice(b"J")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_bigger_delimiter_big_strs() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "string to split is shorter"
// "than the delimiter which happens to be very very long"
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_empty_strs() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "" ""
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"")]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_minimal_example() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "a," ","
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]),
// RocList<RocStr>
// )
// }
// #[test]
// fn str_split_small_str_big_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
// "---- ---- ---- ---- ----"
// |> List.len
// "#
// ),
// 3,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
// "---- ---- ---- ---- ----"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"1"),
// RocStr::from_slice(b"2"),
// RocStr::from_slice(b"")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_small_str_20_char_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |"
// "|-- -- -- -- -- -- |"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"3"),
// RocStr::from_slice(b"4"),
// RocStr::from_slice(b"")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_concat_big_to_big() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.concat
// "First string that is fairly long. Longer strings make for different errors. "
// "Second string that is also fairly long. Two long strings test things that might not appear with short strings."
// "#
// ),
// RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."),
// RocStr
// );
// }
#[test]
fn small_str_literal() {
assert_evals_to!(
"\"JJJJJJJJJJJJJJJ\"",
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_zeroed_literal() {
// // Verifies that we zero out unused bytes in the string.
// // This is important so that string equality tests don't randomly
// // fail due to unused memory being there!
// assert_evals_to!(
// "\"J\"",
// [
// 0x4a,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0b1000_0001
// ],
// [u8; 16]
// );
// }
#[test]
fn small_str_concat_empty_first_arg() {
assert_evals_to!(
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_empty_second_arg() {
assert_evals_to!(
r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_concat_small_to_big() {
// assert_evals_to!(
// r#"Str.concat "abc" " this is longer than 15 chars""#,
// RocStr::from_slice(b"abc this is longer than 15 chars"),
// RocStr
// );
// }
#[test]
fn small_str_concat_small_to_small_staying_small() {
assert_evals_to!(
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_concat_small_to_small_overflow_to_big() {
// assert_evals_to!(
// r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
// RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"),
// RocStr
// );
// }
// #[test]
// fn str_concat_empty() {
// assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr);
// }
// #[test]
// fn small_str_is_empty() {
// assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool);
// }
// #[test]
// fn big_str_is_empty() {
// assert_evals_to!(
// r#"Str.isEmpty "this is more than 15 chars long""#,
// false,
// bool
// );
// }
// #[test]
// fn empty_str_is_empty() {
// assert_evals_to!(r#"Str.isEmpty """#, true, bool);
// }
// #[test]
// fn str_starts_with() {
// assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool);
// assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool);
// assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
// }
// #[test]
// fn str_starts_with_code_point() {
// assert_evals_to!(
// &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32),
// true,
// bool
// );
// assert_evals_to!(
// &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32),
// false,
// bool
// );
// }
// #[test]
// fn str_ends_with() {
// assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
// assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
// }
// #[test]
// fn str_count_graphemes_small_str() {
// assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
// }
// #[test]
// fn str_count_graphemes_three_js() {
// assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize);
// }
// #[test]
// fn str_count_graphemes_big_str() {
// assert_evals_to!(
// r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#,
// 45,
// usize
// );
// }
// #[test]
// fn str_starts_with_same_big_str() {
// assert_evals_to!(
// r#"Str.startsWith "123456789123456789" "123456789123456789""#,
// true,
// bool
// );
// }
// #[test]
// fn str_starts_with_different_big_str() {
// assert_evals_to!(
// r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
// true,
// bool
// );
// }
// #[test]
// fn str_starts_with_same_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
// }
// #[test]
// fn str_starts_with_different_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
// }
// #[test]
// fn str_starts_with_false_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
// }
// #[test]
// fn str_from_int() {
// assert_evals_to!(
// r#"Str.fromInt 1234"#,
// roc_std::RocStr::from_slice("1234".as_bytes()),
// roc_std::RocStr
// );
// assert_evals_to!(
// r#"Str.fromInt 0"#,
// roc_std::RocStr::from_slice("0".as_bytes()),
// roc_std::RocStr
// );
// assert_evals_to!(
// r#"Str.fromInt -1"#,
// roc_std::RocStr::from_slice("-1".as_bytes()),
// roc_std::RocStr
// );
// let max = format!("{}", i64::MAX);
// assert_evals_to!(
// r#"Str.fromInt Num.maxInt"#,
// RocStr::from_slice(max.as_bytes()),
// RocStr
// );
// let min = format!("{}", i64::MIN);
// assert_evals_to!(
// r#"Str.fromInt Num.minInt"#,
// RocStr::from_slice(min.as_bytes()),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_ascii() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_ascii() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("abc~".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_unicode() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("∆".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_unicode() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("∆œ¬".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_grapheme() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_grapheme() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_all() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖b∆".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_invalid_start_byte() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is
// Err (BadUtf8 InvalidStartByte byteIndex) ->
// if byteIndex == 2 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_unexpected_end_of_sequence() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is
// Err (BadUtf8 UnexpectedEndOfSequence byteIndex) ->
// if byteIndex == 3 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_expected_continuation() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is
// Err (BadUtf8 ExpectedContinuation byteIndex) ->
// if byteIndex == 3 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_overlong_encoding() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is
// Err (BadUtf8 OverlongEncoding byteIndex) ->
// if byteIndex == 1 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_codepoint_too_large() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is
// Err (BadUtf8 CodepointTooLarge byteIndex) ->
// if byteIndex == 1 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_surrogate_half() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is
// Err (BadUtf8 EncodesSurrogateHalf byteIndex) ->
// if byteIndex == 2 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_equality() {
// assert_evals_to!(r#""a" == "a""#, true, bool);
// assert_evals_to!(
// r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#,
// true,
// bool
// );
// assert_evals_to!(r#""a" != "b""#, true, bool);
// assert_evals_to!(r#""a" == "b""#, false, bool);
// }
// #[test]
// fn str_clone() {
// use roc_std::RocStr;
// let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes());
// let short = RocStr::from_slice("x".as_bytes());
// let empty = RocStr::from_slice("".as_bytes());
// debug_assert_eq!(long.clone(), long);
// debug_assert_eq!(short.clone(), short);
// debug_assert_eq!(empty.clone(), empty);
// }
// #[test]
// fn nested_recursive_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// Expr : [ Add Expr Expr, Val I64, Var I64 ]
// expr : Expr
// expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))
// printExpr : Expr -> Str
// printExpr = \e ->
// when e is
// Add a b ->
// "Add ("
// |> Str.concat (printExpr a)
// |> Str.concat ") ("
// |> Str.concat (printExpr b)
// |> Str.concat ")"
// Val v -> "Val " |> Str.concat (Str.fromInt v)
// Var v -> "Var " |> Str.concat (Str.fromInt v)
// printExpr expr
// "#
// ),
// RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_small() {
// assert_evals_to!(
// r#"Str.joinWith ["1", "2"] ", " "#,
// RocStr::from("1, 2"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_big() {
// assert_evals_to!(
// r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#,
// RocStr::from("10000000, 2000000, 30000000"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_single() {
// assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
// }
// #[test]
// fn str_from_float() {
// assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr);
// }
// #[test]
// fn str_to_utf8() {
// assert_evals_to!(
// r#"Str.toUtf8 "hello""#,
// RocList::from_slice(&[104, 101, 108, 108, 111]),
// RocList<u8>
// );
// assert_evals_to!(
// r#"Str.toUtf8 "this is a long string""#,
// RocList::from_slice(&[
// 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116,
// 114, 105, 110, 103
// ]),
// RocList<u8>
// );
// }
// #[test]
// fn str_from_utf8_range() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 5, start: 0 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("hello"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_slice() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 4, start: 1 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ello"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_slice_not_end() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 3, start: 1 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ell"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_order_does_not_matter() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 1, count: 3 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ell"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_out_of_bounds_start_value() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 7, count: 3 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_count_too_high() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 0, count: 6 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_count_too_high_for_start() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 4, count: 3 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }
}

View file

@ -18,23 +18,13 @@ roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_std = { path = "../../roc_std" }
morphic_lib = { path = "../../vendor/morphic_lib" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
im = "15.0.0"
im-rc = "15.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.2"
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_load = { path = "../load" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"
bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -328,7 +328,7 @@ enum Mode {
Dec,
}
/// a functin that accepts two arguments: the value to increment, and an amount to increment by
/// a function that accepts two arguments: the value to increment, and an amount to increment by
pub fn build_inc_n_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -337,7 +337,7 @@ pub fn build_inc_n_wrapper<'a, 'ctx, 'env>(
build_rc_wrapper(env, layout_ids, layout, Mode::IncN)
}
/// a functin that accepts two arguments: the value to increment; increments by 1
/// a function that accepts two arguments: the value to increment; increments by 1
pub fn build_inc_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,

View file

@ -3108,7 +3108,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
return_layout: Layout<'a>,
c_function_name: &str,
) -> FunctionValue<'ctx> {
// NOTE we ingore env.is_gen_test here
// NOTE we ignore env.is_gen_test here
let wrapper_return_type = roc_function.get_type().get_return_type().unwrap();
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
@ -3765,7 +3765,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
// our exposed main function adheres to the C calling convention
wrapper_function.set_call_conventions(FAST_CALL_CONV);
// invoke instead of call, so that we can catch any exeptions thrown in Roc code
// invoke instead of call, so that we can catch any exceptions thrown in Roc code
let arguments = wrapper_function.get_params();
let basic_block = context.append_basic_block(wrapper_function, "entry");

View file

@ -1379,7 +1379,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
) {
debug_assert_eq!(cases.len(), 1);
// in this case, don't switch, because the `else` branch below would try to read the (nonexistant) tag id
// in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id
let (_, only_branch) = cases.pop().unwrap();
env.builder.build_unconditional_branch(only_branch);
} else {

View file

@ -9,22 +9,14 @@ edition = "2018"
roc_collections = { path = "../collections" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
bumpalo = { version = "3.6.1", features = ["collections"] }
# TODO: switch to parity-wasm 0.44 once it's out (allows bumpalo vectors in some places)
parity-wasm = { git = "https://github.com/brian-carroll/parity-wasm", branch = "master" }
bumpalo = { version = "3.8.0", features = ["collections"] }
roc_std = { path = "../../roc_std" }
wasmer = "2.0.0"
wasmer-wasi = "2.0.0"
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
[dev-dependencies]
roc_can = { path = "../can" }
roc_builtins = { path = "../builtins" }
roc_load = { path = "../load" }
roc_types = { path = "../types" }
roc_module = { path = "../module" }
indoc = "0.3.3"
pretty_assertions = "0.5.1"
libc = "0.2"
target-lexicon = "0.12.2"
tempfile = "3.1.0"

View file

@ -56,7 +56,7 @@ There is also an improvement on Relooper called ["Stackifier"](https://medium.co
## Stack machine vs register machine
Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can oly operate on whatever data is at the top of the stack.
Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can only operate on whatever data is at the top of the stack.
For example the instruction `i64.add` takes two operands. It pops the top two arguments off the VM stack and pushes the result back.
@ -225,6 +225,4 @@ The Module is a _specification_ for how to create an Instance of the program. Th
A WebAssembly module is equivalent to an executable file. It doesn't normally need relocations since at the WebAssembly layer, there is no Address Space Layout Randomisation. If it has relocations then it's an object file.
The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it has room for "custom sections" that in practice seem to be used for that.
The WebAssembly `tool-conventions` repo has a document on [linking](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md), and the `parity_wasm` crate supports "name" and "relocation" [sections](https://docs.rs/parity-wasm/0.42.2/parity_wasm/elements/enum.Section.html).
The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it does support "custom" sections. Conventions to use those for linking are documented in the WebAssembly `tool-conventions` repo [here](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md) and it mentions that LLVM is using those conventions.

View file

@ -1,37 +1,36 @@
use bumpalo::collections::Vec;
use parity_wasm::builder;
use parity_wasm::builder::{FunctionDefinition, ModuleBuilder};
use code_builder::Align;
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout};
use roc_mono::layout::Layout;
use crate::code_builder::{BlockType, CodeBuilder, ValueType};
use crate::layout::WasmLayout;
use crate::module_builder::RelocationEntry;
use crate::serialize::SerialBuffer;
use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::{copy_memory, CopyMemoryConfig, Env, LocalId, PTR_TYPE};
use crate::wasm_module::linking::{LinkingSection, RelocationSection};
use crate::wasm_module::sections::{
CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection,
ImportSection, MemorySection, TypeSection, WasmModule,
};
use crate::wasm_module::{
code_builder, BlockType, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType,
LocalId, Signature, ValueType,
};
use crate::{copy_memory, CopyMemoryConfig, Env, PTR_TYPE};
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
// Follow Emscripten's example by using 1kB (4 bytes would probably do)
const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
#[derive(Clone, Copy, Debug)]
struct LabelId(u32);
pub struct WasmBackend<'a> {
env: &'a Env<'a>,
// Module-level data
pub module_builder: ModuleBuilder,
pub code_section_bytes: std::vec::Vec<u8>,
pub code_relocations: Vec<'a, RelocationEntry>,
_data_offset_map: MutMap<Literal<'a>, u32>,
_data_offset_next: u32,
proc_symbols: &'a [Symbol],
pub module: WasmModule<'a>,
next_literal_addr: u32,
proc_symbols: Vec<'a, Symbol>,
// Function-level data
code_builder: CodeBuilder<'a>,
@ -43,23 +42,56 @@ pub struct WasmBackend<'a> {
}
impl<'a> WasmBackend<'a> {
pub fn new(env: &'a Env<'a>, proc_symbols: &'a [Symbol]) -> Self {
let mut code_section_bytes = std::vec::Vec::with_capacity(4096);
pub fn new(env: &'a Env<'a>, proc_symbols: Vec<'a, Symbol>) -> Self {
const MEMORY_INIT_SIZE: u32 = 1024 * 1024;
// Code section header
code_section_bytes.reserve_padded_u32(); // byte length, to be written at the end
code_section_bytes.encode_padded_u32(proc_symbols.len() as u32); // modified later in unit tests
let mut module = WasmModule {
types: TypeSection::new(env.arena),
import: ImportSection::new(env.arena),
function: FunctionSection::new(env.arena),
table: (), // Unused in Roc (mainly for function pointers)
memory: MemorySection::new(MEMORY_INIT_SIZE),
global: GlobalSection::new(env.arena),
export: ExportSection::new(env.arena),
start: (), // Entry function. In Roc this would be part of the platform.
element: (), // Unused in Roc (related to table section)
code: CodeSection::new(env.arena),
data: DataSection::new(env.arena),
linking: LinkingSection::new(env.arena),
reloc_code: RelocationSection::new(env.arena, "reloc.CODE"),
reloc_data: RelocationSection::new(env.arena, "reloc.DATA"),
};
module.export.entries.push(Export {
name: "memory".to_string(),
ty: ExportType::Mem,
index: 0,
});
let stack_pointer_global = Global {
ty: GlobalType {
value_type: ValueType::I32,
is_mutable: true,
},
init: ConstExpr::I32(MEMORY_INIT_SIZE as i32),
};
module.global.entries.push(stack_pointer_global);
let literal_segment = DataSegment {
mode: DataMode::Active {
offset: ConstExpr::I32(UNUSED_DATA_SECTION_BYTES as i32),
},
init: Vec::with_capacity_in(64, env.arena),
};
module.data.segments.push(literal_segment);
WasmBackend {
env,
// Module-level data
module_builder: builder::module(),
code_section_bytes,
_data_offset_map: MutMap::default(),
_data_offset_next: UNUSED_DATA_SECTION_BYTES,
module,
next_literal_addr: UNUSED_DATA_SECTION_BYTES,
proc_symbols,
code_relocations: Vec::with_capacity_in(256, env.arena),
// Function-level data
block_depth: 0,
@ -69,8 +101,13 @@ impl<'a> WasmBackend<'a> {
}
}
/// Reset function-level data
fn reset(&mut self) {
self.code_builder.clear();
// Push the completed CodeBuilder into the module and swap it for a new empty one
let mut swap_code_builder = CodeBuilder::new(self.env.arena);
std::mem::swap(&mut swap_code_builder, &mut self.code_builder);
self.module.code.code_builders.push(swap_code_builder);
self.storage.clear();
self.joinpoint_label_map.clear();
assert_eq!(self.block_depth, 0);
@ -82,52 +119,43 @@ impl<'a> WasmBackend<'a> {
***********************************************************/
pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<u32, String> {
// println!("\ngenerating procedure {:?}\n", sym);
pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<(), String> {
// println!("\ngenerating procedure {:?}\n", _sym);
// Use parity-wasm to add the signature in "types" and "functions" sections
// but no instructions, since we are building our own code section
let empty_function_def = self.start_proc(&proc);
let location = self.module_builder.push_function(empty_function_def);
let function_index = location.body;
self.start_proc(&proc);
self.build_stmt(&proc.body, &proc.ret_layout)?;
self.finalize_proc()?;
self.reset();
// println!("\nfinished generating {:?}\n", sym);
// println!("\nfinished generating {:?}\n", _sym);
Ok(function_index)
Ok(())
}
fn start_proc(&mut self, proc: &Proc<'a>) -> FunctionDefinition {
fn start_proc(&mut self, proc: &Proc<'a>) {
let ret_layout = WasmLayout::new(&proc.ret_layout);
let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
let ret_type = if ret_layout.is_stack_memory() {
self.storage.arg_types.push(PTR_TYPE);
self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any)
builder::signature()
None
} else {
let ret_type = ret_layout.value_type();
self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any)
builder::signature().with_result(ret_type.to_parity_wasm())
let ty = ret_layout.value_type();
self.start_block(BlockType::Value(ty)); // block to ensure all paths pop stack memory (if any)
Some(ty)
};
for (layout, symbol) in proc.args {
self.storage.allocate(
&WasmLayout::new(layout),
*symbol,
StoredValueKind::Parameter,
);
let arg_layout = WasmLayout::new(layout);
self.storage
.allocate(&arg_layout, *symbol, StoredValueKind::Parameter);
}
let parity_params = self.storage.arg_types.iter().map(|t| t.to_parity_wasm());
let signature = signature_builder.with_params(parity_params).build_sig();
// parity-wasm FunctionDefinition with no instructions
builder::function().with_signature(signature).build()
self.module.add_function_signature(Signature {
param_types: self.storage.arg_types.clone(),
ret_type,
});
}
fn finalize_proc(&mut self) -> Result<(), String> {
@ -141,8 +169,6 @@ impl<'a> WasmBackend<'a> {
self.storage.stack_frame_pointer,
);
let relocs = self.code_builder.serialize(&mut self.code_section_bytes);
self.code_relocations.extend(relocs);
Ok(())
}
@ -178,9 +204,9 @@ impl<'a> WasmBackend<'a> {
_ => StoredValueKind::Variable,
};
self.storage.allocate(&wasm_layout, *sym, kind);
let sym_storage = self.storage.allocate(&wasm_layout, *sym, kind);
self.build_expr(sym, expr, layout)?;
self.build_expr(sym, expr, layout, &sym_storage)?;
// For primitives, we record that this symbol is at the top of the VM stack
// (For other values, we wrote to memory and there's nothing on the VM stack)
@ -367,9 +393,11 @@ impl<'a> WasmBackend<'a> {
sym: &Symbol,
expr: &Expr<'a>,
layout: &Layout<'a>,
storage: &StoredValue,
) -> Result<(), String> {
let wasm_layout = WasmLayout::new(layout);
match expr {
Expr::Literal(lit) => self.load_literal(lit, layout),
Expr::Literal(lit) => self.load_literal(lit, storage),
Expr::Call(roc_mono::ir::Call {
call_type,
@ -379,7 +407,6 @@ impl<'a> WasmBackend<'a> {
// TODO: See if we can make this more efficient
// Recreating the same WasmLayout again, rather than passing it down,
// to match signature of Backend::build_expr
let wasm_layout = WasmLayout::new(layout);
let mut wasm_args_tmp: Vec<Symbol>;
let (wasm_args, has_return_val) = match wasm_layout {
@ -433,32 +460,69 @@ impl<'a> WasmBackend<'a> {
}
}
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
match lit {
Literal::Bool(x) => self.code_builder.i32_const(*x as i32),
Literal::Byte(x) => self.code_builder.i32_const(*x as i32),
Literal::Int(x) => match layout {
Layout::Builtin(Builtin::Int64) => self.code_builder.i64_const(*x as i64),
Layout::Builtin(
Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Usize,
) => self.code_builder.i32_const(*x as i32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
fn load_literal(&mut self, lit: &Literal<'a>, storage: &StoredValue) -> Result<(), String> {
let not_supported_error = || Err(format!("Literal value {:?} is not yet implemented", lit));
match storage {
StoredValue::VirtualMachineStack { value_type, .. } => {
match (lit, value_type) {
(Literal::Float(x), ValueType::F64) => self.code_builder.f64_const(*x as f64),
(Literal::Float(x), ValueType::F32) => self.code_builder.f32_const(*x as f32),
(Literal::Int(x), ValueType::I64) => self.code_builder.i64_const(*x as i64),
(Literal::Int(x), ValueType::I32) => self.code_builder.i32_const(*x as i32),
(Literal::Bool(x), ValueType::I32) => self.code_builder.i32_const(*x as i32),
(Literal::Byte(x), ValueType::I32) => self.code_builder.i32_const(*x as i32),
_ => {
return not_supported_error();
}
};
}
StoredValue::StackMemory { location, .. } => match lit {
Literal::Str(s) => {
// Load the stack memory address where we want to write
let (local_id, offset) =
location.local_and_offset(self.storage.stack_frame_pointer);
self.code_builder.get_local(local_id);
self.code_builder.i32_const(offset as i32);
self.code_builder.i32_add();
// For either small or regular strings, we write 8 bytes to stack memory
let mut stack_mem_bytes = [0; 8];
let len = s.len();
if len < 8 {
// Small string payload
stack_mem_bytes[0..len].clone_from_slice(s.as_bytes());
// Small string flag and length
stack_mem_bytes[7] = 0x80 | (len as u8);
} else {
// Store the bytes in the data section, to be initialised on module load.
// Point there instead of to the heap
let literal_bytes: &mut Vec<'a, u8> =
&mut self.module.data.segments[0].init;
literal_bytes.extend_from_slice(s.as_bytes());
// Calculate bytes for elements and length
let len32 = len as u32;
let elements_addr = self.next_literal_addr;
self.next_literal_addr += len32;
stack_mem_bytes[0..3].clone_from_slice(&elements_addr.to_le_bytes());
stack_mem_bytes[4..8].clone_from_slice(&len32.to_le_bytes());
};
// Since we have 64 bits of data, we can squeeze them into one instruction
self.code_builder
.i64_const(i64::from_le_bytes(stack_mem_bytes));
self.code_builder.i64_store(Align::Bytes4, offset);
}
_ => {
return not_supported_error();
}
},
Literal::Float(x) => match layout {
Layout::Builtin(Builtin::Float64) => self.code_builder.f64_const(*x as f64),
Layout::Builtin(Builtin::Float32) => self.code_builder.f32_const(*x as f32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
},
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
_ => {
return not_supported_error();
}
};
Ok(())

View file

@ -1,6 +1,6 @@
use roc_mono::layout::{Layout, UnionLayout};
use crate::{code_builder::ValueType, PTR_SIZE, PTR_TYPE};
use crate::{wasm_module::ValueType, PTR_SIZE, PTR_TYPE};
// See README for background information on Wasm locals, memory and function calls
#[derive(Debug, Clone)]
@ -71,11 +71,7 @@ impl WasmLayout {
}
}
#[allow(dead_code)]
pub fn stack_memory(&self) -> u32 {
match self {
Self::StackMemory { size, .. } => *size,
_ => 0,
}
pub fn is_stack_memory(&self) -> bool {
matches!(self, Self::StackMemory { .. })
}
}

View file

@ -1,27 +1,21 @@
mod backend;
pub mod code_builder;
pub mod from_wasm32_memory;
mod layout;
pub mod module_builder;
pub mod opcodes;
pub mod serialize;
mod storage;
pub mod wasm_module;
use bumpalo::{self, collections::Vec, Bump};
use parity_wasm::builder;
use parity_wasm::elements::{Instruction, Internal, Module, Section};
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds;
use crate::backend::WasmBackend;
use crate::code_builder::{Align, CodeBuilder, ValueType};
use crate::module_builder::{
LinkingSection, LinkingSubSection, RelocationSection, SectionId, SymInfo,
use crate::wasm_module::{
Align, CodeBuilder, Export, ExportType, LinkingSubSection, LocalId, SymInfo, ValueType,
WasmModule,
};
use crate::serialize::{SerialBuffer, Serialize};
const PTR_SIZE: u32 = 4;
const PTR_TYPE: ValueType = ValueType::I32;
@ -29,13 +23,6 @@ const PTR_TYPE: ValueType = ValueType::I32;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
/// Code section ID from spec
/// https://webassembly.github.io/spec/core/binary/modules.html#sections
pub const CODE_SECTION_ID: u8 = 10;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocalId(pub u32);
pub struct Env<'a> {
pub arena: &'a Bump,
pub interns: Interns,
@ -46,21 +33,19 @@ pub fn build_module<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<std::vec::Vec<u8>, String> {
let (builder, code_section_bytes) = build_module_help(env, procedures)?;
let mut module = builder.build();
replace_code_section(&mut module, code_section_bytes);
module
.into_bytes()
.map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) })
let mut wasm_module = build_module_help(env, procedures)?;
let mut buffer = std::vec::Vec::with_capacity(4096);
wasm_module.serialize(&mut buffer);
Ok(buffer)
}
pub fn build_module_help<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<(builder::ModuleBuilder, std::vec::Vec<u8>), String> {
) -> Result<WasmModule<'a>, String> {
let proc_symbols = Vec::from_iter_in(procedures.keys().map(|(sym, _)| *sym), env.arena);
let mut backend = WasmBackend::new(env, &proc_symbols);
let mut backend = WasmBackend::new(env, proc_symbols);
let mut layout_ids = LayoutIds::default();
let mut symbol_table_entries = Vec::with_capacity_in(procedures.len(), env.arena);
@ -70,97 +55,25 @@ pub fn build_module_help<'a>(
.to_symbol_string(proc.name, &env.interns);
symbol_table_entries.push(SymInfo::for_function(i as u32, proc_name));
let function_index = backend.build_proc(proc, sym)?;
backend.build_proc(proc, sym)?;
if env.exposed_to_host.contains(&sym) {
let fn_name = layout_ids
.get_toplevel(sym, &layout)
.to_symbol_string(sym, &env.interns);
let export = builder::export()
.field(fn_name.as_str())
.with_internal(Internal::Function(function_index))
.build();
backend.module_builder.push_export(export);
}
}
// Update code section length
let inner_length = (backend.code_section_bytes.len() - 5) as u32;
backend
.code_section_bytes
.overwrite_padded_u32(0, inner_length);
// linking metadata section
let mut linking_section_bytes = std::vec::Vec::with_capacity(symbol_table_entries.len() * 20);
let linking_section = LinkingSection {
subsections: bumpalo::vec![in env.arena;
LinkingSubSection::SymbolTable(symbol_table_entries)
],
};
linking_section.serialize(&mut linking_section_bytes);
backend.module_builder = backend.module_builder.with_section(Section::Unparsed {
id: SectionId::Custom as u8,
payload: linking_section_bytes,
backend.module.export.entries.push(Export {
name: fn_name,
ty: ExportType::Func,
index: i as u32,
});
// We always output the code section at the same index relative to other sections, and we need that for relocations.
// TODO: If there's a data section, this will be 6 so we'll need logic for that
// TODO: Build a cleaner solution after we replace parity-wasm with our own module_builder
const CODE_SECTION_INDEX: u32 = 5;
let code_reloc_section = RelocationSection {
name: "reloc.CODE",
target_section_index: CODE_SECTION_INDEX,
entries: &backend.code_relocations,
};
let mut code_reloc_section_bytes = std::vec::Vec::with_capacity(256);
code_reloc_section.serialize(&mut code_reloc_section_bytes);
// Must come after linking section
backend.module_builder = backend.module_builder.with_section(Section::Unparsed {
id: SectionId::Custom as u8,
payload: code_reloc_section_bytes,
});
const MIN_MEMORY_SIZE_KB: u32 = 1024;
const PAGE_SIZE_KB: u32 = 64;
let memory = builder::MemoryBuilder::new()
.with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB)
.build();
backend.module_builder.push_memory(memory);
let memory_export = builder::export()
.field("memory")
.with_internal(Internal::Memory(0))
.build();
backend.module_builder.push_export(memory_export);
let stack_pointer_global = builder::global()
.with_type(parity_wasm::elements::ValueType::I32)
.mutable()
.init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32))
.build();
backend.module_builder.push_global(stack_pointer_global);
Ok((backend.module_builder, backend.code_section_bytes))
}
}
/// Replace parity-wasm's code section with our own handmade one
pub fn replace_code_section(module: &mut Module, code_section_bytes: std::vec::Vec<u8>) {
let sections = module.sections_mut();
let symbol_table = LinkingSubSection::SymbolTable(symbol_table_entries);
backend.module.linking.subsections.push(symbol_table);
let code_section_index = sections
.iter()
.position(|s| matches!(s, Section::Code(_)))
.unwrap();
sections[code_section_index] = Section::Unparsed {
id: SectionId::Code as u8,
payload: code_section_bytes,
};
Ok(backend.module)
}
pub struct CopyMemoryConfig {

View file

@ -4,9 +4,9 @@ use bumpalo::Bump;
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use crate::code_builder::{CodeBuilder, ValueType, VirtualMachineSymbolState};
use crate::layout::WasmLayout;
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE};
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState};
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
pub enum StoredValueKind {
Parameter,
@ -291,7 +291,7 @@ impl<'a> Storage<'a> {
| StoredValue::Local {
value_type, size, ..
} => {
use crate::code_builder::Align::*;
use crate::wasm_module::Align::*;
code_builder.get_local(to_ptr);
self.load_symbols(code_builder, &[from_symbol]);
match (value_type, size) {

View file

@ -1,14 +1,17 @@
use bumpalo::collections::vec::{Drain, Vec};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use core::panic;
use std::fmt::Debug;
use roc_module::symbol::Symbol;
use crate::module_builder::{IndexRelocType, RelocationEntry};
use crate::opcodes::*;
use crate::serialize::SerialBuffer;
use crate::{round_up_to_alignment, LocalId, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
use super::linking::{IndexRelocType, RelocationEntry};
use super::opcodes::*;
use super::serialize::{SerialBuffer, Serialize};
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocalId(pub u32);
/// Wasm value type. (Rust representation matches Wasm encoding)
#[repr(u8)]
@ -20,15 +23,9 @@ pub enum ValueType {
F64 = 0x7c,
}
// This is a bit unfortunate. Will go away if we generate our own Types section.
impl ValueType {
pub fn to_parity_wasm(&self) -> parity_wasm::elements::ValueType {
match self {
Self::I32 => parity_wasm::elements::ValueType::I32,
Self::I64 => parity_wasm::elements::ValueType::I64,
Self::F32 => parity_wasm::elements::ValueType::F32,
Self::F64 => parity_wasm::elements::ValueType::F64,
}
impl Serialize for ValueType {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(*self as u8);
}
}
@ -92,8 +89,8 @@ pub enum VirtualMachineSymbolState {
// An instruction (local.set or local.tee) to be inserted into the function code
#[derive(Debug)]
struct InsertLocation {
insert_at: usize,
struct Insertion {
at: usize,
start: usize,
end: usize,
}
@ -124,7 +121,7 @@ pub struct CodeBuilder<'a> {
insert_bytes: Vec<'a, u8>,
/// Code locations where the insert_bytes should go
insert_locations: Vec<'a, InsertLocation>,
insertions: Vec<'a, Insertion>,
/// Bytes for local variable declarations and stack-frame setup code.
/// We can't write this until we've finished the main code. But it goes
@ -151,7 +148,7 @@ impl<'a> CodeBuilder<'a> {
pub fn new(arena: &'a Bump) -> Self {
CodeBuilder {
code: Vec::with_capacity_in(1024, arena),
insert_locations: Vec::with_capacity_in(32, arena),
insertions: Vec::with_capacity_in(32, arena),
insert_bytes: Vec::with_capacity_in(64, arena),
preamble: Vec::with_capacity_in(32, arena),
inner_length: Vec::with_capacity_in(5, arena),
@ -160,15 +157,6 @@ impl<'a> CodeBuilder<'a> {
}
}
pub fn clear(&mut self) {
self.code.clear();
self.insert_locations.clear();
self.insert_bytes.clear();
self.preamble.clear();
self.inner_length.clear();
self.vm_stack.clear();
}
/**********************************************************
SYMBOLS
@ -220,8 +208,8 @@ impl<'a> CodeBuilder<'a> {
self.insert_bytes.push(opcode);
self.insert_bytes.encode_u32(immediate);
self.insert_locations.push(InsertLocation {
insert_at,
self.insertions.push(Insertion {
at: insert_at,
start,
end: self.insert_bytes.len(),
});
@ -384,52 +372,59 @@ impl<'a> CodeBuilder<'a> {
let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len();
self.inner_length.encode_u32(inner_len as u32);
}
/// Write out all the bytes in the right order
pub fn serialize<T: SerialBuffer>(
&mut self,
code_section_buf: &mut T,
) -> Drain<RelocationEntry> {
code_section_buf.append_slice(&self.inner_length);
code_section_buf.append_slice(&self.preamble);
// Sort insertions. They are not created in order of assignment, but in order of *second* usage.
self.insert_locations.sort_by_key(|loc| loc.insert_at);
self.insertions.sort_by_key(|ins| ins.at);
}
/// Serialize all byte vectors in the right order
/// Also update relocation offsets relative to the base offset (code section body start)
pub fn serialize_with_relocs<T: SerialBuffer>(
&self,
buffer: &mut T,
final_relocs: &mut Vec<'a, RelocationEntry>,
reloc_base_offset: usize,
) {
buffer.append_slice(&self.inner_length);
buffer.append_slice(&self.preamble);
// Do the insertions & update relocation offsets
const CODE_SECTION_BODY_OFFSET: usize = 5;
let mut reloc_index = 0;
let mut code_pos: usize = 0;
for location in self.insert_locations.iter() {
let mut code_pos = 0;
let mut insert_iter = self.insertions.iter();
loop {
let next_insert = insert_iter.next();
let next_pos = match next_insert {
Some(Insertion { at, .. }) => *at,
None => self.code.len(),
};
// Relocation offset needs to be an index into the body of the code section, but
// at this point it is an index into self.code. Need to adjust for all previous functions
// in the code section, and for insertions in the current function.
let section_body_pos = code_section_buf.size() - CODE_SECTION_BODY_OFFSET;
let section_body_pos = buffer.size() - reloc_base_offset;
while reloc_index < self.relocations.len()
&& self.relocations[reloc_index].offset() < location.insert_at as u32
&& self.relocations[reloc_index].offset() < next_pos as u32
{
let offset_ref = self.relocations[reloc_index].offset_mut();
*offset_ref += (section_body_pos - code_pos) as u32;
let mut reloc_clone = self.relocations[reloc_index].clone();
*reloc_clone.offset_mut() += (section_body_pos - code_pos) as u32;
final_relocs.push(reloc_clone);
reloc_index += 1;
}
code_section_buf.append_slice(&self.code[code_pos..location.insert_at]);
code_section_buf.append_slice(&self.insert_bytes[location.start..location.end]);
code_pos = location.insert_at;
buffer.append_slice(&self.code[code_pos..next_pos]);
match next_insert {
Some(Insertion { at, start, end }) => {
buffer.append_slice(&self.insert_bytes[*start..*end]);
code_pos = *at;
}
None => {
break;
}
}
let section_body_pos = code_section_buf.size() - CODE_SECTION_BODY_OFFSET;
while reloc_index < self.relocations.len() {
let offset_ref = self.relocations[reloc_index].offset_mut();
*offset_ref += (section_body_pos - code_pos) as u32;
reloc_index += 1;
}
let len = self.code.len();
code_section_buf.append_slice(&self.code[code_pos..len]);
self.relocations.drain(0..)
}
/**********************************************************

View file

@ -1,73 +1,9 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use crate::code_builder::Align;
use crate::serialize::{SerialBuffer, Serialize};
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum SectionId {
Custom = 0,
Type = 1,
Import = 2,
Function = 3,
Table = 4,
Memory = 5,
Global = 6,
Export = 7,
Start = 8,
Element = 9,
Code = 10,
Data = 11,
DataCount = 12,
}
struct SectionHeaderIndices {
size_index: usize,
body_index: usize,
}
/// Write a section header, returning the position of the encoded length
fn _write_section_header<T: SerialBuffer>(buffer: &mut T, id: SectionId) -> SectionHeaderIndices {
buffer.append_byte(id as u8);
let size_index = buffer.reserve_padded_u32();
let body_index = buffer.size();
SectionHeaderIndices {
size_index,
body_index,
}
}
/// Write a custom section header, returning the position of the encoded length
fn write_custom_section_header<T: SerialBuffer>(
buffer: &mut T,
name: &str,
) -> SectionHeaderIndices {
// buffer.append_byte(SectionId::Custom as u8); // TODO: uncomment when we get rid of parity_wasm
let size_index = buffer.reserve_padded_u32();
let body_index = buffer.size();
name.serialize(buffer);
SectionHeaderIndices {
size_index,
body_index,
}
}
/// Update a section header with its final size, after writing the bytes
fn update_section_size<T: SerialBuffer>(buffer: &mut T, header_indices: SectionHeaderIndices) {
let size = buffer.size() - header_indices.body_index;
buffer.overwrite_padded_u32(header_indices.size_index, size as u32);
}
fn serialize_vector_with_count<'a, SB, S>(buffer: &mut SB, items: &Vec<'a, S>)
where
SB: SerialBuffer,
S: Serialize,
{
buffer.encode_u32(items.len() as u32);
for item in items.iter() {
item.serialize(buffer);
}
}
use super::sections::{update_section_size, write_custom_section_header};
use super::serialize::{SerialBuffer, Serialize};
use super::Align;
/*******************************************************************
*
@ -132,7 +68,7 @@ pub enum OffsetRelocType {
MemoryAddrI64 = 16,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum RelocationEntry {
Index {
type_id: IndexRelocType,
@ -181,7 +117,7 @@ impl Serialize for RelocationEntry {
offset,
symbol_index,
} => {
buffer.append_byte(*type_id as u8);
buffer.append_u8(*type_id as u8);
buffer.encode_u32(*offset);
buffer.encode_u32(*symbol_index);
}
@ -191,7 +127,7 @@ impl Serialize for RelocationEntry {
symbol_index,
addend,
} => {
buffer.append_byte(*type_id as u8);
buffer.append_u8(*type_id as u8);
buffer.encode_u32(*offset);
buffer.encode_u32(*symbol_index);
buffer.encode_i32(*addend);
@ -204,18 +140,30 @@ impl Serialize for RelocationEntry {
pub struct RelocationSection<'a> {
pub name: &'a str,
/// The *index* (not ID!) of the target section in the module
pub target_section_index: u32,
pub entries: &'a Vec<'a, RelocationEntry>,
pub target_section_index: Option<u32>,
pub entries: Vec<'a, RelocationEntry>,
}
impl<'a> RelocationSection<'a> {
pub fn new(arena: &'a Bump, name: &'a str) -> Self {
RelocationSection {
name,
target_section_index: None,
entries: Vec::with_capacity_in(64, arena),
}
}
}
impl<'a> Serialize for RelocationSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if !self.entries.is_empty() {
let header_indices = write_custom_section_header(buffer, self.name);
buffer.encode_u32(self.target_section_index);
serialize_vector_with_count(buffer, self.entries);
buffer.encode_u32(self.target_section_index.unwrap());
self.entries.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
}
/*******************************************************************
*
@ -231,6 +179,7 @@ pub struct LinkingSegment {
pub alignment: Align,
pub flags: u32,
}
impl Serialize for LinkingSegment {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
@ -242,17 +191,16 @@ pub struct LinkingInitFunc {
pub priority: u32,
pub symbol_index: u32, // index in the symbol table, not the function index
}
impl Serialize for LinkingInitFunc {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
//----------------
//
//------------------------------------------------
// Common data
//
//----------------
//------------------------------------------------
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
@ -269,6 +217,7 @@ pub struct ComdatSym {
pub kind: ComdatSymKind,
pub index: u32,
}
impl Serialize for ComdatSym {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
@ -285,17 +234,16 @@ pub struct LinkingComdat<'a> {
flags: u32,
syms: Vec<'a, ComdatSym>,
}
impl<'a> Serialize for LinkingComdat<'a> {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
//----------------
//
//------------------------------------------------
// Symbol table
//
//----------------
//------------------------------------------------
/// Indicating that this is a weak symbol. When
/// linking multiple modules defining the same symbol, all weak definitions are
@ -339,6 +287,7 @@ pub enum WasmObjectSymbol {
Defined { index: u32, name: String },
Imported { index: u32 },
}
impl Serialize for WasmObjectSymbol {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
@ -365,6 +314,7 @@ pub enum DataSymbol {
name: String,
},
}
impl Serialize for DataSymbol {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
@ -405,6 +355,7 @@ pub struct SymInfo {
flags: u32,
info: SymInfoFields,
}
impl SymInfo {
pub fn for_function(wasm_function_index: u32, name: String) -> Self {
let linking_symbol = WasmObjectSymbol::Defined {
@ -420,7 +371,7 @@ impl SymInfo {
impl Serialize for SymInfo {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_byte(match self.info {
buffer.append_u8(match self.info {
SymInfoFields::Function(_) => 0,
SymInfoFields::Data(_) => 1,
SymInfoFields::Global(_) => 2,
@ -442,11 +393,9 @@ impl Serialize for SymInfo {
}
}
//--------------------------------
//
//----------------------------------------------------------------
// Linking subsections
//
//--------------------------------
//----------------------------------------------------------------
pub enum LinkingSubSection<'a> {
/// Extra metadata about the data segments.
@ -459,9 +408,10 @@ pub enum LinkingSubSection<'a> {
/// Specifies extra information about the symbols present in the module.
SymbolTable(Vec<'a, SymInfo>),
}
impl<'a> Serialize for LinkingSubSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_byte(match self {
buffer.append_u8(match self {
Self::SegmentInfo(_) => 5,
Self::InitFuncs(_) => 6,
Self::ComdatInfo(_) => 7,
@ -470,10 +420,10 @@ impl<'a> Serialize for LinkingSubSection<'a> {
let payload_len_index = buffer.reserve_padded_u32();
let payload_start_index = buffer.size();
match self {
Self::SegmentInfo(items) => serialize_vector_with_count(buffer, items),
Self::InitFuncs(items) => serialize_vector_with_count(buffer, items),
Self::ComdatInfo(items) => serialize_vector_with_count(buffer, items),
Self::SymbolTable(items) => serialize_vector_with_count(buffer, items),
Self::SegmentInfo(items) => items.serialize(buffer),
Self::InitFuncs(items) => items.serialize(buffer),
Self::ComdatInfo(items) => items.serialize(buffer),
Self::SymbolTable(items) => items.serialize(buffer),
}
buffer.overwrite_padded_u32(
payload_len_index,
@ -482,15 +432,28 @@ impl<'a> Serialize for LinkingSubSection<'a> {
}
}
//----------------------------------------------------------------
// Linking metadata section
//----------------------------------------------------------------
const LINKING_VERSION: u8 = 2;
pub struct LinkingSection<'a> {
pub subsections: Vec<'a, LinkingSubSection<'a>>,
}
impl<'a> LinkingSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
LinkingSection {
subsections: Vec::with_capacity_in(1, arena),
}
}
}
impl<'a> Serialize for LinkingSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
let header_indices = write_custom_section_header(buffer, "linking");
buffer.append_byte(LINKING_VERSION);
buffer.append_u8(LINKING_VERSION);
for subsection in self.subsections.iter() {
subsection.serialize(buffer);
}

View file

@ -0,0 +1,11 @@
pub mod code_builder;
pub mod linking;
pub mod opcodes;
pub mod sections;
pub mod serialize;
pub use code_builder::{
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
};
pub use linking::{LinkingSubSection, SymInfo};
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};

View file

@ -0,0 +1,675 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use super::linking::{LinkingSection, RelocationEntry, RelocationSection};
use super::opcodes;
use super::serialize::{SerialBuffer, Serialize};
use super::{CodeBuilder, ValueType};
/*******************************************************************
*
* Helpers
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum SectionId {
Custom = 0,
Type = 1,
Import = 2,
Function = 3,
Table = 4,
Memory = 5,
Global = 6,
Export = 7,
Start = 8,
Element = 9,
Code = 10,
Data = 11,
DataCount = 12,
}
pub struct SectionHeaderIndices {
size_index: usize,
body_index: usize,
}
/// Write a section header, returning the position of the encoded length
fn write_section_header<T: SerialBuffer>(buffer: &mut T, id: SectionId) -> SectionHeaderIndices {
buffer.append_u8(id as u8);
let size_index = buffer.reserve_padded_u32();
let body_index = buffer.size();
SectionHeaderIndices {
size_index,
body_index,
}
}
/// Write a custom section header, returning the position of the encoded length
pub fn write_custom_section_header<T: SerialBuffer>(
buffer: &mut T,
name: &str,
) -> SectionHeaderIndices {
buffer.append_u8(SectionId::Custom as u8);
let size_index = buffer.reserve_padded_u32();
let body_index = buffer.size();
name.serialize(buffer);
SectionHeaderIndices {
size_index,
body_index,
}
}
/// Update a section header with its final size, after writing the bytes
pub fn update_section_size<T: SerialBuffer>(buffer: &mut T, header_indices: SectionHeaderIndices) {
let size = buffer.size() - header_indices.body_index;
buffer.overwrite_padded_u32(header_indices.size_index, size as u32);
}
/// Serialize a section that is just a vector of some struct
fn serialize_vector_section<B: SerialBuffer, T: Serialize>(
buffer: &mut B,
section_id: SectionId,
subsections: &[T],
) {
if !subsections.is_empty() {
let header_indices = write_section_header(buffer, section_id);
subsections.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
/*******************************************************************
*
* Type section
* Deduplicated list of function type signatures
*
*******************************************************************/
#[derive(PartialEq, Eq)]
pub struct Signature<'a> {
pub param_types: Vec<'a, ValueType>,
pub ret_type: Option<ValueType>,
}
impl<'a> Serialize for Signature<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(0x60);
self.param_types.serialize(buffer);
self.ret_type.serialize(buffer);
}
}
pub struct TypeSection<'a> {
/// Private. See WasmModule::add_function_signature
signatures: Vec<'a, Signature<'a>>,
}
impl<'a> TypeSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
TypeSection {
signatures: Vec::with_capacity_in(8, arena),
}
}
/// Find a matching signature or insert a new one. Return the index.
fn insert(&mut self, signature: Signature<'a>) -> u32 {
// Using linear search because we need to preserve indices stored in
// the Function section. (Also for practical sizes it's fast)
let maybe_index = self.signatures.iter().position(|s| *s == signature);
match maybe_index {
Some(index) => index as u32,
None => {
let index = self.signatures.len();
self.signatures.push(signature);
index as u32
}
}
}
}
impl<'a> Serialize for TypeSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Type, &self.signatures);
}
}
/*******************************************************************
*
* Import section
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum RefType {
Func = 0x70,
Extern = 0x6f,
}
pub struct TableType {
pub ref_type: RefType,
pub limits: Limits,
}
impl Serialize for TableType {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(self.ref_type as u8);
self.limits.serialize(buffer);
}
}
pub enum ImportDesc {
Func { signature_index: u32 },
Table { ty: TableType },
Mem { limits: Limits },
Global { ty: GlobalType },
}
pub struct Import {
pub module: String,
pub name: String,
pub description: ImportDesc,
}
impl Serialize for Import {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
self.module.serialize(buffer);
self.name.serialize(buffer);
match &self.description {
ImportDesc::Func { signature_index } => {
buffer.append_u8(0);
buffer.encode_u32(*signature_index);
}
ImportDesc::Table { ty } => {
buffer.append_u8(1);
ty.serialize(buffer);
}
ImportDesc::Mem { limits } => {
buffer.append_u8(2);
limits.serialize(buffer);
}
ImportDesc::Global { ty } => {
buffer.append_u8(3);
ty.serialize(buffer);
}
}
}
}
pub struct ImportSection<'a> {
entries: Vec<'a, Import>,
}
impl<'a> ImportSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
ImportSection {
entries: bumpalo::vec![in arena],
}
}
}
impl<'a> Serialize for ImportSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Import, &self.entries);
}
}
/*******************************************************************
*
* Function section
* Maps function indices (Code section) to signature indices (Type section)
*
*******************************************************************/
pub struct FunctionSection<'a> {
/// Private. See WasmModule::add_function_signature
signature_indices: Vec<'a, u32>,
}
impl<'a> FunctionSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
FunctionSection {
signature_indices: Vec::with_capacity_in(8, arena),
}
}
}
impl<'a> Serialize for FunctionSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Function, &self.signature_indices);
}
}
/*******************************************************************
*
* Memory section
*
*******************************************************************/
pub enum Limits {
Min(u32),
MinMax(u32, u32),
}
impl Serialize for Limits {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Self::Min(min) => {
buffer.append_u8(0);
buffer.encode_u32(*min);
}
Self::MinMax(min, max) => {
buffer.append_u8(1);
buffer.encode_u32(*min);
buffer.encode_u32(*max);
}
}
}
}
pub struct MemorySection(Option<Limits>);
impl MemorySection {
pub const PAGE_SIZE: u32 = 64 * 1024;
pub fn new(bytes: u32) -> Self {
if bytes == 0 {
MemorySection(None)
} else {
let pages = (bytes + Self::PAGE_SIZE - 1) / Self::PAGE_SIZE;
MemorySection(Some(Limits::Min(pages)))
}
}
pub fn min_size(&self) -> Option<u32> {
match self {
MemorySection(Some(Limits::Min(min))) | MemorySection(Some(Limits::MinMax(min, _))) => {
Some(min * Self::PAGE_SIZE)
}
MemorySection(None) => None,
}
}
}
impl Serialize for MemorySection {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if let Some(limits) = &self.0 {
let header_indices = write_section_header(buffer, SectionId::Memory);
buffer.append_u8(1);
limits.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
}
/*******************************************************************
*
* Global section
*
*******************************************************************/
pub struct GlobalType {
pub value_type: ValueType,
pub is_mutable: bool,
}
impl Serialize for GlobalType {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(self.value_type as u8);
buffer.append_u8(self.is_mutable as u8);
}
}
/// Constant expression for initialising globals or data segments
/// Note: This is restricted for simplicity, but the spec allows arbitrary constant expressions
pub enum ConstExpr {
I32(i32),
I64(i64),
F32(f32),
F64(f64),
}
impl Serialize for ConstExpr {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
ConstExpr::I32(x) => {
buffer.append_u8(opcodes::I32CONST);
buffer.encode_i32(*x);
}
ConstExpr::I64(x) => {
buffer.append_u8(opcodes::I64CONST);
buffer.encode_i64(*x);
}
ConstExpr::F32(x) => {
buffer.append_u8(opcodes::F32CONST);
buffer.encode_f32(*x);
}
ConstExpr::F64(x) => {
buffer.append_u8(opcodes::F64CONST);
buffer.encode_f64(*x);
}
}
buffer.append_u8(opcodes::END);
}
}
pub struct Global {
/// Type and mutability of the global
pub ty: GlobalType,
/// Initial value of the global.
pub init: ConstExpr,
}
impl Serialize for Global {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
self.ty.serialize(buffer);
self.init.serialize(buffer);
}
}
pub struct GlobalSection<'a> {
pub entries: Vec<'a, Global>,
}
impl<'a> GlobalSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
GlobalSection {
entries: Vec::with_capacity_in(1, arena),
}
}
}
impl<'a> Serialize for GlobalSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Global, &self.entries);
}
}
/*******************************************************************
*
* Export section
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ExportType {
Func = 0,
Table = 1,
Mem = 2,
Global = 3,
}
pub struct Export {
pub name: String,
pub ty: ExportType,
pub index: u32,
}
impl Serialize for Export {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
self.name.serialize(buffer);
buffer.append_u8(self.ty as u8);
buffer.encode_u32(self.index);
}
}
pub struct ExportSection<'a> {
pub entries: Vec<'a, Export>,
}
impl<'a> ExportSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
ExportSection {
entries: bumpalo::vec![in arena],
}
}
}
impl<'a> Serialize for ExportSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Export, &self.entries);
}
}
/*******************************************************************
*
* Code section (see also code_builder.rs)
*
*******************************************************************/
#[derive(Debug)]
pub struct CodeSection<'a> {
pub code_builders: Vec<'a, CodeBuilder<'a>>,
}
impl<'a> CodeSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
CodeSection {
code_builders: Vec::with_capacity_in(8, arena),
}
}
/// Serialize the code builders for all functions, and get code relocations with final offsets
pub fn serialize_with_relocs<T: SerialBuffer>(
&self,
buffer: &mut T,
relocations: &mut Vec<'a, RelocationEntry>,
) {
let header_indices = write_section_header(buffer, SectionId::Code);
buffer.encode_u32(self.code_builders.len() as u32);
for code_builder in self.code_builders.iter() {
code_builder.serialize_with_relocs(buffer, relocations, header_indices.body_index);
}
update_section_size(buffer, header_indices);
}
}
/*******************************************************************
*
* Data section
*
*******************************************************************/
pub enum DataMode {
/// A data segment that auto-loads into memory on instantiation
Active { offset: ConstExpr },
/// A data segment that can be loaded with the `memory.init` instruction
Passive,
}
pub struct DataSegment<'a> {
pub mode: DataMode,
pub init: Vec<'a, u8>,
}
impl Serialize for DataSegment<'_> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match &self.mode {
DataMode::Active { offset } => {
buffer.append_u8(0);
offset.serialize(buffer);
}
DataMode::Passive => {
buffer.append_u8(1);
}
}
self.init.serialize(buffer);
}
}
pub struct DataSection<'a> {
pub segments: Vec<'a, DataSegment<'a>>,
}
impl<'a> DataSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
DataSection {
segments: Vec::with_capacity_in(1, arena),
}
}
fn is_empty(&self) -> bool {
self.segments.is_empty() || self.segments.iter().all(|seg| seg.init.is_empty())
}
}
impl Serialize for DataSection<'_> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if !self.is_empty() {
serialize_vector_section(buffer, SectionId::Data, &self.segments);
}
}
}
/*******************************************************************
*
* Data Count section
*
* Pre-declares the number of segments in the Data section.
* This helps the runtime to validate the module in a single pass.
* The order of sections is DataCount -> Code -> Data
*
*******************************************************************/
struct DataCountSection {
count: u32,
}
impl DataCountSection {
fn new(data_section: &DataSection<'_>) -> Self {
let count = data_section
.segments
.iter()
.filter(|seg| !seg.init.is_empty())
.count() as u32;
DataCountSection { count }
}
}
impl Serialize for DataCountSection {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if self.count > 0 {
let header_indices = write_section_header(buffer, SectionId::DataCount);
buffer.encode_u32(self.count);
update_section_size(buffer, header_indices);
}
}
}
/*******************************************************************
*
* Module
*
* https://webassembly.github.io/spec/core/binary/modules.html
*
*******************************************************************/
/// Helper struct to count non-empty sections.
/// Needed to generate linking data, which refers to target sections by index.
struct SectionCounter {
buffer_size: usize,
section_index: u32,
}
impl SectionCounter {
/// Update the section counter if buffer size increased since last call
#[inline]
fn update<SB: SerialBuffer>(&mut self, buffer: &mut SB) {
let new_size = buffer.size();
if new_size > self.buffer_size {
self.section_index += 1;
self.buffer_size = new_size;
}
}
#[inline]
fn serialize_and_count<SB: SerialBuffer, S: Serialize>(
&mut self,
buffer: &mut SB,
section: &S,
) {
section.serialize(buffer);
self.update(buffer);
}
}
pub struct WasmModule<'a> {
pub types: TypeSection<'a>,
pub import: ImportSection<'a>,
pub function: FunctionSection<'a>,
/// Dummy placeholder for tables (used for function pointers and host references)
pub table: (),
pub memory: MemorySection,
pub global: GlobalSection<'a>,
pub export: ExportSection<'a>,
/// Dummy placeholder for start function. In Roc, this would be part of the platform.
pub start: (),
/// Dummy placeholder for table elements. Roc does not use tables.
pub element: (),
pub code: CodeSection<'a>,
pub data: DataSection<'a>,
pub linking: LinkingSection<'a>,
pub reloc_code: RelocationSection<'a>,
pub reloc_data: RelocationSection<'a>,
}
impl<'a> WasmModule<'a> {
pub const WASM_VERSION: u32 = 1;
/// Create entries in the Type and Function sections for a function signature
pub fn add_function_signature(&mut self, signature: Signature<'a>) {
let index = self.types.insert(signature);
self.function.signature_indices.push(index);
}
#[allow(clippy::unit_arg)]
pub fn serialize<T: SerialBuffer>(&mut self, buffer: &mut T) {
buffer.append_u8(0);
buffer.append_slice("asm".as_bytes());
buffer.write_unencoded_u32(Self::WASM_VERSION);
// Keep track of (non-empty) section indices for linking
let mut counter = SectionCounter {
buffer_size: buffer.size(),
section_index: 0,
};
counter.serialize_and_count(buffer, &self.types);
counter.serialize_and_count(buffer, &self.import);
counter.serialize_and_count(buffer, &self.function);
counter.serialize_and_count(buffer, &self.table);
counter.serialize_and_count(buffer, &self.memory);
counter.serialize_and_count(buffer, &self.global);
counter.serialize_and_count(buffer, &self.export);
counter.serialize_and_count(buffer, &self.start);
counter.serialize_and_count(buffer, &self.element);
// Data Count section forward-declares the size of the Data section
// so that Code section can be validated in one pass
let data_count_section = DataCountSection::new(&self.data);
counter.serialize_and_count(buffer, &data_count_section);
// Code section mutates its linker relocation data during serialization
let code_section_index = counter.section_index;
self.code
.serialize_with_relocs(buffer, &mut self.reloc_code.entries);
counter.update(buffer);
// Data section is the last one before linking, so we can stop counting
let data_section_index = counter.section_index;
self.data.serialize(buffer);
self.linking.serialize(buffer);
self.reloc_code.target_section_index = Some(code_section_index);
self.reloc_code.serialize(buffer);
self.reloc_data.target_section_index = Some(data_section_index);
self.reloc_data.serialize(buffer);
}
}

View file

@ -1,5 +1,67 @@
use std::fmt::Debug;
use bumpalo::collections::vec::Vec;
pub trait Serialize {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
}
impl Serialize for str {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(self.len() as u32);
buffer.append_slice(self.as_bytes());
}
}
impl Serialize for u8 {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(*self);
}
}
impl Serialize for u32 {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(*self);
}
}
// Unit is used as a placeholder in parts of the Wasm spec we don't use yet
impl Serialize for () {
#[inline(always)]
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {}
}
impl<S: Serialize> Serialize for [S] {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(self.len() as u32);
for item in self.iter() {
item.serialize(buffer);
}
}
}
impl Serialize for Vec<'_, u8> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(self.len() as u32);
buffer.append_slice(self);
}
}
impl<S: Serialize> Serialize for Option<S> {
/// serialize Option as a vector of length 1 or 0
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Some(x) => {
buffer.append_u8(1);
x.serialize(buffer);
}
None => {
buffer.append_u8(0);
}
}
}
}
/// Write an unsigned integer into the provided buffer in LEB-128 format, returning byte length
///
/// All integers in Wasm are variable-length encoded, which saves space for small values.
@ -10,10 +72,10 @@ macro_rules! encode_uleb128 {
let mut x = value;
let start_len = self.size();
while x >= 0x80 {
self.append_byte(0x80 | ((x & 0x7f) as u8));
self.append_u8(0x80 | ((x & 0x7f) as u8));
x >>= 7;
}
self.append_byte(x as u8);
self.append_u8(x as u8);
self.size() - start_len
}
};
@ -30,10 +92,10 @@ macro_rules! encode_sleb128 {
x >>= 7;
let byte_is_negative = (byte & 0x40) != 0;
if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) {
self.append_byte(byte);
self.append_u8(byte);
break;
}
self.append_byte(byte | 0x80);
self.append_u8(byte | 0x80);
}
self.size() - start_len
}
@ -47,7 +109,7 @@ macro_rules! write_unencoded {
let mut x = value;
let size = std::mem::size_of::<$ty>();
for _ in 0..size {
self.append_byte((x & 0xff) as u8);
self.append_u8((x & 0xff) as u8);
x >>= 8;
}
}
@ -61,17 +123,19 @@ macro_rules! encode_padded_sleb128 {
let mut x = value;
let size = (std::mem::size_of::<$ty>() / 4) * 5;
for _ in 0..(size - 1) {
self.append_byte(0x80 | (x & 0x7f) as u8);
self.append_u8(0x80 | (x & 0x7f) as u8);
x >>= 7;
}
self.append_byte((x & 0x7f) as u8);
self.append_u8((x & 0x7f) as u8);
}
};
}
pub trait SerialBuffer {
fn append_byte(&mut self, b: u8);
pub trait SerialBuffer: Debug {
fn append_u8(&mut self, b: u8);
fn overwrite_u8(&mut self, index: usize, b: u8);
fn append_slice(&mut self, b: &[u8]);
fn size(&self) -> usize;
encode_uleb128!(encode_u32, u32);
@ -98,17 +162,6 @@ pub trait SerialBuffer {
encode_padded_sleb128!(encode_padded_i64, i64);
}
pub trait Serialize {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
}
impl Serialize for str {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(self.len() as u32);
buffer.append_slice(self.as_bytes());
}
}
fn overwrite_padded_u32_help(buffer: &mut [u8], value: u32) {
let mut x = value;
for byte in buffer.iter_mut().take(4) {
@ -119,9 +172,12 @@ fn overwrite_padded_u32_help(buffer: &mut [u8], value: u32) {
}
impl SerialBuffer for std::vec::Vec<u8> {
fn append_byte(&mut self, b: u8) {
fn append_u8(&mut self, b: u8) {
self.push(b);
}
fn overwrite_u8(&mut self, index: usize, b: u8) {
self[index] = b;
}
fn append_slice(&mut self, b: &[u8]) {
self.extend_from_slice(b);
}
@ -146,9 +202,12 @@ impl SerialBuffer for std::vec::Vec<u8> {
}
impl<'a> SerialBuffer for Vec<'a, u8> {
fn append_byte(&mut self, b: u8) {
fn append_u8(&mut self, b: u8) {
self.push(b);
}
fn overwrite_u8(&mut self, index: usize, b: u8) {
self[index] = b;
}
fn append_slice(&mut self, b: &[u8]) {
self.extend_from_slice(b);
}

View file

@ -21,15 +21,13 @@ roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.6.1", features = ["collections"] }
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
crossbeam = "0.7"
num_cpus = "1"
bumpalo = { version = "3.8.0", features = ["collections"] }
parking_lot = { version = "0.11.2", features = ["deadlock_detection"] }
crossbeam = "0.8.1"
num_cpus = "1.13.0"
[dev-dependencies]
tempfile = "3.1.0"
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tempfile = "3.2.0"
pretty_assertions = "1.0.0"
maplit = "1.0.2"
indoc = "1.0.3"

View file

@ -1,7 +1,7 @@
use roc_can::annotation::IntroducedVariables;
use roc_can::def::{Declaration, Def};
use roc_can::env::Env;
use roc_can::expr::{Expr, Recursive};
use roc_can::expr::{ClosureData, Expr, Recursive};
use roc_can::pattern::Pattern;
use roc_can::scope::Scope;
use roc_collections::all::{MutSet, SendMap};
@ -117,7 +117,7 @@ fn build_effect_always(
let body = Expr::Var(value_symbol);
Expr::Closure {
Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -127,7 +127,7 @@ fn build_effect_always(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
}
})
};
// \value -> @Effect \{} -> value
@ -146,7 +146,7 @@ fn build_effect_always(
)];
let function_var = var_store.fresh();
let closure = Expr::Closure {
let closure = Expr::Closure(ClosureData {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -156,7 +156,7 @@ fn build_effect_always(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
};
});
(function_var, closure)
};
@ -295,7 +295,7 @@ fn build_effect_map(
Located::at_zero(empty_record_pattern(var_store)),
)];
Expr::Closure {
Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -308,7 +308,7 @@ fn build_effect_map(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(mapper_call)),
}
})
};
let arguments = vec![
@ -339,7 +339,7 @@ fn build_effect_map(
};
let function_var = var_store.fresh();
let map_closure = Expr::Closure {
let map_closure = Expr::Closure(ClosureData {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -349,7 +349,7 @@ fn build_effect_map(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
};
});
let mut introduced_variables = IntroducedVariables::default();
@ -509,7 +509,7 @@ fn build_effect_after(
];
let function_var = var_store.fresh();
let after_closure = Expr::Closure {
let after_closure = Expr::Closure(ClosureData {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -519,7 +519,7 @@ fn build_effect_after(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(to_effect_call)),
};
});
let mut introduced_variables = IntroducedVariables::default();
@ -653,7 +653,7 @@ pub fn build_host_exposed_def(
.unwrap()
};
let effect_closure = Expr::Closure {
let effect_closure = Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -666,7 +666,7 @@ pub fn build_host_exposed_def(
Located::at_zero(empty_record_pattern(var_store)),
)],
loc_body: Box::new(Located::at_zero(low_level_call)),
};
});
let body = Expr::Tag {
variant_var: var_store.fresh(),
@ -675,7 +675,7 @@ pub fn build_host_exposed_def(
arguments: vec![(var_store.fresh(), Located::at_zero(effect_closure))],
};
Expr::Closure {
Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -685,7 +685,7 @@ pub fn build_host_exposed_def(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
}
})
}
_ => {
// not a function
@ -717,7 +717,7 @@ pub fn build_host_exposed_def(
destructs: vec![],
};
let effect_closure = Expr::Closure {
let effect_closure = Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -727,7 +727,7 @@ pub fn build_host_exposed_def(
recursive: Recursive::NotRecursive,
arguments: vec![(var_store.fresh(), Located::at_zero(empty_record_pattern))],
loc_body: Box::new(Located::at_zero(low_level_call)),
};
});
Expr::Tag {
variant_var: var_store.fresh(),

View file

@ -3958,7 +3958,10 @@ fn make_specializations<'a>(
let mut procs = Procs::new_in(arena);
procs.partial_procs = procs_base.partial_procs;
for (symbol, partial_proc) in procs_base.partial_procs.into_iter() {
procs.partial_procs.insert(symbol, partial_proc);
}
procs.module_thunks = procs_base.module_thunks;
procs.runtime_errors = procs_base.runtime_errors;
procs.imported_module_thunks = procs_base.imported_module_thunks;
@ -4126,6 +4129,7 @@ fn add_def_to_module<'a>(
exposed_to_host: &MutMap<Symbol, Variable>,
is_recursive: bool,
) {
use roc_can::expr::ClosureData;
use roc_can::expr::Expr::*;
use roc_can::pattern::Pattern::*;
@ -4134,14 +4138,14 @@ fn add_def_to_module<'a>(
let is_exposed = exposed_to_host.contains_key(&symbol);
match def.loc_expr.value {
Closure {
Closure(ClosureData {
function_type: annotation,
return_type: ret_var,
arguments: loc_args,
loc_body,
captured_symbols,
..
} => {
}) => {
// this is a top-level definition, it should not capture anything
debug_assert!(captured_symbols.is_empty());

View file

@ -9,12 +9,7 @@ license = "UPL-1.0"
roc_region = { path = "../region" }
roc_ident = { path = "../ident" }
roc_collections = { path = "../collections" }
bumpalo = { version = "3.6.1", features = ["collections"] }
lazy_static = "1.4"
bumpalo = { version = "3.8.0", features = ["collections"] }
lazy_static = "1.4.0"
static_assertions = "1.1.0"
snafu = { version = "0.6", features = ["backtraces"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
snafu = { version = "0.6.10", features = ["backtraces"] }

View file

@ -1061,6 +1061,7 @@ define_builtins! {
38 LIST_MAX: "max"
39 LIST_MAX_GT: "#maxGt"
40 LIST_MAP4: "map4"
41 LIST_DROP_FIRST: "dropFirst"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -17,19 +17,13 @@ roc_std = { path = "../../roc_std" }
roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" }
morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.6.1", features = ["collections"] }
bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
ven_ena = { path = "../../vendor/ena" }
ven_graph = { path = "../../vendor/pathfinding" }
linked-hash-map = "0.5.4"
[dev-dependencies]
roc_load= { path = "../load" }
roc_builtins = { path = "../builtins" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -9,6 +9,7 @@ use crate::layout::{
use bumpalo::collections::Vec;
use bumpalo::Bump;
use hashbrown::hash_map::Entry;
use roc_can::expr::ClosureData;
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
@ -71,6 +72,61 @@ pub struct EntryPoint<'a> {
pub layout: ProcLayout<'a>,
}
#[derive(Clone, Copy, Debug)]
pub struct PartialProcId(usize);
#[derive(Clone, Debug, PartialEq)]
pub struct PartialProcs<'a> {
/// maps a function name (symbol) to an index
symbols: Vec<'a, Symbol>,
partial_procs: Vec<'a, PartialProc<'a>>,
}
impl<'a> PartialProcs<'a> {
fn new_in(arena: &'a Bump) -> Self {
Self {
symbols: Vec::new_in(arena),
partial_procs: Vec::new_in(arena),
}
}
fn contains_key(&self, symbol: Symbol) -> bool {
self.symbol_to_id(symbol).is_some()
}
fn symbol_to_id(&self, symbol: Symbol) -> Option<PartialProcId> {
self.symbols
.iter()
.position(|s| *s == symbol)
.map(PartialProcId)
}
fn get_symbol(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
let id = self.symbol_to_id(symbol)?;
Some(self.get_id(id))
}
fn get_id(&self, id: PartialProcId) -> &PartialProc<'a> {
&self.partial_procs[id.0]
}
pub fn insert(&mut self, symbol: Symbol, partial_proc: PartialProc<'a>) -> PartialProcId {
debug_assert!(
!self.contains_key(symbol),
"The {:?} is inserted as a partial proc twice: that's a bug!",
symbol,
);
let id = PartialProcId(self.symbols.len());
self.symbols.push(symbol);
self.partial_procs.push(partial_proc);
id
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PartialProc<'a> {
pub annotation: Variable,
@ -129,7 +185,7 @@ impl<'a> PartialProc<'a> {
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CapturedSymbols<'a> {
None,
Captured(&'a [(Symbol, Variable)]),
@ -418,7 +474,7 @@ impl<'a> ExternalSpecializations<'a> {
#[derive(Clone, Debug)]
pub struct Procs<'a> {
pub partial_procs: BumpMap<Symbol, PartialProc<'a>>,
pub partial_procs: PartialProcs<'a>,
pub imported_module_thunks: &'a [Symbol],
pub module_thunks: &'a [Symbol],
pub pending_specializations:
@ -432,7 +488,7 @@ pub struct Procs<'a> {
impl<'a> Procs<'a> {
pub fn new_in(arena: &'a Bump) -> Self {
Self {
partial_procs: BumpMap::new_in(arena),
partial_procs: PartialProcs::new_in(arena),
imported_module_thunks: &[],
module_thunks: &[],
pending_specializations: Some(BumpMap::new_in(arena)),
@ -459,6 +515,10 @@ impl<'a> Procs<'a> {
self.module_thunks.iter().any(|x| *x == symbol)
}
fn get_partial_proc<'b>(&'b self, symbol: Symbol) -> Option<&'b PartialProc<'a>> {
self.partial_procs.get_symbol(symbol)
}
pub fn get_specialized_procs_without_rc(
self,
env: &mut Env<'a, '_>,
@ -535,9 +595,9 @@ impl<'a> Procs<'a> {
// register the pending specialization, so this gets code genned later
add_pending(pending_specializations, symbol, layout, pending);
match self.partial_procs.entry(symbol) {
Entry::Occupied(occupied) => {
let existing = occupied.get();
match self.partial_procs.symbol_to_id(symbol) {
Some(occupied) => {
let existing = self.partial_procs.get_id(occupied);
// if we're adding the same partial proc twice, they must be the actual same!
//
// NOTE we can't skip extra work! we still need to make the specialization for this
@ -549,7 +609,7 @@ impl<'a> Procs<'a> {
// the partial proc is already in there, do nothing
}
Entry::Vacant(vacant) => {
None => {
let pattern_symbols = pattern_symbols.into_bump_slice();
let partial_proc = PartialProc {
@ -560,7 +620,7 @@ impl<'a> Procs<'a> {
is_self_recursive,
};
vacant.insert(partial_proc);
self.partial_procs.insert(symbol, partial_proc);
}
}
}
@ -572,8 +632,10 @@ impl<'a> Procs<'a> {
let outside_layout = layout;
let partial_proc;
if let Some(existing) = self.partial_procs.get(&symbol) {
let partial_proc_id = if let Some(partial_proc_id) =
self.partial_procs.symbol_to_id(symbol)
{
let existing = self.partial_procs.get_id(partial_proc_id);
// if we're adding the same partial proc twice, they must be the actual same!
//
// NOTE we can't skip extra work! we still need to make the specialization for this
@ -583,21 +645,29 @@ impl<'a> Procs<'a> {
debug_assert_eq!(captured_symbols, existing.captured_symbols);
debug_assert_eq!(is_self_recursive, existing.is_self_recursive);
partial_proc = existing.clone();
partial_proc_id
} else {
let pattern_symbols = pattern_symbols.into_bump_slice();
partial_proc = PartialProc {
let partial_proc = PartialProc {
annotation,
pattern_symbols,
captured_symbols,
body: body.value,
is_self_recursive,
};
}
match specialize(env, self, symbol, layout_cache, pending, partial_proc)
{
self.partial_procs.insert(symbol, partial_proc)
};
match specialize(
env,
self,
symbol,
layout_cache,
pending,
partial_proc_id,
) {
Ok((proc, layout)) => {
let top_level = ProcLayout::from_raw(env.arena, layout);
@ -662,9 +732,8 @@ impl<'a> Procs<'a> {
None => {
let symbol = name;
// TODO should pending_procs hold a Rc<Proc>?
let partial_proc = match self.partial_procs.get(&symbol) {
Some(p) => p.clone(),
let partial_proc_id = match self.partial_procs.symbol_to_id(symbol) {
Some(p) => p,
None => panic!("no partial_proc for {:?} in module {:?}", symbol, env.home),
};
@ -691,7 +760,7 @@ impl<'a> Procs<'a> {
layout_cache,
fn_var,
Default::default(),
partial_proc,
partial_proc_id,
) {
Ok((proc, _ignore_layout)) => {
// the `layout` is a function pointer, while `_ignore_layout` can be a
@ -1726,14 +1795,14 @@ pub fn specialize_all<'a>(
continue;
}
Entry::Vacant(vacant) => {
match procs.partial_procs.get(&name) {
match procs.partial_procs.symbol_to_id(name) {
Some(v) => {
// Mark this proc as in-progress, so if we're dealing with
// mutually recursive functions, we don't loop forever.
// (We had a bug around this before this system existed!)
vacant.insert(InProgress);
v.clone()
v
}
None => {
// TODO this assumes the specialization is done by another module
@ -1808,8 +1877,8 @@ fn specialize_externals_others_need<'a>(
let name = *symbol;
let partial_proc = match procs.partial_procs.get(&name) {
Some(v) => v.clone(),
let partial_proc_id = match procs.partial_procs.symbol_to_id(name) {
Some(v) => v,
None => {
panic!("Cannot find a partial proc for {:?}", name);
}
@ -1823,7 +1892,7 @@ fn specialize_externals_others_need<'a>(
layout_cache,
solved_type,
BumpMap::new_in(env.arena),
partial_proc,
partial_proc_id,
) {
Ok((proc, layout)) => {
let top_level = ProcLayout::from_raw(env.arena, layout);
@ -1901,32 +1970,28 @@ fn specialize_external<'a>(
layout_cache: &mut LayoutCache<'a>,
fn_var: Variable,
host_exposed_variables: &[(Symbol, Variable)],
partial_proc: PartialProc<'a>,
partial_proc_id: PartialProcId,
) -> Result<Proc<'a>, LayoutProblem> {
let PartialProc {
annotation,
pattern_symbols,
captured_symbols,
body,
is_self_recursive,
} = partial_proc;
let partial_proc = procs.partial_procs.get_id(partial_proc_id);
let captured_symbols = partial_proc.captured_symbols;
// unify the called function with the specialized signature, then specialize the function body
let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.snapshot();
let _unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
let _unified = roc_unify::unify::unify(env.subs, partial_proc.annotation, fn_var);
// This will not hold for programs with type errors
// let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
// debug_assert!(is_valid, "unificaton failure for {:?}", proc_name);
// if this is a closure, add the closure record argument
let pattern_symbols = match captured_symbols {
CapturedSymbols::None => pattern_symbols,
CapturedSymbols::Captured([]) => pattern_symbols,
let pattern_symbols = match partial_proc.captured_symbols {
CapturedSymbols::None => partial_proc.pattern_symbols,
CapturedSymbols::Captured([]) => partial_proc.pattern_symbols,
CapturedSymbols::Captured(_) => {
let mut temp = Vec::from_iter_in(pattern_symbols.iter().copied(), env.arena);
let mut temp =
Vec::from_iter_in(partial_proc.pattern_symbols.iter().copied(), env.arena);
temp.push(Symbol::ARG_CLOSURE);
temp.into_bump_slice()
}
@ -2023,12 +2088,13 @@ fn specialize_external<'a>(
}
};
let recursivity = if is_self_recursive {
let recursivity = if partial_proc.is_self_recursive {
SelfRecursive::SelfRecursive(JoinPointId(env.unique_symbol()))
} else {
SelfRecursive::NotSelfRecursive
};
let body = partial_proc.body.clone();
let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache);
match specialized {
@ -2419,13 +2485,13 @@ struct SpecializeFailure<'a> {
type SpecializeSuccess<'a> = (Proc<'a>, RawFunctionLayout<'a>);
fn specialize<'a>(
fn specialize<'a, 'b>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
procs: &'b mut Procs<'a>,
proc_name: Symbol,
layout_cache: &mut LayoutCache<'a>,
pending: PendingSpecialization,
partial_proc: PartialProc<'a>,
partial_proc_id: PartialProcId,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
let PendingSpecialization {
solved_type,
@ -2440,7 +2506,7 @@ fn specialize<'a>(
layout_cache,
&solved_type,
host_exposed_aliases,
partial_proc,
partial_proc_id,
)
}
@ -2470,7 +2536,7 @@ fn specialize_solved_type<'a>(
layout_cache: &mut LayoutCache<'a>,
solved_type: &SolvedType,
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>,
partial_proc_id: PartialProcId,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
specialize_variable_help(
env,
@ -2479,7 +2545,7 @@ fn specialize_solved_type<'a>(
layout_cache,
|env| introduce_solved_type_to_subs(env, solved_type),
host_exposed_aliases,
partial_proc,
partial_proc_id,
)
}
@ -2490,7 +2556,7 @@ fn specialize_variable<'a>(
layout_cache: &mut LayoutCache<'a>,
fn_var: Variable,
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>,
partial_proc_id: PartialProcId,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>> {
specialize_variable_help(
env,
@ -2499,7 +2565,7 @@ fn specialize_variable<'a>(
layout_cache,
|_| fn_var,
host_exposed_aliases,
partial_proc,
partial_proc_id,
)
}
@ -2510,7 +2576,7 @@ fn specialize_variable_help<'a, F>(
layout_cache: &mut LayoutCache<'a>,
fn_var_thunk: F,
host_exposed_aliases: BumpMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>,
partial_proc_id: PartialProcId,
) -> Result<SpecializeSuccess<'a>, SpecializeFailure<'a>>
where
F: FnOnce(&mut Env<'a, '_>) -> Variable,
@ -2541,7 +2607,8 @@ where
};
// make sure rigid variables in the annotation are converted to flex variables
instantiate_rigids(env.subs, partial_proc.annotation);
let annotation_var = procs.partial_procs.get_id(partial_proc_id).annotation;
instantiate_rigids(env.subs, annotation_var);
let mut host_exposed_variables = Vec::with_capacity_in(host_exposed_aliases.len(), env.arena);
@ -2558,7 +2625,7 @@ where
layout_cache,
fn_var,
&host_exposed_variables,
partial_proc,
partial_proc_id,
);
match specialized {
@ -2633,7 +2700,7 @@ fn specialize_naked_symbol<'a>(
symbol: Symbol,
) -> Stmt<'a> {
if procs.is_module_thunk(symbol) {
let partial_proc = procs.partial_procs.get(&symbol).unwrap();
let partial_proc = procs.get_partial_proc(symbol).unwrap();
let fn_var = partial_proc.annotation;
// This is a top-level declaration, which will code gen to a 0-arity thunk.
@ -2847,42 +2914,9 @@ pub fn with_hole<'a>(
}
}
LetNonRec(def, cont, _) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
if let Closure {
function_type,
return_type,
recursive,
arguments,
loc_body: boxed_body,
captured_symbols,
..
} = def.loc_expr.value
{
// Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here!
let loc_body = *boxed_body;
let is_self_recursive =
!matches!(recursive, roc_can::expr::Recursive::NotRecursive);
// this should be a top-level declaration, and hence have no captured symbols
// if we ever do hit this (and it's not a bug), we should make sure to put the
// captured symbols into a CapturedSymbols and give it to PartialProc::from_named_function
debug_assert!(captured_symbols.is_empty());
let partial_proc = PartialProc::from_named_function(
env,
layout_cache,
function_type,
arguments,
loc_body,
CapturedSymbols::None,
is_self_recursive,
return_type,
);
procs.partial_procs.insert(*symbol, partial_proc);
if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value {
if let Closure(closure_data) = def.loc_expr.value {
register_noncapturing_closure(env, procs, layout_cache, symbol, closure_data);
return with_hole(
env,
@ -2894,9 +2928,6 @@ pub fn with_hole<'a>(
hole,
);
}
}
if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value {
// special-case the form `let x = E in x`
// not doing so will drop the `hole`
match &cont.value {
@ -3024,36 +3055,15 @@ pub fn with_hole<'a>(
// because Roc is strict, only functions can be recursive!
for def in defs.into_iter() {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
if let Closure {
function_type,
return_type,
recursive,
arguments,
loc_body: boxed_body,
..
} = def.loc_expr.value
{
// Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here!
let loc_body = *boxed_body;
let is_self_recursive =
!matches!(recursive, roc_can::expr::Recursive::NotRecursive);
let partial_proc = PartialProc::from_named_function(
if let Closure(closure_data) = def.loc_expr.value {
register_noncapturing_closure(
env,
procs,
layout_cache,
function_type,
arguments,
loc_body,
CapturedSymbols::None,
is_self_recursive,
return_type,
*symbol,
closure_data,
);
procs.partial_procs.insert(*symbol, partial_proc);
continue;
}
}
@ -3749,7 +3759,7 @@ pub fn with_hole<'a>(
}
}
Closure {
Closure(ClosureData {
function_type,
return_type,
name,
@ -3757,7 +3767,7 @@ pub fn with_hole<'a>(
captured_symbols,
loc_body: boxed_body,
..
} => {
}) => {
let loc_body = *boxed_body;
let raw = layout_cache.raw_from_var(env.arena, function_type, env.subs);
@ -3814,11 +3824,11 @@ pub fn with_hole<'a>(
// if it's in there, it's a call by name, otherwise it's a call by pointer
let is_known = |key| {
// a proc in this module, or an imported symbol
procs.partial_procs.contains_key(key) || env.is_imported_symbol(*key)
procs.partial_procs.contains_key(key) || env.is_imported_symbol(key)
};
match loc_expr.value {
roc_can::expr::Expr::Var(proc_name) if is_known(&proc_name) => {
roc_can::expr::Expr::Var(proc_name) if is_known(proc_name) => {
// a call by a known name
call_by_name(
env,
@ -4633,6 +4643,131 @@ fn sorted_field_symbols<'a>(
field_symbols_temp
}
/// Insert a closure that does capture symbols (because it is top-level) to the list of partial procs
fn register_noncapturing_closure<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
closure_name: Symbol,
closure_data: ClosureData,
) {
let ClosureData {
function_type,
return_type,
recursive,
arguments,
loc_body: boxed_body,
captured_symbols,
..
} = closure_data;
// Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here!
let loc_body = *boxed_body;
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
// this should be a top-level declaration, and hence have no captured symbols
// if we ever do hit this (and it's not a bug), we should make sure to put the
// captured symbols into a CapturedSymbols and give it to PartialProc::from_named_function
debug_assert!(captured_symbols.is_empty());
let partial_proc = PartialProc::from_named_function(
env,
layout_cache,
function_type,
arguments,
loc_body,
CapturedSymbols::None,
is_self_recursive,
return_type,
);
procs.partial_procs.insert(closure_name, partial_proc);
}
/// Insert a closure that may capture symbols to the list of partial procs
fn register_capturing_closure<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
closure_name: Symbol,
closure_data: ClosureData,
) {
// the function surrounding the closure definition may be specialized multiple times,
// hence in theory this partial proc may be added multiple times. That would be wasteful
// so we check whether this partial proc is already there.
//
// (the `gen_primitives::task_always_twice` test has this behavior)
if !procs.partial_procs.contains_key(closure_name) {
let ClosureData {
function_type,
return_type,
closure_type,
closure_ext_var,
recursive,
arguments,
loc_body: boxed_body,
captured_symbols,
..
} = closure_data;
let loc_body = *boxed_body;
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
// does this function capture any local values?
let function_layout = layout_cache.raw_from_var(env.arena, function_type, env.subs);
let captured_symbols = match function_layout {
Ok(RawFunctionLayout::Function(_, lambda_set, _)) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => {
// top-level thunks cannot capture any variables
debug_assert!(
captured_symbols.is_empty(),
"{:?} with layout {:?} {:?} {:?}",
&captured_symbols,
function_layout,
env.subs,
(function_type, closure_type, closure_ext_var),
);
CapturedSymbols::None
}
Err(_) => {
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
if captured_symbols.is_empty() {
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
};
let partial_proc = PartialProc::from_named_function(
env,
layout_cache,
function_type,
arguments,
loc_body,
captured_symbols,
is_self_recursive,
return_type,
);
procs.partial_procs.insert(closure_name, partial_proc);
}
}
pub fn from_can<'a>(
env: &mut Env<'a, '_>,
variable: Variable,
@ -4752,35 +4887,15 @@ pub fn from_can<'a>(
// Now that we know for sure it's a closure, get an owned
// version of these variant args so we can use them properly.
match def.loc_expr.value {
Closure {
function_type,
return_type,
recursive,
arguments,
loc_body: boxed_body,
..
} => {
// Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here!
let loc_body = *boxed_body;
let is_self_recursive =
!matches!(recursive, roc_can::expr::Recursive::NotRecursive);
let partial_proc = PartialProc::from_named_function(
Closure(closure_data) => {
register_capturing_closure(
env,
procs,
layout_cache,
function_type,
arguments,
loc_body,
CapturedSymbols::None,
is_self_recursive,
return_type,
*symbol,
closure_data,
);
procs.partial_procs.insert(*symbol, partial_proc);
continue;
}
_ => unreachable!("recursive value is not a function"),
@ -4793,90 +4908,12 @@ pub fn from_can<'a>(
}
LetNonRec(def, cont, outer_annotation) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
if let Closure { .. } = &def.loc_expr.value {
// Now that we know for sure it's a closure, get an owned
// version of these variant args so we can use them properly.
match def.loc_expr.value {
Closure {
function_type,
return_type,
closure_type,
closure_ext_var,
recursive,
arguments,
loc_body: boxed_body,
captured_symbols,
..
} => {
// Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here!
let loc_body = *boxed_body;
let is_self_recursive =
!matches!(recursive, roc_can::expr::Recursive::NotRecursive);
// does this function capture any local values?
let function_layout =
layout_cache.raw_from_var(env.arena, function_type, env.subs);
let captured_symbols = match function_layout {
Ok(RawFunctionLayout::Function(_, lambda_set, _)) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation()
{
CapturedSymbols::None
} else {
let mut temp =
Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => {
// top-level thunks cannot capture any variables
debug_assert!(
captured_symbols.is_empty(),
"{:?} with layout {:?} {:?} {:?}",
&captured_symbols,
function_layout,
env.subs,
(function_type, closure_type, closure_ext_var),
);
CapturedSymbols::None
}
Err(_) => {
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
if captured_symbols.is_empty() {
CapturedSymbols::None
} else {
let mut temp =
Vec::from_iter_in(captured_symbols, env.arena);
temp.sort();
CapturedSymbols::Captured(temp.into_bump_slice())
}
}
};
let partial_proc = PartialProc::from_named_function(
env,
layout_cache,
function_type,
arguments,
loc_body,
captured_symbols,
is_self_recursive,
return_type,
);
procs.partial_procs.insert(*symbol, partial_proc);
roc_can::expr::Expr::Closure(closure_data) => {
register_capturing_closure(env, procs, layout_cache, *symbol, closure_data);
return from_can(env, variable, cont.value, procs, layout_cache);
}
_ => unreachable!(),
}
}
match def.loc_expr.value {
roc_can::expr::Expr::Var(original) => {
// a variable is aliased, e.g.
//
@ -6012,7 +6049,7 @@ fn can_reuse_symbol<'a>(
if env.is_imported_symbol(symbol) {
Imported(symbol)
} else if procs.partial_procs.contains_key(&symbol) {
} else if procs.partial_procs.contains_key(symbol) {
LocalFunction(symbol)
} else {
Value(symbol)
@ -6101,7 +6138,7 @@ fn reuse_function_symbol<'a>(
result: Stmt<'a>,
original: Symbol,
) -> Stmt<'a> {
match procs.partial_procs.get(&original) {
match procs.get_partial_proc(original) {
None => {
match arg_var {
Some(arg_var) if env.is_imported_symbol(original) => {
@ -6170,7 +6207,7 @@ fn reuse_function_symbol<'a>(
// and closures by unification. Here we record whether this function captures
// anything.
let captures = partial_proc.captured_symbols.captures();
let captured = partial_proc.captured_symbols.clone();
let captured = partial_proc.captured_symbols;
match res_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
@ -6674,7 +6711,7 @@ fn call_by_name_help<'a>(
assign_to_symbols(env, procs, layout_cache, iter, result)
}
None => {
let opt_partial_proc = procs.partial_procs.get(&proc_name);
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name);
/*
debug_assert_eq!(
@ -6691,9 +6728,6 @@ fn call_by_name_help<'a>(
match opt_partial_proc {
Some(partial_proc) => {
// TODO should pending_procs hold a Rc<Proc> to avoid this .clone()?
let partial_proc = partial_proc.clone();
// Mark this proc as in-progress, so if we're dealing with
// mutually recursive functions, we don't loop forever.
// (We had a bug around this before this system existed!)
@ -6773,10 +6807,11 @@ fn call_by_name_module_thunk<'a>(
let inner_layout = *ret_layout;
// If we've already specialized this one, no further work is needed.
if procs
let already_specialized = procs
.specialized
.contains_key(&(proc_name, top_level_layout))
{
.contains_key(&(proc_name, top_level_layout));
if already_specialized {
force_thunk(env, proc_name, inner_layout, assigned, hole)
} else {
let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var);
@ -6811,13 +6846,10 @@ fn call_by_name_module_thunk<'a>(
force_thunk(env, proc_name, inner_layout, assigned, hole)
}
None => {
let opt_partial_proc = procs.partial_procs.get(&proc_name);
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name);
match opt_partial_proc {
Some(partial_proc) => {
// TODO should pending_procs hold a Rc<Proc> to avoid this .clone()?
let partial_proc = partial_proc.clone();
// Mark this proc as in-progress, so if we're dealing with
// mutually recursive functions, we don't loop forever.
// (We had a bug around this before this system existed!)
@ -6934,7 +6966,7 @@ fn call_specialized_proc<'a>(
match procs
.partial_procs
.get(&proc_name)
.get_symbol(proc_name)
.map(|pp| &pp.captured_symbols)
{
Some(&CapturedSymbols::Captured(captured_symbols)) => {

View file

@ -724,6 +724,14 @@ impl<'a, 'b> Env<'a, 'b> {
}
}
const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 {
if alignment != 0 && width % alignment > 0 {
width + alignment - (width % alignment)
} else {
width
}
}
impl<'a> Layout<'a> {
fn new_help<'b>(
env: &mut Env<'a, 'b>,
@ -859,11 +867,7 @@ impl<'a> Layout<'a> {
let width = self.stack_size_without_alignment(pointer_size);
let alignment = self.alignment_bytes(pointer_size);
if alignment != 0 && width % alignment > 0 {
width + alignment - (width % alignment)
} else {
width
}
round_up_to_alignment(width, alignment)
}
fn stack_size_without_alignment(&self, pointer_size: u32) -> u32 {
@ -885,6 +889,8 @@ impl<'a> Layout<'a> {
match variant {
NonRecursive(fields) => {
let tag_id_builtin = variant.tag_id_builtin();
fields
.iter()
.map(|tag_layout| {
@ -894,9 +900,10 @@ impl<'a> Layout<'a> {
.sum::<u32>()
})
.max()
.map(|w| round_up_to_alignment(w, tag_id_builtin.alignment_bytes(pointer_size)))
.unwrap_or_default()
// the size of the tag_id
+ variant.tag_id_builtin().stack_size(pointer_size)
+ tag_id_builtin.stack_size(pointer_size)
}
Recursive(_)
@ -924,13 +931,28 @@ impl<'a> Layout<'a> {
use UnionLayout::*;
match variant {
NonRecursive(tags) => tags
NonRecursive(tags) => {
let max_alignment = tags
.iter()
.map(|x| x.iter())
.flatten()
.map(|x| x.alignment_bytes(pointer_size))
.max()
.unwrap_or(0),
.flat_map(|layouts| {
layouts
.iter()
.map(|layout| layout.alignment_bytes(pointer_size))
})
.max();
match max_alignment {
Some(align) => {
let tag_id_builtin = variant.tag_id_builtin();
round_up_to_alignment(
align,
tag_id_builtin.alignment_bytes(pointer_size),
)
}
None => 0,
}
}
Recursive(_)
| NullableWrapped { .. }
| NullableUnwrapped { .. }

View file

@ -14,11 +14,11 @@ keep_shadowed_builtins = []
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
bumpalo = { version = "3.6.1", features = ["collections"] }
encode_unicode = "0.3"
bumpalo = { version = "3.8.0", features = ["collections"] }
encode_unicode = "0.3.6"
[dev-dependencies]
pretty_assertions = "0.5.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
pretty_assertions = "1.0.0"
indoc = "1.0.3"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"

View file

@ -1,9 +1,7 @@
use crate::ast::CommentOrNewline;
use crate::ast::Spaceable;
use crate::parser::{
self, and, backtrackable, BadInputError, Col, Parser,
Progress::{self, *},
Row, State,
self, and, backtrackable, BadInputError, Col, Parser, Progress::*, Row, State,
};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
@ -181,109 +179,6 @@ where
spaces_help_help(min_indent, space_problem, indent_problem)
}
pub fn spaces_till_end_of_line<'a, E: 'a>(
tab_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Option<&'a str>, E> {
move |_, mut state: State<'a>| {
let mut bytes = state.bytes;
let mut row = state.line;
let mut col = state.column;
for c in bytes {
match c {
b' ' => {
bytes = &bytes[1..];
col += 1;
}
b'\n' => {
bytes = &bytes[1..];
row += 1;
col = 0;
state.line = row;
state.column = col;
state.bytes = bytes;
return Ok((MadeProgress, None, state));
}
b'\r' => {
bytes = &bytes[1..];
}
b'\t' => {
return Err((
MadeProgress,
tab_problem(row, col),
State {
line: row,
column: col,
..state
},
))
}
b'#' => match chomp_line_comment(bytes) {
Ok(comment) => {
state.line += 1;
state.column = 0;
let width = 1 + comment.len();
if let Some(b'\n') = bytes.get(width) {
state.bytes = &bytes[width + 1..];
} else {
state.bytes = &bytes[width..];
}
return Ok((MadeProgress, Some(comment), state));
}
Err(_) => unreachable!("we check the first character is a #"),
},
_ => break,
}
}
if state.column == col {
Ok((NoProgress, None, state))
} else {
Ok((
MadeProgress,
None,
State {
column: col,
bytes,
..state
},
))
}
}
}
fn chomp_line_comment(buffer: &[u8]) -> Result<&str, Progress> {
if let Some(b'#') = buffer.get(0) {
if (&buffer[1..]).starts_with(b"# ") {
// this is a doc comment, not a line comment
Err(NoProgress)
} else {
use encode_unicode::CharExt;
let mut chomped = 1;
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch == '\n' {
break;
} else {
chomped += width;
}
}
let comment_bytes = &buffer[1..chomped];
let comment = unsafe { std::str::from_utf8_unchecked(comment_bytes) };
Ok(comment)
}
} else {
Err(NoProgress)
}
}
#[inline(always)]
fn spaces_help_help<'a, E>(
min_indent: u16,

View file

@ -416,9 +416,41 @@ mod test_parse {
assert_parses_to(&string, Float(&string));
}
#[test]
fn pos_inf_float() {
assert_parses_to(
"inf",
Var {
module_name: "",
ident: "inf",
},
);
}
#[test]
fn neg_inf_float() {
let arena = Bump::new();
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
let inf_expr = Var {
module_name: "",
ident: "inf",
};
let loc_inf_expr = Located::new(0, 0, 1, 4, inf_expr);
assert_parses_to("-inf", UnaryOp(arena.alloc(loc_inf_expr), loc_op));
}
#[quickcheck]
fn all_f64_values_parse(num: f64) {
assert_parses_to(num.to_string().as_str(), Float(num.to_string().as_str()));
let string = num.to_string();
if string.contains(".") {
assert_parses_to(&string, Float(&string));
} else if num.is_nan() {
assert_parses_to(&string, Expr::GlobalTag(&string));
} else if num.is_finite() {
// These are whole numbers. Add the `.0` back to make float.
let float_string = format!("{}.0", string);
assert_parses_to(&float_string, Float(&float_string));
}
}
// RECORD LITERALS

View file

@ -10,10 +10,3 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -16,18 +16,15 @@ roc_can = { path = "../can" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
ven_pretty = { path = "../../vendor/pretty" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
im = "15.0.0"
im-rc = "15.0.0"
distance = "0.4.0"
bumpalo = { version = "3.6.1", features = ["collections"] }
bumpalo = { version = "3.8.0", features = ["collections"] }
[dev-dependencies]
roc_constrain = { path = "../constrain" }
roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
pretty_assertions = "1.0.0"
indoc = "1.0.3"

View file

@ -1805,8 +1805,10 @@ fn diff_record<'b>(
fields2: SendMap<Lowercase, RecordField<ErrorType>>,
ext2: TypeExt,
) -> Diff<RocDocBuilder<'b>> {
let to_overlap_docs =
|(field, (t1, t2)): &(Lowercase, (RecordField<ErrorType>, RecordField<ErrorType>))| {
let to_overlap_docs = |(field, (t1, t2)): (
&Lowercase,
&(RecordField<ErrorType>, RecordField<ErrorType>),
)| {
let diff = to_diff(
alloc,
Parens::Unnecessary,
@ -1836,8 +1838,7 @@ fn diff_record<'b>(
status: {
match (&t1, &t2) {
(RecordField::Demanded(_), RecordField::Optional(_))
| (RecordField::Optional(_), RecordField::Demanded(_)) => match diff.status
{
| (RecordField::Optional(_), RecordField::Demanded(_)) => match diff.status {
Status::Similar => {
Status::Different(vec![Problem::OptionalRequiredMismatch(
field.clone(),
@ -1855,7 +1856,7 @@ fn diff_record<'b>(
}
};
let to_unknown_docs = |(field, tipe): &(Lowercase, RecordField<ErrorType>)| {
let to_unknown_docs = |(field, tipe): (&Lowercase, &RecordField<ErrorType>)| {
(
field.clone(),
alloc.string(field.as_str().to_string()),
@ -1979,7 +1980,7 @@ fn diff_tag_union<'b>(
status: diff.status,
}
};
let to_unknown_docs = |(field, args): &(TagName, Vec<ErrorType>)| {
let to_unknown_docs = |(field, args): (&TagName, &Vec<ErrorType>)| {
(
field.clone(),
alloc.tag_name(field.clone()),

View file

@ -398,7 +398,7 @@ impl<'a> RocDocAllocator<'a> {
debug_assert!(region.contains(&sub_region2));
// if true, the final line of the snippet will be some ^^^ that point to the region where
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
// the problem is. Otherwise, the snippet will have a > on the lines that are in the region
// where the problem is.
let error_highlight_line = region.start_line == region.end_line;
@ -505,7 +505,7 @@ impl<'a> RocDocAllocator<'a> {
}
// if true, the final line of the snippet will be some ^^^ that point to the region where
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
// the problem is. Otherwise, the snippet will have a > on the lines that are in the region
// where the problem is.
let error_highlight_line = sub_region.start_line == region.end_line;

View file

@ -19,10 +19,7 @@ roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
tempfile = "3.1.0"
quickcheck = "0.8"
quickcheck_macros = "0.8"
bumpalo = { version = "3.6.1", features = ["collections"] }
pretty_assertions = "1.0.0"
indoc = "1.0.3"
tempfile = "3.2.0"
bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -21,7 +21,6 @@ roc_builtins = { path = "../builtins" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -0,0 +1,25 @@
[package]
name = "test_dev"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
roc_collections = { path = "../collections" }
roc_can = { path = "../can" }
roc_build = { path = "../build" }
roc_parse = { path = "../parse" }
roc_reporting = { path = "../reporting" }
roc_load = { path = "../load" }
roc_constrain = { path = "../constrain" }
roc_std = { path = "../../roc_std" }
roc_gen_dev = { path = "../gen_dev" }
roc_mono = { path = "../mono" }
roc_problem = { path = "../problem" }
roc_builtins = { path = "../builtins" }
indoc = "1.0.3"
bumpalo = { version = "3.8.0", features = ["collections"] }
tempfile = "3.2.0"
libloading = "0.7.1"
target-lexicon = "0.12.2"

View file

@ -0,0 +1,871 @@
#![cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
use crate::assert_evals_to;
use indoc::indoc;
#[test]
fn i64_values() {
assert_evals_to!("0", 0, i64);
assert_evals_to!("-0", 0, i64);
assert_evals_to!("-1", -1, i64);
assert_evals_to!("1", 1, i64);
assert_evals_to!("9_000_000_000_000", 9_000_000_000_000, i64);
assert_evals_to!("-9_000_000_000_000", -9_000_000_000_000, i64);
assert_evals_to!("0b1010", 0b1010, i64);
assert_evals_to!("0o17", 0o17, i64);
assert_evals_to!("0x1000_0000_0000_0000", 0x1000_0000_0000_0000, i64);
}
#[test]
fn f64_values() {
assert_evals_to!("0.0", 0.0, f64);
assert_evals_to!("-0.0", 0.0, f64);
assert_evals_to!("1.0", 1.0, f64);
assert_evals_to!("-1.0", -1.0, f64);
assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64);
assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64);
assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64);
}
#[test]
fn gen_add_i64() {
assert_evals_to!(
indoc!(
r#"
1 + 2 + 3
"#
),
6,
i64
);
}
#[test]
fn gen_add_f64() {
assert_evals_to!(
indoc!(
r#"
1.1 + 2.4 + 3
"#
),
6.5,
f64
);
}
#[test]
fn gen_sub_i64() {
assert_evals_to!(
indoc!(
r#"
1 - 2 - 3
"#
),
-4,
i64
);
}
#[test]
fn gen_mul_i64() {
assert_evals_to!(
indoc!(
r#"
2 * 4 * 6
"#
),
48,
i64
);
}
#[test]
fn i64_force_stack() {
// This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64.
assert_evals_to!(
indoc!(
r#"
a = 0
b = 1
c = 2
d = 3
e = 4
f = 5
g = 6
h = 7
i = 8
j = 9
k = 10
l = 11
m = 12
n = 13
o = 14
p = 15
q = 16
r = 17
s = 18
t = 19
u = 20
v = 21
w = 22
x = 23
y = 24
z = 25
aa = 26
ab = 27
ac = 28
ad = 29
ae = 30
af = 31
ag = 32
a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag
"#
),
528,
i64
);
}
#[test]
fn i64_abs() {
assert_evals_to!("Num.abs -6", 6, i64);
assert_evals_to!("Num.abs 7", 7, i64);
assert_evals_to!("Num.abs 0", 0, i64);
assert_evals_to!("Num.abs -0", 0, i64);
assert_evals_to!("Num.abs -1", 1, i64);
assert_evals_to!("Num.abs 1", 1, i64);
assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64);
assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64);
}
#[test]
fn gen_int_eq() {
assert_evals_to!(
indoc!(
r#"
4 == 4
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
3 == 4
"#
),
false,
bool
);
}
#[test]
fn gen_basic_fn() {
assert_evals_to!(
indoc!(
r#"
always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64)
always42 = \_ -> 42
always42 5
"#
),
42,
i64
);
}
#[test]
fn gen_wrap_add_nums() {
assert_evals_to!(
indoc!(
r#"
add2 = \num1, num2 -> num1 + num2
add2 4 5
"#
),
9,
i64
);
}
#[test]
fn gen_wrap_add_nums_force_stack() {
assert_evals_to!(
indoc!(
r#"
add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9
add9 1 2 3 4 5 6 7 8 9
"#
),
45,
i64
);
}
#[test]
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
#[test]
fn acos() {
assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64);
}
#[test]
fn asin() {
assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64);
}
#[test]
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
#[test]
fn gen_if_fn() {
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
x =
if num == 1 then
-1
else if num == -1 then
1
else
num
x
limitedNegate 1
"#
),
-1,
i64
);
}
#[test]
fn gen_fib_fn() {
assert_evals_to!(
indoc!(
r#"
fib = \n ->
if n == 0 then
0
else if n == 1 then
1
else
(fib (n - 1)) + (fib (n - 2))
fib 10
"#
),
55,
i64
);
}
#[test]
fn gen_fast_fib_fn() {
assert_evals_to!(
indoc!(
r#"
fib = \n, a, b ->
if n == 0 then
a
else
fib (n - 1) b (a + b)
fib 10 0 1
"#
),
55,
i64
);
}
#[test]
fn f64_abs() {
assert_evals_to!("Num.abs -4.7", 4.7, f64);
assert_evals_to!("Num.abs 5.8", 5.8, f64);
}
#[test]
fn f64_round() {
assert_evals_to!("Num.round 3.6", 4, i64);
assert_evals_to!("Num.round 3.4", 3, i64);
assert_evals_to!("Num.round 2.5", 3, i64);
assert_evals_to!("Num.round -2.3", -2, i64);
assert_evals_to!("Num.round -2.5", -3, i64);
}
// #[test]
// fn f64_sqrt() {
// // FIXME this works with normal types, but fails when checking uniqueness types
// assert_evals_to!(
// indoc!(
// r#"
// when Num.sqrt 100 is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// 10.0,
// f64
// );
// }
// #[test]
// fn gen_float_eq() {
// assert_evals_to!(
// indoc!(
// r#"
// 1.0 == 1.0
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn gen_div_f64() {
// // FIXME this works with normal types, but fails when checking uniqueness types
// assert_evals_to!(
// indoc!(
// r#"
// when 48 / 2 is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// 24.0,
// f64
// );
// }
// #[test]
// fn gen_int_neq() {
// assert_evals_to!(
// indoc!(
// r#"
// 4 != 5
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn gen_wrap_int_neq() {
// assert_evals_to!(
// indoc!(
// r#"
// wrappedNotEq : a, a -> Bool
// wrappedNotEq = \num1, num2 ->
// num1 != num2
// wrappedNotEq 2 3
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn gen_sub_f64() {
// assert_evals_to!(
// indoc!(
// r#"
// 1.5 - 2.4 - 3
// "#
// ),
// -3.9,
// f64
// );
// }
// #[test]
// fn gen_div_i64() {
// assert_evals_to!(
// indoc!(
// r#"
// when 1000 // 10 is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// 100,
// i64
// );
// }
// #[test]
// fn gen_div_by_zero_i64() {
// assert_evals_to!(
// indoc!(
// r#"
// when 1000 // 0 is
// Err DivByZero -> 99
// _ -> -24
// "#
// ),
// 99,
// i64
// );
// }
// #[test]
// fn gen_rem_i64() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.rem 8 3 is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// 2,
// i64
// );
// }
// #[test]
// fn gen_rem_div_by_zero_i64() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.rem 8 0 is
// Err DivByZero -> 4
// Ok _ -> -23
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn gen_is_zero_i64() {
// assert_evals_to!("Num.isZero 0", true, bool);
// assert_evals_to!("Num.isZero 1", false, bool);
// }
// #[test]
// fn gen_is_positive_i64() {
// assert_evals_to!("Num.isPositive 0", false, bool);
// assert_evals_to!("Num.isPositive 1", true, bool);
// assert_evals_to!("Num.isPositive -5", false, bool);
// }
// #[test]
// fn gen_is_negative_i64() {
// assert_evals_to!("Num.isNegative 0", false, bool);
// assert_evals_to!("Num.isNegative 3", false, bool);
// assert_evals_to!("Num.isNegative -2", true, bool);
// }
// #[test]
// fn gen_is_positive_f64() {
// assert_evals_to!("Num.isPositive 0.0", false, bool);
// assert_evals_to!("Num.isPositive 4.7", true, bool);
// assert_evals_to!("Num.isPositive -8.5", false, bool);
// }
// #[test]
// fn gen_is_negative_f64() {
// assert_evals_to!("Num.isNegative 0.0", false, bool);
// assert_evals_to!("Num.isNegative 9.9", false, bool);
// assert_evals_to!("Num.isNegative -4.4", true, bool);
// }
// #[test]
// fn gen_is_zero_f64() {
// assert_evals_to!("Num.isZero 0", true, bool);
// assert_evals_to!("Num.isZero 0_0", true, bool);
// assert_evals_to!("Num.isZero 0.0", true, bool);
// assert_evals_to!("Num.isZero 1", false, bool);
// }
// #[test]
// fn gen_is_odd() {
// assert_evals_to!("Num.isOdd 4", false, bool);
// assert_evals_to!("Num.isOdd 5", true, bool);
// }
// #[test]
// fn gen_is_even() {
// assert_evals_to!("Num.isEven 6", true, bool);
// assert_evals_to!("Num.isEven 7", false, bool);
// }
// #[test]
// fn sin() {
// assert_evals_to!("Num.sin 0", 0.0, f64);
// assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64);
// }
// #[test]
// fn cos() {
// assert_evals_to!("Num.cos 0", 1.0, f64);
// assert_evals_to!("Num.cos 3.14159265359", -1.0, f64);
// }
// #[test]
// fn tan() {
// assert_evals_to!("Num.tan 0", 0.0, f64);
// assert_evals_to!("Num.tan 1", 1.557407724654902, f64);
// }
// #[test]
// fn lt_i64() {
// assert_evals_to!("1 < 2", true, bool);
// assert_evals_to!("1 < 1", false, bool);
// assert_evals_to!("2 < 1", false, bool);
// assert_evals_to!("0 < 0", false, bool);
// }
// #[test]
// fn lte_i64() {
// assert_evals_to!("1 <= 1", true, bool);
// assert_evals_to!("2 <= 1", false, bool);
// assert_evals_to!("1 <= 2", true, bool);
// assert_evals_to!("0 <= 0", true, bool);
// }
// #[test]
// fn gt_i64() {
// assert_evals_to!("2 > 1", true, bool);
// assert_evals_to!("2 > 2", false, bool);
// assert_evals_to!("1 > 1", false, bool);
// assert_evals_to!("0 > 0", false, bool);
// }
// #[test]
// fn gte_i64() {
// assert_evals_to!("1 >= 1", true, bool);
// assert_evals_to!("1 >= 2", false, bool);
// assert_evals_to!("2 >= 1", true, bool);
// assert_evals_to!("0 >= 0", true, bool);
// }
// #[test]
// fn lt_f64() {
// assert_evals_to!("1.1 < 1.2", true, bool);
// assert_evals_to!("1.1 < 1.1", false, bool);
// assert_evals_to!("1.2 < 1.1", false, bool);
// assert_evals_to!("0.0 < 0.0", false, bool);
// }
// #[test]
// fn lte_f64() {
// assert_evals_to!("1.1 <= 1.1", true, bool);
// assert_evals_to!("1.2 <= 1.1", false, bool);
// assert_evals_to!("1.1 <= 1.2", true, bool);
// assert_evals_to!("0.0 <= 0.0", true, bool);
// }
// #[test]
// fn gt_f64() {
// assert_evals_to!("2.2 > 1.1", true, bool);
// assert_evals_to!("2.2 > 2.2", false, bool);
// assert_evals_to!("1.1 > 2.2", false, bool);
// assert_evals_to!("0.0 > 0.0", false, bool);
// }
// #[test]
// fn gte_f64() {
// assert_evals_to!("1.1 >= 1.1", true, bool);
// assert_evals_to!("1.1 >= 1.2", false, bool);
// assert_evals_to!("1.2 >= 1.1", true, bool);
// assert_evals_to!("0.0 >= 0.0", true, bool);
// }
#[test]
fn gen_order_of_arithmetic_ops() {
assert_evals_to!(
indoc!(
r#"
1 + 3 * 7 - 2
"#
),
20,
i64
);
}
// #[test]
// fn gen_order_of_arithmetic_ops_complex_float() {
// assert_evals_to!(
// indoc!(
// r#"
// 3 - 48 * 2.0
// "#
// ),
// -93.0,
// f64
// );
// }
#[test]
fn if_guard_bind_variable_false() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 10 is
x if x == 5 -> 0
_ -> 42
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn if_guard_bind_variable_true() {
assert_evals_to!(
indoc!(
r#"
wrapper = \{} ->
when 10 is
x if x == 10 -> 42
_ -> 0
wrapper {}
"#
),
42,
i64
);
}
#[test]
fn tail_call_elimination() {
assert_evals_to!(
indoc!(
r#"
sum = \n, accum ->
when n is
0 -> accum
_ -> sum (n - 1) (n + accum)
sum 1_000_000 0
"#
),
500000500000,
i64
);
}
// #[test]
// fn int_negate() {
// assert_evals_to!("Num.neg 123", -123, i64);
// }
// #[test]
// fn gen_wrap_int_neg() {
// assert_evals_to!(
// indoc!(
// r#"
// wrappedNeg = \num -> -num
// wrappedNeg 3
// "#
// ),
// -3,
// i64
// );
// }
// #[test]
// fn int_to_float() {
// assert_evals_to!("Num.toFloat 0x9", 9.0, f64);
// }
// #[test]
// fn num_to_float() {
// assert_evals_to!("Num.toFloat 9", 9.0, f64);
// }
// #[test]
// fn float_to_float() {
// assert_evals_to!("Num.toFloat 0.5", 0.5, f64);
// }
// #[test]
// fn int_compare() {
// assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder);
// assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder);
// assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder);
// }
// #[test]
// fn float_compare() {
// assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder);
// assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder);
// assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder);
// }
// #[test]
// fn pow() {
// assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64);
// }
// #[test]
// fn ceiling() {
// assert_evals_to!("Num.ceiling 1.1", 2, i64);
// }
// #[test]
// fn floor() {
// assert_evals_to!("Num.floor 1.9", 1, i64);
// }
// // #[test]
// // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
// // fn int_overflow() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // 9_223_372_036_854_775_807 + 1
// // "#
// // ),
// // 0,
// // i64
// // );
// // }
// #[test]
// fn int_add_checked() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.addChecked 1 2 is
// Ok v -> v
// _ -> -1
// "#
// ),
// 3,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// when Num.addChecked 9_223_372_036_854_775_807 1 is
// Err Overflow -> -1
// Ok v -> v
// "#
// ),
// -1,
// i64
// );
// }
// #[test]
// fn int_add_wrap() {
// assert_evals_to!(
// indoc!(
// r#"
// Num.addWrap 9_223_372_036_854_775_807 1
// "#
// ),
// std::i64::MIN,
// i64
// );
// }
// #[test]
// fn float_add_checked_pass() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.addChecked 1.0 0.0 is
// Ok v -> v
// Err Overflow -> -1.0
// "#
// ),
// 1.0,
// f64
// );
// }
// #[test]
// fn float_add_checked_fail() {
// assert_evals_to!(
// indoc!(
// r#"
// when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is
// Err Overflow -> -1
// Ok v -> v
// "#
// ),
// -1.0,
// f64
// );
// }
// // #[test]
// // #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
// // fn float_overflow() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // 1.7976931348623157e308 + 1.7976931348623157e308
// // "#
// // ),
// // 0.0,
// // f64
// // );
// // }
// #[test]
// fn max_i128() {
// assert_evals_to!(
// indoc!(
// r#"
// Num.maxI128
// "#
// ),
// i128::MAX,
// i128
// );
// }
// #[test]
// fn num_max_int() {
// assert_evals_to!(
// indoc!(
// r#"
// Num.maxInt
// "#
// ),
// i64::MAX,
// i64
// );
// }
// #[test]
// fn num_min_int() {
// assert_evals_to!(
// indoc!(
// r#"
// Num.minInt
// "#
// ),
// i64::MIN,
// i64
// );
// }

View file

@ -0,0 +1,933 @@
#![cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
use crate::assert_evals_to;
use indoc::indoc;
#[test]
fn basic_record() {
assert_evals_to!(
indoc!(
r#"
{ y: 17, x: 15, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn nested_record() {
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
"#
),
12,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
"#
),
2,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn f64_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.x
"#
),
15.1,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.y
"#
),
17.2,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.z
"#
),
19.3,
f64
);
}
// #[test]
// fn fn_record() {
// assert_evals_to!(
// indoc!(
// r#"
// getRec = \x -> { y: 17, x, z: 19 }
// (getRec 15).x
// "#
// ),
// 15,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.y
// "#
// ),
// 17,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z
// "#
// ),
// 19,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z + rec.x
// "#
// ),
// 34,
// i64
// );
// }
#[test]
fn def_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17, x: 15, z: 19 }
rec.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.z
"#
),
19,
i64
);
}
#[test]
fn when_on_record() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2 } is
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn when_record_with_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: var } -> var + 3
"#
),
5,
i64
);
}
#[test]
fn let_with_record_pattern() {
assert_evals_to!(
indoc!(
r#"
{ x } = { x: 0x2, y: 3.14 }
x
"#
),
2,
i64
);
}
#[test]
fn record_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: 0x4 } -> 5
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn twice_record_access() {
assert_evals_to!(
indoc!(
r#"
x = {a: 0x2, b: 0x3 }
x.a + x.b
"#
),
5,
i64
);
}
#[test]
fn empty_record() {
assert_evals_to!(
indoc!(
r#"
v = {}
v
"#
),
(),
()
);
}
#[test]
fn i64_record1_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3 }
"#
),
3,
i64
);
}
// #[test]
// fn i64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// (3, 5),
// (i64, i64)
// );
// }
// // #[test]
// // fn i64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3, y: 5, z: 17 }
// // "#
// // ),
// // (3, 5, 17),
// // (i64, i64, i64)
// // );
// // }
// #[test]
// fn f64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3.1, y: 5.1 }
// "#
// ),
// (3.1, 5.1),
// (f64, f64)
// );
// }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// // #[test]
// // fn bool_record4_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // record : { a : Bool, b : Bool, c : Bool, d : Bool }
// // record = { a: True, b: True, c : True, d : Bool }
// // record
// // "#
// // ),
// // (true, false, false, true),
// // (bool, bool, bool, bool)
// // );
// // }
// #[test]
// fn i64_record1_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3 }
// "#
// ),
// 3,
// i64
// );
// }
// // #[test]
// // fn i64_record9_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
// // "#
// // ),
// // (3, 5, 17, 1, 9, 12, 13, 14, 15),
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64)
// // );
// // }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// #[test]
// fn bool_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// x : Bool
// x = True
// x
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn optional_field_when_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// main =
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_function_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \{ x ? 10, y } -> x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default_nested() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_singleton_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x : 4 } is
// { x ? 3 } -> x
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn optional_field_empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { } is
// { x ? 3 } -> x
// "#
// ),
// 3,
// i64
// );
// }
// #[test]
// fn return_record_2() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// [3, 5],
// [i64; 2]
// );
// }
// #[test]
// fn return_record_3() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5, z: 4 }
// "#
// ),
// (3, 5, 4),
// (i64, i64, i64)
// );
// }
// #[test]
// fn return_record_4() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2 }
// "#
// ),
// [3, 5, 4, 2],
// [i64; 4]
// );
// }
// #[test]
// fn return_record_5() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1 }
// "#
// ),
// [3, 5, 4, 2, 1],
// [i64; 5]
// );
// }
// #[test]
// fn return_record_6() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
// "#
// ),
// [3, 5, 4, 2, 1, 7],
// [i64; 6]
// );
// }
// #[test]
// fn return_record_7() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
// "#
// ),
// [3, 5, 4, 2, 1, 7, 8],
// [i64; 7]
// );
// }
// #[test]
// fn return_record_float_int() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3.14, b: 0x1 }
// "#
// ),
// (3.14, 0x1),
// (f64, i64)
// );
// }
// #[test]
// fn return_record_int_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 0x1, b: 3.14 }
// "#
// ),
// (0x1, 3.14),
// (i64, f64)
// );
// }
// #[test]
// fn return_record_float_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 6.28, b: 3.14 }
// "#
// ),
// (6.28, 3.14),
// (f64, f64)
// );
// }
// #[test]
// fn return_record_float_float_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 6.28, b: 3.14, c: 0.1 }
// "#
// ),
// (6.28, 3.14, 0.1),
// (f64, f64, f64)
// );
// }
// #[test]
// fn return_nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
// "#
// ),
// (0x0, (6.28, 3.14, 0.1)),
// (i64, (f64, f64, f64))
// );
// }
// #[test]
// fn accessor() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 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 }
// { rec & foo: rec.foo + 1 }
// "#
// ),
// (6, 43),
// (i64, i64)
// );
// }
#[test]
fn update_single_element_record() {
assert_evals_to!(
indoc!(
r#"
rec = { foo: 42}
{ rec & foo: rec.foo + 1 }
"#
),
43,
i64
);
}
// #[test]
// fn booleans_in_record() {
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
// (true, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
// (false, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
// (true, false),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
// (false, false),
// (bool, bool)
// );
// }
// #[test]
// fn alignment_in_record() {
// assert_evals_to!(
// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
// (32i64, true, 2u8),
// (i64, bool, u8)
// );
// }
// #[test]
// fn blue_and_present() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue, y: 7 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn blue_and_absent() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue }
// "#
// ),
// 3,
// i64
// );
// }

View file

@ -0,0 +1,950 @@
#![cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
//use indoc::indoc;
use crate::assert_evals_to;
// use roc_std::{RocList, RocStr};
// #[test]
// fn str_split_bigger_delimiter_small_str() {
// assert_evals_to!(
// indoc!(
// r#"
// List.len (Str.split "hello" "JJJJ there")
// "#
// ),
// 1,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// when List.first (Str.split "JJJ" "JJJJ there") is
// Ok str ->
// Str.countGraphemes str
// _ ->
// -1
// "#
// ),
// 3,
// i64
// );
// }
// #[test]
// fn str_split_str_concat_repeated() {
// assert_evals_to!(
// indoc!(
// r#"
// when List.first (Str.split "JJJJJ" "JJJJ there") is
// Ok str ->
// str
// |> Str.concat str
// |> Str.concat str
// |> Str.concat str
// |> Str.concat str
// _ ->
// "Not Str!"
// "#
// ),
// RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"),
// RocStr
// );
// }
// #[test]
// fn str_split_small_str_bigger_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// when
// List.first
// (Str.split "JJJ" "0123456789abcdefghi")
// is
// Ok str -> str
// _ -> ""
// "#
// ),
// RocStr::from_slice(b"JJJ"),
// RocStr
// );
// }
// #[test]
// fn str_split_big_str_small_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"01234567789abcdefghi"),
// RocStr::from_slice(b"01234567789abcdefghi")
// ]),
// RocList<RocStr>
// );
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"01234567789abcdefghi "),
// RocStr::from_slice(b" 01234567789abcdefghi")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_small_str_small_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "J!J!J" "!"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"J"),
// RocStr::from_slice(b"J"),
// RocStr::from_slice(b"J")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_bigger_delimiter_big_strs() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "string to split is shorter"
// "than the delimiter which happens to be very very long"
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_empty_strs() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "" ""
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"")]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_minimal_example() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "a," ","
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]),
// RocList<RocStr>
// )
// }
// #[test]
// fn str_split_small_str_big_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
// "---- ---- ---- ---- ----"
// |> List.len
// "#
// ),
// 3,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
// "---- ---- ---- ---- ----"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"1"),
// RocStr::from_slice(b"2"),
// RocStr::from_slice(b"")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_small_str_20_char_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |"
// "|-- -- -- -- -- -- |"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"3"),
// RocStr::from_slice(b"4"),
// RocStr::from_slice(b"")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_concat_big_to_big() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.concat
// "First string that is fairly long. Longer strings make for different errors. "
// "Second string that is also fairly long. Two long strings test things that might not appear with short strings."
// "#
// ),
// RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."),
// RocStr
// );
// }
#[test]
fn small_str_literal() {
assert_evals_to!(
"\"JJJJJJJJJJJJJJJ\"",
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_zeroed_literal() {
// // Verifies that we zero out unused bytes in the string.
// // This is important so that string equality tests don't randomly
// // fail due to unused memory being there!
// assert_evals_to!(
// "\"J\"",
// [
// 0x4a,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0b1000_0001
// ],
// [u8; 16]
// );
// }
#[test]
fn small_str_concat_empty_first_arg() {
assert_evals_to!(
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_empty_second_arg() {
assert_evals_to!(
r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_concat_small_to_big() {
// assert_evals_to!(
// r#"Str.concat "abc" " this is longer than 15 chars""#,
// RocStr::from_slice(b"abc this is longer than 15 chars"),
// RocStr
// );
// }
#[test]
fn small_str_concat_small_to_small_staying_small() {
assert_evals_to!(
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_concat_small_to_small_overflow_to_big() {
// assert_evals_to!(
// r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
// RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"),
// RocStr
// );
// }
// #[test]
// fn str_concat_empty() {
// assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr);
// }
// #[test]
// fn small_str_is_empty() {
// assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool);
// }
// #[test]
// fn big_str_is_empty() {
// assert_evals_to!(
// r#"Str.isEmpty "this is more than 15 chars long""#,
// false,
// bool
// );
// }
// #[test]
// fn empty_str_is_empty() {
// assert_evals_to!(r#"Str.isEmpty """#, true, bool);
// }
// #[test]
// fn str_starts_with() {
// assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool);
// assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool);
// assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
// }
// #[test]
// fn str_starts_with_code_point() {
// assert_evals_to!(
// &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32),
// true,
// bool
// );
// assert_evals_to!(
// &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32),
// false,
// bool
// );
// }
// #[test]
// fn str_ends_with() {
// assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
// assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
// }
// #[test]
// fn str_count_graphemes_small_str() {
// assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
// }
// #[test]
// fn str_count_graphemes_three_js() {
// assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize);
// }
// #[test]
// fn str_count_graphemes_big_str() {
// assert_evals_to!(
// r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#,
// 45,
// usize
// );
// }
// #[test]
// fn str_starts_with_same_big_str() {
// assert_evals_to!(
// r#"Str.startsWith "123456789123456789" "123456789123456789""#,
// true,
// bool
// );
// }
// #[test]
// fn str_starts_with_different_big_str() {
// assert_evals_to!(
// r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
// true,
// bool
// );
// }
// #[test]
// fn str_starts_with_same_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
// }
// #[test]
// fn str_starts_with_different_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
// }
// #[test]
// fn str_starts_with_false_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
// }
// #[test]
// fn str_from_int() {
// assert_evals_to!(
// r#"Str.fromInt 1234"#,
// roc_std::RocStr::from_slice("1234".as_bytes()),
// roc_std::RocStr
// );
// assert_evals_to!(
// r#"Str.fromInt 0"#,
// roc_std::RocStr::from_slice("0".as_bytes()),
// roc_std::RocStr
// );
// assert_evals_to!(
// r#"Str.fromInt -1"#,
// roc_std::RocStr::from_slice("-1".as_bytes()),
// roc_std::RocStr
// );
// let max = format!("{}", i64::MAX);
// assert_evals_to!(
// r#"Str.fromInt Num.maxInt"#,
// RocStr::from_slice(max.as_bytes()),
// RocStr
// );
// let min = format!("{}", i64::MIN);
// assert_evals_to!(
// r#"Str.fromInt Num.minInt"#,
// RocStr::from_slice(min.as_bytes()),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_ascii() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_ascii() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("abc~".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_unicode() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("∆".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_unicode() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("∆œ¬".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_grapheme() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_grapheme() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_all() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖b∆".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_invalid_start_byte() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is
// Err (BadUtf8 InvalidStartByte byteIndex) ->
// if byteIndex == 2 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_unexpected_end_of_sequence() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is
// Err (BadUtf8 UnexpectedEndOfSequence byteIndex) ->
// if byteIndex == 3 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_expected_continuation() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is
// Err (BadUtf8 ExpectedContinuation byteIndex) ->
// if byteIndex == 3 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_overlong_encoding() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is
// Err (BadUtf8 OverlongEncoding byteIndex) ->
// if byteIndex == 1 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_codepoint_too_large() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is
// Err (BadUtf8 CodepointTooLarge byteIndex) ->
// if byteIndex == 1 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_surrogate_half() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is
// Err (BadUtf8 EncodesSurrogateHalf byteIndex) ->
// if byteIndex == 2 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_equality() {
// assert_evals_to!(r#""a" == "a""#, true, bool);
// assert_evals_to!(
// r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#,
// true,
// bool
// );
// assert_evals_to!(r#""a" != "b""#, true, bool);
// assert_evals_to!(r#""a" == "b""#, false, bool);
// }
// #[test]
// fn str_clone() {
// use roc_std::RocStr;
// let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes());
// let short = RocStr::from_slice("x".as_bytes());
// let empty = RocStr::from_slice("".as_bytes());
// debug_assert_eq!(long.clone(), long);
// debug_assert_eq!(short.clone(), short);
// debug_assert_eq!(empty.clone(), empty);
// }
// #[test]
// fn nested_recursive_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// Expr : [ Add Expr Expr, Val I64, Var I64 ]
// expr : Expr
// expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))
// printExpr : Expr -> Str
// printExpr = \e ->
// when e is
// Add a b ->
// "Add ("
// |> Str.concat (printExpr a)
// |> Str.concat ") ("
// |> Str.concat (printExpr b)
// |> Str.concat ")"
// Val v -> "Val " |> Str.concat (Str.fromInt v)
// Var v -> "Var " |> Str.concat (Str.fromInt v)
// printExpr expr
// "#
// ),
// RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_small() {
// assert_evals_to!(
// r#"Str.joinWith ["1", "2"] ", " "#,
// RocStr::from("1, 2"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_big() {
// assert_evals_to!(
// r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#,
// RocStr::from("10000000, 2000000, 30000000"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_single() {
// assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
// }
// #[test]
// fn str_from_float() {
// assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr);
// }
// #[test]
// fn str_to_utf8() {
// assert_evals_to!(
// r#"Str.toUtf8 "hello""#,
// RocList::from_slice(&[104, 101, 108, 108, 111]),
// RocList<u8>
// );
// assert_evals_to!(
// r#"Str.toUtf8 "this is a long string""#,
// RocList::from_slice(&[
// 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116,
// 114, 105, 110, 103
// ]),
// RocList<u8>
// );
// }
// #[test]
// fn str_from_utf8_range() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 5, start: 0 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("hello"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_slice() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 4, start: 1 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ello"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_slice_not_end() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 3, start: 1 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ell"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_order_does_not_matter() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 1, count: 3 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ell"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_out_of_bounds_start_value() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 7, count: 3 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_count_too_high() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 0, count: 6 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_count_too_high_for_start() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 4, count: 3 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }

View file

@ -20,8 +20,8 @@ fn promote_expr_to_module(src: &str) -> String {
}
#[allow(dead_code)]
pub fn helper<'a>(
arena: &'a bumpalo::Bump,
pub fn helper(
arena: &bumpalo::Bump,
src: &str,
stdlib: roc_builtins::std::StdLib,
_leak: bool,
@ -205,7 +205,7 @@ pub fn helper<'a>(
// std::fs::copy(&path, "/tmp/libapp.so").unwrap();
let lib = Library::new(path).expect("failed to load shared library");
let lib = unsafe { Library::new(path) }.expect("failed to load shared library");
(main_fn_name, delayed_errors, lib)
}

View file

@ -0,0 +1,4 @@
pub mod dev_num;
pub mod dev_records;
pub mod dev_str;
mod helpers;

View file

@ -24,25 +24,21 @@ roc_parse = { path = "../parse" , features = []}
roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
roc_gen_wasm = { path = "../gen_wasm" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
im = "15.0.0"
im-rc = "15.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
either = "1.6.1"
indoc = "0.3.3"
libc = "0.2"
libc = "0.2.106"
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.2"
libloading = "0.6"
wasmer = "2.0.0"
libloading = "0.7.1"
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer-wasi = "2.0.0"
tempfile = "3.1.0"
tempfile = "3.2.0"
[dev-dependencies]
maplit = "1.0.1"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
bumpalo = { version = "3.8.0", features = ["collections"] }
indoc = "1.0.3"
roc_parse = { path = "../parse" , features = ["keep_shadowed_builtins"]}
[features]

Some files were not shown because too many files have changed in this diff Show more