Merge branch 'trunk' of github.com:rtfeldman/roc into function_closure_to_mark_node

This commit is contained in:
Anton-4 2021-10-25 14:34:58 +02:00
commit 39974ea039
81 changed files with 9691 additions and 1812 deletions

1
.envrc
View file

@ -1 +0,0 @@
use nix

View file

@ -7,26 +7,27 @@ name: Nightly Release Build
jobs: jobs:
build: build:
name: Test and Build name: Test and Build
runs-on: ubuntu-latest runs-on: [self-hosted, i5-4690K]
timeout-minutes: 90
env:
FORCE_COLOR: 1 # for earthly logging
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - name: install earthly
run: sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly && /usr/local/bin/earthly bootstrap --with-autocomplete'
- name: Earthly print version
run: earthly --version
- name: install dependencies, build, run tests, build release
run: ./ci/safe-earthly.sh +build-nightly-release
- name: Create pre-release with test_archive.tar.gz
uses: WebFreak001/deploy-nightly@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with: with:
profile: minimal upload_url: https://uploads.github.com/repos/rtfeldman/roc/releases/51880579/assets{?name,label}
toolchain: stable release_id: 51880579
override: true asset_path: ./roc_linux_x86_64.tar.gz
- run: rustup component add rustfmt asset_name: roc_nightly-linux_x86_64-$$.tar.gz # $$ to inserts date (YYYYMMDD) and 6 letter commit hash
- uses: actions-rs/cargo@v1 asset_content_type: application/gzip
name: cargo update (needed for Inkwell) max_releases: 3
with:
command: update
- uses: actions-rs/cargo@v1
name: cargo test
with:
command: test
args: --release
- uses: actions-rs/cargo@v1
name: cargo build
with:
command: build
args: --release

4
.gitignore vendored
View file

@ -2,6 +2,7 @@ target
generated-docs generated-docs
zig-cache zig-cache
.direnv .direnv
.envrc
*.rs.bk *.rs.bk
*.o *.o
*.tmp *.tmp
@ -38,3 +39,6 @@ bench-folder*
# earthly # earthly
earthly_log.txt earthly_log.txt
# created to test release
roc_linux_x86_64.tar.gz

11
AUTHORS
View file

@ -28,3 +28,14 @@ Ju Liu <ju@noredink.com>
Peter Fields <pcfields@gmail.com> Peter Fields <pcfields@gmail.com>
Brian J. Cardiff <bcardiff@gmail.com> Brian J. Cardiff <bcardiff@gmail.com>
Basile Henry <bjm.henry@gmail.com> Basile Henry <bjm.henry@gmail.com>
Tarjei Skjærset <tskj@tarjei.org>
Brian Hicks <brian@brianthicks.com>
Dan Gieschen Knutson <dan.knutson@gmail.com>
Joshua Hoeflich <joshuaharry411@icloud.com>
Brian Carroll <brian.carroll.ireland@gmail.com>
Kofi Gumbs <h.kofigumbs@gmail.com>
Luiz de Oliveira <luizcarlos1405@gmail.com>
Chelsea Troy <chelsea.dommert@gmail.com>
Shritesh Bhattarai <shr@ite.sh>
Kevin Sjöberg <mail@kevinsjoberg.com>

View file

@ -53,9 +53,12 @@ For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
`/usr/local/opt/llvm/bin` to your `PATH`. You can confirm this worked by `/usr/local/opt/llvm/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 12.0.0" at the top. running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
For Ubuntu and Debian, you can use the `Automatic installation script` at [apt.llvm.org](https://apt.llvm.org): For Ubuntu and Debian:
``` ```
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" sudo apt -y install lsb-release software-properties-common gnupg
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 12
``` ```
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`. If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.

5
Cargo.lock generated
View file

@ -2774,9 +2774,8 @@ dependencies = [
[[package]] [[package]]
name = "parity-wasm" name = "parity-wasm"
version = "0.42.2" version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/brian-carroll/parity-wasm?branch=master#373f655f64d2260a2e9665811f7b6ed17f9db705"
checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"

View file

@ -34,8 +34,8 @@ In the Roc community we strive to go the extra step to look out for each other.
And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Roc programmers comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Roc programmers comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
The enforcement policies listed above apply to all official Roc venues; including official IRC channels (#rust, #rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org (users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Roc Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. The enforcement policies listed above apply to all official Roc venues; including official Zulip chat (https://roc.zulipchat.com); and GitHub repositories under the roc-lang organization. If you wish to use this code of conduct (or the Rust code of conduct, on which it is based) for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* *Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).*
[mod_team]: https://www.rust-lang.org/team.html#Moderation-team [mod_team]: https://www.roc-lang.org/moderation

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock ./ COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
test-zig: test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
@ -67,12 +67,14 @@ check-rustfmt:
check-typos: check-typos:
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix version.txt ./
RUN typos RUN typos
test-rust: test-rust:
FROM +copy-dirs FROM +copy-dirs
ENV RUST_BACKTRACE=1 ENV RUST_BACKTRACE=1
# for race condition problem with cli test
ENV ROC_NUM_WORKERS=1
# run one of the benchmarks to make sure the host is compiled # run one of the benchmarks to make sure the host is compiled
# not pre-compiling the host can cause race conditions # not pre-compiling the host can cause race conditions
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
@ -101,6 +103,16 @@ test-all:
BUILD +test-rust BUILD +test-rust
BUILD +verify-no-git-changes BUILD +verify-no-git-changes
build-nightly-release:
FROM +test-rust
COPY --dir .git ./
# 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 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
# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run. # compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run.
prep-bench-folder: prep-bench-folder:
FROM +copy-dirs FROM +copy-dirs

View file

@ -1,6 +1,3 @@
#[macro_use]
extern crate clap;
#[macro_use] #[macro_use]
extern crate const_format; extern crate const_format;
@ -21,7 +18,6 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture};
pub mod build; pub mod build;
pub mod repl; pub mod repl;
pub const CMD_RUN: &str = "run";
pub const CMD_BUILD: &str = "build"; pub const CMD_BUILD: &str = "build";
pub const CMD_REPL: &str = "repl"; pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit"; pub const CMD_EDIT: &str = "edit";
@ -43,7 +39,7 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
pub fn build_app<'a>() -> App<'a> { pub fn build_app<'a>() -> App<'a> {
let app = App::new("roc") let app = App::new("roc")
.version(concatcp!(crate_version!(), "\n")) .version(concatcp!(include_str!("../../version.txt"), "\n"))
.about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!") .about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!")
.subcommand(App::new(CMD_BUILD) .subcommand(App::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it") .about("Build a binary from the given .roc file, but don't run it")
@ -104,38 +100,6 @@ pub fn build_app<'a>() -> App<'a> {
.required(false), .required(false),
) )
) )
.subcommand(App::new(CMD_RUN)
.about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`")
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEV)
.long(FLAG_DEV)
.help("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to run")
.required(true),
)
.arg(
Arg::with_name(ARGS_FOR_APP)
.help("Arguments to pass into the app being run")
.multiple(true),
)
)
.subcommand(App::new(CMD_REPL) .subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)") .about("Launch the interactive Read Eval Print Loop (REPL)")
) )

View file

@ -1,7 +1,7 @@
use roc_cli::build::check_file; use roc_cli::build::check_file;
use roc_cli::{ use roc_cli::{
build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_REPL, build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_REPL,
CMD_RUN, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
}; };
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
use std::fs::{self, FileType}; use std::fs::{self, FileType};
@ -44,17 +44,6 @@ fn main() -> io::Result<()> {
matches.subcommand_matches(CMD_BUILD).unwrap(), matches.subcommand_matches(CMD_BUILD).unwrap(),
BuildConfig::BuildOnly, BuildConfig::BuildOnly,
)?), )?),
Some(CMD_RUN) => {
// TODO remove CMD_RUN altogether if it is currently September 2021 or later.
println!(
r#"`roc run` is deprecated!
If you're using a prebuilt binary, you no longer need the `run` - just do `roc [FILE]` instead of `roc run [FILE]`.
If you're building the compiler from source you'll want to do `cargo run [FILE]` instead of `cargo run run [FILE]`.
"#
);
Ok(1)
}
Some(CMD_CHECK) => { Some(CMD_CHECK) => {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();
@ -189,6 +178,6 @@ fn launch_editor(project_dir_path: Option<&Path>) -> io::Result<()> {
} }
#[cfg(not(feature = "editor"))] #[cfg(not(feature = "editor"))]
fn launch_editor(_filepaths: &[&Path]) -> io::Result<()> { fn launch_editor(_project_dir_path: Option<&Path>) -> io::Result<()> {
panic!("Cannot launch the editor because this build of roc did not include `feature = \"editor\"`!"); panic!("Cannot launch the editor because this build of roc did not include `feature = \"editor\"`!");
} }

View file

@ -13,7 +13,7 @@ mod cli_run {
run_with_valgrind, ValgrindError, ValgrindErrorXWhat, run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
}; };
use serial_test::serial; use serial_test::serial;
use std::path::Path; use std::path::{Path, PathBuf};
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
@ -39,6 +39,7 @@ mod cli_run {
filename: &'a str, filename: &'a str,
executable_filename: &'a str, executable_filename: &'a str,
stdin: &'a [&'a str], stdin: &'a [&'a str],
input_file: Option<&'a str>,
expected_ending: &'a str, expected_ending: &'a str,
use_valgrind: bool, use_valgrind: bool,
} }
@ -48,6 +49,7 @@ mod cli_run {
stdin: &[&str], stdin: &[&str],
executable_filename: &str, executable_filename: &str,
flags: &[&str], flags: &[&str],
input_file: Option<PathBuf>,
expected_ending: &str, expected_ending: &str,
use_valgrind: bool, use_valgrind: bool,
) { ) {
@ -59,10 +61,20 @@ mod cli_run {
assert!(compile_out.status.success(), "bad status {:?}", compile_out); assert!(compile_out.status.success(), "bad status {:?}", compile_out);
let out = if use_valgrind && ALLOW_VALGRIND { let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = run_with_valgrind( let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
stdin, run_with_valgrind(
&[file.with_file_name(executable_filename).to_str().unwrap()], stdin,
); &[
file.with_file_name(executable_filename).to_str().unwrap(),
input_file.to_str().unwrap(),
],
)
} else {
run_with_valgrind(
stdin,
&[file.with_file_name(executable_filename).to_str().unwrap()],
)
};
if valgrind_out.status.success() { if valgrind_out.status.success() {
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| { let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
@ -100,16 +112,24 @@ mod cli_run {
valgrind_out valgrind_out
} else { } else {
run_cmd( if let Some(input_file) = input_file {
file.with_file_name(executable_filename).to_str().unwrap(), run_cmd(
stdin, file.with_file_name(executable_filename).to_str().unwrap(),
&[], stdin,
) &[input_file.to_str().unwrap()],
)
} else {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[],
)
}
}; };
if !&out.stdout.ends_with(expected_ending) { if !&out.stdout.ends_with(expected_ending) {
panic!( panic!(
"expected output to end with {:?} but instead got {:#?}", "expected output to end with {:?} but instead got {:#?}",
expected_ending, out expected_ending, out.stdout
); );
} }
assert!(out.status.success()); assert!(out.status.success());
@ -121,8 +141,10 @@ mod cli_run {
stdin: &[&str], stdin: &[&str],
executable_filename: &str, executable_filename: &str,
flags: &[&str], flags: &[&str],
input_file: Option<PathBuf>,
expected_ending: &str, expected_ending: &str,
) { ) {
assert_eq!(input_file, None, "Wasm does not support input files");
let mut flags = flags.to_vec(); let mut flags = flags.to_vec();
flags.push("--backend=wasm32"); flags.push("--backend=wasm32");
@ -178,6 +200,7 @@ mod cli_run {
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&[], &[],
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
); );
@ -187,6 +210,7 @@ mod cli_run {
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&["--optimize"], &["--optimize"],
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
); );
@ -199,6 +223,7 @@ mod cli_run {
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&["--roc-linker"], &["--roc-linker"],
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
); );
@ -235,6 +260,7 @@ mod cli_run {
filename: "Hello.roc", filename: "Hello.roc",
executable_filename: "hello-world", executable_filename: "hello-world",
stdin: &[], stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -242,6 +268,7 @@ mod cli_run {
filename: "Hello.roc", filename: "Hello.roc",
executable_filename: "hello-world", executable_filename: "hello-world",
stdin: &[], stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -249,6 +276,7 @@ mod cli_run {
filename: "Hello.roc", filename: "Hello.roc",
executable_filename: "hello-rust", executable_filename: "hello-rust",
stdin: &[], stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -256,6 +284,7 @@ mod cli_run {
filename: "Hello.roc", filename: "Hello.roc",
executable_filename: "hello-web", executable_filename: "hello-web",
stdin: &[], stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -263,6 +292,7 @@ mod cli_run {
filename: "Fib.roc", filename: "Fib.roc",
executable_filename: "fib", executable_filename: "fib",
stdin: &[], stdin: &[],
input_file: None,
expected_ending:"55\n", expected_ending:"55\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -270,6 +300,7 @@ mod cli_run {
filename: "Quicksort.roc", filename: "Quicksort.roc",
executable_filename: "quicksort", executable_filename: "quicksort",
stdin: &[], stdin: &[],
input_file: None,
expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -277,6 +308,7 @@ mod cli_run {
// filename: "Quicksort.roc", // filename: "Quicksort.roc",
// executable_filename: "quicksort", // executable_filename: "quicksort",
// stdin: &[], // stdin: &[],
// input_file: None,
// expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", // expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
// use_valgrind: true, // use_valgrind: true,
// }, // },
@ -284,6 +316,7 @@ mod cli_run {
filename: "Main.roc", filename: "Main.roc",
executable_filename: "effect-example", executable_filename: "effect-example",
stdin: &["hi there!"], stdin: &["hi there!"],
input_file: None,
expected_ending: "hi there!\nIt is known\n", expected_ending: "hi there!\nIt is known\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -291,6 +324,7 @@ mod cli_run {
// filename: "Main.roc", // filename: "Main.roc",
// executable_filename: "tea-example", // executable_filename: "tea-example",
// stdin: &[], // stdin: &[],
// input_file: None,
// expected_ending: "", // expected_ending: "",
// use_valgrind: true, // use_valgrind: true,
// }, // },
@ -298,6 +332,7 @@ mod cli_run {
filename: "Echo.roc", filename: "Echo.roc",
executable_filename: "echo", executable_filename: "echo",
stdin: &["Giovanni\n", "Giorgio\n"], stdin: &["Giovanni\n", "Giorgio\n"],
input_file: None,
expected_ending: "Hi, Giovanni Giorgio!\n", expected_ending: "Hi, Giovanni Giorgio!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -305,6 +340,7 @@ mod cli_run {
// filename: "Main.roc", // filename: "Main.roc",
// executable_filename: "custom-malloc-example", // executable_filename: "custom-malloc-example",
// stdin: &[], // stdin: &[],
// input_file: None,
// expected_ending: "ms!\nThe list was small!\n", // expected_ending: "ms!\nThe list was small!\n",
// use_valgrind: true, // use_valgrind: true,
// }, // },
@ -312,9 +348,20 @@ mod cli_run {
// filename: "Main.roc", // filename: "Main.roc",
// executable_filename: "task-example", // executable_filename: "task-example",
// stdin: &[], // stdin: &[],
// input_file: None,
// expected_ending: "successfully wrote to file\n", // expected_ending: "successfully wrote to file\n",
// use_valgrind: true, // use_valgrind: true,
// }, // },
false_interpreter:"false-interpreter" => {
Example {
filename: "False.roc",
executable_filename: "false",
stdin: &[],
input_file: Some("examples/hello.false"),
expected_ending:"Hello, World!\n",
use_valgrind: false,
}
},
} }
macro_rules! benchmarks { macro_rules! benchmarks {
@ -341,6 +388,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&[], &[],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -350,6 +398,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--optimize"], &["--optimize"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -382,6 +431,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&[], &[],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
); );
@ -390,6 +440,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--optimize"], &["--optimize"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
); );
} }
@ -421,6 +472,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--backend=x86_32"], &["--backend=x86_32"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -430,6 +482,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--backend=x86_32", "--optimize"], &["--backend=x86_32", "--optimize"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -458,6 +511,7 @@ mod cli_run {
filename: "NQueens.roc", filename: "NQueens.roc",
executable_filename: "nqueens", executable_filename: "nqueens",
stdin: &["6"], stdin: &["6"],
input_file: None,
expected_ending: "4\n", expected_ending: "4\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -465,6 +519,7 @@ mod cli_run {
filename: "CFold.roc", filename: "CFold.roc",
executable_filename: "cfold", executable_filename: "cfold",
stdin: &["3"], stdin: &["3"],
input_file: None,
expected_ending: "11 & 11\n", expected_ending: "11 & 11\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -472,6 +527,7 @@ mod cli_run {
filename: "Deriv.roc", filename: "Deriv.roc",
executable_filename: "deriv", executable_filename: "deriv",
stdin: &["2"], stdin: &["2"],
input_file: None,
expected_ending: "1 count: 6\n2 count: 22\n", expected_ending: "1 count: 6\n2 count: 22\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -479,6 +535,7 @@ mod cli_run {
filename: "RBTreeCk.roc", filename: "RBTreeCk.roc",
executable_filename: "rbtree-ck", executable_filename: "rbtree-ck",
stdin: &["100"], stdin: &["100"],
input_file: None,
expected_ending: "10\n", expected_ending: "10\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -486,6 +543,7 @@ mod cli_run {
filename: "RBTreeInsert.roc", filename: "RBTreeInsert.roc",
executable_filename: "rbtree-insert", executable_filename: "rbtree-insert",
stdin: &[], stdin: &[],
input_file: None,
expected_ending: "Node Black 0 {} Empty Empty\n", expected_ending: "Node Black 0 {} Empty Empty\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -493,6 +551,7 @@ mod cli_run {
// filename: "RBTreeDel.roc", // filename: "RBTreeDel.roc",
// executable_filename: "rbtree-del", // executable_filename: "rbtree-del",
// stdin: &["420"], // stdin: &["420"],
// input_file: None,
// expected_ending: "30\n", // expected_ending: "30\n",
// use_valgrind: true, // use_valgrind: true,
// }, // },
@ -500,6 +559,7 @@ mod cli_run {
filename: "TestAStar.roc", filename: "TestAStar.roc",
executable_filename: "test-astar", executable_filename: "test-astar",
stdin: &[], stdin: &[],
input_file: None,
expected_ending: "True\n", expected_ending: "True\n",
use_valgrind: false, use_valgrind: false,
}, },
@ -507,6 +567,7 @@ mod cli_run {
filename: "TestBase64.roc", filename: "TestBase64.roc",
executable_filename: "test-base64", executable_filename: "test-base64",
stdin: &[], stdin: &[],
input_file: None,
expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -514,6 +575,7 @@ mod cli_run {
filename: "Closure.roc", filename: "Closure.roc",
executable_filename: "closure", executable_filename: "closure",
stdin: &[], stdin: &[],
input_file: None,
expected_ending: "", expected_ending: "",
use_valgrind: true, use_valgrind: true,
}, },
@ -521,6 +583,7 @@ mod cli_run {
filename: "QuicksortApp.roc", filename: "QuicksortApp.roc",
executable_filename: "quicksortapp", executable_filename: "quicksortapp",
stdin: &[], stdin: &[],
input_file: None,
expected_ending: "todo put the correct quicksort answer here", expected_ending: "todo put the correct quicksort answer here",
use_valgrind: true, use_valgrind: true,
}, },
@ -600,6 +663,7 @@ mod cli_run {
&[], &[],
"multi-dep-str", "multi-dep-str",
&[], &[],
None,
"I am Dep2.str2\n", "I am Dep2.str2\n",
true, true,
); );
@ -613,6 +677,7 @@ mod cli_run {
&[], &[],
"multi-dep-str", "multi-dep-str",
&["--optimize"], &["--optimize"],
None,
"I am Dep2.str2\n", "I am Dep2.str2\n",
true, true,
); );
@ -626,6 +691,7 @@ mod cli_run {
&[], &[],
"multi-dep-thunk", "multi-dep-thunk",
&[], &[],
None,
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,
); );
@ -639,6 +705,7 @@ mod cli_run {
&[], &[],
"multi-dep-thunk", "multi-dep-thunk",
&["--optimize"], &["--optimize"],
None,
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,
); );

View file

