Merge remote-tracking branch 'origin/trunk' into str-fromUtf8

This commit is contained in:
Folkert 2021-02-21 15:25:36 +01:00
commit 57b78dde06
156 changed files with 19265 additions and 10809 deletions

View file

@ -3,85 +3,25 @@ on: [pull_request]
name: CI name: CI
env: env:
RUSTC_WRAPPER: /usr/local/bin/sccache
SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
SCCACHE_S3_USE_SSL: ${{ secrets.SCCACHE_S3_USE_SSL }}
SCCACHE_REGION: ${{ secrets.SCCACHE_REGION }}
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
jobs: jobs:
test: prep-dependency-container:
name: fmt, clippy, test, test --release name: fmt, clippy, test --release
runs-on: ubuntu-latest runs-on: self-hosted
timeout-minutes: 60 timeout-minutes: 60
env:
FORCE_COLOR: 1
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Log CPU model
run: sudo cat /proc/cpuinfo | grep name | uniq
- name: Install CI Libraries
run: sudo ./ci/install-ci-libraries.sh 10
- name: scope sccache to CPU model
run: |
export SCCACHE_S3_KEY_PREFIX=`sudo cat /proc/cpuinfo | grep name | uniq | awk -F: {'print $2'} | awk -F@ {'print$1'} | xargs | sed -e 's/\s/_/g'`
echo "SCCACHE_S3_KEY_PREFIX=$SCCACHE_S3_KEY_PREFIX" >> $GITHUB_ENV
- name: print SCCACHE_S3_KEY_PREFIX
run: echo $SCCACHE_S3_KEY_PREFIX
- name: sccache version
run: /usr/local/bin/sccache -V
- name: Run Zig tests
run: pushd compiler/builtins/bitcode; ./run-tests.sh; popd;
- name: Enable LLD
run: sudo ./ci/enable-lld.sh
- uses: actions-rs/toolchain@v1
name: Install Rust Toolchain
with: with:
profile: minimal clean: 'false'
toolchain: stable
override: true
- run: rustup component add rustfmt - name: Earthly version
run: earthly --version
- uses: actions-rs/cargo@v1 - name: Make empty cache folder if it does exist
name: rustfmt version run: mkdir -p sccache_dir;
with:
command: fmt
args: --version
- uses: actions-rs/cargo@v1 - name: install dependencies, build, run zig tests, rustfmt, clippy, cargo test --release
name: cargo fmt --check run: earthly +test-all
with:
command: fmt
args: --all -- --check
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
name: clippy version
with:
command: clippy
args: -V
- uses: actions-rs/cargo@v1
name: cargo clippy
with:
command: clippy
args: -- -D warnings
- uses: actions-rs/cargo@v1
name: cargo test --release
with:
command: test
args: --release
- name: sccache stats
run: /usr/local/bin/sccache --show-stats

3
.gitignore vendored
View file

@ -22,3 +22,6 @@ editor/benches/resources/25000000_lines.roc
editor/benches/resources/50000_lines.roc editor/benches/resources/50000_lines.roc
editor/benches/resources/500_lines.roc editor/benches/resources/500_lines.roc
# rust cache (sccache folder)
sccache_dir

28
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,28 @@
# Contributing
## Code of Conduct
We are committed to providing a friendly, safe and welcoming environment for all, make sure to take a look at the [Code of Conduct](CodeOfConduct.md)
## Building from Source
Check [Build from source](BUILDING_FROM_SOURCE.md) for instructions.
## Running Tests
To run all tests as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
```
mkdir -p sccache_dir
earthly +test-all
```
Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is available again after rebooting.
## Contribution Tips
- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com/join/xtk6mkdli5l5zeuphtdbm4q2/) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
- It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review!
## Can we do better?
Feel free to open an issue if you think this document can be improved or is unclear in any way.

636
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@ members = [
"compiler/can", "compiler/can",
"compiler/problem", "compiler/problem",
"compiler/types", "compiler/types",
"compiler/uniq",
"compiler/builtins", "compiler/builtins",
"compiler/constrain", "compiler/constrain",
"compiler/unify", "compiler/unify",
@ -32,8 +31,10 @@ members = [
# Optimizations based on https://deterministic.space/high-performance-rust.html # Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release] [profile.release]
lto = "fat" lto = "thin"
codegen-units = 1 codegen-units = 1
# debug = true # enable when profiling # debug = true # enable when profiling
[profile.bench]
lto = "thin"
codegen-units = 1

98
Earthfile Normal file
View file

@ -0,0 +1,98 @@
FROM rust:1.49-slim-buster
WORKDIR /earthbuild
prep-debian:
RUN apt -y update
install-other-libs:
FROM +prep-debian
RUN apt -y install wget git
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard
RUN apt -y install libc++-dev libc++abi-dev libunwind-dev pkg-config libx11-dev zlib1g-dev
install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs
# zig
RUN wget -c https://ziglang.org/download/0.7.1/zig-linux-x86_64-0.7.1.tar.xz --no-check-certificate
RUN tar -xf zig-linux-x86_64-0.7.1.tar.xz
RUN ln -s /earthbuild/zig-linux-x86_64-0.7.1/zig /usr/bin/zig
# llvm
RUN apt -y install lsb-release software-properties-common gnupg
RUN wget https://apt.llvm.org/llvm.sh
RUN chmod +x llvm.sh
RUN ./llvm.sh 10
RUN ln -s /usr/bin/clang-10 /usr/bin/clang
# use lld as linker
RUN ln -s /usr/bin/lld-10 /usr/bin/ld.lld
RUN echo "[build]" > $CARGO_HOME/config.toml
RUN echo "rustflags = [\"-C\", \"link-arg=-fuse-ld=lld\", \"-C\", \"target-cpu=native\"]" >> $CARGO_HOME/config.tom
# valgrind
RUN apt -y install autotools-dev cmake automake libc6-dbg
RUN wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2
RUN tar -xf valgrind-3.16.1.tar.bz2
# need to cd every time, every command starts at WORKDIR
RUN cd valgrind-3.16.1; ./autogen.sh
RUN cd valgrind-3.16.1; ./configure --disable-dependency-tracking
RUN cd valgrind-3.16.1; make -j`nproc`
RUN cd valgrind-3.16.1; make install
# clippy
RUN rustup component add clippy
# rustfmt
RUN rustup component add rustfmt
# sccache
RUN apt install libssl-dev
RUN cargo install sccache
RUN sccache -V
deps-image:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
SAVE IMAGE roc-deps:latest
copy-dirs-and-cache:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
# cache
COPY --dir sccache_dir ./
# roc dirs
COPY --dir cli compiler docs editor roc_std vendor examples Cargo.toml Cargo.lock ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode; ./run-tests.sh;
build-rust:
FROM +copy-dirs-and-cache
ARG RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ARG SCCACHE_DIR=/earthbuild/sccache_dir
ARG CARGO_INCREMENTAL=0 # no need to recompile package when using new function
RUN cargo build; sccache --show-stats # for clippy
RUN cargo test --release --no-run; sccache --show-stats
check-clippy:
FROM +build-rust
RUN cargo clippy -V
RUN cargo clippy -- -D warnings
check-rustfmt:
FROM +copy-dirs-and-cache
RUN cargo fmt --version
RUN cargo fmt --all -- --check
save-cache:
FROM +build-rust
SAVE ARTIFACT sccache_dir AS LOCAL sccache_dir
test-rust:
FROM +build-rust
ARG RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ARG SCCACHE_DIR=/earthbuild/sccache_dir
ARG RUST_BACKTRACE=1
RUN cargo test --release
test-all:
BUILD +check-clippy
BUILD +check-rustfmt
BUILD +save-cache
BUILD +test-zig
BUILD +test-rust

Binary file not shown.

View file

@ -40,7 +40,6 @@ roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" } roc_types = { path = "../compiler/types" }
roc_builtins = { path = "../compiler/builtins" } roc_builtins = { path = "../compiler/builtins" }
roc_constrain = { path = "../compiler/constrain" } roc_constrain = { path = "../compiler/constrain" }
roc_uniq = { path = "../compiler/uniq" }
roc_unify = { path = "../compiler/unify" } roc_unify = { path = "../compiler/unify" }
roc_solve = { path = "../compiler/solve" } roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" } roc_mono = { path = "../compiler/mono" }

View file

@ -3,6 +3,7 @@ use roc_build::{
link::{link, rebuild_host, LinkType}, link::{link, rebuild_host, LinkType},
program, program,
}; };
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
@ -19,16 +20,16 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
)); ));
} }
pub fn build_file( pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple, target: &Triple,
src_dir: PathBuf, src_dir: PathBuf,
roc_file_path: PathBuf, roc_file_path: PathBuf,
opt_level: OptLevel, opt_level: OptLevel,
emit_debug_info: bool, emit_debug_info: bool,
link_type: LinkType, link_type: LinkType,
) -> Result<PathBuf, LoadingProblem> { ) -> Result<PathBuf, LoadingProblem<'a>> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let arena = Bump::new();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
// Step 1: compile the app and generate the .o file // Step 1: compile the app and generate the .o file
@ -36,16 +37,18 @@ pub fn build_file(
// Release builds use uniqueness optimizations // Release builds use uniqueness optimizations
let stdlib = match opt_level { let stdlib = match opt_level {
OptLevel::Normal => roc_builtins::std::standard_stdlib(), OptLevel::Normal => arena.alloc(roc_builtins::std::standard_stdlib()),
OptLevel::Optimize => roc_builtins::std::standard_stdlib(), OptLevel::Optimize => arena.alloc(roc_builtins::std::standard_stdlib()),
}; };
let loaded = roc_load::file::load_and_monomorphize( let loaded = roc_load::file::load_and_monomorphize(
&arena, &arena,
roc_file_path.clone(), roc_file_path.clone(),
&stdlib, stdlib,
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
ptr_bytes, ptr_bytes,
builtin_defs_map,
)?; )?;
let path_to_platform = loaded.platform_path.clone(); let path_to_platform = loaded.platform_path.clone();
@ -114,22 +117,24 @@ pub fn build_file(
report_timing(buf, "Generate LLVM IR", code_gen_timing.code_gen); report_timing(buf, "Generate LLVM IR", code_gen_timing.code_gen);
report_timing(buf, "Emit .o file", code_gen_timing.emit_o_file); report_timing(buf, "Emit .o file", code_gen_timing.emit_o_file);
println!(
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
buf
);
println!("\nSuccess! 🎉\n\n\t{}\n", app_o_file.display());
let compilation_end = compilation_start.elapsed().unwrap(); let compilation_end = compilation_start.elapsed().unwrap();
let size = std::fs::metadata(&app_o_file).unwrap().len(); let size = std::fs::metadata(&app_o_file).unwrap().len();
println!( if emit_debug_info {
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n", println!(
compilation_end.as_millis(), "\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
size, buf
); );
println!("\nSuccess! 🎉\n\n\t{}\n", app_o_file.display());
println!(
"Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
compilation_end.as_millis(),
size,
);
}
// Step 2: link the precompiled host and compiled app // Step 2: link the precompiled host and compiled app
let mut host_input_path = PathBuf::from(cwd); let mut host_input_path = PathBuf::from(cwd);
@ -141,10 +146,13 @@ pub fn build_file(
let rebuild_host_start = SystemTime::now(); let rebuild_host_start = SystemTime::now();
rebuild_host(host_input_path.as_path()); rebuild_host(host_input_path.as_path());
let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
println!(
"Finished rebuilding the host in {} ms\n", if emit_debug_info {
rebuild_host_end.as_millis() println!(
); "Finished rebuilding the host in {} ms\n",
rebuild_host_end.as_millis()
);
}
// TODO try to move as much of this linking as possible to the precompiled // TODO try to move as much of this linking as possible to the precompiled
// host, to minimize the amount of host-application linking required. // host, to minimize the amount of host-application linking required.
@ -165,7 +173,9 @@ pub fn build_file(
}); });
let link_end = link_start.elapsed().unwrap(); let link_end = link_start.elapsed().unwrap();
println!("Finished linking in {} ms\n", link_end.as_millis()); if emit_debug_info {
println!("Finished linking in {} ms\n", link_end.as_millis());
}
// Clean up the leftover .o file from the Roc, if possible. // Clean up the leftover .o file from the Roc, if possible.
// (If cleaning it up fails, that's fine. No need to take action.) // (If cleaning it up fails, that's fine. No need to take action.)

View file

@ -1,10 +1,12 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use bumpalo::Bump;
use clap::ArgMatches; use clap::ArgMatches;
use clap::{App, Arg}; use clap::{App, Arg};
use roc_build::link::LinkType; use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use std::process; use std::process;
@ -77,7 +79,9 @@ pub fn build_app<'a>() -> App<'a> {
} }
pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
let arena = Bump::new();
let filename = matches.value_of(FLAG_ROC_FILE).unwrap(); let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
let opt_level = if matches.is_present(FLAG_OPTIMIZE) { let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
OptLevel::Optimize OptLevel::Optimize
} else { } else {
@ -107,23 +111,33 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
} }
}); });
let binary_path = build::build_file( let res_binary_path = build::build_file(
&arena,
target, target,
src_dir, src_dir,
path, path,
opt_level, opt_level,
emit_debug_info, emit_debug_info,
LinkType::Executable, LinkType::Executable,
) );
.expect("TODO gracefully handle build_file failing");
if run_after_build { match res_binary_path {
// Run the compiled app Ok(binary_path) => {
Command::new(binary_path) if run_after_build {
.spawn() // Run the compiled app
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err)) Command::new(binary_path)
.wait() .spawn()
.expect("TODO gracefully handle block_on failing"); .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing");
}
}
Err(LoadingProblem::ParsingFailedReport(report)) => {
print!("{}", report);
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
}
} }
Ok(()) Ok(())

View file

@ -1,12 +1,12 @@
use const_format::concatcp; use const_format::concatcp;
use gen::{gen_and_eval, ReplOutput}; use gen::{gen_and_eval, ReplOutput};
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use roc_parse::parser::{Fail, FailReason}; use roc_parse::parser::SyntaxError;
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline::Editor; use rustyline::Editor;
use rustyline_derive::{Completer, Helper, Highlighter, Hinter}; use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
use std::io::{self}; use std::io;
use target_lexicon::Triple; use target_lexicon::Triple;
const BLUE: &str = "\u{001b}[36m"; const BLUE: &str = "\u{001b}[36m";
@ -148,10 +148,10 @@ pub fn main() -> io::Result<()> {
println!("{}", output); println!("{}", output);
pending_src.clear(); pending_src.clear();
} }
Err(Fail { // Err(Fail {
reason: FailReason::Eof(_), // reason: FailReason::Eof(_),
.. // ..
}) => {} // }) => {}
Err(fail) => { Err(fail) => {
report_parse_error(fail); report_parse_error(fail);
pending_src.clear(); pending_src.clear();
@ -191,11 +191,11 @@ pub fn main() -> io::Result<()> {
Ok(()) Ok(())
} }
fn report_parse_error(fail: Fail) { fn report_parse_error(fail: SyntaxError) {
println!("TODO Gracefully report parse error in repl: {:?}", fail); println!("TODO Gracefully report parse error in repl: {:?}", fail);
} }
fn eval_and_format(src: &str) -> Result<String, Fail> { fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output { gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
ReplOutput::NoProblems { expr, expr_type } => { ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type) format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)

View file

@ -3,11 +3,13 @@ use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use roc_build::link::module_to_dylib; use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator; use roc_build::program::FunctionIterator;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable; use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens}; use roc_fmt::annotation::{Newlines, Parens};
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel}; use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_parse::parser::Fail; use roc_load::file::LoadingProblem;
use roc_parse::parser::SyntaxError;
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
@ -18,7 +20,11 @@ pub enum ReplOutput {
NoProblems { expr: String, expr_type: String }, NoProblems { expr: String, expr_type: String },
} }
pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> { pub fn gen_and_eval<'a>(
src: &[u8],
target: Triple,
opt_level: OptLevel,
) -> Result<ReplOutput, SyntaxError<'a>> {
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
}; };
@ -46,9 +52,18 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<R
src_dir, src_dir,
exposed_types, exposed_types,
ptr_bytes, ptr_bytes,
builtin_defs_map,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = match loaded {
Ok(v) => v,
Err(LoadingProblem::ParsingFailedReport(report)) => {
return Ok(ReplOutput::Problems(vec![report]));
}
Err(e) => {
panic!("error while loading module: {:?}", e)
}
};
use roc_load::file::MonomorphizedModule; use roc_load::file::MonomorphizedModule;
let MonomorphizedModule { let MonomorphizedModule {
@ -112,6 +127,9 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<R
Ok(ReplOutput::Problems(lines)) Ok(ReplOutput::Problems(lines))
} else { } else {
let context = Context::create(); let context = Context::create();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "")); let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, ""));
let builder = context.create_builder(); let builder = context.create_builder();
@ -144,8 +162,6 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<R
} }
}; };
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(module); let module = arena.alloc(module);
let (module_pass, function_pass) = let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level); roc_gen::llvm::build::construct_optimization_passes(module, opt_level);

View file

@ -13,6 +13,7 @@ mod helpers;
mod cli_run { mod cli_run {
use crate::helpers::{ use crate::helpers::{
example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind, example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind,
ValgrindError, ValgrindErrorXWhat,
}; };
use serial_test::serial; use serial_test::serial;
use std::path::Path; use std::path::Path;
@ -60,7 +61,24 @@ mod cli_run {
}); });
if !memory_errors.is_empty() { if !memory_errors.is_empty() {
panic!("{:?}", memory_errors); for error in memory_errors {
let ValgrindError {
kind,
what: _,
xwhat,
} = error;
println!("Valgrind Error: {}\n", kind);
if let Some(ValgrindErrorXWhat {
text,
leakedbytes: _,
leakedblocks: _,
}) = xwhat
{
println!(" {}", text);
}
}
panic!("Valgrind reported memory errors");
} }
} else { } else {
let exit_code = match valgrind_out.status.code() { let exit_code = match valgrind_out.status.code() {
@ -96,7 +114,7 @@ mod cli_run {
"hello-world", "hello-world",
&[], &[],
"Hello, World!!!!!!!!!!!!!\n", "Hello, World!!!!!!!!!!!!!\n",
false, true,
); );
} }
@ -108,7 +126,7 @@ mod cli_run {
"hello-world", "hello-world",
&[], &[],
"Hello, World!!!!!!!!!!!!!\n", "Hello, World!!!!!!!!!!!!!\n",
false, true,
); );
} }
@ -120,7 +138,7 @@ mod cli_run {
"quicksort", "quicksort",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, true,
); );
} }
@ -132,28 +150,12 @@ mod cli_run {
"quicksort", "quicksort",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
);
}
#[test]
#[serial(quicksort)]
// TODO: Stop ignoring this test once we are correctly freeing the RocList even when in dev build.
#[ignore]
fn run_quicksort_valgrind() {
check_output(
&example_file("quicksort", "Quicksort.roc"),
"quicksort",
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, true,
); );
} }
#[test] #[test]
#[serial(quicksort)] #[serial(quicksort)]
// TODO: Stop ignoring this test once valgrind supports AVX512.
#[ignore]
fn run_quicksort_optimized_valgrind() { fn run_quicksort_optimized_valgrind() {
check_output( check_output(
&example_file("quicksort", "Quicksort.roc"), &example_file("quicksort", "Quicksort.roc"),
@ -164,34 +166,6 @@ mod cli_run {
); );
} }
#[test]
#[serial(multi_module)]
// TODO: Stop ignoring this test once we are correctly freeing the RocList even when in dev build.
#[ignore]
fn run_multi_module_valgrind() {
check_output(
&example_file("multi-module", "Quicksort.roc"),
"quicksort",
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
);
}
#[test]
#[serial(multi_module)]
// TODO: Stop ignoring this test once valgrind supports AVX512.
#[ignore]
fn run_multi_module_optimized_valgrind() {
check_output(
&example_file("multi-module", "Quicksort.roc"),
"quicksort",
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
);
}
#[test] #[test]
#[serial(nqueens)] #[serial(nqueens)]
fn run_nqueens_not_optimized() { fn run_nqueens_not_optimized() {
@ -201,7 +175,7 @@ mod cli_run {
"nqueens", "nqueens",
&[], &[],
"4\n", "4\n",
false, true,
); );
} }
@ -213,7 +187,7 @@ mod cli_run {
"cfold", "cfold",
&[], &[],
"11 & 11\n", "11 & 11\n",
false, true,
); );
} }
@ -225,7 +199,7 @@ mod cli_run {
"deriv", "deriv",
&[], &[],
"1 count: 6\n2 count: 22\n", "1 count: 6\n2 count: 22\n",
false, true,
); );
} }
@ -237,7 +211,7 @@ mod cli_run {
"rbtree-insert", "rbtree-insert",
&[], &[],
"Node Black 0 {} Empty Empty\n", "Node Black 0 {} Empty Empty\n",
false, true,
); );
} }
@ -249,10 +223,72 @@ mod cli_run {
"rbtree-del", "rbtree-del",
&[], &[],
"30\n", "30\n",
true,
);
}
#[test]
#[serial(astar)]
fn run_astar_optimized_1() {
check_output_with_stdin(
&example_file("benchmarks", "AStarTests.roc"),
"1",
"astar-tests",
&[],
"True\n",
false, false,
); );
} }
#[ignore]
#[test]
#[serial(closure1)]
fn closure1() {
check_output(
&example_file("benchmarks", "Closure1.roc"),
"closure1",
&[],
"",
true,
);
}
#[test]
#[serial(closure2)]
fn closure2() {
check_output(
&example_file("benchmarks", "Closure2.roc"),
"closure2",
&[],
"",
true,
);
}
#[test]
#[serial(closure3)]
fn closure3() {
check_output(
&example_file("benchmarks", "Closure3.roc"),
"closure3",
&[],
"",
true,
);
}
#[test]
#[serial(closure)]
fn closure() {
check_output(
&example_file("benchmarks", "Closure.roc"),
"closure",
&[],
"",
true,
);
}
// #[test] // #[test]
// #[serial(effect)] // #[serial(effect)]
// fn run_effect_unoptimized() { // fn run_effect_unoptimized() {
@ -272,7 +308,7 @@ mod cli_run {
"multi-dep-str", "multi-dep-str",
&[], &[],
"I am Dep2.str2\n", "I am Dep2.str2\n",
false, true,
); );
} }
@ -284,7 +320,7 @@ mod cli_run {
"multi-dep-str", "multi-dep-str",
&["--optimize"], &["--optimize"],
"I am Dep2.str2\n", "I am Dep2.str2\n",
false, true,
); );
} }
@ -296,7 +332,7 @@ mod cli_run {
"multi-dep-thunk", "multi-dep-thunk",
&[], &[],
"I am Dep2.value2\n", "I am Dep2.value2\n",
false, true,
); );
} }
@ -308,7 +344,7 @@ mod cli_run {
"multi-dep-thunk", "multi-dep-thunk",
&["--optimize"], &["--optimize"],
"I am Dep2.value2\n", "I am Dep2.value2\n",
false, true,
); );
} }
} }

View file

@ -192,20 +192,20 @@ struct ValgrindDummyStruct {}
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct ValgrindError { pub struct ValgrindError {
kind: String, pub kind: String,
#[serde(default)] #[serde(default)]
what: Option<String>, pub what: Option<String>,
#[serde(default)] #[serde(default)]
xwhat: Option<ValgrindErrorXWhat>, pub xwhat: Option<ValgrindErrorXWhat>,
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct ValgrindErrorXWhat { pub struct ValgrindErrorXWhat {
text: String, pub text: String,
#[serde(default)] #[serde(default)]
leakedbytes: Option<isize>, pub leakedbytes: Option<isize>,
#[serde(default)] #[serde(default)]
leakedblocks: Option<isize>, pub leakedblocks: Option<isize>,
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -15,7 +15,6 @@ roc_problem = { path = "../problem" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" } roc_constrain = { path = "../constrain" }
roc_uniq = { path = "../uniq" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }

View file

@ -282,6 +282,7 @@ fn link_linux(
.collect::<HashMap<String, String>>(), .collect::<HashMap<String, String>>(),
) )
.args(&[ .args(&[
"--gc-sections",
"--eh-frame-hdr", "--eh-frame-hdr",
"-arch", "-arch",
arch_str(target), arch_str(target),
@ -350,6 +351,7 @@ fn link_macos(
// Don't allow LD_ env vars to affect this // Don't allow LD_ env vars to affect this
.env_clear() .env_clear()
.args(&[ .args(&[
"--gc-sections",
link_type_arg, link_type_arg,
"-arch", "-arch",
target.architecture.to_string().as_str(), target.architecture.to_string().as_str(),

View file

@ -74,7 +74,6 @@ pub fn gen_from_mono_module(
} }
// Generate the binary // Generate the binary
let context = Context::create(); let context = Context::create();
let module = arena.alloc(module_from_builtins(&context, "app")); let module = arena.alloc(module_from_builtins(&context, "app"));
@ -82,21 +81,34 @@ pub fn gen_from_mono_module(
// module.strip_debug_info(); // module.strip_debug_info();
// mark our zig-defined builtins as internal // mark our zig-defined builtins as internal
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::module::Linkage; use inkwell::module::Linkage;
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = context.create_enum_attribute(kind_id, 1);
for function in FunctionIterator::from_module(module) { for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap(); let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") { if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal); function.set_linkage(Linkage::Internal);
} }
if name.starts_with("roc_builtins.dict") || name.starts_with("dict.RocDict") {
function.add_attribute(AttributeLoc::Function, attr);
}
if name.starts_with("roc_builtins.list") || name.starts_with("list.RocList") {
function.add_attribute(AttributeLoc::Function, attr);
}
} }
let builder = context.create_builder(); let builder = context.create_builder();
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module); let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level); let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let env = roc_gen::llvm::build::Env { let env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
builder: &builder, builder: &builder,
@ -145,7 +157,7 @@ pub fn gen_from_mono_module(
if fn_val.verify(true) { if fn_val.verify(true) {
fpm.run_on(&fn_val); fpm.run_on(&fn_val);
} else { } else {
// fn_val.print_to_stderr(); fn_val.print_to_stderr();
// env.module.print_to_stderr(); // env.module.print_to_stderr();
// NOTE: If this fails, uncomment the above println to debug. // NOTE: If this fails, uncomment the above println to debug.
panic!( panic!(

View file

@ -2,3 +2,4 @@ zig-cache
src/zig-cache src/zig-cache
builtins.ll builtins.ll
builtins.bc builtins.bc
builtins.o

View file

@ -4,6 +4,7 @@ const mem = std.mem;
const Builder = std.build.Builder; const Builder = std.build.Builder;
pub fn build(b: *Builder) void { pub fn build(b: *Builder) void {
// b.setPreferredReleaseMode(builtin.Mode.Debug);
b.setPreferredReleaseMode(builtin.Mode.ReleaseFast); b.setPreferredReleaseMode(builtin.Mode.ReleaseFast);
const mode = b.standardReleaseOptions(); const mode = b.standardReleaseOptions();
@ -19,16 +20,29 @@ pub fn build(b: *Builder) void {
const test_step = b.step("test", "Run tests"); const test_step = b.step("test", "Run tests");
test_step.dependOn(&main_tests.step); test_step.dependOn(&main_tests.step);
// Lib // LLVM IR
const obj_name = "builtins"; const obj_name = "builtins";
const llvm_obj = b.addObject(obj_name, main_path);
llvm_obj.setBuildMode(mode);
llvm_obj.linkSystemLibrary("c");
llvm_obj.strip = true;
llvm_obj.emit_llvm_ir = true;
llvm_obj.emit_bin = false;
llvm_obj.bundle_compiler_rt = true;
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&llvm_obj.step);
// Object File
// TODO: figure out how to get this to emit symbols that are only scoped to linkage (global but hidden).
// Also, zig has -ffunction-sections, but I am not sure how to add it here.
// With both of those changes, unused zig functions will be cleaned up by the linker saving around 100k.
const obj = b.addObject(obj_name, main_path); const obj = b.addObject(obj_name, main_path);
obj.setBuildMode(mode); obj.setBuildMode(mode);
obj.linkSystemLibrary("c"); obj.linkSystemLibrary("c");
obj.setOutputDir(".");
obj.strip = true; obj.strip = true;
obj.emit_llvm_ir = true; const obj_step = b.step("object", "Build object file for linking");
obj.emit_bin = false; obj_step.dependOn(&obj.step);
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&obj.step);
b.default_step = ir; b.default_step = ir;
removeInstallSteps(b); removeInstallSteps(b);

View file

@ -0,0 +1,795 @@
const std = @import("std");
const testing = std.testing;
const expectEqual = testing.expectEqual;
const mem = std.mem;
const Allocator = mem.Allocator;
const assert = std.debug.assert;
const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList;
const INITIAL_SEED = 0xc70f6907;
const InPlace = packed enum(u8) {
InPlace,
Clone,
};
const Slot = packed enum(u8) {
Empty,
Filled,
PreviouslyFilled,
};
const MaybeIndexTag = enum {
index, not_found
};
const MaybeIndex = union(MaybeIndexTag) {
index: usize, not_found: void
};
fn nextSeed(seed: u64) u64 {
// TODO is this a valid way to get a new seed? are there better ways?
return seed + 1;
}
fn totalCapacityAtLevel(input: usize) usize {
if (input == 0) {
return 0;
}
var n = input;
var slots: usize = 8;
while (n > 1) : (n -= 1) {
slots = slots * 2 + slots;
}
return slots;
}
fn capacityOfLevel(input: usize) usize {
if (input == 0) {
return 0;
}
var n = input;
var slots: usize = 8;
while (n > 1) : (n -= 1) {
slots = slots * 2;
}
return slots;
}
// aligmnent of elements. The number (16 or 8) indicates the maximum
// alignment of the key and value. The tag furthermore indicates
// which has the biggest aligmnent. If both are the same, we put
// the key first
const Alignment = packed enum(u8) {
Align16KeyFirst,
Align16ValueFirst,
Align8KeyFirst,
Align8ValueFirst,
fn toUsize(self: Alignment) usize {
switch (self) {
.Align16KeyFirst => return 16,
.Align16ValueFirst => return 16,
.Align8KeyFirst => return 8,
.Align8ValueFirst => return 8,
}
}
fn keyFirst(self: Alignment) bool {
switch (self) {
.Align16KeyFirst => return true,
.Align16ValueFirst => return false,
.Align8KeyFirst => return true,
.Align8ValueFirst => return false,
}
}
};
pub fn decref(
allocator: *Allocator,
alignment: Alignment,
bytes_or_null: ?[*]u8,
data_bytes: usize,
) void {
return utils.decref(allocator, alignment.toUsize(), bytes_or_null, data_bytes);
}
pub fn allocateWithRefcount(
allocator: *Allocator,
alignment: Alignment,
data_bytes: usize,
) [*]u8 {
return utils.allocateWithRefcount(allocator, alignment.toUsize(), data_bytes);
}
pub const RocDict = extern struct {
dict_bytes: ?[*]u8,
dict_entries_len: usize,
number_of_levels: usize,
pub fn empty() RocDict {
return RocDict{
.dict_entries_len = 0,
.number_of_levels = 0,
.dict_bytes = null,
};
}
pub fn allocate(
allocator: *Allocator,
number_of_levels: usize,
number_of_entries: usize,
alignment: Alignment,
key_size: usize,
value_size: usize,
) RocDict {
const number_of_slots = totalCapacityAtLevel(number_of_levels);
const slot_size = slotSize(key_size, value_size);
const data_bytes = number_of_slots * slot_size;
return RocDict{
.dict_bytes = allocateWithRefcount(allocator, alignment, data_bytes),
.number_of_levels = number_of_levels,
.dict_entries_len = number_of_entries,
};
}
pub fn reallocate(
self: RocDict,
allocator: *Allocator,
alignment: Alignment,
key_width: usize,
value_width: usize,
) RocDict {
const new_level = self.number_of_levels + 1;
const slot_size = slotSize(key_width, value_width);
const old_capacity = self.capacity();
const new_capacity = totalCapacityAtLevel(new_level);
const delta_capacity = new_capacity - old_capacity;
const data_bytes = new_capacity * slot_size;
const first_slot = allocateWithRefcount(allocator, alignment, data_bytes);
// transfer the memory
if (self.dict_bytes) |source_ptr| {
const dest_ptr = first_slot;
var source_offset: usize = 0;
var dest_offset: usize = 0;
if (alignment.keyFirst()) {
// copy keys
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width);
// copy values
source_offset = old_capacity * key_width;
dest_offset = new_capacity * key_width;
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width);
} else {
// copy values
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * value_width);
// copy keys
source_offset = old_capacity * value_width;
dest_offset = new_capacity * value_width;
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * key_width);
}
// copy slots
source_offset = old_capacity * (key_width + value_width);
dest_offset = new_capacity * (key_width + value_width);
@memcpy(dest_ptr + dest_offset, source_ptr + source_offset, old_capacity * @sizeOf(Slot));
}
var i: usize = 0;
const first_new_slot_value = first_slot + old_capacity * slot_size + delta_capacity * (key_width + value_width);
while (i < (new_capacity - old_capacity)) : (i += 1) {
(first_new_slot_value)[i] = @enumToInt(Slot.Empty);
}
const result = RocDict{
.dict_bytes = first_slot,
.number_of_levels = self.number_of_levels + 1,
.dict_entries_len = self.dict_entries_len,
};
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
decref(allocator, alignment, self.dict_bytes, self.capacity() * slotSize(key_width, value_width));
return result;
}
pub fn asU8ptr(self: RocDict) [*]u8 {
return @ptrCast([*]u8, self.dict_bytes);
}
pub fn len(self: RocDict) usize {
return self.dict_entries_len;
}
pub fn isEmpty(self: RocDict) bool {
return self.len() == 0;
}
pub fn isUnique(self: RocDict) bool {
// the empty dict is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
return true;
}
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.dict_bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
pub fn capacity(self: RocDict) usize {
return totalCapacityAtLevel(self.number_of_levels);
}
pub fn makeUnique(self: RocDict, allocator: *Allocator, alignment: Alignment, key_width: usize, value_width: usize) RocDict {
if (self.isEmpty()) {
return self;
}
if (self.isUnique()) {
return self;
}
// unfortunately, we have to clone
var new_dict = RocDict.allocate(allocator, self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.dict_bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_dict.dict_bytes);
const number_of_bytes = self.capacity() * (@sizeOf(Slot) + key_width + value_width);
@memcpy(new_bytes, old_bytes, number_of_bytes);
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
const data_bytes = self.capacity() * slotSize(key_width, value_width);
decref(allocator, alignment, self.dict_bytes, data_bytes);
return new_dict;
}
fn getSlot(self: *const RocDict, index: usize, key_width: usize, value_width: usize) Slot {
const offset = self.capacity() * (key_width + value_width) + index * @sizeOf(Slot);
const ptr = self.dict_bytes orelse unreachable;
return @intToEnum(Slot, ptr[offset]);
}
fn setSlot(self: *RocDict, index: usize, key_width: usize, value_width: usize, slot: Slot) void {
const offset = self.capacity() * (key_width + value_width) + index * @sizeOf(Slot);
const ptr = self.dict_bytes orelse unreachable;
ptr[offset] = @enumToInt(slot);
}
fn setKey(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void {
if (key_width == 0) {
return;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
} else {
break :blk (self.capacity() * value_width) + (index * key_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
const source = data orelse unreachable;
const dest = ptr + offset;
@memcpy(dest, source, key_width);
}
fn getKey(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (key_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (index * key_width);
} else {
break :blk (self.capacity() * value_width) + (index * key_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
return ptr + offset;
}
fn setValue(self: *RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize, data: Opaque) void {
if (value_width == 0) {
return;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
} else {
break :blk (index * value_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
const source = data orelse unreachable;
const dest = ptr + offset;
@memcpy(dest, source, value_width);
}
fn getValue(self: *const RocDict, index: usize, alignment: Alignment, key_width: usize, value_width: usize) Opaque {
if (value_width == 0) {
return null;
}
const offset = blk: {
if (alignment.keyFirst()) {
break :blk (self.capacity() * key_width) + (index * value_width);
} else {
break :blk (index * value_width);
}
};
const ptr = self.dict_bytes orelse unreachable;
return ptr + offset;
}
fn findIndex(self: *const RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn) MaybeIndex {
if (self.isEmpty()) {
return MaybeIndex.not_found;
}
var seed: u64 = INITIAL_SEED;
var current_level: usize = 1;
var current_level_size: usize = 8;
var next_level_size: usize = 2 * current_level_size;
while (true) {
if (current_level > self.number_of_levels) {
return MaybeIndex.not_found;
}
// hash the key, and modulo by the maximum size
// (so we get an in-bounds index)
const hash = hash_fn(seed, key);
const index = capacityOfLevel(current_level - 1) + (hash % current_level_size);
switch (self.getSlot(index, key_width, value_width)) {
Slot.Empty, Slot.PreviouslyFilled => {
return MaybeIndex.not_found;
},
Slot.Filled => {
// is this the same key, or a new key?
const current_key = self.getKey(index, alignment, key_width, value_width);
if (is_eq(key, current_key)) {
return MaybeIndex{ .index = index };
} else {
current_level += 1;
current_level_size *= 2;
next_level_size *= 2;
seed = nextSeed(seed);
continue;
}
},
}
}
}
};
// Dict.empty
pub fn dictEmpty() callconv(.C) RocDict {
return RocDict.empty();
}
pub fn slotSize(key_size: usize, value_size: usize) usize {
return @sizeOf(Slot) + key_size + value_size;
}
// Dict.len
pub fn dictLen(dict: RocDict) callconv(.C) usize {
return dict.dict_entries_len;
}
// commonly used type aliases
const Opaque = ?[*]u8;
const HashFn = fn (u64, ?[*]u8) callconv(.C) u64;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const Inc = fn (?[*]u8) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
// Dict.insert : Dict k v, k, v -> Dict k v
pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value: Opaque, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
var seed: u64 = INITIAL_SEED;
var result = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
var current_level: usize = 1;
var current_level_size: usize = 8;
var next_level_size: usize = 2 * current_level_size;
while (true) {
if (current_level > result.number_of_levels) {
result = result.reallocate(std.heap.c_allocator, alignment, key_width, value_width);
}
const hash = hash_fn(seed, key);
const index = capacityOfLevel(current_level - 1) + (hash % current_level_size);
assert(index < result.capacity());
switch (result.getSlot(index, key_width, value_width)) {
Slot.Empty, Slot.PreviouslyFilled => {
result.setSlot(index, key_width, value_width, Slot.Filled);
result.setKey(index, alignment, key_width, value_width, key);
result.setValue(index, alignment, key_width, value_width, value);
result.dict_entries_len += 1;
break;
},
Slot.Filled => {
// is this the same key, or a new key?
const current_key = result.getKey(index, alignment, key_width, value_width);
if (is_eq(key, current_key)) {
// we will override the old value, but first have to decrement its refcount
const current_value = result.getValue(index, alignment, key_width, value_width);
dec_value(current_value);
// we must consume the key argument!
dec_key(key);
result.setValue(index, alignment, key_width, value_width, value);
break;
} else {
seed = nextSeed(seed);
current_level += 1;
current_level_size *= 2;
next_level_size *= 2;
continue;
}
},
}
}
// write result into pointer
output.* = result;
}
// Dict.remove : Dict k v, k -> Dict k v
pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
switch (input.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
// the key was not found; we're done
output.* = input;
return;
},
MaybeIndex.index => |index| {
var dict = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
assert(index < dict.capacity());
dict.setSlot(index, key_width, value_width, Slot.PreviouslyFilled);
const old_key = dict.getKey(index, alignment, key_width, value_width);
const old_value = dict.getValue(index, alignment, key_width, value_width);
dec_key(old_key);
dec_value(old_value);
dict.dict_entries_len -= 1;
// if the dict is now completely empty, free its allocation
if (dict.dict_entries_len == 0) {
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes);
output.* = RocDict.empty();
return;
}
output.* = dict;
},
}
}
// Dict.contains : Dict k v, k -> Bool
pub fn dictContains(dict: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn) callconv(.C) bool {
switch (dict.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
return false;
},
MaybeIndex.index => |_| {
return true;
},
}
}
// Dict.get : Dict k v, k -> { flag: bool, value: Opaque }
pub fn dictGet(dict: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_value: Inc) callconv(.C) extern struct { value: Opaque, flag: bool } {
switch (dict.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
return .{ .flag = false, .value = null };
},
MaybeIndex.index => |index| {
var value = dict.getValue(index, alignment, key_width, value_width);
inc_value(value);
return .{ .flag = true, .value = value };
},
}
}
// Dict.elementsRc
// increment or decrement all dict elements (but not the dict's allocation itself)
pub fn elementsRc(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, modify_key: Inc, modify_value: Inc) callconv(.C) void {
const size = dict.capacity();
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
modify_key(dict.getKey(i, alignment, key_width, value_width));
modify_value(dict.getValue(i, alignment, key_width, value_width));
},
else => {},
}
}
}
pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_key: Inc, output: *RocList) callconv(.C) void {
const size = dict.capacity();
var length: usize = 0;
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
length += 1;
},
else => {},
}
}
if (length == 0) {
output.* = RocList.empty();
return;
}
const data_bytes = length * key_width;
var ptr = allocateWithRefcount(std.heap.c_allocator, alignment, data_bytes);
var offset = blk: {
if (alignment.keyFirst()) {
break :blk 0;
} else {
break :blk (dict.capacity() * value_width);
}
};
i = 0;
var copied: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict.getKey(i, alignment, key_width, value_width);
inc_key(key);
const key_cast = @ptrCast([*]const u8, key);
@memcpy(ptr + (copied * key_width), key_cast, key_width);
copied += 1;
},
else => {},
}
}
output.* = RocList{ .bytes = ptr, .length = length };
}
pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_width: usize, inc_value: Inc, output: *RocList) callconv(.C) void {
const size = dict.capacity();
var length: usize = 0;
var i: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
length += 1;
},
else => {},
}
}
if (length == 0) {
output.* = RocList.empty();
return;
}
const data_bytes = length * value_width;
var ptr = allocateWithRefcount(std.heap.c_allocator, alignment, data_bytes);
var offset = blk: {
if (alignment.keyFirst()) {
break :blk (dict.capacity() * key_width);
} else {
break :blk 0;
}
};
i = 0;
var copied: usize = 0;
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const value = dict.getValue(i, alignment, key_width, value_width);
inc_value(value);
const value_cast = @ptrCast([*]const u8, value);
@memcpy(ptr + (copied * value_width), value_cast, value_width);
copied += 1;
},
else => {},
}
}
output.* = RocList{ .bytes = ptr, .length = length };
}
fn doNothing(ptr: Opaque) callconv(.C) void {
return;
}
pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_key: Inc, inc_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
var i: usize = 0;
while (i < dict2.capacity()) : (i += 1) {
switch (dict2.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict2.getKey(i, alignment, key_width, value_width);
switch (output.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
const value = dict2.getValue(i, alignment, key_width, value_width);
inc_value(value);
// we need an extra RC token for the key
inc_key(key);
inc_value(value);
// we know the newly added key is not a duplicate, so the `dec`s are unreachable
const dec_key = doNothing;
const dec_value = doNothing;
dictInsert(output.*, alignment, key, key_width, value, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
MaybeIndex.index => |_| {
// the key is already in the output dict
continue;
},
}
},
else => {},
}
}
}
pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Inc, dec_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
while (i < size) : (i += 1) {
switch (output.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict1.getKey(i, alignment, key_width, value_width);
switch (dict2.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
dictRemove(output.*, alignment, key, key_width, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
MaybeIndex.index => |_| {
// keep this key/value
continue;
},
}
},
else => {},
}
}
}
pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
while (i < size) : (i += 1) {
switch (output.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict1.getKey(i, alignment, key_width, value_width);
switch (dict2.findIndex(alignment, key, key_width, value_width, hash_fn, is_eq)) {
MaybeIndex.not_found => {
// keep this key/value
continue;
},
MaybeIndex.index => |_| {
dictRemove(output.*, alignment, key, key_width, value_width, hash_fn, is_eq, dec_key, dec_value, output);
},
}
},
else => {},
}
}
}
pub fn setFromList(list: RocList, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, output: *RocDict) callconv(.C) void {
output.* = RocDict.empty();
var ptr = @ptrCast([*]u8, list.bytes);
const dec_value = doNothing;
const value = null;
const size = list.length;
var i: usize = 0;
while (i < size) : (i += 1) {
const key = ptr + i * key_width;
dictInsert(output.*, alignment, key, key_width, value, value_width, hash_fn, is_eq, dec_key, dec_value, output);
}
// NOTE: decref checks for the empty case
const data_bytes = size * key_width;
decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
}
const StepperCaller = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, accum: Opaque, alignment: Alignment, key_width: usize, value_width: usize, accum_width: usize, inc_key: Inc, inc_value: Inc, output: Opaque) callconv(.C) void {
// allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable);
var b1 = output orelse unreachable;
var b2 = alloc;
@memcpy(b2, accum orelse unreachable, accum_width);
var i: usize = 0;
const size = dict.capacity();
while (i < size) : (i += 1) {
switch (dict.getSlot(i, key_width, value_width)) {
Slot.Filled => {
const key = dict.getKey(i, alignment, key_width, value_width);
const value = dict.getValue(i, alignment, key_width, value_width);
stepper_caller(stepper, key, value, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
},
else => {},
}
}
@memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]);
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes);
}

View file

@ -0,0 +1,255 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("std");
const str = @import("str.zig");
const mem = std.mem;
pub fn wyhash(seed: u64, bytes: ?[*]const u8, length: usize) callconv(.C) u64 {
const stdout = std.io.getStdOut().writer();
if (bytes) |nonnull| {
return wyhash_hash(seed, nonnull[0..length]);
} else {
return 42;
}
}
pub fn wyhash_rocstr(seed: u64, input: str.RocStr) callconv(.C) u64 {
return wyhash_hash(seed, input.asSlice());
}
const primes = [_]u64{
0xa0761d6478bd642f,
0xe7037ed1a0b428db,
0x8ebc6af09c88c6e3,
0x589965cc75374cc3,
0x1d8e4e27c47d124f,
};
fn read_bytes(comptime bytes: u8, data: []const u8) u64 {
const T = std.meta.Int(.unsigned, 8 * bytes);
return mem.readIntLittle(T, data[0..bytes]);
}
fn read_8bytes_swapped(data: []const u8) u64 {
return (read_bytes(4, data) << 32 | read_bytes(4, data[4..]));
}
fn mum(a: u64, b: u64) u64 {
var r = std.math.mulWide(u64, a, b);
r = (r >> 64) ^ r;
return @truncate(u64, r);
}
fn mix0(a: u64, b: u64, seed: u64) u64 {
return mum(a ^ seed ^ primes[0], b ^ seed ^ primes[1]);
}
fn mix1(a: u64, b: u64, seed: u64) u64 {
return mum(a ^ seed ^ primes[2], b ^ seed ^ primes[3]);
}
// Wyhash version which does not store internal state for handling partial buffers.
// This is needed so that we can maximize the speed for the short key case, which will
// use the non-iterative api which the public Wyhash exposes.
const WyhashStateless = struct {
seed: u64,
msg_len: usize,
pub fn init(seed: u64) WyhashStateless {
return WyhashStateless{
.seed = seed,
.msg_len = 0,
};
}
fn round(self: *WyhashStateless, b: []const u8) void {
std.debug.assert(b.len == 32);
self.seed = mix0(
read_bytes(8, b[0..]),
read_bytes(8, b[8..]),
self.seed,
) ^ mix1(
read_bytes(8, b[16..]),
read_bytes(8, b[24..]),
self.seed,
);
}
pub fn update(self: *WyhashStateless, b: []const u8) void {
std.debug.assert(b.len % 32 == 0);
var off: usize = 0;
while (off < b.len) : (off += 32) {
@call(.{ .modifier = .always_inline }, self.round, .{b[off .. off + 32]});
}
self.msg_len += b.len;
}
pub fn final(self: *WyhashStateless, b: []const u8) u64 {
std.debug.assert(b.len < 32);
const seed = self.seed;
const rem_len = @intCast(u5, b.len);
const rem_key = b[0..rem_len];
self.seed = switch (rem_len) {
0 => seed,
1 => mix0(read_bytes(1, rem_key), primes[4], seed),
2 => mix0(read_bytes(2, rem_key), primes[4], seed),
3 => mix0((read_bytes(2, rem_key) << 8) | read_bytes(1, rem_key[2..]), primes[4], seed),
4 => mix0(read_bytes(4, rem_key), primes[4], seed),
5 => mix0((read_bytes(4, rem_key) << 8) | read_bytes(1, rem_key[4..]), primes[4], seed),
6 => mix0((read_bytes(4, rem_key) << 16) | read_bytes(2, rem_key[4..]), primes[4], seed),
7 => mix0((read_bytes(4, rem_key) << 24) | (read_bytes(2, rem_key[4..]) << 8) | read_bytes(1, rem_key[6..]), primes[4], seed),
8 => mix0(read_8bytes_swapped(rem_key), primes[4], seed),
9 => mix0(read_8bytes_swapped(rem_key), read_bytes(1, rem_key[8..]), seed),
10 => mix0(read_8bytes_swapped(rem_key), read_bytes(2, rem_key[8..]), seed),
11 => mix0(read_8bytes_swapped(rem_key), (read_bytes(2, rem_key[8..]) << 8) | read_bytes(1, rem_key[10..]), seed),
12 => mix0(read_8bytes_swapped(rem_key), read_bytes(4, rem_key[8..]), seed),
13 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 8) | read_bytes(1, rem_key[12..]), seed),
14 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 16) | read_bytes(2, rem_key[12..]), seed),
15 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 24) | (read_bytes(2, rem_key[12..]) << 8) | read_bytes(1, rem_key[14..]), seed),
16 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed),
17 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(1, rem_key[16..]), primes[4], seed),
18 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(2, rem_key[16..]), primes[4], seed),
19 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(2, rem_key[16..]) << 8) | read_bytes(1, rem_key[18..]), primes[4], seed),
20 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(4, rem_key[16..]), primes[4], seed),
21 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 8) | read_bytes(1, rem_key[20..]), primes[4], seed),
22 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 16) | read_bytes(2, rem_key[20..]), primes[4], seed),
23 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 24) | (read_bytes(2, rem_key[20..]) << 8) | read_bytes(1, rem_key[22..]), primes[4], seed),
24 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), primes[4], seed),
25 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(1, rem_key[24..]), seed),
26 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(2, rem_key[24..]), seed),
27 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(2, rem_key[24..]) << 8) | read_bytes(1, rem_key[26..]), seed),
28 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(4, rem_key[24..]), seed),
29 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 8) | read_bytes(1, rem_key[28..]), seed),
30 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 16) | read_bytes(2, rem_key[28..]), seed),
31 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 24) | (read_bytes(2, rem_key[28..]) << 8) | read_bytes(1, rem_key[30..]), seed),
};
self.msg_len += b.len;
return mum(self.seed ^ self.msg_len, primes[4]);
}
pub fn hash(seed: u64, input: []const u8) u64 {
const aligned_len = input.len - (input.len % 32);
var c = WyhashStateless.init(seed);
@call(.{ .modifier = .always_inline }, c.update, .{input[0..aligned_len]});
return @call(.{ .modifier = .always_inline }, c.final, .{input[aligned_len..]});
}
};
/// Fast non-cryptographic 64bit hash function.
/// See https://github.com/wangyi-fudan/wyhash
pub const Wyhash = struct {
state: WyhashStateless,
buf: [32]u8,
buf_len: usize,
pub fn init(seed: u64) Wyhash {
return Wyhash{
.state = WyhashStateless.init(seed),
.buf = undefined,
.buf_len = 0,
};
}
pub fn update(self: *Wyhash, b: []const u8) void {
var off: usize = 0;
if (self.buf_len != 0 and self.buf_len + b.len >= 32) {
off += 32 - self.buf_len;
mem.copy(u8, self.buf[self.buf_len..], b[0..off]);
self.state.update(self.buf[0..]);
self.buf_len = 0;
}
const remain_len = b.len - off;
const aligned_len = remain_len - (remain_len % 32);
self.state.update(b[off .. off + aligned_len]);
mem.copy(u8, self.buf[self.buf_len..], b[off + aligned_len ..]);
self.buf_len += @intCast(u8, b[off + aligned_len ..].len);
}
pub fn final(self: *Wyhash) u64 {
const seed = self.state.seed;
const rem_len = @intCast(u5, self.buf_len);
const rem_key = self.buf[0..self.buf_len];
return self.state.final(rem_key);
}
pub fn hash(seed: u64, input: []const u8) u64 {
return WyhashStateless.hash(seed, input);
}
};
fn wyhash_hash(seed: u64, input: []const u8) u64 {
return Wyhash.hash(seed, input);
}
const expectEqual = std.testing.expectEqual;
test "test vectors" {
const hash = Wyhash.hash;
expectEqual(hash(0, ""), 0x0);
expectEqual(hash(1, "a"), 0xbed235177f41d328);
expectEqual(hash(2, "abc"), 0xbe348debe59b27c3);
expectEqual(hash(3, "message digest"), 0x37320f657213a290);
expectEqual(hash(4, "abcdefghijklmnopqrstuvwxyz"), 0xd0b270e1d8a7019c);
expectEqual(hash(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x602a1894d3bbfe7f);
expectEqual(hash(6, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x829e9c148b75970e);
}
test "test vectors streaming" {
var wh = Wyhash.init(5);
for ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") |e| {
wh.update(mem.asBytes(&e));
}
expectEqual(wh.final(), 0x602a1894d3bbfe7f);
const pattern = "1234567890";
const count = 8;
const result = 0x829e9c148b75970e;
expectEqual(Wyhash.hash(6, pattern ** 8), result);
wh = Wyhash.init(6);
var i: u32 = 0;
while (i < count) : (i += 1) {
wh.update(pattern);
}
expectEqual(wh.final(), result);
}
test "iterative non-divisible update" {
var buf: [8192]u8 = undefined;
for (buf) |*e, i| {
e.* = @truncate(u8, i);
}
const seed = 0x128dad08f;
var end: usize = 32;
while (end < buf.len) : (end += 32) {
const non_iterative_hash = Wyhash.hash(seed, buf[0..end]);
var wy = Wyhash.init(seed);
var i: usize = 0;
while (i < end) : (i += 33) {
wy.update(buf[i..std.math.min(i + 33, end)]);
}
const iterative_hash = wy.final();
std.testing.expectEqual(iterative_hash, non_iterative_hash);
}
}

View file

@ -0,0 +1,326 @@
const std = @import("std");
const utils = @import("utils.zig");
const RocResult = utils.RocResult;
const mem = std.mem;
const Allocator = mem.Allocator;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const Opaque = ?[*]u8;
const Inc = fn (?[*]u8) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
pub const RocList = extern struct {
bytes: ?[*]u8,
length: usize,
pub fn len(self: RocList) usize {
return self.length;
}
pub fn isEmpty(self: RocList) bool {
return self.len() == 0;
}
pub fn empty() RocList {
return RocList{ .bytes = null, .length = 0 };
}
pub fn isUnique(self: RocList) bool {
// the empty list is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
return true;
}
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
pub fn allocate(
allocator: *Allocator,
alignment: usize,
length: usize,
element_size: usize,
) RocList {
const data_bytes = length * element_size;
return RocList{
.bytes = utils.allocateWithRefcount(allocator, alignment, data_bytes),
.length = length,
};
}
pub fn makeUnique(self: RocList, allocator: *Allocator, alignment: usize, element_width: usize) RocList {
if (self.isEmpty()) {
return self;
}
if (self.isUnique()) {
return self;
}
// unfortunately, we have to clone
var new_list = RocList.allocate(allocator, alignment, self.length, element_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_list.bytes);
const number_of_bytes = self.len() * element_width;
@memcpy(new_bytes, old_bytes, number_of_bytes);
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
const data_bytes = self.len() * element_width;
utils.decref(allocator, alignment, self.bytes, data_bytes);
return new_list;
}
pub fn reallocate(
self: RocList,
allocator: *Allocator,
alignment: usize,
new_length: usize,
element_width: usize,
) RocList {
const old_length = self.length;
const delta_length = new_length - old_length;
const data_bytes = new_capacity * slot_size;
const first_slot = allocateWithRefcount(allocator, alignment, data_bytes);
// transfer the memory
if (self.bytes) |source_ptr| {
const dest_ptr = first_slot;
@memcpy(dest_ptr, source_ptr, old_length);
}
// NOTE the newly added elements are left uninitialized
const result = RocList{
.dict_bytes = first_slot,
.length = new_length,
};
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
utils.decref(allocator, alignment, self.bytes, old_length * element_width);
return result;
}
};
const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listMap(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, old_element_width: usize, new_element_width: usize) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
const output = RocList.allocate(std.heap.c_allocator, alignment, size, new_element_width);
const target_ptr = output.bytes orelse unreachable;
while (i < size) : (i += 1) {
caller(transform, source_ptr + (i * old_element_width), target_ptr + (i * new_element_width));
}
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * old_element_width);
return output;
} else {
return RocList.empty();
}
}
pub fn listMapWithIndex(list: RocList, transform: Opaque, caller: Caller2, alignment: usize, old_element_width: usize, new_element_width: usize) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
const output = RocList.allocate(std.heap.c_allocator, alignment, size, new_element_width);
const target_ptr = output.bytes orelse unreachable;
while (i < size) : (i += 1) {
caller(transform, @ptrCast(?[*]u8, &i), source_ptr + (i * old_element_width), target_ptr + (i * new_element_width));
}
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * old_element_width);
return output;
} else {
return RocList.empty();
}
}
pub fn listKeepIf(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, element_width: usize, inc: Inc, dec: Dec) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
var output = RocList.allocate(std.heap.c_allocator, alignment, list.len(), list.len() * element_width);
const target_ptr = output.bytes orelse unreachable;
var kept: usize = 0;
while (i < size) : (i += 1) {
var keep = false;
const element = source_ptr + (i * element_width);
inc(element);
caller(transform, element, @ptrCast(?[*]u8, &keep));
if (keep) {
@memcpy(target_ptr + (kept * element_width), element, element_width);
kept += 1;
} else {
dec(element);
}
}
// consume the input list
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * element_width);
if (kept == 0) {
// if the output is empty, deallocate the space we made for the result
utils.decref(std.heap.c_allocator, alignment, output.bytes, size * element_width);
return RocList.empty();
} else {
output.length = kept;
return output;
}
} else {
return RocList.empty();
}
}
pub fn listKeepOks(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) callconv(.C) RocList {
return listKeepResult(list, RocResult.isOk, transform, caller, alignment, before_width, result_width, after_width, inc_closure, dec_result);
}
pub fn listKeepErrs(list: RocList, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) callconv(.C) RocList {
return listKeepResult(list, RocResult.isErr, transform, caller, alignment, before_width, result_width, after_width, inc_closure, dec_result);
}
pub fn listKeepResult(list: RocList, is_good_constructor: fn (RocResult) bool, transform: Opaque, caller: Caller1, alignment: usize, before_width: usize, result_width: usize, after_width: usize, inc_closure: Inc, dec_result: Dec) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
var output = RocList.allocate(std.heap.c_allocator, alignment, list.len(), list.len() * after_width);
const target_ptr = output.bytes orelse unreachable;
var temporary = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, result_width) catch unreachable);
var kept: usize = 0;
while (i < size) : (i += 1) {
const before_element = source_ptr + (i * before_width);
inc_closure(transform);
caller(transform, before_element, temporary);
const result = utils.RocResult{ .bytes = temporary };
const after_element = temporary + @sizeOf(i64);
if (is_good_constructor(result)) {
@memcpy(target_ptr + (kept * after_width), after_element, after_width);
kept += 1;
} else {
dec_result(temporary);
}
}
utils.decref(std.heap.c_allocator, alignment, list.bytes, size * before_width);
std.heap.c_allocator.free(temporary[0..result_width]);
if (kept == 0) {
utils.decref(std.heap.c_allocator, alignment, output.bytes, size * after_width);
return RocList.empty();
} else {
output.length = kept;
return output;
}
} else {
return RocList.empty();
}
}
pub fn listWalk(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, output: Opaque) callconv(.C) void {
if (accum_width == 0) {
return;
}
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| {
var i: usize = 0;
const size = list.len();
while (i < size) : (i += 1) {
const element = source_ptr + i * element_width;
stepper_caller(stepper, element, output, output);
}
const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
}
}
pub fn listWalkBackwards(list: RocList, stepper: Opaque, stepper_caller: Caller2, accum: Opaque, alignment: usize, element_width: usize, accum_width: usize, output: Opaque) callconv(.C) void {
if (accum_width == 0) {
return;
}
@memcpy(output orelse unreachable, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = size;
while (i > 0) {
i -= 1;
const element = source_ptr + i * element_width;
stepper_caller(stepper, element, output, output);
}
const data_bytes = list.len() * element_width;
utils.decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
}
}
// List.contains : List k, k -> Bool
pub fn listContains(list: RocList, key: Opaque, key_width: usize, is_eq: EqFn) callconv(.C) bool {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
while (i < size) : (i += 1) {
const element = source_ptr + i * key_width;
if (is_eq(element, key)) {
return true;
}
}
}
return false;
}
pub fn listRepeat(count: usize, alignment: usize, element: Opaque, element_width: usize, inc_n_element: Inc) callconv(.C) RocList {
if (count == 0) {
return RocList.empty();
}
const allocator = std.heap.c_allocator;
var output = RocList.allocate(allocator, alignment, count, element_width);
if (output.bytes) |target_ptr| {
var i: usize = 0;
const source = element orelse unreachable;
while (i < count) : (i += 1) {
@memcpy(target_ptr + i * element_width, source, element_width);
}
// TODO do all increments at once!
i = 0;
while (i < count) : (i += 1) {
inc_n_element(element);
}
return output;
} else {
unreachable;
}
}

View file

@ -2,6 +2,46 @@ const builtin = @import("builtin");
const std = @import("std"); const std = @import("std");
const testing = std.testing; const testing = std.testing;
// List Module
const list = @import("list.zig");
comptime {
exportListFn(list.listMap, "map");
exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk");
exportListFn(list.listWalkBackwards, "walk_backwards");
exportListFn(list.listKeepOks, "keep_oks");
exportListFn(list.listKeepErrs, "keep_errs");
exportListFn(list.listContains, "contains");
exportListFn(list.listRepeat, "repeat");
}
// Dict Module
const dict = @import("dict.zig");
const hash = @import("hash.zig");
comptime {
exportDictFn(dict.dictLen, "len");
exportDictFn(dict.dictEmpty, "empty");
exportDictFn(dict.dictInsert, "insert");
exportDictFn(dict.dictRemove, "remove");
exportDictFn(dict.dictContains, "contains");
exportDictFn(dict.dictGet, "get");
exportDictFn(dict.elementsRc, "elementsRc");
exportDictFn(dict.dictKeys, "keys");
exportDictFn(dict.dictValues, "values");
exportDictFn(dict.dictUnion, "union");
exportDictFn(dict.dictIntersection, "intersection");
exportDictFn(dict.dictDifference, "difference");
exportDictFn(dict.dictWalk, "walk");
exportDictFn(dict.setFromList, "set_from_list");
exportDictFn(hash.wyhash, "hash");
exportDictFn(hash.wyhash_rocstr, "hash_str");
}
// Num Module // Num Module
const num = @import("num.zig"); const num = @import("num.zig");
comptime { comptime {
@ -25,6 +65,7 @@ comptime {
exportStrFn(str.strJoinWithC, "joinWith"); exportStrFn(str.strJoinWithC, "joinWith");
exportStrFn(str.strNumberOfBytes, "number_of_bytes"); exportStrFn(str.strNumberOfBytes, "number_of_bytes");
exportStrFn(str.strFromIntC, "from_int"); exportStrFn(str.strFromIntC, "from_int");
exportStrFn(str.strFromFloatC, "from_float");
exportStrFn(str.strEqual, "equal"); exportStrFn(str.strEqual, "equal");
exportStrFn(str.validateUtf8Bytes, "validate_utf8_bytes"); exportStrFn(str.validateUtf8Bytes, "validate_utf8_bytes");
} }
@ -39,6 +80,13 @@ fn exportNumFn(comptime func: anytype, comptime func_name: []const u8) void {
fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void { fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "str." ++ func_name); exportBuiltinFn(func, "str." ++ func_name);
} }
fn exportDictFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "dict." ++ func_name);
}
fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "list." ++ func_name);
}
// Run all tests in imported modules // Run all tests in imported modules
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94 // https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94

View file

@ -1,3 +1,4 @@
const utils = @import("utils.zig");
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const always_inline = std.builtin.CallOptions.Modifier.always_inline; const always_inline = std.builtin.CallOptions.Modifier.always_inline;
@ -41,8 +42,6 @@ pub const RocStr = extern struct {
// This clones the pointed-to bytes if they won't fit in a // This clones the pointed-to bytes if they won't fit in a
// small string, and returns a (pointer, len) tuple which points to them. // small string, and returns a (pointer, len) tuple which points to them.
pub fn init(allocator: *Allocator, bytes_ptr: [*]const u8, length: usize) RocStr { pub fn init(allocator: *Allocator, bytes_ptr: [*]const u8, length: usize) RocStr {
const roc_str_size = @sizeOf(RocStr);
var result = RocStr.allocate(allocator, InPlace.Clone, length); var result = RocStr.allocate(allocator, InPlace.Clone, length);
@memcpy(result.asU8ptr(), bytes_ptr, length); @memcpy(result.asU8ptr(), bytes_ptr, length);
@ -50,18 +49,7 @@ pub const RocStr = extern struct {
} }
pub fn initBig(allocator: *Allocator, in_place: InPlace, number_of_chars: u64) RocStr { pub fn initBig(allocator: *Allocator, in_place: InPlace, number_of_chars: u64) RocStr {
const length = @sizeOf(usize) + number_of_chars; const first_element = utils.allocateWithRefcount(allocator, @sizeOf(usize), number_of_chars);
var new_bytes: []usize = allocator.alloc(usize, length) catch unreachable;
if (in_place == InPlace.InPlace) {
new_bytes[0] = @intCast(usize, number_of_chars);
} else {
const v: isize = std.math.minInt(isize);
new_bytes[0] = @bitCast(usize, v);
}
var first_element = @ptrCast([*]align(@alignOf(usize)) u8, new_bytes);
first_element += @sizeOf(usize);
return RocStr{ return RocStr{
.str_bytes = first_element, .str_bytes = first_element,
@ -315,6 +303,23 @@ fn strFromIntHelp(allocator: *Allocator, comptime T: type, int: T) RocStr {
return RocStr.init(allocator, &buf, result.len); return RocStr.init(allocator, &buf, result.len);
} }
// Str.fromFloat
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
// NOTE the compiled zig for float formatting seems to use LLVM11-specific features
// hopefully we can use zig instead of snprintf in the future when we upgrade
const c = @cImport({
// See https://github.com/ziglang/zig/issues/515
@cDefine("_NO_CRT_STDIO_INLINE", "1");
@cInclude("stdio.h");
});
var buf: [100]u8 = undefined;
const result = c.snprintf(&buf, 100, "%f", float);
return RocStr.init(std.heap.c_allocator, &buf, @intCast(usize, result));
}
// Str.split // Str.split
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator // When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strSplitInPlaceC(array: [*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void { pub fn strSplitInPlaceC(array: [*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void {
@ -829,8 +834,10 @@ pub fn strConcatC(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv
fn strConcat(allocator: *Allocator, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr { fn strConcat(allocator: *Allocator, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
if (arg1.isEmpty()) { if (arg1.isEmpty()) {
// the second argument is borrowed, so we must increment its refcount before returning
return RocStr.clone(allocator, result_in_place, arg2); return RocStr.clone(allocator, result_in_place, arg2);
} else if (arg2.isEmpty()) { } else if (arg2.isEmpty()) {
// the first argument is owned, so we can return it without cloning
return RocStr.clone(allocator, result_in_place, arg1); return RocStr.clone(allocator, result_in_place, arg1);
} else { } else {
const combined_length = arg1.len() + arg2.len(); const combined_length = arg1.len() + arg2.len();

View file

@ -0,0 +1,107 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
pub const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE);
pub fn decref(
allocator: *Allocator,
alignment: usize,
bytes_or_null: ?[*]u8,
data_bytes: usize,
) void {
if (data_bytes == 0) {
return;
}
var bytes = bytes_or_null orelse return;
const isizes: [*]isize = @ptrCast([*]isize, @alignCast(8, bytes));
const refcount = (isizes - 1)[0];
const refcount_isize = @bitCast(isize, refcount);
switch (alignment) {
16 => {
if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 16)[0 .. 16 + data_bytes]);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
else => {
// NOTE enums can currently have an alignment of < 8
if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 8)[0 .. 8 + data_bytes]);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
},
}
}
pub fn allocateWithRefcount(
allocator: *Allocator,
alignment: usize,
data_bytes: usize,
) [*]u8 {
comptime const result_in_place = false;
switch (alignment) {
16 => {
const length = 2 * @sizeOf(usize) + data_bytes;
var new_bytes: []align(16) u8 = allocator.alignedAlloc(u8, 16, length) catch unreachable;
var as_usize_array = @ptrCast([*]usize, new_bytes);
if (result_in_place) {
as_usize_array[0] = 0;
as_usize_array[1] = @intCast(usize, number_of_slots);
} else {
as_usize_array[0] = 0;
as_usize_array[1] = REFCOUNT_ONE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + 2 * @sizeOf(usize);
return first_slot;
},
else => {
const length = @sizeOf(usize) + data_bytes;
var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable;
var as_usize_array = @ptrCast([*]isize, new_bytes);
if (result_in_place) {
as_usize_array[0] = @intCast(isize, number_of_slots);
} else {
as_usize_array[0] = REFCOUNT_ONE_ISIZE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);
const first_slot = as_u8_array + @sizeOf(usize);
return first_slot;
},
}
}
pub const RocResult = extern struct {
bytes: ?[*]u8,
pub fn isOk(self: RocResult) bool {
// assumptions
//
// - the tag is the first field
// - the tag is usize bytes wide
// - Ok has tag_id 1, because Err < Ok
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, self.bytes));
return usizes[0] == 1;
}
pub fn isErr(self: RocResult) bool {
return !self.isOk();
}
};

View file

@ -14,13 +14,23 @@ fn main() {
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap(); let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode"); let bitcode_path = build_script_dir_path.join("bitcode");
let src_path = bitcode_path.join("src"); let src_obj_path = bitcode_path.join("builtins.o");
let src_obj = src_obj_path.to_str().expect("Invalid src object path");
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
let dest_obj_path = Path::new(&out_dir).join("builtins.o");
let dest_obj = dest_obj_path.to_str().expect("Invalid dest object path");
println!("Moving zig object to: {}", dest_obj);
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]);
let dest_ir_path = bitcode_path.join("builtins.ll"); let dest_ir_path = bitcode_path.join("builtins.ll");
let dest_ir = dest_ir_path.to_str().expect("Invalid dest ir path"); let dest_ir = dest_ir_path.to_str().expect("Invalid dest ir path");
println!("Compiling ir to: {}", dest_ir); println!("Compiling ir to: {}", dest_ir);
run_command(bitcode_path, "zig", &["build", "ir", "-Drelease=true"]); run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
let dest_bc_path = Path::new(&out_dir).join("builtins.bc"); let dest_bc_path = Path::new(&out_dir).join("builtins.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path"); let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path");
@ -34,7 +44,8 @@ fn main() {
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=BUILTINS_BC={}", dest_bc); println!("cargo:rustc-env=BUILTINS_BC={}", dest_bc);
get_zig_files(src_path.as_path(), &|path| { println!("cargo:rustc-env=BUILTINS_O={}", dest_obj);
get_zig_files(bitcode_path.as_path(), &|path| {
let path: &Path = path; let path: &Path = path;
println!( println!(
"cargo:rerun-if-changed={}", "cargo:rerun-if-changed={}",
@ -74,7 +85,9 @@ fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
let entry = entry?; let entry = entry?;
let path_buf = entry.path(); let path_buf = entry.path();
if path_buf.is_dir() { if path_buf.is_dir() {
get_zig_files(&path_buf, cb).unwrap(); if !path_buf.ends_with("zig-cache") {
get_zig_files(&path_buf, cb).unwrap();
}
} else { } else {
let path = path_buf.as_path(); let path = path_buf.as_path();

View file

@ -1,7 +1,9 @@
interface Dict2 interface Dict
exposes [ isEmpty, map ] exposes [ isEmpty, map ]
imports [] imports []
size : Dict * * -> Nat
isEmpty : Dict * * -> Bool isEmpty : Dict * * -> Bool
## Convert each key and value in the #Dict to something new, by calling a conversion ## Convert each key and value in the #Dict to something new, by calling a conversion
@ -13,4 +15,8 @@ isEmpty : Dict * * -> Bool
## ##
## `map` functions like this are common in Roc, and they all work similarly. ## `map` functions like this are common in Roc, and they all work similarly.
## See for example #Result.map, #List.map, and #Set.map. ## See for example #Result.map, #List.map, and #Set.map.
map : List before, (before -> after) -> List after map :
Dict beforeKey beforeValue,
(\{ key: beforeKey, value: beforeValue } ->
{ key: afterKey, value: afterValue }
) -> Dict afterKey afterValue

View file

@ -2,15 +2,20 @@ use std::fs::File;
use std::io::prelude::Read; use std::io::prelude::Read;
use std::vec::Vec; use std::vec::Vec;
const PATH: &str = env!( const BC_PATH: &str = env!(
"BUILTINS_BC", "BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?" "Env var BUILTINS_BC not found. Is there a problem with the build script?"
); );
pub const OBJ_PATH: &str = env!(
"BUILTINS_O",
"Env var BUILTINS_O not found. Is there a problem with the build script?"
);
pub fn get_bytes() -> Vec<u8> { pub fn get_bytes() -> Vec<u8> {
// In the build script for the builtins module, we compile the builtins bitcode and set // In the build script for the builtins module, we compile the builtins bitcode and set
// BUILTINS_BC to the path to the compiled output. // BUILTINS_BC to the path to the compiled output.
let mut builtins_bitcode = File::open(PATH).expect("Unable to find builtins bitcode source"); let mut builtins_bitcode = File::open(BC_PATH).expect("Unable to find builtins bitcode source");
let mut buffer = Vec::new(); let mut buffer = Vec::new();
builtins_bitcode builtins_bitcode
.read_to_end(&mut buffer) .read_to_end(&mut buffer)
@ -34,5 +39,34 @@ pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with"; pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes"; pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int"; pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float";
pub const STR_EQUAL: &str = "roc_builtins.str.equal"; pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_VALIDATE_UTF_BYTES: &str = "roc_builtins.str.validate_utf8_bytes"; pub const STR_VALIDATE_UTF_BYTES: &str = "roc_builtins.str.validate_utf8_bytes";
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
pub const DICT_LEN: &str = "roc_builtins.dict.len";
pub const DICT_EMPTY: &str = "roc_builtins.dict.empty";
pub const DICT_INSERT: &str = "roc_builtins.dict.insert";
pub const DICT_REMOVE: &str = "roc_builtins.dict.remove";
pub const DICT_CONTAINS: &str = "roc_builtins.dict.contains";
pub const DICT_GET: &str = "roc_builtins.dict.get";
pub const DICT_ELEMENTS_RC: &str = "roc_builtins.dict.elementsRc";
pub const DICT_KEYS: &str = "roc_builtins.dict.keys";
pub const DICT_VALUES: &str = "roc_builtins.dict.values";
pub const DICT_UNION: &str = "roc_builtins.dict.union";
pub const DICT_DIFFERENCE: &str = "roc_builtins.dict.difference";
pub const DICT_INTERSECTION: &str = "roc_builtins.dict.intersection";
pub const DICT_WALK: &str = "roc_builtins.dict.walk";
pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index";
pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";
pub const LIST_KEEP_ERRS: &str = "roc_builtins.list.keep_errs";
pub const LIST_WALK: &str = "roc_builtins.list.walk";
pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";

View file

@ -3,4 +3,3 @@
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod bitcode; pub mod bitcode;
pub mod std; pub mod std;
pub mod unique;

View file

@ -4,12 +4,36 @@ use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::builtin_aliases::{ use roc_types::builtin_aliases::{
bool_type, dict_type, float_type, int_type, list_type, nat_type, num_type, ordering_type, bool_type, dict_type, float_type, int_type, list_type, nat_type, num_type, ordering_type,
result_type, set_type, str_type, str_utf8_byte_problem_type, u8_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u64_type, u8_type,
}; };
use roc_types::solved_types::SolvedType; use roc_types::solved_types::SolvedType;
use roc_types::subs::VarId; use roc_types::subs::VarId;
use std::collections::HashMap; use std::collections::HashMap;
/// Example:
///
/// let_tvars! { a, b, c }
///
/// This is equivalent to:
///
/// let a = VarId::from_u32(1);
/// let b = VarId::from_u32(2);
/// let c = VarId::from_u32(3);
///
/// The idea is that this is less error-prone than assigning hardcoded IDs by hand.
macro_rules! let_tvars {
($($name:ident,)+) => { let_tvars!($($name),+) };
($($name:ident),*) => {
let mut _current_tvar = 0;
$(
_current_tvar += 1;
let $name = VarId::from_u32(_current_tvar);
)*
};
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Mode { pub enum Mode {
Standard, Standard,
@ -557,6 +581,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// fromFloat : Float a -> Str
add_type(
Symbol::STR_FROM_FLOAT,
top_level_function(vec![float_type(flex(TVAR1))], Box::new(str_type())),
);
// List module // List module
// get : List elem, Nat -> Result elem [ OutOfBounds ]* // get : List elem, Nat -> Result elem [ OutOfBounds ]*
@ -670,6 +700,38 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// keepOks : List before, (before -> Result after *) -> List after
add_type(Symbol::LIST_KEEP_OKS, {
let_tvars! { star, cvar, before, after};
top_level_function(
vec![
list_type(flex(before)),
closure(
vec![flex(before)],
cvar,
Box::new(result_type(flex(after), flex(star))),
),
],
Box::new(list_type(flex(after))),
)
});
// keepOks : List before, (before -> Result * after) -> List after
add_type(Symbol::LIST_KEEP_ERRS, {
let_tvars! { star, cvar, before, after};
top_level_function(
vec![
list_type(flex(before)),
closure(
vec![flex(before)],
cvar,
Box::new(result_type(flex(star), flex(after))),
),
],
Box::new(list_type(flex(after))),
)
});
// map : List before, (before -> after) -> List after // map : List before, (before -> after) -> List after
add_type( add_type(
Symbol::LIST_MAP, Symbol::LIST_MAP,
@ -682,6 +744,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// mapWithIndex : List before, (Nat, before -> after) -> List after
add_type(Symbol::LIST_MAP_WITH_INDEX, {
let_tvars! { cvar, before, after};
top_level_function(
vec![
list_type(flex(before)),
closure(vec![nat_type(), flex(before)], cvar, Box::new(flex(after))),
],
Box::new(list_type(flex(after))),
)
});
// append : List elem, elem -> List elem // append : List elem, elem -> List elem
add_type( add_type(
Symbol::LIST_APPEND, Symbol::LIST_APPEND,
@ -719,7 +793,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_type( add_type(
Symbol::LIST_REPEAT, Symbol::LIST_REPEAT,
top_level_function( top_level_function(
vec![nat_type(), flex(TVAR2)], vec![nat_type(), flex(TVAR1)],
Box::new(list_type(flex(TVAR1))), Box::new(list_type(flex(TVAR1))),
), ),
); );
@ -747,7 +821,22 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Dict module // Dict module
// empty : Dict k v // Dict.hashTestOnly : Nat, v -> Nat
add_type(
Symbol::DICT_TEST_HASH,
top_level_function(vec![u64_type(), flex(TVAR2)], Box::new(nat_type())),
);
// len : Dict * * -> Nat
add_type(
Symbol::DICT_LEN,
top_level_function(
vec![dict_type(flex(TVAR1), flex(TVAR2))],
Box::new(nat_type()),
),
);
// empty : Dict * *
add_type(Symbol::DICT_EMPTY, dict_type(flex(TVAR1), flex(TVAR2))); add_type(Symbol::DICT_EMPTY, dict_type(flex(TVAR1), flex(TVAR2)));
// singleton : k, v -> Dict k v // singleton : k, v -> Dict k v
@ -773,6 +862,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// Dict.insert : Dict k v, k, v -> Dict k v
add_type( add_type(
Symbol::DICT_INSERT, Symbol::DICT_INSERT,
top_level_function( top_level_function(
@ -785,6 +875,95 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// Dict.remove : Dict k v, k -> Dict k v
add_type(
Symbol::DICT_REMOVE,
top_level_function(
vec![dict_type(flex(TVAR1), flex(TVAR2)), flex(TVAR1)],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
),
);
// Dict.contains : Dict k v, k -> Bool
add_type(
Symbol::DICT_CONTAINS,
top_level_function(
vec![dict_type(flex(TVAR1), flex(TVAR2)), flex(TVAR1)],
Box::new(bool_type()),
),
);
// Dict.keys : Dict k v -> List k
add_type(
Symbol::DICT_KEYS,
top_level_function(
vec![dict_type(flex(TVAR1), flex(TVAR2))],
Box::new(list_type(flex(TVAR1))),
),
);
// Dict.values : Dict k v -> List v
add_type(
Symbol::DICT_VALUES,
top_level_function(
vec![dict_type(flex(TVAR1), flex(TVAR2))],
Box::new(list_type(flex(TVAR2))),
),
);
// Dict.union : Dict k v, Dict k v -> Dict k v
add_type(
Symbol::DICT_UNION,
top_level_function(
vec![
dict_type(flex(TVAR1), flex(TVAR2)),
dict_type(flex(TVAR1), flex(TVAR2)),
],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
),
);
// Dict.intersection : Dict k v, Dict k v -> Dict k v
add_type(
Symbol::DICT_INTERSECTION,
top_level_function(
vec![
dict_type(flex(TVAR1), flex(TVAR2)),
dict_type(flex(TVAR1), flex(TVAR2)),
],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
),
);
// Dict.difference : Dict k v, Dict k v -> Dict k v
add_type(
Symbol::DICT_DIFFERENCE,
top_level_function(
vec![
dict_type(flex(TVAR1), flex(TVAR2)),
dict_type(flex(TVAR1), flex(TVAR2)),
],
Box::new(dict_type(flex(TVAR1), flex(TVAR2))),
),
);
// Dict.walk : Dict k v, (k, v, accum -> accum), accum -> accum
add_type(
Symbol::DICT_WALK,
top_level_function(
vec![
dict_type(flex(TVAR1), flex(TVAR2)),
closure(
vec![flex(TVAR1), flex(TVAR2), flex(TVAR3)],
TVAR4,
Box::new(flex(TVAR3)),
),
flex(TVAR3),
],
Box::new(flex(TVAR3)),
),
);
// Set module // Set module
// empty : Set a // empty : Set a
@ -796,6 +975,30 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![flex(TVAR1)], Box::new(set_type(flex(TVAR1)))), top_level_function(vec![flex(TVAR1)], Box::new(set_type(flex(TVAR1)))),
); );
// len : Set * -> Nat
add_type(
Symbol::SET_LEN,
top_level_function(vec![set_type(flex(TVAR1))], Box::new(nat_type())),
);
// toList : Set a -> List a
add_type(
Symbol::SET_TO_LIST,
top_level_function(
vec![set_type(flex(TVAR1))],
Box::new(list_type(flex(TVAR1))),
),
);
// fromList : Set a -> List a
add_type(
Symbol::SET_FROM_LIST,
top_level_function(
vec![list_type(flex(TVAR1))],
Box::new(set_type(flex(TVAR1))),
),
);
// union : Set a, Set a -> Set a // union : Set a, Set a -> Set a
add_type( add_type(
Symbol::SET_UNION, Symbol::SET_UNION,
@ -805,18 +1008,27 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// diff : Set a, Set a -> Set a // difference : Set a, Set a -> Set a
add_type( add_type(
Symbol::SET_DIFF, Symbol::SET_DIFFERENCE,
top_level_function( top_level_function(
vec![set_type(flex(TVAR1)), set_type(flex(TVAR1))], vec![set_type(flex(TVAR1)), set_type(flex(TVAR1))],
Box::new(set_type(flex(TVAR1))), Box::new(set_type(flex(TVAR1))),
), ),
); );
// foldl : Set a, (a -> b -> b), b -> b // intersection : Set a, Set a -> Set a
add_type( add_type(
Symbol::SET_FOLDL, Symbol::SET_INTERSECTION,
top_level_function(
vec![set_type(flex(TVAR1)), set_type(flex(TVAR1))],
Box::new(set_type(flex(TVAR1))),
),
);
// Set.walk : Set a, (a, b -> b), b -> b
add_type(
Symbol::SET_WALK,
top_level_function( top_level_function(
vec![ vec![
set_type(flex(TVAR1)), set_type(flex(TVAR1)),
@ -843,6 +1055,14 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
add_type(
Symbol::SET_CONTAINS,
top_level_function(
vec![set_type(flex(TVAR1)), flex(TVAR1)],
Box::new(bool_type()),
),
);
// Result module // Result module
// map : Result a err, (a -> b) -> Result b err // map : Result a err, (a -> b) -> Result b err
@ -857,6 +1077,27 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// mapErr : Result a x, (x -> y) -> Result a x
add_type(
Symbol::RESULT_MAP_ERR,
top_level_function(
vec![
result_type(flex(TVAR1), flex(TVAR3)),
closure(vec![flex(TVAR3)], TVAR4, Box::new(flex(TVAR2))),
],
Box::new(result_type(flex(TVAR1), flex(TVAR2))),
),
);
// withDefault : Result a x, a -> a
add_type(
Symbol::RESULT_WITH_DEFAULT,
top_level_function(
vec![result_type(flex(TVAR1), flex(TVAR3)), flex(TVAR1)],
Box::new(flex(TVAR1)),
),
);
types types
} }

View file

@ -906,6 +906,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Dict module // Dict module
// : Attr * (Dict k v) -> Attr * Nat
add_type(Symbol::DICT_LEN, {
let_tvars! { star1, k , v, star2, int };
unique_function(vec![dict_type(star1, k, v)], int_type(star2, int))
});
// empty : Attr * (Dict k v) // empty : Attr * (Dict k v)
add_type(Symbol::DICT_EMPTY, { add_type(Symbol::DICT_EMPTY, {
let_tvars! { star, k , v }; let_tvars! { star, k , v };
@ -1046,13 +1053,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// diff : Attr * (Set * a) // diff : Attr * (Set * a)
// , Attr * (Set * a) // , Attr * (Set * a)
// -> Attr * (Set * a) // -> Attr * (Set * a)
add_type(Symbol::SET_DIFF, set_combine); add_type(Symbol::SET_DIFFERENCE, set_combine);
// foldl : Attr (* | u) (Set (Attr u a)) // foldl : Attr (* | u) (Set (Attr u a))
// , Attr Shared (Attr u a -> b -> b) // , Attr Shared (Attr u a -> b -> b)
// , b // , b
// -> b // -> b
add_type(Symbol::SET_FOLDL, { add_type(Symbol::SET_WALK, {
let_tvars! { star, u, a, b, closure }; let_tvars! { star, u, a, b, closure };
unique_function( unique_function(

View file

@ -1,10 +1,11 @@
use crate::def::Def; use crate::def::Def;
use crate::expr::Expr::*; use crate::expr::Expr::*;
use crate::expr::{Expr, Recursive}; use crate::expr::{Expr, Recursive, WhenBranch};
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::{MutMap, SendMap}; use roc_collections::all::{MutMap, SendMap};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -61,6 +62,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_COUNT_GRAPHEMES => str_count_graphemes, STR_COUNT_GRAPHEMES => str_count_graphemes,
STR_FROM_INT => str_from_int, STR_FROM_INT => str_from_int,
STR_FROM_UTF8 => str_from_utf8, STR_FROM_UTF8 => str_from_utf8,
STR_FROM_FLOAT=> str_from_float,
LIST_LEN => list_len, LIST_LEN => list_len,
LIST_GET => list_get, LIST_GET => list_get,
LIST_SET => list_set, LIST_SET => list_set,
@ -77,9 +79,38 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_PREPEND => list_prepend, LIST_PREPEND => list_prepend,
LIST_JOIN => list_join, LIST_JOIN => list_join,
LIST_MAP => list_map, LIST_MAP => list_map,
LIST_MAP_WITH_INDEX => list_map_with_index,
LIST_KEEP_IF => list_keep_if, LIST_KEEP_IF => list_keep_if,
LIST_KEEP_OKS => list_keep_oks,
LIST_KEEP_ERRS=> list_keep_errs,
LIST_WALK => list_walk, LIST_WALK => list_walk,
LIST_WALK_BACKWARDS => list_walk_backwards, LIST_WALK_BACKWARDS => list_walk_backwards,
DICT_TEST_HASH => dict_hash_test_only,
DICT_LEN => dict_len,
DICT_EMPTY => dict_empty,
DICT_SINGLETON => dict_singleton,
DICT_INSERT => dict_insert,
DICT_REMOVE => dict_remove,
DICT_GET => dict_get,
DICT_CONTAINS => dict_contains,
DICT_KEYS => dict_keys,
DICT_VALUES => dict_values,
DICT_UNION=> dict_union,
DICT_INTERSECTION=> dict_intersection,
DICT_DIFFERENCE=> dict_difference,
DICT_WALK=> dict_walk,
SET_EMPTY => set_empty,
SET_LEN => set_len,
SET_SINGLETON => set_singleton,
SET_UNION=> set_union,
SET_INTERSECTION => set_intersection,
SET_DIFFERENCE => set_difference,
SET_TO_LIST => set_to_list,
SET_FROM_LIST => set_from_list,
SET_INSERT => set_insert,
SET_REMOVE => set_remove,
SET_CONTAINS => set_contains,
SET_WALK=> set_walk,
NUM_ADD => num_add, NUM_ADD => num_add,
NUM_ADD_CHECKED => num_add_checked, NUM_ADD_CHECKED => num_add_checked,
NUM_ADD_WRAP => num_add_wrap, NUM_ADD_WRAP => num_add_wrap,
@ -120,7 +151,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_MAX_INT => num_max_int, NUM_MAX_INT => num_max_int,
NUM_MIN_INT => num_min_int, NUM_MIN_INT => num_min_int,
NUM_BITWISE_AND => num_bitwise_and, NUM_BITWISE_AND => num_bitwise_and,
NUM_BITWISE_XOR => num_bitwise_xor NUM_BITWISE_XOR => num_bitwise_xor,
RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err,
RESULT_WITH_DEFAULT => result_with_default,
} }
} }
@ -157,6 +191,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes, Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
Symbol::STR_FROM_INT => str_from_int, Symbol::STR_FROM_INT => str_from_int,
Symbol::STR_FROM_UTF8 => str_from_utf8, Symbol::STR_FROM_UTF8 => str_from_utf8,
Symbol::STR_FROM_FLOAT=> str_from_float,
Symbol::LIST_LEN => list_len, Symbol::LIST_LEN => list_len,
Symbol::LIST_GET => list_get, Symbol::LIST_GET => list_get,
Symbol::LIST_SET => list_set, Symbol::LIST_SET => list_set,
@ -173,9 +208,38 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_PREPEND => list_prepend, Symbol::LIST_PREPEND => list_prepend,
Symbol::LIST_JOIN => list_join, Symbol::LIST_JOIN => list_join,
Symbol::LIST_MAP => list_map, Symbol::LIST_MAP => list_map,
Symbol::LIST_MAP_WITH_INDEX => list_map_with_index,
Symbol::LIST_KEEP_IF => list_keep_if, Symbol::LIST_KEEP_IF => list_keep_if,
Symbol::LIST_KEEP_OKS => list_keep_oks,
Symbol::LIST_KEEP_ERRS=> list_keep_errs,
Symbol::LIST_WALK => list_walk, Symbol::LIST_WALK => list_walk,
Symbol::LIST_WALK_BACKWARDS => list_walk_backwards, Symbol::LIST_WALK_BACKWARDS => list_walk_backwards,
Symbol::DICT_TEST_HASH => dict_hash_test_only,
Symbol::DICT_LEN => dict_len,
Symbol::DICT_EMPTY => dict_empty,
Symbol::DICT_SINGLETON => dict_singleton,
Symbol::DICT_INSERT => dict_insert,
Symbol::DICT_REMOVE => dict_remove,
Symbol::DICT_GET => dict_get,
Symbol::DICT_CONTAINS => dict_contains,
Symbol::DICT_KEYS => dict_keys,
Symbol::DICT_VALUES => dict_values,
Symbol::DICT_UNION=> dict_union,
Symbol::DICT_INTERSECTION=> dict_intersection,
Symbol::DICT_DIFFERENCE=> dict_difference,
Symbol::DICT_WALK=> dict_walk,
Symbol::SET_EMPTY => set_empty,
Symbol::SET_LEN => set_len,
Symbol::SET_SINGLETON => set_singleton,
Symbol::SET_UNION=> set_union,
Symbol::SET_INTERSECTION=> set_intersection,
Symbol::SET_DIFFERENCE=> set_difference,
Symbol::SET_TO_LIST => set_to_list,
Symbol::SET_FROM_LIST => set_from_list,
Symbol::SET_INSERT => set_insert,
Symbol::SET_REMOVE => set_remove,
Symbol::SET_CONTAINS => set_contains,
Symbol::SET_WALK => set_walk,
Symbol::NUM_ADD => num_add, Symbol::NUM_ADD => num_add,
Symbol::NUM_ADD_CHECKED => num_add_checked, Symbol::NUM_ADD_CHECKED => num_add_checked,
Symbol::NUM_ADD_WRAP => num_add_wrap, Symbol::NUM_ADD_WRAP => num_add_wrap,
@ -211,9 +275,83 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_ASIN => num_asin, Symbol::NUM_ASIN => num_asin,
Symbol::NUM_MAX_INT => num_max_int, Symbol::NUM_MAX_INT => num_max_int,
Symbol::NUM_MIN_INT => num_min_int, Symbol::NUM_MIN_INT => num_min_int,
Symbol::RESULT_MAP => result_map,
Symbol::RESULT_MAP_ERR => result_map_err,
Symbol::RESULT_WITH_DEFAULT => result_with_default,
} }
} }
fn lowlevel_1(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![(arg1_var, Var(Symbol::ARG_1))],
ret_var,
};
defn(
symbol,
vec![(arg1_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
fn lowlevel_2(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();
let arg2_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![
(arg1_var, Var(Symbol::ARG_1)),
(arg2_var, Var(Symbol::ARG_2)),
],
ret_var,
};
defn(
symbol,
vec![(arg1_var, Symbol::ARG_1), (arg2_var, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
}
fn lowlevel_3(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();
let arg2_var = var_store.fresh();
let arg3_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![
(arg1_var, Var(Symbol::ARG_1)),
(arg2_var, Var(Symbol::ARG_2)),
(arg3_var, Var(Symbol::ARG_3)),
],
ret_var,
};
defn(
symbol,
vec![
(arg1_var, Symbol::ARG_1),
(arg2_var, Symbol::ARG_2),
(arg3_var, Symbol::ARG_3),
],
var_store,
body,
ret_var,
)
}
/// Num.maxInt : Int /// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
@ -1356,7 +1494,7 @@ fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Str.fromInt : Int -> Str /// Str.fromInt : Int * -> Str
fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def { fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let str_var = var_store.fresh(); let str_var = var_store.fresh();
@ -1480,6 +1618,26 @@ fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Str.fromFloat : Float * -> Str
fn str_from_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
let float_var = var_store.fresh();
let str_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::StrFromFloat,
args: vec![(float_var, Var(Symbol::ARG_1))],
ret_var: str_var,
};
defn(
symbol,
vec![(float_var, Symbol::ARG_1)],
var_store,
body,
str_var,
)
}
/// List.concat : List elem, List elem -> List elem /// List.concat : List elem, List elem -> List elem
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
@ -1886,51 +2044,390 @@ fn list_keep_if(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
// List.contains : List elem, elem, -> Bool /// List.contains : List elem, elem -> Bool
fn list_contains(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_contains(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); lowlevel_2(symbol, LowLevel::ListContains, var_store)
let elem_var = var_store.fresh(); }
let bool_var = var_store.fresh();
let body = RunLowLevel { /// List.keepOks : List before, (before -> Result after *) -> List after
op: LowLevel::ListContains, fn list_keep_oks(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![ lowlevel_2(symbol, LowLevel::ListKeepOks, var_store)
(list_var, Var(Symbol::ARG_1)), }
(elem_var, Var(Symbol::ARG_2)),
],
ret_var: bool_var,
};
defn( /// List.keepErrs: List before, (before -> Result * after) -> List after
symbol, fn list_keep_errs(symbol: Symbol, var_store: &mut VarStore) -> Def {
vec![(list_var, Symbol::ARG_1), (elem_var, Symbol::ARG_2)], lowlevel_2(symbol, LowLevel::ListKeepErrs, var_store)
var_store,
body,
bool_var,
)
} }
/// List.map : List before, (before -> after) -> List after /// List.map : List before, (before -> after) -> List after
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); lowlevel_2(symbol, LowLevel::ListMap, var_store)
let func_var = var_store.fresh(); }
let ret_list_var = var_store.fresh();
/// List.mapWithIndex : List before, (Nat, before -> after) -> List after
fn list_map_with_index(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListMapWithIndex, var_store)
}
/// Dict.hashTestOnly : k, v -> Nat
pub fn dict_hash_test_only(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::Hash, var_store)
}
/// Dict.len : Dict * * -> Nat
fn dict_len(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::DictSize, var_store)
}
/// Dict.empty : Dict * *
fn dict_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let dict_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::DictEmpty,
args: vec![],
ret_var: dict_var,
};
Def {
annotation: None,
expr_var: dict_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
/// Dict.singleton : k, v -> Dict k v
fn dict_singleton(symbol: Symbol, var_store: &mut VarStore) -> Def {
let key_var = var_store.fresh();
let value_var = var_store.fresh();
let dict_var = var_store.fresh();
let empty = RunLowLevel {
op: LowLevel::DictEmpty,
args: vec![],
ret_var: dict_var,
};
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::ListMap, op: LowLevel::DictInsert,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (dict_var, empty),
(func_var, Var(Symbol::ARG_2)), (key_var, Var(Symbol::ARG_1)),
(value_var, Var(Symbol::ARG_2)),
], ],
ret_var: ret_list_var, ret_var: dict_var,
}; };
defn( defn(
symbol, symbol,
vec![(list_var, Symbol::ARG_1), (func_var, Symbol::ARG_2)], vec![(key_var, Symbol::ARG_1), (value_var, Symbol::ARG_2)],
var_store, var_store,
body, body,
ret_list_var, dict_var,
)
}
/// Dict.insert : Dict k v, k, v -> Dict k v
fn dict_insert(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_3(symbol, LowLevel::DictInsert, var_store)
}
/// Dict.remove : Dict k v, k -> Dict k v
fn dict_remove(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::DictRemove, var_store)
}
/// Dict.contains : Dict k v, k -> Bool
fn dict_contains(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::DictContains, var_store)
}
/// Dict.get : Dict k v, k -> Result v [ KeyNotFound ]*
fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_dict = Symbol::ARG_1;
let arg_key = Symbol::ARG_2;
let temp_record = Symbol::DICT_GET_RESULT;
let bool_var = var_store.fresh();
let flag_var = var_store.fresh();
let key_var = var_store.fresh();
let dict_var = var_store.fresh();
let value_var = var_store.fresh();
let ret_var = var_store.fresh();
let temp_record_var = var_store.fresh();
let ext_var1 = var_store.fresh();
let ext_var2 = var_store.fresh();
// NOTE DictGetUnsafe returns a { flag: Bool, value: v }
// when the flag is True, the value is found and defined;
// otherwise it is not and `Dict.get` should return `Err ...`
let def_body = RunLowLevel {
op: LowLevel::DictGetUnsafe,
args: vec![(dict_var, Var(arg_dict)), (key_var, Var(arg_key))],
ret_var: temp_record_var,
};
let def = Def {
annotation: None,
expr_var: temp_record_var,
loc_expr: Located::at_zero(def_body),
loc_pattern: Located::at_zero(Pattern::Identifier(temp_record)),
pattern_vars: Default::default(),
};
let get_value = Access {
record_var: temp_record_var,
ext_var: ext_var1,
field_var: value_var,
loc_expr: Box::new(no_region(Var(temp_record))),
field: "value".into(),
};
let get_flag = Access {
record_var: temp_record_var,
ext_var: ext_var2,
field_var: flag_var,
loc_expr: Box::new(no_region(Var(temp_record))),
field: "zflag".into(),
};
let make_ok = tag("Ok", vec![get_value], var_store);
let make_err = tag(
"Err",
vec![tag("OutOfBounds", Vec::new(), var_store)],
var_store,
);
let inspect = If {
cond_var: bool_var,
branch_var: ret_var,
branches: vec![(
// if-condition
no_region(get_flag),
no_region(make_ok),
)],
final_else: Box::new(no_region(make_err)),
};
let body = LetNonRec(Box::new(def), Box::new(no_region(inspect)), ret_var);
defn(
symbol,
vec![(dict_var, Symbol::ARG_1), (key_var, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
}
/// Dict.keys : Dict k v -> List k
fn dict_keys(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::DictKeys, var_store)
}
/// Dict.values : Dict k v -> List v
fn dict_values(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::DictValues, var_store)
}
/// Dict.union : Dict k v, Dict k v -> Dict k v
fn dict_union(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::DictUnion, var_store)
}
/// Dict.difference : Dict k v, Dict k v -> Dict k v
fn dict_difference(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::DictDifference, var_store)
}
/// Dict.intersection : Dict k v, Dict k v -> Dict k v
fn dict_intersection(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::DictIntersection, var_store)
}
/// Dict.walk : Dict k v, (k, v, accum -> accum), accum -> accum
fn dict_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_3(symbol, LowLevel::DictWalk, var_store)
}
/// Set.empty : Set *
fn set_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let set_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::DictEmpty,
args: vec![],
ret_var: set_var,
};
Def {
annotation: None,
expr_var: set_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
/// Set.singleton : k -> Set k
fn set_singleton(symbol: Symbol, var_store: &mut VarStore) -> Def {
let key_var = var_store.fresh();
let set_var = var_store.fresh();
let value_var = Variable::EMPTY_RECORD;
let empty = RunLowLevel {
op: LowLevel::DictEmpty,
args: vec![],
ret_var: set_var,
};
let body = RunLowLevel {
op: LowLevel::DictInsert,
args: vec![
(set_var, empty),
(key_var, Var(Symbol::ARG_1)),
(value_var, EmptyRecord),
],
ret_var: set_var,
};
defn(
symbol,
vec![(key_var, Symbol::ARG_1)],
var_store,
body,
set_var,
)
}
/// Set.len : Set * -> Nat
fn set_len(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::DictSize, var_store)
}
/// Dict.union : Dict k v, Dict k v -> Dict k v
fn set_union(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::DictUnion, var_store)
}
/// Dict.difference : Dict k v, Dict k v -> Dict k v
fn set_difference(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::DictDifference, var_store)
}
/// Dict.intersection : Dict k v, Dict k v -> Dict k v
fn set_intersection(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::DictIntersection, var_store)
}
/// Set.toList : Set k -> List k
fn set_to_list(symbol: Symbol, var_store: &mut VarStore) -> Def {
dict_keys(symbol, var_store)
}
/// Set.fromList : List k -> Set k
fn set_from_list(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::SetFromList, var_store)
}
/// Set.insert : Set k, k -> Set k
fn set_insert(symbol: Symbol, var_store: &mut VarStore) -> Def {
let dict_var = var_store.fresh();
let key_var = var_store.fresh();
let val_var = Variable::EMPTY_RECORD;
let body = RunLowLevel {
op: LowLevel::DictInsert,
args: vec![
(dict_var, Var(Symbol::ARG_1)),
(key_var, Var(Symbol::ARG_2)),
(val_var, EmptyRecord),
],
ret_var: dict_var,
};
defn(
symbol,
vec![(dict_var, Symbol::ARG_1), (key_var, Symbol::ARG_2)],
var_store,
body,
dict_var,
)
}
/// Set.remove : Set k, k -> Set k
fn set_remove(symbol: Symbol, var_store: &mut VarStore) -> Def {
dict_remove(symbol, var_store)
}
/// Set.remove : Set k, k -> Set k
fn set_contains(symbol: Symbol, var_store: &mut VarStore) -> Def {
dict_contains(symbol, var_store)
}
/// Set.walk : Set k, (k, accum -> accum), accum -> accum
fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
let dict_var = var_store.fresh();
let func_var = var_store.fresh();
let key_var = var_store.fresh();
let accum_var = var_store.fresh();
let wrapper_var = var_store.fresh();
let user_function = Box::new((
func_var,
no_region(Var(Symbol::ARG_2)),
var_store.fresh(),
accum_var,
));
let call_func = Call(
user_function,
vec![
(key_var, no_region(Var(Symbol::ARG_5))),
(accum_var, no_region(Var(Symbol::ARG_6))),
],
CalledVia::Space,
);
let wrapper = Closure {
function_type: wrapper_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: accum_var,
name: Symbol::SET_WALK_USER_FUNCTION,
recursive: Recursive::NotRecursive,
captured_symbols: vec![(Symbol::ARG_2, func_var)],
arguments: vec![
(key_var, no_region(Pattern::Identifier(Symbol::ARG_5))),
(Variable::EMPTY_RECORD, no_region(Pattern::Underscore)),
(accum_var, no_region(Pattern::Identifier(Symbol::ARG_6))),
],
loc_body: Box::new(no_region(call_func)),
};
let body = RunLowLevel {
op: LowLevel::DictWalk,
args: vec![
(dict_var, Var(Symbol::ARG_1)),
(wrapper_var, wrapper),
(accum_var, Var(Symbol::ARG_3)),
],
ret_var: accum_var,
};
defn(
symbol,
vec![
(dict_var, Symbol::ARG_1),
(func_var, Symbol::ARG_2),
(accum_var, Symbol::ARG_3),
],
var_store,
body,
accum_var,
) )
} }
@ -2340,6 +2837,263 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
let ret_var = var_store.fresh();
let func_var = var_store.fresh();
let result_var = var_store.fresh();
let mut branches = vec![];
{
let user_function = Box::new((
func_var,
no_region(Var(Symbol::ARG_2)),
var_store.fresh(),
var_store.fresh(),
));
let call_func = Call(
user_function,
vec![(var_store.fresh(), no_region(Var(Symbol::ARG_5)))],
CalledVia::Space,
);
let tag_name = TagName::Global("Ok".into());
// ok branch
let ok = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: tag_name.clone(),
arguments: vec![(var_store.fresh(), no_region(call_func))],
};
let pattern = Pattern::AppliedTag {
whole_var: result_var,
ext_var: var_store.fresh(),
tag_name,
arguments: vec![(
var_store.fresh(),
no_region(Pattern::Identifier(Symbol::ARG_5)),
)],
};
let branch = WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
};
branches.push(branch);
}
{
// err branch
let tag_name = TagName::Global("Err".into());
let err = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: tag_name.clone(),
arguments: vec![(var_store.fresh(), no_region(Var(Symbol::ARG_4)))],
};
let pattern = Pattern::AppliedTag {
whole_var: result_var,
ext_var: var_store.fresh(),
tag_name,
arguments: vec![(
var_store.fresh(),
no_region(Pattern::Identifier(Symbol::ARG_4)),
)],
};
let branch = WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
};
branches.push(branch);
}
let body = When {
cond_var: result_var,
expr_var: ret_var,
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
};
defn(
symbol,
vec![(result_var, Symbol::ARG_1), (func_var, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
}
fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
let ret_var = var_store.fresh();
let func_var = var_store.fresh();
let result_var = var_store.fresh();
let mut branches = vec![];
{
let user_function = Box::new((
func_var,
no_region(Var(Symbol::ARG_2)),
var_store.fresh(),
var_store.fresh(),
));
let call_func = Call(
user_function,
vec![(var_store.fresh(), no_region(Var(Symbol::ARG_5)))],
CalledVia::Space,
);
let tag_name = TagName::Global("Err".into());
// ok branch
let ok = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: tag_name.clone(),
arguments: vec![(var_store.fresh(), no_region(call_func))],
};
let pattern = Pattern::AppliedTag {
whole_var: result_var,
ext_var: var_store.fresh(),
tag_name,
arguments: vec![(
var_store.fresh(),
no_region(Pattern::Identifier(Symbol::ARG_5)),
)],
};
let branch = WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
};
branches.push(branch);
}
{
// err branch
let tag_name = TagName::Global("Ok".into());
let err = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: tag_name.clone(),
arguments: vec![(var_store.fresh(), no_region(Var(Symbol::ARG_4)))],
};
let pattern = Pattern::AppliedTag {
whole_var: result_var,
ext_var: var_store.fresh(),
tag_name,
arguments: vec![(
var_store.fresh(),
no_region(Pattern::Identifier(Symbol::ARG_4)),
)],
};
let branch = WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
};
branches.push(branch);
}
let body = When {
cond_var: result_var,
expr_var: ret_var,
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
};
defn(
symbol,
vec![(result_var, Symbol::ARG_1), (func_var, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
}
fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
let ret_var = var_store.fresh();
let result_var = var_store.fresh();
let mut branches = vec![];
{
// ok branch
let tag_name = TagName::Global("Ok".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
ext_var: var_store.fresh(),
tag_name,
arguments: vec![(ret_var, no_region(Pattern::Identifier(Symbol::ARG_3)))],
};
let branch = WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_3)),
guard: None,
};
branches.push(branch);
}
{
// err branch
let tag_name = TagName::Global("Err".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
ext_var: var_store.fresh(),
tag_name,
arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))],
};
let branch = WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_2)),
guard: None,
};
branches.push(branch);
}
let body = When {
cond_var: result_var,
expr_var: ret_var,
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
};
defn(
symbol,
vec![(result_var, Symbol::ARG_1), (ret_var, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
}
#[inline(always)] #[inline(always)]
fn no_region<T>(value: T) -> Located<T> { fn no_region<T>(value: T) -> Located<T> {
Located { Located {

View file

@ -1,6 +1,7 @@
use crate::procedure::References; use crate::procedure::References;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::ModuleName;
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -113,7 +114,7 @@ impl<'a> Env<'a> {
Ok(symbol) Ok(symbol)
} }
None => Err(RuntimeError::ValueNotExposed { None => Err(RuntimeError::ValueNotExposed {
module_name, module_name: ModuleName::from(module_name),
ident, ident,
region, region,
}), }),

View file

@ -1,4 +1,3 @@
use crate::builtins;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::env::Env; use crate::env::Env;
use crate::expr::{Expr, Output}; use crate::expr::{Expr, Output};
@ -41,7 +40,7 @@ pub struct ModuleOutput {
// TODO trim these down // TODO trim these down
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a>( pub fn canonicalize_module_defs<'a, F>(
arena: &Bump, arena: &Bump,
loc_defs: &'a [Located<ast::Def<'a>>], loc_defs: &'a [Located<ast::Def<'a>>],
home: ModuleId, home: ModuleId,
@ -52,7 +51,11 @@ pub fn canonicalize_module_defs<'a>(
exposed_imports: MutMap<Ident, (Symbol, Region)>, exposed_imports: MutMap<Ident, (Symbol, Region)>,
exposed_symbols: &MutSet<Symbol>, exposed_symbols: &MutSet<Symbol>,
var_store: &mut VarStore, var_store: &mut VarStore,
) -> Result<ModuleOutput, RuntimeError> { look_up_builtin: F,
) -> Result<ModuleOutput, RuntimeError>
where
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
{
let mut can_exposed_imports = MutMap::default(); let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home, var_store); let mut scope = Scope::new(home, var_store);
let num_deps = dep_idents.len(); let num_deps = dep_idents.len();
@ -284,7 +287,7 @@ pub fn canonicalize_module_defs<'a>(
for symbol in references.iter() { for symbol in references.iter() {
if symbol.is_builtin() { if symbol.is_builtin() {
// this can fail when the symbol is for builtin types, or has no implementation yet // this can fail when the symbol is for builtin types, or has no implementation yet
if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) { if let Some(def) = look_up_builtin(*symbol, var_store) {
declarations.push(Declaration::Builtin(def)); declarations.push(Declaration::Builtin(def));
} }
} }

View file

@ -10,7 +10,7 @@ use roc_collections::all::MutMap;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State}; use roc_parse::parser::{loc, Parser, State, SyntaxError};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -21,19 +21,22 @@ pub fn test_home() -> ModuleId {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, SyntaxError<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(
let state = State::new(input.trim().as_bytes(), Attempting::Module); arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -967,6 +967,21 @@ mod test_can {
} }
} }
#[test]
fn dict() {
let src = indoc!(
r#"
x = Dict.empty
Dict.len x
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
#[test] #[test]
fn unused_def_regression() { fn unused_def_regression() {
let src = indoc!( let src = indoc!(

View file

@ -13,7 +13,6 @@ roc_parse = { path = "../parse" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_uniq = { path = "../uniq" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -5,4 +5,3 @@ pub mod builtins;
pub mod expr; pub mod expr;
pub mod module; pub mod module;
pub mod pattern; pub mod pattern;
pub mod uniq;

View file

@ -26,7 +26,8 @@ pub fn constrain_module(
module: &ModuleOutput, module: &ModuleOutput,
home: ModuleId, home: ModuleId,
mode: Mode, mode: Mode,
var_store: &mut VarStore, // TODO remove parameter
_var_store: &mut VarStore,
) -> Constraint { ) -> Constraint {
use Mode::*; use Mode::*;
@ -40,7 +41,7 @@ pub fn constrain_module(
match mode { match mode {
Standard => constrain_decls(home, decls), Standard => constrain_decls(home, decls),
Uniqueness => crate::uniq::constrain_decls(home, decls, var_store), Uniqueness => constrain_decls(home, decls),
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -17,16 +17,16 @@ mod test_fmt {
use roc_parse::ast::{Attempting, Expr}; use roc_parse::ast::{Attempting, Expr};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
use roc_parse::module::{self, module_defs}; use roc_parse::module::{self, module_defs};
use roc_parse::parser::{Fail, Parser, State}; use roc_parse::parser::{Parser, State, SyntaxError};
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, Fail> { fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes(), Attempting::Module); let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc!(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc!(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr.value) .map(|(_, loc_expr, _)| loc_expr.value)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
fn expr_formats_to(input: &str, expected: &str) { fn expr_formats_to(input: &str, expected: &str) {
@ -55,14 +55,14 @@ mod test_fmt {
let src = src.trim_end(); let src = src.trim_end();
let expected = expected.trim_end(); let expected = expected.trim_end();
match module::header().parse(&arena, State::new(src.as_bytes(), Attempting::Module)) { match module::header().parse(&arena, State::new_in(&arena, src.as_bytes(), Attempting::Module)) {
Ok((actual, state)) => { Ok((_, actual, state)) => {
let mut buf = String::new_in(&arena); let mut buf = String::new_in(&arena);
fmt_module(&mut buf, &actual); fmt_module(&mut buf, &actual);
match module_defs().parse(&arena, state) { match module_defs().parse(&arena, state) {
Ok((loc_defs, _)) => { Ok((_, loc_defs, _)) => {
for loc_def in loc_defs { for loc_def in loc_defs {
fmt_def(&mut buf, arena.alloc(loc_def.value), 0); fmt_def(&mut buf, arena.alloc(loc_def.value), 0);
} }
@ -839,6 +839,7 @@ mod test_fmt {
} }
#[test] #[test]
#[ignore]
fn final_comment_in_empty_record_type_definition() { fn final_comment_in_empty_record_type_definition() {
expr_formats_to( expr_formats_to(
indoc!( indoc!(
@ -862,6 +863,7 @@ mod test_fmt {
} }
#[test] #[test]
#[ignore]
fn multiline_inside_empty_record_annotation() { fn multiline_inside_empty_record_annotation() {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
@ -1296,6 +1298,7 @@ mod test_fmt {
} }
#[test] #[test]
#[ignore]
fn empty_record_with_comment() { fn empty_record_with_comment() {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
@ -1306,6 +1309,7 @@ mod test_fmt {
} }
#[test] #[test]
#[ignore]
fn empty_record_with_newline() { fn empty_record_with_newline() {
expr_formats_to( expr_formats_to(
indoc!( indoc!(

View file

@ -13,7 +13,6 @@ roc_problem = { path = "../problem" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" } roc_constrain = { path = "../constrain" }
roc_uniq = { path = "../uniq" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }

View file

@ -0,0 +1,385 @@
use crate::debug_info_init;
use crate::llvm::build::{set_name, Env, FAST_CALL_CONV};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::{decrement_refcount_layout, increment_refcount_layout, Mode};
use inkwell::attributes::{Attribute, AttributeLoc};
/// Helpers for interacting with the zig that generates bitcode
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Layout, LayoutIds};
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> BasicValueEnum<'ctx> {
call_bitcode_fn_help(env, args, fn_name)
.try_as_basic_value()
.left()
.unwrap_or_else(|| {
panic!(
"LLVM error: Did not get return value from bitcode function {:?}",
fn_name
)
})
}
pub fn call_void_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> InstructionValue<'ctx> {
call_bitcode_fn_help(env, args, fn_name)
.try_as_basic_value()
.right()
.unwrap_or_else(|| panic!("LLVM error: Tried to call void bitcode function, but got return value from bitcode function, {:?}", fn_name))
}
fn call_bitcode_fn_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> CallSiteValue<'ctx> {
let fn_val = env
.module
.get_function(fn_name)
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", fn_name));
let call = env.builder.build_call(fn_val, args, "call_builtin");
call.set_call_convention(fn_val.get_call_conventions());
call
}
const ARGUMENT_SYMBOLS: [Symbol; 8] = [
Symbol::ARG_1,
Symbol::ARG_2,
Symbol::ARG_3,
Symbol::ARG_4,
Symbol::ARG_5,
Symbol::ARG_6,
Symbol::ARG_7,
Symbol::ARG_8,
];
pub fn build_transform_caller<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
function_layout: &Layout<'a>,
argument_layouts: &[Layout<'a>],
) -> FunctionValue<'ctx> {
let symbol = Symbol::ZIG_FUNCTION_CALLER;
let fn_name = layout_ids
.get(symbol, &function_layout)
.to_symbol_string(symbol, &env.interns);
match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => build_transform_caller_help(env, function_layout, argument_layouts, &fn_name),
}
}
fn build_transform_caller_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
function_layout: &Layout<'a>,
argument_layouts: &[Layout<'a>],
fn_name: &str,
) -> FunctionValue<'ctx> {
debug_assert!(argument_layouts.len() <= 7);
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.void_type().into(),
&(bumpalo::vec![ in env.arena; BasicTypeEnum::PointerType(arg_type); argument_layouts.len() + 2 ]),
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let closure_ptr = it.next().unwrap().into_pointer_value();
set_name(closure_ptr.into(), Symbol::ARG_1.ident_string(&env.interns));
let arguments =
bumpalo::collections::Vec::from_iter_in(it.take(argument_layouts.len()), env.arena);
for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS[1..].iter()) {
set_name(*argument, name.ident_string(&env.interns));
}
let closure_type =
basic_type_from_layout(env.arena, env.context, function_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let mut arguments_cast =
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
let basic_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let argument_cast = env
.builder
.build_bitcast(*argument_ptr, basic_type, "load_opaque")
.into_pointer_value();
let argument = env.builder.build_load(argument_cast, "load_opaque");
arguments_cast.push(argument);
}
let closure_cast = env
.builder
.build_bitcast(closure_ptr, closure_type, "load_opaque")
.into_pointer_value();
let fpointer = env.builder.build_load(closure_cast, "load_opaque");
let call = match function_layout {
Layout::FunctionPointer(_, _) => env.builder.build_call(
fpointer.into_pointer_value(),
arguments_cast.as_slice(),
"tmp",
),
Layout::Closure(_, _, _) | Layout::Struct(_) => {
let pair = fpointer.into_struct_value();
let fpointer = env
.builder
.build_extract_value(pair, 0, "get_fpointer")
.unwrap();
let closure_data = env
.builder
.build_extract_value(pair, 1, "get_closure_data")
.unwrap();
arguments_cast.push(closure_data);
env.builder.build_call(
fpointer.into_pointer_value(),
arguments_cast.as_slice(),
"tmp",
)
}
_ => unreachable!("layout is not callable {:?}", function_layout),
};
call.set_call_convention(FAST_CALL_CONV);
let result = call
.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
let result_u8_ptr = function_value
.get_nth_param(argument_layouts.len() as u32 + 1)
.unwrap();
let result_ptr = env
.builder
.build_bitcast(
result_u8_ptr,
result.get_type().ptr_type(AddressSpace::Generic),
"write_result",
)
.into_pointer_value();
env.builder.build_store(result_ptr, result);
env.builder.build_return(None);
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
pub fn build_inc_n_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
n: u64,
) -> FunctionValue<'ctx> {
build_rc_wrapper(env, layout_ids, layout, Mode::Inc(n))
}
pub fn build_inc_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
build_rc_wrapper(env, layout_ids, layout, Mode::Inc(1))
}
pub fn build_dec_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
build_rc_wrapper(env, layout_ids, layout, Mode::Dec)
}
pub fn build_rc_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
rc_operation: Mode,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_RC_REF;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = match rc_operation {
Mode::Inc(n) => format!("{}_inc_{}", fn_name, n),
Mode::Dec => format!("{}_dec", fn_name),
};
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.void_type().into(),
&[arg_type.into()],
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let value_ptr = it.next().unwrap().into_pointer_value();
set_name(value_ptr.into(), Symbol::ARG_1.ident_string(&env.interns));
let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let value_cast = env
.builder
.build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value();
let value = env.builder.build_load(value_cast, "load_opaque");
match rc_operation {
Mode::Inc(n) => {
increment_refcount_layout(env, function_value, layout_ids, n, value, layout);
}
Mode::Dec => {
decrement_refcount_layout(env, function_value, layout_ids, value, layout);
}
}
env.builder.build_return(None);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
pub fn build_eq_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_EQ_REF;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.bool_type().into(),
&[arg_type.into(), arg_type.into()],
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = env.context.create_enum_attribute(kind_id, 1);
function_value.add_attribute(AttributeLoc::Function, attr);
let entry = env.context.append_basic_block(function_value, "entry");
env.builder.position_at_end(entry);
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let value_ptr1 = it.next().unwrap().into_pointer_value();
let value_ptr2 = it.next().unwrap().into_pointer_value();
set_name(value_ptr1.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value_ptr2.into(), Symbol::ARG_2.ident_string(&env.interns));
let value_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let value_cast1 = env
.builder
.build_bitcast(value_ptr1, value_type, "load_opaque")
.into_pointer_value();
let value_cast2 = env
.builder
.build_bitcast(value_ptr2, value_type, "load_opaque")
.into_pointer_value();
let value1 = env.builder.build_load(value_cast1, "load_opaque");
let value2 = env.builder.build_load(value_cast2, "load_opaque");
let result =
crate::llvm::compare::generic_eq(env, layout_ids, value1, value2, layout, layout);
env.builder.build_return(Some(&result));
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,885 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::Env;
use crate::llvm::build::{cast_block_of_memory_to_tag, complex_bitcast, set_name, FAST_CALL_CONV};
use crate::llvm::build_str;
use crate::llvm::convert::basic_type_from_layout;
use bumpalo::collections::Vec;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
#[derive(Clone, Debug)]
enum WhenRecursive<'a> {
Unreachable,
Loop(UnionLayout<'a>),
}
pub fn generic_hash<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
seed: IntValue<'ctx>,
val: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) -> IntValue<'ctx> {
// NOTE: C and Zig use this value for their initial HashMap seed: 0xc70f6907
build_hash_layout(
env,
layout_ids,
seed,
val,
layout,
WhenRecursive::Unreachable,
)
}
fn build_hash_layout<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
seed: IntValue<'ctx>,
val: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
match layout {
Layout::Builtin(builtin) => {
hash_builtin(env, layout_ids, seed, val, layout, builtin, when_recursive)
}
Layout::Struct(fields) => build_hash_struct(
env,
layout_ids,
fields,
when_recursive,
seed,
val.into_struct_value(),
),
Layout::PhantomEmptyStruct => {
// just does nothing and returns the seed
seed
}
Layout::Union(union_layout) => {
build_hash_tag(env, layout_ids, layout, union_layout, seed, val)
}
Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be hashed directly")
}
WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(union_layout.clone());
let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env
.builder
.build_bitcast(val, bt, "i64_to_opaque")
.into_pointer_value();
build_hash_tag(
env,
layout_ids,
&layout,
&union_layout,
seed,
field_cast.into(),
)
}
},
Layout::Pointer(_) => {
unreachable!("unused")
}
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
unreachable!("the type system will guarantee these are never hashed")
}
}
}
fn append_hash_layout<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
seed: IntValue<'ctx>,
val: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
build_hash_layout(env, layout_ids, seed, val, layout, when_recursive)
}
fn hash_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
seed: IntValue<'ctx>,
val: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
builtin: &Builtin<'a>,
when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
let ptr_bytes = env.ptr_bytes;
match builtin {
Builtin::Int128
| Builtin::Int64
| Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Float64
| Builtin::Float32
| Builtin::Float128
| Builtin::Float16
| Builtin::Usize => {
let hash_bytes = store_and_use_as_u8_ptr(env, val, &layout);
hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
}
Builtin::Str => {
// let zig deal with big vs small string
call_bitcode_fn(
env,
&[seed.into(), build_str::str_to_i128(env, val).into()],
&bitcode::DICT_HASH_STR,
)
.into_int_value()
}
Builtin::EmptyStr | Builtin::EmptyDict | Builtin::EmptyList | Builtin::EmptySet => {
hash_empty_collection(seed)
}
Builtin::Dict(_, _) => {
todo!("Implement hash for Dict")
}
Builtin::Set(_) => {
todo!("Implement Hash for Set")
}
Builtin::List(_, element_layout) => build_hash_list(
env,
layout_ids,
layout,
element_layout,
when_recursive,
seed,
val.into_struct_value(),
),
}
}
fn build_hash_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
field_layouts: &'a [Layout<'a>],
when_recursive: WhenRecursive<'a>,
seed: IntValue<'ctx>,
value: StructValue<'ctx>,
) -> IntValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let struct_layout = Layout::Struct(field_layouts);
let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids
.get(symbol, &struct_layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arena = env.arena;
let seed_type = env.context.i64_type();
let arg_type =
basic_type_from_layout(arena, env.context, &struct_layout, env.ptr_bytes);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
seed_type.into(),
&[seed_type.into(), arg_type],
);
build_hash_struct_help(
env,
layout_ids,
function_value,
when_recursive,
field_layouts,
);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[seed.into(), value.into()], "struct_hash");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap().into_int_value()
}
fn build_hash_struct_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
when_recursive: WhenRecursive<'a>,
field_layouts: &[Layout<'a>],
) {
let ctx = env.context;
debug_info_init!(env, parent);
// Add args to scope
let mut it = parent.get_param_iter();
let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap().into_struct_value();
set_name(seed.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value.into(), Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
let result = hash_struct(env, layout_ids, seed, value, when_recursive, field_layouts);
env.builder.build_return(Some(&result));
}
fn hash_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mut seed: IntValue<'ctx>,
value: StructValue<'ctx>,
when_recursive: WhenRecursive<'a>,
field_layouts: &[Layout<'a>],
) -> IntValue<'ctx> {
let ptr_bytes = env.ptr_bytes;
let layout = Layout::Struct(field_layouts);
// Optimization: if the bit representation of equal values is the same
// just hash the bits. Caveat here is tags: e.g. `Nothing` in `Just a`
// contains garbage bits after the tag (currently)
if false {
// this is a struct of only basic types, so we can just hash its bits
let hash_bytes = store_and_use_as_u8_ptr(env, value.into(), &layout);
hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
} else {
for (index, field_layout) in field_layouts.iter().enumerate() {
let field = env
.builder
.build_extract_value(value, index as u32, "eq_field")
.unwrap();
if let Layout::RecursivePointer = field_layout {
match &when_recursive {
WhenRecursive::Unreachable => {
unreachable!("The current layout should not be recursive, but is")
}
WhenRecursive::Loop(union_layout) => {
let field_layout = Layout::Union(union_layout.clone());
let bt = basic_type_from_layout(
env.arena,
env.context,
&field_layout,
env.ptr_bytes,
);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env
.builder
.build_bitcast(field, bt, "i64_to_opaque")
.into_pointer_value();
seed = append_hash_layout(
env,
layout_ids,
seed,
field_cast.into(),
&field_layout,
when_recursive.clone(),
)
}
}
} else {
seed = append_hash_layout(
env,
layout_ids,
seed,
field,
field_layout,
when_recursive.clone(),
);
}
}
seed
}
}
fn build_hash_tag<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
union_layout: &UnionLayout<'a>,
seed: IntValue<'ctx>,
value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arena = env.arena;
let seed_type = env.context.i64_type();
let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
seed_type.into(),
&[seed_type.into(), arg_type],
);
build_hash_tag_help(env, layout_ids, function_value, union_layout);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[seed.into(), value], "struct_hash");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap().into_int_value()
}
fn build_hash_tag_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
union_layout: &UnionLayout<'a>,
) {
let ctx = env.context;
debug_info_init!(env, parent);
// Add args to scope
let mut it = parent.get_param_iter();
let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap();
set_name(seed.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value, Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
let result = hash_tag(env, layout_ids, parent, seed, value, union_layout);
env.builder.build_return(Some(&result));
}
fn hash_tag<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
seed: IntValue<'ctx>,
tag: BasicValueEnum<'ctx>,
union_layout: &UnionLayout<'a>,
) -> IntValue<'ctx> {
use UnionLayout::*;
let entry_block = env.builder.get_insert_block().unwrap();
let merge_block = env.context.append_basic_block(parent, "merge_block");
env.builder.position_at_end(merge_block);
let merge_phi = env.builder.build_phi(env.context.i64_type(), "merge_hash");
env.builder.position_at_end(entry_block);
match union_layout {
NonRecursive(tags) => {
// SAFETY we know that non-recursive tags cannot be NULL
let tag_id = nonrec_tag_id(env, tag.into_struct_value());
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
for (tag_id, field_layouts) in tags.iter().enumerate() {
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
// TODO drop tag id?
let struct_layout = Layout::Struct(field_layouts);
let wrapper_type =
basic_type_from_layout(env.arena, env.context, &struct_layout, env.ptr_bytes);
debug_assert!(wrapper_type.is_struct_type());
let as_struct =
cast_block_of_memory_to_tag(env.builder, tag.into_struct_value(), wrapper_type);
let answer = build_hash_struct(
env,
layout_ids,
field_layouts,
WhenRecursive::Unreachable,
seed,
as_struct,
);
merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block);
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
}
env.builder.position_at_end(entry_block);
let default = cases.pop().unwrap().1;
env.builder.build_switch(tag_id, default, &cases);
}
Recursive(tags) => {
// SAFETY recursive tag unions are not NULL
let tag_id = unsafe { rec_tag_id_unsafe(env, tag.into_pointer_value()) };
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
for (tag_id, field_layouts) in tags.iter().enumerate() {
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let answer = hash_ptr_to_struct(
env,
layout_ids,
union_layout,
field_layouts,
seed,
tag.into_pointer_value(),
);
merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block);
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
}
env.builder.position_at_end(entry_block);
let default = cases.pop().unwrap().1;
env.builder.build_switch(tag_id, default, &cases);
}
NullableUnwrapped { other_fields, .. } => {
let tag = tag.into_pointer_value();
let other_fields = &other_fields[1..];
let is_null = env.builder.build_is_null(tag, "is_null");
let hash_null_block = env.context.append_basic_block(parent, "hash_null_block");
let hash_other_block = env.context.append_basic_block(parent, "hash_other_block");
env.builder
.build_conditional_branch(is_null, hash_null_block, hash_other_block);
{
env.builder.position_at_end(hash_null_block);
let answer = hash_null(seed);
merge_phi.add_incoming(&[(&answer, hash_null_block)]);
env.builder.build_unconditional_branch(merge_block);
}
{
env.builder.position_at_end(hash_other_block);
let answer =
hash_ptr_to_struct(env, layout_ids, union_layout, other_fields, seed, tag);
merge_phi.add_incoming(&[(&answer, hash_other_block)]);
env.builder.build_unconditional_branch(merge_block);
}
}
NullableWrapped { other_tags, .. } => {
let tag = tag.into_pointer_value();
let is_null = env.builder.build_is_null(tag, "is_null");
let hash_null_block = env.context.append_basic_block(parent, "hash_null_block");
let hash_other_block = env.context.append_basic_block(parent, "hash_other_block");
env.builder
.build_conditional_branch(is_null, hash_null_block, hash_other_block);
{
env.builder.position_at_end(hash_null_block);
let answer = hash_null(seed);
merge_phi.add_incoming(&[(&answer, hash_null_block)]);
env.builder.build_unconditional_branch(merge_block);
}
{
env.builder.position_at_end(hash_other_block);
// SAFETY recursive tag unions are not NULL
let tag_id = unsafe { rec_tag_id_unsafe(env, tag) };
let mut cases = Vec::with_capacity_in(other_tags.len(), env.arena);
for (tag_id, field_layouts) in other_tags.iter().enumerate() {
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let answer =
hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block);
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
}
env.builder.position_at_end(hash_other_block);
let default = cases.pop().unwrap().1;
env.builder.build_switch(tag_id, default, &cases);
}
}
NonNullableUnwrapped(field_layouts) => {
let answer = hash_ptr_to_struct(
env,
layout_ids,
union_layout,
field_layouts,
seed,
tag.into_pointer_value(),
);
merge_phi.add_incoming(&[(&answer, entry_block)]);
env.builder.build_unconditional_branch(merge_block);
}
}
env.builder.position_at_end(merge_block);
merge_phi.as_basic_value().into_int_value()
}
fn build_hash_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
element_layout: &Layout<'a>,
when_recursive: WhenRecursive<'a>,
seed: IntValue<'ctx>,
value: StructValue<'ctx>,
) -> IntValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arena = env.arena;
let seed_type = env.context.i64_type();
let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
seed_type.into(),
&[seed_type.into(), arg_type],
);
build_hash_list_help(
env,
layout_ids,
function_value,
when_recursive,
element_layout,
);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[seed.into(), value.into()], "struct_hash");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap().into_int_value()
}
fn build_hash_list_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
when_recursive: WhenRecursive<'a>,
element_layout: &Layout<'a>,
) {
let ctx = env.context;
debug_info_init!(env, parent);
// Add args to scope
let mut it = parent.get_param_iter();
let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap().into_struct_value();
set_name(seed.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value.into(), Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
let result = hash_list(
env,
layout_ids,
parent,
seed,
value,
when_recursive,
element_layout,
);
env.builder.build_return(Some(&result));
}
fn hash_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
seed: IntValue<'ctx>,
value: StructValue<'ctx>,
when_recursive: WhenRecursive<'a>,
element_layout: &Layout<'a>,
) -> IntValue<'ctx> {
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
use inkwell::types::BasicType;
// hash of a list is the hash of its elements
let done_block = env.context.append_basic_block(parent, "done");
let loop_block = env.context.append_basic_block(parent, "loop");
let element_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes);
let ptr_type = element_type.ptr_type(inkwell::AddressSpace::Generic);
let (length, ptr) = load_list(env.builder, value, ptr_type);
let result = env.builder.build_alloca(env.context.i64_type(), "result");
env.builder.build_store(result, seed);
let is_empty = env.builder.build_int_compare(
inkwell::IntPredicate::EQ,
length,
env.ptr_int().const_zero(),
"is_empty",
);
env.builder
.build_conditional_branch(is_empty, done_block, loop_block);
env.builder.position_at_end(loop_block);
let loop_fn = |_index, element| {
let seed = env
.builder
.build_load(result, "load_current")
.into_int_value();
let answer = append_hash_layout(
env,
layout_ids,
seed,
element,
element_layout,
when_recursive.clone(),
);
env.builder.build_store(result, answer);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
length,
"current_index",
loop_fn,
);
env.builder.build_unconditional_branch(done_block);
env.builder.position_at_end(done_block);
env.builder
.build_load(result, "load_current")
.into_int_value()
}
fn hash_null(seed: IntValue<'_>) -> IntValue<'_> {
seed
}
fn hash_empty_collection(seed: IntValue<'_>) -> IntValue<'_> {
seed
}
fn hash_ptr_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
union_layout: &UnionLayout<'a>,
field_layouts: &'a [Layout<'a>],
seed: IntValue<'ctx>,
tag: PointerValue<'ctx>,
) -> IntValue<'ctx> {
use inkwell::types::BasicType;
let struct_layout = Layout::Struct(field_layouts);
let wrapper_type =
basic_type_from_layout(env.arena, env.context, &struct_layout, env.ptr_bytes);
debug_assert!(wrapper_type.is_struct_type());
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = env
.builder
.build_bitcast(
tag,
wrapper_type.ptr_type(inkwell::AddressSpace::Generic),
"opaque_to_correct",
)
.into_pointer_value();
let struct_value = env
.builder
.build_load(struct_ptr, "load_struct1")
.into_struct_value();
build_hash_struct(
env,
layout_ids,
field_layouts,
WhenRecursive::Loop(union_layout.clone()),
seed,
struct_value,
)
}
fn store_and_use_as_u8_ptr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) -> PointerValue<'ctx> {
let basic_type = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
let alloc = env.builder.build_alloca(basic_type, "store");
env.builder.build_store(alloc, value);
env.builder
.build_bitcast(
alloc,
env.context
.i8_type()
.ptr_type(inkwell::AddressSpace::Generic),
"as_u8_ptr",
)
.into_pointer_value()
}
fn hash_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
seed: IntValue<'ctx>,
buffer: PointerValue<'ctx>,
width: u32,
) -> IntValue<'ctx> {
let num_bytes = env.context.i64_type().const_int(width as u64, false);
call_bitcode_fn(
env,
&[seed.into(), buffer.into(), num_bytes.into()],
&bitcode::DICT_HASH,
)
.into_int_value()
}
fn nonrec_tag_id<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
tag: StructValue<'ctx>,
) -> IntValue<'ctx> {
complex_bitcast(
env.builder,
tag.into(),
env.context.i64_type().into(),
"load_tag_id",
)
.into_int_value()
}
unsafe fn rec_tag_id_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
tag: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let ptr = env
.builder
.build_bitcast(
tag,
env.context
.i64_type()
.ptr_type(inkwell::AddressSpace::Generic),
"cast_for_tag_id",
)
.into_pointer_value();
env.builder.build_load(ptr, "load_tag_id").into_int_value()
}

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,12 @@
use crate::llvm::build::{ use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
call_bitcode_fn, call_void_bitcode_fn, complex_bitcast, Env, InPlace, Scope, use crate::llvm::build::{complex_bitcast, Env, InPlace, Scope};
};
use crate::llvm::build_list::{ use crate::llvm::build_list::{
allocate_list, build_basic_phi2, empty_polymorphic_list, list_len, load_list_ptr, store_list, allocate_list, build_basic_phi2, empty_polymorphic_list, list_len, load_list_ptr, store_list,
}; };
use crate::llvm::convert::{collection, get_ptr_type}; use crate::llvm::convert::{collection, get_ptr_type};
use inkwell::builder::Builder; use inkwell::builder::Builder;
use inkwell::types::{BasicTypeEnum, StructType}; use inkwell::types::{BasicTypeEnum, StructType};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, StructValue}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate}; use inkwell::{AddressSpace, IntPredicate};
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -71,7 +70,7 @@ fn str_symbol_to_i128<'a, 'ctx, 'env>(
complex_bitcast(&env.builder, string, i128_type, "str_to_i128").into_int_value() complex_bitcast(&env.builder, string, i128_type, "str_to_i128").into_int_value()
} }
fn str_to_i128<'a, 'ctx, 'env>( pub fn str_to_i128<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
@ -128,6 +127,24 @@ fn zig_str_to_struct<'a, 'ctx, 'env>(
builder.build_load(ptr4, "load").into_struct_value() builder.build_load(ptr4, "load").into_struct_value()
} }
pub fn destructure<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> (PointerValue<'ctx>, IntValue<'ctx>) {
let length = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len")
.unwrap()
.into_int_value();
// a `*mut u8` pointer
let generic_ptr = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap()
.into_pointer_value();
(generic_ptr, length)
}
/// Str.concat : Str, Str -> Str /// Str.concat : Str, Str -> Str
pub fn str_concat<'a, 'ctx, 'env>( pub fn str_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -382,6 +399,19 @@ fn build_struct<'env, 'ctx>(
val.into_struct_value() val.into_struct_value()
} }
/// Str.fromInt : Int -> Str
pub fn str_from_float<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
int_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let float = load_symbol(scope, &int_symbol);
let zig_result = call_bitcode_fn(env, &[float], &bitcode::STR_FROM_FLOAT).into_struct_value();
zig_str_to_struct(env, zig_result).into()
}
/// Str.equal : Str, Str -> Bool /// Str.equal : Str, Str -> Bool
pub fn str_equal<'a, 'ctx, 'env>( pub fn str_equal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@ use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::types::BasicTypeEnum::{self, *}; use inkwell::types::BasicTypeEnum::{self, *};
use inkwell::types::{ArrayType, BasicType, FunctionType, IntType, PointerType, StructType}; use inkwell::types::{ArrayType, BasicType, FunctionType, IntType, PointerType, StructType};
use inkwell::values::BasicValueEnum;
use inkwell::AddressSpace; use inkwell::AddressSpace;
use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::layout::{Builtin, Layout, UnionLayout};
@ -48,6 +49,18 @@ pub fn get_array_type<'ctx>(bt_enum: &BasicTypeEnum<'ctx>, size: u32) -> ArrayTy
} }
} }
/// TODO could this be added to Inkwell itself as a method on BasicValueEnum?
pub fn as_const_zero<'ctx>(bt_enum: &BasicTypeEnum<'ctx>) -> BasicValueEnum<'ctx> {
match bt_enum {
ArrayType(typ) => typ.const_zero().into(),
IntType(typ) => typ.const_zero().into(),
FloatType(typ) => typ.const_zero().into(),
PointerType(typ) => typ.const_zero().into(),
StructType(typ) => typ.const_zero().into(),
VectorType(typ) => typ.const_zero().into(),
}
}
fn basic_type_from_function_layout<'ctx>( fn basic_type_from_function_layout<'ctx>(
arena: &Bump, arena: &Bump,
context: &'ctx Context, context: &'ctx Context,
@ -185,7 +198,7 @@ pub fn basic_type_from_builtin<'ctx>(
Float64 => context.f64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(),
Float32 => context.f32_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(),
Float16 => context.f16_type().as_basic_type_enum(), Float16 => context.f16_type().as_basic_type_enum(),
Dict(_, _) | EmptyDict => panic!("TODO layout_to_basic_type for Builtin::Dict"), Dict(_, _) | EmptyDict => dict(context, ptr_bytes).into(),
Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"), Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"),
List(_, _) | Str | EmptyStr => collection(context, ptr_bytes).into(), List(_, _) | Str | EmptyStr => collection(context, ptr_bytes).into(),
EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)), EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)),
@ -260,6 +273,20 @@ pub fn collection(ctx: &Context, ptr_bytes: u32) -> StructType<'_> {
ctx.struct_type(&[u8_ptr.into(), usize_type.into()], false) ctx.struct_type(&[u8_ptr.into(), usize_type.into()], false)
} }
pub fn dict(ctx: &Context, ptr_bytes: u32) -> StructType<'_> {
let usize_type = ptr_int(ctx, ptr_bytes);
let u8_ptr = ctx.i8_type().ptr_type(AddressSpace::Generic);
ctx.struct_type(
&[u8_ptr.into(), usize_type.into(), usize_type.into()],
false,
)
}
pub fn dict_ptr(ctx: &Context, ptr_bytes: u32) -> PointerType<'_> {
dict(ctx, ptr_bytes).ptr_type(AddressSpace::Generic)
}
pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> { pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {
match ptr_bytes { match ptr_bytes {
1 => ctx.i8_type(), 1 => ctx.i8_type(),

View file

@ -1,4 +1,7 @@
pub mod bitcode;
pub mod build; pub mod build;
pub mod build_dict;
pub mod build_hash;
pub mod build_list; pub mod build_list;
pub mod build_str; pub mod build_str;
pub mod compare; pub mod compare;

View file

@ -1,3 +1,4 @@
use crate::debug_info_init;
use crate::llvm::build::{ use crate::llvm::build::{
cast_basic_basic, cast_block_of_memory_to_tag, set_name, Env, FAST_CALL_CONV, cast_basic_basic, cast_block_of_memory_to_tag, set_name, Env, FAST_CALL_CONV,
LLVM_SADD_WITH_OVERFLOW_I64, LLVM_SADD_WITH_OVERFLOW_I64,
@ -8,11 +9,11 @@ use crate::llvm::convert::{
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::debug_info::AsDIScope;
use inkwell::module::Linkage; use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum}; use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate}; use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode, UnionLayout}; use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode, UnionLayout};
@ -192,23 +193,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let entry = ctx.append_basic_block(parent, "entry"); let entry = ctx.append_basic_block(parent, "entry");
builder.position_at_end(entry); builder.position_at_end(entry);
let subprogram = parent.get_subprogram().unwrap(); debug_info_init!(env, parent);
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ subprogram.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
&ctx,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
env.builder.set_current_debug_location(&ctx, loc);
let refcount_ptr = { let refcount_ptr = {
let raw_refcount_ptr = parent.get_nth_param(0).unwrap(); let raw_refcount_ptr = parent.get_nth_param(0).unwrap();
@ -300,6 +285,7 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
layouts: &[Layout<'a>], layouts: &[Layout<'a>],
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>,
) { ) {
let wrapper_struct = value.into_struct_value(); let wrapper_struct = value.into_struct_value();
@ -310,7 +296,15 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") .build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
.unwrap(); .unwrap();
modify_refcount_layout(env, parent, layout_ids, mode, field_ptr, field_layout); modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_ptr,
field_layout,
);
} }
} }
} }
@ -345,9 +339,9 @@ pub fn decrement_refcount_layout<'a, 'ctx, 'env>(
fn modify_refcount_builtin<'a, 'ctx, 'env>( fn modify_refcount_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
builtin: &Builtin<'a>, builtin: &Builtin<'a>,
@ -357,30 +351,17 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
match builtin { match builtin {
List(memory_mode, element_layout) => { List(memory_mode, element_layout) => {
let wrapper_struct = value.into_struct_value(); let wrapper_struct = value.into_struct_value();
if element_layout.contains_refcounted() {
let ptr_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, wrapper_struct, ptr_type);
let loop_fn = |_index, element| {
modify_refcount_layout(env, parent, layout_ids, mode, element, element_layout);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"modify_rc_index",
loop_fn,
);
}
if let MemoryMode::Refcounted = memory_mode { if let MemoryMode::Refcounted = memory_mode {
modify_refcount_list(env, layout_ids, mode, layout, wrapper_struct); modify_refcount_list(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
wrapper_struct,
);
} }
} }
Set(element_layout) => { Set(element_layout) => {
@ -390,12 +371,19 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
todo!(); todo!();
} }
Dict(key_layout, value_layout) => { Dict(key_layout, value_layout) => {
if key_layout.contains_refcounted() || value_layout.contains_refcounted() { let wrapper_struct = value.into_struct_value();
// TODO decrement all values modify_refcount_dict(
} env,
layout_ids,
todo!(); mode,
when_recursive,
layout,
key_layout,
value_layout,
wrapper_struct,
);
} }
Str => { Str => {
let wrapper_struct = value.into_struct_value(); let wrapper_struct = value.into_struct_value();
modify_refcount_str(env, layout_ids, mode, layout, wrapper_struct); modify_refcount_str(env, layout_ids, mode, layout, wrapper_struct);
@ -413,13 +401,45 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
mode: Mode, mode: Mode,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
) {
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
&WhenRecursive::Unreachable,
value,
layout,
);
}
#[derive(Clone, Debug, PartialEq)]
enum WhenRecursive<'a> {
Unreachable,
Loop(UnionLayout<'a>),
}
fn modify_refcount_layout_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
value: BasicValueEnum<'ctx>,
layout: &Layout<'a>,
) { ) {
use Layout::*; use Layout::*;
match layout { match layout {
Builtin(builtin) => { Builtin(builtin) => modify_refcount_builtin(
modify_refcount_builtin(env, parent, layout_ids, mode, value, layout, builtin) env,
} layout_ids,
mode,
when_recursive,
value,
layout,
builtin,
),
Union(variant) => { Union(variant) => {
use UnionLayout::*; use UnionLayout::*;
@ -434,6 +454,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(variant.clone()),
tags, tags,
value.into_pointer_value(), value.into_pointer_value(),
true, true,
@ -449,6 +470,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(variant.clone()),
&*env.arena.alloc([other_fields]), &*env.arena.alloc([other_fields]),
value.into_pointer_value(), value.into_pointer_value(),
true, true,
@ -462,6 +484,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(variant.clone()),
&*env.arena.alloc([*fields]), &*env.arena.alloc([*fields]),
value.into_pointer_value(), value.into_pointer_value(),
true, true,
@ -474,13 +497,16 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(variant.clone()),
tags, tags,
value.into_pointer_value(), value.into_pointer_value(),
false, false,
); );
} }
NonRecursive(tags) => modify_refcount_union(env, layout_ids, mode, tags, value), NonRecursive(tags) => {
modify_refcount_union(env, layout_ids, mode, when_recursive, tags, value)
}
} }
} }
Closure(_, closure_layout, _) => { Closure(_, closure_layout, _) => {
@ -489,14 +515,15 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
let field_ptr = env let field_ptr = env
.builder .builder
.build_extract_value(wrapper_struct, 1, "increment_closure_data") .build_extract_value(wrapper_struct, 1, "modify_rc_closure_data")
.unwrap(); .unwrap();
modify_refcount_layout( modify_refcount_layout_help(
env, env,
parent, parent,
layout_ids, layout_ids,
mode, mode,
when_recursive,
field_ptr, field_ptr,
&closure_layout.as_block_of_memory_layout(), &closure_layout.as_block_of_memory_layout(),
) )
@ -504,12 +531,45 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
} }
Struct(layouts) => { Struct(layouts) => {
modify_refcount_struct(env, parent, layout_ids, value, layouts, mode); modify_refcount_struct(
env,
parent,
layout_ids,
value,
layouts,
mode,
when_recursive,
);
} }
PhantomEmptyStruct => {} PhantomEmptyStruct => {}
RecursivePointer => todo!("TODO implement decrement layout of recursive tag union"), Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be hashed directly")
}
WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(union_layout.clone());
let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
// cast the i64 pointer to a pointer to block of memory
let field_cast = env
.builder
.build_bitcast(value, bt, "i64_to_opaque")
.into_pointer_value();
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_cast.into(),
&layout,
)
}
},
FunctionPointer(_, _) | Pointer(_) => {} FunctionPointer(_, _) | Pointer(_) => {}
} }
@ -519,20 +579,22 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>, layout: &Layout<'a>,
element_layout: &Layout<'a>,
original_wrapper: StructValue<'ctx>, original_wrapper: StructValue<'ctx>,
) { ) {
let block = env.builder.get_insert_block().expect("to be in a function"); let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap(); let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode { let (call_name, fn_name) = function_name_from_mode(
Mode::Inc(_) => ("increment_list", Symbol::INC), layout_ids,
Mode::Dec => ("decrement_list", Symbol::DEC), &env.interns,
}; "increment_list",
"decrement_list",
let fn_name = layout_ids &layout,
.get(symbol, &layout) mode,
.to_symbol_string(symbol, &env.interns); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
@ -540,7 +602,15 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
let basic_type = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes); let basic_type = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
let function_value = build_header(env, basic_type, mode, &fn_name); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_list_help(env, mode, layout, function_value); modify_refcount_list_help(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
function_value,
);
function_value function_value
} }
@ -562,8 +632,11 @@ fn mode_to_call_mode(function: FunctionValue<'_>, mode: Mode) -> CallMode<'_> {
fn modify_refcount_list_help<'a, 'ctx, 'env>( fn modify_refcount_list_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>, layout: &Layout<'a>,
element_layout: &Layout<'a>,
fn_val: FunctionValue<'ctx>, fn_val: FunctionValue<'ctx>,
) { ) {
let builder = env.builder; let builder = env.builder;
@ -574,22 +647,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
builder.position_at_end(entry); builder.position_at_end(entry);
let func_scope = fn_val.get_subprogram().unwrap(); debug_info_init!(env, fn_val);
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
ctx,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&ctx, loc);
// Add args to scope // Add args to scope
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
@ -617,6 +675,36 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
builder.position_at_end(modification_block); builder.position_at_end(modification_block);
if element_layout.contains_refcounted() {
let ptr_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type);
let loop_fn = |_index, element| {
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
element,
element_layout,
);
};
incrementing_elem_loop(
env.builder,
env.context,
parent,
ptr,
len,
"modify_rc_index",
loop_fn,
);
}
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper); let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
let call_mode = mode_to_call_mode(fn_val, mode); let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env); refcount_ptr.modify(call_mode, layout, env);
@ -639,14 +727,14 @@ fn modify_refcount_str<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function"); let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap(); let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode { let (call_name, fn_name) = function_name_from_mode(
Mode::Inc(_) => ("increment_str", Symbol::INC), layout_ids,
Mode::Dec => ("decrement_str", Symbol::DEC), &env.interns,
}; "increment_str",
"decrement_str",
let fn_name = layout_ids &layout,
.get(symbol, &layout) mode,
.to_symbol_string(symbol, &env.interns); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
@ -681,22 +769,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
builder.position_at_end(entry); builder.position_at_end(entry);
let func_scope = fn_val.get_subprogram().unwrap(); debug_info_init!(env, fn_val);
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
ctx,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&ctx, loc);
// Add args to scope // Add args to scope
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
@ -740,6 +813,142 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
builder.build_return(None); builder.build_return(None);
} }
#[allow(clippy::too_many_arguments)]
fn modify_refcount_dict<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
original_wrapper: StructValue<'ctx>,
) {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_dict",
"decrement_dict",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let basic_type = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_dict_help(
env,
layout_ids,
mode,
when_recursive,
layout,
key_layout,
value_layout,
function_value,
);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
call_help(env, function, mode, original_wrapper.into(), call_name);
}
#[allow(clippy::too_many_arguments)]
fn modify_refcount_dict_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
fn_val: FunctionValue<'ctx>,
) {
debug_assert_eq!(
when_recursive,
&WhenRecursive::Unreachable,
"TODO pipe when_recursive through the dict key/value inc/dec"
);
let builder = env.builder;
let ctx = env.context;
// Add a basic block for the entry point
let entry = ctx.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
debug_info_init!(env, fn_val);
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns));
let parent = fn_val;
let wrapper_struct = arg_val.into_struct_value();
let len = builder
.build_extract_value(wrapper_struct, 1, "read_dict_len")
.unwrap()
.into_int_value();
// the block we'll always jump to when we're done
let cont_block = ctx.append_basic_block(parent, "modify_rc_dict_cont");
let modification_block = ctx.append_basic_block(parent, "modify_rc");
let is_non_empty = builder.build_int_compare(
IntPredicate::SGT,
len,
ptr_int(ctx, env.ptr_bytes).const_zero(),
"is_non_empty",
);
builder.build_conditional_branch(is_non_empty, modification_block, cont_block);
builder.position_at_end(modification_block);
if key_layout.contains_refcounted() || value_layout.contains_refcounted() {
crate::llvm::build_dict::dict_elements_rc(
env,
layout_ids,
wrapper_struct.into(),
key_layout,
value_layout,
mode,
);
}
let data_ptr = env
.builder
.build_extract_value(wrapper_struct, 0, "get_data_ptr")
.unwrap()
.into_pointer_value();
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, data_ptr);
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env);
builder.build_unconditional_branch(cont_block);
builder.position_at_end(cont_block);
// this function returns void
builder.build_return(None);
}
/// Build an increment or decrement function for a specific layout /// Build an increment or decrement function for a specific layout
fn build_header<'a, 'ctx, 'env>( fn build_header<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -793,7 +1002,7 @@ pub fn build_header_help<'a, 'ctx, 'env>(
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum Mode { pub enum Mode {
Inc(u64), Inc(u64),
Dec, Dec,
} }
@ -808,20 +1017,21 @@ fn build_rec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [Layout<'a>]], fields: &'a [&'a [Layout<'a>]],
value: PointerValue<'ctx>, value: PointerValue<'ctx>,
is_nullable: bool, is_nullable: bool,
) { ) {
let layout = Layout::Union(UnionLayout::Recursive(fields)); let layout = Layout::Union(UnionLayout::Recursive(fields));
let (call_name, symbol) = match mode { let (call_name, fn_name) = function_name_from_mode(
Mode::Inc(_) => ("increment_rec_union", Symbol::INC), layout_ids,
Mode::Dec => ("decrement_rec_union", Symbol::DEC), &env.interns,
}; "increment_rec_union",
"decrement_rec_union",
let fn_name = layout_ids &layout,
.get(symbol, &layout) mode,
.to_symbol_string(symbol, &env.interns); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
@ -834,7 +1044,15 @@ fn build_rec_union<'a, 'ctx, 'env>(
.into(); .into();
let function_value = build_header(env, basic_type, mode, &fn_name); let function_value = build_header(env, basic_type, mode, &fn_name);
build_rec_union_help(env, layout_ids, mode, fields, function_value, is_nullable); build_rec_union_help(
env,
layout_ids,
mode,
when_recursive,
fields,
function_value,
is_nullable,
);
env.builder.position_at_end(block); env.builder.position_at_end(block);
env.builder env.builder
@ -851,6 +1069,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>,
tags: &[&[Layout<'a>]], tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>, fn_val: FunctionValue<'ctx>,
is_nullable: bool, is_nullable: bool,
@ -867,22 +1086,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
builder.position_at_end(entry); builder.position_at_end(entry);
let func_scope = fn_val.get_subprogram().unwrap(); debug_info_init!(env, fn_val);
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
context,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&context, loc);
// Add args to scope // Add args to scope
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
@ -938,8 +1142,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
// next, make a jump table for all possible values of the tag_id // next, make a jump table for all possible values of the tag_id
let mut cases = Vec::with_capacity_in(tags.len(), env.arena); let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
builder.set_current_debug_location(&context, loc);
for (tag_id, field_layouts) in tags.iter().enumerate() { for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on // if none of the fields are or contain anything refcounted, just move on
if !field_layouts if !field_layouts
@ -1024,7 +1226,15 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
refcount_ptr.modify(call_mode, &layout, env); refcount_ptr.modify(call_mode, &layout, env);
for (field, field_layout) in deferred_nonrec { for (field, field_layout) in deferred_nonrec {
modify_refcount_layout(env, parent, layout_ids, mode, field, field_layout); modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field,
field_layout,
);
} }
let call_name = pick("recursive_tag_increment", "recursive_tag_decrement"); let call_name = pick("recursive_tag_increment", "recursive_tag_decrement");
@ -1115,10 +1325,31 @@ fn call_help<'a, 'ctx, 'env>(
call call
} }
fn function_name_from_mode<'a>(
layout_ids: &mut LayoutIds<'a>,
interns: &Interns,
if_inc: &'static str,
if_dec: &'static str,
layout: &Layout<'a>,
mode: Mode,
) -> (&'static str, String) {
// NOTE this is not a typo, we always determine the layout ID
// using the DEC symbol. Anything that is incrementing must also be
// decremented, so `dec` is used on more layouts. That can cause the
// layout ids of the inc and dec versions to be different, which is
// rather confusing, so now `inc_x` always corresponds to `dec_x`
let layout_id = layout_ids.get(Symbol::DEC, layout);
match mode {
Mode::Inc(_) => (if_inc, layout_id.to_symbol_string(Symbol::INC, interns)),
Mode::Dec => (if_dec, layout_id.to_symbol_string(Symbol::DEC, interns)),
}
}
fn modify_refcount_union<'a, 'ctx, 'env>( fn modify_refcount_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [Layout<'a>]], fields: &'a [&'a [Layout<'a>]],
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) { ) {
@ -1127,14 +1358,14 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function"); let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap(); let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode { let (call_name, fn_name) = function_name_from_mode(
Mode::Inc(_) => ("increment_union", Symbol::INC), layout_ids,
Mode::Dec => ("decrement_union", Symbol::DEC), &env.interns,
}; "increment_union",
"decrement_union",
let fn_name = layout_ids &layout,
.get(symbol, &layout) mode,
.to_symbol_string(symbol, &env.interns); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
@ -1142,7 +1373,14 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let basic_type = block_of_memory(env.context, &layout, env.ptr_bytes); let basic_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let function_value = build_header(env, basic_type, mode, &fn_name); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_union_help(env, layout_ids, mode, fields, function_value); modify_refcount_union_help(
env,
layout_ids,
mode,
when_recursive,
fields,
function_value,
);
function_value function_value
} }
@ -1159,6 +1397,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>,
tags: &[&[Layout<'a>]], tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>, fn_val: FunctionValue<'ctx>,
) { ) {
@ -1172,22 +1411,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
builder.position_at_end(entry); builder.position_at_end(entry);
let func_scope = fn_val.get_subprogram().unwrap(); debug_info_init!(env, fn_val);
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
context,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&context, loc);
// Add args to scope // Add args to scope
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
@ -1258,7 +1482,15 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
.build_extract_value(wrapper_struct, i as u32, "modify_tag_field") .build_extract_value(wrapper_struct, i as u32, "modify_tag_field")
.unwrap(); .unwrap();
modify_refcount_layout(env, parent, layout_ids, mode, field_ptr, field_layout); modify_refcount_layout_help(
env,
parent,
layout_ids,
mode,
when_recursive,
field_ptr,
field_layout,
);
} }
} }

View file

@ -0,0 +1,455 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_num {
#[test]
fn eq_i64() {
assert_evals_to!(
indoc!(
r#"
i : I64
i = 1
i == i
"#
),
true,
bool
);
}
#[test]
fn neq_i64() {
assert_evals_to!(
indoc!(
r#"
i : I64
i = 1
i != i
"#
),
false,
bool
);
}
#[test]
fn eq_u64() {
assert_evals_to!(
indoc!(
r#"
i : U64
i = 1
i == i
"#
),
true,
bool
);
}
#[test]
fn neq_u64() {
assert_evals_to!(
indoc!(
r#"
i : U64
i = 1
i != i
"#
),
false,
bool
);
}
#[test]
fn eq_f64() {
assert_evals_to!(
indoc!(
r#"
i : F64
i = 1
i == i
"#
),
true,
bool
);
}
#[test]
fn neq_f64() {
assert_evals_to!(
indoc!(
r#"
i : F64
i = 1
i != i
"#
),
false,
bool
);
}
#[test]
fn eq_bool_tag() {
assert_evals_to!(
indoc!(
r#"
true : Bool
true = True
true == True
"#
),
true,
bool
);
}
#[test]
fn neq_bool_tag() {
assert_evals_to!(
indoc!(
r#"
true : Bool
true = True
true == False
"#
),
false,
bool
);
}
#[test]
fn empty_record() {
assert_evals_to!("{} == {}", true, bool);
assert_evals_to!("{} != {}", false, bool);
}
#[test]
fn unit() {
assert_evals_to!("Unit == Unit", true, bool);
assert_evals_to!("Unit != Unit", false, bool);
}
#[test]
fn newtype() {
assert_evals_to!("Identity 42 == Identity 42", true, bool);
assert_evals_to!("Identity 42 != Identity 42", false, bool);
}
#[test]
fn small_str() {
assert_evals_to!("\"aaa\" == \"aaa\"", true, bool);
assert_evals_to!("\"aaa\" == \"bbb\"", false, bool);
assert_evals_to!("\"aaa\" != \"aaa\"", false, bool);
}
#[test]
fn large_str() {
assert_evals_to!(
indoc!(
r#"
x = "Unicode can represent text values which span multiple languages"
y = "Unicode can represent text values which span multiple languages"
x == y
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
x = "Unicode can represent text values which span multiple languages"
y = "Here are some valid Roc strings"
x != y
"#
),
true,
bool
);
}
#[test]
fn eq_result_tag_true() {
assert_evals_to!(
indoc!(
r#"
x : Result I64 I64
x = Ok 1
y : Result I64 I64
y = Ok 1
x == y
"#
),
true,
bool
);
}
#[test]
fn eq_result_tag_false() {
assert_evals_to!(
indoc!(
r#"
x : Result I64 I64
x = Ok 1
y : Result I64 I64
y = Err 1
x == y
"#
),
false,
bool
);
}
#[test]
fn eq_expr() {
assert_evals_to!(
indoc!(
r#"
Expr : [ Add Expr Expr, Mul Expr Expr, Val I64, Var I64 ]
x : Expr
x = Val 0
y : Expr
y = Val 0
x == y
"#
),
true,
bool
);
}
#[test]
fn eq_linked_list() {
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
x : LinkedList I64
x = Nil
y : LinkedList I64
y = Nil
x == y
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
x : LinkedList I64
x = Cons 1 Nil
y : LinkedList I64
y = Cons 1 Nil
x == y
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
x : LinkedList I64
x = Cons 1 (Cons 2 Nil)
y : LinkedList I64
y = Cons 1 (Cons 2 Nil)
x == y
"#
),
true,
bool
);
}
#[test]
fn eq_linked_list_false() {
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
x : LinkedList I64
x = Cons 1 Nil
y : LinkedList I64
y = Cons 1 (Cons 2 Nil)
y == x
"#
),
false,
bool
);
}
#[test]
fn eq_nullable_expr() {
assert_evals_to!(
indoc!(
r#"
Expr : [ Add Expr Expr, Mul Expr Expr, Val I64, Empty ]
x : Expr
x = Val 0
y : Expr
y = Add x x
x != y
"#
),
true,
bool
);
}
#[test]
fn eq_rosetree() {
assert_evals_to!(
indoc!(
r#"
Rose a : [ Rose (List (Rose a)) ]
x : Rose I64
x = Rose []
y : Rose I64
y = Rose []
x == y
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
Rose a : [ Rose (List (Rose a)) ]
x : Rose I64
x = Rose []
y : Rose I64
y = Rose []
x != y
"#
),
false,
bool
);
}
#[test]
#[ignore]
fn rosetree_with_tag() {
// currently stack overflows in type checking
assert_evals_to!(
indoc!(
r#"
Rose a : [ Rose (Result (List (Rose a)) I64) ]
x : Rose I64
x = (Rose (Ok []))
y : Rose I64
y = (Rose (Ok []))
x == y
"#
),
true,
bool
);
}
#[test]
fn list_eq_empty() {
assert_evals_to!("[] == []", true, bool);
assert_evals_to!("[] != []", false, bool);
}
#[test]
fn list_eq_by_length() {
assert_evals_to!("[1] == []", false, bool);
assert_evals_to!("[] == [1]", false, bool);
}
#[test]
fn list_eq_compare_pointwise() {
assert_evals_to!("[1] == [1]", true, bool);
assert_evals_to!("[2] == [1]", false, bool);
}
#[test]
fn list_eq_nested() {
assert_evals_to!("[[1]] == [[1]]", true, bool);
assert_evals_to!("[[2]] == [[1]]", false, bool);
}
#[test]
fn list_neq_compare_pointwise() {
assert_evals_to!("[1] != [1]", false, bool);
assert_evals_to!("[2] != [1]", true, bool);
}
#[test]
fn list_neq_nested() {
assert_evals_to!("[[1]] != [[1]]", false, bool);
assert_evals_to!("[[2]] != [[1]]", true, bool);
}
}

View file

@ -0,0 +1,522 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_dict {
use roc_std::RocStr;
#[test]
fn dict_empty_len() {
assert_evals_to!(
indoc!(
r#"
Dict.len Dict.empty
"#
),
0,
usize
);
}
#[test]
fn dict_insert_empty() {
assert_evals_to!(
indoc!(
r#"
Dict.insert Dict.empty 42 32
|> Dict.len
"#
),
1,
usize
);
}
#[test]
fn dict_empty_contains() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.empty
Dict.contains empty 42
"#
),
false,
bool
);
}
#[test]
fn dict_nonempty_contains() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.insert Dict.empty 42 3.14
Dict.contains empty 42
"#
),
true,
bool
);
}
#[test]
fn dict_empty_remove() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.empty
empty
|> Dict.remove 42
|> Dict.len
"#
),
0,
i64
);
}
#[test]
fn dict_nonempty_remove() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.insert Dict.empty 42 3.14
empty
|> Dict.remove 42
|> Dict.len
"#
),
0,
i64
);
}
#[test]
fn dict_nonempty_get() {
assert_evals_to!(
indoc!(
r#"
empty : Dict I64 F64
empty = Dict.insert Dict.empty 42 3.14
withDefault = \x, def ->
when x is
Ok v -> v
Err _ -> def
empty
|> Dict.insert 42 3.14
|> Dict.get 42
|> withDefault 0
"#
),
3.14,
f64
);
assert_evals_to!(
indoc!(
r#"
withDefault = \x, def ->
when x is
Ok v -> v
Err _ -> def
Dict.empty
|> Dict.insert 42 3.14
|> Dict.get 43
|> withDefault 0
"#
),
0.0,
f64
);
}
#[test]
fn keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict =
Dict.empty
|> Dict.insert 0 100
|> Dict.insert 1 100
|> Dict.insert 2 100
Dict.keys myDict
"#
),
&[0, 1, 2],
&[i64]
);
}
#[test]
fn values() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict =
Dict.empty
|> Dict.insert 0 100
|> Dict.insert 1 200
|> Dict.insert 2 300
Dict.values myDict
"#
),
&[100, 200, 300],
&[i64]
);
}
#[test]
fn from_list_with_fold() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict =
[1,2,3]
|> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty
Dict.values myDict
"#
),
&[2, 3, 1],
&[i64]
);
assert_evals_to!(
indoc!(
r#"
range : I64, I64, List I64-> List I64
range = \low, high, accum ->
if low < high then
range (low + 1) high (List.append accum low)
else
accum
myDict : Dict I64 I64
myDict =
# 25 elements (8 + 16 + 1) is guaranteed to overflow/reallocate at least twice
range 0 25 []
|> List.walk (\value, accum -> Dict.insert accum value value) Dict.empty
Dict.values myDict
|> List.len
"#
),
25,
i64
);
}
#[test]
fn small_str_keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict Str I64
myDict =
Dict.empty
|> Dict.insert "a" 100
|> Dict.insert "b" 100
|> Dict.insert "c" 100
Dict.keys myDict
"#
),
&[RocStr::from("c"), RocStr::from("a"), RocStr::from("b"),],
&[RocStr]
);
}
#[test]
fn big_str_keys() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict Str I64
myDict =
Dict.empty
|> Dict.insert "Leverage agile frameworks to provide a robust" 100
|> Dict.insert "synopsis for high level overviews. Iterative approaches" 200
|> Dict.insert "to corporate strategy foster collaborative thinking to" 300
Dict.keys myDict
"#
),
&[
RocStr::from("Leverage agile frameworks to provide a robust"),
RocStr::from("to corporate strategy foster collaborative thinking to"),
RocStr::from("synopsis for high level overviews. Iterative approaches"),
],
&[RocStr]
);
}
#[test]
fn big_str_values() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 Str
myDict =
Dict.empty
|> Dict.insert 100 "Leverage agile frameworks to provide a robust"
|> Dict.insert 200 "synopsis for high level overviews. Iterative approaches"
|> Dict.insert 300 "to corporate strategy foster collaborative thinking to"
Dict.values myDict
"#
),
&[
RocStr::from("Leverage agile frameworks to provide a robust"),
RocStr::from("to corporate strategy foster collaborative thinking to"),
RocStr::from("synopsis for high level overviews. Iterative approaches"),
],
&[RocStr]
);
}
#[test]
fn unit_values() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 {}
myDict =
Dict.empty
|> Dict.insert 0 {}
|> Dict.insert 1 {}
|> Dict.insert 2 {}
|> Dict.insert 3 {}
Dict.len myDict
"#
),
4,
i64
);
}
#[test]
fn singleton() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 {}
myDict =
Dict.singleton 0 {}
Dict.len myDict
"#
),
1,
i64
);
}
#[test]
fn union() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 {}
myDict =
Dict.union (Dict.singleton 0 {}) (Dict.singleton 1 {})
Dict.len myDict
"#
),
2,
i64
);
}
#[test]
fn union_prefer_first() {
assert_evals_to!(
indoc!(
r#"
myDict : Dict I64 I64
myDict =
Dict.union (Dict.singleton 0 100) (Dict.singleton 0 200)
Dict.values myDict
"#
),
&[100],
&[i64]
);
}
#[test]
fn intersection() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 {}
dict1 =
Dict.empty
|> Dict.insert 1 {}
|> Dict.insert 2 {}
|> Dict.insert 3 {}
|> Dict.insert 4 {}
|> Dict.insert 5 {}
dict2 : Dict I64 {}
dict2 =
Dict.empty
|> Dict.insert 0 {}
|> Dict.insert 2 {}
|> Dict.insert 4 {}
Dict.intersection dict1 dict2
|> Dict.len
"#
),
2,
i64
);
}
#[test]
fn intersection_prefer_first() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 I64
dict1 =
Dict.empty
|> Dict.insert 1 1
|> Dict.insert 2 2
|> Dict.insert 3 3
|> Dict.insert 4 4
|> Dict.insert 5 5
dict2 : Dict I64 I64
dict2 =
Dict.empty
|> Dict.insert 0 100
|> Dict.insert 2 200
|> Dict.insert 4 300
Dict.intersection dict1 dict2
|> Dict.values
"#
),
&[4, 2],
&[i64]
);
}
#[test]
fn difference() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 {}
dict1 =
Dict.empty
|> Dict.insert 1 {}
|> Dict.insert 2 {}
|> Dict.insert 3 {}
|> Dict.insert 4 {}
|> Dict.insert 5 {}
dict2 : Dict I64 {}
dict2 =
Dict.empty
|> Dict.insert 0 {}
|> Dict.insert 2 {}
|> Dict.insert 4 {}
Dict.difference dict1 dict2
|> Dict.len
"#
),
3,
i64
);
}
#[test]
fn difference_prefer_first() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 I64
dict1 =
Dict.empty
|> Dict.insert 1 1
|> Dict.insert 2 2
|> Dict.insert 3 3
|> Dict.insert 4 4
|> Dict.insert 5 5
dict2 : Dict I64 I64
dict2 =
Dict.empty
|> Dict.insert 0 100
|> Dict.insert 2 200
|> Dict.insert 4 300
Dict.difference dict1 dict2
|> Dict.values
"#
),
&[5, 3, 1],
&[i64]
);
}
#[test]
fn walk_sum_keys() {
assert_evals_to!(
indoc!(
r#"
dict1 : Dict I64 I64
dict1 =
Dict.empty
|> Dict.insert 1 1
|> Dict.insert 2 2
|> Dict.insert 3 3
|> Dict.insert 4 4
|> Dict.insert 5 5
Dict.walk dict1 (\k, _, a -> k + a) 0
"#
),
15,
i64
);
}
}

View file

@ -0,0 +1,157 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_hash {
#[test]
fn basic_hash() {
assert_evals_to!(
indoc!(
r#"
Dict.hashTestOnly 0 0
"#
),
9718519427346233646,
u64
);
}
#[test]
fn hash_str_with_seed() {
assert_evals_to!("Dict.hashTestOnly 1 \"a\"", 0xbed235177f41d328, u64);
assert_evals_to!("Dict.hashTestOnly 2 \"abc\"", 0xbe348debe59b27c3, u64);
}
#[test]
fn hash_record() {
assert_evals_to!("Dict.hashTestOnly 1 { x: \"a\" } ", 0xbed235177f41d328, u64);
assert_evals_to!(
"Dict.hashTestOnly 1 { x: 42, y: 3.14 } ",
5348189196103430707,
u64
);
}
#[test]
fn hash_result() {
assert_evals_to!(
"Dict.hashTestOnly 0 (List.get [ 0x1 ] 0) ",
2878521786781103245,
u64
);
}
#[test]
fn hash_linked_list() {
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
input : LinkedList I64
input = Nil
Dict.hashTestOnly 0 input
"#
),
0,
u64
);
assert_evals_to!(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
input : LinkedList I64
input = Cons 4 (Cons 3 Nil)
Dict.hashTestOnly 0 input
"#
),
8287696503006938486,
u64
);
}
#[test]
fn hash_expr() {
assert_evals_to!(
indoc!(
r#"
Expr : [ Add Expr Expr, Mul Expr Expr, Val I64, Var I64 ]
x : Expr
x = Val 1
Dict.hashTestOnly 0 (Add x x)
"#
),
18264046914072177411,
u64
);
}
#[test]
fn hash_nullable_expr() {
assert_evals_to!(
indoc!(
r#"
Expr : [ Add Expr Expr, Mul Expr Expr, Val I64, Empty ]
x : Expr
x = Val 1
Dict.hashTestOnly 0 (Add x x)
"#
),
11103255846683455235,
u64
);
}
#[test]
fn hash_rosetree() {
assert_evals_to!(
indoc!(
r#"
Rose a : [ Rose (List (Rose a)) ]
x : Rose I64
x = Rose []
Dict.hashTestOnly 0 x
"#
),
0,
u64
);
}
#[test]
fn hash_list() {
assert_evals_to!(
indoc!(
r#"
x : List Str
x = [ "foo", "bar", "baz" ]
Dict.hashTestOnly 0 x
"#
),
10731521034618280801,
u64
);
}
}

View file

@ -1710,39 +1710,32 @@ mod gen_list {
} }
#[test] #[test]
fn list_eq_empty() { fn list_keep_oks() {
assert_evals_to!("[] == []", true, bool); assert_evals_to!("List.keepOks [] (\\x -> x)", 0, i64);
assert_evals_to!("[] != []", false, bool); assert_evals_to!("List.keepOks [1,2] (\\x -> Ok x)", &[1, 2], &[i64]);
assert_evals_to!("List.keepOks [1,2] (\\x -> x % 2)", &[1, 0], &[i64]);
assert_evals_to!("List.keepOks [Ok 1, Err 2] (\\x -> x)", &[1], &[i64]);
} }
#[test] #[test]
fn list_eq_by_length() { fn list_keep_errs() {
assert_evals_to!("[1] == []", false, bool); assert_evals_to!("List.keepErrs [] (\\x -> x)", 0, i64);
assert_evals_to!("[] == [1]", false, bool); assert_evals_to!("List.keepErrs [1,2] (\\x -> Err x)", &[1, 2], &[i64]);
assert_evals_to!(
"List.keepErrs [0,1,2] (\\x -> x % 0 |> Result.mapErr (\\_ -> 32))",
&[32, 32, 32],
&[i64]
);
assert_evals_to!("List.keepErrs [Ok 1, Err 2] (\\x -> x)", &[2], &[i64]);
} }
#[test] #[test]
fn list_eq_compare_pointwise() { fn list_map_with_index() {
assert_evals_to!("[1] == [1]", true, bool); assert_evals_to!(
assert_evals_to!("[2] == [1]", false, bool); "List.mapWithIndex [0,0,0] (\\index, x -> index + x)",
} &[0, 1, 2],
&[i64]
#[test] );
fn list_eq_nested() {
assert_evals_to!("[[1]] == [[1]]", true, bool);
assert_evals_to!("[[2]] == [[1]]", false, bool);
}
#[test]
fn list_neq_compare_pointwise() {
assert_evals_to!("[1] != [1]", false, bool);
assert_evals_to!("[2] != [1]", true, bool);
}
#[test]
fn list_neq_nested() {
assert_evals_to!("[[1]] != [[1]]", false, bool);
assert_evals_to!("[[2]] != [[1]]", true, bool);
} }
#[test] #[test]

View file

@ -51,6 +51,9 @@ mod gen_num {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
app "test" provides [ main ] to "./platform"
main =
i : I64 i : I64
i = 64 i = 64
@ -446,24 +449,6 @@ mod gen_num {
-1, -1,
i64 i64
); );
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
if num == 1 then
-1
else if num == -1 then
1
else
num
limitedNegate 1
"#
),
-1,
i64
);
} }
#[test] #[test]

View file

@ -1952,7 +1952,7 @@ mod gen_primitives {
main = main =
x : Tree F64 x : Tree F64
x = singleton 3 x = singleton 3
when x is when x is
Tree 3.0 _ -> True Tree 3.0 _ -> True
_ -> False _ -> False
"# "#
@ -2215,4 +2215,23 @@ mod gen_primitives {
i64 i64
); );
} }
#[test]
fn build_then_apply_closure() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main : Str
main =
x = "long string that is malloced"
(\_ -> x) {}
"#
),
"long string that is malloced",
&'static str
);
}
} }

View file

@ -0,0 +1,111 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_result {
#[test]
fn with_default() {
assert_evals_to!(
indoc!(
r#"
result : Result I64 {}
result = Ok 2
Result.withDefault result 0
"#
),
2,
i64
);
assert_evals_to!(
indoc!(
r#"
result : Result I64 {}
result = Err {}
Result.withDefault result 0
"#
),
0,
i64
);
}
#[test]
fn result_map() {
assert_evals_to!(
indoc!(
r#"
result : Result I64 {}
result = Ok 2
result
|> Result.map (\x -> x + 1)
|> Result.withDefault 0
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
result : Result I64 {}
result = Err {}
result
|> Result.map (\x -> x + 1)
|> Result.withDefault 0
"#
),
0,
i64
);
}
#[test]
fn result_map_err() {
assert_evals_to!(
indoc!(
r#"
result : Result {} I64
result = Err 2
when Result.mapErr result (\x -> x + 1) is
Err n -> n
Ok _ -> 0
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
result : Result {} I64
result = Ok {}
when Result.mapErr result (\x -> x + 1) is
Err n -> n
Ok _ -> 0
"#
),
0,
i64
);
}
}

View file

@ -0,0 +1,248 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_set {
#[test]
fn empty_len() {
assert_evals_to!(
indoc!(
r#"
Set.len Set.empty
"#
),
0,
usize
);
}
#[test]
fn singleton_len() {
assert_evals_to!(
indoc!(
r#"
Set.len (Set.singleton 42)
"#
),
1,
usize
);
}
#[test]
fn singleton_to_list() {
assert_evals_to!(
indoc!(
r#"
Set.toList (Set.singleton 42)
"#
),
&[42],
&[i64]
);
assert_evals_to!(
indoc!(
r#"
Set.toList (Set.singleton 1)
"#
),
&[1],
&[i64]
);
assert_evals_to!(
indoc!(
r#"
Set.toList (Set.singleton 1.0)
"#
),
&[1.0],
&[f64]
);
}
#[test]
fn insert() {
assert_evals_to!(
indoc!(
r#"
Set.empty
|> Set.insert 0
|> Set.insert 1
|> Set.insert 2
|> Set.toList
"#
),
&[0, 1, 2],
&[i64]
);
}
#[test]
fn remove() {
assert_evals_to!(
indoc!(
r#"
Set.empty
|> Set.insert 0
|> Set.insert 1
|> Set.remove 1
|> Set.remove 2
|> Set.toList
"#
),
&[0],
&[i64]
);
}
#[test]
fn union() {
assert_evals_to!(
indoc!(
r#"
set1 : Set I64
set1 = Set.fromList [1,2]
set2 : Set I64
set2 = Set.fromList [1,3,4]
Set.union set1 set2
|> Set.toList
"#
),
&[4, 2, 3, 1],
&[i64]
);
}
#[test]
fn difference() {
assert_evals_to!(
indoc!(
r#"
set1 : Set I64
set1 = Set.fromList [1,2]
set2 : Set I64
set2 = Set.fromList [1,3,4]
Set.difference set1 set2
|> Set.toList
"#
),
&[2],
&[i64]
);
}
#[test]
fn intersection() {
assert_evals_to!(
indoc!(
r#"
set1 : Set I64
set1 = Set.fromList [1,2]
set2 : Set I64
set2 = Set.fromList [1,3,4]
Set.intersection set1 set2
|> Set.toList
"#
),
&[1],
&[i64]
);
}
#[test]
fn walk_sum() {
assert_evals_to!(
indoc!(
r#"
Set.walk (Set.fromList [1,2,3]) (\x, y -> x + y) 0
"#
),
6,
i64
);
}
#[test]
fn contains() {
assert_evals_to!(
indoc!(
r#"
Set.contains (Set.fromList [1,3,4]) 4
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
Set.contains (Set.fromList [1,3,4]) 2
"#
),
false,
bool
);
}
#[test]
fn from_list() {
assert_evals_to!(
indoc!(
r#"
[1,2,2,3,1,4]
|> Set.fromList
|> Set.toList
"#
),
&[4, 2, 3, 1],
&[i64]
);
assert_evals_to!(
indoc!(
r#"
[]
|> Set.fromList
|> Set.toList
"#
),
&[],
&[i64]
);
assert_evals_to!(
indoc!(
r#"
empty : List I64
empty = []
empty
|> Set.fromList
|> Set.toList
"#
),
&[],
&[i64]
);
}
}

View file

@ -811,4 +811,9 @@ mod gen_str {
fn str_join_comma_single() { fn str_join_comma_single() {
assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr); assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
} }
#[test]
fn str_from_float() {
assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.140000"), RocStr);
}
} }

View file

@ -1,7 +1,11 @@
use libloading::Library; use libloading::Library;
use roc_build::link::module_to_dylib; use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator; use roc_build::program::FunctionIterator;
use roc_can::builtins::{builtin_defs_map, dict_hash_test_only};
use roc_can::def::Def;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol;
use roc_types::subs::VarStore;
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");
@ -15,6 +19,15 @@ fn promote_expr_to_module(src: &str) -> String {
buffer buffer
} }
pub fn test_builtin_defs(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> {
match builtin_defs_map(symbol, var_store) {
Some(def) => Some(def),
None => match symbol {
Symbol::DICT_TEST_HASH => Some(dict_hash_test_only(symbol, var_store)),
_ => None,
},
}
}
pub fn helper<'a>( pub fn helper<'a>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
@ -53,6 +66,7 @@ pub fn helper<'a>(
src_dir, src_dir,
exposed_types, exposed_types,
ptr_bytes, ptr_bytes,
test_builtin_defs,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");
@ -179,12 +193,22 @@ pub fn helper<'a>(
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module); let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
// mark our zig-defined builtins as internal // mark our zig-defined builtins as internal
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::module::Linkage; use inkwell::module::Linkage;
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = context.create_enum_attribute(kind_id, 1);
for function in FunctionIterator::from_module(module) { for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap(); let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") { if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal); function.set_linkage(Linkage::Internal);
} }
if name.starts_with("roc_builtins.dict") {
function.add_attribute(AttributeLoc::Function, attr);
}
} }
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
@ -251,7 +275,7 @@ pub fn helper<'a>(
mode, mode,
); );
fn_val.print_to_stderr(); // fn_val.print_to_stderr();
// module.print_to_stderr(); // module.print_to_stderr();
panic!( panic!(

View file

@ -14,7 +14,6 @@ roc_problem = { path = "../problem" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" } roc_constrain = { path = "../constrain" }
roc_uniq = { path = "../uniq" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }

View file

@ -1,6 +1,9 @@
use crate::generic64::{Assembler, CallConv, RegTrait}; use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage};
use crate::Relocation; use crate::Relocation;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use roc_mono::layout::Layout;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[allow(dead_code)] #[allow(dead_code)]
@ -136,29 +139,33 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
#[inline(always)] #[inline(always)]
fn setup_stack( fn setup_stack(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
leaf_function: bool,
saved_regs: &[AArch64GeneralReg], saved_regs: &[AArch64GeneralReg],
requested_stack_size: i32, requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String> { ) -> Result<i32, String> {
// full size is upcast to i64 to make sure we don't overflow here. // Full size is upcast to i64 to make sure we don't overflow here.
let mut full_size = 8 * saved_regs.len() as i64 + requested_stack_size as i64; let full_stack_size = requested_stack_size
if !leaf_function { .checked_add(8 * saved_regs.len() as i32 + 8) // The extra 8 is space to store the frame pointer.
full_size += 8; .ok_or("Ran out of stack space")?
} .checked_add(fn_call_stack_size)
let alignment = if full_size <= 0 { .ok_or("Ran out of stack space")?;
let alignment = if full_stack_size <= 0 {
0 0
} else { } else {
full_size % STACK_ALIGNMENT as i64 full_stack_size % STACK_ALIGNMENT as i32
}; };
let offset = if alignment == 0 { let offset = if alignment == 0 {
0 0
} else { } else {
STACK_ALIGNMENT - alignment as u8 STACK_ALIGNMENT - alignment as u8
}; };
if let Some(aligned_stack_size) = if let Some(aligned_stack_size) = full_stack_size.checked_add(offset as i32) {
requested_stack_size.checked_add(8 * saved_regs.len() as i32 + offset as i32)
{
if aligned_stack_size > 0 { if aligned_stack_size > 0 {
AArch64Assembler::mov_reg64_reg64(
buf,
AArch64GeneralReg::FP,
AArch64GeneralReg::ZRSP,
);
AArch64Assembler::sub_reg64_reg64_imm32( AArch64Assembler::sub_reg64_reg64_imm32(
buf, buf,
AArch64GeneralReg::ZRSP, AArch64GeneralReg::ZRSP,
@ -168,15 +175,15 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
// All the following stores could be optimized by using `STP` to store pairs. // All the following stores could be optimized by using `STP` to store pairs.
let mut offset = aligned_stack_size; let mut offset = aligned_stack_size;
if !leaf_function { offset -= 8;
offset -= 8; AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::LR);
AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::LR); offset -= 8;
offset -= 8; AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::FP);
AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::FP);
} offset = aligned_stack_size - fn_call_stack_size;
for reg in saved_regs { for reg in saved_regs {
offset -= 8; offset -= 8;
AArch64Assembler::mov_stack32_reg64(buf, offset, *reg); AArch64Assembler::mov_base32_reg64(buf, offset, *reg);
} }
Ok(aligned_stack_size) Ok(aligned_stack_size)
} else { } else {
@ -190,22 +197,22 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
#[inline(always)] #[inline(always)]
fn cleanup_stack( fn cleanup_stack(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
leaf_function: bool,
saved_regs: &[AArch64GeneralReg], saved_regs: &[AArch64GeneralReg],
aligned_stack_size: i32, aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String> { ) -> Result<(), String> {
if aligned_stack_size > 0 { if aligned_stack_size > 0 {
// All the following stores could be optimized by using `STP` to store pairs. // All the following stores could be optimized by using `STP` to store pairs.
let mut offset = aligned_stack_size; let mut offset = aligned_stack_size;
if !leaf_function { offset -= 8;
offset -= 8; AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::LR, offset);
AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::LR, offset); offset -= 8;
offset -= 8; AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::FP, offset);
AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::FP, offset);
} offset = aligned_stack_size - fn_call_stack_size;
for reg in saved_regs { for reg in saved_regs {
offset -= 8; offset -= 8;
AArch64Assembler::mov_reg64_stack32(buf, *reg, offset); AArch64Assembler::mov_reg64_base32(buf, *reg, offset);
} }
AArch64Assembler::add_reg64_reg64_imm32( AArch64Assembler::add_reg64_reg64_imm32(
buf, buf,
@ -216,6 +223,25 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
} }
Ok(()) Ok(())
} }
#[inline(always)]
fn load_args<'a>(
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)],
) -> Result<(), String> {
Err("Loading args not yet implemented for AArch64".to_string())
}
#[inline(always)]
fn store_args<'a>(
_buf: &mut Vec<'a, u8>,
_symbol_map: &MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [Symbol],
_arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>,
) -> Result<u32, String> {
Err("Storing args not yet implemented for AArch64".to_string())
}
} }
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler { impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
@ -241,7 +267,6 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
); );
} }
} }
#[inline(always)] #[inline(always)]
fn add_reg64_reg64_reg64( fn add_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -251,7 +276,6 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
) { ) {
add_reg64_reg64_reg64(buf, dst, src1, src2); add_reg64_reg64_reg64(buf, dst, src1, src2);
} }
#[inline(always)] #[inline(always)]
fn add_freg64_freg64_freg64( fn add_freg64_freg64_freg64(
_buf: &mut Vec<'_, u8>, _buf: &mut Vec<'_, u8>,
@ -262,6 +286,25 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
unimplemented!("adding floats not yet implemented for AArch64"); unimplemented!("adding floats not yet implemented for AArch64");
} }
#[inline(always)]
fn call(_buf: &mut Vec<'_, u8>, _relocs: &mut Vec<'_, Relocation>, _fn_name: String) {
unimplemented!("calling functions literal not yet implemented for AArch64");
}
#[inline(always)]
fn jmp_imm32(_buf: &mut Vec<'_, u8>, _offset: i32) -> usize {
unimplemented!("jump instructions not yet implemented for AArch64");
}
#[inline(always)]
fn jne_reg64_imm64_imm32(
_buf: &mut Vec<'_, u8>,
_reg: AArch64GeneralReg,
_imm: u64,
_offset: i32,
) -> usize {
unimplemented!("jump not equal instructions not yet implemented for AArch64");
}
#[inline(always)] #[inline(always)]
fn mov_freg64_imm64( fn mov_freg64_imm64(
_buf: &mut Vec<'_, u8>, _buf: &mut Vec<'_, u8>,
@ -271,7 +314,6 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
) { ) {
unimplemented!("loading float literal not yet implemented for AArch64"); unimplemented!("loading float literal not yet implemented for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, imm: i64) { fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, imm: i64) {
let mut remaining = imm as u64; let mut remaining = imm as u64;
@ -289,17 +331,52 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
movk_reg64_imm16(buf, dst, remaining as u16, 3); movk_reg64_imm16(buf, dst, remaining as u16, 3);
} }
} }
#[inline(always)] #[inline(always)]
fn mov_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) { fn mov_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) {
unimplemented!("moving data between float registers not yet implemented for AArch64"); unimplemented!("moving data between float registers not yet implemented for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) { fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) {
mov_reg64_reg64(buf, dst, src); mov_reg64_reg64(buf, dst, src);
} }
#[inline(always)]
fn mov_freg64_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
unimplemented!(
"loading floating point reg from base offset not yet implemented for AArch64"
);
}
#[inline(always)]
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32) {
if offset < 0 {
unimplemented!("negative base offsets are not yet implement for AArch64");
} else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0);
ldr_reg64_imm12(buf, dst, AArch64GeneralReg::FP, (offset as u16) >> 3);
} else {
unimplemented!("base offsets over 32k are not yet implement for AArch64");
}
}
#[inline(always)]
fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) {
unimplemented!("saving floating point reg to base offset not yet implemented for AArch64");
}
#[inline(always)]
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) {
if offset < 0 {
unimplemented!("negative base offsets are not yet implement for AArch64");
} else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0);
str_reg64_imm12(buf, src, AArch64GeneralReg::FP, (offset as u16) >> 3);
} else {
unimplemented!("base offsets over 32k are not yet implement for AArch64");
}
}
#[inline(always)]
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
unimplemented!("loading floating point reg from stack not yet implemented for AArch64");
}
#[inline(always)] #[inline(always)]
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32) { fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32) {
if offset < 0 { if offset < 0 {
@ -311,16 +388,10 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
unimplemented!("stack offsets over 32k are not yet implement for AArch64"); unimplemented!("stack offsets over 32k are not yet implement for AArch64");
} }
} }
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
unimplemented!("loading floating point reg from stack not yet implemented for AArch64");
}
#[inline(always)] #[inline(always)]
fn mov_stack32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) { fn mov_stack32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) {
unimplemented!("saving floating point reg to stack not yet implemented for AArch64"); unimplemented!("saving floating point reg to stack not yet implemented for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) { fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) {
if offset < 0 { if offset < 0 {
@ -352,7 +423,6 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
); );
} }
} }
#[inline(always)] #[inline(always)]
fn sub_reg64_reg64_reg64( fn sub_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>, _buf: &mut Vec<'_, u8>,
@ -372,6 +442,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
) { ) {
unimplemented!("registers equality not implemented yet for AArch64"); unimplemented!("registers equality not implemented yet for AArch64");
} }
#[inline(always)] #[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) { fn ret(buf: &mut Vec<'_, u8>) {
ret_reg64(buf, AArch64GeneralReg::LR) ret_reg64(buf, AArch64GeneralReg::LR)

View file

@ -2,7 +2,8 @@ use crate::{Backend, Env, Relocation};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::{Literal, Stmt}; use roc_mono::ir::{BranchInfo, Literal, Stmt};
use roc_mono::layout::{Builtin, Layout};
use std::marker::PhantomData; use std::marker::PhantomData;
use target_lexicon::Triple; use target_lexicon::Triple;
@ -33,16 +34,33 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn setup_stack<'a>( fn setup_stack<'a>(
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
leaf_function: bool,
general_saved_regs: &[GeneralReg], general_saved_regs: &[GeneralReg],
requested_stack_size: i32, requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String>; ) -> Result<i32, String>;
fn cleanup_stack<'a>( fn cleanup_stack<'a>(
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
leaf_function: bool,
general_saved_regs: &[GeneralReg], general_saved_regs: &[GeneralReg],
aligned_stack_size: i32, aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String>; ) -> Result<(), String>;
// load_args updates the symbol map to know where every arg is stored.
fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
) -> Result<(), String>;
// store_args stores the args in registers and on the stack for function calling.
// It returns the amount of stack space needed to temporarily store the args.
fn store_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>,
) -> Result<u32, String>;
} }
/// Assembler contains calls to the backend assembly generator. /// Assembler contains calls to the backend assembly generator.
@ -53,6 +71,7 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
/// dst should always come before sources. /// dst should always come before sources.
pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> { pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn add_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32); fn add_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
fn add_freg64_freg64_freg64( fn add_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -66,6 +85,24 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
src1: GeneralReg, src1: GeneralReg,
src2: GeneralReg, src2: GeneralReg,
); );
fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String);
// Jumps by an offset of offset bytes unconditionally.
// It should always generate the same number of bytes to enable replacement if offset changes.
// It returns the base offset to calculate the jump from (generally the instruction after the jump).
fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize;
// Jumps by an offset of offset bytes if reg is not equal to imm.
// It should always generate the same number of bytes to enable replacement if offset changes.
// It returns the base offset to calculate the jump from (generally the instruction after the jump).
fn jne_reg64_imm64_imm32(
buf: &mut Vec<'_, u8>,
reg: GeneralReg,
imm: u64,
offset: i32,
) -> usize;
fn mov_freg64_imm64( fn mov_freg64_imm64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>, relocs: &mut Vec<'_, Relocation>,
@ -75,10 +112,18 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: GeneralReg, imm: i64); fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: GeneralReg, imm: i64);
fn mov_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); fn mov_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
// base32 is similar to stack based instructions but they reference the base/frame pointer.
fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32); fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
fn sub_reg64_reg64_reg64( fn sub_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -86,25 +131,27 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
src1: GeneralReg, src1: GeneralReg,
src2: GeneralReg, src2: GeneralReg,
); );
fn eq_reg64_reg64_reg64( fn eq_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
dst: GeneralReg, dst: GeneralReg,
src1: GeneralReg, src1: GeneralReg,
src2: GeneralReg, src2: GeneralReg,
); );
fn ret(buf: &mut Vec<'_, u8>); fn ret(buf: &mut Vec<'_, u8>);
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> { pub enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
// These may need layout, but I am not sure. // These may need layout, but I am not sure.
// I think whenever a symbol would be used, we specify layout anyways. // I think whenever a symbol would be used, we specify layout anyways.
GeneralReg(GeneralReg), GeneralReg(GeneralReg),
FloatReg(FloatReg), FloatReg(FloatReg),
Stack(i32), Base(i32),
StackAndGeneralReg(GeneralReg, i32), BaseAndGeneralReg(GeneralReg, i32),
StackAndFloatReg(FloatReg, i32), BaseAndFloatReg(FloatReg, i32),
} }
pub trait RegTrait: Copy + Eq + std::hash::Hash + std::fmt::Debug + 'static {} pub trait RegTrait: Copy + Eq + std::hash::Hash + std::fmt::Debug + 'static {}
@ -120,11 +167,7 @@ pub struct Backend64Bit<
phantom_cc: PhantomData<CC>, phantom_cc: PhantomData<CC>,
env: &'a Env<'a>, env: &'a Env<'a>,
buf: Vec<'a, u8>, buf: Vec<'a, u8>,
relocs: Vec<'a, Relocation<'a>>, relocs: Vec<'a, Relocation>,
/// leaf_function is true if the only calls this function makes are tail calls.
/// If that is the case, we can skip emitting the frame pointer and updating the stack.
leaf_function: bool,
last_seen_map: MutMap<Symbol, *const Stmt<'a>>, last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
@ -146,7 +189,9 @@ pub struct Backend64Bit<
general_used_callee_saved_regs: MutSet<GeneralReg>, general_used_callee_saved_regs: MutSet<GeneralReg>,
float_used_callee_saved_regs: MutSet<FloatReg>, float_used_callee_saved_regs: MutSet<FloatReg>,
stack_size: i32, stack_size: u32,
// The ammount of stack space needed to pass args for function calling.
fn_call_stack_size: u32,
} }
impl< impl<
@ -162,7 +207,6 @@ impl<
phantom_asm: PhantomData, phantom_asm: PhantomData,
phantom_cc: PhantomData, phantom_cc: PhantomData,
env, env,
leaf_function: true,
buf: bumpalo::vec!(in env.arena), buf: bumpalo::vec!(in env.arena),
relocs: bumpalo::vec!(in env.arena), relocs: bumpalo::vec!(in env.arena),
last_seen_map: MutMap::default(), last_seen_map: MutMap::default(),
@ -176,6 +220,7 @@ impl<
float_used_regs: bumpalo::vec![in env.arena], float_used_regs: bumpalo::vec![in env.arena],
float_used_callee_saved_regs: MutSet::default(), float_used_callee_saved_regs: MutSet::default(),
stack_size: 0, stack_size: 0,
fn_call_stack_size: 0,
}) })
} }
@ -185,7 +230,7 @@ impl<
fn reset(&mut self) { fn reset(&mut self) {
self.stack_size = 0; self.stack_size = 0;
self.leaf_function = true; self.fn_call_stack_size = 0;
self.last_seen_map.clear(); self.last_seen_map.clear();
self.free_map.clear(); self.free_map.clear();
self.symbols_map.clear(); self.symbols_map.clear();
@ -202,11 +247,6 @@ impl<
.extend_from_slice(CC::FLOAT_DEFAULT_FREE_REGS); .extend_from_slice(CC::FLOAT_DEFAULT_FREE_REGS);
} }
fn set_not_leaf_function(&mut self) {
self.leaf_function = false;
self.stack_size = CC::SHADOW_SPACE_SIZE as i32;
}
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>> { fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>> {
&mut self.literal_map &mut self.literal_map
} }
@ -223,27 +263,204 @@ impl<
&mut self.free_map &mut self.free_map
} }
fn finalize(&mut self) -> Result<(&'a [u8], &[&Relocation]), String> { fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String> {
let mut out = bumpalo::vec![in self.env.arena]; let mut out = bumpalo::vec![in self.env.arena];
// Setup stack. // Setup stack.
let mut used_regs = bumpalo::vec![in self.env.arena]; let mut used_regs = bumpalo::vec![in self.env.arena];
used_regs.extend(&self.general_used_callee_saved_regs); used_regs.extend(&self.general_used_callee_saved_regs);
let aligned_stack_size = let aligned_stack_size = CC::setup_stack(
CC::setup_stack(&mut out, self.leaf_function, &used_regs, self.stack_size)?; &mut out,
&used_regs,
self.stack_size as i32,
self.fn_call_stack_size as i32,
)?;
let setup_offset = out.len();
// Add function body. // Add function body.
out.extend(&self.buf); out.extend(&self.buf);
// Cleanup stack. // Cleanup stack.
CC::cleanup_stack(&mut out, self.leaf_function, &used_regs, aligned_stack_size)?; CC::cleanup_stack(
&mut out,
&used_regs,
aligned_stack_size,
self.fn_call_stack_size as i32,
)?;
ASM::ret(&mut out); ASM::ret(&mut out);
// Update relocs to include stack setup offset.
let mut out_relocs = bumpalo::vec![in self.env.arena]; let mut out_relocs = bumpalo::vec![in self.env.arena];
out_relocs.extend(&self.relocs); let old_relocs = std::mem::replace(&mut self.relocs, bumpalo::vec![in self.env.arena]);
out_relocs.extend(old_relocs.into_iter().map(|reloc| match reloc {
Relocation::LocalData { offset, data } => Relocation::LocalData {
offset: offset + setup_offset as u64,
data,
},
Relocation::LinkedData { offset, name } => Relocation::LinkedData {
offset: offset + setup_offset as u64,
name,
},
Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction {
offset: offset + setup_offset as u64,
name,
},
}));
Ok((out.into_bump_slice(), out_relocs.into_bump_slice())) Ok((out.into_bump_slice(), out_relocs.into_bump_slice()))
} }
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String> {
CC::load_args(&mut self.symbols_map, args)?;
// Update used and free regs.
for (sym, storage) in &self.symbols_map {
match storage {
SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => {
self.general_free_regs.retain(|r| *r != *reg);
self.general_used_regs.push((*reg, *sym));
}
SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg(reg, _) => {
self.float_free_regs.retain(|r| *r != *reg);
self.float_used_regs.push((*reg, *sym));
}
SymbolStorage::Base(_) => {}
}
}
Ok(())
}
fn build_fn_call(
&mut self,
dst: &Symbol,
fn_name: String,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
// Save used caller saved regs.
let old_general_used_regs = std::mem::replace(
&mut self.general_used_regs,
bumpalo::vec![in self.env.arena],
);
for (reg, saved_sym) in old_general_used_regs.into_iter() {
if CC::general_caller_saved(&reg) {
self.general_free_regs.push(reg);
self.free_to_stack(&saved_sym)?;
} else {
self.general_used_regs.push((reg, saved_sym));
}
}
let old_float_used_regs =
std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]);
for (reg, saved_sym) in old_float_used_regs.into_iter() {
if CC::float_caller_saved(&reg) {
self.float_free_regs.push(reg);
self.free_to_stack(&saved_sym)?;
} else {
self.float_used_regs.push((reg, saved_sym));
}
}
// Put values in param regs or on top of the stack.
let tmp_stack_size = CC::store_args(
&mut self.buf,
&self.symbols_map,
args,
arg_layouts,
ret_layout,
)?;
self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size);
// Call function and generate reloc.
ASM::call(&mut self.buf, &mut self.relocs, fn_name);
// move return value to dst.
match ret_layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]);
Ok(())
}
Layout::Builtin(Builtin::Float64) => {
let dst_reg = self.claim_float_reg(dst)?;
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]);
Ok(())
}
x => Err(format!(
"recieving return type, {:?}, is not yet implemented",
x
)),
}
}
fn build_switch(
&mut self,
cond_symbol: &Symbol,
_cond_layout: &Layout<'a>, // cond_layout must be a integer due to potential jump table optimizations.
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
default_branch: &(BranchInfo<'a>, &'a Stmt<'a>),
_ret_layout: &Layout<'a>,
) -> Result<(), String> {
// Switches are a little complex due to keeping track of jumps.
// In general I am trying to not have to loop over things multiple times or waste memory.
// The basic plan is to make jumps to nowhere and then correct them once we know the correct address.
let cond_reg = self.load_to_general_reg(cond_symbol)?;
let mut ret_jumps = bumpalo::vec![in self.env.arena];
let mut tmp = bumpalo::vec![in self.env.arena];
for (val, branch_info, stmt) in branches.iter() {
tmp.clear();
if let BranchInfo::None = branch_info {
// Create jump to next branch if not cond_sym not equal to value.
// Since we don't know the offset yet, set it to 0 and overwrite later.
let jne_location = self.buf.len();
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
// Build all statements in this branch.
self.build_stmt(stmt)?;
// Build unconditional jump to the end of this switch.
// Since we don't know the offset yet, set it to 0 and overwrite later.
let jmp_location = self.buf.len();
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0);
ret_jumps.push((jmp_location, jmp_offset));
// Overwite the original jne with the correct offset.
let end_offset = self.buf.len();
let jne_offset = end_offset - start_offset;
ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32);
for (i, byte) in tmp.iter().enumerate() {
self.buf[jne_location + i] = *byte;
}
} else {
return Err(format!(
"branch info, {:?}, is not yet implemented in switch statemens",
branch_info
));
}
}
let (branch_info, stmt) = default_branch;
if let BranchInfo::None = branch_info {
self.build_stmt(stmt)?;
// Update all return jumps to jump past the default case.
let ret_offset = self.buf.len();
for (jmp_location, start_offset) in ret_jumps.into_iter() {
tmp.clear();
let jmp_offset = ret_offset - start_offset;
ASM::jmp_imm32(&mut tmp, jmp_offset as i32);
for (i, byte) in tmp.iter().enumerate() {
self.buf[jmp_location + i] = *byte;
}
}
Ok(())
} else {
Err(format!(
"branch info, {:?}, is not yet implemented in switch statemens",
branch_info
))
}
}
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String> { fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String> {
let dst_reg = self.claim_general_reg(dst)?; let dst_reg = self.claim_general_reg(dst)?;
let src_reg = self.load_to_general_reg(src)?; let src_reg = self.load_to_general_reg(src)?;
@ -411,24 +628,21 @@ impl<
.insert(*sym, SymbolStorage::GeneralReg(reg)); .insert(*sym, SymbolStorage::GeneralReg(reg));
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::FloatReg(_reg)) => { Some(SymbolStorage::Base(offset)) => {
Err("Cannot load floating point symbol into GeneralReg".to_string())
}
Some(SymbolStorage::StackAndGeneralReg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::StackAndGeneralReg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::StackAndFloatReg(_reg, _offset)) => {
Err("Cannot load floating point symbol into GeneralReg".to_string())
}
Some(SymbolStorage::Stack(offset)) => {
let reg = self.claim_general_reg(sym)?; let reg = self.claim_general_reg(sym)?;
self.symbols_map self.symbols_map
.insert(*sym, SymbolStorage::StackAndGeneralReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
ASM::mov_reg64_stack32(&mut self.buf, reg, offset as i32); ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::FloatReg(_)) | Some(SymbolStorage::BaseAndFloatReg(_, _)) => {
Err("Cannot load floating point symbol into GeneralReg".to_string())
}
None => Err(format!("Unknown symbol: {}", sym)), None => Err(format!("Unknown symbol: {}", sym)),
} }
} }
@ -436,28 +650,25 @@ impl<
fn load_to_float_reg(&mut self, sym: &Symbol) -> Result<FloatReg, String> { fn load_to_float_reg(&mut self, sym: &Symbol) -> Result<FloatReg, String> {
let val = self.symbols_map.remove(sym); let val = self.symbols_map.remove(sym);
match val { match val {
Some(SymbolStorage::GeneralReg(_reg)) => {
Err("Cannot load integer point symbol into FloatReg".to_string())
}
Some(SymbolStorage::FloatReg(reg)) => { Some(SymbolStorage::FloatReg(reg)) => {
self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg)); self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg));
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::StackAndGeneralReg(_reg, _offset)) => { Some(SymbolStorage::Base(offset)) => {
Err("Cannot load integer point symbol into FloatReg".to_string())
}
Some(SymbolStorage::StackAndFloatReg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::StackAndFloatReg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::Stack(offset)) => {
let reg = self.claim_float_reg(sym)?; let reg = self.claim_float_reg(sym)?;
self.symbols_map self.symbols_map
.insert(*sym, SymbolStorage::StackAndFloatReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
ASM::mov_freg64_stack32(&mut self.buf, reg, offset as i32); ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::GeneralReg(_)) | Some(SymbolStorage::BaseAndGeneralReg(_, _)) => {
Err("Cannot load integer point symbol into FloatReg".to_string())
}
None => Err(format!("Unknown symbol: {}", sym)), None => Err(format!("Unknown symbol: {}", sym)),
} }
} }
@ -466,27 +677,31 @@ impl<
let val = self.symbols_map.remove(sym); let val = self.symbols_map.remove(sym);
match val { match val {
Some(SymbolStorage::GeneralReg(reg)) => { Some(SymbolStorage::GeneralReg(reg)) => {
let offset = self.increase_stack_size(8)?; let offset = self.increase_stack_size(8)? as i32;
ASM::mov_stack32_reg64(&mut self.buf, offset as i32, reg); // For base addresssing, use the negative offset - 8.
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset)); ASM::mov_base32_reg64(&mut self.buf, -offset - 8, reg);
self.symbols_map
.insert(*sym, SymbolStorage::Base(-offset - 8));
Ok(()) Ok(())
} }
Some(SymbolStorage::FloatReg(reg)) => { Some(SymbolStorage::FloatReg(reg)) => {
let offset = self.increase_stack_size(8)?; let offset = self.increase_stack_size(8)? as i32;
ASM::mov_stack32_freg64(&mut self.buf, offset as i32, reg); // For base addresssing, use the negative offset.
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset)); ASM::mov_base32_freg64(&mut self.buf, -offset - 8, reg);
self.symbols_map
.insert(*sym, SymbolStorage::Base(-offset - 8));
Ok(()) Ok(())
} }
Some(SymbolStorage::StackAndGeneralReg(_, offset)) => { Some(SymbolStorage::Base(offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset)); self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::StackAndFloatReg(_, offset)) => { Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset)); self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::Stack(offset)) => { Some(SymbolStorage::BaseAndFloatReg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset)); self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
None => Err(format!("Unknown symbol: {}", sym)), None => Err(format!("Unknown symbol: {}", sym)),
@ -494,12 +709,17 @@ impl<
} }
/// increase_stack_size increase the current stack size and returns the offset of the stack. /// increase_stack_size increase the current stack size and returns the offset of the stack.
fn increase_stack_size(&mut self, amount: i32) -> Result<i32, String> { fn increase_stack_size(&mut self, amount: u32) -> Result<u32, String> {
debug_assert!(amount > 0); debug_assert!(amount > 0);
let offset = self.stack_size; let offset = self.stack_size;
if let Some(new_size) = self.stack_size.checked_add(amount) { if let Some(new_size) = self.stack_size.checked_add(amount) {
self.stack_size = new_size; // Since stack size is u32, but the max offset is i32, if we pass i32 max, we have overflowed.
Ok(offset) if new_size > i32::MAX as u32 {
Err("Ran out of stack space".to_string())
} else {
self.stack_size = new_size;
Ok(offset)
}
} else { } else {
Err("Ran out of stack space".to_string()) Err("Ran out of stack space".to_string())
} }

File diff suppressed because it is too large Load diff

View file

@ -3,12 +3,13 @@
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
use bumpalo::{collections::Vec, Bump}; use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::TagName; use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::ir::{BranchInfo, CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout, LayoutIds};
use target_lexicon::Triple; use target_lexicon::Triple;
mod generic64; mod generic64;
@ -23,18 +24,11 @@ pub struct Env<'a> {
pub lazy_literals: bool, pub lazy_literals: bool,
} }
// INLINED_SYMBOLS is a set of all of the functions we automatically inline if seen.
const INLINED_SYMBOLS: [Symbol; 4] = [
Symbol::NUM_ABS,
Symbol::NUM_ADD,
Symbol::NUM_SUB,
Symbol::BOOL_EQ,
];
// These relocations likely will need a length. // These relocations likely will need a length.
// They may even need more definition, but this should be at least good enough for how we will use elf. // They may even need more definition, but this should be at least good enough for how we will use elf.
#[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum Relocation<'a> { pub enum Relocation {
LocalData { LocalData {
offset: u64, offset: u64,
// This should probably technically be a bumpalo::Vec. // This should probably technically be a bumpalo::Vec.
@ -43,11 +37,11 @@ pub enum Relocation<'a> {
}, },
LinkedFunction { LinkedFunction {
offset: u64, offset: u64,
name: &'a str, name: String,
}, },
LinkedData { LinkedData {
offset: u64, offset: u64,
name: &'a str, name: String,
}, },
} }
@ -67,12 +61,16 @@ where
/// finalize does setup because things like stack size and jump locations are not know until the function is written. /// finalize does setup because things like stack size and jump locations are not know until the function is written.
/// For example, this can store the frame pionter and setup stack space. /// For example, this can store the frame pionter and setup stack space.
/// finalize is run at the end of build_proc when all internal code is finalized. /// finalize is run at the end of build_proc when all internal code is finalized.
fn finalize(&mut self) -> Result<(&'a [u8], &[&Relocation]), String>; fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String>;
// load_args is used to let the backend know what the args are.
// The backend should track these args so it can use them as needed.
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String>;
/// build_proc creates a procedure and outputs it to the wrapped object writer. /// build_proc creates a procedure and outputs it to the wrapped object writer.
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[&Relocation]), String> { fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset(); self.reset();
// TODO: let the backend know of all the arguments. self.load_args(&proc.args)?;
// let start = std::time::Instant::now(); // let start = std::time::Instant::now();
self.scan_ast(&proc.body); self.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();
@ -110,9 +108,36 @@ where
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass); let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
self.build_stmt(&stmt) self.build_stmt(&stmt)
} }
Stmt::Switch {
cond_symbol,
cond_layout,
branches,
default_branch,
ret_layout,
} => {
self.load_literal_symbols(&[*cond_symbol])?;
self.build_switch(
cond_symbol,
cond_layout,
branches,
default_branch,
ret_layout,
)?;
self.free_symbols(stmt);
Ok(())
}
x => Err(format!("the statement, {:?}, is not yet implemented", x)), x => Err(format!("the statement, {:?}, is not yet implemented", x)),
} }
} }
// build_switch generates a instructions for a switch statement.
fn build_switch(
&mut self,
cond_symbol: &Symbol,
cond_layout: &Layout<'a>,
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
default_branch: &(BranchInfo<'a>, &'a Stmt<'a>),
ret_layout: &Layout<'a>,
) -> Result<(), String>;
/// build_expr builds the expressions for the specified symbol. /// build_expr builds the expressions for the specified symbol.
/// The builder must keep track of the symbol because it may be refered to later. /// The builder must keep track of the symbol because it may be refered to later.
@ -136,24 +161,52 @@ where
arguments, arguments,
}) => { }) => {
match call_type { match call_type {
CallType::ByName { name: func_sym, .. } => { CallType::ByName {
name: func_sym,
arg_layouts,
ret_layout,
..
} => {
// For most builtins instead of calling a function, we can just inline the low level.
match *func_sym { match *func_sym {
Symbol::NUM_ABS => { Symbol::NUM_ABS => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::NumAbs, arguments, layout) self.build_run_low_level(sym, &LowLevel::NumAbs, arguments, layout)
} }
Symbol::NUM_ADD => { Symbol::NUM_ADD => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::NumAdd, arguments, layout) self.build_run_low_level(sym, &LowLevel::NumAdd, arguments, layout)
} }
Symbol::NUM_ACOS => {
self.build_run_low_level(sym, &LowLevel::NumAcos, arguments, layout)
}
Symbol::NUM_ASIN => {
self.build_run_low_level(sym, &LowLevel::NumAsin, arguments, layout)
}
Symbol::NUM_ATAN => {
self.build_run_low_level(sym, &LowLevel::NumAtan, arguments, layout)
}
Symbol::NUM_POW_INT => self.build_run_low_level(
sym,
&LowLevel::NumPowInt,
arguments,
layout,
),
Symbol::NUM_SUB => { Symbol::NUM_SUB => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout) self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout)
} }
Symbol::BOOL_EQ => { Symbol::BOOL_EQ => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout) self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout)
} }
x if x
.module_string(&self.env().interns)
.starts_with(ModuleName::APP) =>
{
let fn_name = LayoutIds::default()
.get(*func_sym, layout)
.to_symbol_string(*func_sym, &self.env().interns);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(arguments)?;
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
}
x => Err(format!("the function, {:?}, is not yet implemented", x)), x => Err(format!("the function, {:?}, is not yet implemented", x)),
} }
} }
@ -199,6 +252,34 @@ where
x => Err(format!("layout, {:?}, not implemented yet", x)), x => Err(format!("layout, {:?}, not implemented yet", x)),
} }
} }
LowLevel::NumAcos => self.build_fn_call(
sym,
bitcode::NUM_ACOS.to_string(),
args,
&[layout.clone()],
layout,
),
LowLevel::NumAsin => self.build_fn_call(
sym,
bitcode::NUM_ASIN.to_string(),
args,
&[layout.clone()],
layout,
),
LowLevel::NumAtan => self.build_fn_call(
sym,
bitcode::NUM_ATAN.to_string(),
args,
&[layout.clone()],
layout,
),
LowLevel::NumPowInt => self.build_fn_call(
sym,
bitcode::NUM_POW_INT.to_string(),
args,
&[layout.clone(), layout.clone()],
layout,
),
LowLevel::NumSub => { LowLevel::NumSub => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method. // TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout { match layout {
@ -218,6 +299,17 @@ where
} }
} }
/// build_fn_call creates a call site for a function.
/// This includes dealing with things like saving regs and propagating the returned value.
fn build_fn_call(
&mut self,
dst: &Symbol,
fn_name: String,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_abs_i64 stores the absolute value of src into dst. /// build_num_abs_i64 stores the absolute value of src into dst.
/// It only deals with inputs and outputs of i64 type. /// It only deals with inputs and outputs of i64 type.
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>; fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>;
@ -312,11 +404,7 @@ where
/// set_free_map sets the free map to the given map. /// set_free_map sets the free map to the given map.
fn set_free_map(&mut self, map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>); fn set_free_map(&mut self, map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>);
/// set_not_leaf_function lets the backend know that it is not a leaf function.
fn set_not_leaf_function(&mut self);
/// scan_ast runs through the ast and fill the last seen map. /// scan_ast runs through the ast and fill the last seen map.
/// It also checks if the function is a leaf function or not.
/// This must iterate through the ast in the same way that build_stmt does. i.e. then before else. /// This must iterate through the ast in the same way that build_stmt does. i.e. then before else.
fn scan_ast(&mut self, stmt: &Stmt<'a>) { fn scan_ast(&mut self, stmt: &Stmt<'a>) {
match stmt { match stmt {
@ -443,18 +531,12 @@ where
} }
match call_type { match call_type {
CallType::ByName { name: sym, .. } => { CallType::ByName { .. } => {}
// For functions that we won't inline, we should not be a leaf function.
if !INLINED_SYMBOLS.contains(sym) {
self.set_not_leaf_function();
}
}
CallType::ByPointer { name: sym, .. } => { CallType::ByPointer { name: sym, .. } => {
self.set_not_leaf_function();
self.set_last_seen(*sym, stmt); self.set_last_seen(*sym, stmt);
} }
CallType::LowLevel { .. } => {} CallType::LowLevel { .. } => {}
CallType::Foreign { .. } => self.set_not_leaf_function(), CallType::Foreign { .. } => {}
} }
} }
} }

View file

@ -1,8 +1,8 @@
use crate::generic64::{aarch64, x86_64, Backend64Bit}; use crate::generic64::{aarch64, x86_64, Backend64Bit};
use crate::{Backend, Env, Relocation, INLINED_SYMBOLS}; use crate::{Backend, Env, Relocation};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use object::write; use object::write;
use object::write::{Object, StandardSection, Symbol, SymbolSection}; use object::write::{Object, StandardSection, StandardSegment, Symbol, SymbolSection};
use object::{ use object::{
Architecture, BinaryFormat, Endianness, RelocationEncoding, RelocationKind, SectionKind, Architecture, BinaryFormat, Endianness, RelocationEncoding, RelocationKind, SectionKind,
SymbolFlags, SymbolKind, SymbolScope, SymbolFlags, SymbolKind, SymbolScope,
@ -71,7 +71,6 @@ fn build_object<'a, B: Backend<'a>>(
mut backend: B, mut backend: B,
mut output: Object, mut output: Object,
) -> Result<Object, String> { ) -> Result<Object, String> {
let text = output.section_id(StandardSection::Text);
let data_section = output.section_id(StandardSection::Data); let data_section = output.section_id(StandardSection::Data);
let comment = output.add_section(vec![], b"comment".to_vec(), SectionKind::OtherString); let comment = output.add_section(vec![], b"comment".to_vec(), SectionKind::OtherString);
output.append_section_data( output.append_section_data(
@ -84,15 +83,16 @@ fn build_object<'a, B: Backend<'a>>(
let mut layout_ids = roc_mono::layout::LayoutIds::default(); let mut layout_ids = roc_mono::layout::LayoutIds::default();
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
for ((sym, layout), proc) in procedures { for ((sym, layout), proc) in procedures {
// This is temporary until we support passing args to functions.
if INLINED_SYMBOLS.contains(&sym) {
continue;
}
let fn_name = layout_ids let fn_name = layout_ids
.get(sym, &layout) .get(sym, &layout)
.to_symbol_string(sym, &env.interns); .to_symbol_string(sym, &env.interns);
let section_id = output.add_section(
output.segment_name(StandardSegment::Text).to_vec(),
format!(".text.{}", fn_name).as_bytes().to_vec(),
SectionKind::Text,
);
let proc_symbol = Symbol { let proc_symbol = Symbol {
name: fn_name.as_bytes().to_vec(), name: fn_name.as_bytes().to_vec(),
value: 0, value: 0,
@ -106,19 +106,20 @@ fn build_object<'a, B: Backend<'a>>(
SymbolScope::Linkage SymbolScope::Linkage
}, },
weak: false, weak: false,
section: SymbolSection::Section(text), section: SymbolSection::Section(section_id),
flags: SymbolFlags::None, flags: SymbolFlags::None,
}; };
let proc_id = output.add_symbol(proc_symbol); let proc_id = output.add_symbol(proc_symbol);
procs.push((fn_name, proc_id, proc)); procs.push((fn_name, section_id, proc_id, proc));
} }
// Build procedures. // Build procedures.
for (fn_name, proc_id, proc) in procs { let mut relocations = bumpalo::vec![in env.arena];
for (fn_name, section_id, proc_id, proc) in procs {
let mut local_data_index = 0; let mut local_data_index = 0;
let (proc_data, relocations) = backend.build_proc(proc)?; let (proc_data, relocs) = backend.build_proc(proc)?;
let proc_offset = output.add_symbol_data(proc_id, text, proc_data, 16); let proc_offset = output.add_symbol_data(proc_id, section_id, proc_data, 16);
for reloc in relocations { for reloc in relocs {
let elfreloc = match reloc { let elfreloc = match reloc {
Relocation::LocalData { offset, data } => { Relocation::LocalData { offset, data } => {
let data_symbol = write::Symbol { let data_symbol = write::Symbol {
@ -130,7 +131,7 @@ fn build_object<'a, B: Backend<'a>>(
kind: SymbolKind::Data, kind: SymbolKind::Data,
scope: SymbolScope::Compilation, scope: SymbolScope::Compilation,
weak: false, weak: false,
section: write::SymbolSection::Section(data_section), section: SymbolSection::Section(data_section),
flags: SymbolFlags::None, flags: SymbolFlags::None,
}; };
local_data_index += 1; local_data_index += 1;
@ -156,10 +157,26 @@ fn build_object<'a, B: Backend<'a>>(
addend: -4, addend: -4,
} }
} else { } else {
return Err(format!("failed to find symbol for {:?}", name)); return Err(format!("failed to find data symbol for {:?}", name));
} }
} }
Relocation::LinkedFunction { offset, name } => { Relocation::LinkedFunction { offset, name } => {
// If the symbol is an undefined zig builtin, we need to add it here.
if output.symbol_id(name.as_bytes()) == None
&& name.starts_with("roc_builtins.")
{
let builtin_symbol = Symbol {
name: name.as_bytes().to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: SymbolSection::Undefined,
flags: SymbolFlags::None,
};
output.add_symbol(builtin_symbol);
}
if let Some(sym_id) = output.symbol_id(name.as_bytes()) { if let Some(sym_id) = output.symbol_id(name.as_bytes()) {
write::Relocation { write::Relocation {
offset: offset + proc_offset, offset: offset + proc_offset,
@ -170,14 +187,17 @@ fn build_object<'a, B: Backend<'a>>(
addend: -4, addend: -4,
} }
} else { } else {
return Err(format!("failed to find symbol for {:?}", name)); return Err(format!("failed to find fn symbol for {:?}", name));
} }
} }
}; };
output relocations.push((section_id, elfreloc));
.add_relocation(text, elfreloc)
.map_err(|e| format!("{:?}", e))?;
} }
} }
for (section_id, reloc) in relocations {
output
.add_relocation(section_id, reloc)
.map_err(|e| format!("{:?}", e))?;
}
Ok(output) Ok(output)
} }

View file

@ -139,32 +139,93 @@ mod gen_num {
assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64); assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64);
} }
/*
#[test] #[test]
fn f64_sqrt() { fn gen_int_eq() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
when Num.sqrt 100 is 4 == 4
Ok val -> val
Err _ -> -1
"# "#
), ),
10.0, true,
f64 bool
);
assert_evals_to!(
indoc!(
r#"
3 == 4
"#
),
false,
bool
); );
} }
#[test] #[test]
fn f64_round_old() { fn gen_basic_fn() {
assert_evals_to!("Num.round 3.6", 4, i64); assert_evals_to!(
indoc!(
r#"
always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64)
always42 = \_ -> 42
always42 5
"#
),
42,
i64
);
} }
#[test] #[test]
fn f64_abs() { fn gen_wrap_add_nums() {
assert_evals_to!("Num.abs -4.7", 4.7, f64); assert_evals_to!(
assert_evals_to!("Num.abs 5.8", 5.8, f64); indoc!(
r#"
add2 = \num1, num2 -> num1 + num2
add2 4 5
"#
),
9,
i64
);
}
#[test]
fn gen_wrap_add_nums_force_stack() {
assert_evals_to!(
indoc!(
r#"
add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9
add9 1 2 3 4 5 6 7 8 9
"#
),
45,
i64
);
}
#[test]
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
#[test]
fn acos() {
assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64);
}
#[test]
fn asin() {
assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64);
}
#[test]
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
} }
#[test] #[test]
@ -208,6 +269,55 @@ mod gen_num {
); );
} }
#[test]
fn gen_fib_fn() {
assert_evals_to!(
indoc!(
r#"
fib = \n ->
if n == 0 then
0
else if n == 1 then
1
else
(fib (n - 1)) + (fib (n - 2))
fib 10
"#
),
55,
i64
);
}
/*
#[test]
fn f64_sqrt() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!(
indoc!(
r#"
when Num.sqrt 100 is
Ok val -> val
Err _ -> -1
"#
),
10.0,
f64
);
}
#[test]
fn f64_round_old() {
assert_evals_to!("Num.round 3.6", 4, i64);
}
#[test]
fn f64_abs() {
assert_evals_to!("Num.abs -4.7", 4.7, f64);
assert_evals_to!("Num.abs 5.8", 5.8, f64);
}
#[test] #[test]
fn gen_float_eq() { fn gen_float_eq() {
assert_evals_to!( assert_evals_to!(
@ -221,21 +331,6 @@ mod gen_num {
); );
} }
#[test]
fn gen_wrap_add_nums() {
assert_evals_to!(
indoc!(
r#"
add2 = \num1, num2 -> num1 + num2
add2 4 5
"#
),
9,
i64
);
}
#[test] #[test]
fn gen_div_f64() { fn gen_div_f64() {
// FIXME this works with normal types, but fails when checking uniqueness types // FIXME this works with normal types, but fails when checking uniqueness types
@ -251,20 +346,7 @@ mod gen_num {
f64 f64
); );
} }
*/
#[test]
fn gen_int_eq() {
assert_evals_to!(
indoc!(
r#"
4 == 4
"#
),
true,
bool
);
}
/*
#[test] #[test]
fn gen_int_neq() { fn gen_int_neq() {
assert_evals_to!( assert_evals_to!(
@ -630,21 +712,6 @@ mod gen_num {
); );
} }
#[test]
fn gen_basic_fn() {
assert_evals_to!(
indoc!(
r#"
always42 : Num.Num Num.Integer -> Num.Num Num.Integer
always42 = \_ -> 42
always42 5
"#
),
42,
i64
);
}
#[test] #[test]
fn int_to_float() { fn int_to_float() {
@ -690,16 +757,6 @@ mod gen_num {
assert_evals_to!("Num.floor 1.9", 1, i64); assert_evals_to!("Num.floor 1.9", 1, i64);
} }
#[test]
fn pow_int() {
assert_evals_to!("Num.powInt 2 3", 8, i64);
}
#[test]
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
// #[test] // #[test]
// #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)] // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
// fn int_overflow() { // fn int_overflow() {

View file

@ -1,5 +1,7 @@
use libloading::Library; use libloading::Library;
use roc_build::link::{link, LinkType}; use roc_build::link::{link, LinkType};
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use tempfile::tempdir; use tempfile::tempdir;
@ -51,6 +53,7 @@ pub fn helper<'a>(
src_dir, src_dir,
exposed_types, exposed_types,
8, 8,
builtin_defs_map,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");
@ -173,10 +176,14 @@ pub fn helper<'a>(
.expect("failed to build output object"); .expect("failed to build output object");
std::fs::write(&app_o_file, module_out).expect("failed to write object to file"); std::fs::write(&app_o_file, module_out).expect("failed to write object to file");
// std::fs::copy(&app_o_file, "/tmp/app.o").unwrap();
let (mut child, dylib_path) = link( let (mut child, dylib_path) = link(
&target, &target,
app_o_file.clone(), app_o_file.clone(),
&[app_o_file.to_str().unwrap()], // Long term we probably want a smarter way to link in zig builtins.
// With the current method all methods are kept and it adds about 100k to all outputs.
&[app_o_file.to_str().unwrap(), bitcode::OBJ_PATH],
LinkType::Dylib, LinkType::Dylib,
) )
.expect("failed to link dynamic library"); .expect("failed to link dynamic library");
@ -186,7 +193,6 @@ pub fn helper<'a>(
// Load the dylib // Load the dylib
let path = dylib_path.as_path().to_str().unwrap(); let path = dylib_path.as_path().to_str().unwrap();
// std::fs::copy(&app_o_file, "/tmp/app.o").unwrap();
// std::fs::copy(&path, "/tmp/libapp.so").unwrap(); // std::fs::copy(&path, "/tmp/libapp.so").unwrap();
let lib = Library::new(path).expect("failed to load shared library"); let lib = Library::new(path).expect("failed to load shared library");

View file

@ -18,6 +18,7 @@ roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
parking_lot = { version = "0.11", features = ["deadlock_detection"] } parking_lot = { version = "0.11", features = ["deadlock_detection"] }

View file

@ -6,7 +6,7 @@ use crossbeam::thread;
use parking_lot::Mutex; use parking_lot::Mutex;
use roc_builtins::std::{Mode, StdLib}; use roc_builtins::std::{Mode, StdLib};
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::def::Declaration; use roc_can::def::{Declaration, Def};
use roc_can::module::{canonicalize_module_defs, Module}; use roc_can::module::{canonicalize_module_defs, Module};
use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_constrain::module::{ use roc_constrain::module::{
@ -27,7 +27,7 @@ use roc_parse::header::{
ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent, ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
}; };
use roc_parse::module::module_defs; use roc_parse::module::module_defs;
use roc_parse::parser::{self, Fail, Parser}; use roc_parse::parser::{self, ParseProblem, Parser, SyntaxError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_solve::module::SolvedModule; use roc_solve::module::SolvedModule;
use roc_solve::solve; use roc_solve::solve;
@ -762,6 +762,8 @@ enum Msg<'a> {
subs: Subs, subs: Subs,
exposed_to_host: MutMap<Symbol, Variable>, exposed_to_host: MutMap<Symbol, Variable>,
}, },
FailedToParse(ParseProblem<'a, SyntaxError<'a>>),
} }
#[derive(Debug)] #[derive(Debug)]
@ -968,20 +970,20 @@ enum WorkerMsg {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum LoadingProblem { pub enum LoadingProblem<'a> {
FileProblem { FileProblem {
filename: PathBuf, filename: PathBuf,
error: io::ErrorKind, error: io::ErrorKind,
msg: &'static str, msg: &'static str,
}, },
ParsingFailed { ParsingFailed(ParseProblem<'a, SyntaxError<'a>>),
filename: PathBuf,
fail: Fail,
},
UnexpectedHeader(String), UnexpectedHeader(String),
MsgChannelDied, MsgChannelDied,
ErrJoiningWorkerThreads, ErrJoiningWorkerThreads,
TriedToImportAppModule, TriedToImportAppModule,
/// a formatted report of parsing failure
ParsingFailedReport(String),
} }
pub enum Phases { pub enum Phases {
@ -998,7 +1000,7 @@ fn enqueue_task<'a>(
injector: &Injector<BuildTask<'a>>, injector: &Injector<BuildTask<'a>>,
listeners: &[Sender<WorkerMsg>], listeners: &[Sender<WorkerMsg>],
task: BuildTask<'a>, task: BuildTask<'a>,
) -> Result<(), LoadingProblem> { ) -> Result<(), LoadingProblem<'a>> {
injector.push(task); injector.push(task);
for listener in listeners { for listener in listeners {
@ -1010,14 +1012,18 @@ fn enqueue_task<'a>(
Ok(()) Ok(())
} }
pub fn load_and_typecheck( pub fn load_and_typecheck<'a, F>(
arena: &Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
stdlib: &StdLib, stdlib: &'a StdLib,
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<LoadedModule, LoadingProblem> { look_up_builtin: F,
) -> Result<LoadedModule, LoadingProblem<'a>>
where
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
{
use LoadResult::*; use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?; let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
@ -1030,20 +1036,25 @@ pub fn load_and_typecheck(
exposed_types, exposed_types,
Phase::SolveTypes, Phase::SolveTypes,
ptr_bytes, ptr_bytes,
look_up_builtin,
)? { )? {
Monomorphized(_) => unreachable!(""), Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module), TypeChecked(module) => Ok(module),
} }
} }
pub fn load_and_monomorphize<'a>( pub fn load_and_monomorphize<'a, F>(
arena: &'a Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
stdlib: &'a StdLib, stdlib: &'a StdLib,
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> { look_up_builtin: F,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>>
where
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
{
use LoadResult::*; use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?; let load_start = LoadStart::from_path(arena, filename, stdlib.mode)?;
@ -1056,13 +1067,15 @@ pub fn load_and_monomorphize<'a>(
exposed_types, exposed_types,
Phase::MakeSpecializations, Phase::MakeSpecializations,
ptr_bytes, ptr_bytes,
look_up_builtin,
)? { )? {
Monomorphized(module) => Ok(module), Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""), TypeChecked(_) => unreachable!(""),
} }
} }
pub fn load_and_monomorphize_from_str<'a>( #[allow(clippy::too_many_arguments)]
pub fn load_and_monomorphize_from_str<'a, F>(
arena: &'a Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
src: &'a str, src: &'a str,
@ -1070,7 +1083,11 @@ pub fn load_and_monomorphize_from_str<'a>(
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> { look_up_builtin: F,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>>
where
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
{
use LoadResult::*; use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, src, stdlib.mode)?; let load_start = LoadStart::from_str(arena, filename, src, stdlib.mode)?;
@ -1083,6 +1100,7 @@ pub fn load_and_monomorphize_from_str<'a>(
exposed_types, exposed_types,
Phase::MakeSpecializations, Phase::MakeSpecializations,
ptr_bytes, ptr_bytes,
look_up_builtin,
)? { )? {
Monomorphized(module) => Ok(module), Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""), TypeChecked(_) => unreachable!(""),
@ -1101,7 +1119,7 @@ impl<'a> LoadStart<'a> {
arena: &'a Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
mode: Mode, mode: Mode,
) -> Result<Self, LoadingProblem> { ) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default())); let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0); let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids)); let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1134,7 +1152,7 @@ impl<'a> LoadStart<'a> {
filename: PathBuf, filename: PathBuf,
src: &'a str, src: &'a str,
mode: Mode, mode: Mode,
) -> Result<Self, LoadingProblem> { ) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default())); let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0); let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids)); let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -1211,7 +1229,8 @@ enum LoadResult<'a> {
/// and then linking them together, and possibly caching them by the hash of their /// and then linking them together, and possibly caching them by the hash of their
/// specializations, so if none of their specializations changed, we don't even need /// specializations, so if none of their specializations changed, we don't even need
/// to rebuild the module and can link in the cached one directly.) /// to rebuild the module and can link in the cached one directly.)
fn load<'a>( #[allow(clippy::too_many_arguments)]
fn load<'a, F>(
arena: &'a Bump, arena: &'a Bump,
//filename: PathBuf, //filename: PathBuf,
load_start: LoadStart<'a>, load_start: LoadStart<'a>,
@ -1220,8 +1239,10 @@ fn load<'a>(
exposed_types: SubsByModule, exposed_types: SubsByModule,
goal_phase: Phase, goal_phase: Phase,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<LoadResult<'a>, LoadingProblem> look_up_builtins: F,
) -> Result<LoadResult<'a>, LoadingProblem<'a>>
where where
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
{ {
let LoadStart { let LoadStart {
arc_modules, arc_modules,
@ -1310,11 +1331,12 @@ where
let injector = &injector; let injector = &injector;
// Record this thread's handle so the main thread can join it later. // Record this thread's handle so the main thread can join it later.
thread_scope let res_join_handle = thread_scope
.builder() .builder()
.stack_size(EXPANDED_STACK_SIZE) .stack_size(EXPANDED_STACK_SIZE)
.spawn(move |_| { .spawn(move |_| {
// Keep listening until we receive a Shutdown msg // Keep listening until we receive a Shutdown msg
for msg in worker_msg_rx.iter() { for msg in worker_msg_rx.iter() {
match msg { match msg {
WorkerMsg::Shutdown => { WorkerMsg::Shutdown => {
@ -1322,7 +1344,7 @@ where
// shut down the thread, so when the main thread // shut down the thread, so when the main thread
// blocks on joining with all the worker threads, // blocks on joining with all the worker threads,
// it can finally exit too! // it can finally exit too!
return; return Ok(());
} }
WorkerMsg::TaskAdded => { WorkerMsg::TaskAdded => {
// Find a task - either from this thread's queue, // Find a task - either from this thread's queue,
@ -1335,14 +1357,27 @@ where
// added. In that case, do nothing, and keep waiting // added. In that case, do nothing, and keep waiting
// until we receive a Shutdown message. // until we receive a Shutdown message.
if let Some(task) = find_task(&worker, injector, stealers) { if let Some(task) = find_task(&worker, injector, stealers) {
run_task( let result = run_task(
task, task,
worker_arena, worker_arena,
src_dir, src_dir,
msg_tx.clone(), msg_tx.clone(),
ptr_bytes, ptr_bytes,
) look_up_builtins,
.expect("Msg channel closed unexpectedly."); );
match result {
Ok(()) => {}
Err(LoadingProblem::MsgChannelDied) => {
panic!("Msg channel closed unexpectedly.")
}
Err(LoadingProblem::ParsingFailed(problem)) => {
msg_tx.send(Msg::FailedToParse(problem)).unwrap();
}
Err(other) => {
return Err(other);
}
}
} }
} }
} }
@ -1351,8 +1386,11 @@ where
// Needed to prevent a borrow checker error about this closure // Needed to prevent a borrow checker error about this closure
// outliving its enclosing function. // outliving its enclosing function.
drop(worker_msg_rx); drop(worker_msg_rx);
})
.unwrap(); Ok(())
});
res_join_handle.unwrap();
} }
let mut state = State { let mut state = State {
@ -1440,6 +1478,51 @@ where
exposed_to_host, exposed_to_host,
))); )));
} }
Msg::FailedToParse(problem) => {
// Shut down all the worker threads.
for listener in worker_listeners {
listener
.send(WorkerMsg::Shutdown)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
}
use roc_reporting::report::{
parse_problem, RocDocAllocator, DEFAULT_PALETTE,
};
// TODO this is not in fact safe
let src = unsafe { from_utf8_unchecked(problem.bytes) };
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
let mut module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| {
panic!("There were still outstanding Arc references to module_ids")
})
.into_inner()
.into_module_ids();
let module_id =
module_ids.get_or_insert(&"find module name somehow?".into());
let interns = Interns {
module_ids,
all_ident_ids: state.constrained_ident_ids,
};
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let starting_line = 0;
let report =
parse_problem(&alloc, problem.filename.clone(), starting_line, problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
return Err(LoadingProblem::ParsingFailedReport(buf));
}
msg => { msg => {
// This is where most of the main thread's work gets done. // This is where most of the main thread's work gets done.
// Everything up to this point has been setting up the threading // Everything up to this point has been setting up the threading
@ -1468,7 +1551,7 @@ fn start_tasks<'a>(
state: &mut State<'a>, state: &mut State<'a>,
injector: &Injector<BuildTask<'a>>, injector: &Injector<BuildTask<'a>>,
worker_listeners: &'a [Sender<WorkerMsg>], worker_listeners: &'a [Sender<WorkerMsg>],
) -> Result<(), LoadingProblem> { ) -> Result<(), LoadingProblem<'a>> {
for (module_id, phase) in work { for (module_id, phase) in work {
for task in start_phase(module_id, phase, state) { for task in start_phase(module_id, phase, state) {
enqueue_task(&injector, worker_listeners, task)? enqueue_task(&injector, worker_listeners, task)?
@ -1485,7 +1568,7 @@ fn update<'a>(
injector: &Injector<BuildTask<'a>>, injector: &Injector<BuildTask<'a>>,
worker_listeners: &'a [Sender<WorkerMsg>], worker_listeners: &'a [Sender<WorkerMsg>],
arena: &'a Bump, arena: &'a Bump,
) -> Result<State<'a>, LoadingProblem> { ) -> Result<State<'a>, LoadingProblem<'a>> {
use self::Msg::*; use self::Msg::*;
match msg { match msg {
@ -1942,6 +2025,9 @@ fn update<'a>(
Msg::FinishedAllSpecialization { .. } => { Msg::FinishedAllSpecialization { .. } => {
unreachable!(); unreachable!();
} }
Msg::FailedToParse(_) => {
unreachable!();
}
} }
} }
@ -2064,7 +2150,7 @@ fn load_pkg_config<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode, mode: Mode,
) -> Result<Msg<'a>, LoadingProblem> { ) -> Result<Msg<'a>, LoadingProblem<'a>> {
let module_start_time = SystemTime::now(); let module_start_time = SystemTime::now();
let filename = PathBuf::from(src_dir); let filename = PathBuf::from(src_dir);
@ -2074,9 +2160,10 @@ fn load_pkg_config<'a>(
let file_io_duration = file_io_start.elapsed().unwrap(); let file_io_duration = file_io_start.elapsed().unwrap();
match file { match file {
Ok(bytes) => { Ok(bytes_vec) => {
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new(arena.alloc(bytes), Attempting::Module); let bytes = arena.alloc(bytes_vec);
let parse_state = parser::State::new_in(arena, bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state); let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap(); let parse_header_duration = parse_start.elapsed().unwrap();
@ -2091,19 +2178,19 @@ fn load_pkg_config<'a>(
effect_module_timing.parse_header = parse_header_duration; effect_module_timing.parse_header = parse_header_duration;
match parsed { match parsed {
Ok((ast::Module::Interface { header }, _parse_state)) => { Ok((_, ast::Module::Interface { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!( Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got Interface with header\n{:?}", "expected platform/package module, got Interface with header\n{:?}",
header header
))) )))
} }
Ok((ast::Module::App { header }, _parse_state)) => { Ok((_, ast::Module::App { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!( Err(LoadingProblem::UnexpectedHeader(format!(
"expected platform/package module, got App with header\n{:?}", "expected platform/package module, got App with header\n{:?}",
header header
))) )))
} }
Ok((ast::Module::Platform { header }, parser_state)) => { Ok((_, ast::Module::Platform { header }, parser_state)) => {
// make a Pkg-Config module that ultimately exposes `main` to the host // make a Pkg-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module( let pkg_config_module_msg = fabricate_pkg_config_module(
arena, arena,
@ -2131,7 +2218,9 @@ fn load_pkg_config<'a>(
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
} }
Err((fail, _)) => Err(LoadingProblem::ParsingFailed { filename, fail }), Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, bytes),
)),
} }
} }
@ -2152,7 +2241,7 @@ fn load_module<'a>(
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>, arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageOrPath<'a>>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
mode: Mode, mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let module_start_time = SystemTime::now(); let module_start_time = SystemTime::now();
let mut filename = PathBuf::new(); let mut filename = PathBuf::new();
@ -2240,9 +2329,9 @@ fn parse_header<'a>(
mode: Mode, mode: Mode,
src_bytes: &'a [u8], src_bytes: &'a [u8],
start_time: SystemTime, start_time: SystemTime,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new(src_bytes, Attempting::Module); let parse_state = parser::State::new_in(arena, src_bytes, Attempting::Module);
let parsed = roc_parse::module::header().parse(&arena, parse_state); let parsed = roc_parse::module::header().parse(&arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap(); let parse_header_duration = parse_start.elapsed().unwrap();
@ -2253,7 +2342,7 @@ fn parse_header<'a>(
module_timing.parse_header = parse_header_duration; module_timing.parse_header = parse_header_duration;
match parsed { match parsed {
Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header( Ok((_, ast::Module::Interface { header }, parse_state)) => Ok(send_header(
Located { Located {
region: header.name.region, region: header.name.region,
value: ModuleNameEnum::Interface(header.name.value), value: ModuleNameEnum::Interface(header.name.value),
@ -2269,7 +2358,7 @@ fn parse_header<'a>(
ident_ids_by_module, ident_ids_by_module,
module_timing, module_timing,
)), )),
Ok((ast::Module::App { header }, parse_state)) => { Ok((_, ast::Module::App { header }, parse_state)) => {
let mut pkg_config_dir = filename.clone(); let mut pkg_config_dir = filename.clone();
pkg_config_dir.pop(); pkg_config_dir.pop();
@ -2367,7 +2456,7 @@ fn parse_header<'a>(
}, },
} }
} }
Ok((ast::Module::Platform { header }, _parse_state)) => fabricate_effects_module( Ok((_, ast::Module::Platform { header }, _parse_state)) => fabricate_effects_module(
arena, arena,
&"", &"",
module_ids, module_ids,
@ -2376,7 +2465,9 @@ fn parse_header<'a>(
header, header,
module_timing, module_timing,
), ),
Err((fail, _)) => Err(LoadingProblem::ParsingFailed { filename, fail }), Err((_, fail, _)) => Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(filename, src_bytes),
)),
} }
} }
@ -2389,7 +2480,7 @@ fn load_filename<'a>(
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_start_time: SystemTime, module_start_time: SystemTime,
mode: Mode, mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = SystemTime::now(); let file_io_start = SystemTime::now();
let file = fs::read(&filename); let file = fs::read(&filename);
let file_io_duration = file_io_start.elapsed().unwrap(); let file_io_duration = file_io_start.elapsed().unwrap();
@ -2425,7 +2516,7 @@ fn load_from_str<'a>(
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_start_time: SystemTime, module_start_time: SystemTime,
mode: Mode, mode: Mode,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = SystemTime::now(); let file_io_start = SystemTime::now();
let file_io_duration = file_io_start.elapsed().unwrap(); let file_io_duration = file_io_start.elapsed().unwrap();
@ -2993,7 +3084,7 @@ fn fabricate_pkg_config_module<'a>(
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
header: &PlatformHeader<'a>, header: &PlatformHeader<'a>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
header.provides.clone().into_bump_slice(); header.provides.clone().into_bump_slice();
@ -3022,7 +3113,7 @@ fn fabricate_effects_module<'a>(
mode: Mode, mode: Mode,
header: PlatformHeader<'a>, header: PlatformHeader<'a>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let num_exposes = header.provides.len() + 1; let num_exposes = header.provides.len() + 1;
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes); let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
@ -3292,7 +3383,8 @@ fn unpack_exposes_entries<'a>(
output output
} }
fn canonicalize_and_constrain<'a>( #[allow(clippy::too_many_arguments)]
fn canonicalize_and_constrain<'a, F>(
arena: &'a Bump, arena: &'a Bump,
module_ids: &ModuleIds, module_ids: &ModuleIds,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
@ -3300,7 +3392,11 @@ fn canonicalize_and_constrain<'a>(
aliases: MutMap<Symbol, Alias>, aliases: MutMap<Symbol, Alias>,
mode: Mode, mode: Mode,
parsed: ParsedModule<'a>, parsed: ParsedModule<'a>,
) -> Result<Msg<'a>, LoadingProblem> { look_up_builtins: F,
) -> Result<Msg<'a>, LoadingProblem<'a>>
where
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
{
let canonicalize_start = SystemTime::now(); let canonicalize_start = SystemTime::now();
let ParsedModule { let ParsedModule {
@ -3338,6 +3434,7 @@ fn canonicalize_and_constrain<'a>(
exposed_imports, exposed_imports,
&exposed_symbols, &exposed_symbols,
&mut var_store, &mut var_store,
look_up_builtins,
); );
let canonicalize_end = SystemTime::now(); let canonicalize_end = SystemTime::now();
@ -3381,13 +3478,18 @@ fn canonicalize_and_constrain<'a>(
} }
} }
fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, LoadingProblem> { fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, LoadingProblem<'a>> {
let mut module_timing = header.module_timing; let mut module_timing = header.module_timing;
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new(&header.src, Attempting::Module); let parse_state = parser::State::new_in(arena, &header.src, Attempting::Module);
let (parsed_defs, _) = module_defs() let parsed_defs = match module_defs().parse(&arena, parse_state) {
.parse(&arena, parse_state) Ok((_, success, _state)) => success,
.expect("TODO gracefully handle parse error on module defs. IMPORTANT: Bail out entirely if there are any BadUtf8 problems! That means the whole source file is not valid UTF-8 and any other errors we report may get mis-reported. We rely on this for safety in an `unsafe` block later on in this function."); Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed(
fail.into_parse_problem(header.module_path, header.src),
));
}
};
let parsed_defs = parsed_defs.into_bump_slice(); let parsed_defs = parsed_defs.into_bump_slice();
@ -3761,13 +3863,17 @@ fn add_def_to_module<'a>(
} }
} }
fn run_task<'a>( fn run_task<'a, F>(
task: BuildTask<'a>, task: BuildTask<'a>,
arena: &'a Bump, arena: &'a Bump,
src_dir: &Path, src_dir: &Path,
msg_tx: MsgSender<'a>, msg_tx: MsgSender<'a>,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<(), LoadingProblem> { look_up_builtins: F,
) -> Result<(), LoadingProblem<'a>>
where
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
{
use BuildTask::*; use BuildTask::*;
let msg = match task { let msg = match task {
@ -3803,6 +3909,7 @@ fn run_task<'a>(
aliases, aliases,
mode, mode,
parsed, parsed,
look_up_builtins,
), ),
Solve { Solve {
module, module,

View file

@ -42,7 +42,7 @@ cheapestOpen = \costFunction, model ->
else else
Ok smallestSoFar Ok smallestSoFar
Set.foldl model.openSet folder (Err KeyNotFound) Set.walk model.openSet folder (Err KeyNotFound)
|> Result.map (\x -> x.position) |> Result.map (\x -> x.position)
@ -101,11 +101,11 @@ astar = \costFn, moveFn, goal, model ->
neighbours = moveFn current neighbours = moveFn current
newNeighbours = Set.diff neighbours modelPopped.evaluated newNeighbours = Set.difference neighbours modelPopped.evaluated
modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours } modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours }
modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours modelWithCosts = Set.walk newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
astar costFn moveFn goal modelWithCosts astar costFn moveFn goal modelWithCosts

View file

@ -42,7 +42,7 @@ cheapestOpen = \costFunction, model ->
else else
Ok smallestSoFar Ok smallestSoFar
Set.foldl model.openSet folder (Err KeyNotFound) Set.walk model.openSet folder (Err KeyNotFound)
|> Result.map (\x -> x.position) |> Result.map (\x -> x.position)
@ -101,11 +101,11 @@ astar = \costFn, moveFn, goal, model ->
neighbours = moveFn current neighbours = moveFn current
newNeighbours = Set.diff neighbours modelPopped.evaluated newNeighbours = Set.difference neighbours modelPopped.evaluated
modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours } modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours }
modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours modelWithCosts = Set.walk newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
astar costFn moveFn goal modelWithCosts astar costFn moveFn goal modelWithCosts

View file

@ -1,426 +1,7 @@
extern crate bumpalo; extern crate bumpalo;
use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint;
use roc_can::env::Env;
use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator;
use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, Import};
use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_solve::solve;
use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type;
use std::hash::Hash;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
}
#[allow(dead_code)]
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<solve::TypeError>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: MutMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
}
/// In --release builds, don't increase the stack size. Run the test normally.
/// This way, we find out if any of our tests are blowing the stack even after
/// optimizations in release builds.
#[allow(dead_code)]
#[cfg(not(debug_assertions))]
#[inline(always)]
pub fn with_larger_debug_stack<F>(run_test: F)
where
F: FnOnce() -> (),
F: Send,
F: 'static,
{
run_test()
}
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str)
}
#[allow(dead_code)]
pub fn uniq_expr(
expr_str: &str,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
}
#[allow(dead_code)]
pub fn uniq_expr_with(
arena: &Bump,
expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let home = test_home();
let CanExprOut {
loc_expr,
output,
problems,
var_store: mut old_var_store,
var,
interns,
..
} = can_expr_with(arena, home, expr_str);
// double check
let mut var_store = VarStore::new(old_var_store.fresh());
let expected2 = Expected::NoExpectation(Type::Variable(var));
let constraint = roc_constrain::uniq::constrain_declaration(
home,
&mut var_store,
Region::zero(),
&loc_expr,
declared_idents,
expected2,
);
let stdlib = uniq_stdlib();
let types = stdlib.types;
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
// load builtin values
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
let subs2 = Subs::new(var_store.into());
(
loc_expr, output, problems, subs2, var, constraint, home, interns,
)
}
pub struct CanExprOut {
pub loc_expr: Located<Expr>,
pub output: Output,
pub problems: Vec<Problem>,
pub home: ModuleId,
pub interns: Interns,
pub var_store: VarStore,
pub var: Variable,
pub constraint: Constraint,
}
#[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e
)
});
let mut var_store = VarStore::default();
let var = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(var));
let module_ids = ModuleIds::default();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar_expr(arena, &loc_expr);
let mut scope = Scope::new(home, &mut var_store);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr(
&mut env,
&mut var_store,
&mut scope,
Region::zero(),
&loc_expr.value,
);
let constraint = constrain_expr(
&roc_constrain::expr::Env {
rigids: ImMap::default(),
home,
},
loc_expr.region,
&loc_expr.value,
expected,
);
let types = roc_builtins::std::types();
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
//load builtin values
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
let mut all_ident_ids = MutMap::default();
// When pretty printing types, we may need the exposed builtins,
// so include them in the Interns we'll ultimately return.
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
all_ident_ids.insert(module_id, ident_ids);
}
all_ident_ids.insert(home, env.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids,
};
CanExprOut {
loc_expr,
output,
problems: env.problems,
home: env.home,
var_store,
interns,
var,
constraint,
}
}
#[allow(dead_code)]
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq,
{
let mut answer = MutMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn im_map_from_pairs<K, V, I>(pairs: I) -> ImMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq + Clone,
V: Clone,
{
let mut answer = ImMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn send_set_from<V, I>(elems: I) -> SendSet<V>
where
I: IntoIterator<Item = V>,
V: Hash + Eq + Clone,
{
let mut answer = SendSet::default();
for elem in elems {
answer.insert(elem);
}
answer
}
#[allow(dead_code)]
pub fn fixtures_dir<'a>() -> PathBuf { pub fn fixtures_dir<'a>() -> PathBuf {
Path::new("tests").join("fixtures").join("build") Path::new("tests").join("fixtures").join("build")
} }
#[allow(dead_code)]
pub fn builtins_dir<'a>() -> PathBuf {
PathBuf::new().join("builtins")
}
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
#[allow(dead_code)]
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
used.remove(unsafe { &Variable::unsafe_test_debug_variable(2) });
used.remove(unsafe { &Variable::unsafe_test_debug_variable(3) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Store(tipe, var, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
used.insert(*var);
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

View file

@ -18,6 +18,7 @@ mod test_load {
use crate::helpers::fixtures_dir; use crate::helpers::fixtures_dir;
use bumpalo::Bump; use bumpalo::Bump;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_can::builtins::builtin_defs_map;
use roc_can::def::Declaration::*; use roc_can::def::Declaration::*;
use roc_can::def::Def; use roc_can::def::Def;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
@ -86,6 +87,7 @@ mod test_load {
dir.path(), dir.path(),
exposed_types, exposed_types,
8, 8,
builtin_defs_map,
) )
}; };
@ -124,10 +126,11 @@ mod test_load {
let loaded = roc_load::file::load_and_typecheck( let loaded = roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
&roc_builtins::std::standard_stdlib(), arena.alloc(roc_builtins::std::standard_stdlib()),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
8, 8,
builtin_defs_map,
); );
let mut loaded_module = loaded.expect("Test module failed to load"); let mut loaded_module = loaded.expect("Test module failed to load");
@ -287,10 +290,11 @@ mod test_load {
let loaded = roc_load::file::load_and_typecheck( let loaded = roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
&roc_builtins::std::standard_stdlib(), arena.alloc(roc_builtins::std::standard_stdlib()),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
8, 8,
builtin_defs_map,
); );
let mut loaded_module = loaded.expect("Test module failed to load"); let mut loaded_module = loaded.expect("Test module failed to load");

View file

@ -12,6 +12,7 @@ pub enum LowLevel {
StrCountGraphemes, StrCountGraphemes,
StrFromInt, StrFromInt,
StrFromUtf8, StrFromUtf8,
StrFromFloat,
ListLen, ListLen,
ListGetUnsafe, ListGetUnsafe,
ListSet, ListSet,
@ -25,10 +26,26 @@ pub enum LowLevel {
ListPrepend, ListPrepend,
ListJoin, ListJoin,
ListMap, ListMap,
ListMapWithIndex,
ListKeepIf, ListKeepIf,
ListWalk, ListWalk,
ListWalkBackwards, ListWalkBackwards,
ListSum, ListSum,
ListKeepOks,
ListKeepErrs,
DictSize,
DictEmpty,
DictInsert,
DictRemove,
DictContains,
DictGetUnsafe,
DictKeys,
DictValues,
DictUnion,
DictIntersection,
DictDifference,
DictWalk,
SetFromList,
NumAdd, NumAdd,
NumAddWrap, NumAddWrap,
NumAddChecked, NumAddChecked,
@ -67,4 +84,5 @@ pub enum LowLevel {
And, And,
Or, Or,
Not, Not,
Hash,
} }

View file

@ -741,6 +741,21 @@ define_builtins! {
11 DEC: "#dec" // internal function that increments the refcount 11 DEC: "#dec" // internal function that increments the refcount
12 ARG_CLOSURE: "#arg_closure" // symbol used to store the closure record 12 ARG_CLOSURE: "#arg_closure" // symbol used to store the closure record
13 LIST_EQ: "#list_eq" // internal function that checks list equality 13 LIST_EQ: "#list_eq" // internal function that checks list equality
14 GENERIC_HASH: "#generic_hash" // hash of arbitrary layouts
15 GENERIC_HASH_REF: "#generic_hash_by_ref" // hash of arbitrary layouts, passed as an opaque pointer
16 GENERIC_EQ_REF: "#generic_eq_by_ref" // equality of arbitrary layouts, passed as an opaque pointer
17 GENERIC_RC_REF: "#generic_rc_by_ref" // refcount of arbitrary layouts, passed as an opaque pointer
18 GENERIC_EQ: "#generic_eq" // internal function that checks generic equality
// a user-defined function that we need to capture in a closure
// see e.g. Set.walk
19 USER_FUNCTION: "#user_function"
// A caller (wrapper) that we pass to zig for it to be able to call Roc functions
20 ZIG_FUNCTION_CALLER: "#zig_function_caller"
} }
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
@ -857,9 +872,10 @@ define_builtins! {
8 STR_STARTS_WITH: "startsWith" 8 STR_STARTS_WITH: "startsWith"
9 STR_ENDS_WITH: "endsWith" 9 STR_ENDS_WITH: "endsWith"
10 STR_FROM_INT: "fromInt" 10 STR_FROM_INT: "fromInt"
11 STR_FROM_UTF8: "fromUtf8" 11 STR_FROM_FLOAT: "fromFloat"
12 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias 12 STR_FROM_UTF8: "fromUtf8"
13 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias 13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias
@ -883,10 +899,15 @@ define_builtins! {
18 LIST_SUM: "sum" 18 LIST_SUM: "sum"
19 LIST_WALK: "walk" 19 LIST_WALK: "walk"
20 LIST_LAST: "last" 20 LIST_LAST: "last"
21 LIST_KEEP_OKS: "keepOks"
22 LIST_KEEP_ERRS: "keepErrs"
23 LIST_MAP_WITH_INDEX: "mapWithIndex"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias
1 RESULT_MAP: "map" 1 RESULT_MAP: "map"
2 RESULT_MAP_ERR: "mapErr"
3 RESULT_WITH_DEFAULT: "withDefault"
} }
6 DICT: "Dict" => { 6 DICT: "Dict" => {
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias 0 DICT_DICT: "Dict" imported // the Dict.Dict type alias
@ -894,18 +915,42 @@ define_builtins! {
2 DICT_EMPTY: "empty" 2 DICT_EMPTY: "empty"
3 DICT_SINGLETON: "singleton" 3 DICT_SINGLETON: "singleton"
4 DICT_GET: "get" 4 DICT_GET: "get"
5 DICT_INSERT: "insert" 5 DICT_GET_RESULT: "#get_result" // symbol used in the definition of Dict.get
6 DICT_WALK: "walk"
7 DICT_INSERT: "insert"
8 DICT_LEN: "len"
// This should not be exposed to users, its for testing the
// hash function ONLY
9 DICT_TEST_HASH: "hashTestOnly"
10 DICT_REMOVE: "remove"
11 DICT_CONTAINS: "contains"
12 DICT_KEYS: "keys"
13 DICT_VALUES: "values"
14 DICT_UNION: "union"
15 DICT_INTERSECTION: "intersection"
16 DICT_DIFFERENCE: "difference"
} }
7 SET: "Set" => { 7 SET: "Set" => {
0 SET_SET: "Set" imported // the Set.Set type alias 0 SET_SET: "Set" imported // the Set.Set type alias
1 SET_AT_SET: "@Set" // the Set.@Set private tag 1 SET_AT_SET: "@Set" // the Set.@Set private tag
2 SET_EMPTY: "empty" 2 SET_EMPTY: "empty"
3 SET_SINGLETON: "singleton" 3 SET_SINGLETON: "singleton"
4 SET_UNION: "union" 4 SET_LEN: "len"
5 SET_FOLDL: "foldl" 5 SET_INSERT: "insert"
6 SET_INSERT: "insert" 6 SET_REMOVE: "remove"
7 SET_REMOVE: "remove" 7 SET_UNION: "union"
8 SET_DIFF: "diff" 8 SET_DIFFERENCE: "difference"
9 SET_INTERSECTION: "intersection"
10 SET_TO_LIST: "toList"
11 SET_FROM_LIST: "fromList"
12 SET_WALK: "walk"
13 SET_WALK_USER_FUNCTION: "#walk_user_function"
14 SET_CONTAINS: "contains"
} }
num_modules: 8 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) num_modules: 8 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)

View file

@ -6,6 +6,13 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
fn should_borrow_layout(layout: &Layout) -> bool {
match layout {
Layout::Closure(_, _, _) => false,
_ => layout.is_refcounted(),
}
}
pub fn infer_borrow<'a>( pub fn infer_borrow<'a>(
arena: &'a Bump, arena: &'a Bump,
procs: &MutMap<(Symbol, Layout<'a>), Proc<'a>>, procs: &MutMap<(Symbol, Layout<'a>), Proc<'a>>,
@ -14,8 +21,8 @@ pub fn infer_borrow<'a>(
items: MutMap::default(), items: MutMap::default(),
}; };
for proc in procs.values() { for (key, proc) in procs {
param_map.visit_proc(arena, proc); param_map.visit_proc(arena, proc, key.clone());
} }
let mut env = BorrowInfState { let mut env = BorrowInfState {
@ -39,8 +46,8 @@ pub fn infer_borrow<'a>(
// TODO in the future I think we need to do this properly, and group // TODO in the future I think we need to do this properly, and group
// mutually recursive functions (or just make all their arguments owned) // mutually recursive functions (or just make all their arguments owned)
for proc in procs.values() { for (key, proc) in procs {
env.collect_proc(proc); env.collect_proc(proc, key.1.clone());
} }
if !env.modified { if !env.modified {
@ -56,19 +63,19 @@ pub fn infer_borrow<'a>(
} }
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Key { pub enum Key<'a> {
Declaration(Symbol), Declaration(Symbol, Layout<'a>),
JoinPoint(JoinPointId), JoinPoint(JoinPointId),
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ParamMap<'a> { pub struct ParamMap<'a> {
items: MutMap<Key, &'a [Param<'a>]>, items: MutMap<Key<'a>, &'a [Param<'a>]>,
} }
impl<'a> IntoIterator for ParamMap<'a> { impl<'a> IntoIterator for ParamMap<'a> {
type Item = (Key, &'a [Param<'a>]); type Item = (Key<'a>, &'a [Param<'a>]);
type IntoIter = <std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter; type IntoIter = <std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.items.into_iter() self.items.into_iter()
@ -76,8 +83,9 @@ impl<'a> IntoIterator for ParamMap<'a> {
} }
impl<'a> IntoIterator for &'a ParamMap<'a> { impl<'a> IntoIterator for &'a ParamMap<'a> {
type Item = (&'a Key, &'a &'a [Param<'a>]); type Item = (&'a Key<'a>, &'a &'a [Param<'a>]);
type IntoIter = <&'a std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter; type IntoIter =
<&'a std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.items.iter() self.items.iter()
@ -85,8 +93,8 @@ impl<'a> IntoIterator for &'a ParamMap<'a> {
} }
impl<'a> ParamMap<'a> { impl<'a> ParamMap<'a> {
pub fn get_symbol(&self, symbol: Symbol) -> Option<&'a [Param<'a>]> { pub fn get_symbol(&self, symbol: Symbol, layout: Layout<'a>) -> Option<&'a [Param<'a>]> {
let key = Key::Declaration(symbol); let key = Key::Declaration(symbol, layout);
self.items.get(&key).copied() self.items.get(&key).copied()
} }
@ -116,7 +124,7 @@ impl<'a> ParamMap<'a> {
fn init_borrow_args(arena: &'a Bump, ps: &'a [(Layout<'a>, Symbol)]) -> &'a [Param<'a>] { fn init_borrow_args(arena: &'a Bump, ps: &'a [(Layout<'a>, Symbol)]) -> &'a [Param<'a>] {
Vec::from_iter_in( Vec::from_iter_in(
ps.iter().map(|(layout, symbol)| Param { ps.iter().map(|(layout, symbol)| Param {
borrow: layout.is_refcounted(), borrow: should_borrow_layout(layout),
layout: layout.clone(), layout: layout.clone(),
symbol: *symbol, symbol: *symbol,
}), }),
@ -125,11 +133,46 @@ impl<'a> ParamMap<'a> {
.into_bump_slice() .into_bump_slice()
} }
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>) { fn init_borrow_args_always_owned(
self.items.insert( arena: &'a Bump,
Key::Declaration(proc.name), ps: &'a [(Layout<'a>, Symbol)],
) -> &'a [Param<'a>] {
Vec::from_iter_in(
ps.iter().map(|(layout, symbol)| Param {
borrow: false,
layout: layout.clone(),
symbol: *symbol,
}),
arena,
)
.into_bump_slice()
}
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>, key: (Symbol, Layout<'a>)) {
if proc.must_own_arguments {
self.visit_proc_always_owned(arena, proc, key);
return;
}
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
Self::init_borrow_args(arena, proc.args), Self::init_borrow_args(arena, proc.args),
); );
debug_assert!(already_in_there.is_none());
self.visit_stmt(arena, proc.name, &proc.body);
}
fn visit_proc_always_owned(
&mut self,
arena: &'a Bump,
proc: &Proc<'a>,
key: (Symbol, Layout<'a>),
) {
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
Self::init_borrow_args_always_owned(arena, proc.args),
);
debug_assert!(already_in_there.is_none());
self.visit_stmt(arena, proc.name, &proc.body); self.visit_stmt(arena, proc.name, &proc.body);
} }
@ -147,8 +190,10 @@ impl<'a> ParamMap<'a> {
remainder: v, remainder: v,
continuation: b, continuation: b,
} => { } => {
self.items let already_in_there = self
.items
.insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs)); .insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs));
debug_assert!(already_in_there.is_none());
stack.push(v); stack.push(v);
stack.push(b); stack.push(b);
@ -211,7 +256,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn update_param_map(&mut self, k: Key) { fn update_param_map(&mut self, k: Key<'a>) {
let arena = self.arena; let arena = self.arena;
if let Some(ps) = self.param_map.items.get(&k) { if let Some(ps) = self.param_map.items.get(&k) {
let ps = Vec::from_iter_in( let ps = Vec::from_iter_in(
@ -261,6 +306,16 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn own_arg(&mut self, x: Symbol) {
self.own_var(x);
}
fn own_args(&mut self, xs: &[Symbol]) {
for x in xs.iter() {
self.own_arg(*x);
}
}
/// For each xs[i], if xs[i] is owned, then mark ps[i] as owned. /// For each xs[i], if xs[i] is owned, then mark ps[i] as owned.
/// We use this action to preserve tail calls. That is, if we have /// We use this action to preserve tail calls. That is, if we have
/// a tail call `f xs`, if the i-th parameter is borrowed, but `xs[i]` is owned /// a tail call `f xs`, if the i-th parameter is borrowed, but `xs[i]` is owned
@ -309,30 +364,34 @@ impl<'a> BorrowInfState<'a> {
match call_type { match call_type {
ByName { ByName {
name, arg_layouts, .. name, full_layout, ..
}
| ByPointer {
name, arg_layouts, ..
} => { } => {
// get the borrow signature of the applied function // get the borrow signature of the applied function
let ps = match self.param_map.get_symbol(*name) { match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(slice) => slice, Some(ps) => {
None => Vec::from_iter_in( // the return value will be owned
arg_layouts.iter().cloned().map(|layout| Param { self.own_var(z);
symbol: Symbol::UNDERSCORE,
borrow: false,
layout,
}),
self.arena,
)
.into_bump_slice(),
};
// if the function exects an owned argument (ps), the argument must be owned (args)
self.own_args_using_params(arguments, ps);
}
None => {
// this is really an indirect call, but the function was bound to a symbol
// the return value will be owned
self.own_var(z);
// if the function exects an owned argument (ps), the argument must be owned (args)
self.own_args(arguments);
}
}
}
ByPointer { .. } => {
// the return value will be owned // the return value will be owned
self.own_var(z); self.own_var(z);
// if the function exects an owned argument (ps), the argument must be owned (args) // if the function exects an owned argument (ps), the argument must be owned (args)
self.own_args_using_params(arguments, ps); self.own_args(arguments);
} }
LowLevel { op } => { LowLevel { op } => {
@ -405,7 +464,12 @@ impl<'a> BorrowInfState<'a> {
match (v, b) { match (v, b) {
( (
Expr::Call(crate::ir::Call { Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::ByName { name: g, .. }, call_type:
crate::ir::CallType::ByName {
name: g,
full_layout,
..
},
arguments: ys, arguments: ys,
.. ..
}), }),
@ -413,7 +477,12 @@ impl<'a> BorrowInfState<'a> {
) )
| ( | (
Expr::Call(crate::ir::Call { Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::ByPointer { name: g, .. }, call_type:
crate::ir::CallType::ByPointer {
name: g,
full_layout,
..
},
arguments: ys, arguments: ys,
.. ..
}), }),
@ -422,7 +491,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == *g && x == *z { if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known) // anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine // can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g) { if let Some(ps) = self.param_map.get_symbol(*g, full_layout.clone()) {
self.own_params_using_args(ys, ps) self.own_params_using_args(ys, ps)
} }
} }
@ -464,8 +533,10 @@ impl<'a> BorrowInfState<'a> {
Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => { Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => {
// ensure that the function pointed to is in the param map // ensure that the function pointed to is in the param map
if let Some(params) = self.param_map.get_symbol(*fsymbol) { if let Some(params) = self.param_map.get_symbol(*fsymbol, layout.clone()) {
self.param_map.items.insert(Key::Declaration(*x), params); self.param_map
.items
.insert(Key::Declaration(*x, layout.clone()), params);
} }
self.collect_stmt(b); self.collect_stmt(b);
@ -521,7 +592,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn collect_proc(&mut self, proc: &Proc<'a>) { fn collect_proc(&mut self, proc: &Proc<'a>, layout: Layout<'a>) {
let old = self.param_set.clone(); let old = self.param_set.clone();
let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice(); let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice();
@ -532,14 +603,16 @@ impl<'a> BorrowInfState<'a> {
self.owned.entry(proc.name).or_default(); self.owned.entry(proc.name).or_default();
self.collect_stmt(&proc.body); self.collect_stmt(&proc.body);
self.update_param_map(Key::Declaration(proc.name)); self.update_param_map(Key::Declaration(proc.name, layout));
self.param_set = old; self.param_set = old;
} }
} }
pub fn foreign_borrow_signature(arena: &Bump, arity: usize) -> &[bool] { pub fn foreign_borrow_signature(arena: &Bump, arity: usize) -> &[bool] {
let all = bumpalo::vec![in arena; false; arity]; // NOTE this means that Roc is responsible for cleaning up resources;
// the host cannot (currently) take ownership
let all = bumpalo::vec![in arena; true; arity];
all.into_bump_slice() all.into_bump_slice()
} }
@ -561,25 +634,30 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListConcat | StrConcat => arena.alloc_slice_copy(&[owned, borrowed]), ListConcat | StrConcat => arena.alloc_slice_copy(&[borrowed, borrowed]),
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]), StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListSingle => arena.alloc_slice_copy(&[irrelevant]), ListSingle => arena.alloc_slice_copy(&[irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, irrelevant]), ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]),
ListReverse => arena.alloc_slice_copy(&[owned]), ListReverse => arena.alloc_slice_copy(&[owned]),
ListAppend => arena.alloc_slice_copy(&[owned, owned]),
ListPrepend => arena.alloc_slice_copy(&[owned, owned]), ListPrepend => arena.alloc_slice_copy(&[owned, owned]),
StrJoinWith => arena.alloc_slice_copy(&[irrelevant, irrelevant]), StrJoinWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListJoin => arena.alloc_slice_copy(&[irrelevant]), ListJoin => arena.alloc_slice_copy(&[irrelevant]),
ListMap => arena.alloc_slice_copy(&[owned, irrelevant]), ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, irrelevant]),
ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]), ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListWalk => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListWalkBackwards => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]),
ListSum => arena.alloc_slice_copy(&[borrowed]), ListSum => arena.alloc_slice_copy(&[borrowed]),
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap // TODO when we have lists with capacity (if ever)
| NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte // List.append should own its first argument
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd ListAppend => arena.alloc_slice_copy(&[borrowed, owned]),
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked
| NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare
| NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd
| NumBitwiseXor => arena.alloc_slice_copy(&[irrelevant, irrelevant]), | NumBitwiseXor => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
@ -587,7 +665,21 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
arena.alloc_slice_copy(&[irrelevant]) arena.alloc_slice_copy(&[irrelevant])
} }
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
StrFromInt => arena.alloc_slice_copy(&[irrelevant]),
StrFromUtf8 => arena.alloc_slice_copy(&[owned]), StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
DictSize => arena.alloc_slice_copy(&[borrowed]),
DictEmpty => &[],
DictInsert => arena.alloc_slice_copy(&[owned, owned, owned]),
DictRemove => arena.alloc_slice_copy(&[owned, borrowed]),
DictContains => arena.alloc_slice_copy(&[borrowed, borrowed]),
DictGetUnsafe => arena.alloc_slice_copy(&[borrowed, borrowed]),
DictKeys | DictValues => arena.alloc_slice_copy(&[borrowed]),
DictUnion | DictDifference | DictIntersection => arena.alloc_slice_copy(&[owned, borrowed]),
// borrow function argument so we don't have to worry about RC of the closure
DictWalk => arena.alloc_slice_copy(&[owned, borrowed, owned]),
SetFromList => arena.alloc_slice_copy(&[owned]),
} }
} }

View file

@ -157,6 +157,34 @@ impl<'a, 'i> Env<'a, 'i> {
} }
} }
fn try_insert_struct_info(&mut self, symbol: Symbol, layout: &Layout<'a>) {
use Layout::*;
match layout {
Struct(fields) => {
self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields));
}
Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = self.arena.alloc([fpointer, closure_layout.layout.clone()]);
self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields));
}
_ => {}
}
}
fn insert_struct_info(&mut self, symbol: Symbol, fields: &'a [Layout<'a>]) {
self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields));
}
fn remove_struct_info(&mut self, symbol: Symbol) {
self.constructor_map.remove(&symbol);
self.layout_map.remove(&symbol);
}
pub fn unique_symbol(&mut self) -> Symbol { pub fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique(); let ident_id = self.ident_ids.gen_unique();
@ -175,7 +203,7 @@ impl<'a, 'i> Env<'a, 'i> {
} }
fn layout_for_constructor<'a>( fn layout_for_constructor<'a>(
_arena: &'a Bump, arena: &'a Bump,
layout: &Layout<'a>, layout: &Layout<'a>,
constructor: u64, constructor: u64,
) -> ConstructorLayout<&'a [Layout<'a>]> { ) -> ConstructorLayout<&'a [Layout<'a>]> {
@ -213,7 +241,16 @@ fn layout_for_constructor<'a>(
} }
} }
} }
_ => unreachable!(), Struct(fields) => {
debug_assert_eq!(constructor, 0);
HasFields(fields)
}
Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = arena.alloc([fpointer, closure_layout.layout.clone()]);
HasFields(fields)
}
other => unreachable!("weird layout {:?}", other),
} }
} }
@ -239,11 +276,11 @@ fn work_for_constructor<'a>(
match layout_for_constructor(env.arena, full_layout, constructor) { match layout_for_constructor(env.arena, full_layout, constructor) {
Unknown => Unknown, Unknown => Unknown,
IsNull => IsNull, IsNull => IsNull,
HasFields(cons_layout) => { HasFields(constructor_layout) => {
// figure out if there is at least one aliased refcounted field. Only then // figure out if there is at least one aliased refcounted field. Only then
// does it make sense to inline the decrement // does it make sense to inline the decrement
let at_least_one_aliased = (|| { let at_least_one_aliased = (|| {
for (i, field_layout) in cons_layout.iter().enumerate() { for (i, field_layout) in constructor_layout.iter().enumerate() {
if field_layout.contains_refcounted() if field_layout.contains_refcounted()
&& field_aliases.and_then(|map| map.get(&(i as u64))).is_some() && field_aliases.and_then(|map| map.get(&(i as u64))).is_some()
{ {
@ -255,7 +292,7 @@ fn work_for_constructor<'a>(
// for each field, if it has refcounted content, check if it has an alias // for each field, if it has refcounted content, check if it has an alias
// if so, use the alias, otherwise load the field. // if so, use the alias, otherwise load the field.
for (i, field_layout) in cons_layout.iter().enumerate() { for (i, field_layout) in constructor_layout.iter().enumerate() {
if field_layout.contains_refcounted() { if field_layout.contains_refcounted() {
match field_aliases.and_then(|map| map.get(&(i as u64))) { match field_aliases.and_then(|map| map.get(&(i as u64))) {
Some(alias_symbol) => { Some(alias_symbol) => {
@ -269,7 +306,7 @@ fn work_for_constructor<'a>(
let expr = Expr::AccessAtIndex { let expr = Expr::AccessAtIndex {
index: i as u64, index: i as u64,
field_layouts: cons_layout, field_layouts: constructor_layout,
structure: *symbol, structure: *symbol,
wrapped: Wrapped::MultiTagUnion, wrapped: Wrapped::MultiTagUnion,
}; };
@ -322,7 +359,40 @@ enum ConstructorLayout<T> {
Unknown, Unknown,
} }
pub fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> { pub fn expand_and_cancel_proc<'a>(
env: &mut Env<'a, '_>,
stmt: &'a Stmt<'a>,
arguments: &'a [(Layout<'a>, Symbol)],
) -> &'a Stmt<'a> {
let mut introduced = Vec::new_in(env.arena);
for (layout, symbol) in arguments {
match layout {
Layout::Struct(fields) => {
env.insert_struct_info(*symbol, fields);
introduced.push(*symbol);
}
Layout::Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = env.arena.alloc([fpointer, closure_layout.layout.clone()]);
env.insert_struct_info(*symbol, fields);
introduced.push(*symbol);
}
_ => {}
}
}
let result = expand_and_cancel(env, stmt);
for symbol in introduced {
env.remove_struct_info(symbol);
}
result
}
fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> {
use Stmt::*; use Stmt::*;
let mut deferred_default = Deferred { let mut deferred_default = Deferred {
@ -351,7 +421,7 @@ pub fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a S
// prevent long chains of `Let`s from blowing the stack // prevent long chains of `Let`s from blowing the stack
let mut literal_stack = Vec::new_in(env.arena); let mut literal_stack = Vec::new_in(env.arena);
while !matches!(&expr, Expr::AccessAtIndex { .. } ) { while !matches!(&expr, Expr::AccessAtIndex { .. } | Expr::Struct(_)) {
if let Stmt::Let(symbol1, expr1, layout1, cont1) = cont { if let Stmt::Let(symbol1, expr1, layout1, cont1) = cont {
literal_stack.push((symbol, expr.clone(), layout.clone())); literal_stack.push((symbol, expr.clone(), layout.clone()));
@ -366,25 +436,47 @@ pub fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a S
let new_cont; let new_cont;
if let Expr::AccessAtIndex { match &expr {
structure, index, .. Expr::AccessAtIndex {
} = &expr structure,
{ index,
let entry = env field_layouts,
.alias_map ..
.entry(*structure) } => {
.or_insert_with(MutMap::default); let entry = env
.alias_map
.entry(*structure)
.or_insert_with(MutMap::default);
entry.insert(*index, symbol); entry.insert(*index, symbol);
new_cont = expand_and_cancel(env, cont); // if the field is a struct, we know its constructor too!
let field_layout = &field_layouts[*index as usize];
env.try_insert_struct_info(symbol, field_layout);
// make sure to remove the alias, so other branches don't use it by accident new_cont = expand_and_cancel(env, cont);
env.alias_map
.get_mut(structure) env.remove_struct_info(symbol);
.and_then(|map| map.remove(index));
} else { // make sure to remove the alias, so other branches don't use it by accident
new_cont = expand_and_cancel(env, cont); env.alias_map
.get_mut(structure)
.and_then(|map| map.remove(index));
}
Expr::Struct(_) => {
if let Layout::Struct(fields) = layout {
env.insert_struct_info(symbol, fields);
new_cont = expand_and_cancel(env, cont);
env.remove_struct_info(symbol);
} else {
new_cont = expand_and_cancel(env, cont);
}
}
_ => {
new_cont = expand_and_cancel(env, cont);
}
} }
let stmt = Let(symbol, expr.clone(), layout.clone(), new_cont); let stmt = Let(symbol, expr.clone(), layout.clone(), new_cont);

View file

@ -232,7 +232,7 @@ impl<'a> Context<'a> {
let mut vars = MutMap::default(); let mut vars = MutMap::default();
for (key, _) in param_map.into_iter() { for (key, _) in param_map.into_iter() {
if let crate::borrow::Key::Declaration(symbol) = key { if let crate::borrow::Key::Declaration(symbol, _) = key {
vars.insert( vars.insert(
*symbol, *symbol,
VarInfo { VarInfo {
@ -467,34 +467,47 @@ impl<'a> Context<'a> {
} }
ByName { ByName {
name, arg_layouts, .. name, full_layout, ..
}
| ByPointer {
name, arg_layouts, ..
} => { } => {
// get the borrow signature // get the borrow signature
let ps = match self.param_map.get_symbol(*name) { match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(slice) => slice, Some(ps) => {
None => Vec::from_iter_in( let v = Expr::Call(crate::ir::Call {
arg_layouts.iter().cloned().map(|layout| Param { call_type,
symbol: Symbol::UNDERSCORE, arguments,
borrow: false, });
layout,
}),
self.arena,
)
.into_bump_slice(),
};
let b = self.add_dec_after_application(arguments, ps, b, b_live_vars);
let b = self.arena.alloc(Stmt::Let(z, v, l, b));
self.add_inc_before(arguments, ps, b, b_live_vars)
}
None => {
// an indirect call that was bound to a name
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
self.add_inc_before_consume_all(
arguments,
self.arena.alloc(Stmt::Let(z, v, l, b)),
b_live_vars,
)
}
}
}
ByPointer { .. } => {
let v = Expr::Call(crate::ir::Call { let v = Expr::Call(crate::ir::Call {
call_type, call_type,
arguments, arguments,
}); });
let b = self.add_dec_after_application(arguments, ps, b, b_live_vars); self.add_inc_before_consume_all(
let b = self.arena.alloc(Stmt::Let(z, v, l, b)); arguments,
self.arena.alloc(Stmt::Let(z, v, l, b)),
self.add_inc_before(arguments, ps, b, b_live_vars) b_live_vars,
)
} }
} }
} }
@ -590,7 +603,7 @@ impl<'a> Context<'a> {
persistent: bool, persistent: bool,
consume: bool, consume: bool,
) -> Self { ) -> Self {
// can this type be reference-counted at runtime? // should we perform incs and decs on this value?
let reference = layout.contains_refcounted(); let reference = layout.contains_refcounted();
let info = VarInfo { let info = VarInfo {
@ -716,8 +729,12 @@ impl<'a> Context<'a> {
layout, layout,
} => { } => {
// TODO this combines parts of Let and Switch. Did this happen correctly? // TODO this combines parts of Let and Switch. Did this happen correctly?
let mut case_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default()); let mut case_live_vars = collect_stmt(pass, &self.jp_live_vars, MutSet::default());
case_live_vars.extend(collect_stmt(fail, &self.jp_live_vars, MutSet::default()));
// the result of an invoke should not be touched in the fail branch
// but it should be present in the pass branch (otherwise it would be dead)
debug_assert!(case_live_vars.contains(symbol));
case_live_vars.remove(symbol); case_live_vars.remove(symbol);
let fail = { let fail = {
@ -745,9 +762,50 @@ impl<'a> Context<'a> {
layout: layout.clone(), layout: layout.clone(),
}; };
let stmt = self.arena.alloc(invoke); let cont = self.arena.alloc(invoke);
(stmt, case_live_vars) use crate::ir::CallType;
let stmt = match &call.call_type {
CallType::LowLevel { op } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &case_live_vars)
}
CallType::Foreign { .. } => {
let ps = crate::borrow::foreign_borrow_signature(
self.arena,
call.arguments.len(),
);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &case_live_vars)
}
CallType::ByName {
name, full_layout, ..
} => {
// get the borrow signature
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => self.add_dec_after_application(
call.arguments,
ps,
cont,
&case_live_vars,
),
None => self.add_inc_before_consume_all(
call.arguments,
cont,
&case_live_vars,
),
}
}
CallType::ByPointer { .. } => {
self.add_inc_before_consume_all(call.arguments, cont, &case_live_vars)
}
};
let mut invoke_live_vars = case_live_vars;
occuring_variables_call(call, &mut invoke_live_vars);
(stmt, invoke_live_vars)
} }
Join { Join {
id: j, id: j,
@ -991,10 +1049,15 @@ pub fn visit_declaration<'a>(
ctx.add_dec_for_dead_params(params, b, &b_live_vars) ctx.add_dec_for_dead_params(params, b, &b_live_vars)
} }
pub fn visit_proc<'a>(arena: &'a Bump, param_map: &'a ParamMap<'a>, proc: &mut Proc<'a>) { pub fn visit_proc<'a>(
arena: &'a Bump,
param_map: &'a ParamMap<'a>,
proc: &mut Proc<'a>,
layout: Layout<'a>,
) {
let ctx = Context::new(arena, param_map); let ctx = Context::new(arena, param_map);
let params = match param_map.get_symbol(proc.name) { let params = match param_map.get_symbol(proc.name, layout) {
Some(slice) => slice, Some(slice) => slice,
None => Vec::from_iter_in( None => Vec::from_iter_in(
proc.args.iter().cloned().map(|(layout, symbol)| Param { proc.args.iter().cloned().map(|(layout, symbol)| Param {

View file

@ -1,8 +1,8 @@
use self::InProgressProc::*; use self::InProgressProc::*;
use crate::exhaustive::{Ctor, Guard, RenderAs, TagId}; use crate::exhaustive::{Ctor, Guard, RenderAs, TagId};
use crate::layout::{ use crate::layout::{
Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem, UnionLayout, WrappedVariant, BuildClosureData, Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem, UnionLayout,
TAG_SIZE, WrappedVariant, TAG_SIZE,
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
@ -19,6 +19,28 @@ use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder};
pub const PRETTY_PRINT_IR_SYMBOLS: bool = false; pub const PRETTY_PRINT_IR_SYMBOLS: bool = false;
macro_rules! return_on_layout_error {
($env:expr, $layout_result:expr) => {
match $layout_result {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Stmt::RuntimeError($env.arena.alloc(format!(
"UnresolvedTypeVar {} line {}",
file!(),
line!()
)));
}
Err(LayoutProblem::Erroneous) => {
return Stmt::RuntimeError($env.arena.alloc(format!(
"Erroneous {} line {}",
file!(),
line!()
)));
}
}
};
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum MonoProblem { pub enum MonoProblem {
PatternProblem(crate::exhaustive::Error), PatternProblem(crate::exhaustive::Error),
@ -96,6 +118,7 @@ pub struct Proc<'a> {
pub closure_data_layout: Option<Layout<'a>>, pub closure_data_layout: Option<Layout<'a>>,
pub ret_layout: Layout<'a>, pub ret_layout: Layout<'a>,
pub is_self_recursive: SelfRecursive, pub is_self_recursive: SelfRecursive,
pub must_own_arguments: bool,
pub host_exposed_layouts: HostExposedLayouts<'a>, pub host_exposed_layouts: HostExposedLayouts<'a>,
} }
@ -114,8 +137,15 @@ pub enum SelfRecursive {
SelfRecursive(JoinPointId), SelfRecursive(JoinPointId),
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Parens {
NotNeeded,
InTypeParam,
InFunction,
}
impl<'a> Proc<'a> { impl<'a> Proc<'a> {
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, _parens: bool) -> DocBuilder<'b, D, A> pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, _parens: Parens) -> DocBuilder<'b, D, A>
where where
D: DocAllocator<'b, A>, D: DocAllocator<'b, A>,
D::Doc: Clone, D::Doc: Clone,
@ -126,20 +156,36 @@ impl<'a> Proc<'a> {
.iter() .iter()
.map(|(_, symbol)| symbol_to_doc(alloc, *symbol)); .map(|(_, symbol)| symbol_to_doc(alloc, *symbol));
alloc if PRETTY_PRINT_IR_SYMBOLS {
.text("procedure ") alloc
.append(symbol_to_doc(alloc, self.name)) .text("procedure : ")
.append(" (") .append(symbol_to_doc(alloc, self.name))
.append(alloc.intersperse(args_doc, ", ")) .append(" ")
.append("):") .append(self.ret_layout.to_doc(alloc, Parens::NotNeeded))
.append(alloc.hardline()) .append(alloc.hardline())
.append(self.body.to_doc(alloc).indent(4)) .append(alloc.text("procedure = "))
.append(symbol_to_doc(alloc, self.name))
.append(" (")
.append(alloc.intersperse(args_doc, ", "))
.append("):")
.append(alloc.hardline())
.append(self.body.to_doc(alloc).indent(4))
} else {
alloc
.text("procedure ")
.append(symbol_to_doc(alloc, self.name))
.append(" (")
.append(alloc.intersperse(args_doc, ", "))
.append("):")
.append(alloc.hardline())
.append(self.body.to_doc(alloc).indent(4))
}
} }
pub fn to_pretty(&self, width: usize) -> String { pub fn to_pretty(&self, width: usize) -> String {
let allocator = BoxAllocator; let allocator = BoxAllocator;
let mut w = std::vec::Vec::new(); let mut w = std::vec::Vec::new();
self.to_doc::<_, ()>(&allocator, false) self.to_doc::<_, ()>(&allocator, Parens::NotNeeded)
.1 .1
.render(width, &mut w) .render(width, &mut w)
.unwrap(); .unwrap();
@ -153,8 +199,8 @@ impl<'a> Proc<'a> {
) { ) {
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs)); let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs));
for (_, proc) in procs.iter_mut() { for (key, proc) in procs.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc); crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
} }
} }
@ -183,7 +229,11 @@ impl<'a> Proc<'a> {
}; };
for (_, proc) in procs.iter_mut() { for (_, proc) in procs.iter_mut() {
let b = expand_rc::expand_and_cancel(&mut env, arena.alloc(proc.body.clone())); let b = expand_rc::expand_and_cancel_proc(
&mut env,
arena.alloc(proc.body.clone()),
proc.args,
);
proc.body = b.clone(); proc.body = b.clone();
} }
} }
@ -342,8 +392,8 @@ impl<'a> Procs<'a> {
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result)); let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
for (_, proc) in result.iter_mut() { for (key, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc); crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
} }
result result
@ -382,8 +432,8 @@ impl<'a> Procs<'a> {
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result)); let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
for (_, proc) in result.iter_mut() { for (key, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc); crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
} }
(result, borrow_params) (result, borrow_params)
@ -1852,6 +1902,7 @@ fn specialize_external<'a>(
closure_data_layout, closure_data_layout,
ret_layout: full_layout, ret_layout: full_layout,
is_self_recursive: recursivity, is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts, host_exposed_layouts,
}; };
@ -1879,12 +1930,14 @@ fn specialize_external<'a>(
match tag_layout { match tag_layout {
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {
// NOTE closure unions do not store the tag!
let field_layouts = &field_layouts[1..];
// TODO check for field_layouts.len() == 1 and do a rename in that case? // TODO check for field_layouts.len() == 1 and do a rename in that case?
for (index, (symbol, _variable)) in captured.iter().enumerate() for (mut index, (symbol, _variable)) in
captured.iter().enumerate()
{ {
// the field layouts do store the tag, but the tag value is
// not captured. So we drop the layout of the tag ID here
index += 1;
// TODO therefore should the wrapped here not be RecordOrSingleTagUnion? // TODO therefore should the wrapped here not be RecordOrSingleTagUnion?
let expr = Expr::AccessAtIndex { let expr = Expr::AccessAtIndex {
index: index as _, index: index as _,
@ -1986,6 +2039,7 @@ fn specialize_external<'a>(
closure_data_layout, closure_data_layout,
ret_layout, ret_layout,
is_self_recursive: recursivity, is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts, host_exposed_layouts,
}; };
@ -2409,7 +2463,7 @@ fn specialize_naked_symbol<'a>(
match hole { match hole {
Stmt::Jump(_, _) => todo!("not sure what to do in this case yet"), Stmt::Jump(_, _) => todo!("not sure what to do in this case yet"),
_ => { _ => {
let expr = Expr::FunctionPointer(symbol, layout.clone()); let expr = call_by_pointer(env, procs, symbol, layout.clone());
let new_symbol = env.unique_symbol(); let new_symbol = env.unique_symbol();
return Stmt::Let( return Stmt::Let(
new_symbol, new_symbol,
@ -2706,8 +2760,7 @@ pub fn with_hole<'a>(
); );
let outer_symbol = env.unique_symbol(); let outer_symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
.unwrap();
// convert the def body, store in outer_symbol // convert the def body, store in outer_symbol
with_hole( with_hole(
@ -2782,7 +2835,25 @@ pub fn with_hole<'a>(
use crate::layout::UnionVariant::*; use crate::layout::UnionVariant::*;
let arena = env.arena; let arena = env.arena;
let variant = crate::layout::union_sorted_tags(env.arena, variant_var, env.subs); let res_variant = crate::layout::union_sorted_tags(env.arena, variant_var, env.subs);
let variant = match res_variant {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"UnresolvedTypeVar {} line {}",
file!(),
line!()
)));
}
Err(LayoutProblem::Erroneous) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"Erroneous {} line {}",
file!(),
line!()
)));
}
};
match variant { match variant {
Never => unreachable!( Never => unreachable!(
@ -3479,7 +3550,7 @@ pub fn with_hole<'a>(
// TODO should the let have layout Pointer? // TODO should the let have layout Pointer?
Stmt::Let( Stmt::Let(
assigned, assigned,
Expr::FunctionPointer(name, layout.clone()), call_by_pointer(env, procs, name, layout.clone()),
layout, layout,
hole, hole,
) )
@ -3661,8 +3732,12 @@ pub fn with_hole<'a>(
.into_bump_slice(); .into_bump_slice();
// define the closure data, unless it's a basic unwrapped type already // define the closure data, unless it's a basic unwrapped type already
match closure_layout.build_closure_data(name, symbols) { match closure_layout.build_closure_data(name, &symbols) {
Ok(expr) => { BuildClosureData::Alias(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
}
BuildClosureData::Struct(expr) => {
stmt = Stmt::Let( stmt = Stmt::Let(
closure_data, closure_data,
expr, expr,
@ -3670,13 +3745,44 @@ pub fn with_hole<'a>(
env.arena.alloc(stmt), env.arena.alloc(stmt),
); );
} }
Err(current) => { BuildClosureData::Union {
// there is only one symbol captured, use that immediately tag_id,
substitute_in_exprs(env.arena, &mut stmt, closure_data, current); tag_layout,
union_size,
tag_name,
} => {
let tag_id_symbol = env.unique_symbol();
let mut tag_symbols =
Vec::with_capacity_in(symbols.len() + 1, env.arena);
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i64));
let expr2 = Expr::Tag {
tag_id,
tag_layout,
union_size,
tag_name,
arguments: tag_symbols.into_bump_slice(),
};
stmt = Stmt::Let(
closure_data,
expr2,
closure_data_layout.clone(),
env.arena.alloc(stmt),
);
stmt = Stmt::Let(
tag_id_symbol,
expr1,
Layout::Builtin(Builtin::Int64),
env.arena.alloc(stmt),
);
} }
} }
let expr = Expr::FunctionPointer(name, function_ptr_layout.clone()); let expr = call_by_pointer(env, procs, name, function_ptr_layout.clone());
stmt = Stmt::Let( stmt = Stmt::Let(
function_pointer, function_pointer,
@ -3702,7 +3808,7 @@ pub fn with_hole<'a>(
// TODO should the let have layout Pointer? // TODO should the let have layout Pointer?
Stmt::Let( Stmt::Let(
assigned, assigned,
Expr::FunctionPointer(name, layout.clone()), call_by_pointer(env, procs, name, layout.clone()),
layout, layout,
hole, hole,
) )
@ -3768,11 +3874,10 @@ pub fn with_hole<'a>(
) )
.into_bump_slice(); .into_bump_slice();
let full_layout = layout_cache let full_layout = return_on_layout_error!(
.from_var(env.arena, fn_var, env.subs) env,
.unwrap_or_else(|err| { layout_cache.from_var(env.arena, fn_var, env.subs)
panic!("TODO turn fn_var into a RuntimeError {:?}", err) );
});
let arg_layouts = match full_layout { let arg_layouts = match full_layout {
Layout::FunctionPointer(args, _) => args, Layout::FunctionPointer(args, _) => args,
@ -3780,11 +3885,10 @@ pub fn with_hole<'a>(
_ => unreachable!("function has layout that is not function pointer"), _ => unreachable!("function has layout that is not function pointer"),
}; };
let ret_layout = layout_cache let ret_layout = return_on_layout_error!(
.from_var(env.arena, ret_var, env.subs) env,
.unwrap_or_else(|err| { layout_cache.from_var(env.arena, ret_var, env.subs)
panic!("TODO turn fn_var into a RuntimeError {:?}", err) );
});
// if the function expression (loc_expr) is already a symbol, // if the function expression (loc_expr) is already a symbol,
// re-use that symbol, and don't define its value again // re-use that symbol, and don't define its value again
@ -4418,12 +4522,10 @@ pub fn from_can<'a>(
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value { if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap()
} else { } else {
let outer_symbol = env.unique_symbol(); let outer_symbol = env.unique_symbol();
stmt = stmt =
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
.unwrap();
// convert the def body, store in outer_symbol // convert the def body, store in outer_symbol
with_hole( with_hole(
@ -4581,9 +4683,23 @@ fn from_can_when<'a>(
} }
let opt_branches = to_opt_branches(env, region, branches, layout_cache); let opt_branches = to_opt_branches(env, region, branches, layout_cache);
let cond_layout = layout_cache let cond_layout = match layout_cache.from_var(env.arena, cond_var, env.subs) {
.from_var(env.arena, cond_var, env.subs) Ok(cached) => cached,
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"UnresolvedTypeVar {} line {}",
file!(),
line!()
)));
}
Err(LayoutProblem::Erroneous) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"Erroneous {} line {}",
file!(),
line!()
)));
}
};
let ret_layout = layout_cache let ret_layout = layout_cache
.from_var(env.arena, expr_var, env.subs) .from_var(env.arena, expr_var, env.subs)
@ -4620,31 +4736,21 @@ fn from_can_when<'a>(
jump, jump,
); );
match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt) { let new_guard_stmt =
Ok(new_guard_stmt) => ( store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt);
pattern, (
Guard::Guard { pattern,
id, Guard::Guard {
symbol, id,
stmt: new_guard_stmt, symbol,
}, stmt: new_guard_stmt,
branch_stmt, },
), branch_stmt,
Err(msg) => ( )
Pattern::Underscore,
Guard::NoGuard,
Stmt::RuntimeError(env.arena.alloc(msg)),
),
}
} else { } else {
match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch_stmt) { let new_branch_stmt =
Ok(new_branch_stmt) => (pattern, Guard::NoGuard, new_branch_stmt), store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch_stmt);
Err(msg) => ( (pattern, Guard::NoGuard, new_branch_stmt)
Pattern::Underscore,
Guard::NoGuard,
Stmt::RuntimeError(env.arena.alloc(msg)),
),
}
} }
}); });
let mono_branches = Vec::from_iter_in(it, arena); let mono_branches = Vec::from_iter_in(it, arena);
@ -5029,13 +5135,36 @@ fn substitute_in_expr<'a>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn store_pattern<'a>( fn store_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
outer_symbol: Symbol,
stmt: Stmt<'a>,
) -> Stmt<'a> {
match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
StorePattern::Productive(new) => new,
StorePattern::NotProductive(new) => new,
}
}
enum StorePattern<'a> {
/// we bound new symbols
Productive(Stmt<'a>),
/// no new symbols were bound in this pattern
NotProductive(Stmt<'a>),
}
/// It is crucial for correct RC insertion that we don't create dead variables!
#[allow(clippy::too_many_arguments)]
fn store_pattern_help<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>, can_pat: &Pattern<'a>,
outer_symbol: Symbol, outer_symbol: Symbol,
mut stmt: Stmt<'a>, mut stmt: Stmt<'a>,
) -> Result<Stmt<'a>, &'a str> { ) -> StorePattern<'a> {
use Pattern::*; use Pattern::*;
match can_pat { match can_pat {
@ -5044,12 +5173,15 @@ fn store_pattern<'a>(
} }
Underscore => { Underscore => {
// do nothing // do nothing
return StorePattern::NotProductive(stmt);
} }
IntLiteral(_) IntLiteral(_)
| FloatLiteral(_) | FloatLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
| StrLiteral(_) => {} | StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
AppliedTag { AppliedTag {
arguments, layout, .. arguments, layout, ..
} => { } => {
@ -5057,6 +5189,7 @@ fn store_pattern<'a>(
let write_tag = wrapped == Wrapped::MultiTagUnion; let write_tag = wrapped == Wrapped::MultiTagUnion;
let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena); let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena);
let mut is_productive = false;
if write_tag { if write_tag {
// add an element for the tag discriminant // add an element for the tag discriminant
@ -5087,6 +5220,7 @@ fn store_pattern<'a>(
Identifier(symbol) => { Identifier(symbol) => {
// store immediately in the given symbol // store immediately in the given symbol
stmt = Stmt::Let(*symbol, load, arg_layout.clone(), env.arena.alloc(stmt)); stmt = Stmt::Let(*symbol, load, arg_layout.clone(), env.arena.alloc(stmt));
is_productive = true;
} }
Underscore => { Underscore => {
// ignore // ignore
@ -5101,17 +5235,36 @@ fn store_pattern<'a>(
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol // first recurse, continuing to unpack symbol
stmt = store_pattern(env, procs, layout_cache, argument, symbol, stmt)?; match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
StorePattern::Productive(new) => {
// then store the symbol is_productive = true;
stmt = Stmt::Let(symbol, load, arg_layout.clone(), env.arena.alloc(stmt)); stmt = new;
// only if we bind one of its (sub)fields to a used name should we
// extract the field
stmt = Stmt::Let(
symbol,
load,
arg_layout.clone(),
env.arena.alloc(stmt),
);
}
StorePattern::NotProductive(new) => {
// do nothing
stmt = new;
}
}
} }
} }
} }
if !is_productive {
return StorePattern::NotProductive(stmt);
}
} }
RecordDestructure(destructs, Layout::Struct(sorted_fields)) => { RecordDestructure(destructs, Layout::Struct(sorted_fields)) => {
let mut is_productive = false;
for (index, destruct) in destructs.iter().enumerate().rev() { for (index, destruct) in destructs.iter().enumerate().rev() {
stmt = store_record_destruct( match store_record_destruct(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -5120,7 +5273,19 @@ fn store_pattern<'a>(
outer_symbol, outer_symbol,
sorted_fields, sorted_fields,
stmt, stmt,
)?; ) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
}
StorePattern::NotProductive(new) => {
stmt = new;
}
}
}
if !is_productive {
return StorePattern::NotProductive(stmt);
} }
} }
@ -5129,7 +5294,7 @@ fn store_pattern<'a>(
} }
} }
Ok(stmt) StorePattern::Productive(stmt)
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -5142,7 +5307,7 @@ fn store_record_destruct<'a>(
outer_symbol: Symbol, outer_symbol: Symbol,
sorted_fields: &'a [Layout<'a>], sorted_fields: &'a [Layout<'a>],
mut stmt: Stmt<'a>, mut stmt: Stmt<'a>,
) -> Result<Stmt<'a>, &'a str> { ) -> StorePattern<'a> {
use Pattern::*; use Pattern::*;
let wrapped = Wrapped::from_layout(&Layout::Struct(sorted_fields)); let wrapped = Wrapped::from_layout(&Layout::Struct(sorted_fields));
@ -5185,24 +5350,32 @@ fn store_record_destruct<'a>(
// { x, y: _ } -> ... // { x, y: _ } -> ...
// //
// internally. But `y` is never used, so we must make sure it't not stored/loaded. // internally. But `y` is never used, so we must make sure it't not stored/loaded.
return StorePattern::NotProductive(stmt);
} }
IntLiteral(_) IntLiteral(_)
| FloatLiteral(_) | FloatLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
| BitLiteral { .. } | BitLiteral { .. }
| StrLiteral(_) => {} | StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
_ => { _ => {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, guard_pattern, symbol, stmt)?; match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
StorePattern::Productive(new) => {
stmt = Stmt::Let(symbol, load, destruct.layout.clone(), env.arena.alloc(stmt)); stmt = new;
stmt =
Stmt::Let(symbol, load, destruct.layout.clone(), env.arena.alloc(stmt));
}
StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt),
}
} }
}, },
} }
Ok(stmt) StorePattern::Productive(stmt)
} }
/// We want to re-use symbols that are not function symbols /// We want to re-use symbols that are not function symbols
@ -5271,7 +5444,7 @@ fn handle_variable_aliasing<'a>(
.from_var(env.arena, variable, env.subs) .from_var(env.arena, variable, env.subs)
.unwrap(); .unwrap();
let expr = Expr::FunctionPointer(right, layout.clone()); let expr = call_by_pointer(env, procs, right, layout.clone());
Stmt::Let(left, expr, layout, env.arena.alloc(result)) Stmt::Let(left, expr, layout, env.arena.alloc(result))
} else { } else {
substitute_in_exprs(env.arena, &mut result, left, right); substitute_in_exprs(env.arena, &mut result, left, right);
@ -5319,7 +5492,7 @@ fn reuse_function_symbol<'a>(
// an imported symbol is always a function pointer: // an imported symbol is always a function pointer:
// either it's a function, or a top-level 0-argument thunk // either it's a function, or a top-level 0-argument thunk
let expr = Expr::FunctionPointer(original, layout.clone()); let expr = call_by_pointer(env, procs, original, layout.clone());
return Stmt::Let(symbol, expr, layout, env.arena.alloc(result)); return Stmt::Let(symbol, expr, layout, env.arena.alloc(result));
} }
_ => { _ => {
@ -5402,8 +5575,12 @@ fn reuse_function_symbol<'a>(
}; };
// define the closure data, unless it's a basic unwrapped type already // define the closure data, unless it's a basic unwrapped type already
match closure_layout.build_closure_data(original, symbols) { match closure_layout.build_closure_data(original, &symbols) {
Ok(expr) => { BuildClosureData::Alias(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
}
BuildClosureData::Struct(expr) => {
stmt = Stmt::Let( stmt = Stmt::Let(
closure_data, closure_data,
expr, expr,
@ -5411,13 +5588,44 @@ fn reuse_function_symbol<'a>(
env.arena.alloc(stmt), env.arena.alloc(stmt),
); );
} }
Err(current) => { BuildClosureData::Union {
// there is only one symbol captured, use that immediately tag_id,
substitute_in_exprs(env.arena, &mut stmt, closure_data, current); tag_layout,
union_size,
tag_name,
} => {
let tag_id_symbol = env.unique_symbol();
let mut tag_symbols =
Vec::with_capacity_in(symbols.len() + 1, env.arena);
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i64));
let expr2 = Expr::Tag {
tag_id,
tag_layout,
union_size,
tag_name,
arguments: tag_symbols.into_bump_slice(),
};
stmt = Stmt::Let(
closure_data,
expr2,
closure_data_layout.clone(),
env.arena.alloc(stmt),
);
stmt = Stmt::Let(
tag_id_symbol,
expr1,
Layout::Builtin(Builtin::Int64),
env.arena.alloc(stmt),
);
} }
} }
let expr = Expr::FunctionPointer(original, function_ptr_layout.clone()); let expr = call_by_pointer(env, procs, original, function_ptr_layout.clone());
stmt = Stmt::Let( stmt = Stmt::Let(
function_pointer, function_pointer,
@ -5439,7 +5647,7 @@ fn reuse_function_symbol<'a>(
Stmt::Let( Stmt::Let(
symbol, symbol,
Expr::FunctionPointer(original, layout.clone()), call_by_pointer(env, procs, original, layout.clone()),
layout, layout,
env.arena.alloc(result), env.arena.alloc(result),
) )
@ -5516,6 +5724,96 @@ where
result result
} }
fn call_by_pointer<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
symbol: Symbol,
layout: Layout<'a>,
) -> Expr<'a> {
// when we call a known function by-pointer, we must make sure we call a function that owns all
// its arguments (in an RC sense). we can't know this at this point, so we wrap such calls in
// a proc that we guarantee owns all its arguments. E.g. we turn
//
// foo = \x -> ...
//
// x = List.map [ ... ] foo
//
// into
//
// foo = \x -> ...
//
// @owns_all_arguments
// foo1 = \x -> foo x
//
// x = List.map [ ... ] foo1
// TODO can we cache this `any`?
let is_specialized = procs.specialized.keys().any(|(s, _)| *s == symbol);
if env.is_imported_symbol(symbol) || procs.partial_procs.contains_key(&symbol) || is_specialized
{
match layout {
Layout::FunctionPointer(arg_layouts, ret_layout) => {
if arg_layouts.iter().any(|l| l.contains_refcounted()) {
let name = env.unique_symbol();
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
let mut arg_symbols = Vec::with_capacity_in(arg_layouts.len(), env.arena);
for layout in arg_layouts {
let symbol = env.unique_symbol();
args.push((layout.clone(), symbol));
arg_symbols.push(symbol);
}
let args = args.into_bump_slice();
let call_symbol = env.unique_symbol();
let call_type = CallType::ByName {
name: symbol,
full_layout: layout.clone(),
ret_layout: ret_layout.clone(),
arg_layouts,
};
let call = Call {
call_type,
arguments: arg_symbols.into_bump_slice(),
};
let expr = Expr::Call(call);
let mut body = Stmt::Ret(call_symbol);
body = Stmt::Let(call_symbol, expr, ret_layout.clone(), env.arena.alloc(body));
let closure_data_layout = None;
let proc = Proc {
name,
args,
body,
closure_data_layout,
ret_layout: ret_layout.clone(),
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: true,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
procs
.specialized
.insert((name, layout.clone()), InProgressProc::Done(proc));
Expr::FunctionPointer(name, layout)
} else {
// if none of the arguments is refcounted, then owning the arguments has no
// meaning
Expr::FunctionPointer(symbol, layout)
}
}
_ => {
// e.g. Num.maxInt or other constants
Expr::FunctionPointer(symbol, layout)
}
}
} else {
Expr::FunctionPointer(symbol, layout)
}
}
fn add_needed_external<'a>( fn add_needed_external<'a>(
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
@ -5641,7 +5939,8 @@ fn call_by_name<'a>(
debug_assert_eq!( debug_assert_eq!(
arg_layouts.len(), arg_layouts.len(),
field_symbols.len(), field_symbols.len(),
"see call_by_name for background (scroll down a bit)" "see call_by_name for background (scroll down a bit), function is {:?}",
proc_name,
); );
let call = self::Call { let call = self::Call {
@ -5692,7 +5991,8 @@ fn call_by_name<'a>(
debug_assert_eq!( debug_assert_eq!(
arg_layouts.len(), arg_layouts.len(),
field_symbols.len(), field_symbols.len(),
"see call_by_name for background (scroll down a bit)" "see call_by_name for background (scroll down a bit), function is {:?}",
proc_name,
); );
let call = self::Call { let call = self::Call {
@ -5873,20 +6173,34 @@ fn call_by_name<'a>(
None if assigned.module_id() != proc_name.module_id() => { None if assigned.module_id() != proc_name.module_id() => {
add_needed_external(procs, env, original_fn_var, proc_name); add_needed_external(procs, env, original_fn_var, proc_name);
debug_assert_eq!( let call = if proc_name.module_id() == ModuleId::ATTR {
arg_layouts.len(), // the callable is one of the ATTR::ARG_n symbols
field_symbols.len(), // we must call those by-pointer
"scroll up a bit for background" self::Call {
); call_type: CallType::ByPointer {
name: proc_name,
let call = self::Call { ret_layout: ret_layout.clone(),
call_type: CallType::ByName { full_layout: full_layout.clone(),
name: proc_name, arg_layouts,
ret_layout: ret_layout.clone(), },
full_layout: full_layout.clone(), arguments: field_symbols,
arg_layouts, }
}, } else {
arguments: field_symbols, debug_assert_eq!(
arg_layouts.len(),
field_symbols.len(),
"scroll up a bit for background {:?}",
proc_name
);
self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: ret_layout.clone(),
full_layout: full_layout.clone(),
arg_layouts,
},
arguments: field_symbols,
}
}; };
let result = let result =
@ -6039,7 +6353,15 @@ fn from_can_pattern_help<'a>(
use crate::exhaustive::Union; use crate::exhaustive::Union;
use crate::layout::UnionVariant::*; use crate::layout::UnionVariant::*;
let variant = crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs); let res_variant = crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs);
let variant = match res_variant {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Err(RuntimeError::UnresolvedTypeVar)
}
Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType),
};
let result = match variant { let result = match variant {
Never => unreachable!( Never => unreachable!(
@ -6192,8 +6514,10 @@ fn from_can_pattern_help<'a>(
debug_assert_eq!( debug_assert_eq!(
arguments.len(), arguments.len(),
argument_layouts[1..].len(), argument_layouts[1..].len(),
"{:?}", "The {:?} tag got {} arguments, but its layout expects {}!",
tag_name tag_name,
arguments.len(),
argument_layouts[1..].len(),
); );
let it = argument_layouts[1..].iter(); let it = argument_layouts[1..].iter();

View file

@ -1,3 +1,4 @@
use crate::ir::Parens;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_collections::all::{default_hasher, MutMap, MutSet};
@ -6,6 +7,7 @@ use roc_module::symbol::{Interns, Symbol};
use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, Subs, Variable};
use roc_types::types::RecordField; use roc_types::types::RecordField;
use std::collections::HashMap; use std::collections::HashMap;
use ven_pretty::{DocAllocator, DocBuilder};
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize; pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize;
const GENERATE_NULLABLE: bool = true; const GENERATE_NULLABLE: bool = true;
@ -63,6 +65,34 @@ pub enum UnionLayout<'a> {
}, },
} }
impl<'a> UnionLayout<'a> {
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, _parens: Parens) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use UnionLayout::*;
match self {
NonRecursive(tags) => {
let tags_doc = tags.iter().map(|fields| {
alloc.text("C ").append(alloc.intersperse(
fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)),
" ",
))
});
alloc
.text("[")
.append(alloc.intersperse(tags_doc, ", "))
.append(alloc.text("]"))
}
_ => alloc.text("TODO"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ClosureLayout<'a> { pub struct ClosureLayout<'a> {
/// the layout that this specific closure captures /// the layout that this specific closure captures
@ -70,7 +100,9 @@ pub struct ClosureLayout<'a> {
/// the vec is likely to be small, so linear search is fine /// the vec is likely to be small, so linear search is fine
captured: &'a [(TagName, &'a [Layout<'a>])], captured: &'a [(TagName, &'a [Layout<'a>])],
layout: &'a Layout<'a>, /// use with care; there is some stuff happening here re. unwrapping
/// one-element records that might cause issues
pub layout: &'a Layout<'a>,
} }
impl<'a> ClosureLayout<'a> { impl<'a> ClosureLayout<'a> {
@ -284,16 +316,16 @@ impl<'a> ClosureLayout<'a> {
&self, &self,
original: Symbol, original: Symbol,
symbols: &'a [Symbol], symbols: &'a [Symbol],
) -> Result<crate::ir::Expr<'a>, Symbol> { ) -> BuildClosureData<'a> {
use crate::ir::Expr; use crate::ir::Expr;
match self.layout { match self.layout {
Layout::Struct(fields) if fields.len() == 1 => Err(symbols[0]), Layout::Struct(fields) if fields.len() == 1 => BuildClosureData::Alias(symbols[0]),
Layout::Struct(fields) => { Layout::Struct(fields) => {
debug_assert!(fields.len() > 1); debug_assert!(fields.len() > 1);
debug_assert_eq!(fields.len(), symbols.len()); debug_assert_eq!(fields.len(), symbols.len());
Ok(Expr::Struct(symbols)) BuildClosureData::Struct(Expr::Struct(symbols))
} }
Layout::Union(UnionLayout::NonRecursive(tags)) => { Layout::Union(UnionLayout::NonRecursive(tags)) => {
// NOTE it's very important that this Union consists of Closure tags // NOTE it's very important that this Union consists of Closure tags
@ -305,20 +337,17 @@ impl<'a> ClosureLayout<'a> {
.position(|(tn, _)| *tn == TagName::Closure(original)) .position(|(tn, _)| *tn == TagName::Closure(original))
.unwrap() as _; .unwrap() as _;
let expr = Expr::Tag { BuildClosureData::Union {
tag_layout: Layout::Union(UnionLayout::NonRecursive(tags)), tag_layout: Layout::Union(UnionLayout::NonRecursive(tags)),
tag_name: TagName::Closure(original), tag_name: TagName::Closure(original),
tag_id, tag_id,
union_size: tags.len() as u8, union_size: tags.len() as u8,
arguments: symbols, }
};
Ok(expr)
} }
Layout::PhantomEmptyStruct => { Layout::PhantomEmptyStruct => {
debug_assert_eq!(symbols.len(), 1); debug_assert_eq!(symbols.len(), 1);
Ok(Expr::Struct(&[])) BuildClosureData::Struct(Expr::Struct(&[]))
} }
_ => { _ => {
@ -330,12 +359,23 @@ impl<'a> ClosureLayout<'a> {
&self.layout &self.layout
); );
Err(symbols[0]) BuildClosureData::Alias(symbols[0])
} }
} }
} }
} }
pub enum BuildClosureData<'a> {
Alias(Symbol),
Struct(crate::ir::Expr<'a>),
Union {
tag_layout: Layout<'a>,
tag_name: TagName,
tag_id: u8,
union_size: u8,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum MemoryMode { pub enum MemoryMode {
Unique, Unique,
@ -392,7 +432,6 @@ impl<'a> Layout<'a> {
content: Content, content: Content,
) -> Result<Self, LayoutProblem> { ) -> Result<Self, LayoutProblem> {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
match content { match content {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
@ -653,6 +692,51 @@ impl<'a> Layout<'a> {
FunctionPointer(_, _) | Pointer(_) => false, FunctionPointer(_, _) | Pointer(_) => false,
} }
} }
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, parens: Parens) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use Layout::*;
match self {
Builtin(builtin) => builtin.to_doc(alloc, parens),
PhantomEmptyStruct => alloc.text("{}"),
Struct(fields) => {
let fields_doc = fields.iter().map(|x| x.to_doc(alloc, parens));
alloc
.text("{")
.append(alloc.intersperse(fields_doc, ", "))
.append(alloc.text("}"))
}
Union(union_layout) => union_layout.to_doc(alloc, parens),
RecursivePointer => alloc.text("*self"),
FunctionPointer(args, result) => {
let args_doc = args.iter().map(|x| x.to_doc(alloc, Parens::InFunction));
alloc
.intersperse(args_doc, ", ")
.append(alloc.text(" -> "))
.append(result.to_doc(alloc, Parens::InFunction))
}
Closure(args, closure_layout, result) => {
let args_doc = args.iter().map(|x| x.to_doc(alloc, Parens::InFunction));
let bom = closure_layout.layout.to_doc(alloc, Parens::NotNeeded);
alloc
.intersperse(args_doc, ", ")
.append(alloc.text(" {| "))
.append(bom)
.append(" |} -> ")
.append(result.to_doc(alloc, Parens::InFunction))
}
Pointer(_) => todo!(),
}
}
} }
/// Avoid recomputing Layout from Variable multiple times. /// Avoid recomputing Layout from Variable multiple times.
@ -793,7 +877,7 @@ impl<'a> Builtin<'a> {
/// Number of machine words in an empty one of these /// Number of machine words in an empty one of these
pub const STR_WORDS: u32 = 2; pub const STR_WORDS: u32 = 2;
pub const DICT_WORDS: u32 = 6; pub const DICT_WORDS: u32 = 3;
pub const SET_WORDS: u32 = Builtin::DICT_WORDS; // Set is an alias for Dict with {} for value pub const SET_WORDS: u32 = Builtin::DICT_WORDS; // Set is an alias for Dict with {} for value
pub const LIST_WORDS: u32 = 2; pub const LIST_WORDS: u32 = 2;
@ -877,6 +961,47 @@ impl<'a> Builtin<'a> {
Str | Dict(_, _) | Set(_) => true, Str | Dict(_, _) | Set(_) => true,
} }
} }
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, _parens: Parens) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
A: Clone,
{
use Builtin::*;
match self {
Int128 => alloc.text("Int128"),
Int64 => alloc.text("Int64"),
Int32 => alloc.text("Int32"),
Int16 => alloc.text("Int16"),
Int8 => alloc.text("Int8"),
Int1 => alloc.text("Int1"),
Usize => alloc.text("Usize"),
Float128 => alloc.text("Float128"),
Float64 => alloc.text("Float64"),
Float32 => alloc.text("Float32"),
Float16 => alloc.text("Float16"),
EmptyStr => alloc.text("EmptyStr"),
EmptyList => alloc.text("EmptyList"),
EmptyDict => alloc.text("EmptyDict"),
EmptySet => alloc.text("EmptySet"),
Str => alloc.text("Str"),
List(_, layout) => alloc
.text("List ")
.append(layout.to_doc(alloc, Parens::InTypeParam)),
Set(layout) => alloc
.text("Set ")
.append(layout.to_doc(alloc, Parens::InTypeParam)),
Dict(key_layout, value_layout) => alloc
.text("Dict ")
.append(key_layout.to_doc(alloc, Parens::InTypeParam))
.append(" ")
.append(value_layout.to_doc(alloc, Parens::InTypeParam)),
}
}
} }
fn layout_from_flat_type<'a>( fn layout_from_flat_type<'a>(
@ -961,6 +1086,7 @@ fn layout_from_flat_type<'a>(
Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)),
Symbol::LIST_LIST => list_layout_from_elem(env, args[0]), Symbol::LIST_LIST => list_layout_from_elem(env, args[0]),
Symbol::DICT_DICT => dict_layout_from_key_value(env, args[0], args[1]),
Symbol::ATTR_ATTR => { Symbol::ATTR_ATTR => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
@ -986,6 +1112,7 @@ fn layout_from_flat_type<'a>(
other => Ok(other), other => Ok(other),
} }
} }
Symbol::SET_SET => dict_layout_from_key_value(env, args[0], Variable::EMPTY_RECORD),
_ => { _ => {
panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args)); panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args));
} }
@ -1324,7 +1451,11 @@ impl<'a> WrappedVariant<'a> {
} }
} }
pub fn union_sorted_tags<'a>(arena: &'a Bump, var: Variable, subs: &Subs) -> UnionVariant<'a> { pub fn union_sorted_tags<'a>(
arena: &'a Bump,
var: Variable,
subs: &Subs,
) -> Result<UnionVariant<'a>, LayoutProblem> {
let var = let var =
if let Content::RecursionVar { structure, .. } = subs.get_without_compacting(var).content { if let Content::RecursionVar { structure, .. } = subs.get_without_compacting(var).content {
structure structure
@ -1338,10 +1469,11 @@ pub fn union_sorted_tags<'a>(arena: &'a Bump, var: Variable, subs: &Subs) -> Uni
let opt_rec_var = get_recursion_var(subs, var); let opt_rec_var = get_recursion_var(subs, var);
union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs) union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs)
} }
Err((_, Content::Error)) => return Err(LayoutProblem::Erroneous),
Err(other) => panic!("invalid content in tag union variable: {:?}", other), Err(other) => panic!("invalid content in tag union variable: {:?}", other),
}; };
result Ok(result)
} }
fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> { fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> {
@ -1791,6 +1923,47 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutPr
} }
} }
fn dict_layout_from_key_value<'a>(
env: &mut Env<'a, '_>,
key_var: Variable,
value_var: Variable,
) -> Result<Layout<'a>, LayoutProblem> {
match env.subs.get_without_compacting(key_var).content {
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, key_args)) => {
debug_assert_eq!(key_args.len(), 2);
let var = *key_args.get(1).unwrap();
dict_layout_from_key_value(env, var, value_var)
}
Content::FlexVar(_) | Content::RigidVar(_) => {
// If this was still a (Dict * *) then it must have been an empty dict
Ok(Layout::Builtin(Builtin::EmptyDict))
}
key_content => {
match env.subs.get_without_compacting(value_var).content {
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, value_args)) => {
debug_assert_eq!(value_args.len(), 2);
let var = *value_args.get(1).unwrap();
dict_layout_from_key_value(env, key_var, var)
}
value_content => {
let key_layout = Layout::new_help(env, key_var, key_content)?;
let value_layout = Layout::new_help(env, value_var, value_content)?;
// This is a normal list.
Ok(Layout::Builtin(Builtin::Dict(
env.arena.alloc(key_layout),
env.arena.alloc(value_layout),
)))
}
}
}
}
}
pub fn list_layout_from_elem<'a>( pub fn list_layout_from_elem<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
elem_var: Variable, elem_var: Variable,

View file

@ -1,443 +1,5 @@
extern crate bumpalo; extern crate bumpalo;
use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint;
use roc_can::env::Env;
use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator;
use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, Import};
use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_solve::solve;
use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type;
use std::hash::Hash;
use std::path::{Path, PathBuf};
pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
}
#[allow(dead_code)]
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<roc_solve::solve::TypeError>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: MutMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
}
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(input.as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str)
}
#[allow(dead_code)]
pub fn uniq_expr(
expr_str: &str,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
}
#[allow(dead_code)]
pub fn uniq_expr_with(
arena: &Bump,
expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let home = test_home();
let CanExprOut {
loc_expr,
output,
problems,
var_store: mut old_var_store,
var,
interns,
..
} = can_expr_with(arena, home, expr_str);
// double check
let mut var_store = VarStore::new(old_var_store.fresh());
let expected2 = Expected::NoExpectation(Type::Variable(var));
let constraint = roc_constrain::uniq::constrain_declaration(
home,
&mut var_store,
Region::zero(),
&loc_expr,
declared_idents,
expected2,
);
let stdlib = uniq_stdlib();
let types = stdlib.types;
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
// load builtin values
// TODO what to do with those rigids?
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
let subs2 = Subs::new(var_store.into());
(
loc_expr, output, problems, subs2, var, constraint, home, interns,
)
}
pub struct CanExprOut {
pub loc_expr: Located<Expr>,
pub output: Output,
pub problems: Vec<Problem>,
pub home: ModuleId,
pub interns: Interns,
pub var_store: VarStore,
pub var: Variable,
pub constraint: Constraint,
}
#[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e
)
});
let mut var_store = VarStore::default();
let var = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(var));
let module_ids = ModuleIds::default();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar_expr(arena, &loc_expr);
let mut scope = Scope::new(home, &mut var_store);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr(
&mut env,
&mut var_store,
&mut scope,
Region::zero(),
&loc_expr.value,
);
// Add the builtins' defs.
let mut with_builtins = loc_expr.value;
// Add builtin defs (e.g. List.get) directly to the canonical Expr,
// since we aren't using modules here.
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
for (symbol, def) in builtin_defs {
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{
with_builtins = roc_can::expr::Expr::LetNonRec(
Box::new(def),
Box::new(Located {
region: Region::zero(),
value: with_builtins,
}),
var_store.fresh(),
);
}
}
let loc_expr = Located {
region: loc_expr.region,
value: with_builtins,
};
let constraint = constrain_expr(
&roc_constrain::expr::Env {
rigids: ImMap::default(),
home,
},
loc_expr.region,
&loc_expr.value,
expected,
);
let types = roc_builtins::std::types();
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
//load builtin values
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
let mut all_ident_ids = MutMap::default();
// When pretty printing types, we may need the exposed builtins,
// so include them in the Interns we'll ultimately return.
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
all_ident_ids.insert(module_id, ident_ids);
}
all_ident_ids.insert(home, env.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids,
};
CanExprOut {
loc_expr,
output,
problems: env.problems,
home: env.home,
var_store,
interns,
var,
constraint,
}
}
#[allow(dead_code)]
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq,
{
let mut answer = MutMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn im_map_from_pairs<K, V, I>(pairs: I) -> ImMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq + Clone,
V: Clone,
{
let mut answer = ImMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn send_set_from<V, I>(elems: I) -> SendSet<V>
where
I: IntoIterator<Item = V>,
V: Hash + Eq + Clone,
{
let mut answer = SendSet::default();
for elem in elems {
answer.insert(elem);
}
answer
}
#[allow(dead_code)]
pub fn fixtures_dir<'a>() -> PathBuf {
Path::new("tests").join("fixtures").join("build")
}
#[allow(dead_code)]
pub fn builtins_dir<'a>() -> PathBuf {
PathBuf::new().join("builtins")
}
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
#[allow(dead_code)]
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
used.remove(unsafe { &Variable::unsafe_test_debug_variable(2) });
used.remove(unsafe { &Variable::unsafe_test_debug_variable(3) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Store(tipe, var, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
used.insert(*var);
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}
/// Used in the with_larger_debug_stack() function, for tests that otherwise /// Used in the with_larger_debug_stack() function, for tests that otherwise
/// run out of stack space in debug builds (but don't in --release builds) /// run out of stack space in debug builds (but don't in --release builds)
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -12,6 +12,7 @@ mod helpers;
// Test monomorphization // Test monomorphization
#[cfg(test)] #[cfg(test)]
mod test_mono { mod test_mono {
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::Proc; use roc_mono::ir::Proc;
@ -54,6 +55,7 @@ mod test_mono {
} }
let exposed_types = MutMap::default(); let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str( let loaded = roc_load::file::load_and_monomorphize_from_str(
arena, arena,
filename, filename,
@ -62,6 +64,7 @@ mod test_mono {
src_dir, src_dir,
exposed_types, exposed_types,
8, 8,
builtin_defs_map,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");
@ -631,6 +634,31 @@ mod test_mono {
) )
} }
#[test]
fn dict() {
compiles_to_ir(
r#"
Dict.len Dict.empty
"#,
indoc!(
r#"
procedure Dict.2 ():
let Test.4 = lowlevel DictEmpty ;
ret Test.4;
procedure Dict.8 (#Attr.2):
let Test.3 = lowlevel DictSize #Attr.2;
ret Test.3;
procedure Test.0 ():
let Test.2 = FunctionPointer Dict.2;
let Test.1 = CallByName Dict.8 Test.2;
ret Test.1;
"#
),
)
}
#[test] #[test]
fn list_append_closure() { fn list_append_closure() {
compiles_to_ir( compiles_to_ir(
@ -655,6 +683,7 @@ mod test_mono {
let Test.9 = 2i64; let Test.9 = 2i64;
let Test.4 = Array [Test.8, Test.9]; let Test.4 = Array [Test.8, Test.9];
let Test.3 = CallByName Test.1 Test.4; let Test.3 = CallByName Test.1 Test.4;
dec Test.4;
ret Test.3; ret Test.3;
"# "#
), ),
@ -680,6 +709,7 @@ mod test_mono {
let Test.2 = Array [Test.5]; let Test.2 = Array [Test.5];
let Test.3 = 2i64; let Test.3 = 2i64;
let Test.1 = CallByName List.5 Test.2 Test.3; let Test.1 = CallByName List.5 Test.2 Test.3;
dec Test.2;
ret Test.1; ret Test.1;
"# "#
), ),
@ -921,8 +951,7 @@ mod test_mono {
let Test.5 = 3.14f64; let Test.5 = 3.14f64;
let Test.3 = Struct {Test.4, Test.5}; let Test.3 = Struct {Test.4, Test.5};
let Test.1 = Index 0 Test.3; let Test.1 = Index 0 Test.3;
inc Test.1; decref Test.3;
dec Test.3;
ret Test.1; ret Test.1;
"# "#
), ),
@ -1940,15 +1969,14 @@ mod test_mono {
let Test.7 = Index 1 Test.2; let Test.7 = Index 1 Test.2;
let Test.8 = 0i64; let Test.8 = 0i64;
let Test.9 = Index 0 Test.7; let Test.9 = Index 0 Test.7;
dec Test.7;
decref Test.2;
let Test.10 = lowlevel Eq Test.8 Test.9; let Test.10 = lowlevel Eq Test.8 Test.9;
if Test.10 then if Test.10 then
let Test.4 = Index 1 Test.2;
let Test.3 = 1i64; let Test.3 = 1i64;
decref Test.2;
ret Test.3; ret Test.3;
else else
let Test.5 = 0i64; let Test.5 = 0i64;
dec Test.2;
ret Test.5; ret Test.5;
else else
let Test.6 = 0i64; let Test.6 = 0i64;

View file

@ -611,6 +611,7 @@ pub enum Attempting {
TypeVariable, TypeVariable,
WhenCondition, WhenCondition,
WhenBranch, WhenBranch,
TODO,
} }
impl<'a> Expr<'a> { impl<'a> Expr<'a> {

View file

@ -1,24 +1,29 @@
use crate::ast::CommentOrNewline::{self, *}; use crate::ast::CommentOrNewline::{self, *};
use crate::ast::{Attempting, Spaceable}; use crate::ast::{Attempting, Spaceable};
use crate::parser::{ use crate::parser::{
self, and, ascii_char, ascii_string, optional, parse_utf8, peek_utf8_char, then, unexpected, self, and, ascii_char, ascii_string, backtrackable, bad_input_to_syntax_error, optional,
unexpected_eof, FailReason, Parser, State, parse_utf8, peek_utf8_char, then, unexpected, unexpected_eof, BadInputError, Col, Parser,
Progress::{self, *},
Row, State, SyntaxError,
}; };
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_region::all::Located; use roc_region::all::{Located, Region};
/// Parses the given expression with 0 or more (spaces/comments/newlines) before and/or after it. /// Parses the given expression with 0 or more (spaces/comments/newlines) before and/or after it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces. /// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// If any newlines or comments were found, the Expr will be wrapped in a SpaceBefore and/or /// If any newlines or comments were found, the Expr will be wrapped in a SpaceBefore and/or
/// SpaceAfter as appropriate. /// SpaceAfter as appropriate.
pub fn space0_around<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>> pub fn space0_around<'a, P, S>(
parser: P,
min_indent: u16,
) -> impl Parser<'a, Located<S>, SyntaxError<'a>>
where where
S: Spaceable<'a>, S: Spaceable<'a>,
S: 'a, S: 'a,
S: Sized, S: Sized,
P: Parser<'a, Located<S>>, P: Parser<'a, Located<S>, SyntaxError<'a>>,
P: 'a, P: 'a,
{ {
parser::map_with_arena( parser::map_with_arena(
@ -55,16 +60,69 @@ where
) )
} }
pub fn space0_around_e<'a, P, S, E>(
parser: P,
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Located<S>, E>,
P: 'a,
E: 'a,
{
parser::map_with_arena(
and(
space0_e(min_indent, space_problem, indent_problem),
and(parser, space0_e(min_indent, space_problem, indent_problem)),
),
move |arena: &'a Bump,
tuples: (
&'a [CommentOrNewline<'a>],
(Located<S>, &'a [CommentOrNewline<'a>]),
)| {
let (spaces_before, (loc_val, spaces_after)) = tuples;
if spaces_before.is_empty() {
if spaces_after.is_empty() {
loc_val
} else {
arena
.alloc(loc_val.value)
.with_spaces_after(spaces_after, loc_val.region)
}
} else if spaces_after.is_empty() {
arena
.alloc(loc_val.value)
.with_spaces_before(spaces_before, loc_val.region)
} else {
let wrapped_expr = arena
.alloc(loc_val.value)
.with_spaces_after(spaces_after, loc_val.region);
arena
.alloc(wrapped_expr.value)
.with_spaces_before(spaces_before, wrapped_expr.region)
}
},
)
}
/// Parses the given expression with 1 or more (spaces/comments/newlines) before it, /// Parses the given expression with 1 or more (spaces/comments/newlines) before it,
/// and also 1 or more spaces after it. /// and also 1 or more spaces after it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces. /// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// If any newlines or comments were found, the Expr will be wrapped in a SpaceBefore and/or /// If any newlines or comments were found, the Expr will be wrapped in a SpaceBefore and/or
/// SpaceAfter as appropriate. /// SpaceAfter as appropriate.
pub fn space1_around<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>> pub fn space1_around<'a, P, S>(
parser: P,
min_indent: u16,
) -> impl Parser<'a, Located<S>, SyntaxError<'a>>
where where
S: Spaceable<'a>, S: Spaceable<'a>,
S: 'a, S: 'a,
P: Parser<'a, Located<S>>, P: Parser<'a, Located<S>, SyntaxError<'a>>,
P: 'a, P: 'a,
{ {
parser::map_with_arena( parser::map_with_arena(
@ -98,11 +156,14 @@ where
/// Parses the given expression with 0 or more (spaces/comments/newlines) before it. /// Parses the given expression with 0 or more (spaces/comments/newlines) before it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces. /// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found. /// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found.
pub fn space0_before<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>> pub fn space0_before<'a, P, S>(
parser: P,
min_indent: u16,
) -> impl Parser<'a, Located<S>, SyntaxError<'a>>
where where
S: Spaceable<'a>, S: Spaceable<'a>,
S: 'a, S: 'a,
P: Parser<'a, Located<S>>, P: Parser<'a, Located<S>, SyntaxError<'a>>,
P: 'a, P: 'a,
{ {
parser::map_with_arena( parser::map_with_arena(
@ -119,18 +180,75 @@ where
) )
} }
/// Parses the given expression with 1 or more (spaces/comments/newlines) before it. pub fn space0_before_e<'a, P, S, E>(
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces. parser: P,
/// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found. min_indent: u16,
pub fn space1_before<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>> space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E>
where where
S: Spaceable<'a>, S: Spaceable<'a>,
S: 'a, S: 'a,
P: Parser<'a, Located<S>>, P: Parser<'a, Located<S>, E>,
P: 'a,
E: 'a,
{
parser::map_with_arena(
and!(space0_e(min_indent, space_problem, indent_problem), parser),
|arena: &'a Bump, (space_list, loc_expr): (&'a [CommentOrNewline<'a>], Located<S>)| {
if space_list.is_empty() {
loc_expr
} else {
arena
.alloc(loc_expr.value)
.with_spaces_before(space_list, loc_expr.region)
}
},
)
}
pub fn space0_after_e<'a, P, S, E>(
parser: P,
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Located<S>, E>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Located<S>, E>,
P: 'a,
E: 'a,
{
parser::map_with_arena(
and!(parser, space0_e(min_indent, space_problem, indent_problem)),
|arena: &'a Bump, (loc_expr, space_list): (Located<S>, &'a [CommentOrNewline<'a>])| {
if space_list.is_empty() {
loc_expr
} else {
arena
.alloc(loc_expr.value)
.with_spaces_after(space_list, loc_expr.region)
}
},
)
}
/// Parses the given expression with 1 or more (spaces/comments/newlines) before it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// The Expr will be wrapped in a SpaceBefore if there were any newlines or comments found.
pub fn space1_before<'a, P, S>(
parser: P,
min_indent: u16,
) -> impl Parser<'a, Located<S>, SyntaxError<'a>>
where
S: Spaceable<'a>,
S: 'a,
P: Parser<'a, Located<S>, SyntaxError<'a>>,
P: 'a, P: 'a,
{ {
parser::map_with_arena( parser::map_with_arena(
and!(space1(min_indent), parser), and!(backtrackable(space1(min_indent)), parser),
|arena, (space_list, loc_expr)| { |arena, (space_list, loc_expr)| {
if space_list.is_empty() { if space_list.is_empty() {
loc_expr loc_expr
@ -146,11 +264,14 @@ where
/// Parses the given expression with 0 or more (spaces/comments/newlines) after it. /// Parses the given expression with 0 or more (spaces/comments/newlines) after it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces. /// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// The Expr will be wrapped in a SpaceAfter if there were any newlines or comments found. /// The Expr will be wrapped in a SpaceAfter if there were any newlines or comments found.
pub fn space0_after<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>> pub fn space0_after<'a, P, S>(
parser: P,
min_indent: u16,
) -> impl Parser<'a, Located<S>, SyntaxError<'a>>
where where
S: Spaceable<'a>, S: Spaceable<'a>,
S: 'a, S: 'a,
P: Parser<'a, Located<S>>, P: Parser<'a, Located<S>, SyntaxError<'a>>,
P: 'a, P: 'a,
{ {
parser::map_with_arena( parser::map_with_arena(
@ -170,11 +291,14 @@ where
/// Parses the given expression with 1 or more (spaces/comments/newlines) after it. /// Parses the given expression with 1 or more (spaces/comments/newlines) after it.
/// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces. /// Returns a Located<Expr> where the location is around the Expr, ignoring the spaces.
/// The Expr will be wrapped in a SpaceAfter if there were any newlines or comments found. /// The Expr will be wrapped in a SpaceAfter if there were any newlines or comments found.
pub fn space1_after<'a, P, S>(parser: P, min_indent: u16) -> impl Parser<'a, Located<S>> pub fn space1_after<'a, P, S>(
parser: P,
min_indent: u16,
) -> impl Parser<'a, Located<S>, SyntaxError<'a>>
where where
S: Spaceable<'a>, S: Spaceable<'a>,
S: 'a, S: 'a,
P: Parser<'a, Located<S>>, P: Parser<'a, Located<S>, SyntaxError<'a>>,
P: 'a, P: 'a,
{ {
parser::map_with_arena( parser::map_with_arena(
@ -192,12 +316,25 @@ where
} }
/// Zero or more (spaces/comments/newlines). /// Zero or more (spaces/comments/newlines).
pub fn space0<'a>(min_indent: u16) -> impl Parser<'a, &'a [CommentOrNewline<'a>]> { pub fn space0<'a>(min_indent: u16) -> impl Parser<'a, &'a [CommentOrNewline<'a>], SyntaxError<'a>> {
spaces(false, min_indent) spaces(false, min_indent)
} }
pub fn space0_e<'a, E>(
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
where
E: 'a,
{
spaces_help(false, min_indent, space_problem, indent_problem, |_, _| {
unreachable!("no spaces are required, so this is unreachable")
})
}
/// One or more (spaces/comments/newlines). /// One or more (spaces/comments/newlines).
pub fn space1<'a>(min_indent: u16) -> impl Parser<'a, &'a [CommentOrNewline<'a>]> { pub fn space1<'a>(min_indent: u16) -> impl Parser<'a, &'a [CommentOrNewline<'a>], SyntaxError<'a>> {
// TODO try benchmarking a short-circuit for the typical case: see if there is // TODO try benchmarking a short-circuit for the typical case: see if there is
// exactly one space followed by char that isn't [' ', '\n', or '#'], and // exactly one space followed by char that isn't [' ', '\n', or '#'], and
// if so, return empty slice. The case where there's exactly 1 space should // if so, return empty slice. The case where there's exactly 1 space should
@ -212,12 +349,12 @@ enum LineState {
DocComment, DocComment,
} }
pub fn line_comment<'a>() -> impl Parser<'a, &'a str> { pub fn line_comment<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
then( then(
and!(ascii_char(b'#'), optional(ascii_string("# "))), and!(ascii_char(b'#'), optional(ascii_string("# "))),
|_arena: &'a Bump, state: State<'a>, (_, opt_doc)| { |arena: &'a Bump, state: State<'a>, _, (_, opt_doc)| {
if opt_doc != None { if opt_doc != None {
return Err(unexpected(3, state, Attempting::LineComment)); return Err(unexpected(arena, 3, Attempting::LineComment, state));
} }
let mut length = 0; let mut length = 0;
@ -230,20 +367,20 @@ pub fn line_comment<'a>() -> impl Parser<'a, &'a str> {
} }
let comment = &state.bytes[..length]; let comment = &state.bytes[..length];
let state = state.advance_without_indenting(length + 1)?; let state = state.advance_without_indenting(arena, length + 1)?;
match parse_utf8(comment) { match parse_utf8(comment) {
Ok(comment_str) => Ok((comment_str, state)), Ok(comment_str) => Ok((MadeProgress, comment_str, state)),
Err(reason) => state.fail(reason), Err(reason) => state.fail(arena, MadeProgress, reason),
} }
}, },
) )
} }
#[inline(always)] #[inline(always)]
pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> { pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, (), SyntaxError<'a>> {
move |_arena: &'a Bump, state: State<'a>| { move |arena: &'a Bump, state: State<'a>| {
if spaces_expected == 0 { if spaces_expected == 0 {
return Ok(((), state)); return Ok((NoProgress, (), state));
} }
let mut state = state; let mut state = state;
@ -253,31 +390,34 @@ pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((' ', _)) => { Ok((' ', _)) => {
spaces_seen += 1; spaces_seen += 1;
state = state.advance_spaces(1)?; state = state.advance_spaces(arena, 1)?;
if spaces_seen == spaces_expected { if spaces_seen == spaces_expected {
return Ok(((), state)); return Ok((MadeProgress, (), state));
} }
} }
Ok(_) => { Ok(_) => {
return Err(unexpected( return Err(unexpected(
arena,
spaces_seen.into(), spaces_seen.into(),
Attempting::TODO,
state.clone(), state.clone(),
state.attempting,
)); ));
} }
Err(FailReason::BadUtf8) => { Err(SyntaxError::BadUtf8) => {
// If we hit an invalid UTF-8 character, bail out immediately. // If we hit an invalid UTF-8 character, bail out immediately.
return state.fail(FailReason::BadUtf8); let progress = Progress::progress_when(spaces_seen != 0);
return state.fail(arena, progress, SyntaxError::BadUtf8);
} }
Err(_) => { Err(_) => {
if spaces_seen == 0 { if spaces_seen == 0 {
return Err(unexpected_eof(0, state.attempting, state)); return Err(unexpected_eof(arena, state, 0));
} else { } else {
return Err(unexpected( return Err(unexpected(
arena,
spaces_seen.into(), spaces_seen.into(),
Attempting::TODO,
state.clone(), state.clone(),
state.attempting,
)); ));
} }
} }
@ -285,12 +425,13 @@ pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> {
} }
if spaces_seen == 0 { if spaces_seen == 0 {
Err(unexpected_eof(0, state.attempting, state)) Err(unexpected_eof(arena, state, 0))
} else { } else {
Err(unexpected( Err(unexpected(
arena,
spaces_seen.into(), spaces_seen.into(),
state.clone(), Attempting::TODO,
state.attempting, state,
)) ))
} }
} }
@ -300,7 +441,27 @@ pub fn spaces_exactly<'a>(spaces_expected: u16) -> impl Parser<'a, ()> {
fn spaces<'a>( fn spaces<'a>(
require_at_least_one: bool, require_at_least_one: bool,
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, &'a [CommentOrNewline<'a>]> { ) -> impl Parser<'a, &'a [CommentOrNewline<'a>], SyntaxError<'a>> {
spaces_help(
require_at_least_one,
min_indent,
bad_input_to_syntax_error,
|_, _| SyntaxError::OutdentedTooFar,
|_, _| SyntaxError::Eof(Region::zero()),
)
}
#[inline(always)]
fn spaces_help<'a, E>(
require_at_least_one: bool,
min_indent: u16,
space_problem: fn(BadInputError, Row, Col) -> E,
indent_problem: fn(Row, Col) -> E,
missing_space_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
where
E: 'a,
{
move |arena: &'a Bump, state: State<'a>| { move |arena: &'a Bump, state: State<'a>| {
let original_state = state.clone(); let original_state = state.clone();
let mut space_list = Vec::new_in(arena); let mut space_list = Vec::new_in(arena);
@ -310,6 +471,11 @@ fn spaces<'a>(
let mut state = state; let mut state = state;
let mut any_newlines = false; let mut any_newlines = false;
let start_row = original_state.line;
let start_col = original_state.column;
let start_bytes_len = state.bytes.len();
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((ch, utf8_len)) => { Ok((ch, utf8_len)) => {
@ -321,28 +487,51 @@ fn spaces<'a>(
' ' => { ' ' => {
// Don't check indentation here; it might not be enough // Don't check indentation here; it might not be enough
// indentation yet, but maybe it will be after more spaces happen! // indentation yet, but maybe it will be after more spaces happen!
state = state.advance_spaces(1)?; state = state.advance_spaces_e(arena, 1, space_problem)?;
} }
'\r' => { '\r' => {
// Ignore carriage returns. // Ignore carriage returns.
state = state.advance_spaces(1)?; state = state.advance_spaces_e(arena, 1, space_problem)?;
} }
'\n' => { '\n' => {
// No need to check indentation because we're about to reset it anyway. // don't need to check the indent here since we'll reset it
state = state.newline()?; // anyway
state = state.newline_e(arena, space_problem)?;
// Newlines only get added to the list when they're outside comments. // Newlines only get added to the list when they're outside comments.
space_list.push(Newline); space_list.push(Newline);
any_newlines = true; any_newlines = true;
} }
'\t' => {
return Err((
MadeProgress,
space_problem(
BadInputError::HasTab,
state.line,
state.column,
),
state,
));
}
'#' => { '#' => {
// Check indentation to make sure we were indented enough // Check indentation to make sure we were indented enough
// before this comment began. // before this comment began.
let progress =
Progress::from_lengths(start_bytes_len, state.bytes.len());
state = state state = state
.check_indent(min_indent) .check_indent_e(
.map_err(|(fail, _)| (fail, original_state.clone()))? arena,
.advance_without_indenting(1)?; min_indent,
indent_problem,
start_row,
start_col,
)
.map_err(|(fail, _)| {
(progress, fail, original_state.clone())
})?
.advance_without_indenting_e(arena, 1, space_problem)?;
// We're now parsing a line comment! // We're now parsing a line comment!
line_state = LineState::Comment; line_state = LineState::Comment;
@ -351,7 +540,11 @@ fn spaces<'a>(
return if require_at_least_one && bytes_parsed <= 1 { return if require_at_least_one && bytes_parsed <= 1 {
// We've parsed 1 char and it was not a space, // We've parsed 1 char and it was not a space,
// but we require parsing at least one space! // but we require parsing at least one space!
Err(unexpected(0, state.clone(), state.attempting)) Err((
NoProgress,
missing_space_problem(state.line, state.column),
state,
))
} else { } else {
// First make sure we were indented enough! // First make sure we were indented enough!
// //
@ -360,13 +553,25 @@ fn spaces<'a>(
// It's actively important for correctness that we skip // It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise // this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.) // we would have false positives for single-line defs.)
let progress = Progress::from_lengths(
start_bytes_len,
state.bytes.len(),
);
if any_newlines { if any_newlines {
state = state state = state
.check_indent(min_indent) .check_indent_e(
.map_err(|(fail, _)| (fail, original_state))?; arena,
min_indent,
indent_problem,
start_row,
start_col,
)
.map_err(|(fail, _)| {
(progress, fail, original_state.clone())
})?;
} }
Ok((space_list.into_bump_slice(), state)) Ok((progress, space_list.into_bump_slice(), state))
}; };
} }
} }
@ -375,7 +580,11 @@ fn spaces<'a>(
match ch { match ch {
' ' => { ' ' => {
// If we're in a line comment, this won't affect indentation anyway. // If we're in a line comment, this won't affect indentation anyway.
state = state.advance_without_indenting(1)?; state = state.advance_without_indenting_e(
arena,
1,
space_problem,
)?;
if comment_line_buf.len() == 1 { if comment_line_buf.len() == 1 {
match comment_line_buf.chars().next() { match comment_line_buf.chars().next() {
@ -400,7 +609,7 @@ fn spaces<'a>(
} }
} }
'\n' => { '\n' => {
state = state.newline()?; state = state.newline_e(arena, space_problem)?;
match (comment_line_buf.len(), comment_line_buf.chars().next()) match (comment_line_buf.len(), comment_line_buf.chars().next())
{ {
@ -423,9 +632,24 @@ fn spaces<'a>(
} }
} }
} }
'\t' => {
return Err((
MadeProgress,
space_problem(
BadInputError::HasTab,
state.line,
state.column,
),
state,
));
}
nonblank => { nonblank => {
// Chars can have btye lengths of more than 1! // Chars can have btye lengths of more than 1!
state = state.advance_without_indenting(nonblank.len_utf8())?; state = state.advance_without_indenting_e(
arena,
nonblank.len_utf8(),
space_problem,
)?;
comment_line_buf.push(nonblank); comment_line_buf.push(nonblank);
} }
@ -435,12 +659,16 @@ fn spaces<'a>(
match ch { match ch {
' ' => { ' ' => {
// If we're in a doc comment, this won't affect indentation anyway. // If we're in a doc comment, this won't affect indentation anyway.
state = state.advance_without_indenting(1)?; state = state.advance_without_indenting_e(
arena,
1,
space_problem,
)?;
comment_line_buf.push(ch); comment_line_buf.push(ch);
} }
'\n' => { '\n' => {
state = state.newline()?; state = state.newline_e(arena, space_problem)?;
// This was a newline, so end this doc comment. // This was a newline, so end this doc comment.
space_list.push(DocComment(comment_line_buf.into_bump_str())); space_list.push(DocComment(comment_line_buf.into_bump_str()));
@ -448,8 +676,23 @@ fn spaces<'a>(
line_state = LineState::Normal; line_state = LineState::Normal;
} }
'\t' => {
return Err((
MadeProgress,
space_problem(
BadInputError::HasTab,
state.line,
state.column,
),
state,
));
}
nonblank => { nonblank => {
state = state.advance_without_indenting(utf8_len)?; state = state.advance_without_indenting_e(
arena,
utf8_len,
space_problem,
)?;
comment_line_buf.push(nonblank); comment_line_buf.push(nonblank);
} }
@ -457,13 +700,24 @@ fn spaces<'a>(
} }
} }
} }
Err(FailReason::BadUtf8) => { Err(SyntaxError::BadUtf8) => {
// If we hit an invalid UTF-8 character, bail out immediately. // If we hit an invalid UTF-8 character, bail out immediately.
return state.fail(FailReason::BadUtf8); let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
let row = state.line;
let col = state.column;
return state.fail(
arena,
progress,
space_problem(BadInputError::BadUtf8, row, col),
);
} }
Err(_) => { Err(_) => {
if require_at_least_one && bytes_parsed == 0 { if require_at_least_one && bytes_parsed == 0 {
return Err(unexpected_eof(0, state.attempting, state)); return Err((
NoProgress,
missing_space_problem(state.line, state.column),
state,
));
} else { } else {
let space_slice = space_list.into_bump_slice(); let space_slice = space_list.into_bump_slice();
@ -474,24 +728,35 @@ fn spaces<'a>(
// It's actively important for correctness that we skip // It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise // this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.) // we would have false positives for single-line defs.)
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
if any_newlines { if any_newlines {
return Ok(( return Ok((
progress,
space_slice, space_slice,
state state
.check_indent(min_indent) .check_indent_e(
.map_err(|(fail, _)| (fail, original_state))?, arena,
min_indent,
indent_problem,
start_row,
start_col,
)
.map_err(|(fail, _)| (progress, fail, original_state))?,
)); ));
} }
return Ok((space_slice, state)); return Ok((progress, space_slice, state));
} }
} }
}; };
} }
// If we didn't parse anything, return unexpected EOF
if require_at_least_one && original_state.bytes.len() == state.bytes.len() { if require_at_least_one && original_state.bytes.len() == state.bytes.len() {
Err(unexpected_eof(0, state.attempting, state)) Err((
NoProgress,
missing_space_problem(state.line, state.column),
state,
))
} else { } else {
// First make sure we were indented enough! // First make sure we were indented enough!
// //
@ -500,13 +765,14 @@ fn spaces<'a>(
// It's actively important for correctness that we skip // It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise // this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.) // we would have false positives for single-line defs.)
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
if any_newlines { if any_newlines {
state = state state = state
.check_indent(min_indent) .check_indent_e(arena, min_indent, indent_problem, start_row, start_col)
.map_err(|(fail, _)| (fail, original_state))?; .map_err(|(fail, _)| (progress, fail, original_state))?;
} }
Ok((space_list.into_bump_slice(), state)) Ok((progress, space_list.into_bump_slice(), state))
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ use crate::ast::{CommentOrNewline, Spaceable, StrLiteral, TypeAnnotation};
use crate::blankspace::space0; use crate::blankspace::space0;
use crate::ident::lowercase_ident; use crate::ident::lowercase_ident;
use crate::module::package_name; use crate::module::package_name;
use crate::parser::{ascii_char, optional, Either, Parser}; use crate::parser::{ascii_char, optional, Either, Parser, Progress::*, State, SyntaxError};
use crate::string_literal; use crate::string_literal;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
@ -239,18 +239,19 @@ impl<'a> Spaceable<'a> for PackageEntry<'a> {
} }
} }
pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>> { pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>, SyntaxError<'a>> {
move |arena, state| { move |arena, state| {
// You may optionally have a package shorthand, // You may optionally have a package shorthand,
// e.g. "uc" in `uc: roc/unicode 1.0.0` // e.g. "uc" in `uc: roc/unicode 1.0.0`
// //
// (Indirect dependencies don't have a shorthand.) // (Indirect dependencies don't have a shorthand.)
let (opt_shorthand, state) = optional(and!( let (_, opt_shorthand, state) = optional(and!(
skip_second!(lowercase_ident(), ascii_char(b':')), skip_second!(lowercase_ident(), ascii_char(b':')),
space0(1) space0(1)
)) ))
.parse(arena, state)?; .parse(arena, state)?;
let (package_or_path, state) = loc!(package_or_path()).parse(arena, state)?; let (_, package_or_path, state) = loc!(package_or_path()).parse(arena, state)?;
let entry = match opt_shorthand { let entry = match opt_shorthand {
Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry { Some((shorthand, spaces_after_shorthand)) => PackageEntry::Entry {
shorthand, shorthand,
@ -264,11 +265,11 @@ pub fn package_entry<'a>() -> impl Parser<'a, PackageEntry<'a>> {
}, },
}; };
Ok((entry, state)) Ok((MadeProgress, entry, state))
} }
} }
pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>> { pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>, SyntaxError<'a>> {
map!( map!(
either!( either!(
string_literal::parse(), string_literal::parse(),
@ -286,6 +287,6 @@ pub fn package_or_path<'a>() -> impl Parser<'a, PackageOrPath<'a>> {
) )
} }
fn package_version<'a>() -> impl Parser<'a, Version<'a>> { fn package_version<'a>() -> impl Parser<'a, Version<'a>, SyntaxError<'a>> {
move |_, _| todo!("TODO parse package version") move |_, _| todo!("TODO parse package version")
} }

View file

@ -1,6 +1,7 @@
use crate::ast::Attempting; use crate::ast::Attempting;
use crate::keyword; use crate::keyword;
use crate::parser::{peek_utf8_char, unexpected, Fail, FailReason, ParseResult, Parser, State}; use crate::parser::Progress::{self, *};
use crate::parser::{peek_utf8_char, unexpected, ParseResult, Parser, State, SyntaxError};
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
@ -70,7 +71,7 @@ impl<'a> Ident<'a> {
pub fn parse_ident<'a>( pub fn parse_ident<'a>(
arena: &'a Bump, arena: &'a Bump,
mut state: State<'a>, mut state: State<'a>,
) -> ParseResult<'a, (Ident<'a>, Option<char>)> { ) -> ParseResult<'a, (Ident<'a>, Option<char>), SyntaxError<'a>> {
let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.) let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.)
let mut capitalized_parts: Vec<&'a str> = Vec::new_in(arena); let mut capitalized_parts: Vec<&'a str> = Vec::new_in(arena);
let mut noncapitalized_parts: Vec<&'a str> = Vec::new_in(arena); let mut noncapitalized_parts: Vec<&'a str> = Vec::new_in(arena);
@ -78,6 +79,8 @@ pub fn parse_ident<'a>(
let is_accessor_fn; let is_accessor_fn;
let mut is_private_tag = false; let mut is_private_tag = false;
let start_bytes_len = state.bytes.len();
// Identifiers and accessor functions must start with either a letter or a dot. // Identifiers and accessor functions must start with either a letter or a dot.
// If this starts with neither, it must be something else! // If this starts with neither, it must be something else!
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
@ -88,20 +91,20 @@ pub fn parse_ident<'a>(
is_capitalized = first_ch.is_uppercase(); is_capitalized = first_ch.is_uppercase();
is_accessor_fn = false; is_accessor_fn = false;
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} else if first_ch == '.' { } else if first_ch == '.' {
is_capitalized = false; is_capitalized = false;
is_accessor_fn = true; is_accessor_fn = true;
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} else if first_ch == '@' { } else if first_ch == '@' {
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
// '@' must always be followed by a capital letter! // '@' must always be followed by a capital letter!
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((next_ch, next_bytes_parsed)) => { Ok((next_ch, next_bytes_parsed)) => {
if next_ch.is_uppercase() { if next_ch.is_uppercase() {
state = state.advance_without_indenting(next_bytes_parsed)?; state = state.advance_without_indenting(arena, next_bytes_parsed)?;
part_buf.push('@'); part_buf.push('@');
part_buf.push(next_ch); part_buf.push(next_ch);
@ -111,19 +114,26 @@ pub fn parse_ident<'a>(
is_accessor_fn = false; is_accessor_fn = false;
} else { } else {
return Err(unexpected( return Err(unexpected(
arena,
bytes_parsed + next_bytes_parsed, bytes_parsed + next_bytes_parsed,
state,
Attempting::Identifier, Attempting::Identifier,
state,
)); ));
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
}
} }
} else { } else {
return Err(unexpected(0, state, Attempting::Identifier)); return Err(unexpected(arena, 0, Attempting::Identifier, state));
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
}
} }
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
@ -183,9 +193,12 @@ pub fn parse_ident<'a>(
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
}
Err(reason) => {
let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
return state.fail(arena, progress, reason);
} }
Err(reason) => return state.fail(reason),
} }
} }
@ -241,7 +254,7 @@ pub fn parse_ident<'a>(
// We had neither capitalized nor noncapitalized parts, // We had neither capitalized nor noncapitalized parts,
// yet we made it this far. The only explanation is that this was // yet we made it this far. The only explanation is that this was
// a stray '.' drifting through the cosmos. // a stray '.' drifting through the cosmos.
return Err(unexpected(1, state, Attempting::Identifier)); return Err(unexpected(arena, 1, Attempting::Identifier, state));
} }
} }
} else if is_private_tag { } else if is_private_tag {
@ -255,7 +268,9 @@ pub fn parse_ident<'a>(
} }
}; };
Ok(((answer, None), state)) let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
debug_assert_eq!(progress, Progress::MadeProgress,);
Ok((Progress::MadeProgress, (answer, None), state))
} }
fn malformed<'a>( fn malformed<'a>(
@ -264,7 +279,7 @@ fn malformed<'a>(
mut state: State<'a>, mut state: State<'a>,
capitalized_parts: Vec<&'a str>, capitalized_parts: Vec<&'a str>,
noncapitalized_parts: Vec<&'a str>, noncapitalized_parts: Vec<&'a str>,
) -> ParseResult<'a, (Ident<'a>, Option<char>)> { ) -> ParseResult<'a, (Ident<'a>, Option<char>), SyntaxError<'a>> {
// Reconstruct the original string that we've been parsing. // Reconstruct the original string that we've been parsing.
let mut full_string = String::new_in(arena); let mut full_string = String::new_in(arena);
@ -293,28 +308,29 @@ fn malformed<'a>(
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
} }
} }
Ok(( Ok((
MadeProgress,
(Ident::Malformed(full_string.into_bump_str()), next_char), (Ident::Malformed(full_string.into_bump_str()), next_char),
state, state,
)) ))
} }
pub fn ident<'a>() -> impl Parser<'a, Ident<'a>> { pub fn ident<'a>() -> impl Parser<'a, Ident<'a>, SyntaxError<'a>> {
move |arena: &'a Bump, state: State<'a>| { move |arena: &'a Bump, state: State<'a>| {
// Discard next_char; we don't need it. // Discard next_char; we don't need it.
let ((string, _), state) = parse_ident(arena, state)?; let (progress, (string, _), state) = parse_ident(arena, state)?;
Ok((string, state)) Ok((progress, string, state))
} }
} }
pub fn global_tag_or_ident<'a, F>(pred: F) -> impl Parser<'a, &'a str> pub fn global_tag_or_ident<'a, F>(pred: F) -> impl Parser<'a, &'a str, SyntaxError<'a>>
where where
F: Fn(char) -> bool, F: Fn(char) -> bool,
{ {
@ -323,19 +339,19 @@ where
let (first_letter, bytes_parsed) = match peek_utf8_char(&state) { let (first_letter, bytes_parsed) = match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => { Ok((first_letter, bytes_parsed)) => {
if !pred(first_letter) { if !pred(first_letter) {
return Err(unexpected(0, state, Attempting::RecordFieldLabel)); return Err(unexpected(arena, 0, Attempting::RecordFieldLabel, state));
} }
(first_letter, bytes_parsed) (first_letter, bytes_parsed)
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, NoProgress, reason),
}; };
let mut buf = String::with_capacity_in(1, arena); let mut buf = String::with_capacity_in(1, arena);
buf.push(first_letter); buf.push(first_letter);
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
@ -348,17 +364,17 @@ where
if ch.is_alphabetic() || ch.is_ascii_digit() { if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch); buf.push(ch);
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} else { } else {
// This is the end of the field. We're done! // This is the end of the field. We're done!
break; break;
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
}; };
} }
Ok((buf.into_bump_str(), state)) Ok((MadeProgress, buf.into_bump_str(), state))
} }
} }
@ -366,11 +382,14 @@ where
/// ///
/// * A record field, e.g. "email" in `.email` or in `email:` /// * A record field, e.g. "email" in `.email` or in `email:`
/// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->` /// * A named pattern match, e.g. "foo" in `foo =` or `foo ->` or `\foo ->`
pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> { pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
move |arena, state| { move |arena, state: State<'a>| {
let (ident, state) = let (progress, ident, state) =
global_tag_or_ident(|first_char| first_char.is_lowercase()).parse(arena, state)?; global_tag_or_ident(|first_char| first_char.is_lowercase()).parse(arena, state)?;
// to parse a valid ident, progress must be made
debug_assert_eq!(progress, MadeProgress);
if (ident == keyword::IF) if (ident == keyword::IF)
|| (ident == keyword::THEN) || (ident == keyword::THEN)
|| (ident == keyword::ELSE) || (ident == keyword::ELSE)
@ -380,15 +399,9 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> {
{ {
// TODO Calculate the correct region based on state // TODO Calculate the correct region based on state
let region = Region::zero(); let region = Region::zero();
Err(( Err((MadeProgress, SyntaxError::ReservedKeyword(region), state))
Fail {
reason: FailReason::ReservedKeyword(region),
attempting: Attempting::Identifier,
},
state,
))
} else { } else {
Ok((ident, state)) Ok((MadeProgress, ident, state))
} }
} }
} }
@ -398,11 +411,11 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> {
/// * A module name /// * A module name
/// * A type name /// * A type name
/// * A global tag /// * A global tag
pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str> { pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
global_tag_or_ident(|first_char| first_char.is_uppercase()) global_tag_or_ident(|first_char| first_char.is_uppercase())
} }
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str> { pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, SyntaxError<'a>> {
global_tag_or_ident(|first_char| first_char.is_alphabetic()) global_tag_or_ident(|first_char| first_char.is_alphabetic())
} }

View file

@ -4,3 +4,5 @@ pub static ELSE: &str = "else";
pub static WHEN: &str = "when"; pub static WHEN: &str = "when";
pub static AS: &str = "as"; pub static AS: &str = "as";
pub static IS: &str = "is"; pub static IS: &str = "is";
pub static KEYWORDS: [&str; 6] = [IF, THEN, ELSE, WHEN, AS, IS];

View file

@ -7,9 +7,10 @@ use crate::header::{
TypedIdent, TypedIdent,
}; };
use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident};
use crate::parser::Progress::{self, *};
use crate::parser::{ use crate::parser::{
self, ascii_char, ascii_string, loc, optional, peek_utf8_char, peek_utf8_char_at, unexpected, self, ascii_char, ascii_string, backtrackable, end_of_file, loc, optional, peek_utf8_char,
unexpected_eof, Either, ParseResult, Parser, State, peek_utf8_char_at, unexpected, unexpected_eof, Either, ParseResult, Parser, State, SyntaxError,
}; };
use crate::string_literal; use crate::string_literal;
use crate::type_annotation; use crate::type_annotation;
@ -17,29 +18,29 @@ use bumpalo::collections::{String, Vec};
use bumpalo::Bump; use bumpalo::Bump;
use roc_region::all::Located; use roc_region::all::Located;
pub fn header<'a>() -> impl Parser<'a, Module<'a>> { pub fn header<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> {
one_of!(interface_module(), app_module(), platform_module()) one_of!(interface_module(), app_module(), platform_module())
} }
#[inline(always)] #[inline(always)]
fn app_module<'a>() -> impl Parser<'a, Module<'a>> { fn app_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> {
map!(app_header(), |header| { Module::App { header } }) map!(app_header(), |header| { Module::App { header } })
} }
#[inline(always)] #[inline(always)]
fn platform_module<'a>() -> impl Parser<'a, Module<'a>> { fn platform_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> {
map!(platform_header(), |header| { Module::Platform { header } }) map!(platform_header(), |header| { Module::Platform { header } })
} }
#[inline(always)] #[inline(always)]
fn interface_module<'a>() -> impl Parser<'a, Module<'a>> { fn interface_module<'a>() -> impl Parser<'a, Module<'a>, SyntaxError<'a>> {
map!(interface_header(), |header| { map!(interface_header(), |header| {
Module::Interface { header } Module::Interface { header }
}) })
} }
#[inline(always)] #[inline(always)]
pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> { pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, SyntaxError<'a>> {
parser::map( parser::map(
and!( and!(
skip_first!( skip_first!(
@ -70,7 +71,7 @@ pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
} }
#[inline(always)] #[inline(always)]
pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>> { pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, SyntaxError<'a>> {
// e.g. rtfeldman/blah // e.g. rtfeldman/blah
// //
// Package names and accounts can be capitalized and can contain dashes. // Package names and accounts can be capitalized and can contain dashes.
@ -86,7 +87,10 @@ pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>> {
) )
} }
pub fn parse_package_part<'a>(arena: &'a Bump, mut state: State<'a>) -> ParseResult<'a, &'a str> { pub fn parse_package_part<'a>(
arena: &'a Bump,
mut state: State<'a>,
) -> ParseResult<'a, &'a str, SyntaxError<'a>> {
let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.) let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.)
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
@ -95,32 +99,36 @@ pub fn parse_package_part<'a>(arena: &'a Bump, mut state: State<'a>) -> ParseRes
if ch == '-' || ch.is_ascii_alphanumeric() { if ch == '-' || ch.is_ascii_alphanumeric() {
part_buf.push(ch); part_buf.push(ch);
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
} else { } else {
return Ok((part_buf.into_bump_str(), state)); let progress = Progress::progress_when(!part_buf.is_empty());
return Ok((progress, part_buf.into_bump_str(), state));
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => {
let progress = Progress::progress_when(!part_buf.is_empty());
return state.fail(arena, progress, reason);
}
} }
} }
Err(unexpected_eof(0, state.attempting, state)) Err(unexpected_eof(arena, state, 0))
} }
#[inline(always)] #[inline(always)]
pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> { pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, SyntaxError<'a>> {
move |arena, mut state: State<'a>| { move |arena, mut state: State<'a>| {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
Ok((first_letter, bytes_parsed)) => { Ok((first_letter, bytes_parsed)) => {
if !first_letter.is_uppercase() { if !first_letter.is_uppercase() {
return Err(unexpected(0, state, Attempting::Module)); return Err(unexpected(arena, 0, Attempting::Module, state));
}; };
let mut buf = String::with_capacity_in(4, arena); let mut buf = String::with_capacity_in(4, arena);
buf.push(first_letter); buf.push(first_letter);
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
match peek_utf8_char(&state) { match peek_utf8_char(&state) {
@ -131,7 +139,7 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric() // * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A '.' separating module parts // * A '.' separating module parts
if ch.is_alphabetic() || ch.is_ascii_digit() { if ch.is_alphabetic() || ch.is_ascii_digit() {
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting(arena, bytes_parsed)?;
buf.push(ch); buf.push(ch);
} else if ch == '.' { } else if ch == '.' {
@ -143,6 +151,7 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
buf.push(next); buf.push(next);
state = state.advance_without_indenting( state = state.advance_without_indenting(
arena,
bytes_parsed + next_bytes_parsed, bytes_parsed + next_bytes_parsed,
)?; )?;
} else { } else {
@ -151,31 +160,32 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
// There may be an identifier after this '.', // There may be an identifier after this '.',
// e.g. "baz" in `Foo.Bar.baz` // e.g. "baz" in `Foo.Bar.baz`
return Ok(( return Ok((
MadeProgress,
ModuleName::new(buf.into_bump_str()), ModuleName::new(buf.into_bump_str()),
state, state,
)); ));
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
} }
} else { } else {
// This is the end of the module name. We're done! // This is the end of the module name. We're done!
break; break;
} }
} }
Err(reason) => return state.fail(reason), Err(reason) => return state.fail(arena, MadeProgress, reason),
} }
} }
Ok((ModuleName::new(buf.into_bump_str()), state)) Ok((MadeProgress, ModuleName::new(buf.into_bump_str()), state))
} }
Err(reason) => state.fail(reason), Err(reason) => state.fail(arena, MadeProgress, reason),
} }
} }
} }
#[inline(always)] #[inline(always)]
pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> { pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, SyntaxError<'a>> {
map_with_arena!( map_with_arena!(
and!( and!(
skip_first!( skip_first!(
@ -233,7 +243,7 @@ pub fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
} }
#[inline(always)] #[inline(always)]
pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> { pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, SyntaxError<'a>> {
parser::map( parser::map(
and!( and!(
skip_first!( skip_first!(
@ -289,8 +299,9 @@ pub fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>> {
} }
#[inline(always)] #[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> { pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, SyntaxError<'a>> {
zero_or_more!(space0_around(loc(def(0)), 0)) // force that we pare until the end of the input
skip_second!(zero_or_more!(space0_around(loc(def(0)), 0)), end_of_file())
} }
struct ProvidesTo<'a> { struct ProvidesTo<'a> {
@ -304,10 +315,13 @@ struct ProvidesTo<'a> {
} }
#[inline(always)] #[inline(always)]
fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>> { fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, SyntaxError<'a>> {
map!( map!(
and!( and!(
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)), and!(
skip_second!(backtrackable(space1(1)), ascii_string("provides")),
space1(1)
),
and!( and!(
collection!( collection!(
ascii_char(b'['), ascii_char(b'['),
@ -361,6 +375,7 @@ fn provides_without_to<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, &'a str>>>, Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
), ),
SyntaxError<'a>,
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("provides")), space1(1)), and!(skip_second!(space1(1), ascii_string("provides")), space1(1)),
@ -381,6 +396,7 @@ fn requires<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<TypedIdent<'a>>>, Vec<'a, Located<TypedIdent<'a>>>,
), ),
SyntaxError<'a>,
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("requires")), space1(1)), and!(skip_second!(space1(1), ascii_string("requires")), space1(1)),
@ -401,6 +417,7 @@ fn exposes_values<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, &'a str>>>, Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
), ),
SyntaxError<'a>,
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)), and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
@ -421,6 +438,7 @@ fn exposes_modules<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>, Vec<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
), ),
SyntaxError<'a>,
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)), and!(skip_second!(space1(1), ascii_string("exposes")), space1(1)),
@ -434,6 +452,7 @@ fn exposes_modules<'a>() -> impl Parser<
) )
} }
#[derive(Debug)]
struct Packages<'a> { struct Packages<'a> {
entries: Vec<'a, Located<PackageEntry<'a>>>, entries: Vec<'a, Located<PackageEntry<'a>>>,
@ -442,10 +461,13 @@ struct Packages<'a> {
} }
#[inline(always)] #[inline(always)]
fn packages<'a>() -> impl Parser<'a, Packages<'a>> { fn packages<'a>() -> impl Parser<'a, Packages<'a>, SyntaxError<'a>> {
map!( map!(
and!( and!(
and!(skip_second!(space1(1), ascii_string("packages")), space1(1)), and!(
skip_second!(backtrackable(space1(1)), ascii_string("packages")),
space1(1)
),
collection!( collection!(
ascii_char(b'{'), ascii_char(b'{'),
loc!(package_entry()), loc!(package_entry()),
@ -471,9 +493,13 @@ fn imports<'a>() -> impl Parser<
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ImportsEntry<'a>>>, Vec<'a, Located<ImportsEntry<'a>>>,
), ),
SyntaxError<'a>,
> { > {
and!( and!(
and!(skip_second!(space1(1), ascii_string("imports")), space1(1)), and!(
skip_second!(backtrackable(space1(1)), ascii_string("imports")),
space1(1)
),
collection!( collection!(
ascii_char(b'['), ascii_char(b'['),
loc!(imports_entry()), loc!(imports_entry()),
@ -485,19 +511,19 @@ fn imports<'a>() -> impl Parser<
} }
#[inline(always)] #[inline(always)]
fn effects<'a>() -> impl Parser<'a, Effects<'a>> { fn effects<'a>() -> impl Parser<'a, Effects<'a>, SyntaxError<'a>> {
move |arena, state| { move |arena, state| {
let (spaces_before_effects_keyword, state) = let (_, spaces_before_effects_keyword, state) =
skip_second!(space1(0), ascii_string("effects")).parse(arena, state)?; skip_second!(space1(0), ascii_string("effects")).parse(arena, state)?;
let (spaces_after_effects_keyword, state) = space1(0).parse(arena, state)?; let (_, spaces_after_effects_keyword, state) = space1(0).parse(arena, state)?;
// e.g. `fx.` // e.g. `fx.`
let (type_shortname, state) = let (_, type_shortname, state) =
skip_second!(lowercase_ident(), ascii_char(b'.')).parse(arena, state)?; skip_second!(lowercase_ident(), ascii_char(b'.')).parse(arena, state)?;
let ((type_name, spaces_after_type_name), state) = let (_, (type_name, spaces_after_type_name), state) =
and!(uppercase_ident(), space1(0)).parse(arena, state)?; and!(uppercase_ident(), space1(0)).parse(arena, state)?;
let (entries, state) = collection!( let (_, entries, state) = collection!(
ascii_char(b'{'), ascii_char(b'{'),
loc!(typed_ident()), loc!(typed_ident()),
ascii_char(b','), ascii_char(b','),
@ -507,6 +533,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>> {
.parse(arena, state)?; .parse(arena, state)?;
Ok(( Ok((
MadeProgress,
Effects { Effects {
spaces_before_effects_keyword, spaces_before_effects_keyword,
spaces_after_effects_keyword, spaces_after_effects_keyword,
@ -521,14 +548,14 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>> {
} }
#[inline(always)] #[inline(always)]
fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> { fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>, SyntaxError<'a>> {
move |arena, state| { move |arena, state| {
// You must have a field name, e.g. "email" // You must have a field name, e.g. "email"
let (ident, state) = loc!(lowercase_ident()).parse(arena, state)?; let (_, ident, state) = loc!(lowercase_ident()).parse(arena, state)?;
let (spaces_before_colon, state) = space0(0).parse(arena, state)?; let (_, spaces_before_colon, state) = space0(0).parse(arena, state)?;
let (ann, state) = skip_first!( let (_, ann, state) = skip_first!(
ascii_char(b':'), ascii_char(b':'),
space0_before(type_annotation::located(0), 0) space0_before(type_annotation::located(0), 0)
) )
@ -539,6 +566,7 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> {
// printLine : Str -> Effect {} // printLine : Str -> Effect {}
Ok(( Ok((
MadeProgress,
TypedIdent::Entry { TypedIdent::Entry {
ident, ident,
spaces_before_colon, spaces_before_colon,
@ -551,7 +579,7 @@ fn typed_ident<'a>() -> impl Parser<'a, TypedIdent<'a>> {
#[inline(always)] #[inline(always)]
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> { fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, SyntaxError<'a>> {
map_with_arena!( map_with_arena!(
and!( and!(
and!( and!(

View file

@ -1,22 +1,25 @@
use crate::ast::{Attempting, Base, Expr}; use crate::ast::{Attempting, Base, Expr};
use crate::parser::{parse_utf8, unexpected, unexpected_eof, ParseResult, Parser, State}; use crate::parser::{
parse_utf8, unexpected, unexpected_eof, ParseResult, Parser, Progress, State, SyntaxError,
};
use bumpalo::Bump;
use std::char; use std::char;
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>> { pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>, SyntaxError<'a>> {
move |_arena, state: State<'a>| { move |arena, state: State<'a>| {
let bytes = &mut state.bytes.iter(); let bytes = &mut state.bytes.iter();
match bytes.next() { match bytes.next() {
Some(&first_byte) => { Some(&first_byte) => {
// Number literals must start with either an '-' or a digit. // Number literals must start with either an '-' or a digit.
if first_byte == b'-' || (first_byte as char).is_ascii_digit() { if first_byte == b'-' || (first_byte as char).is_ascii_digit() {
parse_number_literal(first_byte as char, bytes, state) parse_number_literal(first_byte as char, bytes, arena, state)
} else { } else {
Err(unexpected(1, state, Attempting::NumberLiteral)) Err(unexpected(arena, 1, Attempting::NumberLiteral, state))
} }
} }
None => Err(unexpected_eof(0, state.attempting, state)), None => Err(unexpected_eof(arena, state, 0)),
} }
} }
} }
@ -25,8 +28,9 @@ pub fn number_literal<'a>() -> impl Parser<'a, Expr<'a>> {
fn parse_number_literal<'a, I>( fn parse_number_literal<'a, I>(
first_ch: char, first_ch: char,
bytes: &mut I, bytes: &mut I,
arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Expr<'a>> ) -> ParseResult<'a, Expr<'a>, SyntaxError<'a>>
where where
I: Iterator<Item = &'a u8>, I: Iterator<Item = &'a u8>,
{ {
@ -42,9 +46,10 @@ where
for &next_byte in bytes { for &next_byte in bytes {
let err_unexpected = || { let err_unexpected = || {
Err(unexpected( Err(unexpected(
arena,
bytes_parsed, bytes_parsed,
state.clone(),
Attempting::NumberLiteral, Attempting::NumberLiteral,
state.clone(),
)) ))
}; };
@ -126,21 +131,23 @@ where
// we'll succeed with an appropriate Expr which records that. // we'll succeed with an appropriate Expr which records that.
match typ { match typ {
Num => Ok(( Num => Ok((
Progress::from_consumed(bytes_parsed),
// SAFETY: it's safe to use from_utf8_unchecked here, because we've // SAFETY: it's safe to use from_utf8_unchecked here, because we've
// already validated that this range contains only ASCII digits // already validated that this range contains only ASCII digits
Expr::Num(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }), Expr::Num(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }),
state.advance_without_indenting(bytes_parsed)?, state.advance_without_indenting(arena, bytes_parsed)?,
)), )),
Float => Ok(( Float => Ok((
Progress::from_consumed(bytes_parsed),
// SAFETY: it's safe to use from_utf8_unchecked here, because we've // SAFETY: it's safe to use from_utf8_unchecked here, because we've
// already validated that this range contains only ASCII digits // already validated that this range contains only ASCII digits
Expr::Float(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }), Expr::Float(unsafe { from_utf8_unchecked(&state.bytes[0..bytes_parsed]) }),
state.advance_without_indenting(bytes_parsed)?, state.advance_without_indenting(arena, bytes_parsed)?,
)), )),
// For these we trim off the 0x/0o/0b part // For these we trim off the 0x/0o/0b part
Hex => from_base(Base::Hex, first_ch, bytes_parsed, state), Hex => from_base(Base::Hex, first_ch, bytes_parsed, arena, state),
Octal => from_base(Base::Octal, first_ch, bytes_parsed, state), Octal => from_base(Base::Octal, first_ch, bytes_parsed, arena, state),
Binary => from_base(Base::Binary, first_ch, bytes_parsed, state), Binary => from_base(Base::Binary, first_ch, bytes_parsed, arena, state),
} }
} }
@ -153,12 +160,13 @@ enum LiteralType {
Binary, Binary,
} }
fn from_base( fn from_base<'a>(
base: Base, base: Base,
first_ch: char, first_ch: char,
bytes_parsed: usize, bytes_parsed: usize,
state: State<'_>, arena: &'a Bump,
) -> ParseResult<'_, Expr<'_>> { state: State<'a>,
) -> ParseResult<'a, Expr<'a>, SyntaxError<'a>> {
let is_negative = first_ch == '-'; let is_negative = first_ch == '-';
let bytes = if is_negative { let bytes = if is_negative {
&state.bytes[3..bytes_parsed] &state.bytes[3..bytes_parsed]
@ -168,13 +176,14 @@ fn from_base(
match parse_utf8(bytes) { match parse_utf8(bytes) {
Ok(string) => Ok(( Ok(string) => Ok((
Progress::from_consumed(bytes_parsed),
Expr::NonBase10Int { Expr::NonBase10Int {
is_negative, is_negative,
string, string,
base, base,
}, },
state.advance_without_indenting(bytes_parsed)?, state.advance_without_indenting(arena, bytes_parsed)?,
)), )),
Err(reason) => state.fail(reason), Err(reason) => state.fail(arena, Progress::from_consumed(bytes_parsed), reason),
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,17 @@
use crate::ast::Pattern;
use crate::blankspace::{space0_around_e, space0_before_e, space0_e};
use crate::ident::{ident, lowercase_ident, Ident};
use crate::number_literal::number_literal;
use crate::parser::Progress::{self, *};
use crate::parser::{
backtrackable, optional, specialize, specialize_ref, word1, BadInputError, EPattern, PInParens,
PRecord, ParseResult, Parser, State, SyntaxError,
};
use bumpalo::collections::string::String;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_region::all::{Located, Region};
/// Different patterns are supported in different circumstances. /// Different patterns are supported in different circumstances.
/// For example, when branches can pattern match on number literals, but /// For example, when branches can pattern match on number literals, but
/// assignments and function args can't. Underscore is supported in function /// assignments and function args can't. Underscore is supported in function
@ -9,3 +23,421 @@ pub enum PatternType {
FunctionArg, FunctionArg,
WhenBranch, WhenBranch,
} }
pub fn loc_closure_param<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
specialize(
|e, _, _| SyntaxError::Pattern(e),
move |arena, state| parse_closure_param(arena, state, min_indent),
)
}
fn parse_closure_param<'a>(
arena: &'a Bump,
state: State<'a>,
min_indent: u16,
) -> ParseResult<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
// An ident is the most common param, e.g. \foo -> ...
loc_ident_pattern_help(min_indent, true),
// Underscore is also common, e.g. \_ -> ...
loc!(underscore_pattern_help()),
// You can destructure records in params, e.g. \{ x, y } -> ...
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
// If you wrap it in parens, you can match any arbitrary pattern at all.
// e.g. \User.UserId userId -> ...
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent))
)
.parse(arena, state)
}
pub fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, SyntaxError<'a>> {
specialize(
|e, _, _| SyntaxError::Pattern(e),
loc_pattern_help(min_indent),
)
}
pub fn loc_pattern_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()),
loc_ident_pattern_help(min_indent, true),
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
loc!(string_pattern_help()),
loc!(number_pattern_help())
)
}
fn loc_tag_pattern_args_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>, EPattern<'a>> {
zero_or_more!(loc_tag_pattern_arg(min_indent))
}
fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
// Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args!
move |arena, state| {
let (_, spaces, state) =
backtrackable(space0_e(min_indent, EPattern::Space, EPattern::IndentStart))
.parse(arena, state)?;
let (_, loc_pat, state) = loc_parse_tag_pattern_arg(min_indent, arena, state)?;
let Located { region, value } = loc_pat;
Ok((
MadeProgress,
if spaces.is_empty() {
Located::at(region, value)
} else {
Located::at(region, Pattern::SpaceBefore(arena.alloc(value), spaces))
},
state,
))
}
}
fn loc_parse_tag_pattern_arg<'a>(
min_indent: u16,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Pattern<'a>>, EPattern<'a>> {
one_of!(
specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)),
loc!(underscore_pattern_help()),
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
loc_ident_pattern_help(min_indent, false),
loc!(specialize(
EPattern::Record,
crate::pattern::record_pattern_help(min_indent)
)),
loc!(string_pattern_help()),
loc!(number_pattern_help())
)
.parse(arena, state)
}
fn loc_pattern_in_parens_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, PInParens<'a>> {
between!(
word1(b'(', PInParens::Open),
space0_around_e(
move |arena, state| specialize_ref(PInParens::Syntax, loc_pattern(min_indent))
.parse(arena, state),
min_indent,
PInParens::Space,
PInParens::IndentEnd,
),
word1(b')', PInParens::End)
)
}
fn number_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
specialize(
|_, r, c| EPattern::Start(r, c),
map_with_arena!(number_literal(), |arena, expr| {
crate::expr::expr_to_pattern(arena, &expr).unwrap()
}),
)
}
fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
specialize(
|_, r, c| EPattern::Start(r, c),
map!(crate::string_literal::parse(), Pattern::StrLiteral),
)
}
fn loc_ident_pattern_help<'a>(
min_indent: u16,
can_have_arguments: bool,
) -> impl Parser<'a, Located<Pattern<'a>>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let original_state = state.clone();
let (_, loc_ident, state) =
specialize(|_, r, c| EPattern::Start(r, c), loc!(ident())).parse(arena, state)?;
match loc_ident.value {
Ident::GlobalTag(tag) => {
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::GlobalTag(tag),
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args_help(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::PrivateTag(tag) => {
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::PrivateTag(tag),
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args_help(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((MadeProgress, Located { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
}
}
Ident::Access { module_name, parts } => {
// Plain identifiers (e.g. `foo`) are allowed in patterns, but
// more complex ones (e.g. `Foo.bar` or `foo.bar.baz`) are not.
if crate::keyword::KEYWORDS.contains(&parts[0]) {
Err((
NoProgress,
EPattern::End(original_state.line, original_state.column),
original_state,
))
} else if module_name.is_empty() && parts.len() == 1 {
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Identifier(parts[0]),
},
state,
))
} else {
let malformed_str = if module_name.is_empty() {
parts.join(".")
} else {
format!("{}.{}", module_name, parts.join("."))
};
Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Malformed(
String::from_str_in(&malformed_str, &arena).into_bump_str(),
),
},
state,
))
}
}
Ident::AccessorFunction(string) => Ok((
MadeProgress,
Located {
region: loc_ident.region,
value: Pattern::Malformed(string),
},
state,
)),
Ident::Malformed(malformed) => {
debug_assert!(!malformed.is_empty());
Err((
MadeProgress,
EPattern::Start(state.line, state.column),
state,
))
}
}
}
}
pub fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
specialize(|e, _, _| SyntaxError::Pattern(e), underscore_pattern_help())
}
fn underscore_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let (_, _, next_state) = word1(b'_', EPattern::Underscore).parse(arena, state)?;
let (_, output, final_state) =
optional(lowercase_ident_pattern).parse(arena, next_state)?;
match output {
Some(name) => Ok((MadeProgress, Pattern::Underscore(name), final_state)),
None => Ok((MadeProgress, Pattern::Underscore(&""), final_state)),
}
}
}
fn lowercase_ident_pattern<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, &'a str, EPattern<'a>> {
let row = state.line;
let col = state.column;
specialize(move |_, _, _| EPattern::End(row, col), lowercase_ident()).parse(arena, state)
}
pub fn record_pattern<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, SyntaxError<'a>> {
specialize(
|e, r, c| SyntaxError::Pattern(EPattern::Record(e, r, c)),
record_pattern_help(min_indent),
)
}
#[inline(always)]
fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> {
move |arena, state| {
let (_, (fields, final_comments), state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', PRecord::Open, min_indent, PRecord::IndentOpen),
word1(b'{', PRecord::Open),
record_pattern_field(min_indent),
word1(b',', PRecord::End),
// word1_check_indent!(b'}', PRecord::End, min_indent, PRecord::IndentEnd),
word1(b'}', PRecord::End),
min_indent,
PRecord::Open,
PRecord::Space,
PRecord::IndentEnd
)
.parse(arena, state)?;
// TODO
let _unused = final_comments;
let result = Pattern::RecordDestructure(fields.into_bump_slice());
Ok((MadeProgress, result, state))
}
}
fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>, PRecord<'a>> {
use crate::parser::Either::*;
move |arena, state: State<'a>| {
// You must have a field name, e.g. "email"
// using the initial row/col is important for error reporting
let row = state.line;
let col = state.column;
let (progress, loc_label, state) = loc!(specialize(
move |_, _, _| PRecord::Field(row, col),
lowercase_ident()
))
.parse(arena, state)?;
debug_assert_eq!(progress, MadeProgress);
let (_, spaces, state) =
space0_e(min_indent, PRecord::Space, PRecord::IndentEnd).parse(arena, state)?;
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.)
let (_, opt_loc_val, state) = optional(either!(
word1(b':', PRecord::Colon),
word1(b'?', PRecord::Optional)
))
.parse(arena, state)?;
match opt_loc_val {
Some(First(_)) => {
let val_parser = specialize_ref(PRecord::Syntax, loc_pattern(min_indent));
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
let Located {
value: label,
region,
} = loc_label;
let region = Region::span_across(&region, &loc_val.region);
Ok((
MadeProgress,
Located::at(
region,
Pattern::RequiredField(
label,
// TODO spaces are dropped here
// arena.alloc(arena.alloc(value).with_spaces_before(spaces, region)),
arena.alloc(loc_val),
),
),
state,
))
}
Some(Second(_)) => {
let val_parser =
specialize_ref(PRecord::Syntax, loc!(crate::expr::expr(min_indent)));
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, PRecord::Space, PRecord::IndentColon)
.parse(arena, state)?;
let Located {
value: label,
region,
} = loc_label;
let region = Region::span_across(&region, &loc_val.region);
Ok((
MadeProgress,
Located::at(
region,
Pattern::OptionalField(
label,
// TODO spaces are dropped
// arena.alloc(arena.alloc(value).with_spaces_before(spaces, region)),
arena.alloc(loc_val),
),
),
state,
))
}
// If no value was provided, record it as a Var.
// Canonicalize will know what to do with a Var later.
None => {
let Located { value, region } = loc_label;
let value = if !spaces.is_empty() {
Pattern::SpaceAfter(arena.alloc(Pattern::Identifier(value)), spaces)
} else {
Pattern::Identifier(value)
};
Ok((MadeProgress, Located::at(region, value), state))
}
}
}
}

View file

@ -1,13 +1,14 @@
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment}; use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
use crate::expr; use crate::expr;
use crate::parser::Progress::*;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Fail, allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof,
FailReason, ParseResult, Parser, State, ParseResult, Parser, State, SyntaxError,
}; };
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> { pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, SyntaxError<'a>> {
use StrLiteral::*; use StrLiteral::*;
move |arena: &'a Bump, mut state: State<'a>| { move |arena: &'a Bump, mut state: State<'a>| {
@ -17,16 +18,16 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match bytes.next() { match bytes.next() {
Some(&byte) => { Some(&byte) => {
if byte != b'"' { if byte != b'"' {
return Err(unexpected(0, state, Attempting::StrLiteral)); return Err(unexpected(arena, 0, Attempting::StrLiteral, state));
} }
} }
None => { None => {
return Err(unexpected_eof(0, Attempting::StrLiteral, state)); return Err(unexpected_eof(arena, state, 0));
} }
} }
// Advance past the opening quotation mark. // Advance past the opening quotation mark.
state = state.advance_without_indenting(1)?; state = state.advance_without_indenting(arena, 1)?;
// At the parsing stage we keep the entire raw string, because the formatter // At the parsing stage we keep the entire raw string, because the formatter
// needs the raw string. (For example, so it can "remember" whether you // needs the raw string. (For example, so it can "remember" whether you
@ -43,7 +44,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
segments.push(StrSegment::EscapedChar($ch)); segments.push(StrSegment::EscapedChar($ch));
// Advance past the segment we just added // Advance past the segment we just added
state = state.advance_without_indenting(segment_parsed_bytes)?; state = state.advance_without_indenting(arena, segment_parsed_bytes)?;
// Reset the segment // Reset the segment
segment_parsed_bytes = 0; segment_parsed_bytes = 0;
@ -62,12 +63,12 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match parse_utf8(string_bytes) { match parse_utf8(string_bytes) {
Ok(string) => { Ok(string) => {
state = state.advance_without_indenting(string.len())?; state = state.advance_without_indenting(arena, string.len())?;
segments.push($transform(string)); segments.push($transform(string));
} }
Err(reason) => { Err(reason) => {
return state.fail(reason); return state.fail(arena, MadeProgress, reason);
} }
} }
} }
@ -101,7 +102,11 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
} }
_ => { _ => {
// Advance 1 for the close quote // Advance 1 for the close quote
return Ok((PlainLine(""), state.advance_without_indenting(1)?)); return Ok((
MadeProgress,
PlainLine(""),
state.advance_without_indenting(arena, 1)?,
));
} }
} }
} else { } else {
@ -123,7 +128,11 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
}; };
// Advance the state 1 to account for the closing `"` // Advance the state 1 to account for the closing `"`
return Ok((expr, state.advance_without_indenting(1)?)); return Ok((
MadeProgress,
expr,
state.advance_without_indenting(arena, 1)?,
));
}; };
} }
b'\n' => { b'\n' => {
@ -133,9 +142,10 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// it should make it easiest to debug; the file will be a giant // it should make it easiest to debug; the file will be a giant
// error starting from where the open quote appeared. // error starting from where the open quote appeared.
return Err(unexpected( return Err(unexpected(
arena,
state.bytes.len() - 1, state.bytes.len() - 1,
state,
Attempting::StrLiteral, Attempting::StrLiteral,
state,
)); ));
} }
b'\\' => { b'\\' => {
@ -153,7 +163,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
match bytes.next() { match bytes.next() {
Some(b'(') => { Some(b'(') => {
// Advance past the `\(` before using the expr parser // Advance past the `\(` before using the expr parser
state = state.advance_without_indenting(2)?; state = state.advance_without_indenting(arena, 2)?;
let original_byte_count = state.bytes.len(); let original_byte_count = state.bytes.len();
@ -161,7 +171,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// Parse an arbitrary expression, then give a // Parse an arbitrary expression, then give a
// canonicalization error if that expression variant // canonicalization error if that expression variant
// is not allowed inside a string interpolation. // is not allowed inside a string interpolation.
let (loc_expr, new_state) = let (_progress, loc_expr, new_state) =
skip_second!(loc(allocated(expr::expr(0))), ascii_char(b')')) skip_second!(loc(allocated(expr::expr(0))), ascii_char(b')'))
.parse(arena, state)?; .parse(arena, state)?;
@ -178,14 +188,14 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
} }
Some(b'u') => { Some(b'u') => {
// Advance past the `\u` before using the expr parser // Advance past the `\u` before using the expr parser
state = state.advance_without_indenting(2)?; state = state.advance_without_indenting(arena, 2)?;
let original_byte_count = state.bytes.len(); let original_byte_count = state.bytes.len();
// Parse the hex digits, surrounded by parens, then // Parse the hex digits, surrounded by parens, then
// give a canonicalization error if the digits form // give a canonicalization error if the digits form
// an invalid unicode code point. // an invalid unicode code point.
let (loc_digits, new_state) = between!( let (_progress, loc_digits, new_state) = between!(
ascii_char(b'('), ascii_char(b'('),
loc(ascii_hex_digits()), loc(ascii_hex_digits()),
ascii_char(b')') ascii_char(b')')
@ -223,9 +233,10 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
// by either an open paren or else one of the // by either an open paren or else one of the
// escapable characters (\n, \t, \", \\, etc) // escapable characters (\n, \t, \", \\, etc)
return Err(unexpected( return Err(unexpected(
arena,
state.bytes.len() - 1, state.bytes.len() - 1,
state,
Attempting::StrLiteral, Attempting::StrLiteral,
state,
)); ));
} }
} }
@ -237,11 +248,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>> {
} }
// We ran out of characters before finding a closed quote // We ran out of characters before finding a closed quote
Err(unexpected_eof( Err(unexpected_eof(arena, state.clone(), state.bytes.len()))
state.bytes.len(),
Attempting::StrLiteral,
state.clone(),
))
} }
} }
@ -249,7 +256,7 @@ fn parse_block_string<'a, I>(
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
bytes: &mut I, bytes: &mut I,
) -> ParseResult<'a, StrLiteral<'a>> ) -> ParseResult<'a, StrLiteral<'a>, SyntaxError<'a>>
where where
I: Iterator<Item = &'a u8>, I: Iterator<Item = &'a u8>,
{ {
@ -283,17 +290,15 @@ where
// Ok((StrLiteral::Block(lines.into_bump_slice()), state)) // Ok((StrLiteral::Block(lines.into_bump_slice()), state))
Err(( Err((
Fail { MadeProgress,
attempting: state.attempting, SyntaxError::NotYetImplemented(format!(
reason: FailReason::NotYetImplemented(format!( "TODO parse this line in a block string: {:?}",
"TODO parse this line in a block string: {:?}", line
line )),
)),
},
state, state,
)) ))
} }
Err(reason) => state.fail(reason), Err(reason) => state.fail(arena, MadeProgress, reason),
}; };
} }
quotes_seen += 1; quotes_seen += 1;
@ -310,7 +315,7 @@ where
line_start = parsed_chars; line_start = parsed_chars;
} }
Err(reason) => { Err(reason) => {
return state.fail(reason); return state.fail(arena, MadeProgress, reason);
} }
} }
} }
@ -323,10 +328,5 @@ where
} }
// We ran out of characters before finding 3 closing quotes // We ran out of characters before finding 3 closing quotes
Err(unexpected_eof( Err(unexpected_eof(arena, state, parsed_chars))
parsed_chars,
// TODO custom BlockStrLiteral?
Attempting::StrLiteral,
state,
))
} }

View file

@ -2,44 +2,53 @@ use crate::ast::{self, Attempting};
use crate::blankspace::space0_before; use crate::blankspace::space0_before;
use crate::expr::expr; use crate::expr::expr;
use crate::module::{header, module_defs}; use crate::module::{header, module_defs};
use crate::parser::{loc, Fail, Parser, State}; use crate::parser::{loc, Parser, State, SyntaxError};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_region::all::Located; use roc_region::all::Located;
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_expr_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_expr_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<ast::Expr<'a>, SyntaxError<'a>> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
} }
pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Module<'a>, Fail> { pub fn parse_header_with<'a>(
let state = State::new(input.trim().as_bytes(), Attempting::Module); arena: &'a Bump,
input: &'a str,
) -> Result<ast::Module<'a>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let answer = header().parse(arena, state); let answer = header().parse(arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_defs_with<'a>( pub fn parse_defs_with<'a>(
arena: &'a Bump, arena: &'a Bump,
input: &'a str, input: &'a str,
) -> Result<Vec<'a, Located<ast::Def<'a>>>, Fail> { ) -> Result<Vec<'a, Located<ast::Def<'a>>>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes(), Attempting::Module); let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let answer = module_defs().parse(arena, state); let answer = module_defs().parse(arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(
let state = State::new(input.trim().as_bytes(), Attempting::Module); arena: &'a Bump,
input: &'a str,
) -> Result<Located<ast::Expr<'a>>, SyntaxError<'a>> {
let state = State::new_in(arena, input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(expr(0)), 0); let parser = space0_before(loc(expr(0)), 0);
let answer = parser.parse(&arena, state); let answer = parser.parse(&arena, state);
answer answer
.map(|(loc_expr, _)| loc_expr) .map(|(_, loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail) .map_err(|(_, fail, _)| fail)
} }

View file

@ -1,77 +1,89 @@
use crate::ast::{AssignedField, Attempting, CommentOrNewline, Tag, TypeAnnotation}; use crate::ast::{AssignedField, Tag, TypeAnnotation};
use crate::blankspace::{space0_around, space0_before, space1, space1_before}; use crate::blankspace::{space0_around_e, space0_before_e, space0_e};
use crate::expr::{global_tag, private_tag};
use crate::ident::join_module_parts; use crate::ident::join_module_parts;
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail, allocated, backtrackable, not_e, optional, peek_utf8_char_e, specialize, specialize_ref, word1,
FailReason, ParseResult, Parser, State, word2, BadInputError, ParseResult, Parser,
Progress::{self, *},
State, SyntaxError, TApply, TInParens, TRecord, TTagUnion, TVariable, Type,
}; };
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::arena_join;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
pub fn located<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> { pub fn located<'a>(
expression(min_indent) min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, SyntaxError<'a>> {
specialize(|x, _, _| SyntaxError::Type(x), expression(min_indent))
} }
macro_rules! tag_union { #[inline(always)]
($min_indent:expr) => { fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TTagUnion<'a>> {
map!( move |arena, state| {
and!( let (_, (tags, final_comments), state) = collection_trailing_sep_e!(
collection_trailing_sep!( word1(b'[', TTagUnion::Open),
ascii_char(b'['), loc!(tag_type(min_indent)),
loc!(tag_type($min_indent)), word1(b',', TTagUnion::End),
ascii_char(b','), word1(b']', TTagUnion::End),
ascii_char(b']'), min_indent,
$min_indent TTagUnion::Open,
), TTagUnion::Space,
optional( TTagUnion::IndentEnd
// This could be an open tag union, e.g. `[ Foo, Bar ]a`
move |arena, state| allocated(term($min_indent)).parse(arena, state)
)
),
|((tags, final_comments), ext): (
(Vec<'a, Located<Tag<'a>>>, &'a [CommentOrNewline<'a>]),
Option<&'a Located<TypeAnnotation<'a>>>,
)| TypeAnnotation::TagUnion {
tags: tags.into_bump_slice(),
ext,
final_comments
}
) )
}; .parse(arena, state)?;
// This could be an open tag union, e.g. `[ Foo, Bar ]a`
let (_, ext, state) =
optional(allocated(specialize_ref(TTagUnion::Type, term(min_indent))))
.parse(arena, state)?;
let result = TypeAnnotation::TagUnion {
tags: tags.into_bump_slice(),
ext,
final_comments,
};
Ok((MadeProgress, result, state))
}
} }
#[allow(clippy::type_complexity)] fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
map_with_arena!( map_with_arena!(
and!( and!(
one_of!( one_of!(
loc_wildcard(), loc_wildcard(),
loc_parenthetical_type(min_indent), specialize(Type::TInParens, loc_type_in_parens(min_indent)),
loc!(record_type(min_indent)), loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(tag_union!(min_indent)), loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc!(applied_type(min_indent)), loc!(applied_type(min_indent)),
loc!(parse_type_variable) loc!(specialize(Type::TVariable, parse_type_variable))
), ),
optional( // Inline alias notation, e.g. [ Nil, Cons a (List a) ] as List a
// Inline type annotation, e.g. [ Nil, Cons a (List a) ] as List a one_of![
and!( map!(
space1(min_indent), and!(
skip_first!( skip_second!(
ascii_string(keyword::AS), backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentEnd)),
space1_before(term(min_indent), min_indent) crate::parser::keyword_e(keyword::AS, Type::TEnd)
) ),
) space0_before_e(
) term(min_indent),
min_indent,
Type::TSpace,
Type::TAsIndentStart
)
),
Some
),
|_, state| Ok((NoProgress, None, state))
]
), ),
|arena: &'a Bump, |arena: &'a Bump,
(loc_ann, opt_as): ( (loc_ann, opt_as): (
Located<TypeAnnotation<'a>>, Located<TypeAnnotation<'a>>,
Option<(&'a [CommentOrNewline<'a>], Located<TypeAnnotation<'a>>)> Option<(&'a [_], Located<TypeAnnotation<'a>>)>
)| { )| {
match opt_as { match opt_as {
Some((spaces, loc_as)) => { Some((spaces, loc_as)) => {
@ -89,106 +101,304 @@ pub fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>>
} }
/// The `*` type variable, e.g. in (List *) Wildcard, /// The `*` type variable, e.g. in (List *) Wildcard,
fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>> { fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
map!(loc!(ascii_char(b'*')), |loc_val: Located<()>| { map!(loc!(word1(b'*', Type::TWildcard)), |loc_val: Located<()>| {
loc_val.map(|_| TypeAnnotation::Wildcard) loc_val.map(|_| TypeAnnotation::Wildcard)
}) })
} }
pub fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> { fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
skip_first!( use crate::ast::Spaceable;
// Once we hit an "as", stop parsing args
not(ascii_string(keyword::AS)),
one_of!(
loc_wildcard(),
loc_parenthetical_type(min_indent),
loc!(record_type(min_indent)),
loc!(tag_union!(min_indent)),
loc!(parse_concrete_type),
loc!(parse_type_variable)
)
)
}
#[inline(always)] map_with_arena!(
fn loc_parenthetical_type<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> {
between!(
ascii_char(b'('),
space0_around(
move |arena, state| expression(min_indent).parse(arena, state),
min_indent,
),
ascii_char(b')')
)
}
#[inline(always)]
#[allow(clippy::type_complexity)]
fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>> {
map!(
and!( and!(
either!(loc!(private_tag()), loc!(global_tag())), backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentStart)),
// Optionally parse space-separated arguments for the constructor, skip_first!(
// e.g. `ok err` in `Result ok err` // Once we hit an "as", stop parsing args
zero_or_more!(space1_before( // and roll back parsing of preceding spaces
move |arena, state| loc_applied_arg(min_indent).parse(arena, state), not_e(
min_indent, crate::parser::keyword(keyword::AS, min_indent),
)) Type::TStart
), ),
|(either_name, args): ( one_of!(
Either<Located<&'a str>, Located<&'a str>>, loc_wildcard(),
Vec<'a, Located<TypeAnnotation<'a>>> specialize(Type::TInParens, loc_type_in_parens(min_indent)),
)| match either_name { loc!(specialize(Type::TRecord, record_type(min_indent))),
Either::First(name) => Tag::Private { loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
name, loc!(specialize(Type::TApply, parse_concrete_type)),
args: args.into_bump_slice() loc!(specialize(Type::TVariable, parse_type_variable))
}, )
Either::Second(name) => Tag::Global {
name,
args: args.into_bump_slice()
},
}
)
}
#[inline(always)]
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
use crate::type_annotation::TypeAnnotation::*;
type Fields<'a> = Vec<'a, Located<AssignedField<'a, TypeAnnotation<'a>>>>;
map!(
and!(
record_without_update!(
move |arena, state| term(min_indent).parse(arena, state),
min_indent
),
optional(
// This could be an open record, e.g. `{ name: Str }r`
move |arena, state| allocated(term(min_indent)).parse(arena, state)
) )
), ),
|((fields, final_comments), ext): ( |arena: &'a Bump, (spaces, argument): (&'a [_], Located<TypeAnnotation<'a>>)| {
(Fields<'a>, &'a [CommentOrNewline<'a>]), if spaces.is_empty() {
Option<&'a Located<TypeAnnotation<'a>>>, argument
)| { } else {
Record { let Located { region, value } = argument;
fields: fields.into_bump_slice(), arena.alloc(value).with_spaces_before(spaces, region)
ext,
final_comments,
} }
} }
) )
} }
fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> { fn loc_type_in_parens<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, TInParens<'a>> {
between!(
word1(b'(', TInParens::Open),
space0_around_e(
move |arena, state| specialize_ref(TInParens::Type, expression(min_indent))
.parse(arena, state),
min_indent,
TInParens::Space,
TInParens::IndentEnd,
),
word1(b')', TInParens::End)
)
}
#[inline(always)]
fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>, TTagUnion<'a>> {
move |arena, state: State<'a>| {
let (_, name, state) = loc!(parse_tag_name(TTagUnion::End)).parse(arena, state)?;
let (_, args, state) =
specialize_ref(TTagUnion::Type, loc_applied_args_e(min_indent)).parse(arena, state)?;
let result = if name.value.starts_with('@') {
Tag::Private {
name,
args: args.into_bump_slice(),
}
} else {
Tag::Global {
name,
args: args.into_bump_slice(),
}
};
Ok((MadeProgress, result, state))
}
}
use crate::parser::{Col, Row};
fn parse_tag_name<'a, F, E>(to_problem: F) -> impl Parser<'a, &'a str, E>
where
F: Fn(Row, Col) -> E,
E: 'a,
{
use encode_unicode::CharExt;
move |arena, mut state: State<'a>| {
let mut buf;
match char::from_utf8_slice_start(state.bytes) {
Ok((first_letter, bytes_parsed)) => match first_letter {
'@' => {
debug_assert_eq!(bytes_parsed, 1);
// parsing a private tag name
match char::from_utf8_slice_start(&state.bytes[1..]) {
Ok((second_letter, bytes_parsed_2)) if second_letter.is_uppercase() => {
let total_parsed = bytes_parsed + bytes_parsed_2;
buf = String::with_capacity_in(total_parsed, arena);
buf.push('@');
buf.push(second_letter);
state = state
.advance_without_indenting(arena, total_parsed)
.map_err(|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
})?;
}
_ => {
// important for error messages
state = state
.advance_without_indenting(arena, bytes_parsed)
.map_err(|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
})?;
let row = state.line;
let col = state.column;
return state.fail(arena, MadeProgress, to_problem(row, col));
}
}
}
_ if first_letter.is_uppercase() => {
buf = String::with_capacity_in(1, arena);
buf.push(first_letter);
state = state
.advance_without_indenting(arena, bytes_parsed)
.map_err(|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
})?;
}
_ => {
let row = state.line;
let col = state.column;
return state.fail(arena, NoProgress, to_problem(row, col));
}
},
Err(_) => {
let row = state.line;
let col = state.column;
return state.fail(arena, NoProgress, to_problem(row, col));
}
};
while !state.bytes.is_empty() {
match char::from_utf8_slice_start(state.bytes) {
Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A ':' indicating the end of the field
if ch.is_alphabetic() || ch.is_ascii_digit() {
buf.push(ch);
state = state
.advance_without_indenting(arena, bytes_parsed)
.map_err(|(progress, _, state)| {
(progress, to_problem(state.line, state.column), state)
})?;
} else {
// This is the end of the field. We're done!
break;
}
}
Err(_) => {
let row = state.line;
let col = state.column;
return state.fail(arena, MadeProgress, to_problem(row, col));
}
};
}
Ok((MadeProgress, buf.into_bump_str(), state))
}
}
fn record_type_field<'a>(
min_indent: u16,
) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, TRecord<'a>> {
use crate::ident::lowercase_ident;
use crate::parser::Either::*;
use AssignedField::*;
move |arena, state: State<'a>| {
// You must have a field name, e.g. "email"
// using the initial row/col is important for error reporting
let row = state.line;
let col = state.column;
let (progress, loc_label, state) = loc!(specialize(
move |_, _, _| TRecord::Field(row, col),
lowercase_ident()
))
.parse(arena, state)?;
debug_assert_eq!(progress, MadeProgress);
let (_, spaces, state) =
space0_e(min_indent, TRecord::Space, TRecord::IndentEnd).parse(arena, state)?;
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.)
let (_, opt_loc_val, state) = optional(either!(
word1(b':', TRecord::Colon),
word1(b'?', TRecord::Optional)
))
.parse(arena, state)?;
let val_parser = specialize_ref(TRecord::Type, term(min_indent));
match opt_loc_val {
Some(First(_)) => {
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, TRecord::Space, TRecord::IndentColon)
.parse(arena, state)?;
Ok((
MadeProgress,
RequiredValue(loc_label, spaces, arena.alloc(loc_val)),
state,
))
}
Some(Second(_)) => {
let (_, loc_val, state) = space0_before_e(
val_parser,
min_indent,
TRecord::Space,
TRecord::IndentOptional,
)
.parse(arena, state)?;
Ok((
MadeProgress,
OptionalValue(loc_label, spaces, arena.alloc(loc_val)),
state,
))
}
// If no value was provided, record it as a Var.
// Canonicalize will know what to do with a Var later.
None => {
let value = if !spaces.is_empty() {
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
} else {
LabelOnly(loc_label)
};
Ok((MadeProgress, value, state))
}
}
}
}
#[inline(always)]
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TRecord<'a>> {
use crate::type_annotation::TypeAnnotation::*;
move |arena, state| {
let (_, (fields, final_comments), state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen),
word1(b'{', TRecord::Open),
loc!(record_type_field(min_indent)),
word1(b',', TRecord::End),
// word1_check_indent!(b'}', TRecord::End, min_indent, TRecord::IndentEnd),
word1(b'}', TRecord::End),
min_indent,
TRecord::Open,
TRecord::Space,
TRecord::IndentEnd
)
.parse(arena, state)?;
let field_term = specialize_ref(TRecord::Type, term(min_indent));
let (_, ext, state) = optional(allocated(field_term)).parse(arena, state)?;
let result = Record {
fields: fields.into_bump_slice(),
ext,
final_comments,
};
Ok((MadeProgress, result, state))
}
}
fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, Type<'a>> {
map!( map!(
and!( and!(
parse_concrete_type, specialize(Type::TApply, parse_concrete_type),
// Optionally parse space-separated arguments for the constructor, // Optionally parse space-separated arguments for the constructor,
// e.g. `Str Float` in `Map Str Float` // e.g. `Str Float` in `Map Str Float`
zero_or_more!(space1_before( loc_applied_args_e(min_indent)
move |arena, state| loc_applied_arg(min_indent).parse(arena, state),
min_indent,
))
), ),
|(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located<TypeAnnotation<'a>>>)| { |(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located<TypeAnnotation<'a>>>)| {
match &ctor { match &ctor {
@ -207,24 +417,56 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>> {
) )
} }
fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>> { fn loc_applied_args_e<'a>(
use crate::blankspace::space0; min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<TypeAnnotation<'a>>>, Type<'a>> {
zero_or_more!(loc_applied_arg(min_indent))
}
fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let (first, state) = space0_before(term(min_indent), min_indent).parse(arena, state)?; let (p1, first, state) = space0_before_e(
let (rest, state) = zero_or_more!(skip_first!( term(min_indent),
ascii_char(b','), min_indent,
space0_around(term(min_indent), min_indent) Type::TSpace,
Type::TIndentStart,
)
.parse(arena, state)?;
let (p2, rest, state) = zero_or_more!(skip_first!(
word1(b',', Type::TFunctionArgument),
one_of![
space0_around_e(
term(min_indent),
min_indent,
Type::TSpace,
Type::TIndentStart
),
|_, state: State<'a>| Err((
NoProgress,
Type::TFunctionArgument(state.line, state.column),
state
))
]
)) ))
.parse(arena, state)?; .parse(arena, state)?;
// TODO this space0 is dropped, so newlines just before the function arrow when there // TODO this space0 is dropped, so newlines just before the function arrow when there
// is only one argument are not seen by the formatter. Can we do better? // is only one argument are not seen by the formatter. Can we do better?
let (is_function, state) = let (p3, is_function, state) = optional(skip_first!(
optional(skip_first!(space0(min_indent), ascii_string("->"))).parse(arena, state)?; space0_e(min_indent, Type::TSpace, Type::TIndentStart),
word2(b'-', b'>', Type::TStart)
))
.parse(arena, state)?;
if is_function.is_some() { if is_function.is_some() {
let (return_type, state) = let (p4, return_type, state) = space0_before_e(
space0_before(term(min_indent), min_indent).parse(arena, state)?; term(min_indent),
min_indent,
Type::TSpace,
Type::TIndentStart,
)
.parse(arena, state)?;
// prepare arguments // prepare arguments
let mut arguments = Vec::with_capacity_in(rest.len() + 1, &arena); let mut arguments = Vec::with_capacity_in(rest.len() + 1, &arena);
@ -236,20 +478,16 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
region: return_type.region, region: return_type.region,
value: TypeAnnotation::Function(output, arena.alloc(return_type)), value: TypeAnnotation::Function(output, arena.alloc(return_type)),
}; };
Ok((result, state)) let progress = p1.or(p2).or(p3).or(p4);
Ok((progress, result, state))
} else { } else {
let progress = p1.or(p2).or(p3);
// if there is no function arrow, there cannot be more than 1 "argument" // if there is no function arrow, there cannot be more than 1 "argument"
if rest.is_empty() { if rest.is_empty() {
Ok((first, state)) Ok((progress, first, state))
} else { } else {
// e.g. `Int,Int` without an arrow and return type // e.g. `Int,Int` without an arrow and return type
Err(( panic!()
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented("TODO: Decide the correct error to return for 'Invalid function signature'".to_string()),
},
state,
))
} }
} }
} }
@ -274,28 +512,27 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
fn parse_concrete_type<'a>( fn parse_concrete_type<'a>(
arena: &'a Bump, arena: &'a Bump,
mut state: State<'a>, mut state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>> { ) -> ParseResult<'a, TypeAnnotation<'a>, TApply> {
let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.) let mut part_buf = String::new_in(arena); // The current "part" (parts are dot-separated.)
let mut parts: Vec<&'a str> = Vec::new_in(arena); let mut parts: Vec<&'a str> = Vec::new_in(arena);
// Qualified types must start with a capitalized letter. // Qualified types must start with a capitalized letter.
match peek_utf8_char(&state) { match peek_utf8_char_e(&state, TApply::StartNotUppercase, TApply::Space) {
Ok((first_letter, bytes_parsed)) => { Ok((first_letter, bytes_parsed)) => {
if first_letter.is_alphabetic() && first_letter.is_uppercase() { if first_letter.is_alphabetic() && first_letter.is_uppercase() {
part_buf.push(first_letter); part_buf.push(first_letter);
} else { } else {
return Err(unexpected(0, state, Attempting::ConcreteType)); let problem = TApply::StartNotUppercase(state.line, state.column + 1);
return Err((NoProgress, problem, state));
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting_e(arena, bytes_parsed, TApply::Space)?;
} }
Err(reason) => return state.fail(reason), Err(reason) => return Err((NoProgress, reason, state)),
} }
let mut next_char = None;
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
match peek_utf8_char(&state) { match peek_utf8_char_e(&state, TApply::End, TApply::Space) {
Ok((ch, bytes_parsed)) => { Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed: // After the first character, only these are allowed:
// //
@ -305,21 +542,33 @@ fn parse_concrete_type<'a>(
if ch.is_alphabetic() { if ch.is_alphabetic() {
if part_buf.is_empty() && !ch.is_uppercase() { if part_buf.is_empty() && !ch.is_uppercase() {
// Each part must begin with a capital letter. // Each part must begin with a capital letter.
return malformed(Some(ch), arena, state, parts); return Err((
MadeProgress,
TApply::StartNotUppercase(state.line, state.column),
state,
));
} }
part_buf.push(ch); part_buf.push(ch);
} else if ch.is_ascii_digit() { } else if ch.is_ascii_digit() {
// Parts may not start with numbers! // Parts may not start with numbers!
if part_buf.is_empty() { if part_buf.is_empty() {
return malformed(Some(ch), arena, state, parts); return Err((
MadeProgress,
TApply::StartIsNumber(state.line, state.column),
state,
));
} }
part_buf.push(ch); part_buf.push(ch);
} else if ch == '.' { } else if ch == '.' {
// Having two consecutive dots is an error. // Having two consecutive dots is an error.
if part_buf.is_empty() { if part_buf.is_empty() {
return malformed(Some(ch), arena, state, parts); return Err((
MadeProgress,
TApply::DoubleDot(state.line, state.column),
state,
));
} }
parts.push(part_buf.into_bump_str()); parts.push(part_buf.into_bump_str());
@ -328,14 +577,14 @@ fn parse_concrete_type<'a>(
part_buf = String::new_in(arena); part_buf = String::new_in(arena);
} else { } else {
// This must be the end of the type. We're done! // This must be the end of the type. We're done!
next_char = Some(ch);
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting_e(arena, bytes_parsed, TApply::Space)?;
}
Err(reason) => {
return Err((MadeProgress, reason, state));
} }
Err(reason) => return state.fail(reason),
} }
} }
@ -346,14 +595,11 @@ fn parse_concrete_type<'a>(
// //
// If we made it this far and don't have a next_char, then necessarily // If we made it this far and don't have a next_char, then necessarily
// we have consumed a '.' char previously. // we have consumed a '.' char previously.
return malformed(next_char.or(Some('.')), arena, state, parts); return Err((
} MadeProgress,
TApply::TrailingDot(state.line, state.column),
if part_buf.is_empty() { state,
// We had neither capitalized nor noncapitalized parts, ));
// yet we made it this far. The only explanation is that this was
// a stray '.' drifting through the cosmos.
return Err(unexpected(1, state, Attempting::Identifier));
} }
let answer = TypeAnnotation::Apply( let answer = TypeAnnotation::Apply(
@ -362,31 +608,37 @@ fn parse_concrete_type<'a>(
&[], &[],
); );
Ok((answer, state)) Ok((MadeProgress, answer, state))
} }
fn parse_type_variable<'a>( fn parse_type_variable<'a>(
arena: &'a Bump, arena: &'a Bump,
mut state: State<'a>, mut state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>> { ) -> ParseResult<'a, TypeAnnotation<'a>, TVariable> {
let mut buf = String::new_in(arena); let mut buf = String::new_in(arena);
match peek_utf8_char(&state) { let start_bytes_len = state.bytes.len();
match peek_utf8_char_e(&state, TVariable::StartNotLowercase, TVariable::Space) {
Ok((first_letter, bytes_parsed)) => { Ok((first_letter, bytes_parsed)) => {
// Type variables must start with a lowercase letter. // Type variables must start with a lowercase letter.
if first_letter.is_alphabetic() && first_letter.is_lowercase() { if first_letter.is_alphabetic() && first_letter.is_lowercase() {
buf.push(first_letter); buf.push(first_letter);
} else { } else {
return Err(unexpected(0, state, Attempting::TypeVariable)); return Err((
NoProgress,
TVariable::StartNotLowercase(state.line, state.column),
state,
));
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting_e(arena, bytes_parsed, TVariable::Space)?;
} }
Err(reason) => return state.fail(reason), Err(reason) => return Err((NoProgress, reason, state)),
} }
while !state.bytes.is_empty() { while !state.bytes.is_empty() {
match peek_utf8_char(&state) { match peek_utf8_char_e(&state, TVariable::End, TVariable::Space) {
Ok((ch, bytes_parsed)) => { Ok((ch, bytes_parsed)) => {
// After the first character, only these are allowed: // After the first character, only these are allowed:
// //
@ -399,52 +651,16 @@ fn parse_type_variable<'a>(
break; break;
} }
state = state.advance_without_indenting(bytes_parsed)?; state = state.advance_without_indenting_e(arena, bytes_parsed, TVariable::Space)?;
}
Err(reason) => {
return state.fail(arena, MadeProgress, reason);
} }
Err(reason) => return state.fail(reason),
} }
} }
let answer = TypeAnnotation::BoundVariable(buf.into_bump_str()); let answer = TypeAnnotation::BoundVariable(buf.into_bump_str());
Ok((answer, state)) let progress = Progress::from_lengths(start_bytes_len, state.bytes.len());
} Ok((progress, answer, state))
fn malformed<'a>(
opt_bad_char: Option<char>,
arena: &'a Bump,
mut state: State<'a>,
parts: Vec<&'a str>,
) -> ParseResult<'a, TypeAnnotation<'a>> {
// Reconstruct the original string that we've been parsing.
let mut full_string = String::new_in(arena);
full_string.push_str(arena_join(arena, &mut parts.into_iter(), ".").into_bump_str());
if let Some(bad_char) = opt_bad_char {
full_string.push(bad_char);
}
// Consume the remaining chars in the identifier.
while !state.bytes.is_empty() {
match peek_utf8_char(&state) {
Ok((ch, bytes_parsed)) => {
// We can't use ch.is_alphanumeric() here because that passes for
// things that are "numeric" but not ASCII digits, like `¾`
if ch == '.' || ch.is_alphabetic() || ch.is_ascii_digit() {
full_string.push(ch);
} else {
break;
}
state = state.advance_without_indenting(bytes_parsed)?;
}
Err(reason) => return state.fail(reason),
}
}
Ok((
TypeAnnotation::Malformed(full_string.into_bump_str()),
state,
))
} }

View file

@ -31,7 +31,7 @@ mod test_parse {
PackageName, PackageOrPath, PlatformHeader, To, PackageName, PackageOrPath, PlatformHeader, To,
}; };
use roc_parse::module::{app_header, interface_header, module_defs, platform_header}; use roc_parse::module::{app_header, interface_header, module_defs, platform_header};
use roc_parse::parser::{Fail, FailReason, Parser, State}; use roc_parse::parser::{Parser, State, SyntaxError};
use roc_parse::test_helpers::parse_expr_with; use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use std::{f64, i64}; use std::{f64, i64};
@ -43,12 +43,12 @@ mod test_parse {
assert_eq!(Ok(expected_expr), actual); assert_eq!(Ok(expected_expr), actual);
} }
fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) { fn assert_parsing_fails<'a>(input: &'a str, _reason: SyntaxError, _attempting: Attempting) {
let arena = Bump::new(); let arena = Bump::new();
let actual = parse_expr_with(&arena, input); let actual = parse_expr_with(&arena, input);
let expected_fail = Fail { reason, attempting }; // let expected_fail = Fail { reason, attempting };
assert_eq!(Err(expected_fail), actual); assert!(actual.is_err());
} }
fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) { fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) {
@ -291,7 +291,7 @@ mod test_parse {
#[test] #[test]
fn empty_source_file() { fn empty_source_file() {
assert_parsing_fails("", FailReason::Eof(Region::zero()), Attempting::Module); assert_parsing_fails("", SyntaxError::Eof(Region::zero()), Attempting::Module);
} }
#[test] #[test]
@ -310,7 +310,7 @@ mod test_parse {
assert_parsing_fails( assert_parsing_fails(
&too_long_str, &too_long_str,
FailReason::LineTooLong(0), SyntaxError::LineTooLong(0),
Attempting::Module, Attempting::Module,
); );
} }
@ -2410,8 +2410,11 @@ mod test_parse {
"# "#
); );
let actual = app_header() let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2448,8 +2451,11 @@ mod test_parse {
"# "#
); );
let actual = app_header() let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2499,9 +2505,13 @@ mod test_parse {
provides [ quicksort ] to base provides [ quicksort ] to base
"# "#
); );
let actual = app_header() let actual = app_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2544,8 +2554,11 @@ mod test_parse {
let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects fx.Blah {}"; let src = "platform rtfeldman/blah requires {} exposes [] packages {} imports [] provides [] effects fx.Blah {}";
let actual = platform_header() let actual = platform_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2612,8 +2625,11 @@ mod test_parse {
"# "#
); );
let actual = platform_header() let actual = platform_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2641,8 +2657,11 @@ mod test_parse {
"# "#
); );
let actual = interface_header() let actual = interface_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2670,8 +2689,11 @@ mod test_parse {
"# "#
); );
let actual = interface_header() let actual = interface_header()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2697,8 +2719,11 @@ mod test_parse {
"# "#
); );
let actual = module_defs() let actual = module_defs()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
// It should occur twice in the debug output - once for the pattern, // It should occur twice in the debug output - once for the pattern,
// and then again for the lookup. // and then again for the lookup.
@ -2709,7 +2734,7 @@ mod test_parse {
#[test] #[test]
fn standalone_module_defs() { fn standalone_module_defs() {
use roc_parse::ast::Def::*; use Def::*;
let arena = Bump::new(); let arena = Bump::new();
let newlines1 = &[Newline, Newline]; let newlines1 = &[Newline, Newline];
@ -2745,6 +2770,7 @@ mod test_parse {
Located::new(2, 2, 0, 10, def2), Located::new(2, 2, 0, 10, def2),
Located::new(3, 3, 0, 13, def3), Located::new(3, 3, 0, 13, def3),
]; ];
let src = indoc!( let src = indoc!(
r#" r#"
foo = 1 foo = 1
@ -2753,13 +2779,91 @@ mod test_parse {
baz = "stuff" baz = "stuff"
"# "#
); );
let actual = module_defs() let actual = module_defs()
.parse(&arena, State::new(src.as_bytes(), Attempting::Module)) .parse(
.map(|tuple| tuple.0); &arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
#[test]
fn module_def_newline() {
let arena = Bump::new();
let src = indoc!(
r#"
main =
i = 64
i
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert!(actual.is_ok());
}
#[test]
fn nested_def_annotation() {
let arena = Bump::new();
let src = indoc!(
r#"
main =
wrappedNotEq : a, a -> Bool
wrappedNotEq = \num1, num2 ->
num1 != num2
wrappedNotEq 2 3
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
assert!(actual.is_ok());
}
#[test]
fn outdenting_newline_after_else() {
let arena = Bump::new();
// highlights a problem with the else branch demanding a newline after its expression
let src = indoc!(
r#"
main =
v = \y -> if x then y else z
1
"#
);
let actual = module_defs()
.parse(
&arena,
State::new_in(&arena, src.as_bytes(), Attempting::Module),
)
.map(|tuple| tuple.1);
dbg!(&actual);
assert!(actual.is_ok());
}
#[test] #[test]
fn newline_after_equals() { fn newline_after_equals() {
// Regression test for https://github.com/rtfeldman/roc/issues/51 // Regression test for https://github.com/rtfeldman/roc/issues/51

View file

@ -1,6 +1,6 @@
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, ModuleName, TagName};
use roc_module::operator::BinOp; use roc_module::operator::BinOp;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_parse::ast::Base; use roc_parse::ast::Base;
@ -117,9 +117,13 @@ pub enum RuntimeError {
UnsupportedPattern(Region), UnsupportedPattern(Region),
// Example: when 1 is 1.X -> 32 // Example: when 1 is 1.X -> 32
MalformedPattern(MalformedPatternProblem, Region), MalformedPattern(MalformedPatternProblem, Region),
UnresolvedTypeVar,
ErroneousType,
LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>), LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>),
ValueNotExposed { ValueNotExposed {
module_name: InlinableString, module_name: ModuleName,
ident: InlinableString, ident: InlinableString,
region: Region, region: Region,
}, },

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