@ -116,6 +116,11 @@ mod repl_eval {
); );
} }
#[test]
fn num_ceil_division_success() {
expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*")
}
#[test] #[test]
fn bool_in_record() { fn bool_in_record() {
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");

View file

@ -42,7 +42,7 @@ quickcheck = "0.8"
quickcheck_macros = "0.8" quickcheck_macros = "0.8"
[features] [features]
default = ["llvm", "target-webassembly"] default = ["llvm", "target-webassembly", "target-aarch64"]
target-arm = [] target-arm = []
target-aarch64 = [] target-aarch64 = []
target-webassembly = [] target-webassembly = []

View file

@ -36,7 +36,6 @@ pub fn link(
.. ..
} => link_linux(target, output_path, input_paths, link_type), } => link_linux(target, output_path, input_paths, link_type),
Triple { Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Darwin, operating_system: OperatingSystem::Darwin,
.. ..
} => link_macos(target, output_path, input_paths, link_type), } => link_macos(target, output_path, input_paths, link_type),
@ -724,42 +723,56 @@ fn link_macos(
String::from("-lSystem") String::from("-lSystem")
}; };
Ok(( let arch = match target.architecture {
Architecture::Aarch64(_) => "arm64".to_string(),
_ => target.architecture.to_string(),
};
let mut ld_child = Command::new("ld")
// NOTE: order of arguments to `ld` matters here! // NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments // The `-l` flags should go after the `.o` arguments
Command::new("ld") // Don't allow LD_ env vars to affect this
// Don't allow LD_ env vars to affect this .env_clear()
.env_clear() .args(&[
.args(&[ // NOTE: we don't do --gc-sections on macOS because the default
// NOTE: we don't do --gc-sections on macOS because the default // macOS linker doesn't support it, but it's a performance
// macOS linker doesn't support it, but it's a performance // optimization, so if we ever switch to a different linker,
// optimization, so if we ever switch to a different linker, // we'd like to re-enable it on macOS!
// we'd like to re-enable it on macOS! // "--gc-sections",
// "--gc-sections", link_type_arg,
link_type_arg, "-arch",
"-arch", &arch,
target.architecture.to_string().as_str(), ])
]) .args(input_paths)
.args(input_paths) .args(&[
.args(&[ // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274 // for discussion and further references
// for discussion and further references &big_sur_fix,
&big_sur_fix, "-lSystem",
"-lSystem", "-lresolv",
"-lresolv", "-lpthread",
"-lpthread", // "-lrt", // TODO shouldn't we need this?
// "-lrt", // TODO shouldn't we need this? // "-lc_nonshared", // TODO shouldn't we need this?
// "-lc_nonshared", // TODO shouldn't we need this? // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 // "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli
// "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli // "Security",
// "Security", // Output
// Output "-o",
"-o", output_path.to_str().unwrap(), // app
output_path.to_str().unwrap(), // app ])
]) .spawn()?;
.spawn()?,
output_path, match target.architecture {
)) Architecture::Aarch64(_) => {
ld_child.wait()?;
let child = Command::new("codesign")
.args(&["-s", "-", output_path.to_str().unwrap()])
.spawn()?;
Ok((child, output_path))
}
_ => Ok((ld_child, output_path)),
}
} }
fn link_wasm32( fn link_wasm32(

View file

@ -28,8 +28,8 @@ const LLVM_VERSION: &str = "12";
pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize { pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize {
report_problems_help( report_problems_help(
loaded.total_problems(), loaded.total_problems(),
&loaded.sources,
&loaded.header_sources, &loaded.header_sources,
&loaded.sources,
&loaded.interns, &loaded.interns,
&mut loaded.can_problems, &mut loaded.can_problems,
&mut loaded.type_problems, &mut loaded.type_problems,
@ -40,8 +40,8 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize
pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize {
report_problems_help( report_problems_help(
loaded.total_problems(), loaded.total_problems(),
&loaded.sources,
&loaded.header_sources, &loaded.header_sources,
&loaded.sources,
&loaded.interns, &loaded.interns,
&mut loaded.can_problems, &mut loaded.can_problems,
&mut loaded.type_problems, &mut loaded.type_problems,

View file

@ -31,6 +31,11 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
operating_system: OperatingSystem::Linux, operating_system: OperatingSystem::Linux,
.. ..
} => "aarch64-unknown-linux-gnu", } => "aarch64-unknown-linux-gnu",
Triple {
architecture: Architecture::Aarch64(_),
operating_system: OperatingSystem::Darwin,
..
} => "aarch64-apple-darwin",
Triple { Triple {
architecture: Architecture::X86_64, architecture: Architecture::X86_64,
operating_system: OperatingSystem::Darwin, operating_system: OperatingSystem::Darwin,

View file

@ -1,4 +1,9 @@
const std = @import("std"); const std = @import("std");
const math = std.math;
const utils = @import("utils.zig");
const ROC_BUILTINS = "roc_builtins";
const NUM = "num";
// Dec Module // Dec Module
const dec = @import("dec.zig"); const dec = @import("dec.zig");
@ -72,15 +77,28 @@ comptime {
// Num Module // Num Module
const num = @import("num.zig"); const num = @import("num.zig");
const INTEGERS = [_]type{ i8, i16, i32, i64, i128, u8, u16, u32, u64, u128 };
const FLOATS = [_]type{ f32, f64 };
const NUMBERS = INTEGERS ++ FLOATS;
comptime { comptime {
exportNumFn(num.atan, "atan");
exportNumFn(num.isFinite, "is_finite");
exportNumFn(num.powInt, "pow_int");
exportNumFn(num.acos, "acos");
exportNumFn(num.asin, "asin");
exportNumFn(num.bytesToU16C, "bytes_to_u16"); exportNumFn(num.bytesToU16C, "bytes_to_u16");
exportNumFn(num.bytesToU32C, "bytes_to_u32"); exportNumFn(num.bytesToU32C, "bytes_to_u32");
exportNumFn(num.round, "round");
inline for (INTEGERS) |T| {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
}
inline for (FLOATS) |T| {
num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin.");
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round.");
}
} }
// Str Module // Str Module
@ -106,7 +124,7 @@ comptime {
} }
// Utils // Utils
const utils = @import("utils.zig");
comptime { comptime {
exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefC, "decref");

View file

@ -3,24 +3,67 @@ const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const math = std.math; const math = std.math;
const RocList = @import("list.zig").RocList; const RocList = @import("list.zig").RocList;
pub fn atan(num: f64) callconv(.C) f64 { pub fn exportPow(comptime T: type, comptime name: []const u8) void {
return @call(.{ .modifier = always_inline }, math.atan, .{num}); comptime var f = struct {
fn func(base: T, exp: T) callconv(.C) T {
return std.math.pow(T, base, exp);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn isFinite(num: f64) callconv(.C) bool { pub fn exportIsFinite(comptime T: type, comptime name: []const u8) void {
return @call(.{ .modifier = always_inline }, math.isFinite, .{num}); comptime var f = struct {
fn func(input: T) callconv(.C) bool {
return std.math.isFinite(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn powInt(base: i64, exp: i64) callconv(.C) i64 { pub fn exportAsin(comptime T: type, comptime name: []const u8) void {
return @call(.{ .modifier = always_inline }, math.pow, .{ i64, base, exp }); comptime var f = struct {
fn func(input: T) callconv(.C) T {
return std.math.asin(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn acos(num: f64) callconv(.C) f64 { pub fn exportAcos(comptime T: type, comptime name: []const u8) void {
return @call(.{ .modifier = always_inline }, math.acos, .{num}); comptime var f = struct {
fn func(input: T) callconv(.C) T {
return std.math.acos(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn asin(num: f64) callconv(.C) f64 { pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
return @call(.{ .modifier = always_inline }, math.asin, .{num}); comptime var f = struct {
fn func(input: T) callconv(.C) T {
return std.math.atan(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) i64 {
return @floatToInt(i64, (@round(input)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(a: T, b: T) callconv(.C) T {
return math.divCeil(T, a, b) catch unreachable;
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
} }
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 { pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
@ -40,7 +83,3 @@ fn bytesToU32(arg: RocList, position: usize) u32 {
const bytes = @ptrCast([*]const u8, arg.bytes); const bytes = @ptrCast([*]const u8, arg.bytes);
return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }); return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] });
} }
pub fn round(num: f64) callconv(.C) i64 {
return @floatToInt(i32, (@round(num)));
}

View file

@ -1,16 +1,129 @@
use std::ops::Index;
pub const OBJ_PATH: &str = env!( pub const OBJ_PATH: &str = env!(
"BUILTINS_O", "BUILTINS_O",
"Env var BUILTINS_O not found. Is there a problem with the build script?" "Env var BUILTINS_O not found. Is there a problem with the build script?"
); );
pub const NUM_ASIN: &str = "roc_builtins.num.asin"; #[derive(Debug, Default)]
pub const NUM_ACOS: &str = "roc_builtins.num.acos"; pub struct IntrinsicName {
pub const NUM_ATAN: &str = "roc_builtins.num.atan"; pub options: [&'static str; 14],
pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite"; }
pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int";
impl IntrinsicName {
pub const fn default() -> Self {
Self { options: [""; 14] }
}
}
#[repr(u8)]
pub enum DecWidth {
Dec,
}
#[repr(u8)]
pub enum FloatWidth {
F32,
F64,
F128,
}
#[repr(u8)]
pub enum IntWidth {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
}
impl Index<DecWidth> for IntrinsicName {
type Output = str;
fn index(&self, _: DecWidth) -> &Self::Output {
self.options[0]
}
}
impl Index<FloatWidth> for IntrinsicName {
type Output = str;
fn index(&self, index: FloatWidth) -> &Self::Output {
match index {
FloatWidth::F32 => self.options[1],
FloatWidth::F64 => self.options[2],
FloatWidth::F128 => self.options[3],
}
}
}
impl Index<IntWidth> for IntrinsicName {
type Output = str;
fn index(&self, index: IntWidth) -> &Self::Output {
match index {
IntWidth::U8 => self.options[4],
IntWidth::U16 => self.options[5],
IntWidth::U32 => self.options[6],
IntWidth::U64 => self.options[7],
IntWidth::U128 => self.options[8],
IntWidth::I8 => self.options[9],
IntWidth::I16 => self.options[10],
IntWidth::I32 => self.options[11],
IntWidth::I64 => self.options[12],
IntWidth::I128 => self.options[13],
}
}
}
#[macro_export]
macro_rules! float_intrinsic {
($name:literal) => {{
let mut output = IntrinsicName::default();
output.options[1] = concat!($name, ".f32");
output.options[2] = concat!($name, ".f64");
output.options[3] = concat!($name, ".f128");
output
}};
}
#[macro_export]
macro_rules! int_intrinsic {
($name:literal) => {{
let mut output = IntrinsicName::default();
output.options[4] = concat!($name, ".i8");
output.options[5] = concat!($name, ".i16");
output.options[6] = concat!($name, ".i32");
output.options[7] = concat!($name, ".i64");
output.options[8] = concat!($name, ".i128");
output.options[9] = concat!($name, ".i8");
output.options[10] = concat!($name, ".i16");
output.options[11] = concat!($name, ".i32");
output.options[12] = concat!($name, ".i64");
output.options[13] = concat!($name, ".i128");
output
}};
}
pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite");
pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
pub const NUM_ROUND: IntrinsicName = float_intrinsic!("roc_builtins.num.round");
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
pub const NUM_ROUND: &str = "roc_builtins.num.round";
pub const STR_INIT: &str = "roc_builtins.str.init"; pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";

View file

@ -293,18 +293,25 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// minInt : Int range // minInt : Int range
add_type!(Symbol::NUM_MIN_INT, int_type(flex(TVAR1))); add_type!(Symbol::NUM_MIN_INT, int_type(flex(TVAR1)));
// divInt : Int a, Int a -> Result (Int a) [ DivByZero ]*
let div_by_zero = SolvedType::TagUnion( let div_by_zero = SolvedType::TagUnion(
vec![(TagName::Global("DivByZero".into()), vec![])], vec![(TagName::Global("DivByZero".into()), vec![])],
Box::new(SolvedType::Wildcard), Box::new(SolvedType::Wildcard),
); );
// divInt : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_DIV_INT, Symbol::NUM_DIV_INT,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
); );
//divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_CEIL,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
);
// bitwiseAnd : Int a, Int a -> Int a // bitwiseAnd : Int a, Int a -> Int a
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_BITWISE_AND, Symbol::NUM_BITWISE_AND,

View file

@ -143,6 +143,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_TAN => num_tan, NUM_TAN => num_tan,
NUM_DIV_FLOAT => num_div_float, NUM_DIV_FLOAT => num_div_float,
NUM_DIV_INT => num_div_int, NUM_DIV_INT => num_div_int,
NUM_DIV_CEIL => num_div_ceil,
NUM_ABS => num_abs, NUM_ABS => num_abs,
NUM_NEG => num_neg, NUM_NEG => num_neg,
NUM_REM => num_rem, NUM_REM => num_rem,
@ -2844,6 +2845,72 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Num.divCeil : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
let unbound_zero_precision_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(
// Num.neq denominator 0
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(num_var, Var(Symbol::ARG_2)),
(
num_var,
int(unbound_zero_var, unbound_zero_precision_var, 0),
),
],
ret_var: bool_var,
},
),
// denominator was not zero
no_region(
// Ok (Int.#divUnchecked numerator denominator)
tag(
"Ok",
vec![
// Num.#divUnchecked numerator denominator
RunLowLevel {
op: LowLevel::NumDivCeilUnchecked,
args: vec![
(num_var, Var(Symbol::ARG_1)),
(num_var, Var(Symbol::ARG_2)),
],
ret_var: num_var,
},
],
var_store,
),
),
)],
final_else: Box::new(
// denominator was zero
no_region(tag(
"Err",
vec![tag("DivByZero", Vec::new(), var_store)],
var_store,
)),
),
};
defn(
symbol,
vec![(num_var, Symbol::ARG_1), (num_var, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
}
/// List.first : List elem -> Result elem [ ListWasEmpty ]* /// List.first : List elem -> Result elem [ ListWasEmpty ]*
/// ///
/// List.first : /// List.first :

View file

@ -18,7 +18,7 @@ use roc_region::all::Located;
/// Just (Just a) /// Just (Just a)
/// List (List a) /// List (List a)
/// reverse (reverse l) /// reverse (reverse l)
#[derive(PartialEq, Eq, Clone, Copy)] #[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Parens { pub enum Parens {
NotNeeded, NotNeeded,
InFunctionType, InFunctionType,

View file

@ -133,9 +133,13 @@ impl<'a> Formattable<'a> for Expr<'a> {
} }
} }
ParensAround(sub_expr) => { ParensAround(sub_expr) => {
buf.push('('); if parens == Parens::NotNeeded && !sub_expr_requests_parens(sub_expr) {
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
buf.push(')'); } else {
buf.push('(');
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
buf.push(')');
}
} }
Str(literal) => { Str(literal) => {
use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrLiteral::*;
@ -315,7 +319,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
buf.push_str(key); buf.push_str(key);
} }
Access(expr, key) => { Access(expr, key) => {
expr.format_with_options(buf, parens, Newlines::Yes, indent); expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.'); buf.push('.');
buf.push_str(key); buf.push_str(key);
} }
@ -394,6 +398,8 @@ fn fmt_bin_ops<'a>(
|| lefts.iter().any(|(expr, _)| expr.value.is_multiline()); || lefts.iter().any(|(expr, _)| expr.value.is_multiline());
for (loc_left_side, loc_bin_op) in lefts { for (loc_left_side, loc_bin_op) in lefts {
let bin_op = loc_bin_op.value;
loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, indent); loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, indent);
if is_multiline { if is_multiline {
@ -402,7 +408,7 @@ fn fmt_bin_ops<'a>(
buf.push(' '); buf.push(' ');
} }
push_op(buf, loc_bin_op.value); push_op(buf, bin_op);
buf.push(' '); buf.push(' ');
} }
@ -1046,3 +1052,32 @@ fn format_field_multiline<'a, T>(
} }
} }
} }
fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
match expr {
Expr::BinOps(left_side, _) => {
left_side
.iter()
.any(|(_, loc_bin_op)| match loc_bin_op.value {
BinOp::Caret
| BinOp::Star
| BinOp::Slash
| BinOp::DoubleSlash
| BinOp::Percent
| BinOp::DoublePercent
| BinOp::Plus
| BinOp::Minus
| BinOp::Equals
| BinOp::NotEquals
| BinOp::LessThan
| BinOp::GreaterThan
| BinOp::LessThanOrEq
| BinOp::GreaterThanOrEq
| BinOp::And
| BinOp::Or => true,
BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false,
})
}
_ => false,
}
}

View file

@ -362,32 +362,57 @@ mod test_fmt {
); );
} }
// #[test] #[test]
// fn defs_with_defs() { fn excess_parens() {
// expr_formats_to( expr_formats_to(
// indoc!( indoc!(
// r#" r#"
// x = x = (5)
// y = 4
// z = 8
// w y = ((10))
42
"#
),
indoc!(
r#"
x = 5
y = 10
42
"#
),
);
}
// x #[test]
// "# fn defs_with_defs() {
// ), expr_formats_to(
// indoc!( indoc!(
// r#" r#"
// x = x =
// y = 4 y = 4
// z = 8 z = 8
w
// w x
"#
),
indoc!(
r#"
x =
y = 4
z = 8
// x w
// "#
// ), x
// ); "#
// } ),
);
}
#[test] #[test]
fn comment_between_two_defs() { fn comment_between_two_defs() {
@ -548,15 +573,16 @@ mod test_fmt {
)); ));
} }
// #[test] #[test]
// fn record_field_destructuring() { fn record_field_destructuring() {
// expr_formats_same(indoc!( expr_formats_same(indoc!(
// r#" r#"
// when foo is when foo is
// { x: 5 } -> 42 { x: 5 } ->
// "# 42
// )); "#
// } ));
}
#[test] #[test]
fn record_updating() { fn record_updating() {
@ -917,24 +943,43 @@ mod test_fmt {
"# "#
)); ));
// expr_formats_to( expr_formats_to(
// indoc!( indoc!(
// r#" r#"
// identity = \a identity = \a
// -> a -> a
// identity 41 identity 41
// "# "#
// ), ),
// indoc!( indoc!(
// r#" r#"
// identity = \a -> identity = \a -> a
// a
// identity 41 identity 41
// "# "#
// ), ),
// ); );
expr_formats_to(
indoc!(
r#"
identity = \a
->
a + b
identity 4010
"#
),
indoc!(
r#"
identity = \a ->
a + b
identity 4010
"#
),
);
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
@ -944,19 +989,19 @@ mod test_fmt {
"# "#
)); ));
// expr_formats_same(indoc!( // expr_formats_same(indoc!(
// r#" // r#"
// identity = // identity =
// \{ // \{
// x, // x,
// y // y
// } // }
// -> a // -> a
//
// identity 43
// "#
// ));
// //
// identity 43
// "#
// ));
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
identity = \a, identity = \a,
@ -1212,17 +1257,17 @@ mod test_fmt {
} }
#[test] #[test]
fn multi_line_list_def() { fn multi_line_list_def() {
// expr_formats_same(indoc!( expr_formats_same(indoc!(
// r#" r#"
// l = l =
// [ [
// 1, 1,
// 2 2,
// ] ]
// l l
// "# "#
// )); ));
expr_formats_to( expr_formats_to(
indoc!( indoc!(
@ -1248,32 +1293,32 @@ mod test_fmt {
), ),
); );
// expr_formats_to( expr_formats_to(
// indoc!( indoc!(
// r#" r#"
// results = results =
// # Let's count past 6 # Let's count past 6
// [ [
// Ok 6, Ok 6,
// Err CountError Err CountError
// ] ]
// allOks results allOks results
// "# "#
// ), ),
// indoc!( indoc!(
// r#" r#"
// results = results =
// # Let's count past 6 # Let's count past 6
// [ [
// Ok 6, Ok 6,
// Err CountError Err CountError,
// ] ]
// allOks results allOks results
// "# "#
// ), ),
// ); );
} }
// RECORD LITERALS // RECORD LITERALS
@ -1330,18 +1375,18 @@ mod test_fmt {
#[test] #[test]
fn multi_line_record_def() { fn multi_line_record_def() {
// expr_formats_same(indoc!( expr_formats_same(indoc!(
// r#" r#"
// pos = pos =
// { {
// x: 4, x: 4,
// y: 11, y: 11,
// z: 16 z: 16,
// } }
// pos pos
// "# "#
// )); ));
expr_formats_to( expr_formats_to(
indoc!( indoc!(
@ -2204,7 +2249,7 @@ mod test_fmt {
} }
#[test] #[test]
fn precedence_conflict_exponents() { fn binop_parens() {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
if 4 == (6 ^ 6 ^ 7 ^ 8) then if 4 == (6 ^ 6 ^ 7 ^ 8) then
@ -2213,6 +2258,39 @@ mod test_fmt {
"Naturally" "Naturally"
"# "#
)); ));
expr_formats_same(indoc!(
r#"
if 5 == 1 ^ 1 ^ 1 ^ 1 then
"Not buying it"
else
"True"
"#
));
expr_formats_to(
indoc!(
r#"
if (1 == 1)
&& (2 == 1) && (3 == 2) then
"true"
else
"false"
"#
),
indoc!(
r#"
if
(1 == 1)
&& (2 == 1)
&& (3 == 2)
then
"true"
else
"false"
"#
),
);
} }
#[test] #[test]

View file

@ -3,7 +3,7 @@
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
use bumpalo::{collections::Vec, Bump}; use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode; use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{ModuleName, TagName}; use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
@ -400,21 +400,21 @@ where
} }
LowLevel::NumAcos => self.build_fn_call( LowLevel::NumAcos => self.build_fn_call(
sym, sym,
bitcode::NUM_ACOS.to_string(), bitcode::NUM_ACOS[FloatWidth::F64].to_string(),
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::NumAsin => self.build_fn_call( LowLevel::NumAsin => self.build_fn_call(
sym, sym,
bitcode::NUM_ASIN.to_string(), bitcode::NUM_ASIN[FloatWidth::F64].to_string(),
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::NumAtan => self.build_fn_call( LowLevel::NumAtan => self.build_fn_call(
sym, sym,
bitcode::NUM_ATAN.to_string(), bitcode::NUM_ATAN[FloatWidth::F64].to_string(),
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
@ -437,7 +437,7 @@ where
} }
LowLevel::NumPowInt => self.build_fn_call( LowLevel::NumPowInt => self.build_fn_call(
sym, sym,
bitcode::NUM_POW_INT.to_string(), bitcode::NUM_POW_INT[IntWidth::I64].to_string(),
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
@ -473,7 +473,7 @@ where
} }
LowLevel::NumRound => self.build_fn_call( LowLevel::NumRound => self.build_fn_call(
sym, sym,
bitcode::NUM_ROUND.to_string(), bitcode::NUM_ROUND[FloatWidth::F64].to_string(),
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,

File diff suppressed because it is too large Load diff

View file

@ -502,38 +502,6 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
env.builder.build_load(result_ptr, "load_result") env.builder.build_load(result_ptr, "load_result")
} }
#[allow(dead_code)]
#[repr(u8)]
enum IntWidth {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
Usize,
}
impl From<roc_mono::layout::Builtin<'_>> for IntWidth {
fn from(builtin: Builtin) -> Self {
use IntWidth::*;
match builtin {
Builtin::Int128 => I128,
Builtin::Int64 => I64,
Builtin::Int32 => I32,
Builtin::Int16 => I16,
Builtin::Int8 => I8,
Builtin::Usize => Usize,
_ => unreachable!(),
}
}
}
/// List.range : Int a, Int a -> List (Int a) /// List.range : Int a, Int a -> List (Int a)
pub fn list_range<'a, 'ctx, 'env>( pub fn list_range<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -552,7 +520,10 @@ pub fn list_range<'a, 'ctx, 'env>(
let int_width = env let int_width = env
.context .context
.i8_type() .i8_type()
.const_int(IntWidth::from(builtin) as u64, false) .const_int(
crate::llvm::build::intwidth_from_builtin(builtin, env.ptr_bytes) as u64,
false,
)
.into(); .into();
call_bitcode_fn( call_bitcode_fn(

View file

@ -1,3 +1,4 @@
*.wasm *.wasm
*.wat *.wat
/notes.md /notes.md
/tmp

View file

@ -10,7 +10,8 @@ roc_collections = { path = "../collections" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
parity-wasm = "0.42" # 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" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
wasmer = "2.0.0" wasmer = "2.0.0"

View file

@ -14,14 +14,14 @@
- [x] Push and pop stack frames - [x] Push and pop stack frames
- [x] Deal with returning structs - [x] Deal with returning structs
- [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc. - [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc.
- [ ] Ensure early Return statements don't skip stack cleanup - [x] Ensure early Return statements don't skip stack cleanup
- [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec` - [x] Model the stack machine as a storage mechanism, to make generated code "less bad"
- [x] Switch vectors to `bumpalo::Vec` where possible
- [ ] Implement relocations - [ ] Implement relocations
- Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec<Instruction>` rather than a `Vec<u8>`. It may be worth serialising each instruction as it is inserted. - Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec<Instruction>` rather than a `Vec<u8>`. It may be worth serialising each instruction as it is inserted.
- Refactor for code sharing with CPU backends - Refactor for code sharing with CPU backends
- [ ] Implement a `scan_ast` pre-pass like `Backend` does, but for reusing Wasm locals rather than CPU registers
- [ ] Extract a trait from `WasmBackend` that looks as similar as possible to `Backend`, to prepare for code sharing - [ ] Extract a trait from `WasmBackend` that looks as similar as possible to `Backend`, to prepare for code sharing
- [ ] Refactor to actually share code between `WasmBackend` and `Backend` if it seems feasible - [ ] Refactor to actually share code between `WasmBackend` and `Backend` if it seems feasible
@ -88,7 +88,14 @@ main =
1 + 2 + 4 1 + 2 + 4
``` ```
### Direct translation of Mono IR
The Mono IR contains two functions, `Num.add` and `main`, so we generate two corresponding WebAssembly functions. The Mono IR contains two functions, `Num.add` and `main`, so we generate two corresponding WebAssembly functions.
Since it has a Symbol for every expression, the simplest thing is to create a local for each one.
The code ends up being quite bloated, with lots of `local.set` and `local.get` instructions.
I've added comments on each line to show what is on the stack and in the locals at each point in the program.
``` ```
(func (;0;) (param i64 i64) (result i64) ; declare function index 0 (Num.add) with two i64 parameters and an i64 result (func (;0;) (param i64 i64) (result i64) ; declare function index 0 (Num.add) with two i64 parameters and an i64 result
@ -115,13 +122,10 @@ The Mono IR contains two functions, `Num.add` and `main`, so we generate two cor
return) ; return the value at the top of the stack return) ; return the value at the top of the stack
``` ```
If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away (which is all of them in this example!). The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). ### Handwritten equivalent
``` This code doesn't actually require any locals at all.
$ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm (It also doesn't need the `return` instructions, but that's less of a problem.)
```
The optimised functions have no local variables at all for this example. (Of course, this is an oversimplified toy example! It might not be so extreme in a real program.)
``` ```
(func (;0;) (param i64 i64) (result i64) (func (;0;) (param i64 i64) (result i64)
@ -138,13 +142,36 @@ The optimised functions have no local variables at all for this example. (Of cou
### Reducing sets and gets ### Reducing sets and gets
It would be nice to find some cheap optimisation to reduce the number of `local.set` and `local.get` instructions. For our example code, we don't need any locals because the WebAssembly virtual machine effectively _stores_ intermediate results in a stack. Since it's already storing those values, there is no need for us to create locals. If you compare the two versions, you'll see that the `local.set` and `local.get` instructions have simply been deleted and the other instructions are in the same order.
We don't need a `local` if the value we want is already at the top of the VM stack. In fact, for our example above, it just so happens that if we simply skip generating the `local.set` instructions, everything _does_ appear on the VM stack in the right order, which means we can skip the `local.get` too. It ends up being very close to the fully optimised version! I assume this is because the Mono IR within the function is in dependency order, but I'm not sure... But sometimes we really do need locals! We may need to use the same symbol twice, or values could end up on the stack in the wrong order and need to be swapped around by setting a local and getting it again.
Of course the trick is to do this reliably for more complex dependency graphs. I am investigating whether we can do it by optimistically assuming it's OK not to create a local, and then keeping track of which symbols are at which positions in the VM stack after every instruction. Then when we need to use a symbol we can first check if it's on the VM stack and only create a local if it's not. In cases where we _do_ need to create a local, we need to go back and insert a `local.set` instruction at an earlier point in the program. We can make this fast by waiting to do all of the insertions in one batch when we're finalising the procedure. The hard part is knowing when we need a local, and when we don't. For that, the `WasmBackend` needs to have some understanding of the stack machine.
For a while we thought it would be very helpful to reuse the same local for multiple symbols at different points in the program. And we already have similar code in the CPU backends for register allocation. But on further examination, it doesn't actually buy us much! In our example above, we would still have the same number of `local.set` and `local.get` instructions - they'd just be operating on two locals instead of four! That doesn't shrink much code. Only the declaration at the top of the function would shrink from `(local i64 i64 i64 i64)` to `(local i64 i64)`... and in fact that's only smaller in the text format, it's the same size in the binary format! So the `scan_ast` pass doesn't seem worthwhile for Wasm. To help with this, the `CodeBuilder` maintains a vector that represents the stack. For every instruction the backend generates, `CodeBuilder` simulates the right number of pushes and pops for that instruction, so that we always know the state of the VM stack at every point in the program.
When the `WasmBackend` generates code for a `Let` statement, it can "label" the top of the stack with the relevant `Symbol`. Then at any later point in the program, when we need to retrieve a list of symbols in a certain order, we can check whether they already happen to be at the top of the stack in that order (as they were in our example above.)
In practice it should be very common for values to appear on the VM stack in the right order, because in the Mono IR, statements occur in dependency order! We should only generate locals when the dependency graph is a little more complicated, and we actually need them.
```
┌─────────────────┐ ┌─────────────┐
│ │ │ │
│ ├─────► Storage ├──────┐
│ │ │ │ │
│ │ └─────────────┘ │
│ │ Manage state about │
│ │ how/where symbol │ Delegate part of
│ WasmBackend │ values are stored │ state management
│ │ │ for values on
│ │ │ the VM stack
│ │ │
│ │ Generate ┌────────▼──────┐
│ │ instructions │ │
│ ├─────────────────► CodeBuilder │
│ │ │ │
└─────────────────┘ └───────────────┘
```
## Memory ## Memory

View file

@ -1,3 +1,4 @@
use bumpalo::collections::Vec;
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder}; use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder};
use parity_wasm::elements::{ use parity_wasm::elements::{
@ -10,11 +11,11 @@ use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use crate::code_builder::CodeBuilder;
use crate::layout::WasmLayout; use crate::layout::WasmLayout;
use crate::storage::{StackMemoryLocation, SymbolStorage}; use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::{ use crate::{
copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig, copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, Env, LocalId, PTR_TYPE,
LocalId, PTR_SIZE, PTR_TYPE,
}; };
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
@ -24,84 +25,73 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct LabelId(u32); struct LabelId(u32);
enum LocalKind {
Parameter,
Variable,
}
// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43) // TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43)
pub struct WasmBackend<'a> { pub struct WasmBackend<'a> {
// Module: Wasm AST // Module level: Wasm AST
pub builder: ModuleBuilder, pub module_builder: ModuleBuilder,
env: &'a Env<'a>,
// Module: internal state & IR mappings // Module level: internal state & IR mappings
_data_offset_map: MutMap<Literal<'a>, u32>, _data_offset_map: MutMap<Literal<'a>, u32>,
_data_offset_next: u32, _data_offset_next: u32,
proc_symbol_map: MutMap<Symbol, CodeLocation>, proc_symbol_map: MutMap<Symbol, CodeLocation>,
// Functions: Wasm AST // Function level
instructions: std::vec::Vec<Instruction>, code_builder: CodeBuilder<'a>,
arg_types: std::vec::Vec<ValueType>, storage: Storage<'a>,
locals: std::vec::Vec<Local>,
// Functions: internal state & IR mappings
stack_memory: i32,
stack_frame_pointer: Option<LocalId>,
symbol_storage_map: MutMap<Symbol, SymbolStorage>,
/// how many blocks deep are we (used for jumps) /// how many blocks deep are we (used for jumps)
block_depth: u32, block_depth: u32,
joinpoint_label_map: MutMap<JoinPointId, (u32, std::vec::Vec<LocalId>)>, joinpoint_label_map: MutMap<JoinPointId, (u32, Vec<'a, StoredValue>)>,
} }
impl<'a> WasmBackend<'a> { impl<'a> WasmBackend<'a> {
pub fn new() -> Self { pub fn new(env: &'a Env<'a>) -> Self {
WasmBackend { WasmBackend {
// Module: Wasm AST // Module: Wasm AST
builder: builder::module(), module_builder: builder::module(),
env,
// Module: internal state & IR mappings // Module: internal state & IR mappings
_data_offset_map: MutMap::default(), _data_offset_map: MutMap::default(),
_data_offset_next: UNUSED_DATA_SECTION_BYTES, _data_offset_next: UNUSED_DATA_SECTION_BYTES,
proc_symbol_map: MutMap::default(), proc_symbol_map: MutMap::default(),
// Functions: Wasm AST
instructions: std::vec::Vec::with_capacity(256),
arg_types: std::vec::Vec::with_capacity(8),
locals: std::vec::Vec::with_capacity(32),
// Functions: internal state & IR mappings
stack_memory: 0,
stack_frame_pointer: None,
symbol_storage_map: MutMap::default(),
block_depth: 0, block_depth: 0,
joinpoint_label_map: MutMap::default(), joinpoint_label_map: MutMap::default(),
// Functions
code_builder: CodeBuilder::new(env.arena),
storage: Storage::new(env.arena),
} }
} }
fn reset(&mut self) { fn reset(&mut self) {
// Functions: Wasm AST self.code_builder.clear();
self.instructions.clear(); self.storage.clear();
self.arg_types.clear();
self.locals.clear();
// Functions: internal state & IR mappings
self.stack_memory = 0;
self.stack_frame_pointer = None;
self.symbol_storage_map.clear();
self.joinpoint_label_map.clear(); self.joinpoint_label_map.clear();
assert_eq!(self.block_depth, 0); assert_eq!(self.block_depth, 0);
} }
/**********************************************************
PROCEDURE
***********************************************************/
pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
// println!("\ngenerating procedure {:?}\n", sym);
let signature_builder = self.start_proc(&proc); let signature_builder = self.start_proc(&proc);
self.build_stmt(&proc.body, &proc.ret_layout)?; self.build_stmt(&proc.body, &proc.ret_layout)?;
let function_def = self.finalize_proc(signature_builder); let function_def = self.finalize_proc(signature_builder);
let location = self.builder.push_function(function_def); let location = self.module_builder.push_function(function_def);
let function_index = location.body; let function_index = location.body;
self.proc_symbol_map.insert(sym, location); self.proc_symbol_map.insert(sym, location);
self.reset(); self.reset();
// println!("\nfinished generating {:?}\n", sym);
Ok(function_index) Ok(function_index)
} }
@ -110,7 +100,7 @@ impl<'a> WasmBackend<'a> {
let ret_layout = WasmLayout::new(&proc.ret_layout); let ret_layout = WasmLayout::new(&proc.ret_layout);
let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
self.arg_types.push(PTR_TYPE); self.storage.arg_types.push(PTR_TYPE);
self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any) self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any)
builder::signature() builder::signature()
} else { } else {
@ -120,213 +110,117 @@ impl<'a> WasmBackend<'a> {
}; };
for (layout, symbol) in proc.args { for (layout, symbol) in proc.args {
self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); self.storage.allocate(
&WasmLayout::new(layout),
*symbol,
StoredValueKind::Parameter,
);
} }
signature_builder.with_params(self.arg_types.clone()) signature_builder.with_params(self.storage.arg_types.clone())
} }
fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition {
self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any) self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any)
let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); const STACK_FRAME_INSTRUCTIONS_LEN: usize = 10;
let mut final_instructions =
std::vec::Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN);
if self.stack_memory > 0 { if self.storage.stack_frame_size > 0 {
push_stack_frame( push_stack_frame(
&mut final_instructions, &mut final_instructions,
self.stack_memory, self.storage.stack_frame_size,
self.stack_frame_pointer.unwrap(), self.storage.stack_frame_pointer.unwrap(),
); );
} }
final_instructions.extend(self.instructions.drain(0..)); self.code_builder.finalize_into(&mut final_instructions);
if self.stack_memory > 0 { if self.storage.stack_frame_size > 0 {
pop_stack_frame( pop_stack_frame(
&mut final_instructions, &mut final_instructions,
self.stack_memory, self.storage.stack_frame_size,
self.stack_frame_pointer.unwrap(), self.storage.stack_frame_pointer.unwrap(),
); );
} }
final_instructions.push(End); final_instructions.push(End);
// Declare local variables (in batches of the same type)
let num_locals = self.storage.local_types.len();
let mut locals = Vec::with_capacity_in(num_locals, self.env.arena);
if num_locals > 0 {
let mut batch_type = self.storage.local_types[0];
let mut batch_size = 0;
for t in &self.storage.local_types {
if *t == batch_type {
batch_size += 1;
} else {
locals.push(Local::new(batch_size, batch_type));
batch_type = *t;
batch_size = 1;
}
}
locals.push(Local::new(batch_size, batch_type));
}
builder::function() builder::function()
.with_signature(signature_builder.build_sig()) .with_signature(signature_builder.build_sig())
.body() .body()
.with_locals(self.locals.clone()) .with_locals(locals)
.with_instructions(Instructions::new(final_instructions)) .with_instructions(Instructions::new(final_instructions))
.build() // body .build() // body
.build() // function .build() // function
} }
fn insert_local( /**********************************************************
&mut self,
wasm_layout: WasmLayout,
symbol: Symbol,
kind: LocalKind,
) -> Option<LocalId> {
let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32);
match kind { STATEMENTS
LocalKind::Parameter => {
self.arg_types.push(wasm_layout.value_type());
}
LocalKind::Variable => {
self.locals.push(Local::new(1, wasm_layout.value_type()));
}
}
let (maybe_local_id, storage) = match wasm_layout { ***********************************************************/
WasmLayout::LocalOnly(value_type, size) => (
Some(next_local_id),
SymbolStorage::Local {
local_id: next_local_id,
value_type,
size,
},
),
WasmLayout::HeapMemory => (
Some(next_local_id),
SymbolStorage::Local {
local_id: next_local_id,
value_type: PTR_TYPE,
size: PTR_SIZE,
},
),
WasmLayout::StackMemory {
size,
alignment_bytes,
} => {
let location = match kind {
LocalKind::Parameter => StackMemoryLocation::PointerArg(next_local_id),
LocalKind::Variable => {
match self.stack_frame_pointer {
Some(_) => {}
None => {
self.stack_frame_pointer = Some(next_local_id);
}
};
let offset =
round_up_to_alignment(self.stack_memory, alignment_bytes as i32);
self.stack_memory = offset + size as i32;
StackMemoryLocation::FrameOffset(offset as u32)
}
};
(
None,
SymbolStorage::StackMemory {
location,
size,
alignment_bytes,
},
)
}
};
self.symbol_storage_map.insert(symbol, storage);
maybe_local_id
}
fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage {
self.symbol_storage_map.get(sym).unwrap_or_else(|| {
panic!(
"Symbol {:?} not found in function scope:\n{:?}",
sym, self.symbol_storage_map
)
})
}
fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId {
let storage = self.get_symbol_storage(sym);
match storage {
SymbolStorage::Local { local_id, .. } => *local_id,
_ => {
panic!("{:?} does not have a local_id", sym);
}
}
}
/// Load a symbol, e.g. for passing to a function call
fn load_symbol(&mut self, sym: &Symbol) {
let storage = self.get_symbol_storage(sym).to_owned();
match storage {
SymbolStorage::Local { local_id, .. }
| SymbolStorage::StackMemory {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
self.instructions.push(GetLocal(local_id.0));
}
SymbolStorage::StackMemory {
location: StackMemoryLocation::FrameOffset(offset),
..
} => {
self.instructions.extend([
GetLocal(self.stack_frame_pointer.unwrap().0),
I32Const(offset as i32),
I32Add,
]);
}
}
}
/// start a loop that leaves a value on the stack /// start a loop that leaves a value on the stack
fn start_loop_with_return(&mut self, value_type: ValueType) { fn start_loop_with_return(&mut self, value_type: ValueType) {
self.block_depth += 1; self.block_depth += 1;
self.instructions.push(Loop(BlockType::Value(value_type))); self.code_builder.push(Loop(BlockType::Value(value_type)));
} }
fn start_block(&mut self, block_type: BlockType) { fn start_block(&mut self, block_type: BlockType) {
self.block_depth += 1; self.block_depth += 1;
self.instructions.push(Block(block_type)); self.code_builder.push(Block(block_type));
} }
fn end_block(&mut self) { fn end_block(&mut self) {
self.block_depth -= 1; self.block_depth -= 1;
self.instructions.push(End); self.code_builder.push(End);
} }
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
match stmt { match stmt {
// Simple optimisation: if we are just returning the expression, we don't need a local
Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => {
let wasm_layout = WasmLayout::new(layout);
if let WasmLayout::StackMemory {
size,
alignment_bytes,
} = wasm_layout
{
// Map this symbol to the first argument (pointer into caller's stack)
// Saves us from having to copy it later
let storage = SymbolStorage::StackMemory {
location: StackMemoryLocation::PointerArg(LocalId(0)),
size,
alignment_bytes,
};
self.symbol_storage_map.insert(*let_sym, storage);
}
self.build_expr(let_sym, expr, layout)?;
self.instructions.push(Br(self.block_depth)); // jump to end of function (stack frame pop)
Ok(())
}
Stmt::Let(sym, expr, layout, following) => { Stmt::Let(sym, expr, layout, following) => {
let wasm_layout = WasmLayout::new(layout); let wasm_layout = WasmLayout::new(layout);
let maybe_local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable);
let kind = match following {
Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue,
_ => StoredValueKind::Variable,
};
self.storage.allocate(&wasm_layout, *sym, kind);
self.build_expr(sym, expr, layout)?; self.build_expr(sym, expr, layout)?;
if let Some(local_id) = maybe_local_id { // For primitives, we record that this symbol is at the top of the VM stack
self.instructions.push(SetLocal(local_id.0)); // (For other values, we wrote to memory and there's nothing on the VM stack)
if let WasmLayout::Primitive(value_type, size) = wasm_layout {
let vm_state = self.code_builder.set_top_symbol(*sym);
self.storage.symbol_storage_map.insert(
*sym,
StoredValue::VirtualMachineStack {
vm_state,
value_type,
size,
},
);
} }
self.build_stmt(following, ret_layout)?; self.build_stmt(following, ret_layout)?;
@ -334,9 +228,9 @@ impl<'a> WasmBackend<'a> {
} }
Stmt::Ret(sym) => { Stmt::Ret(sym) => {
use crate::storage::SymbolStorage::*; use crate::storage::StoredValue::*;
let storage = self.symbol_storage_map.get(sym).unwrap(); let storage = self.storage.symbol_storage_map.get(sym).unwrap();
match storage { match storage {
StackMemory { StackMemory {
@ -344,15 +238,10 @@ impl<'a> WasmBackend<'a> {
size, size,
alignment_bytes, alignment_bytes,
} => { } => {
let (from_ptr, from_offset) = match location { let (from_ptr, from_offset) =
StackMemoryLocation::PointerArg(local_id) => (*local_id, 0), location.local_and_offset(self.storage.stack_frame_pointer);
StackMemoryLocation::FrameOffset(offset) => {
(self.stack_frame_pointer.unwrap(), *offset)
}
};
copy_memory( copy_memory(
&mut self.instructions, &mut self.code_builder,
CopyMemoryConfig { CopyMemoryConfig {
from_ptr, from_ptr,
from_offset, from_offset,
@ -364,9 +253,9 @@ impl<'a> WasmBackend<'a> {
); );
} }
Local { local_id, .. } => { _ => {
self.instructions.push(GetLocal(local_id.0)); self.storage.load_symbols(&mut self.code_builder, &[*sym]);
self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop)
} }
} }
@ -384,26 +273,33 @@ impl<'a> WasmBackend<'a> {
// We may be able to improve this in the future with `Select` // We may be able to improve this in the future with `Select`
// or `BrTable` // or `BrTable`
// Ensure the condition value is not stored only in the VM stack
// Otherwise we can't reach it from inside the block
let cond_storage = self.storage.get(cond_symbol).to_owned();
self.storage.ensure_value_has_local(
&mut self.code_builder,
*cond_symbol,
cond_storage,
);
// create (number_of_branches - 1) new blocks. // create (number_of_branches - 1) new blocks.
for _ in 0..branches.len() { for _ in 0..branches.len() {
self.start_block(BlockType::NoResult) self.start_block(BlockType::NoResult)
} }
// the LocalId of the symbol that we match on
let matched_on = self.local_id_from_symbol(cond_symbol);
// then, we jump whenever the value under scrutiny is equal to the value of a branch // then, we jump whenever the value under scrutiny is equal to the value of a branch
for (i, (value, _, _)) in branches.iter().enumerate() { for (i, (value, _, _)) in branches.iter().enumerate() {
// put the cond_symbol on the top of the stack // put the cond_symbol on the top of the stack
self.instructions.push(GetLocal(matched_on.0)); self.storage
.load_symbols(&mut self.code_builder, &[*cond_symbol]);
self.instructions.push(I32Const(*value as i32)); self.code_builder.push(I32Const(*value as i32));
// compare the 2 topmost values // compare the 2 topmost values
self.instructions.push(I32Eq); self.code_builder.push(I32Eq);
// "break" out of `i` surrounding blocks // "break" out of `i` surrounding blocks
self.instructions.push(BrIf(i as u32)); self.code_builder.push(BrIf(i as u32));
} }
// if we never jumped because a value matched, we're in the default case // if we never jumped because a value matched, we're in the default case
@ -427,19 +323,26 @@ impl<'a> WasmBackend<'a> {
remainder, remainder,
} => { } => {
// make locals for join pointer parameters // make locals for join pointer parameters
let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena);
for parameter in parameters.iter() { for parameter in parameters.iter() {
let wasm_layout = WasmLayout::new(&parameter.layout); let wasm_layout = WasmLayout::new(&parameter.layout);
let maybe_local_id = let mut param_storage = self.storage.allocate(
self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable); &wasm_layout,
let jp_param_id = maybe_local_id.unwrap(); parameter.symbol,
jp_parameter_local_ids.push(jp_param_id); StoredValueKind::Variable,
);
param_storage = self.storage.ensure_value_has_local(
&mut self.code_builder,
parameter.symbol,
param_storage,
);
jp_param_storages.push(param_storage);
} }
self.start_block(BlockType::NoResult); self.start_block(BlockType::NoResult);
self.joinpoint_label_map self.joinpoint_label_map
.insert(*id, (self.block_depth, jp_parameter_local_ids)); .insert(*id, (self.block_depth, jp_param_storages));
self.build_stmt(remainder, ret_layout)?; self.build_stmt(remainder, ret_layout)?;
@ -458,18 +361,21 @@ impl<'a> WasmBackend<'a> {
Ok(()) Ok(())
} }
Stmt::Jump(id, arguments) => { Stmt::Jump(id, arguments) => {
let (target, locals) = &self.joinpoint_label_map[id]; let (target, param_storages) = self.joinpoint_label_map[id].clone();
// put the arguments on the stack for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) {
for (symbol, local_id) in arguments.iter().zip(locals.iter()) { let arg_storage = self.storage.get(arg_symbol).clone();
let argument = self.local_id_from_symbol(symbol); self.storage.clone_value(
self.instructions.push(GetLocal(argument.0)); &mut self.code_builder,
self.instructions.push(SetLocal(local_id.0)); param_storage,
&arg_storage,
*arg_symbol,
);
} }
// jump // jump
let levels = self.block_depth - target; let levels = self.block_depth - target;
self.instructions.push(Br(levels)); self.code_builder.push(Br(levels));
Ok(()) Ok(())
} }
@ -477,6 +383,12 @@ impl<'a> WasmBackend<'a> {
} }
} }
/**********************************************************
EXPRESSIONS
***********************************************************/
fn build_expr( fn build_expr(
&mut self, &mut self,
sym: &Symbol, sym: &Symbol,
@ -491,14 +403,35 @@ impl<'a> WasmBackend<'a> {
arguments, arguments,
}) => match call_type { }) => match call_type {
CallType::ByName { name: func_sym, .. } => { CallType::ByName { name: func_sym, .. } => {
for arg in *arguments { // TODO: See if we can make this more efficient
self.load_symbol(arg); // 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 {
WasmLayout::StackMemory { .. } => {
wasm_args_tmp =
Vec::with_capacity_in(arguments.len() + 1, self.env.arena);
wasm_args_tmp.push(*sym);
wasm_args_tmp.extend_from_slice(*arguments);
(wasm_args_tmp.as_slice(), false)
}
_ => (*arguments, true),
};
self.storage.load_symbols(&mut self.code_builder, wasm_args);
let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!(
"Cannot find function {:?} called from {:?}", "Cannot find function {:?} called from {:?}",
func_sym, sym func_sym, sym
))?; ))?;
self.instructions.push(Call(function_location.body));
self.code_builder.push_call(
function_location.body,
wasm_args.len(),
has_return_val,
);
Ok(()) Ok(())
} }
@ -542,7 +475,7 @@ impl<'a> WasmBackend<'a> {
return Err(format!("loading literal, {:?}, is not yet implemented", x)); return Err(format!("loading literal, {:?}, is not yet implemented", x));
} }
}; };
self.instructions.push(instruction); self.code_builder.push(instruction);
Ok(()) Ok(())
} }
@ -552,20 +485,23 @@ impl<'a> WasmBackend<'a> {
layout: &Layout<'a>, layout: &Layout<'a>,
fields: &'a [Symbol], fields: &'a [Symbol],
) -> Result<(), String> { ) -> Result<(), String> {
let storage = self.get_symbol_storage(sym).to_owned(); // TODO: we just calculated storage and now we're getting it out of a map
// Not passing it as an argument because I'm trying to match Backend method signatures
let storage = self.storage.get(sym).to_owned();
if let Layout::Struct(field_layouts) = layout { if let Layout::Struct(field_layouts) = layout {
match storage { match storage {
SymbolStorage::StackMemory { location, size, .. } => { StoredValue::StackMemory { location, size, .. } => {
if size > 0 { if size > 0 {
let (local_id, struct_offset) = let (local_id, struct_offset) =
location.local_and_offset(self.stack_frame_pointer); location.local_and_offset(self.storage.stack_frame_pointer);
let mut field_offset = struct_offset; let mut field_offset = struct_offset;
for (field, _) in fields.iter().zip(field_layouts.iter()) { for (field, _) in fields.iter().zip(field_layouts.iter()) {
field_offset += self.copy_symbol_to_pointer_at_offset( field_offset += self.storage.copy_value_to_memory(
&mut self.code_builder,
local_id, local_id,
field_offset, field_offset,
field, *field,
); );
} }
} else { } else {
@ -581,40 +517,20 @@ impl<'a> WasmBackend<'a> {
}; };
} else { } else {
// Struct expression but not Struct layout => single element. Copy it. // Struct expression but not Struct layout => single element. Copy it.
let field_storage = self.get_symbol_storage(&fields[0]).to_owned(); let field_storage = self.storage.get(&fields[0]).to_owned();
storage.copy_from( self.storage
&field_storage, .clone_value(&mut self.code_builder, &storage, &field_storage, fields[0]);
&mut self.instructions,
self.stack_frame_pointer,
);
} }
Ok(()) Ok(())
} }
fn copy_symbol_to_pointer_at_offset(
&mut self,
to_ptr: LocalId,
to_offset: u32,
from_symbol: &Symbol,
) -> u32 {
let from_storage = self.get_symbol_storage(from_symbol).to_owned();
from_storage.copy_to_memory(
&mut self.instructions,
to_ptr,
to_offset,
self.stack_frame_pointer,
)
}
fn build_call_low_level( fn build_call_low_level(
&mut self, &mut self,
lowlevel: &LowLevel, lowlevel: &LowLevel,
args: &'a [Symbol], args: &'a [Symbol],
return_layout: &Layout<'a>, return_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
for arg in args { self.storage.load_symbols(&mut self.code_builder, args);
self.load_symbol(arg);
}
let wasm_layout = WasmLayout::new(return_layout); let wasm_layout = WasmLayout::new(return_layout);
self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?;
Ok(()) Ok(())
@ -631,7 +547,6 @@ impl<'a> WasmBackend<'a> {
// For those, we'll need to pre-process each argument before the main op, // For those, we'll need to pre-process each argument before the main op,
// so simple arrays of instructions won't work. But there are common patterns. // so simple arrays of instructions won't work. But there are common patterns.
let instructions: &[Instruction] = match lowlevel { let instructions: &[Instruction] = match lowlevel {
// Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_symbol?
LowLevel::NumAdd => match return_value_type { LowLevel::NumAdd => match return_value_type {
ValueType::I32 => &[I32Add], ValueType::I32 => &[I32Add],
ValueType::I64 => &[I64Add], ValueType::I64 => &[I64Add],
@ -658,7 +573,7 @@ impl<'a> WasmBackend<'a> {
return Err(format!("unsupported low-level op {:?}", lowlevel)); return Err(format!("unsupported low-level op {:?}", lowlevel));
} }
}; };
self.instructions.extend_from_slice(instructions); self.code_builder.extend_from_slice(instructions);
Ok(()) Ok(())
} }
} }

View file

@ -0,0 +1,459 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use core::panic;
use std::collections::BTreeMap;
use std::fmt::Debug;
use parity_wasm::elements::{Instruction, Instruction::*};
use roc_module::symbol::Symbol;
use crate::LocalId;
const DEBUG_LOG: bool = false;
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum VirtualMachineSymbolState {
/// Value doesn't exist yet
NotYetPushed,
/// Value has been pushed onto the VM stack but not yet popped
/// Remember where it was pushed, in case we need to insert another instruction there later
Pushed { pushed_at: usize },
/// Value has been pushed and popped, so it's not on the VM stack any more.
/// If we want to use it again later, we will have to create a local for it,
/// by going back to insert a local.tee instruction at pushed_at
Popped { pushed_at: usize },
}
#[derive(Debug)]
pub struct CodeBuilder<'a> {
/// The main container for the instructions
code: Vec<'a, Instruction>,
/// Extra instructions to insert at specific positions during finalisation
/// (Go back and set locals when we realise we need them)
/// We need BTree rather than Map or Vec, to ensure keys are sorted.
/// Entries may not be added in order. They are created when a Symbol
/// is used for the second time, or is in an inconvenient VM stack position,
/// so it's not a simple predictable order.
insertions: BTreeMap<usize, Instruction>,
/// Our simulation model of the Wasm stack machine
/// Keeps track of where Symbol values are in the VM stack
vm_stack: Vec<'a, Symbol>,
}
#[allow(clippy::new_without_default)]
impl<'a> CodeBuilder<'a> {
pub fn new(arena: &'a Bump) -> Self {
CodeBuilder {
vm_stack: Vec::with_capacity_in(32, arena),
insertions: BTreeMap::default(),
code: Vec::with_capacity_in(1024, arena),
}
}
pub fn clear(&mut self) {
self.code.clear();
self.insertions.clear();
self.vm_stack.clear();
}
/// Add an instruction
pub fn push(&mut self, inst: Instruction) {
let (pops, push) = get_pops_and_pushes(&inst);
let new_len = self.vm_stack.len() - pops as usize;
self.vm_stack.truncate(new_len);
if push {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
if DEBUG_LOG {
println!("{:?} {:?}", inst, self.vm_stack);
}
self.code.push(inst);
}
/// Add many instructions
pub fn extend_from_slice(&mut self, instructions: &[Instruction]) {
let old_len = self.vm_stack.len();
let mut len = old_len;
let mut min_len = len;
for inst in instructions {
let (pops, push) = get_pops_and_pushes(inst);
len -= pops as usize;
if len < min_len {
min_len = len;
}
if push {
len += 1;
}
}
self.vm_stack.truncate(min_len);
self.vm_stack
.resize(len, Symbol::WASM_ANONYMOUS_STACK_VALUE);
if DEBUG_LOG {
println!("{:?} {:?}", instructions, self.vm_stack);
}
self.code.extend_from_slice(instructions);
}
/// Special-case method to add a Call instruction
/// Specify the number of arguments the function pops from the VM stack, and whether it pushes a return value
pub fn push_call(&mut self, function_index: u32, pops: usize, push: bool) {
let stack_depth = self.vm_stack.len();
if pops > stack_depth {
let mut final_code =
std::vec::Vec::with_capacity(self.code.len() + self.insertions.len());
self.finalize_into(&mut final_code);
panic!(
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\nfinal_code={:?}\nvm_stack={:?}",
function_index, pops, stack_depth, final_code, self.vm_stack
);
}
self.vm_stack.truncate(stack_depth - pops);
if push {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
let inst = Call(function_index);
if DEBUG_LOG {
println!("{:?} {:?}", inst, self.vm_stack);
}
self.code.push(inst);
}
/// Finalize a function body by copying all instructions into a vector
pub fn finalize_into(&mut self, final_code: &mut std::vec::Vec<Instruction>) {
let mut insertions_iter = self.insertions.iter();
let mut next_insertion = insertions_iter.next();
for (pos, instruction) in self.code.drain(0..).enumerate() {
match next_insertion {
Some((&insert_pos, insert_inst)) if insert_pos == pos => {
final_code.push(insert_inst.to_owned());
next_insertion = insertions_iter.next();
}
_ => {}
}
final_code.push(instruction);
}
debug_assert!(next_insertion == None);
}
/// Total number of instructions in the final output
pub fn len(&self) -> usize {
self.code.len() + self.insertions.len()
}
/// Set the Symbol that is at the top of the VM stack right now
/// We will use this later when we need to load the Symbol
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState {
let len = self.vm_stack.len();
let pushed_at = self.code.len();
if len == 0 {
panic!(
"trying to set symbol with nothing on stack, code = {:?}",
self.code
);
}
self.vm_stack[len - 1] = sym;
VirtualMachineSymbolState::Pushed { pushed_at }
}
/// Verify if a sequence of symbols is at the top of the stack
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
let n_symbols = symbols.len();
let stack_depth = self.vm_stack.len();
if n_symbols > stack_depth {
return false;
}
let offset = stack_depth - n_symbols;
for (i, sym) in symbols.iter().enumerate() {
if self.vm_stack[offset + i] != *sym {
return false;
}
}
true
}
/// Load a Symbol that is stored in the VM stack
/// If it's already at the top of the stack, no code will be generated.
/// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided.
///
/// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call.
/// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local.
/// (In this case, the caller must remember to declare the local in the function header.)
pub fn load_symbol(
&mut self,
symbol: Symbol,
vm_state: VirtualMachineSymbolState,
next_local_id: LocalId,
) -> Option<VirtualMachineSymbolState> {
use VirtualMachineSymbolState::*;
match vm_state {
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol),
Pushed { pushed_at } => {
let &top = self.vm_stack.last().unwrap();
if top == symbol {
// We're lucky, the symbol is already on top of the VM stack
// No code to generate! (This reduces code size by up to 25% in tests.)
// Just let the caller know what happened
Some(Popped { pushed_at })
} else {
// Symbol is not on top of the stack. Find it.
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
// Insert a SetLocal where the value was created (this removes it from the VM stack)
self.insertions.insert(pushed_at, SetLocal(next_local_id.0));
self.vm_stack.remove(found_index);
// Insert a GetLocal at the current position
let inst = GetLocal(next_local_id.0);
if DEBUG_LOG {
println!(
"{:?} {:?} (& insert {:?} at {:?})",
inst,
self.vm_stack,
SetLocal(next_local_id.0),
pushed_at
);
}
self.code.push(inst);
self.vm_stack.push(symbol);
// This Symbol is no longer stored in the VM stack, but in a local
None
} else {
panic!(
"{:?} has state {:?} but not found in VM stack",
symbol, vm_state
);
}
}
}
Popped { pushed_at } => {
// This Symbol is being used for a second time
// Insert a TeeLocal where it was created (must remain on the stack for the first usage)
self.insertions.insert(pushed_at, TeeLocal(next_local_id.0));
// Insert a GetLocal at the current position
let inst = GetLocal(next_local_id.0);
if DEBUG_LOG {
println!(
"{:?} {:?} (& insert {:?} at {:?})",
inst,
self.vm_stack,
TeeLocal(next_local_id.0),
pushed_at
);
}
self.code.push(inst);
self.vm_stack.push(symbol);
// This symbol has been promoted to a Local
// Tell the caller it no longer has a VirtualMachineSymbolState
None
}
}
}
}
fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) {
match inst {
Unreachable => (0, false),
Nop => (0, false),
Block(_) => (0, false),
Loop(_) => (0, false),
If(_) => (1, false),
Else => (0, false),
End => (0, false),
Br(_) => (0, false),
BrIf(_) => (1, false),
BrTable(_) => (1, false),
Return => (0, false),
Call(_) | CallIndirect(_, _) => {
panic!("Unknown number of pushes and pops. Use add_call()");
}
Drop => (1, false),
Select => (3, true),
GetLocal(_) => (0, true),
SetLocal(_) => (1, false),
TeeLocal(_) => (1, true),
GetGlobal(_) => (0, true),
SetGlobal(_) => (1, false),
I32Load(_, _) => (1, true),
I64Load(_, _) => (1, true),
F32Load(_, _) => (1, true),
F64Load(_, _) => (1, true),
I32Load8S(_, _) => (1, true),
I32Load8U(_, _) => (1, true),
I32Load16S(_, _) => (1, true),
I32Load16U(_, _) => (1, true),
I64Load8S(_, _) => (1, true),
I64Load8U(_, _) => (1, true),
I64Load16S(_, _) => (1, true),
I64Load16U(_, _) => (1, true),
I64Load32S(_, _) => (1, true),
I64Load32U(_, _) => (1, true),
I32Store(_, _) => (2, false),
I64Store(_, _) => (2, false),
F32Store(_, _) => (2, false),
F64Store(_, _) => (2, false),
I32Store8(_, _) => (2, false),
I32Store16(_, _) => (2, false),
I64Store8(_, _) => (2, false),
I64Store16(_, _) => (2, false),
I64Store32(_, _) => (2, false),
CurrentMemory(_) => (0, true),
GrowMemory(_) => (1, true),
I32Const(_) => (0, true),
I64Const(_) => (0, true),
F32Const(_) => (0, true),
F64Const(_) => (0, true),
I32Eqz => (1, true),
I32Eq => (2, true),
I32Ne => (2, true),
I32LtS => (2, true),
I32LtU => (2, true),
I32GtS => (2, true),
I32GtU => (2, true),
I32LeS => (2, true),
I32LeU => (2, true),
I32GeS => (2, true),
I32GeU => (2, true),
I64Eqz => (1, true),
I64Eq => (2, true),
I64Ne => (2, true),
I64LtS => (2, true),
I64LtU => (2, true),
I64GtS => (2, true),
I64GtU => (2, true),
I64LeS => (2, true),
I64LeU => (2, true),
I64GeS => (2, true),
I64GeU => (2, true),
F32Eq => (2, true),
F32Ne => (2, true),
F32Lt => (2, true),
F32Gt => (2, true),
F32Le => (2, true),
F32Ge => (2, true),
F64Eq => (2, true),
F64Ne => (2, true),
F64Lt => (2, true),
F64Gt => (2, true),
F64Le => (2, true),
F64Ge => (2, true),
I32Clz => (1, true),
I32Ctz => (1, true),
I32Popcnt => (1, true),
I32Add => (2, true),
I32Sub => (2, true),
I32Mul => (2, true),
I32DivS => (2, true),
I32DivU => (2, true),
I32RemS => (2, true),
I32RemU => (2, true),
I32And => (2, true),
I32Or => (2, true),
I32Xor => (2, true),
I32Shl => (2, true),
I32ShrS => (2, true),
I32ShrU => (2, true),
I32Rotl => (2, true),
I32Rotr => (2, true),
I64Clz => (1, true),
I64Ctz => (1, true),
I64Popcnt => (1, true),
I64Add => (2, true),
I64Sub => (2, true),
I64Mul => (2, true),
I64DivS => (2, true),
I64DivU => (2, true),
I64RemS => (2, true),
I64RemU => (2, true),
I64And => (2, true),
I64Or => (2, true),
I64Xor => (2, true),
I64Shl => (2, true),
I64ShrS => (2, true),
I64ShrU => (2, true),
I64Rotl => (2, true),
I64Rotr => (2, true),
F32Abs => (1, true),
F32Neg => (1, true),
F32Ceil => (1, true),
F32Floor => (1, true),
F32Trunc => (1, true),
F32Nearest => (1, true),
F32Sqrt => (1, true),
F32Add => (2, true),
F32Sub => (2, true),
F32Mul => (2, true),
F32Div => (2, true),
F32Min => (2, true),
F32Max => (2, true),
F32Copysign => (2, true),
F64Abs => (1, true),
F64Neg => (1, true),
F64Ceil => (1, true),
F64Floor => (1, true),
F64Trunc => (1, true),
F64Nearest => (1, true),
F64Sqrt => (1, true),
F64Add => (2, true),
F64Sub => (2, true),
F64Mul => (2, true),
F64Div => (2, true),
F64Min => (2, true),
F64Max => (2, true),
F64Copysign => (2, true),
I32WrapI64 => (1, true),
I32TruncSF32 => (1, true),
I32TruncUF32 => (1, true),
I32TruncSF64 => (1, true),
I32TruncUF64 => (1, true),
I64ExtendSI32 => (1, true),
I64ExtendUI32 => (1, true),
I64TruncSF32 => (1, true),
I64TruncUF32 => (1, true),
I64TruncSF64 => (1, true),
I64TruncUF64 => (1, true),
F32ConvertSI32 => (1, true),
F32ConvertUI32 => (1, true),
F32ConvertSI64 => (1, true),
F32ConvertUI64 => (1, true),
F32DemoteF64 => (1, true),
F64ConvertSI32 => (1, true),
F64ConvertUI32 => (1, true),
F64ConvertSI64 => (1, true),
F64ConvertUI64 => (1, true),
F64PromoteF32 => (1, true),
I32ReinterpretF32 => (1, true),
I64ReinterpretF64 => (1, true),
F32ReinterpretI32 => (1, true),
F64ReinterpretI64 => (1, true),
}
}

View file

@ -6,9 +6,9 @@ use crate::{PTR_SIZE, PTR_TYPE};
// See README for background information on Wasm locals, memory and function calls // See README for background information on Wasm locals, memory and function calls
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum WasmLayout { pub enum WasmLayout {
// Primitive number value. Just a Wasm local, without any stack memory. // Primitive number value, without any stack memory.
// For example, Roc i8 is represented as Wasm i32. Store the type and the original size. // For example, Roc i8 is represented as Primitive(ValueType::I32, 1)
LocalOnly(ValueType, u32), Primitive(ValueType, u32),
// Local pointer to stack memory // Local pointer to stack memory
StackMemory { size: u32, alignment_bytes: u32 }, StackMemory { size: u32, alignment_bytes: u32 },
@ -27,13 +27,13 @@ impl WasmLayout {
let alignment_bytes = layout.alignment_bytes(PTR_SIZE); let alignment_bytes = layout.alignment_bytes(PTR_SIZE);
match layout { match layout {
Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::Primitive(I32, size),
Layout::Builtin(Int64) => Self::LocalOnly(I64, size), Layout::Builtin(Int64) => Self::Primitive(I64, size),
Layout::Builtin(Float32) => Self::LocalOnly(F32, size), Layout::Builtin(Float32) => Self::Primitive(F32, size),
Layout::Builtin(Float64) => Self::LocalOnly(F64, size), Layout::Builtin(Float64) => Self::Primitive(F64, size),
Layout::Builtin( Layout::Builtin(
Int128 Int128
@ -67,7 +67,7 @@ impl WasmLayout {
pub fn value_type(&self) -> ValueType { pub fn value_type(&self) -> ValueType {
match self { match self {
Self::LocalOnly(type_, _) => *type_, Self::Primitive(type_, _) => *type_,
_ => PTR_TYPE, _ => PTR_TYPE,
} }
} }

View file

@ -1,8 +1,10 @@
mod backend; mod backend;
mod code_builder;
pub mod from_wasm32_memory; pub mod from_wasm32_memory;
mod layout; mod layout;
mod storage; mod storage;
use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType}; use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType};
@ -13,6 +15,7 @@ use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
use crate::backend::WasmBackend; use crate::backend::WasmBackend;
use crate::code_builder::CodeBuilder;
const PTR_SIZE: u32 = 4; const PTR_SIZE: u32 = 4;
const PTR_TYPE: ValueType = ValueType::I32; const PTR_TYPE: ValueType = ValueType::I32;
@ -30,7 +33,7 @@ pub const STACK_ALIGNMENT_BYTES: i32 = 16;
pub struct LocalId(pub u32); pub struct LocalId(pub u32);
pub struct Env<'a> { pub struct Env<'a> {
pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot pub arena: &'a Bump,
pub interns: Interns, pub interns: Interns,
pub exposed_to_host: MutSet<Symbol>, pub exposed_to_host: MutSet<Symbol>,
} }
@ -38,11 +41,11 @@ pub struct Env<'a> {
pub fn build_module<'a>( pub fn build_module<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<Vec<u8>, String> { ) -> Result<std::vec::Vec<u8>, String> {
let (builder, _) = build_module_help(env, procedures)?; let (builder, _) = build_module_help(env, procedures)?;
let module = builder.build(); let module = builder.build();
module module
.to_bytes() .into_bytes()
.map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) }) .map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) })
} }
@ -50,7 +53,7 @@ pub fn build_module_help<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<(builder::ModuleBuilder, u32), String> { ) -> Result<(builder::ModuleBuilder, u32), String> {
let mut backend = WasmBackend::new(); let mut backend = WasmBackend::new(env);
let mut layout_ids = LayoutIds::default(); let mut layout_ids = LayoutIds::default();
// Sort procedures by occurrence order // Sort procedures by occurrence order
@ -63,7 +66,7 @@ pub fn build_module_help<'a>(
// //
// This means that for now other functions in the file have to be ordered "in reverse": if A // This means that for now other functions in the file have to be ordered "in reverse": if A
// uses B, then the name of A must first occur after the first occurrence of the name of B // uses B, then the name of A must first occur after the first occurrence of the name of B
let mut procedures: std::vec::Vec<_> = procedures.into_iter().collect(); let mut procedures = Vec::from_iter_in(procedures.into_iter(), env.arena);
procedures.sort_by(|a, b| b.0 .0.cmp(&a.0 .0)); procedures.sort_by(|a, b| b.0 .0.cmp(&a.0 .0));
let mut function_index: u32 = 0; let mut function_index: u32 = 0;
@ -79,7 +82,7 @@ pub fn build_module_help<'a>(
.with_internal(Internal::Function(function_index)) .with_internal(Internal::Function(function_index))
.build(); .build();
backend.builder.push_export(export); backend.module_builder.push_export(export);
} }
} }
@ -94,21 +97,21 @@ pub fn build_module_help<'a>(
let memory = builder::MemoryBuilder::new() let memory = builder::MemoryBuilder::new()
.with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB) .with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB)
.build(); .build();
backend.builder.push_memory(memory); backend.module_builder.push_memory(memory);
let memory_export = builder::export() let memory_export = builder::export()
.field("memory") .field("memory")
.with_internal(Internal::Memory(0)) .with_internal(Internal::Memory(0))
.build(); .build();
backend.builder.push_export(memory_export); backend.module_builder.push_export(memory_export);
let stack_pointer_global = builder::global() let stack_pointer_global = builder::global()
.with_type(PTR_TYPE) .with_type(PTR_TYPE)
.mutable() .mutable()
.init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32)) .init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32))
.build(); .build();
backend.builder.push_global(stack_pointer_global); backend.module_builder.push_global(stack_pointer_global);
Ok((backend.builder, main_function_index)) Ok((backend.module_builder, main_function_index))
} }
fn encode_alignment(bytes: u32) -> u32 { fn encode_alignment(bytes: u32) -> u32 {
@ -130,34 +133,45 @@ pub struct CopyMemoryConfig {
alignment_bytes: u32, alignment_bytes: u32,
} }
pub fn copy_memory(instructions: &mut Vec<Instruction>, config: CopyMemoryConfig) { pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) {
if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset {
return;
}
let alignment_flag = encode_alignment(config.alignment_bytes); let alignment_flag = encode_alignment(config.alignment_bytes);
let mut i = 0; let mut i = 0;
while config.size - i >= 8 { while config.size - i >= 8 {
instructions.push(GetLocal(config.to_ptr.0)); code_builder.extend_from_slice(&[
instructions.push(GetLocal(config.from_ptr.0)); GetLocal(config.to_ptr.0),
instructions.push(I64Load(alignment_flag, i + config.from_offset)); GetLocal(config.from_ptr.0),
instructions.push(I64Store(alignment_flag, i + config.to_offset)); I64Load(alignment_flag, i + config.from_offset),
I64Store(alignment_flag, i + config.to_offset),
]);
i += 8; i += 8;
} }
if config.size - i >= 4 { if config.size - i >= 4 {
instructions.push(GetLocal(config.to_ptr.0)); code_builder.extend_from_slice(&[
instructions.push(GetLocal(config.from_ptr.0)); GetLocal(config.to_ptr.0),
instructions.push(I32Load(alignment_flag, i + config.from_offset)); GetLocal(config.from_ptr.0),
instructions.push(I32Store(alignment_flag, i + config.to_offset)); I32Load(alignment_flag, i + config.from_offset),
I32Store(alignment_flag, i + config.to_offset),
]);
i += 4; i += 4;
} }
while config.size - i > 0 { while config.size - i > 0 {
instructions.push(GetLocal(config.to_ptr.0)); code_builder.extend_from_slice(&[
instructions.push(GetLocal(config.from_ptr.0)); GetLocal(config.to_ptr.0),
instructions.push(I32Load8U(alignment_flag, i + config.from_offset)); GetLocal(config.from_ptr.0),
instructions.push(I32Store8(alignment_flag, i + config.to_offset)); I32Load8U(alignment_flag, i + config.from_offset),
I32Store8(alignment_flag, i + config.to_offset),
]);
i += 1; i += 1;
} }
} }
/// Round up to alignment_bytes (assumed to be a power of 2) /// Round up to alignment_bytes (which must be a power of 2)
pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
debug_assert!(alignment_bytes.count_ones() == 1);
let mut aligned = unaligned; let mut aligned = unaligned;
aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary
aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0 aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0
@ -165,7 +179,7 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
} }
pub fn push_stack_frame( pub fn push_stack_frame(
instructions: &mut Vec<Instruction>, instructions: &mut std::vec::Vec<Instruction>,
size: i32, size: i32,
local_frame_pointer: LocalId, local_frame_pointer: LocalId,
) { ) {
@ -180,7 +194,7 @@ pub fn push_stack_frame(
} }
pub fn pop_stack_frame( pub fn pop_stack_frame(
instructions: &mut Vec<Instruction>, instructions: &mut std::vec::Vec<Instruction>,
size: i32, size: i32,
local_frame_pointer: LocalId, local_frame_pointer: LocalId,
) { ) {

View file

@ -1,5 +1,22 @@
use crate::{copy_memory, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use bumpalo::collections::Vec;
use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; use bumpalo::Bump;
use parity_wasm::elements::{Instruction::*, ValueType};
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState};
use crate::layout::WasmLayout;
use crate::{
copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4,
ALIGN_8, PTR_SIZE, PTR_TYPE,
};
pub enum StoredValueKind {
Parameter,
Variable,
ReturnValue,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum StackMemoryLocation { pub enum StackMemoryLocation {
@ -17,98 +34,268 @@ impl StackMemoryLocation {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SymbolStorage { pub enum StoredValue {
// TODO: implicit storage in the VM stack /// A value stored implicitly in the VM stack (primitives only)
// TODO: const data storage VirtualMachineStack {
vm_state: VirtualMachineSymbolState,
value_type: ValueType,
size: u32,
},
/// A local variable in the Wasm function (primitives only)
Local { Local {
local_id: LocalId, local_id: LocalId,
value_type: ValueType, value_type: ValueType,
size: u32, size: u32,
}, },
/// A Struct, or other non-primitive value, stored in stack memory
StackMemory { StackMemory {
location: StackMemoryLocation, location: StackMemoryLocation,
size: u32, size: u32,
alignment_bytes: u32, alignment_bytes: u32,
}, },
// TODO: const data storage (fixed address)
} }
impl SymbolStorage { /// Helper structure for WasmBackend, to keep track of how values are stored,
/// generate code to copy from another storage of the same type /// including the VM stack, local variables, and linear memory
pub fn copy_from( pub struct Storage<'a> {
&self, pub arg_types: Vec<'a, ValueType>,
from: &Self, pub local_types: Vec<'a, ValueType>,
instructions: &mut Vec<Instruction>, pub symbol_storage_map: MutMap<Symbol, StoredValue>,
stack_frame_pointer: Option<LocalId>, pub stack_frame_pointer: Option<LocalId>,
) { pub stack_frame_size: i32,
match (self, from) { }
(
Self::Local { impl<'a> Storage<'a> {
local_id: to_local_id, pub fn new(arena: &'a Bump) -> Self {
value_type: to_value_type, Storage {
size: to_size, arg_types: Vec::with_capacity_in(8, arena),
local_types: Vec::with_capacity_in(32, arena),
symbol_storage_map: MutMap::default(),
stack_frame_pointer: None,
stack_frame_size: 0,
}
}
pub fn clear(&mut self) {
self.arg_types.clear();
self.local_types.clear();
self.symbol_storage_map.clear();
self.stack_frame_pointer = None;
self.stack_frame_size = 0;
}
/// Internal use only. If you think you want it externally, you really want `allocate`
fn get_next_local_id(&self) -> LocalId {
LocalId((self.arg_types.len() + self.local_types.len()) as u32)
}
/// Allocate storage for a Roc value
///
/// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack.
/// This is really just a way to model how the stack machine works as a sort of
/// temporary storage. It doesn't result in any code generation.
/// For some values, this initial storage allocation may need to be upgraded later
/// to a Local. See `load_symbols`.
///
/// Structs and Tags are stored in memory rather than in Wasm primitives.
/// They are allocated a certain offset and size in the stack frame.
pub fn allocate(
&mut self,
wasm_layout: &WasmLayout,
symbol: Symbol,
kind: StoredValueKind,
) -> StoredValue {
let next_local_id = self.get_next_local_id();
let storage = match wasm_layout {
WasmLayout::Primitive(value_type, size) => match kind {
StoredValueKind::Parameter => {
self.arg_types.push(*value_type);
StoredValue::Local {
local_id: next_local_id,
value_type: *value_type,
size: *size,
}
}
_ => StoredValue::VirtualMachineStack {
vm_state: VirtualMachineSymbolState::NotYetPushed,
value_type: *value_type,
size: *size,
}, },
Self::Local { },
local_id: from_local_id,
value_type: from_value_type, WasmLayout::HeapMemory => {
size: from_size, match kind {
}, StoredValueKind::Parameter => self.arg_types.push(PTR_TYPE),
) => { _ => self.local_types.push(PTR_TYPE),
debug_assert!(to_value_type == from_value_type); }
debug_assert!(to_size == from_size); StoredValue::Local {
instructions.push(GetLocal(from_local_id.0)); local_id: next_local_id,
instructions.push(SetLocal(to_local_id.0)); value_type: PTR_TYPE,
size: PTR_SIZE,
}
} }
(
Self::StackMemory { WasmLayout::StackMemory {
location: to_location, size,
size: to_size, alignment_bytes,
alignment_bytes: to_alignment_bytes, } => {
}, let location = match kind {
Self::StackMemory { StoredValueKind::Parameter => {
location: from_location, self.arg_types.push(PTR_TYPE);
size: from_size, StackMemoryLocation::PointerArg(next_local_id)
alignment_bytes: from_alignment_bytes, }
},
) => { StoredValueKind::Variable => {
let (from_ptr, from_offset) = from_location.local_and_offset(stack_frame_pointer); if self.stack_frame_pointer.is_none() {
let (to_ptr, to_offset) = to_location.local_and_offset(stack_frame_pointer); self.stack_frame_pointer = Some(next_local_id);
debug_assert!(*to_size == *from_size); self.local_types.push(PTR_TYPE);
debug_assert!(*to_alignment_bytes == *from_alignment_bytes); }
let offset =
round_up_to_alignment(self.stack_frame_size, *alignment_bytes as i32);
self.stack_frame_size = offset + (*size as i32);
StackMemoryLocation::FrameOffset(offset as u32)
}
StoredValueKind::ReturnValue => StackMemoryLocation::PointerArg(LocalId(0)),
};
StoredValue::StackMemory {
location,
size: *size,
alignment_bytes: *alignment_bytes,
}
}
};
self.symbol_storage_map.insert(symbol, storage.clone());
storage
}
/// Get storage info for a given symbol
pub fn get(&self, sym: &Symbol) -> &StoredValue {
self.symbol_storage_map.get(sym).unwrap_or_else(|| {
panic!(
"Symbol {:?} not found in function scope:\n{:?}",
sym, self.symbol_storage_map
)
})
}
/// Load symbols to the top of the VM stack
/// Avoid calling this method in a loop with one symbol at a time! It will work,
/// but it generates very inefficient Wasm code.
pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) {
if code_builder.verify_stack_match(symbols) {
// The symbols were already at the top of the stack, do nothing!
// This should be quite common due to the structure of the Mono IR
return;
}
for sym in symbols.iter() {
let storage = self.get(sym).to_owned();
match storage {
StoredValue::VirtualMachineStack {
vm_state,
value_type,
size,
} => {
let next_local_id = self.get_next_local_id();
let maybe_next_vm_state =
code_builder.load_symbol(*sym, vm_state, next_local_id);
match maybe_next_vm_state {
// The act of loading the value changed the VM state, so update it
Some(next_vm_state) => {
self.symbol_storage_map.insert(
*sym,
StoredValue::VirtualMachineStack {
vm_state: next_vm_state,
value_type,
size,
},
);
}
None => {
// Loading the value required creating a new local, because
// it was not in a convenient position in the VM stack.
self.local_types.push(value_type);
self.symbol_storage_map.insert(
*sym,
StoredValue::Local {
local_id: next_local_id,
value_type,
size,
},
);
}
}
}
StoredValue::Local { local_id, .. }
| StoredValue::StackMemory {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
code_builder.push(GetLocal(local_id.0));
code_builder.set_top_symbol(*sym);
}
StoredValue::StackMemory {
location: StackMemoryLocation::FrameOffset(offset),
..
} => {
code_builder.extend_from_slice(&[
GetLocal(self.stack_frame_pointer.unwrap().0),
I32Const(offset as i32),
I32Add,
]);
code_builder.set_top_symbol(*sym);
}
}
}
}
/// Generate code to copy a StoredValue to an arbitrary memory location
/// (defined by a pointer and offset).
pub fn copy_value_to_memory(
&mut self,
code_builder: &mut CodeBuilder,
to_ptr: LocalId,
to_offset: u32,
from_symbol: Symbol,
) -> u32 {
let from_storage = self.get(&from_symbol).to_owned();
match from_storage {
StoredValue::StackMemory {
location,
size,
alignment_bytes,
} => {
let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer);
copy_memory( copy_memory(
instructions, code_builder,
CopyMemoryConfig { CopyMemoryConfig {
from_ptr, from_ptr,
from_offset, from_offset,
to_ptr, to_ptr,
to_offset, to_offset,
size: *from_size, size,
alignment_bytes: *from_alignment_bytes, alignment_bytes,
}, },
); );
size
} }
_ => {
panic!(
"Cannot copy different storage types {:?} to {:?}",
from, self
);
}
}
}
/// Generate code to copy to a memory address (such as a struct index) StoredValue::VirtualMachineStack {
pub fn copy_to_memory( value_type, size, ..
&self, }
instructions: &mut Vec<Instruction>, | StoredValue::Local {
to_ptr: LocalId, value_type, size, ..
to_offset: u32,
stack_frame_pointer: Option<LocalId>,
) -> u32 {
match self {
Self::Local {
local_id,
value_type,
size,
..
} => { } => {
let store_instruction = match (value_type, size) { let store_instruction = match (value_type, size) {
(ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset),
@ -121,31 +308,133 @@ impl SymbolStorage {
panic!("Cannot store {:?} with alignment of {:?}", value_type, size); panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
} }
}; };
instructions.push(GetLocal(to_ptr.0)); code_builder.push(GetLocal(to_ptr.0));
instructions.push(GetLocal(local_id.0)); self.load_symbols(code_builder, &[from_symbol]);
instructions.push(store_instruction); code_builder.push(store_instruction);
*size size
}
}
}
/// Generate code to copy from one StoredValue to another
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
pub fn clone_value(
&mut self,
code_builder: &mut CodeBuilder,
to: &StoredValue,
from: &StoredValue,
from_symbol: Symbol,
) {
use StoredValue::*;
match (to, from) {
(
Local {
local_id: to_local_id,
value_type: to_value_type,
size: to_size,
},
VirtualMachineStack {
value_type: from_value_type,
size: from_size,
..
},
) => {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
self.load_symbols(code_builder, &[from_symbol]);
code_builder.push(SetLocal(to_local_id.0));
self.symbol_storage_map.insert(from_symbol, to.clone());
} }
Self::StackMemory { (
location, Local {
size, local_id: to_local_id,
alignment_bytes, value_type: to_value_type,
} => { size: to_size,
let (from_ptr, from_offset) = location.local_and_offset(stack_frame_pointer); },
Local {
local_id: from_local_id,
value_type: from_value_type,
size: from_size,
},
) => {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
code_builder
.extend_from_slice(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]);
}
(
StackMemory {
location: to_location,
size: to_size,
alignment_bytes: to_alignment_bytes,
},
StackMemory {
location: from_location,
size: from_size,
alignment_bytes: from_alignment_bytes,
},
) => {
let (from_ptr, from_offset) =
from_location.local_and_offset(self.stack_frame_pointer);
let (to_ptr, to_offset) = to_location.local_and_offset(self.stack_frame_pointer);
debug_assert!(*to_size == *from_size);
debug_assert!(*to_alignment_bytes == *from_alignment_bytes);
copy_memory( copy_memory(
instructions, code_builder,
CopyMemoryConfig { CopyMemoryConfig {
from_ptr, from_ptr,
from_offset, from_offset,
to_ptr, to_ptr,
to_offset, to_offset,
size: *size, size: *from_size,
alignment_bytes: *alignment_bytes, alignment_bytes: *from_alignment_bytes,
}, },
); );
*size }
_ => {
panic!("Cannot copy storage from {:?} to {:?}", from, to);
} }
} }
} }
/// Ensure a StoredValue has an associated local
/// This is useful when a value needs to be accessed from a more deeply-nested block.
/// In that case we want to make sure it's not just stored in the VM stack, because
/// blocks can't access the VM stack from outer blocks, but they can access locals.
/// (In the case of structs in stack memory, we just use the stack frame pointer local)
pub fn ensure_value_has_local(
&mut self,
code_builder: &mut CodeBuilder,
symbol: Symbol,
storage: StoredValue,
) -> StoredValue {
if let StoredValue::VirtualMachineStack {
vm_state,
value_type,
size,
} = storage
{
let local_id = self.get_next_local_id();
if vm_state != VirtualMachineSymbolState::NotYetPushed {
code_builder.load_symbol(symbol, vm_state, local_id);
code_builder.push(SetLocal(local_id.0));
}
self.local_types.push(value_type);
let new_storage = StoredValue::Local {
local_id,
value_type,
size,
};
self.symbol_storage_map.insert(symbol, new_storage.clone());
new_storage
} else {
storage
}
}
} }

View file

@ -0,0 +1,30 @@
#!/bin/bash
if [[ -z "$1" || -z "$2" ]]
then
echo "$0 needs 2 arguments: the directories to compare"
exit 1
fi
OVERHEAD_BYTES=114 # total file size minus generated code size (test wrapper + module headers)
printf "filename \tLHS\tRHS\tchange\n"
printf "======== \t===\t===\t======\n"
for f in `ls $1/wasm`
do
if [[ ! -f "$2/wasm/$f" ]]
then
echo "$f found in $1/wasm but not in $2/wasm"
continue
fi
SIZE1=$(stat --format '%s' "$1/wasm/$f")
SIZE2=$(stat --format '%s' "$2/wasm/$f")
CHANGE=$(( $SIZE2 - $SIZE1 ))
NET_SIZE1=$(( $SIZE1 - $OVERHEAD_BYTES ))
NET_SIZE2=$(( $SIZE2 - $OVERHEAD_BYTES ))
PERCENT_CHANGE=$(( $CHANGE * 100 / $NET_SIZE1 ))
printf "%s\t%d\t%d\t%d\t%d%%\n" $f $NET_SIZE1 $NET_SIZE2 $CHANGE $PERCENT_CHANGE
done

24
compiler/gen_wasm/test-run.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash
TARGET_DIR=$1
if [[ -z "$TARGET_DIR" ]]
then
echo "$0 needs an argument: target directory for output wasm and wat files"
exit 1
fi
rm -rf output $TARGET_DIR
mkdir -p output $TARGET_DIR $TARGET_DIR/wasm $TARGET_DIR/wat
cargo test -- --test-threads=1 --nocapture
mv output/* $TARGET_DIR/wasm
for f in `ls $TARGET_DIR/wasm`
do
wasm2wat $TARGET_DIR/wasm/$f -o $TARGET_DIR/wat/${f%.wasm}.wat
done
SIZE=$(du -b "$TARGET_DIR/wasm")
echo "Total bytes *.wasm = $SIZE"

View file

@ -1,3 +1,7 @@
use std::cell::Cell;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use roc_can::builtins::builtin_defs_map; use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
// use roc_std::{RocDec, RocList, RocOrder, RocStr}; // use roc_std::{RocDec, RocList, RocOrder, RocStr};
@ -6,6 +10,10 @@ use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
const TEST_WRAPPER_NAME: &str = "test_wrapper"; const TEST_WRAPPER_NAME: &str = "test_wrapper";
std::thread_local! {
static TEST_COUNTER: Cell<u32> = Cell::new(0);
}
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
@ -98,12 +106,23 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
roc_gen_wasm::build_module_help(&env, procedures).unwrap(); roc_gen_wasm::build_module_help(&env, procedures).unwrap();
T::insert_test_wrapper(&mut builder, TEST_WRAPPER_NAME, main_function_index); T::insert_test_wrapper(&mut builder, TEST_WRAPPER_NAME, main_function_index);
let module_bytes = builder.build().to_bytes().unwrap(); let module_bytes = builder.build().into_bytes().unwrap();
// for debugging (e.g. with wasm2wat) // for debugging (e.g. with wasm2wat)
if false { if false {
use std::io::Write; use std::io::Write;
let path = "/home/brian/Documents/roc/compiler/gen_wasm/debug.wasm";
let mut hash_state = DefaultHasher::new();
src.hash(&mut hash_state);
let src_hash = hash_state.finish();
// Filename contains a hash of the Roc test source code. Helpful when comparing across commits.
let dir = "/tmp/roc/compiler/gen_wasm/output";
std::fs::create_dir_all(dir).unwrap();
let path = format!("{}/test-{:016x}.wasm", dir, src_hash);
// Print out filename (appears just after test name)
println!("dumping file {:?}", path);
match std::fs::File::create(path) { match std::fs::File::create(path) {
Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e), Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e),
@ -171,7 +190,6 @@ macro_rules! assert_wasm_evals_to {
match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $expected) { match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $expected) {
Err(msg) => println!("{:?}", msg), Err(msg) => println!("{:?}", msg),
Ok(actual) => { Ok(actual) => {
#[allow(clippy::bool_assert_comparison)]
assert_eq!($transform(actual), $expected) assert_eq!($transform(actual), $expected)
} }
} }

View file

@ -641,19 +641,6 @@ mod wasm_records {
// ); // );
// } // }
#[test]
fn return_record_2() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5 }
"#
),
[3, 5],
[i64; 2]
);
}
#[test] #[test]
fn return_record_3() { fn return_record_3() {
assert_evals_to!( assert_evals_to!(

View file

@ -8,7 +8,7 @@ use roc_builtins::std::StdLib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::def::{Declaration, Def}; use roc_can::def::{Declaration, Def};
use roc_can::module::{canonicalize_module_defs, Module}; use roc_can::module::{canonicalize_module_defs, Module};
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, BumpSet, MutMap, MutSet}; use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet};
use roc_constrain::module::{ use roc_constrain::module::{
constrain_imports, pre_constrain_imports, ConstrainableImports, Import, constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
}; };
@ -553,7 +553,7 @@ fn start_phase<'a>(
ident_ids, ident_ids,
} = typechecked; } = typechecked;
let mut imported_module_thunks = BumpSet::new_in(arena); let mut imported_module_thunks = bumpalo::collections::Vec::new_in(arena);
if let Some(imports) = state.module_cache.imports.get(&module_id) { if let Some(imports) = state.module_cache.imports.get(&module_id) {
for imported in imports.iter() { for imported in imports.iter() {
@ -570,7 +570,7 @@ fn start_phase<'a>(
module_id, module_id,
module_timing, module_timing,
solved_subs, solved_subs,
imported_module_thunks, imported_module_thunks: imported_module_thunks.into_bump_slice(),
decls, decls,
ident_ids, ident_ids,
exposed_to_host: state.exposed_to_host.clone(), exposed_to_host: state.exposed_to_host.clone(),
@ -593,7 +593,7 @@ fn start_phase<'a>(
module_id, module_id,
ident_ids, ident_ids,
subs, subs,
procs, procs_base,
layout_cache, layout_cache,
module_timing, module_timing,
} = found_specializations; } = found_specializations;
@ -602,7 +602,7 @@ fn start_phase<'a>(
module_id, module_id,
ident_ids, ident_ids,
subs, subs,
procs, procs_base,
layout_cache, layout_cache,
specializations_we_must_make, specializations_we_must_make,
module_timing, module_timing,
@ -717,13 +717,13 @@ pub struct TypeCheckedModule<'a> {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct FoundSpecializationsModule<'a> { struct FoundSpecializationsModule<'a> {
pub module_id: ModuleId, module_id: ModuleId,
pub ident_ids: IdentIds, ident_ids: IdentIds,
pub layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
pub procs: Procs<'a>, procs_base: ProcsBase<'a>,
pub subs: Subs, subs: Subs,
pub module_timing: ModuleTiming, module_timing: ModuleTiming,
} }
#[derive(Debug)] #[derive(Debug)]
@ -822,7 +822,7 @@ enum Msg<'a> {
module_id: ModuleId, module_id: ModuleId,
ident_ids: IdentIds, ident_ids: IdentIds,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
procs: Procs<'a>, procs_base: ProcsBase<'a>,
problems: Vec<roc_mono::ir::MonoProblem>, problems: Vec<roc_mono::ir::MonoProblem>,
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
@ -1042,7 +1042,7 @@ enum BuildTask<'a> {
module_timing: ModuleTiming, module_timing: ModuleTiming,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
imported_module_thunks: BumpSet<Symbol>, imported_module_thunks: &'a [Symbol],
module_id: ModuleId, module_id: ModuleId,
ident_ids: IdentIds, ident_ids: IdentIds,
decls: Vec<Declaration>, decls: Vec<Declaration>,
@ -1052,7 +1052,7 @@ enum BuildTask<'a> {
module_id: ModuleId, module_id: ModuleId,
ident_ids: IdentIds, ident_ids: IdentIds,
subs: Subs, subs: Subs,
procs: Procs<'a>, procs_base: ProcsBase<'a>,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations<'a>, specializations_we_must_make: ExternalSpecializations<'a>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
@ -2057,7 +2057,7 @@ fn update<'a>(
} }
FoundSpecializations { FoundSpecializations {
module_id, module_id,
procs, procs_base,
solved_subs, solved_subs,
ident_ids, ident_ids,
layout_cache, layout_cache,
@ -2067,16 +2067,14 @@ fn update<'a>(
log!("found specializations for {:?}", module_id); log!("found specializations for {:?}", module_id);
let subs = solved_subs.into_inner(); let subs = solved_subs.into_inner();
if let Some(pending) = &procs.pending_specializations { for (symbol, specs) in &procs_base.pending_specializations {
for (symbol, specs) in pending { let existing = match state.all_pending_specializations.entry(*symbol) {
let existing = match state.all_pending_specializations.entry(*symbol) { Vacant(entry) => entry.insert(MutMap::default()),
Vacant(entry) => entry.insert(MutMap::default()), Occupied(entry) => entry.into_mut(),
Occupied(entry) => entry.into_mut(), };
};
for (layout, pend) in specs { for (layout, pend) in specs {
existing.insert(*layout, pend.clone()); existing.insert(*layout, pend.clone());
}
} }
} }
@ -2085,13 +2083,13 @@ fn update<'a>(
.top_level_thunks .top_level_thunks
.entry(module_id) .entry(module_id)
.or_default() .or_default()
.extend(procs.module_thunks.iter().copied()); .extend(procs_base.module_thunks.iter().copied());
let found_specializations_module = FoundSpecializationsModule { let found_specializations_module = FoundSpecializationsModule {
module_id, module_id,
ident_ids, ident_ids,
layout_cache, layout_cache,
procs, procs_base,
subs, subs,
module_timing, module_timing,
}; };
@ -3937,7 +3935,7 @@ fn make_specializations<'a>(
home: ModuleId, home: ModuleId,
mut ident_ids: IdentIds, mut ident_ids: IdentIds,
mut subs: Subs, mut subs: Subs,
mut procs: Procs<'a>, procs_base: ProcsBase<'a>,
mut layout_cache: LayoutCache<'a>, mut layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations<'a>, specializations_we_must_make: ExternalSpecializations<'a>,
mut module_timing: ModuleTiming, mut module_timing: ModuleTiming,
@ -3958,6 +3956,13 @@ fn make_specializations<'a>(
call_specialization_counter: 1, call_specialization_counter: 1,
}; };
let mut procs = Procs::new_in(arena);
procs.partial_procs = procs_base.partial_procs;
procs.module_thunks = procs_base.module_thunks;
procs.runtime_errors = procs_base.runtime_errors;
procs.imported_module_thunks = procs_base.imported_module_thunks;
// TODO: for now this final specialization pass is sequential, // TODO: for now this final specialization pass is sequential,
// with no parallelization at all. We should try to parallelize // with no parallelization at all. We should try to parallelize
// this, but doing so will require a redesign of Procs. // this, but doing so will require a redesign of Procs.
@ -3965,6 +3970,7 @@ fn make_specializations<'a>(
&mut mono_env, &mut mono_env,
procs, procs,
specializations_we_must_make, specializations_we_must_make,
procs_base.pending_specializations,
&mut layout_cache, &mut layout_cache,
); );
@ -3988,11 +3994,36 @@ fn make_specializations<'a>(
} }
} }
#[derive(Clone, Debug)]
struct ProcsBase<'a> {
partial_procs: BumpMap<Symbol, PartialProc<'a>>,
module_thunks: &'a [Symbol],
pending_specializations: BumpMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>,
runtime_errors: BumpMap<Symbol, &'a str>,
imported_module_thunks: &'a [Symbol],
}
impl<'a> ProcsBase<'a> {
fn add_pending(
&mut self,
symbol: Symbol,
layout: ProcLayout<'a>,
pending: PendingSpecialization<'a>,
) {
let all_pending = self
.pending_specializations
.entry(symbol)
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
all_pending.insert(layout, pending);
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn build_pending_specializations<'a>( fn build_pending_specializations<'a>(
arena: &'a Bump, arena: &'a Bump,
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
imported_module_thunks: BumpSet<Symbol>, imported_module_thunks: &'a [Symbol],
home: ModuleId, home: ModuleId,
mut ident_ids: IdentIds, mut ident_ids: IdentIds,
decls: Vec<Declaration>, decls: Vec<Declaration>,
@ -4003,10 +4034,16 @@ fn build_pending_specializations<'a>(
exposed_to_host: MutMap<Symbol, Variable>, exposed_to_host: MutMap<Symbol, Variable>,
) -> Msg<'a> { ) -> Msg<'a> {
let find_specializations_start = SystemTime::now(); let find_specializations_start = SystemTime::now();
let mut procs = Procs::new_in(arena);
debug_assert!(procs.imported_module_thunks.is_empty()); let mut module_thunks = bumpalo::collections::Vec::new_in(arena);
procs.imported_module_thunks = imported_module_thunks;
let mut procs_base = ProcsBase {
partial_procs: BumpMap::default(),
module_thunks: &[],
pending_specializations: BumpMap::default(),
runtime_errors: BumpMap::default(),
imported_module_thunks,
};
let mut mono_problems = std::vec::Vec::new(); let mut mono_problems = std::vec::Vec::new();
let mut subs = solved_subs.into_inner(); let mut subs = solved_subs.into_inner();
@ -4029,7 +4066,8 @@ fn build_pending_specializations<'a>(
match decl { match decl {
Declare(def) | Builtin(def) => add_def_to_module( Declare(def) | Builtin(def) => add_def_to_module(
&mut layout_cache, &mut layout_cache,
&mut procs, &mut procs_base,
&mut module_thunks,
&mut mono_env, &mut mono_env,
def, def,
&exposed_to_host, &exposed_to_host,
@ -4039,7 +4077,8 @@ fn build_pending_specializations<'a>(
for def in defs { for def in defs {
add_def_to_module( add_def_to_module(
&mut layout_cache, &mut layout_cache,
&mut procs, &mut procs_base,
&mut module_thunks,
&mut mono_env, &mut mono_env,
def, def,
&exposed_to_host, &exposed_to_host,
@ -4054,6 +4093,8 @@ fn build_pending_specializations<'a>(
} }
} }
procs_base.module_thunks = module_thunks.into_bump_slice();
let problems = mono_env.problems.to_vec(); let problems = mono_env.problems.to_vec();
let find_specializations_end = SystemTime::now(); let find_specializations_end = SystemTime::now();
@ -4066,7 +4107,7 @@ fn build_pending_specializations<'a>(
solved_subs: roc_types::solved_types::Solved(subs), solved_subs: roc_types::solved_types::Solved(subs),
ident_ids, ident_ids,
layout_cache, layout_cache,
procs, procs_base,
problems, problems,
module_timing, module_timing,
} }
@ -4074,7 +4115,8 @@ fn build_pending_specializations<'a>(
fn add_def_to_module<'a>( fn add_def_to_module<'a>(
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>, procs: &mut ProcsBase<'a>,
module_thunks: &mut bumpalo::collections::Vec<'a, Symbol>,
mono_env: &mut roc_mono::ir::Env<'a, '_>, mono_env: &mut roc_mono::ir::Env<'a, '_>,
def: roc_can::def::Def, def: roc_can::def::Def,
exposed_to_host: &MutMap<Symbol, Variable>, exposed_to_host: &MutMap<Symbol, Variable>,
@ -4136,20 +4178,23 @@ fn add_def_to_module<'a>(
} }
}; };
procs.insert_exposed( let pending = PendingSpecialization::from_exposed_function(
symbol,
ProcLayout::from_raw(mono_env.arena, layout),
mono_env.arena, mono_env.arena,
mono_env.subs, mono_env.subs,
def.annotation, def.annotation,
annotation, annotation,
); );
procs.add_pending(
symbol,
ProcLayout::from_raw(mono_env.arena, layout),
pending,
);
} }
procs.insert_named( let partial_proc = PartialProc::from_named_function(
mono_env, mono_env,
layout_cache, layout_cache,
symbol,
annotation, annotation,
loc_args, loc_args,
*loc_body, *loc_body,
@ -4157,10 +4202,12 @@ fn add_def_to_module<'a>(
is_recursive, is_recursive,
ret_var, ret_var,
); );
procs.partial_procs.insert(symbol, partial_proc);
} }
body => { body => {
// mark this symbols as a top-level thunk before any other work on the procs // mark this symbols as a top-level thunk before any other work on the procs
procs.module_thunks.insert(symbol); module_thunks.push(symbol);
// If this is an exposed symbol, we need to // If this is an exposed symbol, we need to
// register it as such. Otherwise, since it // register it as such. Otherwise, since it
@ -4195,14 +4242,14 @@ fn add_def_to_module<'a>(
} }
}; };
procs.insert_exposed( let pending = PendingSpecialization::from_exposed_function(
symbol,
top_level,
mono_env.arena, mono_env.arena,
mono_env.subs, mono_env.subs,
def.annotation, def.annotation,
annotation, annotation,
); );
procs.add_pending(symbol, top_level, pending);
} }
let proc = PartialProc { let proc = PartialProc {
@ -4316,7 +4363,7 @@ where
module_id, module_id,
ident_ids, ident_ids,
subs, subs,
procs, procs_base,
layout_cache, layout_cache,
specializations_we_must_make, specializations_we_must_make,
module_timing, module_timing,
@ -4325,7 +4372,7 @@ where
module_id, module_id,
ident_ids, ident_ids,
subs, subs,
procs, procs_base,
layout_cache, layout_cache,
specializations_we_must_make, specializations_we_must_make,
module_timing, module_timing,

View file

@ -71,6 +71,7 @@ pub enum LowLevel {
NumLte, NumLte,
NumCompare, NumCompare,
NumDivUnchecked, NumDivUnchecked,
NumDivCeilUnchecked,
NumRemUnchecked, NumRemUnchecked,
NumIsMultipleOf, NumIsMultipleOf,
NumAbs, NumAbs,
@ -107,6 +108,119 @@ pub enum LowLevel {
ExpectTrue, ExpectTrue,
} }
macro_rules! first_order {
() => {
StrConcat
| StrJoinWith
| StrIsEmpty
| StrStartsWith
| StrStartsWithCodePt
| StrEndsWith
| StrSplit
| StrCountGraphemes
| StrFromInt
| StrFromUtf8
| StrFromUtf8Range
| StrToUtf8
| StrRepeat
| StrFromFloat
| ListLen
| ListGetUnsafe
| ListSet
| ListDrop
| ListDropAt
| ListSingle
| ListRepeat
| ListReverse
| ListConcat
| ListContains
| ListAppend
| ListPrepend
| ListJoin
| ListRange
| ListSwap
| DictSize
| DictEmpty
| DictInsert
| DictRemove
| DictContains
| DictGetUnsafe
| DictKeys
| DictValues
| DictUnion
| DictIntersection
| DictDifference
| SetFromList
| NumAdd
| NumAddWrap
| NumAddChecked
| NumSub
| NumSubWrap
| NumSubChecked
| NumMul
| NumMulWrap
| NumMulChecked
| NumGt
| NumGte
| NumLt
| NumLte
| NumCompare
| NumDivUnchecked
| NumDivCeilUnchecked
| NumRemUnchecked
| NumIsMultipleOf
| NumAbs
| NumNeg
| NumSin
| NumCos
| NumSqrtUnchecked
| NumLogUnchecked
| NumRound
| NumToFloat
| NumPow
| NumCeiling
| NumPowInt
| NumFloor
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumBitwiseAnd
| NumBitwiseXor
| NumBitwiseOr
| NumShiftLeftBy
| NumShiftRightBy
| NumBytesToU16
| NumBytesToU32
| NumShiftRightZfBy
| NumIntCast
| Eq
| NotEq
| And
| Or
| Not
| Hash
| ExpectTrue
};
}
macro_rules! higher_order {
() => {
ListMap
| ListMap2
| ListMap3
| ListMapWithIndex
| ListKeepIf
| ListWalk
| ListWalkUntil
| ListWalkBackwards
| ListKeepOks
| ListKeepErrs
| ListSortWith
| DictWalk
};
}
impl LowLevel { impl LowLevel {
/// is one of the arguments always a function? /// is one of the arguments always a function?
/// An example is List.map. /// An example is List.map.
@ -114,25 +228,28 @@ impl LowLevel {
use LowLevel::*; use LowLevel::*;
match self { match self {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt first_order!() => false,
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 higher_order!() => true,
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe }
| ListSet | ListDrop | ListDropAt | ListSingle | ListRepeat | ListReverse }
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange
| ListSwap | DictSize | DictEmpty | DictInsert | DictRemove | DictContains
| DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection
| DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub
| NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte
| NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf
| NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
| NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan
| NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast
| Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false,
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk pub fn function_argument_position(&self) -> usize {
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith use LowLevel::*;
| DictWalk => true,
match self {
first_order!() => unreachable!(),
ListMap => 1,
ListMap2 => 2,
ListMap3 => 3,
ListMapWithIndex => 1,
ListKeepIf => 1,
ListWalk => 2,
ListWalkUntil => 2,
ListWalkBackwards => 2,
ListKeepOks => 1,
ListKeepErrs => 1,
ListSortWith => 1,
DictWalk => 2,
} }
} }
} }

View file

@ -872,6 +872,9 @@ define_builtins! {
// used by the dev backend to store the pointer to where to store large return types // used by the dev backend to store the pointer to where to store large return types
23 RET_POINTER: "#ret_pointer" 23 RET_POINTER: "#ret_pointer"
// used in wasm dev backend to mark values in the VM stack that have no other Symbol
24 WASM_ANONYMOUS_STACK_VALUE: "#wasm_anonymous_stack_value"
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -980,6 +983,7 @@ define_builtins! {
103 NUM_BYTES_TO_U16: "bytesToU16" 103 NUM_BYTES_TO_U16: "bytesToU16"
104 NUM_BYTES_TO_U32: "bytesToU32" 104 NUM_BYTES_TO_U32: "bytesToU32"
105 NUM_CAST_TO_NAT: "#castToNat" 105 NUM_CAST_TO_NAT: "#castToNat"
106 NUM_DIV_CEIL: "divCeil"
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -611,200 +611,178 @@ fn call_spec(
op, op,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
function_name,
function_env,
.. ..
} => { } => {
let array = specialization_id.to_bytes(); let array = specialization_id.to_bytes();
let spec_var = CalleeSpecVar(&array); let spec_var = CalleeSpecVar(&array);
let symbol = {
use roc_module::low_level::LowLevel::*;
match op {
ListMap | ListMapWithIndex => call.arguments[1],
ListMap2 => call.arguments[2],
ListMap3 => call.arguments[3],
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => call.arguments[2],
ListKeepIf | ListKeepOks | ListKeepErrs => call.arguments[1],
ListSortWith => call.arguments[1],
_ => unreachable!(),
}
};
let it = arg_layouts.iter().copied(); let it = arg_layouts.iter().copied();
let bytes = func_name_bytes_help(symbol, it, *ret_layout); let bytes = func_name_bytes_help(*function_name, it, *ret_layout);
let name = FuncName(&bytes); let name = FuncName(&bytes);
let module = MOD_APP; let module = MOD_APP;
{ use crate::low_level::HigherOrder::*;
use roc_module::low_level::LowLevel::*;
match op { match op {
DictWalk => { DictWalk { xs, state } => {
let dict = env.symbols[&call.arguments[0]]; let dict = env.symbols[xs];
let state = env.symbols[&call.arguments[1]]; let state = env.symbols[state];
let closure_env = env.symbols[&call.arguments[3]]; let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?; let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?; let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?; let first = builder.add_bag_get(block, bag)?;
let key = builder.add_get_tuple_field(block, first, 0)?; let key = builder.add_get_tuple_field(block, first, 0)?;
let val = builder.add_get_tuple_field(block, first, 1)?; let val = builder.add_get_tuple_field(block, first, 1)?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[state, key, val])? builder.add_make_tuple(block, &[state, key, val])?
} else { } else {
builder.add_make_tuple(block, &[state, key, val, closure_env])? builder.add_make_tuple(block, &[state, key, val, closure_env])?
}; };
builder.add_call(block, spec_var, module, name, argument)?; builder.add_call(block, spec_var, module, name, argument)?;
} }
ListWalk | ListWalkBackwards | ListWalkUntil => { ListWalk { xs, state }
let list = env.symbols[&call.arguments[0]]; | ListWalkBackwards { xs, state }
let state = env.symbols[&call.arguments[1]]; | ListWalkUntil { xs, state } => {
let closure_env = env.symbols[&call.arguments[3]]; let list = env.symbols[xs];
let state = env.symbols[state];
let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?; let first = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[state, first])? builder.add_make_tuple(block, &[state, first])?
} else { } else {
builder.add_make_tuple(block, &[state, first, closure_env])? builder.add_make_tuple(block, &[state, first, closure_env])?
}; };
builder.add_call(block, spec_var, module, name, argument)?; builder.add_call(block, spec_var, module, name, argument)?;
} }
ListMapWithIndex => { ListMapWithIndex { xs } => {
let list = env.symbols[&call.arguments[0]]; let list = env.symbols[xs];
let closure_env = env.symbols[&call.arguments[2]]; let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?; let first = builder.add_bag_get(block, bag)?;
let index = builder.add_make_tuple(block, &[])?; let index = builder.add_make_tuple(block, &[])?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[index, first])? builder.add_make_tuple(block, &[index, first])?
} else { } else {
builder.add_make_tuple(block, &[index, first, closure_env])? builder.add_make_tuple(block, &[index, first, closure_env])?
}; };
builder.add_call(block, spec_var, module, name, argument)?; builder.add_call(block, spec_var, module, name, argument)?;
} }
ListMap => { ListMap { xs } => {
let list1 = env.symbols[&call.arguments[0]]; let list = env.symbols[xs];
let closure_env = env.symbols[&call.arguments[2]]; let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?; let elem1 = builder.add_bag_get(block, bag1)?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1])? builder.add_make_tuple(block, &[elem1])?
} else { } else {
builder.add_make_tuple(block, &[elem1, closure_env])? builder.add_make_tuple(block, &[elem1, closure_env])?
}; };
builder.add_call(block, spec_var, module, name, argument)?; builder.add_call(block, spec_var, module, name, argument)?;
} }
ListSortWith => { ListSortWith { xs } => {
let list1 = env.symbols[&call.arguments[0]]; let list = env.symbols[xs];
let closure_env = env.symbols[&call.arguments[2]]; let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?; let elem1 = builder.add_bag_get(block, bag1)?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem1])? builder.add_make_tuple(block, &[elem1, elem1])?
} else { } else {
builder.add_make_tuple(block, &[elem1, elem1, closure_env])? builder.add_make_tuple(block, &[elem1, elem1, closure_env])?
}; };
builder.add_call(block, spec_var, module, name, argument)?; builder.add_call(block, spec_var, module, name, argument)?;
} }
ListMap2 => { ListMap2 { xs, ys } => {
let list1 = env.symbols[&call.arguments[0]]; let list1 = env.symbols[xs];
let list2 = env.symbols[&call.arguments[1]]; let list2 = env.symbols[ys];
let closure_env = env.symbols[&call.arguments[3]]; let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?; let elem1 = builder.add_bag_get(block, bag1)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?; let elem2 = builder.add_bag_get(block, bag2)?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2])? builder.add_make_tuple(block, &[elem1, elem2])?
} else { } else {
builder.add_make_tuple(block, &[elem1, elem2, closure_env])? builder.add_make_tuple(block, &[elem1, elem2, closure_env])?
}; };
builder.add_call(block, spec_var, module, name, argument)?; builder.add_call(block, spec_var, module, name, argument)?;
} }
ListMap3 => { ListMap3 { xs, ys, zs } => {
let list1 = env.symbols[&call.arguments[0]]; let list1 = env.symbols[xs];
let list2 = env.symbols[&call.arguments[1]]; let list2 = env.symbols[ys];
let list3 = env.symbols[&call.arguments[2]]; let list3 = env.symbols[zs];
let closure_env = env.symbols[&call.arguments[4]]; let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?; let elem1 = builder.add_bag_get(block, bag1)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?; let elem2 = builder.add_bag_get(block, bag2)?;
let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?; let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?; let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?;
let elem3 = builder.add_bag_get(block, bag3)?; let elem3 = builder.add_bag_get(block, bag3)?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2, elem3])? builder.add_make_tuple(block, &[elem1, elem2, elem3])?
} else { } else {
builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])? builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])?
}; };
builder.add_call(block, spec_var, module, name, argument)?; builder.add_call(block, spec_var, module, name, argument)?;
} }
ListKeepIf | ListKeepOks | ListKeepErrs => { ListKeepIf { xs } | ListKeepOks { xs } | ListKeepErrs { xs } => {
let list = env.symbols[&call.arguments[0]]; let list = env.symbols[xs];
let closure_env = env.symbols[&call.arguments[2]]; let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
// let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; // let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?; let first = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() { let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[first])? builder.add_make_tuple(block, &[first])?
} else { } else {
builder.add_make_tuple(block, &[first, closure_env])? builder.add_make_tuple(block, &[first, closure_env])?
}; };
let result = builder.add_call(block, spec_var, module, name, argument)?; let result = builder.add_call(block, spec_var, module, name, argument)?;
let unit = builder.add_tuple_type(&[])?; let unit = builder.add_tuple_type(&[])?;
builder.add_unknown_with(block, &[result], unit)?; builder.add_unknown_with(block, &[result], unit)?;
}
_ => {
// fake a call to the function argument
// to make sure the function is specialized
// very invalid
let arg_value_id = build_tuple_value(builder, env, block, &[])?;
builder.add_call(block, spec_var, module, name, arg_value_id)?;
}
} }
} }

View file

@ -597,131 +597,84 @@ impl<'a> BorrowInfState<'a> {
op, op,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
function_name,
function_env,
.. ..
} => { } => {
use roc_module::low_level::LowLevel::*; use crate::low_level::HigherOrder::*;
debug_assert!(op.is_higher_order());
let closure_layout = ProcLayout { let closure_layout = ProcLayout {
arguments: arg_layouts, arguments: arg_layouts,
result: *ret_layout, result: *ret_layout,
}; };
let function_ps = match param_map.get_symbol(*function_name, closure_layout) {
Some(function_ps) => function_ps,
None => unreachable!(),
};
match op { match op {
ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => { ListMap { xs }
match param_map.get_symbol(arguments[1], closure_layout) { | ListKeepIf { xs }
Some(function_ps) => { | ListKeepOks { xs }
// own the list if the function wants to own the element | ListKeepErrs { xs } => {
if !function_ps[0].borrow { // own the list if the function wants to own the element
self.own_var(arguments[0]); if !function_ps[0].borrow {
} self.own_var(*xs);
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(1).map(|p| p.borrow) {
self.own_var(arguments[2]);
}
}
None => unreachable!(),
} }
} }
ListMapWithIndex => { ListMapWithIndex { xs } => {
match param_map.get_symbol(arguments[1], closure_layout) { // own the list if the function wants to own the element
Some(function_ps) => { if !function_ps[1].borrow {
// own the list if the function wants to own the element self.own_var(*xs);
if !function_ps[1].borrow {
self.own_var(arguments[0]);
}
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
self.own_var(arguments[2]);
}
}
None => unreachable!(),
} }
} }
ListMap2 => match param_map.get_symbol(arguments[2], closure_layout) { ListMap2 { xs, ys } => {
Some(function_ps) => { // own the lists if the function wants to own the element
// own the lists if the function wants to own the element if !function_ps[0].borrow {
if !function_ps[0].borrow { self.own_var(*xs);
self.own_var(arguments[0]);
}
if !function_ps[1].borrow {
self.own_var(arguments[1]);
}
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
self.own_var(arguments[3]);
}
} }
None => unreachable!(),
},
ListMap3 => match param_map.get_symbol(arguments[3], closure_layout) {
Some(function_ps) => {
// own the lists if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(arguments[0]);
}
if !function_ps[1].borrow {
self.own_var(arguments[1]);
}
if !function_ps[2].borrow {
self.own_var(arguments[2]);
}
// own the closure environment if the function needs to own it if !function_ps[1].borrow {
if let Some(false) = function_ps.get(3).map(|p| p.borrow) { self.own_var(*ys);
self.own_var(arguments[4]);
}
}
None => unreachable!(),
},
ListSortWith => {
match param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => {
// always own the input list
self.own_var(arguments[0]);
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
self.own_var(arguments[2]);
}
}
None => unreachable!(),
} }
} }
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { ListMap3 { xs, ys, zs } => {
match param_map.get_symbol(arguments[2], closure_layout) { // own the lists if the function wants to own the element
Some(function_ps) => { if !function_ps[0].borrow {
// own the default value if the function wants to own it self.own_var(*xs);
if !function_ps[0].borrow { }
self.own_var(arguments[1]); if !function_ps[1].borrow {
} self.own_var(*ys);
}
// own the data structure if the function wants to own the element if !function_ps[2].borrow {
if !function_ps[1].borrow { self.own_var(*zs);
self.own_var(arguments[0]);
}
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
self.own_var(arguments[3]);
}
}
None => unreachable!(),
} }
} }
_ => { ListSortWith { xs } => {
// very unsure what demand RunLowLevel should place upon its arguments // always own the input list
self.own_var(z); self.own_var(*xs);
let ps = lowlevel_borrow_signature(self.arena, *op);
self.own_args_using_bools(arguments, ps);
} }
ListWalk { xs, state }
| ListWalkUntil { xs, state }
| ListWalkBackwards { xs, state }
| DictWalk { xs, state } => {
// own the default value if the function wants to own it
if !function_ps[0].borrow {
self.own_var(*state);
}
// own the data structure if the function wants to own the element
if !function_ps[1].borrow {
self.own_var(*xs);
}
}
}
// own the closure environment if the function needs to own it
let function_env_position = op.function_arity();
if let Some(false) = function_ps.get(function_env_position).map(|p| p.borrow) {
self.own_var(*function_env);
} }
} }
@ -1000,9 +953,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked
| NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare
| NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt | NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow
| NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
| NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin

View file

@ -469,8 +469,13 @@ impl<'a> Context<'a> {
specialization_id, specialization_id,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
function_name,
function_env,
.. ..
} => { } => {
// setup
use crate::low_level::HigherOrder::*;
macro_rules! create_call { macro_rules! create_call {
($borrows:expr) => { ($borrows:expr) => {
Expr::Call(crate::ir::Call { Expr::Call(crate::ir::Call {
@ -480,6 +485,8 @@ impl<'a> Context<'a> {
closure_env_layout: *closure_env_layout, closure_env_layout: *closure_env_layout,
function_owns_closure_data: true, function_owns_closure_data: true,
specialization_id: *specialization_id, specialization_id: *specialization_id,
function_name: *function_name,
function_env: *function_env,
arg_layouts, arg_layouts,
ret_layout: *ret_layout, ret_layout: *ret_layout,
} }
@ -512,167 +519,101 @@ impl<'a> Context<'a> {
result: *ret_layout, result: *ret_layout,
}; };
let function_ps = match self.param_map.get_symbol(*function_name, function_layout) {
Some(function_ps) => function_ps,
None => unreachable!(),
};
match op { match op {
roc_module::low_level::LowLevel::ListMap ListMap { xs }
| roc_module::low_level::LowLevel::ListKeepIf | ListKeepIf { xs }
| roc_module::low_level::LowLevel::ListKeepOks | ListKeepOks { xs }
| roc_module::low_level::LowLevel::ListKeepErrs => { | ListKeepErrs { xs } => {
match self.param_map.get_symbol(arguments[1], function_layout) { let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
Some(function_ps) => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel( let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
arguments,
&borrows,
b,
b_live_vars,
);
// if the list is owned, then all elements have been consumed, but not the list itself // if the list is owned, then all elements have been consumed, but not the list itself
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b); let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let v = create_call!(function_ps.get(1)); let v = create_call!(function_ps.get(1));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) &*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
} }
roc_module::low_level::LowLevel::ListMapWithIndex => { ListMap2 { xs, ys } => {
match self.param_map.get_symbol(arguments[1], function_layout) { let borrows = [
Some(function_ps) => { function_ps[0].borrow,
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA]; function_ps[1].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel( let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
arguments,
&borrows,
b,
b_live_vars,
);
let b = decref_if_owned!(function_ps[1].borrow, arguments[0], b); let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let v = create_call!(function_ps.get(2)); let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) &*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
} }
roc_module::low_level::LowLevel::ListMap2 => { ListMap3 { xs, ys, zs } => {
match self.param_map.get_symbol(arguments[2], function_layout) { let borrows = [
Some(function_ps) => { function_ps[0].borrow,
let borrows = [ function_ps[1].borrow,
function_ps[0].borrow, function_ps[2].borrow,
function_ps[1].borrow, FUNCTION,
FUNCTION, CLOSURE_DATA,
CLOSURE_DATA, ];
];
let b = self.add_dec_after_lowlevel( let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
arguments,
&borrows,
b,
b_live_vars,
);
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b); let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, arguments[1], b); let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let b = decref_if_owned!(function_ps[2].borrow, *zs, b);
let v = create_call!(function_ps.get(2)); let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) &*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
} }
roc_module::low_level::LowLevel::ListMap3 => { ListMapWithIndex { xs } => {
match self.param_map.get_symbol(arguments[3], function_layout) { let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];
Some(function_ps) => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
function_ps[2].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel( let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
arguments,
&borrows,
b,
b_live_vars,
);
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b); let b = decref_if_owned!(function_ps[1].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, arguments[1], b);
let b = decref_if_owned!(function_ps[2].borrow, arguments[2], b);
let v = create_call!(function_ps.get(3)); let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) &*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
} }
roc_module::low_level::LowLevel::ListSortWith => { ListSortWith { xs: _ } => {
match self.param_map.get_symbol(arguments[1], function_layout) { let borrows = [OWNED, FUNCTION, CLOSURE_DATA];
Some(function_ps) => {
let borrows = [OWNED, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel( let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
arguments,
&borrows,
b,
b_live_vars,
);
let v = create_call!(function_ps.get(2)); let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) &*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
} }
roc_module::low_level::LowLevel::ListWalk ListWalk { xs, state: _ }
| roc_module::low_level::LowLevel::ListWalkUntil | ListWalkUntil { xs, state: _ }
| roc_module::low_level::LowLevel::ListWalkBackwards | ListWalkBackwards { xs, state: _ }
| roc_module::low_level::LowLevel::DictWalk => { | DictWalk { xs, state: _ } => {
match self.param_map.get_symbol(arguments[2], function_layout) { // borrow data structure based on first argument of the folded function
Some(function_ps) => { // borrow the default based on second argument of the folded function
// borrow data structure based on first argument of the folded function let borrows = [
// borrow the default based on second argument of the folded function function_ps[1].borrow,
let borrows = [ function_ps[0].borrow,
function_ps[1].borrow, FUNCTION,
function_ps[0].borrow, CLOSURE_DATA,
FUNCTION, ];
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel( let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
arguments,
&borrows,
b,
b_live_vars,
);
let b = decref_if_owned!(function_ps[1].borrow, arguments[0], b); let b = decref_if_owned!(function_ps[1].borrow, *xs, b);
let v = create_call!(function_ps.get(2)); let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
}
_ => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
&*self.arena.alloc(Stmt::Let(z, v, l, b)) &*self.arena.alloc(Stmt::Let(z, v, l, b))
} }

View file

@ -8,7 +8,7 @@ use crate::layout::{
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, BumpSet, MutMap, MutSet}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap, MutSet};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
@ -79,6 +79,55 @@ pub struct PartialProc<'a> {
pub is_self_recursive: bool, pub is_self_recursive: bool,
} }
impl<'a> PartialProc<'a> {
#[allow(clippy::too_many_arguments)]
pub fn from_named_function(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
annotation: Variable,
loc_args: std::vec::Vec<(Variable, Located<roc_can::pattern::Pattern>)>,
loc_body: Located<roc_can::expr::Expr>,
captured_symbols: CapturedSymbols<'a>,
is_self_recursive: bool,
ret_var: Variable,
) -> PartialProc<'a> {
let number_of_arguments = loc_args.len();
match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) {
Ok((_, pattern_symbols, body)) => {
// a named closure. Since these aren't specialized by the surrounding
// context, we can't add pending specializations for them yet.
// (If we did, all named polymorphic functions would immediately error
// on trying to convert a flex var to a Layout.)
let pattern_symbols = pattern_symbols.into_bump_slice();
PartialProc {
annotation,
pattern_symbols,
captured_symbols,
body: body.value,
is_self_recursive,
}
}
Err(error) => {
let mut pattern_symbols = Vec::with_capacity_in(number_of_arguments, env.arena);
for _ in 0..number_of_arguments {
pattern_symbols.push(env.unique_symbol());
}
PartialProc {
annotation,
pattern_symbols: pattern_symbols.into_bump_slice(),
captured_symbols: CapturedSymbols::None,
body: roc_can::expr::Expr::RuntimeError(error.value),
is_self_recursive: false,
}
}
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum CapturedSymbols<'a> { pub enum CapturedSymbols<'a> {
None, None,
@ -139,6 +188,24 @@ impl<'a> PendingSpecialization<'a> {
_lifetime: std::marker::PhantomData, _lifetime: std::marker::PhantomData,
} }
} }
/// Add a named function that will be publicly exposed to the host
pub fn from_exposed_function(
arena: &'a Bump,
subs: &Subs,
opt_annotation: Option<roc_can::def::Annotation>,
fn_var: Variable,
) -> Self {
match opt_annotation {
None => PendingSpecialization::from_var(arena, subs, fn_var),
Some(annotation) => PendingSpecialization::from_var_host_exposed(
arena,
subs,
fn_var,
&annotation.introduced_variables.host_exposed_aliases,
),
}
}
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -350,8 +417,8 @@ impl<'a> ExternalSpecializations<'a> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Procs<'a> { pub struct Procs<'a> {
pub partial_procs: BumpMap<Symbol, PartialProc<'a>>, pub partial_procs: BumpMap<Symbol, PartialProc<'a>>,
pub imported_module_thunks: BumpSet<Symbol>, pub imported_module_thunks: &'a [Symbol],
pub module_thunks: BumpSet<Symbol>, pub module_thunks: &'a [Symbol],
pub pending_specializations: pub pending_specializations:
Option<BumpMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>>, Option<BumpMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>>,
pub specialized: BumpMap<(Symbol, ProcLayout<'a>), InProgressProc<'a>>, pub specialized: BumpMap<(Symbol, ProcLayout<'a>), InProgressProc<'a>>,
@ -364,8 +431,8 @@ impl<'a> Procs<'a> {
pub fn new_in(arena: &'a Bump) -> Self { pub fn new_in(arena: &'a Bump) -> Self {
Self { Self {
partial_procs: BumpMap::new_in(arena), partial_procs: BumpMap::new_in(arena),
imported_module_thunks: BumpSet::new_in(arena), imported_module_thunks: &[],
module_thunks: BumpSet::new_in(arena), module_thunks: &[],
pending_specializations: Some(BumpMap::new_in(arena)), pending_specializations: Some(BumpMap::new_in(arena)),
specialized: BumpMap::new_in(arena), specialized: BumpMap::new_in(arena),
runtime_errors: BumpMap::new_in(arena), runtime_errors: BumpMap::new_in(arena),
@ -382,6 +449,14 @@ pub enum InProgressProc<'a> {
} }
impl<'a> Procs<'a> { impl<'a> Procs<'a> {
fn is_imported_module_thunk(&self, symbol: Symbol) -> bool {
self.imported_module_thunks.iter().any(|x| *x == symbol)
}
fn is_module_thunk(&self, symbol: Symbol) -> bool {
self.module_thunks.iter().any(|x| *x == symbol)
}
pub fn get_specialized_procs_without_rc( pub fn get_specialized_procs_without_rc(
self, self,
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
@ -420,65 +495,9 @@ impl<'a> Procs<'a> {
result result
} }
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
pub fn insert_named(
&mut self,
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
name: Symbol,
annotation: Variable,
loc_args: std::vec::Vec<(Variable, Located<roc_can::pattern::Pattern>)>,
loc_body: Located<roc_can::expr::Expr>,
captured_symbols: CapturedSymbols<'a>,
is_self_recursive: bool,
ret_var: Variable,
) {
let number_of_arguments = loc_args.len();
match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) {
Ok((_, pattern_symbols, body)) => {
// a named closure. Since these aren't specialized by the surrounding
// context, we can't add pending specializations for them yet.
// (If we did, all named polymorphic functions would immediately error
// on trying to convert a flex var to a Layout.)
let pattern_symbols = pattern_symbols.into_bump_slice();
self.partial_procs.insert(
name,
PartialProc {
annotation,
pattern_symbols,
captured_symbols,
body: body.value,
is_self_recursive,
},
);
}
Err(error) => {
let mut pattern_symbols = Vec::with_capacity_in(number_of_arguments, env.arena);
for _ in 0..number_of_arguments {
pattern_symbols.push(env.unique_symbol());
}
self.partial_procs.insert(
name,
PartialProc {
annotation,
pattern_symbols: pattern_symbols.into_bump_slice(),
captured_symbols: CapturedSymbols::None,
body: roc_can::expr::Expr::RuntimeError(error.value),
is_self_recursive: false,
},
);
}
}
}
// TODO trim these down // TODO trim these down
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn insert_anonymous( fn insert_anonymous(
&mut self, &mut self,
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
symbol: Symbol, symbol: Symbol,
@ -540,12 +559,13 @@ impl<'a> Procs<'a> {
}; };
} }
if self.is_module_thunk(symbol) {
debug_assert!(layout.arguments.is_empty());
}
match &mut self.pending_specializations { match &mut self.pending_specializations {
Some(pending_specializations) => { Some(pending_specializations) => {
// register the pending specialization, so this gets code genned later // register the pending specialization, so this gets code genned later
if self.module_thunks.contains(&symbol) {
debug_assert!(layout.arguments.is_empty());
}
add_pending(pending_specializations, symbol, layout, pending); add_pending(pending_specializations, symbol, layout, pending);
self.partial_procs.insert(symbol, partial_proc); self.partial_procs.insert(symbol, partial_proc);
@ -569,7 +589,7 @@ impl<'a> Procs<'a> {
proc.name proc.name
); );
if self.module_thunks.contains(&proc.name) { if self.is_module_thunk(proc.name) {
debug_assert!(top_level.arguments.is_empty()); debug_assert!(top_level.arguments.is_empty());
} }
@ -589,50 +609,7 @@ impl<'a> Procs<'a> {
} }
} }
/// Add a named function that will be publicly exposed to the host fn insert_passed_by_name(
pub fn insert_exposed(
&mut self,
name: Symbol,
layout: ProcLayout<'a>,
arena: &'a Bump,
subs: &Subs,
opt_annotation: Option<roc_can::def::Annotation>,
fn_var: Variable,
) {
let tuple = (name, layout);
// If we've already specialized this one, no further work is needed.
if self.specialized.contains_key(&tuple) {
return;
}
// We're done with that tuple, so move layout back out to avoid cloning it.
let (name, layout) = tuple;
let pending = match opt_annotation {
None => PendingSpecialization::from_var(arena, subs, fn_var),
Some(annotation) => PendingSpecialization::from_var_host_exposed(
arena,
subs,
fn_var,
&annotation.introduced_variables.host_exposed_aliases,
),
};
// This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass!
match &mut self.pending_specializations {
Some(pending_specializations) => {
// register the pending specialization, so this gets code genned later
add_pending(pending_specializations, name, layout, pending)
}
None => unreachable!(
r"insert_exposed was called after the pending specializations phase had already completed!"
),
}
}
/// TODO
pub fn insert_passed_by_name(
&mut self, &mut self,
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
fn_var: Variable, fn_var: Variable,
@ -651,16 +628,17 @@ impl<'a> Procs<'a> {
return; return;
} }
// register the pending specialization, so this gets code genned later
if self.module_thunks.contains(&name) {
debug_assert!(layout.arguments.is_empty());
}
// This should only be called when pending_specializations is Some. // This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass! // Otherwise, it's being called in the wrong pass!
match &mut self.pending_specializations { match &mut self.pending_specializations {
Some(pending_specializations) => { Some(pending_specializations) => {
let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var);
// register the pending specialization, so this gets code genned later
if self.module_thunks.contains(&name) {
debug_assert!(layout.arguments.is_empty());
}
add_pending(pending_specializations, name, layout, pending) add_pending(pending_specializations, name, layout, pending)
} }
None => { None => {
@ -1102,14 +1080,23 @@ pub enum CallType<'a> {
update_mode: UpdateModeId, update_mode: UpdateModeId,
}, },
HigherOrderLowLevel { HigherOrderLowLevel {
op: LowLevel, op: crate::low_level::HigherOrder,
/// the layout of the closure argument, if any /// the layout of the closure argument, if any
closure_env_layout: Option<Layout<'a>>, closure_env_layout: Option<Layout<'a>>,
/// specialization id of the function argument
specialization_id: CallSpecId, /// name of the top-level function that is passed as an argument
/// does the function need to own the closure data /// e.g. in `List.map xs Num.abs` this would be `Num.abs`
function_name: Symbol,
/// Symbol of the environment captured by the function argument
function_env: Symbol,
/// does the function argument need to own the closure data
function_owns_closure_data: bool, function_owns_closure_data: bool,
/// function layout
/// specialization id of the function argument, used for name generation
specialization_id: CallSpecId,
/// function layout, used for name generation
arg_layouts: &'a [Layout<'a>], arg_layouts: &'a [Layout<'a>],
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
}, },
@ -1694,15 +1681,23 @@ pub fn specialize_all<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
mut procs: Procs<'a>, mut procs: Procs<'a>,
externals_others_need: ExternalSpecializations<'a>, externals_others_need: ExternalSpecializations<'a>,
pending_specializations: BumpMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
) -> Procs<'a> { ) -> Procs<'a> {
specialize_all_help(env, &mut procs, externals_others_need, layout_cache); specialize_all_help(env, &mut procs, externals_others_need, layout_cache);
// When calling from_can, pending_specializations should be unavailable. // When calling from_can, pending_specializations should be unavailable.
// This must be a single pass, and we must not add any more entries to it! // This must be a single pass, and we must not add any more entries to it!
// observation: specialize_all_help does add to pending_specializations, but does not reference
// any existing values in it.
let opt_pending_specializations = std::mem::replace(&mut procs.pending_specializations, None); let opt_pending_specializations = std::mem::replace(&mut procs.pending_specializations, None);
for (name, by_layout) in opt_pending_specializations.into_iter().flatten() { let it = pending_specializations
.into_iter()
.chain(opt_pending_specializations.into_iter().flatten());
for (name, by_layout) in it {
for (outside_layout, pending) in by_layout.into_iter() { for (outside_layout, pending) in by_layout.into_iter() {
// If we've already seen this (Symbol, Layout) combination before, // If we've already seen this (Symbol, Layout) combination before,
// don't try to specialize it again. If we do, we'll loop forever! // don't try to specialize it again. If we do, we'll loop forever!
@ -1737,7 +1732,7 @@ pub fn specialize_all<'a>(
// TODO thiscode is duplicated elsewhere // TODO thiscode is duplicated elsewhere
let top_level = ProcLayout::from_raw(env.arena, layout); let top_level = ProcLayout::from_raw(env.arena, layout);
if procs.module_thunks.contains(&proc.name) { if procs.is_module_thunk(proc.name) {
debug_assert!( debug_assert!(
top_level.arguments.is_empty(), top_level.arguments.is_empty(),
"{:?} from {:?}", "{:?} from {:?}",
@ -1817,7 +1812,7 @@ fn specialize_all_help<'a>(
Ok((proc, layout)) => { Ok((proc, layout)) => {
let top_level = ProcLayout::from_raw(env.arena, layout); let top_level = ProcLayout::from_raw(env.arena, layout);
if procs.module_thunks.contains(&name) { if procs.is_module_thunk(name) {
debug_assert!(top_level.arguments.is_empty()); debug_assert!(top_level.arguments.is_empty());
} }
@ -2517,7 +2512,7 @@ where
.raw_from_var(env.arena, fn_var, env.subs) .raw_from_var(env.arena, fn_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
let raw = if procs.module_thunks.contains(&proc_name) { let raw = if procs.is_module_thunk(proc_name) {
match raw { match raw {
RawFunctionLayout::Function(_, lambda_set, _) => { RawFunctionLayout::Function(_, lambda_set, _) => {
RawFunctionLayout::ZeroArgumentThunk(Layout::LambdaSet(lambda_set)) RawFunctionLayout::ZeroArgumentThunk(Layout::LambdaSet(lambda_set))
@ -2620,7 +2615,7 @@ fn specialize_naked_symbol<'a>(
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
symbol: Symbol, symbol: Symbol,
) -> Stmt<'a> { ) -> Stmt<'a> {
if procs.module_thunks.contains(&symbol) { if procs.is_module_thunk(symbol) {
let partial_proc = procs.partial_procs.get(&symbol).unwrap(); let partial_proc = procs.partial_procs.get(&symbol).unwrap();
let fn_var = partial_proc.annotation; let fn_var = partial_proc.annotation;
@ -2679,48 +2674,6 @@ fn specialize_naked_symbol<'a>(
) )
} }
macro_rules! match_on_closure_argument {
($env:expr, $procs:expr, $layout_cache:expr, $closure_data_symbol:expr, $closure_data_var:expr, $op:expr, [$($x:expr),* $(,)?], $layout: expr, $assigned:expr, $hole:expr) => {{
let closure_data_layout = return_on_layout_error!(
$env,
$layout_cache.raw_from_var($env.arena, $closure_data_var, $env.subs)
);
let top_level = ProcLayout::from_raw($env.arena, closure_data_layout);
let arena = $env.arena;
let arg_layouts = top_level.arguments;
let ret_layout = top_level.result;
match closure_data_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
lowlevel_match_on_lambda_set(
$env,
lambda_set,
$op,
$closure_data_symbol,
|top_level_function, closure_data, closure_env_layout, specialization_id| self::Call {
call_type: CallType::HigherOrderLowLevel {
op: $op,
closure_env_layout,
specialization_id,
function_owns_closure_data: false,
arg_layouts,
ret_layout,
},
arguments: arena.alloc([$($x,)* top_level_function, closure_data]),
},
$layout,
$assigned,
$hole,
)
}
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"),
}
}};
}
fn try_make_literal<'a>( fn try_make_literal<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
can_expr: &roc_can::expr::Expr, can_expr: &roc_can::expr::Expr,
@ -2898,13 +2851,12 @@ pub fn with_hole<'a>(
// this should be a top-level declaration, and hence have no captured symbols // 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 // 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 insert_named // captured symbols into a CapturedSymbols and give it to PartialProc::from_named_function
debug_assert!(captured_symbols.is_empty()); debug_assert!(captured_symbols.is_empty());
procs.insert_named( let partial_proc = PartialProc::from_named_function(
env, env,
layout_cache, layout_cache,
*symbol,
function_type, function_type,
arguments, arguments,
loc_body, loc_body,
@ -2913,6 +2865,8 @@ pub fn with_hole<'a>(
return_type, return_type,
); );
procs.partial_procs.insert(*symbol, partial_proc);
return with_hole( return with_hole(
env, env,
cont.value, cont.value,
@ -3070,10 +3024,9 @@ pub fn with_hole<'a>(
let is_self_recursive = let is_self_recursive =
!matches!(recursive, roc_can::expr::Recursive::NotRecursive); !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
procs.insert_named( let partial_proc = PartialProc::from_named_function(
env, env,
layout_cache, layout_cache,
*symbol,
function_type, function_type,
arguments, arguments,
loc_body, loc_body,
@ -3082,6 +3035,8 @@ pub fn with_hole<'a>(
return_type, return_type,
); );
procs.partial_procs.insert(*symbol, partial_proc);
continue; continue;
} }
} }
@ -4013,51 +3968,66 @@ pub fn with_hole<'a>(
let layout = let layout =
return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs)); return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs));
use LowLevel::*; macro_rules! match_on_closure_argument {
match op { ( $ho:ident, [$($x:ident),* $(,)?]) => {{
ListMap | ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs let closure_index = op.function_argument_position();
| ListSortWith => {
debug_assert_eq!(arg_symbols.len(), 2);
let closure_index = 1;
let closure_data_symbol = arg_symbols[closure_index]; let closure_data_symbol = arg_symbols[closure_index];
let closure_data_var = args[closure_index].0; let closure_data_var = args[closure_index].0;
match_on_closure_argument!( let closure_data_layout = return_on_layout_error!(
env, env,
procs, layout_cache.raw_from_var(env.arena, closure_data_var, env.subs)
layout_cache, );
closure_data_symbol,
closure_data_var, let top_level = ProcLayout::from_raw(env.arena, closure_data_layout);
op,
[arg_symbols[0]], let arena = env.arena;
layout,
assigned, let arg_layouts = top_level.arguments;
hole let ret_layout = top_level.result;
)
} match closure_data_layout {
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { RawFunctionLayout::Function(_, lambda_set, _) => {
lowlevel_match_on_lambda_set(
env,
lambda_set,
op,
closure_data_symbol,
|top_level_function, closure_data, closure_env_layout, specialization_id| self::Call {
call_type: CallType::HigherOrderLowLevel {
op: crate::low_level::HigherOrder::$ho { $($x,)* },
closure_env_layout,
specialization_id,
function_owns_closure_data: false,
function_env: closure_data_symbol,
function_name: top_level_function,
arg_layouts,
ret_layout,
},
arguments: arena.alloc([$($x,)* top_level_function, closure_data]),
},
layout,
assigned,
hole,
)
}
RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"),
}
}};
}
macro_rules! walk {
($oh:ident) => {{
debug_assert_eq!(arg_symbols.len(), 3); debug_assert_eq!(arg_symbols.len(), 3);
const LIST_INDEX: usize = 0; const LIST_INDEX: usize = 0;
const DEFAULT_INDEX: usize = 1; const DEFAULT_INDEX: usize = 1;
const CLOSURE_INDEX: usize = 2; const CLOSURE_INDEX: usize = 2;
let closure_data_symbol = arg_symbols[CLOSURE_INDEX]; let xs = arg_symbols[LIST_INDEX];
let closure_data_var = args[CLOSURE_INDEX].0; let state = arg_symbols[DEFAULT_INDEX];
let stmt = match_on_closure_argument!( let stmt = match_on_closure_argument!($oh, [xs, state]);
env,
procs,
layout_cache,
closure_data_symbol,
closure_data_var,
op,
[arg_symbols[LIST_INDEX], arg_symbols[DEFAULT_INDEX]],
layout,
assigned,
hole
);
// because of a hack to implement List.product and List.sum, we need to also // because of a hack to implement List.product and List.sum, we need to also
// assign to symbols here. Normally the arguments to a lowlevel function are // assign to symbols here. Normally the arguments to a lowlevel function are
@ -4094,46 +4064,62 @@ pub fn with_hole<'a>(
arg_symbols[CLOSURE_INDEX], arg_symbols[CLOSURE_INDEX],
stmt, stmt,
) )
}};
}
use LowLevel::*;
match op {
ListMap => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListMap, [xs])
} }
ListMapWithIndex => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListMapWithIndex, [xs])
}
ListKeepIf => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListKeepIf, [xs])
}
ListKeepOks => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListKeepOks, [xs])
}
ListKeepErrs => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListKeepErrs, [xs])
}
ListSortWith => {
debug_assert_eq!(arg_symbols.len(), 2);
let xs = arg_symbols[0];
match_on_closure_argument!(ListSortWith, [xs])
}
ListWalk => walk!(ListWalk),
ListWalkUntil => walk!(ListWalkUntil),
ListWalkBackwards => walk!(ListWalkBackwards),
DictWalk => walk!(DictWalk),
ListMap2 => { ListMap2 => {
debug_assert_eq!(arg_symbols.len(), 3); debug_assert_eq!(arg_symbols.len(), 3);
let closure_index = 2; let xs = arg_symbols[0];
let closure_data_symbol = arg_symbols[closure_index]; let ys = arg_symbols[1];
let closure_data_var = args[closure_index].0;
match_on_closure_argument!( match_on_closure_argument!(ListMap2, [xs, ys])
env,
procs,
layout_cache,
closure_data_symbol,
closure_data_var,
op,
[arg_symbols[0], arg_symbols[1]],
layout,
assigned,
hole
)
} }
ListMap3 => { ListMap3 => {
debug_assert_eq!(arg_symbols.len(), 4); debug_assert_eq!(arg_symbols.len(), 4);
let closure_index = 3; let xs = arg_symbols[0];
let closure_data_symbol = arg_symbols[closure_index]; let ys = arg_symbols[1];
let closure_data_var = args[closure_index].0; let zs = arg_symbols[2];
match_on_closure_argument!( match_on_closure_argument!(ListMap3, [xs, ys, zs])
env,
procs,
layout_cache,
closure_data_symbol,
closure_data_var,
op,
[arg_symbols[0], arg_symbols[1], arg_symbols[2]],
layout,
assigned,
hole
)
} }
_ => { _ => {
let call = self::Call { let call = self::Call {
@ -4754,10 +4740,9 @@ pub fn from_can<'a>(
let is_self_recursive = let is_self_recursive =
!matches!(recursive, roc_can::expr::Recursive::NotRecursive); !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
procs.insert_named( let partial_proc = PartialProc::from_named_function(
env, env,
layout_cache, layout_cache,
*symbol,
function_type, function_type,
arguments, arguments,
loc_body, loc_body,
@ -4766,6 +4751,8 @@ pub fn from_can<'a>(
return_type, return_type,
); );
procs.partial_procs.insert(*symbol, partial_proc);
continue; continue;
} }
_ => unreachable!("recursive value is not a function"), _ => unreachable!("recursive value is not a function"),
@ -4842,10 +4829,9 @@ pub fn from_can<'a>(
} }
}; };
procs.insert_named( let partial_proc = PartialProc::from_named_function(
env, env,
layout_cache, layout_cache,
*symbol,
function_type, function_type,
arguments, arguments,
loc_body, loc_body,
@ -4854,6 +4840,8 @@ pub fn from_can<'a>(
return_type, return_type,
); );
procs.partial_procs.insert(*symbol, partial_proc);
return from_can(env, variable, cont.value, procs, layout_cache); return from_can(env, variable, cont.value, procs, layout_cache);
} }
_ => unreachable!(), _ => unreachable!(),
@ -6093,7 +6081,7 @@ fn reuse_function_symbol<'a>(
.raw_from_var(env.arena, arg_var, env.subs) .raw_from_var(env.arena, arg_var, env.subs)
.expect("creating layout does not fail"); .expect("creating layout does not fail");
if procs.imported_module_thunks.contains(&original) { if procs.is_imported_module_thunk(original) {
let layout = match raw { let layout = match raw {
RawFunctionLayout::ZeroArgumentThunk(layout) => layout, RawFunctionLayout::ZeroArgumentThunk(layout) => layout,
RawFunctionLayout::Function(_, lambda_set, _) => { RawFunctionLayout::Function(_, lambda_set, _) => {
@ -6189,7 +6177,7 @@ fn reuse_function_symbol<'a>(
closure_data, closure_data,
env.arena.alloc(result), env.arena.alloc(result),
) )
} else if procs.module_thunks.contains(&original) { } else if procs.is_module_thunk(original) {
// this is a 0-argument thunk // this is a 0-argument thunk
// TODO suspicious // TODO suspicious
@ -6385,7 +6373,7 @@ fn call_by_name<'a>(
evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args) evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args)
} }
Ok(RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout)) => { Ok(RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout)) => {
if procs.module_thunks.contains(&proc_name) { if procs.is_module_thunk(proc_name) {
if loc_args.is_empty() { if loc_args.is_empty() {
call_by_name_module_thunk( call_by_name_module_thunk(
env, env,
@ -6462,7 +6450,7 @@ fn call_by_name<'a>(
} }
} }
Ok(RawFunctionLayout::ZeroArgumentThunk(ret_layout)) => { Ok(RawFunctionLayout::ZeroArgumentThunk(ret_layout)) => {
if procs.module_thunks.contains(&proc_name) { if procs.is_module_thunk(proc_name) {
// here we turn a call to a module thunk into forcing of that thunk // here we turn a call to a module thunk into forcing of that thunk
call_by_name_module_thunk( call_by_name_module_thunk(
env, env,
@ -6564,7 +6552,7 @@ fn call_by_name_help<'a>(
add_needed_external(procs, env, original_fn_var, proc_name); add_needed_external(procs, env, original_fn_var, proc_name);
debug_assert_ne!(proc_name.module_id(), ModuleId::ATTR); debug_assert_ne!(proc_name.module_id(), ModuleId::ATTR);
if procs.imported_module_thunks.contains(&proc_name) { if procs.is_imported_module_thunk(proc_name) {
force_thunk( force_thunk(
env, env,
proc_name, proc_name,
@ -6616,14 +6604,14 @@ fn call_by_name_help<'a>(
// the same specialization independently), we work through the // the same specialization independently), we work through the
// queue of pending specializations to complete each specialization // queue of pending specializations to complete each specialization
// exactly once. // exactly once.
if procs.is_module_thunk(proc_name) {
debug_assert!(top_level_layout.arguments.is_empty());
}
match &mut procs.pending_specializations { match &mut procs.pending_specializations {
Some(pending_specializations) => { Some(pending_specializations) => {
debug_assert!(!env.is_imported_symbol(proc_name)); debug_assert!(!env.is_imported_symbol(proc_name));
if procs.module_thunks.contains(&proc_name) {
debug_assert!(top_level_layout.arguments.is_empty());
}
// register the pending specialization, so this gets code genned later // register the pending specialization, so this gets code genned later
add_pending( add_pending(
pending_specializations, pending_specializations,
@ -6751,8 +6739,6 @@ fn call_by_name_module_thunk<'a>(
) -> Stmt<'a> { ) -> Stmt<'a> {
debug_assert!(!env.is_imported_symbol(proc_name)); debug_assert!(!env.is_imported_symbol(proc_name));
// debug_assert!(!procs.module_thunks.contains(&proc_name), "{:?}", proc_name);
let top_level_layout = ProcLayout::new(env.arena, &[], *ret_layout); let top_level_layout = ProcLayout::new(env.arena, &[], *ret_layout);
let inner_layout = *ret_layout; let inner_layout = *ret_layout;
@ -6777,14 +6763,14 @@ fn call_by_name_module_thunk<'a>(
// the same specialization independently), we work through the // the same specialization independently), we work through the
// queue of pending specializations to complete each specialization // queue of pending specializations to complete each specialization
// exactly once. // exactly once.
if procs.is_module_thunk(proc_name) {
debug_assert!(top_level_layout.arguments.is_empty());
}
match &mut procs.pending_specializations { match &mut procs.pending_specializations {
Some(pending_specializations) => { Some(pending_specializations) => {
debug_assert!(!env.is_imported_symbol(proc_name)); debug_assert!(!env.is_imported_symbol(proc_name));
if procs.module_thunks.contains(&proc_name) {
debug_assert!(top_level_layout.arguments.is_empty());
}
// register the pending specialization, so this gets code genned later // register the pending specialization, so this gets code genned later
add_pending( add_pending(
pending_specializations, pending_specializations,

View file

@ -8,6 +8,7 @@ pub mod expand_rc;
pub mod inc_dec; pub mod inc_dec;
pub mod ir; pub mod ir;
pub mod layout; pub mod layout;
pub mod low_level;
pub mod reset_reuse; pub mod reset_reuse;
pub mod tail_recursion; pub mod tail_recursion;

View file

@ -0,0 +1,329 @@
use roc_module::symbol::Symbol;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HigherOrder {
ListMap { xs: Symbol },
ListMap2 { xs: Symbol, ys: Symbol },
ListMap3 { xs: Symbol, ys: Symbol, zs: Symbol },
ListMapWithIndex { xs: Symbol },
ListKeepIf { xs: Symbol },
ListWalk { xs: Symbol, state: Symbol },
ListWalkUntil { xs: Symbol, state: Symbol },
ListWalkBackwards { xs: Symbol, state: Symbol },
ListKeepOks { xs: Symbol },
ListKeepErrs { xs: Symbol },
ListSortWith { xs: Symbol },
DictWalk { xs: Symbol, state: Symbol },
}
impl HigherOrder {
pub fn function_arity(&self) -> usize {
match self {
HigherOrder::ListMap { .. } => 1,
HigherOrder::ListMap2 { .. } => 2,
HigherOrder::ListMap3 { .. } => 3,
HigherOrder::ListMapWithIndex { .. } => 2,
HigherOrder::ListKeepIf { .. } => 1,
HigherOrder::ListWalk { .. } => 2,
HigherOrder::ListWalkUntil { .. } => 2,
HigherOrder::ListWalkBackwards { .. } => 2,
HigherOrder::ListKeepOks { .. } => 1,
HigherOrder::ListKeepErrs { .. } => 1,
HigherOrder::ListSortWith { .. } => 2,
HigherOrder::DictWalk { .. } => 2,
}
}
}
#[allow(dead_code)]
enum FirstOrder {
StrConcat,
StrJoinWith,
StrIsEmpty,
StrStartsWith,
StrStartsWithCodePt,
StrEndsWith,
StrSplit,
StrCountGraphemes,
StrFromInt,
StrFromUtf8,
StrFromUtf8Range,
StrToUtf8,
StrRepeat,
StrFromFloat,
ListLen,
ListGetUnsafe,
ListSet,
ListDrop,
ListDropAt,
ListSingle,
ListRepeat,
ListReverse,
ListConcat,
ListContains,
ListAppend,
ListPrepend,
ListJoin,
ListRange,
ListSwap,
DictSize,
DictEmpty,
DictInsert,
DictRemove,
DictContains,
DictGetUnsafe,
DictKeys,
DictValues,
DictUnion,
DictIntersection,
DictDifference,
SetFromList,
NumAdd,
NumAddWrap,
NumAddChecked,
NumSub,
NumSubWrap,
NumSubChecked,
NumMul,
NumMulWrap,
NumMulChecked,
NumGt,
NumGte,
NumLt,
NumLte,
NumCompare,
NumDivUnchecked,
NumRemUnchecked,
NumIsMultipleOf,
NumAbs,
NumNeg,
NumSin,
NumCos,
NumSqrtUnchecked,
NumLogUnchecked,
NumRound,
NumToFloat,
NumPow,
NumCeiling,
NumPowInt,
NumFloor,
NumIsFinite,
NumAtan,
NumAcos,
NumAsin,
NumBitwiseAnd,
NumBitwiseXor,
NumBitwiseOr,
NumShiftLeftBy,
NumShiftRightBy,
NumBytesToU16,
NumBytesToU32,
NumShiftRightZfBy,
NumIntCast,
Eq,
NotEq,
And,
Or,
Not,
Hash,
ExpectTrue,
}
/*
enum FirstOrHigher {
First(FirstOrder),
Higher(HigherOrder),
}
fn from_low_level(low_level: &LowLevel, arguments: &[Symbol]) -> FirstOrHigher {
use FirstOrHigher::*;
use FirstOrder::*;
use HigherOrder::*;
match low_level {
LowLevel::StrConcat => First(StrConcat),
LowLevel::StrJoinWith => First(StrJoinWith),
LowLevel::StrIsEmpty => First(StrIsEmpty),
LowLevel::StrStartsWith => First(StrStartsWith),
LowLevel::StrStartsWithCodePt => First(StrStartsWithCodePt),
LowLevel::StrEndsWith => First(StrEndsWith),
LowLevel::StrSplit => First(StrSplit),
LowLevel::StrCountGraphemes => First(StrCountGraphemes),
LowLevel::StrFromInt => First(StrFromInt),
LowLevel::StrFromUtf8 => First(StrFromUtf8),
LowLevel::StrFromUtf8Range => First(StrFromUtf8Range),
LowLevel::StrToUtf8 => First(StrToUtf8),
LowLevel::StrRepeat => First(StrRepeat),
LowLevel::StrFromFloat => First(StrFromFloat),
LowLevel::ListLen => First(ListLen),
LowLevel::ListGetUnsafe => First(ListGetUnsafe),
LowLevel::ListSet => First(ListSet),
LowLevel::ListDrop => First(ListDrop),
LowLevel::ListDropAt => First(ListDropAt),
LowLevel::ListSingle => First(ListSingle),
LowLevel::ListRepeat => First(ListRepeat),
LowLevel::ListReverse => First(ListReverse),
LowLevel::ListConcat => First(ListConcat),
LowLevel::ListContains => First(ListContains),
LowLevel::ListAppend => First(ListAppend),
LowLevel::ListPrepend => First(ListPrepend),
LowLevel::ListJoin => First(ListJoin),
LowLevel::ListRange => First(ListRange),
LowLevel::ListSwap => First(ListSwap),
LowLevel::DictSize => First(DictSize),
LowLevel::DictEmpty => First(DictEmpty),
LowLevel::DictInsert => First(DictInsert),
LowLevel::DictRemove => First(DictRemove),
LowLevel::DictContains => First(DictContains),
LowLevel::DictGetUnsafe => First(DictGetUnsafe),
LowLevel::DictKeys => First(DictKeys),
LowLevel::DictValues => First(DictValues),
LowLevel::DictUnion => First(DictUnion),
LowLevel::DictIntersection => First(DictIntersection),
LowLevel::DictDifference => First(DictDifference),
LowLevel::SetFromList => First(SetFromList),
LowLevel::NumAdd => First(NumAdd),
LowLevel::NumAddWrap => First(NumAddWrap),
LowLevel::NumAddChecked => First(NumAddChecked),
LowLevel::NumSub => First(NumSub),
LowLevel::NumSubWrap => First(NumSubWrap),
LowLevel::NumSubChecked => First(NumSubChecked),
LowLevel::NumMul => First(NumMul),
LowLevel::NumMulWrap => First(NumMulWrap),
LowLevel::NumMulChecked => First(NumMulChecked),
LowLevel::NumGt => First(NumGt),
LowLevel::NumGte => First(NumGte),
LowLevel::NumLt => First(NumLt),
LowLevel::NumLte => First(NumLte),
LowLevel::NumCompare => First(NumCompare),
LowLevel::NumDivUnchecked => First(NumDivUnchecked),
LowLevel::NumRemUnchecked => First(NumRemUnchecked),
LowLevel::NumIsMultipleOf => First(NumIsMultipleOf),
LowLevel::NumAbs => First(NumAbs),
LowLevel::NumNeg => First(NumNeg),
LowLevel::NumSin => First(NumSin),
LowLevel::NumCos => First(NumCos),
LowLevel::NumSqrtUnchecked => First(NumSqrtUnchecked),
LowLevel::NumLogUnchecked => First(NumLogUnchecked),
LowLevel::NumRound => First(NumRound),
LowLevel::NumToFloat => First(NumToFloat),
LowLevel::NumPow => First(NumPow),
LowLevel::NumCeiling => First(NumCeiling),
LowLevel::NumPowInt => First(NumPowInt),
LowLevel::NumFloor => First(NumFloor),
LowLevel::NumIsFinite => First(NumIsFinite),
LowLevel::NumAtan => First(NumAtan),
LowLevel::NumAcos => First(NumAcos),
LowLevel::NumAsin => First(NumAsin),
LowLevel::NumBitwiseAnd => First(NumBitwiseAnd),
LowLevel::NumBitwiseXor => First(NumBitwiseXor),
LowLevel::NumBitwiseOr => First(NumBitwiseOr),
LowLevel::NumShiftLeftBy => First(NumShiftLeftBy),
LowLevel::NumShiftRightBy => First(NumShiftRightBy),
LowLevel::NumBytesToU16 => First(NumBytesToU16),
LowLevel::NumBytesToU32 => First(NumBytesToU32),
LowLevel::NumShiftRightZfBy => First(NumShiftRightZfBy),
LowLevel::NumIntCast => First(NumIntCast),
LowLevel::Eq => First(Eq),
LowLevel::NotEq => First(NotEq),
LowLevel::And => First(And),
LowLevel::Or => First(Or),
LowLevel::Not => First(Not),
LowLevel::Hash => First(Hash),
LowLevel::ExpectTrue => First(ExpectTrue),
LowLevel::ListMap => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListMap {
xs: arguments[0],
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListMap2 => {
debug_assert_eq!(arguments.len(), 4);
Higher(ListMap2 {
xs: arguments[0],
ys: arguments[1],
function_name: arguments[2],
function_env: arguments[3],
})
}
LowLevel::ListMap3 => {
debug_assert_eq!(arguments.len(), 5);
Higher(ListMap3 {
xs: arguments[0],
ys: arguments[1],
zs: arguments[2],
function_name: arguments[3],
function_env: arguments[4],
})
}
LowLevel::ListMapWithIndex => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListMapWithIndex {
xs: arguments[0],
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListKeepIf => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListKeepIf {
xs: arguments[0],
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListWalk => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListWalk {
xs: arguments[0],
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListWalkUntil => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListWalkUntil {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListWalkBackwards => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListWalkBackwards {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListKeepOks => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListKeepOks {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListKeepErrs => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListKeepErrs {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListSortWith => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListSortWith {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::DictWalk => {
debug_assert_eq!(arguments.len(), 3);
Higher(DictWalk {
function_name: arguments[1],
function_env: arguments[2],
})
}
}
}
*/

View file

@ -3305,6 +3305,18 @@ mod solve_expr {
); );
} }
#[test]
fn div_ceil() {
infer_eq_without_problem(
indoc!(
r#"
Num.divCeil
"#
),
"Int a, Int a -> Result (Int a) [ DivByZero ]*",
);
}
#[test] #[test]
fn pow_int() { fn pow_int() {
infer_eq_without_problem( infer_eq_without_problem(

View file

@ -43,16 +43,22 @@ pub enum EdError {
}, },
#[snafu(display("ClipboardReadFailed: could not get clipboard contents: {}", err_msg))] #[snafu(display("ClipboardReadFailed: could not get clipboard contents: {}", err_msg))]
ClipboardReadFailed { err_msg: String }, ClipboardReadFailed {
err_msg: String,
},
#[snafu(display("ClipboardWriteFailed: could not set clipboard contents: {}", err_msg))] #[snafu(display("ClipboardWriteFailed: could not set clipboard contents: {}", err_msg))]
ClipboardWriteFailed { err_msg: String }, ClipboardWriteFailed {
err_msg: String,
},
#[snafu(display( #[snafu(display(
"ClipboardInitFailed: could not initialize ClipboardContext: {}.", "ClipboardInitFailed: could not initialize ClipboardContext: {}.",
err_msg err_msg
))] ))]
ClipboardInitFailed { err_msg: String }, ClipboardInitFailed {
err_msg: String,
},
#[snafu(display( #[snafu(display(
"ExpectedTextNode: the function {} expected a Text node, got {} instead.", "ExpectedTextNode: the function {} expected a Text node, got {} instead.",
@ -68,7 +74,9 @@ pub enum EdError {
#[snafu(display( #[snafu(display(
"EmptyCodeString: I need to have a code string (code_str) that contains either an app, interface or Package-Config header. The code string was empty.", "EmptyCodeString: I need to have a code string (code_str) that contains either an app, interface or Package-Config header. The code string was empty.",
))] ))]
EmptyCodeString { backtrace: Backtrace }, EmptyCodeString {
backtrace: Backtrace,
},
#[snafu(display("FailedToUpdateIdentIdName: {}", err_str))] #[snafu(display("FailedToUpdateIdentIdName: {}", err_str))]
FailedToUpdateIdentIdName { FailedToUpdateIdentIdName {
@ -77,7 +85,9 @@ pub enum EdError {
}, },
#[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))] #[snafu(display("GetContentOnNestedNode: tried to get string content from Nested MarkupNode. Can only get content from Text or Blank nodes."))]
GetContentOnNestedNode { backtrace: Backtrace }, GetContentOnNestedNode {
backtrace: Backtrace,
},
#[snafu(display( #[snafu(display(
"IndexOfFailed: Element {} was not found in collection {}.", "IndexOfFailed: Element {} was not found in collection {}.",
@ -108,7 +118,9 @@ pub enum EdError {
#[snafu(display( #[snafu(display(
"MissingSelection: ed_model.selected_expr2_id was Some(ExprId) but ed_model.caret_w_sel_vec did not contain any Some(Selection)." "MissingSelection: ed_model.selected_expr2_id was Some(ExprId) but ed_model.caret_w_sel_vec did not contain any Some(Selection)."
))] ))]
MissingSelection { backtrace: Backtrace }, MissingSelection {
backtrace: Backtrace,
},
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))] #[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
NestedNodeMissingChild { NestedNodeMissingChild {
@ -139,7 +151,9 @@ pub enum EdError {
}, },
#[snafu(display("NodeWithoutAttributes: expected to have a node with attributes. This is a Nested MarkupNode, only Text and Blank nodes have attributes."))] #[snafu(display("NodeWithoutAttributes: expected to have a node with attributes. This is a Nested MarkupNode, only Text and Blank nodes have attributes."))]
NodeWithoutAttributes { backtrace: Backtrace }, NodeWithoutAttributes {
backtrace: Backtrace,
},
#[snafu(display( #[snafu(display(
"NodeIdNotInGridNodeMap: MarkNodeId {} was not found in ed_model.grid_node_map.", "NodeIdNotInGridNodeMap: MarkNodeId {} was not found in ed_model.grid_node_map.",
@ -159,6 +173,41 @@ pub enum EdError {
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.",
index,
collection_name,
len
))]
OutOfBounds {
index: usize,
collection_name: String,
len: usize,
backtrace: Backtrace,
},
#[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmptyRecord."))]
RecordWithoutFields {
backtrace: Backtrace,
},
#[snafu(display(
"RocCheckFailed: `cargo run check`/`roc check` detected errors(see terminal)."
))]
RocCheckFailed,
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))]
SrcParseError {
syntax_err: String,
backtrace: Backtrace,
},
#[snafu(display("StringParseError: {}", msg))]
StringParseError {
msg: String,
backtrace: Backtrace,
},
#[snafu(display( #[snafu(display(
"UnexpectedASTNode: required a {} at this position, node was a {}.", "UnexpectedASTNode: required a {} at this position, node was a {}.",
required_node_type, required_node_type,
@ -170,6 +219,15 @@ pub enum EdError {
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display(
"UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.",
descriptive_vec_name
))]
UnexpectedEmptyPoolVec {
descriptive_vec_name: String,
backtrace: Backtrace,
},
#[snafu(display( #[snafu(display(
"UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.", "UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.",
required_pattern2, required_pattern2,
@ -181,40 +239,21 @@ pub enum EdError {
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display( #[snafu(display("ASTError: {}", msg))]
"UnexpectedEmptyPoolVec: expected PoolVec {} to have at least one element.", ASTErrorBacktrace {
descriptive_vec_name msg: String,
))]
UnexpectedEmptyPoolVec {
descriptive_vec_name: String,
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display("UIError: {}", msg))]
#[snafu(display( UIErrorBacktrace {
"OutOfBounds: index {} was out of bounds for {} with length {}.", msg: String,
index,
collection_name,
len
))]
OutOfBounds {
index: usize,
collection_name: String,
len: usize,
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display("MarkError: {}", msg))]
#[snafu(display("ParseError: Failed to parse AST: SyntaxError: {}.", syntax_err))] MarkErrorBacktrace {
SrcParseError { msg: String,
syntax_err: String,
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmptyRecord."))]
RecordWithoutFields { backtrace: Backtrace },
#[snafu(display("StringParseError: {}", msg))]
StringParseError { msg: String, backtrace: Backtrace },
WrapASTError { WrapASTError {
#[snafu(backtrace)] #[snafu(backtrace)]
source: ASTError, source: ASTError,
@ -231,6 +270,9 @@ pub enum EdError {
#[snafu(backtrace)] #[snafu(backtrace)]
source: ModuleError, source: ModuleError,
}, },
WrapIoError {
source: std::io::Error,
},
} }
pub type EdResult<T, E = EdError> = std::result::Result<T, E>; pub type EdResult<T, E = EdError> = std::result::Result<T, E>;
@ -315,3 +357,9 @@ impl From<ModuleError> for EdError {
Self::WrapModuleError { source: module_err } Self::WrapModuleError { source: module_err }
} }
} }
impl From<std::io::Error> for EdError {
fn from(io_err: std::io::Error) -> Self {
Self::WrapIoError { source: io_err }
}
}

View file

@ -5,7 +5,7 @@ use std::process::Stdio;
use crate::editor::code_lines::CodeLines; use crate::editor::code_lines::CodeLines;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::MissingSelection; use crate::editor::ed_error::{MissingSelection, RocCheckFailed};
use crate::editor::grid_node_map::GridNodeMap; use crate::editor::grid_node_map::GridNodeMap;
use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::app_update::InputOutcome;
use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_model::EdModel;
@ -547,6 +547,7 @@ impl<'a> EdModel<'a> {
} }
R => { R => {
if modifiers.cmd_or_ctrl() { if modifiers.cmd_or_ctrl() {
self.check_file()?;
self.run_file()? self.run_file()?
} }
} }
@ -631,8 +632,27 @@ impl<'a> EdModel<'a> {
Ok(()) Ok(())
} }
fn run_file(&mut self) -> UIResult<()> { fn check_file(&mut self) -> EdResult<()> {
println!("Executing file..."); println!("Checking file (cargo run check <file>)...");
let roc_file_str = path_to_string(self.file_path);
let cmd_out = Command::new("cargo")
.arg("run")
.arg("check")
.arg(roc_file_str)
.stdout(Stdio::inherit())
.output()?;
if !cmd_out.status.success() {
RocCheckFailed.fail()?
}
Ok(())
}
fn run_file(&mut self) -> EdResult<()> {
println!("Executing file (cargo run <file>)...");
let roc_file_str = path_to_string(self.file_path); let roc_file_str = path_to_string(self.file_path);
@ -641,8 +661,7 @@ impl<'a> EdModel<'a> {
.arg(roc_file_str) .arg(roc_file_str)
.stdout(Stdio::inherit()) .stdout(Stdio::inherit())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
.output() .output()?;
.expect("Failed to run file");
Ok(()) Ok(())
} }

View file

@ -29,7 +29,6 @@ extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void;
extern fn roc__mainForHost_1_Fx_size() i64; extern fn roc__mainForHost_1_Fx_size() i64;
extern fn roc__mainForHost_1_Fx_result_size() i64; extern fn roc__mainForHost_1_Fx_result_size() i64;
const Align = 2 * @alignOf(usize); const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void;
@ -137,15 +136,8 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output); roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output);
const elements = @ptrCast([*]u64, @alignCast(8, output)); // The closure returns result, nothing interesting to do with it
return;
var flag = elements[0];
if (flag == 0) {
return;
} else {
unreachable;
}
} }
pub export fn roc_fx_putInt(int: i64) i64 { pub export fn roc_fx_putInt(int: i64) i64 {

View file

@ -54,11 +54,11 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
std.process.exit(0); std.process.exit(0);
} }
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{ export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void {
return memcpy(dst, src, size); return memcpy(dst, src, size);
} }
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
return memset(dst, value, size); return memset(dst, value, size);
} }
@ -104,6 +104,18 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator; const allocator = std.heap.page_allocator;
const size = roc__mainForHost_1_Fx_result_size(); const size = roc__mainForHost_1_Fx_result_size();
if (size == 0) {
// the function call returns an empty record
// allocating 0 bytes causes issues because the allocator will return a NULL pointer
// So it's special-cased
const flags: u8 = 0;
var result: [1]u8 = .{0};
roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, &result);
return;
}
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output); var output = @ptrCast([*]u8, raw_output);

1
examples/false-interpreter/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
false

View file

@ -0,0 +1,99 @@
interface Context
exposes [ Context, Data, with, getChar, Option, pushStack, popStack, toStr, inWhileScope ]
imports [ base.File, base.Task.{ Task }, Variable.{ Variable } ]
Option a : [ Some a, None ]
# The underlying context of the current location within the file
Data : [ Lambda (List U8), Number I32, Var Variable ]
# While loops are special and have their own Scope specific state.
WhileState: { cond: List U8, body: List U8, state: [ InCond, InBody ] }
Scope : { data: Option File.Handle, index: Nat, buf: List U8, whileInfo: Option WhileState }
State : [ Executing, InComment, InLambda Nat (List U8), InString (List U8), InNumber I32, InSpecialChar, LoadChar]
Context : { scopes: List Scope, stack: List Data, vars: List Data, state: State }
pushStack: Context, Data -> Context
pushStack = \ctx, data ->
{ctx & stack: List.append ctx.stack data}
# I think an open tag union should just work here.
# Instead at a call sites, I need to match on the error and then return the same error.
# Otherwise it hits unreachable code in ir.rs
popStack: Context -> Result [T Context Data] [ EmptyStack ]*
popStack = \ctx ->
when List.last ctx.stack is
Ok val ->
poppedCtx = { ctx & stack: List.dropAt ctx.stack (List.len ctx.stack - 1) }
Ok (T poppedCtx val)
Err ListWasEmpty ->
Err EmptyStack
toStrData: Data -> Str
toStrData = \data ->
when data is
Lambda _ -> "[]"
Number n -> Str.fromInt (Num.intCast n)
Var v -> Variable.toStr v
toStrState: State -> Str
toStrState = \state ->
when state is
Executing -> "Executing"
InComment -> "InComment"
InString _ -> "InString"
InNumber _ -> "InNumber"
InLambda _ _ -> "InLambda"
InSpecialChar -> "InSpecialChar"
LoadChar -> "LoadChar"
toStr: Context -> Str
toStr = \{scopes, stack, state, vars} ->
depth = Str.fromInt (List.len scopes)
stateStr = toStrState state
stackStr = Str.joinWith (List.map stack toStrData) " "
varsStr = Str.joinWith (List.map vars toStrData) " "
"\n============\nDepth: \(depth)\nState: \(stateStr)\nStack: [\(stackStr)]\nVars: [\(varsStr)]\n============\n"
with : Str, (Context -> Task {} a) -> Task {} a
with = \path, callback ->
handle <- File.withOpen path
# I cant define scope here and put it in the list in callback. It breaks alias anaysis.
# Instead I have to inline this.
# root_scope = { data: Some handle, index: 0, buf: [], whileInfo: None }
callback { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: (List.repeat Variable.totalCount (Number 0)) }
# I am pretty sure there is a syntax to destructure and keep a reference to the whole, but Im not sure what it is.
getChar: Context -> Task [T U8 Context] [ EndOfData, NoScope ]*
getChar = \ctx ->
when List.last ctx.scopes is
Ok scope ->
(T val newScope) <- Task.await (getCharScope scope)
Task.succeed (T val {ctx & scopes: List.set ctx.scopes ((List.len ctx.scopes) - 1) newScope })
Err ListWasEmpty ->
Task.fail NoScope
getCharScope: Scope -> Task [T U8 Scope] [ EndOfData, NoScope ]*
getCharScope = \scope ->
when List.get scope.buf scope.index is
Ok val -> Task.succeed (T val { scope & index: scope.index + 1 })
Err OutOfBounds ->
when scope.data is
Some h ->
bytes <- Task.await (File.chunk h)
when List.first bytes is
Ok val ->
# This starts at 1 because the first charater is already being returned.
Task.succeed (T val {scope & buf: bytes, index: 1 })
Err ListWasEmpty ->
Task.fail EndOfData
None ->
Task.fail EndOfData
inWhileScope: Context -> Bool
inWhileScope = \ctx ->
when List.last ctx.scopes is
Ok scope ->
scope.whileInfo != None
Err ListWasEmpty ->
False

View file

@ -0,0 +1,441 @@
#!/usr/bin/env roc
app "false"
packages { base: "platform" }
imports [ base.Task.{ Task }, base.Stdout, base.Stdin, Context.{ Context }, Variable.{ Variable } ]
provides [ main ] to base
# An interpreter for the False programming language: https://strlen.com/false-language/
# This is just a silly example to test this variety of program.
# In general think of this as a program that parses a number of files and prints some output.
# It has some extra contraints:
# 1) The input files are considered too large to just read in at once. Instead it is read via buffer or line.
# 2) The output is also considered too large to generate in memory. It must be printed as we go via buffer or line.
# I think one of the biggest issues with this implementation is that it doesn't return the the platform frequently enough.
# What I mean by that is we build a chain of all Tasks period and return that to the host.
# In something like the elm architecture you return a single step with one Task.
# The huge difference here is when it comes to things like stack overflows.
# In an imperative language, a few of these peices would be in while loops and it would basically never overflow.
# This implementation is easy to overflow, either make the input long enough or make a false while loop run long enough.
# I assume all of the Task.awaits are the cause of this, but I am not 100% sure.
InterpreterErrors : [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, MaxInputNumber, NoLambdaOnStack, NoNumberOnStack, NoVariableOnStack, NoScope, OutOfBounds, UnexpectedEndOfData ]
main : Str -> Task {} []
main = \filename ->
interpretFile filename
|> Task.onFail (\StringErr e -> Stdout.line "Ran into problem:\n\(e)\n")
interpretFile : Str -> Task {} [ StringErr Str ]
interpretFile = \filename ->
ctx <- Context.with filename
result <- Task.attempt (interpretCtx ctx)
when result is
Ok _ ->
Task.succeed {}
Err BadUtf8 ->
Task.fail (StringErr "Failed to convert string from Utf8 bytes")
Err DivByZero ->
Task.fail (StringErr "Division by zero")
Err EmptyStack ->
Task.fail (StringErr "Tried to pop a value off of the stack when it was empty")
Err InvalidBooleanValue ->
Task.fail (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)")
Err (InvalidChar char) ->
Task.fail (StringErr "Ran into an invalid character with ascii code: \(char)")
Err MaxInputNumber ->
Task.fail (StringErr "Like the original false compiler, the max input number is 320,000")
Err NoLambdaOnStack ->
Task.fail (StringErr "Tried to run a lambda when no lambda was on the stack")
Err NoNumberOnStack ->
Task.fail (StringErr "Tried to run a number when no number was on the stack")
Err NoVariableOnStack ->
Task.fail (StringErr "Tried to load a variable when no variable was on the stack")
Err NoScope ->
Task.fail (StringErr "Tried to run code when not in any scope")
Err OutOfBounds ->
Task.fail (StringErr "Tried to load from an offset that was outside of the stack")
Err UnexpectedEndOfData ->
Task.fail (StringErr "Hit end of data while still parsing something")
isDigit : U8 -> Bool
isDigit = \char ->
char >= 0x30 # `0`
&& char <= 0x39 # `0`
isWhitespace : U8 -> Bool
isWhitespace = \char ->
char == 0xA # new line
|| char == 0xB # carriage return
|| char == 0x20 # space
|| char == 0x9 # tab
interpretCtx : Context -> Task Context InterpreterErrors
interpretCtx = \ctx ->
when ctx.state is
Executing if Context.inWhileScope ctx ->
# Deal with the current while loop potentially looping.
last = ((List.len ctx.scopes) - 1)
when List.get ctx.scopes last is
Ok scope ->
when scope.whileInfo is
Some ({state: InCond, body, cond}) ->
# Just ran condition. Check the top of stack to see if body should run.
when popNumber ctx is
Ok (T popCtx n) ->
if n == 0 then
newScope = {scope & whileInfo: None}
interpretCtx {popCtx & scopes: List.set ctx.scopes last newScope}
else
newScope = {scope & whileInfo: Some {state: InBody, body, cond}}
interpretCtx {popCtx & scopes: List.append (List.set ctx.scopes last newScope) {data: None, buf: body, index: 0, whileInfo: None}}
Err e -> Task.fail e
Some ({state: InBody, body, cond}) ->
# Just rand the body. Run the condition again.
newScope = {scope & whileInfo: Some {state: InCond, body, cond}}
interpretCtx {ctx & scopes: List.append (List.set ctx.scopes last newScope) {data: None, buf: cond, index: 0, whileInfo: None}}
None ->
Task.fail NoScope
Err OutOfBounds ->
Task.fail NoScope
Executing ->
# {} <- Task.await (Stdout.line (Context.toStr ctx))
result <- Task.attempt (Context.getChar ctx)
when result is
Ok (T val newCtx) ->
execCtx <- Task.await (stepExecCtx newCtx val)
interpretCtx execCtx
Err NoScope ->
Task.fail NoScope
Err EndOfData ->
# Computation complete for this scope.
# Drop a scope.
dropCtx = {ctx & scopes: List.dropAt ctx.scopes (List.len ctx.scopes - 1) }
# If no scopes left, all execution complete.
if List.isEmpty dropCtx.scopes then
Task.succeed dropCtx
else
interpretCtx dropCtx
InComment ->
result <- Task.attempt (Context.getChar ctx)
when result is
Ok (T val newCtx) ->
if val == 0x7D then # `}` end of comment
interpretCtx {newCtx & state: Executing}
else
interpretCtx {newCtx & state: InComment}
Err NoScope ->
Task.fail NoScope
Err EndOfData ->
Task.fail UnexpectedEndOfData
InNumber accum ->
result <- Task.attempt (Context.getChar ctx)
when result is
Ok (T val newCtx) ->
if isDigit val then # still in the number
# i32 multiplication is kinda broken because it implicitly seems to want to upcast to i64.
# so like should be (i32, i32) -> i32, but seems to be (i32, i32) -> i64
# so this is make i64 mul by 10 then convert back to i32.
nextAccum = (10 * Num.intCast accum) + (Num.intCast (val - 0x30))
interpretCtx {newCtx & state: InNumber (Num.intCast nextAccum) }
else
# outside of number now, this needs to be executed.
pushCtx = Context.pushStack newCtx (Number accum)
execCtx <- Task.await (stepExecCtx {pushCtx & state: Executing} val)
interpretCtx execCtx
Err NoScope ->
Task.fail NoScope
Err EndOfData ->
Task.fail UnexpectedEndOfData
InString bytes ->
result <- Task.attempt (Context.getChar ctx)
when result is
Ok (T val newCtx) ->
if val == 0x22 then # `"` end of string
when Str.fromUtf8 bytes is
Ok str ->
{} <- Task.await (Stdout.raw str)
interpretCtx {newCtx & state: Executing}
Err _ ->
Task.fail BadUtf8
else
interpretCtx {newCtx & state: InString (List.append bytes val)}
Err NoScope ->
Task.fail NoScope
Err EndOfData ->
Task.fail UnexpectedEndOfData
InLambda depth bytes ->
result <- Task.attempt (Context.getChar ctx)
when result is
Ok (T val newCtx) ->
if val == 0x5B then # start of a nested lambda `[`
interpretCtx {newCtx & state: InLambda (depth + 1) (List.append bytes val)}
else if val == 0x5D then # `]` end of current lambda
if depth == 0 then # end of all lambdas
interpretCtx (Context.pushStack {newCtx & state: Executing} (Lambda bytes))
else # end of nested lambda
interpretCtx {newCtx & state: InLambda (depth - 1) (List.append bytes val)}
else
interpretCtx {newCtx & state: InLambda depth (List.append bytes val)}
Err NoScope ->
Task.fail NoScope
Err EndOfData ->
Task.fail UnexpectedEndOfData
InSpecialChar ->
result <- Task.attempt (Context.getChar {ctx & state: Executing})
when result is
Ok (T 0xB8 newCtx) ->
result2 =
(T popCtx index) <- Result.after (popNumber newCtx)
# I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers.
size = (List.len popCtx.stack) - 1
offset = (Num.intCast size) - index
if offset >= 0 then
stackVal <- Result.after (List.get popCtx.stack (Num.intCast offset))
Ok (Context.pushStack popCtx stackVal)
else
Err OutOfBounds
when result2 is
Ok a -> interpretCtx a
Err e -> Task.fail e
Ok (T 0x9F newCtx) ->
# This is supposed to flush io buffers. We don't buffer, so it does nothing
interpretCtx newCtx
Ok (T x _) ->
data = Str.fromInt (Num.intCast x)
Task.fail (InvalidChar data)
Err NoScope ->
Task.fail NoScope
Err EndOfData ->
Task.fail UnexpectedEndOfData
LoadChar ->
result <- Task.attempt (Context.getChar {ctx & state: Executing})
when result is
Ok (T x newCtx) ->
interpretCtx (Context.pushStack newCtx (Number (Num.intCast x)))
Err NoScope ->
Task.fail NoScope
Err EndOfData ->
Task.fail UnexpectedEndOfData
# If it weren't for reading stdin or writing to stdout, this could return a result.
stepExecCtx : Context, U8 -> Task Context InterpreterErrors
stepExecCtx = \ctx, char ->
when char is
0x21 -> # `!` execute lambda
Task.fromResult (
(T popCtx bytes) <- Result.after (popLambda ctx)
Ok {popCtx & scopes: List.append popCtx.scopes {data: None, buf: bytes, index: 0, whileInfo: None}}
)
0x3F -> # `?` if
Task.fromResult (
(T popCtx1 bytes) <- Result.after (popLambda ctx)
(T popCtx2 n1) <- Result.after (popNumber popCtx1)
if n1 == 0 then
Ok popCtx2
else
Ok {popCtx2 & scopes: List.append popCtx2.scopes {data: None, buf: bytes, index: 0, whileInfo: None}}
)
0x23 -> # `#` while
Task.fromResult (
(T popCtx1 body) <- Result.after (popLambda ctx)
(T popCtx2 cond) <- Result.after (popLambda popCtx1)
last = ((List.len popCtx2.scopes) - 1)
when List.get popCtx2.scopes last is
Ok scope ->
# set the current scope to be in a while loop.
scopes = List.set popCtx2.scopes last {scope & whileInfo: Some {cond: cond, body: body, state: InCond} }
# push a scope to execute the condition.
Ok {popCtx2 & scopes: List.append scopes {data: None, buf: cond, index: 0, whileInfo: None}}
Err OutOfBounds ->
Err NoScope
)
0x24 -> # `$` dup
# Switching this to List.last and changing the error to ListWasEmpty leads to a compiler bug.
# Complains about the types eq not matching.
when List.get ctx.stack ((List.len ctx.stack) - 1) is
Ok dupItem ->
Task.succeed (Context.pushStack ctx dupItem)
Err OutOfBounds ->
Task.fail EmptyStack
0x25 -> # `%` drop
when Context.popStack ctx is
# Dropping with an empty stack, all results here are fine
Ok (T popCtx _) ->
Task.succeed popCtx
Err _ ->
Task.succeed ctx
0x5C -> # `\` swap
result2 =
(T popCtx1 n1) <- Result.after (Context.popStack ctx)
(T popCtx2 n2) <- Result.after (Context.popStack popCtx1)
Ok (Context.pushStack (Context.pushStack popCtx2 n1) n2)
when result2 is
Ok a -> Task.succeed a
# Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack
Err EmptyStack -> Task.fail EmptyStack
0x40 -> # `@` rot
result2 =
(T popCtx1 n1) <- Result.after (Context.popStack ctx)
(T popCtx2 n2) <- Result.after (Context.popStack popCtx1)
(T popCtx3 n3) <- Result.after (Context.popStack popCtx2)
Ok (Context.pushStack (Context.pushStack (Context.pushStack popCtx3 n2) n1) n3)
when result2 is
Ok a -> Task.succeed a
# Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack
Err EmptyStack -> Task.fail EmptyStack
0xC3 -> # `ø` pick or `ß` flush
# these are actually 2 bytes, 0xC3 0xB8 or 0xC3 0x9F
# requires special parsing
Task.succeed {ctx & state: InSpecialChar}
0x4F -> # `O` also treat this as pick for easier script writing
Task.fromResult (
(T popCtx index) <- Result.after (popNumber ctx)
# I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers.
size = (List.len popCtx.stack) - 1
offset = (Num.intCast size) - index
if offset >= 0 then
stackVal <- Result.after (List.get popCtx.stack (Num.intCast offset))
Ok (Context.pushStack popCtx stackVal)
else
Err OutOfBounds
)
0x42 -> # `B` also treat this as flush for easier script writing
# This is supposed to flush io buffers. We don't buffer, so it does nothing
Task.succeed ctx
0x27 -> # `'` load next char
Task.succeed {ctx & state: LoadChar}
0x2B -> # `+` add
Task.fromResult (binaryOp ctx Num.addWrap)
0x2D -> # `-` sub
Task.fromResult (binaryOp ctx Num.subWrap)
0x2A -> # `*` mul
Task.fromResult (binaryOp ctx Num.mulWrap)
0x2F -> # `/` div
# Due to possible division by zero error, this must be handled specially.
Task.fromResult (
(T popCtx1 numR) <- Result.after (popNumber ctx)
(T popCtx2 numL) <- Result.after (popNumber popCtx1)
res <- Result.after (Num.divFloor numL numR)
Ok (Context.pushStack popCtx2 (Number res))
)
0x26 -> # `&` bitwise and
Task.fromResult (binaryOp ctx Num.bitwiseAnd)
0x7C -> # `|` bitwise or
Task.fromResult (binaryOp ctx Num.bitwiseOr)
0x3D -> # `=` equals
Task.fromResult
(a, b <- binaryOp ctx
if a == b then
-1
else
0
)
0x3E -> # `>` greater than
Task.fromResult
(a, b <- binaryOp ctx
if a > b then
-1
else
0
)
0x5F -> # `_` negate
Task.fromResult (unaryOp ctx Num.neg)
0x7E -> # `~` bitwise not
Task.fromResult (unaryOp ctx (\x -> Num.bitwiseXor x -1)) # xor with -1 should be bitwise not
0x2C -> # `,` write char
when popNumber ctx is
Ok (T popCtx num) ->
when Str.fromUtf8 [Num.intCast num] is
Ok str ->
{} <- Task.await (Stdout.raw str)
Task.succeed popCtx
Err _ ->
Task.fail BadUtf8
Err e ->
Task.fail e
0x2E -> # `.` write int
when popNumber ctx is
Ok (T popCtx num) ->
{} <- Task.await (Stdout.raw (Str.fromInt (Num.intCast num)))
Task.succeed popCtx
Err e ->
Task.fail e
0x5E -> # `^` read char as int
in <- Task.await Stdin.char
if in == 255 then # max char sent on EOF. Change to -1
Task.succeed (Context.pushStack ctx (Number -1))
else
Task.succeed (Context.pushStack ctx (Number (Num.intCast in)))
0x3A -> # `:` store to variable
Task.fromResult (
(T popCtx1 var) <- Result.after (popVariable ctx)
# The Result.mapErr on the next line maps from EmptyStack in Context.roc to the full InterpreterErrors union here.
(T popCtx2 n1) <- Result.after (Result.mapErr (Context.popStack popCtx1) (\(EmptyStack) -> EmptyStack))
Ok {popCtx2 & vars: List.set popCtx2.vars (Variable.toIndex var) n1}
)
0x3B -> # `;` load from variable
Task.fromResult (
(T popCtx var) <- Result.after (popVariable ctx)
elem <- Result.after (List.get popCtx.vars (Variable.toIndex var))
Ok (Context.pushStack popCtx elem)
)
0x22 -> # `"` string start
Task.succeed {ctx & state: InString []}
0x5B -> # `"` string start
Task.succeed {ctx & state: InLambda 0 []}
0x7B -> # `{` comment start
Task.succeed {ctx & state: InComment}
x if isDigit x -> # number start
Task.succeed {ctx & state: InNumber (Num.intCast (x - 0x30))}
x if isWhitespace x -> Task.succeed ctx
x ->
when (Variable.fromUtf8 x) is # letters are variable names
Ok var ->
Task.succeed (Context.pushStack ctx (Var var))
Err _ ->
data = Str.fromInt (Num.intCast x)
Task.fail (InvalidChar data)
unaryOp: Context, (I32 -> I32) -> Result Context InterpreterErrors
unaryOp = \ctx, op ->
(T popCtx num) <- Result.after (popNumber ctx)
Ok (Context.pushStack popCtx (Number (op num)))
binaryOp: Context, (I32, I32 -> I32) -> Result Context InterpreterErrors
binaryOp = \ctx, op ->
(T popCtx1 numR) <- Result.after (popNumber ctx)
(T popCtx2 numL) <- Result.after (popNumber popCtx1)
Ok (Context.pushStack popCtx2 (Number (op numL numR)))
popNumber: Context -> Result [T Context I32] InterpreterErrors
popNumber = \ctx ->
when Context.popStack ctx is
Ok (T popCtx (Number num)) ->
Ok (T popCtx num)
Ok _ ->
Err (NoNumberOnStack)
Err EmptyStack ->
Err EmptyStack
popLambda: Context -> Result [T Context (List U8)] InterpreterErrors
popLambda = \ctx ->
when Context.popStack ctx is
Ok (T popCtx (Lambda bytes)) ->
Ok (T popCtx bytes)
Ok _ ->
Err NoLambdaOnStack
Err EmptyStack ->
Err EmptyStack
popVariable: Context -> Result [T Context Variable] InterpreterErrors
popVariable = \ctx ->
when Context.popStack ctx is
Ok (T popCtx (Var var)) ->
Ok (T popCtx var)
Ok _ ->
Err NoVariableOnStack
Err EmptyStack ->
Err EmptyStack

View file

@ -0,0 +1,6 @@
# False Interpreter
This is an interpreter for the [false programming language](https://strlen.com/false-language/).
It is currently functional but runs in a way that devours stack space.
There are many examples of applications in the examples sub folder.
Many of them will currently cause stack overflows if stack size is not increased with something like `ulimit -s unlimited`.

View file

@ -0,0 +1,34 @@
interface Variable
exposes [ Variable, fromUtf8, toIndex, totalCount, toStr ]
imports [ ]
# Variables in False can only be single letters. Thus, the valid variables are "a" to "z".
# This opaque type deals with ensure we always have valid variables.
Variable : [ @Variable U8 ]
totalCount: Nat
totalCount =
0x7A # "z"
- 0x61 # "a"
+ 1
toStr : Variable -> Str
toStr = \@Variable char ->
when Str.fromUtf8 [char] is
Ok str -> str
_ -> "_"
fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ]
fromUtf8 = \char ->
if char >= 0x61 # "a"
&& char <= 0x7A # "z"
then
Ok (@Variable char)
else
Err InvalidVariableUtf8
toIndex : Variable -> Nat
toIndex = \@Variable char ->
Num.intCast (char - 0x61) # "a"
# List.first (Str.toUtf8 "a")

View file

@ -0,0 +1,5 @@
{ False version of 99 Bottles by Marcus Comstedt (marcus@lysator.liu.se) }
[$0=["no more bottles"]?$1=["One bottle"]?$1>[$." bottles"]?%" of beer"]b:
100[$0>][$b;!" on the wall, "$b;!".
"1-"Take one down, pass it around, "$b;!" on the wall.
"]#%

View file

@ -0,0 +1,224 @@
{
This should do the exact same thing as the linux cksum utility.
It does a crc32 of the input data.
One core difference, this reads off of stdin while cksum reads from a file.
With the interpreter, it currently runs about 350x slower though and requires extended stack size.
}
{
Load 256 constants from https://github.com/wertarbyte/coreutils/blob/f70c7b785b93dd436788d34827b209453157a6f2/src/cksum.c#L117
To support the original false interpreter, numbers must be less than 32000.
To deal with loading, just split all the numbers in two chunks.
First chunk is lower 16 bits, second chunk is higher 16 bits shift to the right.
Its values are then shifted back and merged together.
}
16564 45559 65536*| 23811 46390 65536*| 31706 47221 65536*| 26221 48308 65536*|
13928 41715 65536*| 11231 42546 65536*| 3334 43889 65536*| 4273 44976 65536*|
44300 38911 65536*| 45243 37694 65536*| 38498 40573 65536*| 35797 39612 65536*|
56272 34043 65536*| 50791 32826 65536*| 57534 36217 65536*| 64777 35256 65536*|
39876 64998 65536*| 34419 63783 65536*| 41130 62564 65536*| 48413 61605 65536*|
60696 61154 65536*| 61615 59939 65536*| 54902 59232 65536*| 52161 58273 65536*|
30332 56302 65536*| 27595 57135 65536*| 19730 53868 65536*| 20645 54957 65536*|
160 51434 65536*| 7447 52267 65536*| 15310 49512 65536*| 9849 50601 65536*|
63060 10708 65536*| 60387 11541 65536*| 52538 8278 65536*| 53389 9367 65536*|
32904 15056 65536*| 40255 15889 65536*| 48102 13138 65536*| 42577 14227 65536*|
7148 4060 65536*| 1627 2845 65536*| 8322 1630 65536*| 15669 671 65536*|
27952 7384 65536*| 28807 6169 65536*| 22110 5466 65536*| 19433 4507 65536*|
11556 26053 65536*| 12435 24836 65536*| 5706 27719 65536*| 3069 26758 65536*|
23544 30401 65536*| 17999 29184 65536*| 24726 32579 65536*| 32033 31618 65536*|
49308 17357 65536*| 56619 18188 65536*| 64498 19023 65536*| 58949 20110 65536*|
46656 20681 65536*| 44023 21512 65536*| 36142 22859 65536*| 37017 23946 65536*|
12483 34161 65536*| 11636 33200 65536*| 2989 36083 65536*| 5658 34866 65536*|
17951 38517 65536*| 23464 37556 65536*| 32113 40951 65536*| 24774 39734 65536*|
56699 41849 65536*| 49356 42936 65536*| 58901 43771 65536*| 64418 44602 65536*|
43943 45181 65536*| 46608 46268 65536*| 37065 47615 65536*| 36222 48446 65536*|
60339 51552 65536*| 62980 52641 65536*| 53469 49378 65536*| 52586 50211 65536*|
40303 55908 65536*| 32984 56997 65536*| 42497 54246 65536*| 48054 55079 65536*|
1547 61288 65536*| 7100 60329 65536*| 15717 59114 65536*| 8402 57899 65536*|
28887 64620 65536*| 28000 63661 65536*| 19385 62958 65536*| 22030 61743 65536*|
34339 7506 65536*| 39828 6547 65536*| 48461 5328 65536*| 41210 4113 65536*|
61695 3670 65536*| 60744 2711 65536*| 52113 2004 65536*| 54822 789 65536*|
27547 15194 65536*| 30252 16283 65536*| 20725 13016 65536*| 19778 13849 65536*|
7495 10334 65536*| 240 11423 65536*| 9769 8668 65536*| 15262 9501 65536*|
23891 20803 65536*| 16612 21890 65536*| 26173 22721 65536*| 31626 23552 65536*|
11151 16967 65536*| 13880 18054 65536*| 4321 19397 65536*| 3414 20228 65536*|
45291 30539 65536*| 44380 29578 65536*| 35717 32457 65536*| 38450 31240 65536*|
50743 25679 65536*| 56192 24718 65536*| 64857 28109 65536*| 57582 26892 65536*|
41050 55547 65536*| 48621 56378 65536*| 39732 53625 65536*| 34435 54712 65536*|
54918 52223 65536*| 52017 53054 65536*| 60904 49789 65536*| 61535 50876 65536*|
19938 65267 65536*| 20565 64050 65536*| 30348 63345 65536*| 27451 62384 65536*|
15166 60919 65536*| 9865 59702 65536*| 80 58485 65536*| 7655 57524 65536*|
31530 38122 65536*| 26269 36907 65536*| 16452 40296 65536*| 24051 39337 65536*|
3574 34798 65536*| 4161 33583 65536*| 13976 36460 65536*| 11055 35501 65536*|
38546 45794 65536*| 35621 46627 65536*| 44540 47968 65536*| 45131 49057 65536*|
57422 41446 65536*| 65017 42279 65536*| 56096 43108 65536*| 50839 44197 65536*|
5818 16600 65536*| 2829 17433 65536*| 11732 18778 65536*| 12387 19867 65536*|
24678 21468 65536*| 32209 22301 65536*| 23304 23134 65536*| 18111 24223 65536*|
64258 26320 65536*| 59061 25105 65536*| 49260 28498 65536*| 56795 27539 65536*|
36318 30164 65536*| 36969 28949 65536*| 46768 31830 65536*| 43783 30871 65536*|
52682 3273 65536*| 53373 2056 65536*| 63140 1355 65536*| 60179 394 65536*|
47894 8141 65536*| 42657 6924 65536*| 32888 5711 65536*| 40399 4750 65536*|
8306 10945 65536*| 15813 11776 65536*| 6940 9027 65536*| 1707 10114 65536*|
22190 14789 65536*| 19225 15620 65536*| 28096 12359 65536*| 28791 13446 65536*|
53293 60541 65536*| 52634 59580 65536*| 60227 58879 65536*| 63220 57662 65536*|
42737 65401 65536*| 47942 64440 65536*| 40351 63227 65536*| 32808 62010 65536*|
15765 51829 65536*| 8226 52916 65536*| 1787 50167 65536*| 6988 50998 65536*|
19273 55665 65536*| 22270 56752 65536*| 28711 53491 65536*| 28048 54322 65536*|
2909 41068 65536*| 5866 42157 65536*| 12339 43502 65536*| 11652 44335 65536*|
32129 45928 65536*| 24630 47017 65536*| 18159 47850 65536*| 23384 48683 65536*|
59109 34404 65536*| 64338 33445 65536*| 56715 36838 65536*| 49212 35623 65536*|
36921 38240 65536*| 36238 37281 65536*| 43863 40162 65536*| 46816 38947 65536*|
26317 29790 65536*| 31610 28831 65536*| 23971 32220 65536*| 16404 31005 65536*|
4113 26458 65536*| 3494 25499 65536*| 11135 28376 65536*| 14024 27161 65536*|
35701 21078 65536*| 38594 22167 65536*| 45083 23508 65536*| 44460 24341 65536*|
64937 16722 65536*| 57374 17811 65536*| 50887 18640 65536*| 56176 19473 65536*|
48573 14415 65536*| 40970 15502 65536*| 34515 12749 65536*| 39780 13580 65536*|
52065 11083 65536*| 54998 12170 65536*| 61455 8905 65536*| 60856 9736 65536*|
20485 7751 65536*| 19890 6790 65536*| 27499 6085 65536*| 30428 4868 65536*|
9945 3395 65536*| 15214 2434 65536*| 7607 1217 65536*| 0
{load crc32 base 0}
0
{load the xor function into x for use later}
[
{duplicate both inputs}
{nand inputs}
&~
{bring original inputs to top of stack with rotation}
@@
{or inputs}
|
{and the nand and or result to get xor}
&
]x:
{load right shift function to r for later use}
[
{will right shift the second from top value by the top value}
{while top value > 0}
[$0>][
{minus one from the top value}
1-
{swap values}
\
{divide the bottom value by 2}
2/
{zero top bit to avoid sign extension}
65535 32767 65536*|&
{swap back}
\
]#
{drop the top value}
%
]r:
{i will be used to count the length. Set it to zero to start}
0i:
{load the first character}
^
{while data != - 1: # -1 is eof}
[$1_=~][
{increment i}
i;1+i:
{duplicate crc32 which is on the stack under the current character}
{Shift crc32 right by 24}
24r;!
{xor the data and crc32}
x;!
{and with 255 to ensure it is in range}
255&
{
The index goes into the constant array, but currently the crc32 is loaded infront of the constant array.
Add 1 to the index to skip this and then load the value from the stack.
}
1+ø
{swap the crc32 on top of the stack}
\
{left shift it by 8 (multiply by 0x100)}
256*
{xor with the loaded constant}
x;!
{load the next character}
^
]#
{drop the -1 left on top of the stack}
%
{to match ck sum, add length to crc32}
{load i}
i;
{Note, this will break if i is negative from overflow}
{while i != 0}
[$0=~][
{duplicate and get last byte of i by and with 0xFF}
$255&
{duplicate crc32 which is on the stack under i and the current byte}
{Shift crc32 right by 24}
24r;!
{xor the data and crc32}
x;!
{and with 255 to ensure it is in range}
255&
{
The index goes into the constant array, but currently the i and the crc32 is loaded infront of the constant array.
Add 1 to the index to skip this and then load the value from the stack.
}
2+ø
{rotate the crc32 on top of the stack}
@
{left shift it by 8 (multiply by 0x100)}
256*
{xor with the loaded constant}
x;!
{swap i back on top of the stack and right shift it by 8}
\8r;!
]#
{drop i}
%
{ binary negate the crc32 }
~
{print the crc32}
.
{print a space}
" "
{print the length}
i;.

View file

@ -0,0 +1,2 @@
{ This will stack overflow if the input is too large }
[^$1_=~][,]#

View file

@ -0,0 +1,7 @@
{ unix cksum, CRC32. -- Jonathan Neuschäfer <j.neuschaefer@gmx.net> }
[[$0>][\2*\1-]#%]l:[0\128[$0>][$2O&0>[$@\64/x;!@@$g;*@x;!@@]?2/]#%%]h:
[[$0>][\2/\1-]#%]r:[1O$8 28l;!1-&$@=~\24r;!\[128|]?x;!h;!\8l;!x;!]s:79764919g:
[q1_0[\1+\^$1_>]s;#%@%\$@[1O0>][1O255&s;!\8r;!\]#~n;!32,%.10,]m:[$2O&@@|~|~]x:
[$0\>\1O[$u;!]?\~[$.]?%]n:[h;y:[3+O]h:255[$0>][$y;!\1-]#m;!256[$0>][\%1-]#%]o:
[1000$$**0@[$$0\>\4O\>~|][2O-\1+\]#\.\[10/$0>][\$2O/$.2O*-\]#%%]u: {width: 78}
{ usage: run m for "main" or o for "optimized" (builds a lookup table) } o;!

View file

@ -0,0 +1,11 @@
{ This is a comment}
{ White space doesn't matter}
{ Strings in False just automatically print...So, "Hello, World!", I guess }
"Hello, World!
"
{ Note the new line created by the white space that matters in strings }

View file

@ -0,0 +1 @@
abc

View file

@ -0,0 +1,10 @@
{
Dijkstra's odd words Problem
You'll need to enter a sentence in the Input box, ending with a period. Odd words are reversed.
}
[[$' =][%^]#]b:
[$$'.=\' =|~]w:
[$'.=~[' ,]?]s:
[w;![^o;!\,]?]o:
^b;![$'.=~][w;[,^]#b;!s;!o;!b;!s;!]#,

View file

@ -0,0 +1,2 @@
{This prints all of the primes from 1 to 99}
99 9[1-$][\$@$@$@$@\/*=[1-$$[%\1-$@]?0=[\$.' ,\]?]?]#

View file

@ -0,0 +1,17 @@
{ queens }
{ solves the problem of placing n queens on a }
{ n*n chessboard without attacking eachother }
{ written in false }
{ by Marcel van Kervinck }
{ e-mail <marcelk@stack.urc.tue.nl> }
{ June 1994 }
[$0=$[1\]?~[0 2O[$0=~][$$_&$7O7O|_1-&[@1O7O|$+2
O7O|2/3O_1-7O&6O1-q;!+@@]?_1-&]#%]?\%\%\%\%]q:
"enter number of queens [0..9] "B^B'0-
0$@$1[1O0>][$+\1-\]#1-\%\q;!." solutions
"
{ that's all }

View file

@ -0,0 +1,3 @@
{This will roughly calculate the sqrt of the number pushed here}
2000000
[\$@$@\/+2/]r: [127r;!r;!r;!r;!r;!r;!r;!\%]s: s;!.

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,23 @@
[package]
name = "host"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
links = "app"
[lib]
name = "host"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib"]
[[bin]]
name = "host"
path = "src/main.rs"
[dependencies]
roc_std = { path = "../../../roc_std" }
libc = "0.2"
[workspace]

View file

@ -0,0 +1,27 @@
interface File
exposes [ line, Handle, withOpen, chunk ]
imports [ fx.Effect, Task.{ Task } ]
Handle: [ @Handle U64 ]
line : Handle -> Task.Task Str *
line = \@Handle handle -> Effect.after (Effect.getFileLine handle) Task.succeed
chunk : Handle -> Task.Task (List U8) *
chunk = \@Handle handle -> Effect.after (Effect.getFileBytes handle) Task.succeed
open : Str -> Task.Task Handle *
open = \path ->
Effect.openFile path
|> Effect.map (\id -> @Handle id)
|> Effect.after Task.succeed
close : Handle -> Task.Task {} *
close = \@Handle handle -> Effect.after (Effect.closeFile handle) Task.succeed
withOpen : Str, (Handle -> Task {} a) -> Task {} a
withOpen = \path, callback ->
handle <- Task.await (open path)
result <- Task.attempt (callback handle)
{} <- Task.await (close handle)
Task.fromResult result

View file

@ -0,0 +1,22 @@
platform examples/cli
requires {}{ main : Str -> Task {} [] } # TODO FIXME
exposes []
packages {}
imports [ Task.{ Task } ]
provides [ mainForHost ]
effects fx.Effect
{
openFile : Str -> Effect U64,
closeFile : U64 -> Effect {},
withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {},
getFileLine : U64 -> Effect Str,
getFileBytes : U64 -> Effect (List U8),
putLine : Str -> Effect {},
putRaw : Str -> Effect {},
# Is there a limit to the number of effect, uncomment the next line and it crashes
#getLine : Effect Str,
getChar : Effect U8
}
mainForHost : Str -> Task {} [] as Fx
mainForHost = \file -> main file

View file

@ -0,0 +1,9 @@
interface Stdin
exposes [ char ]
imports [ fx.Effect, Task ]
#line : Task.Task Str *
#line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice
char : Task.Task U8 *
char = Effect.after Effect.getChar Task.succeed # TODO FIXME Effect.getLine should suffice

View file

@ -0,0 +1,9 @@
interface Stdout
exposes [ line, raw ]
imports [ fx.Effect, Task.{ Task } ]
line : Str -> Task {} *
line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {})
raw : Str -> Task {} *
raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {})

View file

@ -0,0 +1,50 @@
interface Task
exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult ]
imports [ fx.Effect ]
Task ok err : Effect.Effect (Result ok err)
succeed : val -> Task val *
succeed = \val ->
Effect.always (Ok val)
fail : err -> Task * err
fail = \val ->
Effect.always (Err val)
fromResult : Result a e -> Task a e
fromResult = \result ->
when result is
Ok a -> succeed a
Err e -> fail e
attempt : Task a b, (Result a b -> Task c d) -> Task c d
attempt = \effect, transform ->
Effect.after effect \result ->
when result is
Ok ok -> transform (Ok ok)
Err err -> transform (Err err)
await : Task a err, (a -> Task b err) -> Task b err
await = \effect, transform ->
Effect.after effect \result ->
when result is
Ok a -> transform a
Err err -> Task.fail err
onFail : Task ok a, (a -> Task ok b) -> Task ok b
onFail = \effect, transform ->
Effect.after effect \result ->
when result is
Ok a -> Task.succeed a
Err err -> transform err
map : Task a err, (a -> b) -> Task b err
map = \effect, transform ->
Effect.after effect \result ->
when result is
Ok a -> Task.succeed (transform a)
Err err -> Task.fail err

View file

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-lib=dylib=app");
println!("cargo:rustc-link-search=.");
}

View file

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

View file

@ -0,0 +1,221 @@
#![allow(non_snake_case)]
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::MaybeUninit;
use libc;
use roc_std::{RocList, RocStr};
use std::env;
use std::ffi::CStr;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Write};
use std::os::raw::c_char;
extern "C" {
#[link_name = "roc__mainForHost_1_exposed"]
fn roc_main(args: RocStr, output: *mut u8) -> ();
#[link_name = "roc__mainForHost_size"]
fn roc_main_size() -> i64;
#[link_name = "roc__mainForHost_1_Fx_caller"]
fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[allow(dead_code)]
#[link_name = "roc__mainForHost_1_Fx_size"]
fn size_Fx() -> i64;
#[link_name = "roc__mainForHost_1_Fx_result_size"]
fn size_Fx_result() -> i64;
}
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
}
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}
#[no_mangle]
pub fn rust_main() -> i32 {
let arg = env::args().skip(1).next().unwrap();
let arg = RocStr::from_slice(arg.as_bytes());
let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
unsafe {
// TODO allocate on the stack if it's under a certain size
let buffer = std::alloc::alloc(layout);
roc_main(arg, buffer);
let result = call_the_closure(buffer);
std::alloc::dealloc(buffer, layout);
result
};
// Exit code
0
}
unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
let size = size_Fx_result() as usize;
let layout = Layout::array::<u8>(size).unwrap();
let buffer = std::alloc::alloc(layout) as *mut u8;
call_Fx(
// This flags pointer will never get dereferenced
MaybeUninit::uninit().as_ptr(),
closure_data_ptr as *const u8,
buffer as *mut u8,
);
std::alloc::dealloc(buffer, layout);
0
}
#[no_mangle]
pub fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead};
let stdin = io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from_slice(line1.as_bytes())
}
#[no_mangle]
pub fn roc_fx_getChar() -> u8 {
use std::io::{self, BufRead};
let mut buffer = [0];
if let Err(ioerr) = io::stdin().lock().read_exact(&mut buffer[..]) {
if ioerr.kind() == io::ErrorKind::UnexpectedEof {
u8::MAX
} else {
panic!("Got an unexpected error while reading char from stdin");
}
} else {
buffer[0]
}
}
#[no_mangle]
pub fn roc_fx_putLine(line: RocStr) -> () {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
std::io::stdout().lock().flush();
// don't mess with the refcount!
core::mem::forget(line);
()
}
#[no_mangle]
pub fn roc_fx_putRaw(line: RocStr) -> () {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
print!("{}", string);
std::io::stdout().lock().flush();
// don't mess with the refcount!
core::mem::forget(line);
()
}
#[no_mangle]
pub fn roc_fx_getFileLine(br_ptr: *mut BufReader<File>) -> RocStr {
let br = unsafe { &mut *br_ptr };
let mut line1 = String::default();
br.read_line(&mut line1)
.expect("Failed to read line from file");
RocStr::from_slice(line1.as_bytes())
}
#[no_mangle]
pub fn roc_fx_getFileBytes(br_ptr: *mut BufReader<File>) -> RocList<u8> {
let br = unsafe { &mut *br_ptr };
let mut buffer = [0; 0x10 /* This is intentially small to ensure correct implementation */];
let count = br
.read(&mut buffer[..])
.expect("Failed to read bytes from file");
RocList::from_slice(&buffer[..count])
}
#[no_mangle]
pub fn roc_fx_closeFile(br_ptr: *mut BufReader<File>) -> () {
unsafe {
Box::from_raw(br_ptr);
}
}
#[no_mangle]
pub fn roc_fx_openFile(name: RocStr) -> *mut BufReader<File> {
let f = File::open(name.as_str()).expect("Unable to open file");
let br = BufReader::new(f);
Box::into_raw(Box::new(br))
}
#[no_mangle]
pub fn roc_fx_withFileOpen(name: RocStr, buffer: *const u8) -> () {
// let f = File::open(name.as_str()).expect("Unable to open file");
// let mut br = BufReader::new(f);
// unsafe {
// let closure_data_ptr = buffer.offset(8);
// call_the_closure(closure_data_ptr);
// }
// // don't mess with the refcount!
// core::mem::forget(name);
()
}

View file

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main());
}

1
version.txt Normal file
View file

@ -0,0 +1 @@
<to be filled by CI on nightly release>