Merge remote-tracking branch 'origin/trunk' into examples

This commit is contained in:
Richard Feldman 2021-07-17 20:08:47 -04:00
commit c9703e34fe
295 changed files with 19527 additions and 9572 deletions

4
.earthignore Normal file
View File

@ -0,0 +1,4 @@
AUTHORS
nix
.envrc
.gitignore

155
.envrc
View File

@ -1,154 +1 @@
# Load environment variables from `nix-shell` and export it out.
#
# Usage: use_nix [-s <nix-expression>] [-w <path>] [-w <path>] ...
# -s nix-expression: The nix expression to use for building the shell environment.
# -w path: watch a file for changes. It can be specified multiple times. The
# shell specified with -s is automatically watched.
#
# If no nix-expression were given with -s, it will attempt to find and load
# the shell using the following files in order: shell.nix and default.nix.
#
# Example:
# - use_nix
# - use_nix -s shell.nix -w .nixpkgs-version.json
#
# The dependencies pulled by nix-shell are added to Nix's garbage collector
# roots, such that the environment remains persistent.
#
# Nix-shell is invoked only once per environment, and the output is cached for
# better performance. If any of the watched files change, then the environment
# is rebuilt.
#
# To remove old environments, and allow the GC to collect their dependencies:
# rm -f .direnv
#
use_nix() {
if ! validate_version; then
echo "This .envrc requires direnv version 2.18.2 or above."
exit 1
fi
# define all local variables
local shell
local files_to_watch=()
local opt OPTARG OPTIND # define vars used by getopts locally
while getopts ":n:s:w:" opt; do
case "${opt}" in
s)
shell="${OPTARG}"
files_to_watch=("${files_to_watch[@]}" "${shell}")
;;
w)
files_to_watch=("${files_to_watch[@]}" "${OPTARG}")
;;
:)
fail "Invalid option: $OPTARG requires an argument"
;;
\?)
fail "Invalid option: $OPTARG"
;;
esac
done
shift $((OPTIND -1))
if [[ -z "${shell}" ]]; then
if [[ -f shell.nix ]]; then
shell=shell.nix
files_to_watch=("${files_to_watch[@]}" shell.nix)
elif [[ -f default.nix ]]; then
shell=default.nix
files_to_watch=("${files_to_watch[@]}" default.nix)
else
fail "ERR: no shell was given"
fi
fi
local f
for f in "${files_to_watch[@]}"; do
if ! [[ -f "${f}" ]]; then
fail "cannot watch file ${f} because it does not exist"
fi
done
# compute the hash of all the files that makes up the development environment
local env_hash="$(hash_contents "${files_to_watch[@]}")"
# define the paths
local dir="$(direnv_layout_dir)"
local wd="${dir}/wd-${env_hash}"
local drv="${wd}/env.drv"
local dump="${wd}/dump.env"
# Generate the environment if we do not have one generated already.
if [[ ! -f "${drv}" ]]; then
mkdir -p "${wd}"
log_status "use nix: deriving new environment"
IN_NIX_SHELL=1 nix-instantiate --add-root "${drv}" --indirect "${shell}" > /dev/null
nix-store -r $(nix-store --query --references "${drv}") --add-root "${wd}/dep" --indirect > /dev/null
if [[ "${?}" -ne 0 ]] || [[ ! -f "${drv}" ]]; then
rm -rf "${wd}"
fail "use nix: was not able to derive the new environment. Please run 'direnv reload' to try again."
fi
log_status "use nix: updating cache"
nix-shell --pure "${drv}" --show-trace --run "$(join_args "$direnv" dump bash)" > "${dump}"
if [[ "${?}" -ne 0 ]] || [[ ! -f "${dump}" ]] || ! grep -q IN_NIX_SHELL "${dump}"; then
rm -rf "${wd}"
fail "use nix: was not able to update the cache of the environment. Please run 'direnv reload' to try again."
fi
fi
# evaluate the dump created by nix-shell earlier, but have to merge the PATH
# with the current PATH
# NOTE: we eval the dump here as opposed to direnv_load it because we don't
# want to persist environment variables coming from the shell at the time of
# the dump. See https://github.com/direnv/direnv/issues/405 for context.
local path_backup="${PATH}"
eval $(cat "${dump}")
export PATH="${PATH}:${path_backup}"
# cleanup the environment of variables that are not requried, or are causing issues.
unset shellHook # when shellHook is present, then any nix-shell'd script will execute it!
# watch all the files we were asked to watch for the environment
for f in "${files_to_watch[@]}"; do
watch_file "${f}"
done
}
fail() {
log_error "${@}"
exit 1
}
hash_contents() {
if has md5sum; then
cat "${@}" | md5sum | cut -c -32
elif has md5; then
cat "${@}" | md5 -q
fi
}
hash_file() {
if has md5sum; then
md5sum "${@}" | cut -c -32
elif has md5; then
md5 -q "${@}"
fi
}
validate_version() {
local version="$("${direnv}" version)"
local major="$(echo "${version}" | cut -d. -f1)"
local minor="$(echo "${version}" | cut -d. -f2)"
local patch="$(echo "${version}" | cut -d. -f3)"
if [[ "${major}" -gt 2 ]]; then return 0; fi
if [[ "${major}" -eq 2 ]] && [[ "${minor}" -gt 18 ]]; then return 0; fi
if [[ "${major}" -eq 2 ]] && [[ "${minor}" -eq 18 ]] && [[ "${patch}" -ge 2 ]]; then return 0; fi
return 1
}
use_nix -s shell.nix
use nix

38
.github/workflows/benchmarks.yml vendored Normal file
View File

@ -0,0 +1,38 @@
on:
pull_request:
paths-ignore:
- '**.md'
name: Benchmarks
env:
RUST_BACKTRACE: 1
jobs:
prep-dependency-container:
name: benchmark roc programs
runs-on: [self-hosted, i7-6700K]
timeout-minutes: 60
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@v2
with:
ref: "trunk"
clean: "true"
- name: Earthly version
run: earthly --version
- name: on trunk; prepare a self-contained benchmark folder
run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder
- uses: actions/checkout@v2
with:
clean: "false" # we want to keep the benchmark folder
- name: on current branch; prepare a self-contained benchmark folder
run: ./ci/safe-earthly.sh +prep-bench-folder
- name: execute benchmarks with regression check
run: ./ci/bench-runner.sh

View File

@ -1,4 +1,7 @@
on: [pull_request]
on:
pull_request:
paths-ignore:
- '**.md'
name: CI
@ -21,5 +24,4 @@ jobs:
run: earthly --version
- name: install dependencies, build, run zig tests, rustfmt, clippy, cargo test --release
run: ./ci/safe-earthly-test-all.sh
run: ./ci/safe-earthly.sh +test-all

View File

@ -1,4 +1,4 @@
on:
on:
schedule:
- cron: '0 0 * * *'

24
.github/workflows/spellcheck.yml vendored Normal file
View File

@ -0,0 +1,24 @@
on: [pull_request]
name: SpellCheck
env:
RUST_BACKTRACE: 1
jobs:
spell-check:
name: spell check
runs-on: [self-hosted]
timeout-minutes: 10
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@v2
with:
clean: "true"
- name: Earthly version
run: earthly --version
- name: install spell checker, do spell check
run: ./ci/safe-earthly.sh +check-typos

22
.github/workflows/www.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: deploy www.roc-lang.org
# Whenever a commit lands on trunk, deploy the site
on:
push:
branches:
- deploy-www # TODO change to trunk
jobs:
deploy:
name: 'Deploy to Netlify'
runs-on: [self-hosted]
steps:
- uses: jsmrcaga/action-netlify-deploy@v1.6.0
with:
install_command: 'pwd; cd ../../www'
build_command: 'bash build.sh'
build_directory: 'build'
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_DEPLOY_MESSAGE: "Deploy git ref ${{ github.ref }}"
NETLIFY_DEPLOY_TO_PROD: true

3
.gitignore vendored
View File

@ -14,6 +14,7 @@ vgcore.*
#editors
.idea/
.vscode/
.ignore
#files too big to track in git
editor/benches/resources/100000_lines.roc
@ -27,3 +28,5 @@ editor/benches/resources/500_lines.roc
# rust cache (sccache folder)
sccache_dir
# self-contained benchmark folder
bench-folder*

View File

@ -1 +1 @@
10.0.0
12.0.0

View File

@ -20,7 +20,7 @@ For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir).
### libunwind & libc++-dev
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be donw with `sudo apt-get install libunwind-dev`).
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be done with `sudo apt-get install libunwind-dev`).
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.)
### libcxb libraries
@ -40,23 +40,43 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
```
### Zig
**version: 0.7.x**
**version: 0.8.0**
If you're on MacOS, you can install with `brew install zig`
If you're on Ubuntu and use Snap, you can install with `snap install zig --classic --beta`
For any other OS, checkout the [Zig installation page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations.
If you prefer a package manager, you can try the following:
- For MacOS, you can install with `brew install zig`
- For, Ubuntu, you can use Snap, you can install with `snap install zig --classic --beta`
- For other systems, checkout this [page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
If you want to install it manually, you can also download Zig directly [here](https://ziglang.org/download/). Just make sure you download the right version, the bleeding edge master build is the first download link on this page.
### LLVM
**version: 10.0.x**
**version: 12.0.x**
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
`/usr/local/opt/llvm/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
For Ubuntu and Debian, you can use the `Automatic installation script` at [apt.llvm.org](https://apt.llvm.org):
```
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
```
For macOS, check the troubleshooting section below.
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.
By default, the script installs them as `llvm-as-12` and `clang-12`,
respectively. You can address this with symlinks like so:
There are also plenty of alternative options at http://releases.llvm.org/download.html
```
sudo ln -s /usr/bin/clang-12 /usr/bin/clang
```
```
sudo ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as
````
There are also alternative installation options at http://releases.llvm.org/download.html
[Troubleshooting](#troubleshooting)
## Using Nix
@ -94,6 +114,10 @@ You should be in a shell with everything needed to build already installed. Next
You should be in a repl now. Have fun!
### Extra tips
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/target/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly!
### Editor
When you want to run the editor from Ubuntu inside nix you need to install [nixGL](https://github.com/guibou/nixGL) as well:
@ -132,33 +156,24 @@ On Ubuntu, running `sudo apt install pkg-config cmake libx11-dev` fixed this.
If you encounter `cannot find -lz` run `sudo apt install zlib1g-dev`.
If you encounter:
```
error: No suitable version of LLVM was found system-wide or pointed
to by LLVM_SYS_120_PREFIX.
```
Add `export LLVM_SYS_120_PREFIX=/usr/lib/llvm-12` to your `~/.bashrc` or equivalent file for your shell.
### LLVM installation on macOS
By default homebrew will try to install llvm 11, which is currently
unsupported. You need to install an older version (10.0.0_3) by doing:
```
$ brew edit llvm
# Replace the contents of the file with https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50fb0b24dbe30f5e975210bdad63257f517/Formula/llvm.rb
# we expect llvm-as-10 to be present
$ ln -s /usr/local/opt/llvm/bin/{llvm-as,llvm-as-10}
# "pinning" ensures that homebrew doesn't update it automatically
$ brew pin llvm
```
If installing LLVM fails, it might help to run `sudo xcode-select -r` before installing again.
It might also be useful to add these exports to your shell:
```
export PATH="/usr/local/opt/llvm/bin:$PATH"
export LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
export CPPFLAGS="-I/usr/local/opt/llvm/include"
```
If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again.
### LLVM installation on Windows
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source

643
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -14,20 +14,30 @@ members = [
"compiler/reporting",
"compiler/fmt",
"compiler/mono",
"compiler/test_mono_macros",
"compiler/test_mono",
"compiler/load",
"compiler/gen",
"compiler/gen_llvm",
"compiler/gen_dev",
"compiler/build",
"compiler/arena_pool",
"compiler/test_gen",
"vendor/ena",
"vendor/inkwell",
"vendor/pathfinding",
"vendor/pretty",
"editor",
"cli",
"cli/cli_utils",
"roc_std",
"docs"
"docs",
]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
# see www/build.sh for more.
#
# Without the `-p` flag, cargo ignores `--no-default-features` when you have a
# workspace, and without `resolver = "2"` here, you can't use `-p` like this.
resolver = "2"
# Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release]

View File

@ -1,4 +1,4 @@
FROM rust:1.52-slim-buster
FROM rust:1.53-slim-buster
WORKDIR /earthbuild
prep-debian:
@ -13,17 +13,18 @@ install-other-libs:
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
RUN wget -c https://ziglang.org/download/0.8.0/zig-linux-x86_64-0.8.0.tar.xz --no-check-certificate
RUN tar -xf zig-linux-x86_64-0.8.0.tar.xz
RUN ln -s /earthbuild/zig-linux-x86_64-0.8.0/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
RUN ./llvm.sh 12
RUN ln -s /usr/bin/clang-12 /usr/bin/clang
RUN ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as
# use lld as linker
RUN ln -s /usr/bin/lld-10 /usr/bin/ld.lld
RUN ln -s /usr/bin/lld-12 /usr/bin/ld.lld
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
# valgrind
RUN apt -y install autotools-dev cmake automake libc6-dbg
@ -38,6 +39,8 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN rustup component add clippy
# rustfmt
RUN rustup component add rustfmt
# criterion
RUN cargo install --git https://github.com/Anton-4/cargo-criterion --branch main
# sccache
RUN apt -y install libssl-dev
RUN cargo install sccache
@ -97,15 +100,49 @@ check-rustfmt:
RUN cargo fmt --version
RUN cargo fmt --all -- --check
check-typos:
RUN cargo install typos-cli
COPY --dir .github ci cli compiler docs editor examples nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
RUN typos
test-rust:
FROM +copy-dirs-and-cache
ENV RUST_BACKTRACE=1
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release && sccache --show-stats
verify-no-git-changes:
FROM +test-rust
# If running tests caused anything to be changed or added (without being
# included in a .gitignore somewhere), fail the build!
#
# How it works: the `git ls-files` command lists all the modified or
# uncommitted files in the working tree, the `| grep -E .` command returns a
# zero exit code if it listed any files and nonzero otherwise (which is the
# opposite of what we want), and the `!` at the start inverts the exit code.
RUN ! git ls-files --deleted --modified --others --exclude-standard | grep -E .
test-all:
BUILD +test-zig
BUILD +check-rustfmt
BUILD +check-clippy
BUILD +check-typos
BUILD +test-rust
BUILD +verify-no-git-changes
# compile everything needed for benchmarks and output a self-contained folder
prep-bench-folder:
FROM +copy-dirs-and-cache
ARG BENCH_SUFFIX=branch
RUN cargo criterion -V
RUN --mount=type=cache,target=$SCCACHE_DIR cd cli && cargo criterion --no-run
RUN mkdir -p bench-folder/compiler/builtins/bitcode/src
RUN mkdir -p bench-folder/target/release/deps
RUN mkdir -p bench-folder/examples/benchmarks
RUN cp examples/benchmarks/*.roc bench-folder/examples/benchmarks/
RUN cp -r examples/benchmarks/platform bench-folder/examples/benchmarks/
RUN cp compiler/builtins/bitcode/src/str.zig bench-folder/compiler/builtins/bitcode/src
RUN cp target/release/roc bench-folder/target/release
# copy the most recent time bench to bench-folder
RUN cp target/release/deps/`ls -t target/release/deps/ | grep time_bench | head -n 1` bench-folder/target/release/deps/time_bench
SAVE ARTIFACT bench-folder AS LOCAL bench-folder-$BENCH_SUFFIX

View File

@ -46,7 +46,7 @@ By using systems-level programming languages like C and C++, platform authors sa
Roc is designed to make the "systems-level platform, higher-level application" experience as nice as possible.
* **Application** authors code exclusively in Roc. It's a language designed for nice ergonomics. The syntax resembles Ruby or CoffeeScript, and it has a fast compiler with full type inference.
* **Platform** authors code almost exclusively in a systems-level langauge like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
Every Roc application is built on top of exactly one Roc platform. There is no such thing as a Roc application that runs without a platform, and there is no default platform. You must choose one!

84
ci/bench-runner.sh Executable file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env bash
# script to return exit code 1 if benchmarks have regressed
# benchmark trunk
ulimit -s unlimited
cd bench-folder-trunk
./target/release/deps/time_bench --bench
cd ..
# move benchmark results so they can be compared later
cp -r bench-folder-trunk/target/criterion bench-folder-branch/target/
cd bench-folder-branch
LOG_FILE="bench_log.txt"
touch $LOG_FILE
FULL_CMD=" ./target/release/deps/time_bench --bench"
echo $FULL_CMD
script -efq $LOG_FILE -c "$FULL_CMD"
EXIT_CODE=$?
if cat $LOG_FILE | grep -q "regressed"; then
grep -B3 "regressed" $LOG_FILE | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g' | grep -o "\".*\"" | rev | cut -d' ' -f1 | rev > slow_benches_1.txt
echo "regression(s) detected in:"
cat slow_benches_1.txt
echo ""
echo ""
echo "------<<<<<<>>>>>>------"
echo "Benchmark detected regression. Running benchmark again to confirm..."
echo "------<<<<<<>>>>>>------"
echo ""
echo ""
# delete criterion folder to remove old benchmark data
rm -rf ./target/criterion
# benchmark trunk again
cd ../bench-folder-trunk
rm -rf target/criterion
./target/release/deps/time_bench --bench
cd ../bench-folder-branch
cp -r ../bench-folder-trunk/target/criterion ./target
rm $LOG_FILE
touch $LOG_FILE
script -efq $LOG_FILE -c "$FULL_CMD"
EXIT_CODE=$?
if cat $LOG_FILE | grep -q "regressed"; then
grep -B3 "regressed" $LOG_FILE | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g' | grep -o "\".*\"" | rev | cut -d' ' -f1 | rev > slow_benches_2.txt
echo "regression(s) detected in:"
cat slow_benches_2.txt
if [[ $(grep -Fxf slow_benches_1.txt slow_benches_2.txt | wc -l) -gt 0 ]]; then
echo ""
echo ""
echo "------<<<<<<!!!!!!>>>>>>------"
echo "Benchmarks were run twice and a regression was detected both times for the following benchmarks:"
grep -Fxf slow_benches_1.txt slow_benches_2.txt
echo "------<<<<<<!!!!!!>>>>>>------"
echo ""
echo ""
exit 1
else
echo "Benchmarks were run twice and a regression was detected on one run. We assume this was a fluke."
exit 0
fi
else
echo "Benchmarks were run twice and a regression was detected on one run. We assume this was a fluke."
exit 0
fi
else
echo ""
echo "Benchmark execution finished with exit code: $EXIT_CODE."
echo ""
exit $EXIT_CODE
fi

View File

@ -1,9 +1,12 @@
#!/usr/bin/env bash
LOG_FILE="earthly_log.txt"
touch $LOG_FILE
script -efq $LOG_FILE -c "earthly --config ci/earthly-conf.yml +test-all"
# first arg + everything after
ARGS=${@:1}
FULL_CMD="earthly --config ci/earthly-conf.yml $ARGS"
echo $FULL_CMD
script -efq $LOG_FILE -c "$FULL_CMD"
EXIT_CODE=$?
if grep -q "failed to mount" "$LOG_FILE"; then
@ -14,7 +17,7 @@ if grep -q "failed to mount" "$LOG_FILE"; then
echo "------<<<<<<!!!!!!>>>>>>------"
echo ""
echo ""
earthly --config ci/earthly-conf.yml --no-cache +test-all
earthly --config ci/earthly-conf.yml --no-cache $ARGS
else
exit $EXIT_CODE
fi

View File

@ -15,7 +15,12 @@ test = false
bench = false
[features]
default = ["target-x86"]
default = ["target-x86", "llvm", "editor"]
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
llvm = ["inkwell", "roc_gen_llvm", "roc_build/llvm"]
editor = ["roc_editor"]
target-x86 = []
@ -45,11 +50,11 @@ roc_unify = { path = "../compiler/unify" }
roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_gen = { path = "../compiler/gen" }
roc_build = { path = "../compiler/build" }
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor" }
roc_editor = { path = "../editor", optional = true }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
const_format = "0.2.8"
@ -62,24 +67,7 @@ inlinable_string = "0.1"
libc = "0.2"
libloading = "0.6"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release4", features = [ "llvm10-0" ] }
inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.10"
tempfile = "3.1.0"
@ -89,8 +77,12 @@ maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
strip-ansi-escapes = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"
serial_test = "0.5"
tempfile = "3.1.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "cli_utils" }
[[bench]]
name = "time_bench"
harness = false

17
cli/benches/README.md Normal file
View File

@ -0,0 +1,17 @@
# Running the benchmarks
Install cargo criterion:
```
cargo install cargo-criterion
```
To prevent stack overflow on the `CFold` benchmark:
```
ulimit -s unlimited
```
In the `cli` folder execute:
```
cargo criterion
```

31
cli/benches/time_bench.rs Normal file
View File

@ -0,0 +1,31 @@
use cli_utils::bench_utils::{
bench_cfold, bench_deriv, bench_nqueens, bench_quicksort, bench_rbtree_ck, bench_rbtree_delete,
};
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, SamplingMode,
};
fn bench_group_wall_time(c: &mut Criterion) {
let mut group = c.benchmark_group("bench-group_wall-time");
// calculate statistics based on a fixed(flat) 300 runs
group.sampling_mode(SamplingMode::Flat);
group.sample_size(300);
let bench_funcs: Vec<fn(Option<&mut BenchmarkGroup<WallTime>>) -> ()> = vec![
bench_nqueens, // queens 11
bench_cfold, // e = mkExpr 17 1
bench_deriv, // nest deriv 8 f
bench_rbtree_ck, // ms = makeMap 5 80000
bench_rbtree_delete, // m = makeMap 100000
bench_quicksort, // list size 10000
];
for bench_func in bench_funcs.iter() {
bench_func(Some(&mut group))
}
group.finish();
}
criterion_group!(benches, bench_group_wall_time);
criterion_main!(benches);

23
cli/cli_utils/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "cli_utils"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
edition = "2018"
description = "Shared code for cli tests and benchmarks"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
roc_cli = { path = "../../cli" }
roc_collections = { path = "../../compiler/collections" }
roc_load = { path = "../../compiler/load" }
roc_module = { path = "../../compiler/module" }
bumpalo = { version = "3.6.1", features = ["collections"] }
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
inlinable_string = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"
strip-ansi-escapes = "0.1"
tempfile = "3.1.0"

File diff suppressed because one or more lines are too long

2
cli/cli_utils/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod bench_utils;
pub mod helpers;

View File

@ -5,8 +5,8 @@ use roc_build::{
};
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
@ -26,12 +26,23 @@ pub enum BuildOutcome {
Errors,
}
impl BuildOutcome {
pub fn status_code(&self) -> i32 {
match self {
Self::NoProblems => 0,
Self::OnlyWarnings => 1,
Self::Errors => 2,
}
}
}
pub struct BuiltFile {
pub binary_path: PathBuf,
pub outcome: BuildOutcome,
pub total_time: Duration,
}
#[cfg(feature = "llvm")]
pub fn build_file<'a>(
arena: &'a Bump,
target: &Triple,
@ -204,10 +215,14 @@ pub fn build_file<'a>(
let total_time = compilation_start.elapsed().unwrap();
// If the cmd errored out, return the Err.
cmd_result?;
let exit_status = cmd_result?;
// TODO change this to report whether there were errors or warnings!
let outcome = BuildOutcome::NoProblems;
let outcome = if exit_status.success() {
BuildOutcome::NoProblems
} else {
BuildOutcome::Errors
};
Ok(BuiltFile {
binary_path,

View File

@ -1,12 +1,12 @@
#[macro_use]
extern crate clap;
use build::{build_file, BuildOutcome, BuiltFile};
use build::{BuildOutcome, BuiltFile};
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
@ -25,12 +25,13 @@ pub const CMD_DOCS: &str = "docs";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_LIB: &str = "lib";
pub const ROC_FILE: &str = "ROC_FILE";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
pub fn build_app<'a>() -> App<'a> {
App::new("roc")
let app = App::new("roc")
.version(crate_version!())
.subcommand(App::new(CMD_BUILD)
.about("Build a program")
@ -45,6 +46,12 @@ pub fn build_app<'a>() -> App<'a> {
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_LIB)
.long(FLAG_LIB)
.help("Build a C library instead of an executable.")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
@ -81,26 +88,31 @@ pub fn build_app<'a>() -> App<'a> {
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(App::new(CMD_EDIT)
.about("Launch the Roc editor")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.required(false)
.help("(optional) The directory or files to open on launch.")
)
)
.subcommand(
App::new(CMD_DOCS)
.about("Generate documentation for Roc modules")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.required(true)
.required(false)
.help("The directory or files to build documentation for")
)
);
if cfg!(feature = "editor") {
app.subcommand(
App::new(CMD_EDIT).about("Launch the Roc editor").arg(
Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.required(false)
.help("(optional) The directory or files to open on launch."),
),
)
} else {
app
}
}
pub fn docs(files: Vec<PathBuf>) {
@ -111,12 +123,15 @@ pub fn docs(files: Vec<PathBuf>) {
)
}
#[derive(Debug, PartialEq, Eq)]
pub enum BuildConfig {
BuildOnly,
BuildAndRun { roc_file_arg_index: usize },
}
#[cfg(feature = "llvm")]
pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use build::build_file;
use BuildConfig::*;
let arena = Bump::new();
@ -130,6 +145,12 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
};
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let link_type = if matches.is_present(FLAG_LIB) {
LinkType::Dylib
} else {
LinkType::Executable
};
let path = Path::new(filename).canonicalize().unwrap();
let src_dir = path.parent().unwrap().canonicalize().unwrap();
@ -159,7 +180,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
path,
opt_level,
emit_debug_info,
LinkType::Executable,
link_type,
);
match res_binary_path {
@ -175,13 +196,6 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
.strip_prefix(env::current_dir().unwrap())
.unwrap_or(&binary_path);
// Return a nonzero exit code if there were problems
let status_code = match outcome {
BuildOutcome::NoProblems => 0,
BuildOutcome::OnlyWarnings => 1,
BuildOutcome::Errors => 2,
};
// No need to waste time freeing this memory,
// since the process is about to exit anyway.
std::mem::forget(arena);
@ -192,7 +206,8 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
total_time.as_millis()
);
Ok(status_code)
// Return a nonzero exit code if there were problems
Ok(outcome.status_code())
}
BuildAndRun { roc_file_arg_index } => {
let mut cmd = Command::new(binary_path);
@ -210,23 +225,9 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
}
}
// Run the compiled app
let exit_status = cmd
.current_dir(original_cwd)
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing when roc run spawns a subprocess for the compiled app");
// `roc run` exits with the same status code as the app it ran.
//
// If you want to know whether there were compilation problems
// via status code, use either `roc build` or `roc check` instead!
match exit_status.code() {
Some(code) => Ok(code),
None => {
todo!("TODO gracefully handle the roc run subprocess terminating with a signal.");
}
match outcome {
BuildOutcome::Errors => Ok(outcome.status_code()),
_ => roc_run(cmd.current_dir(original_cwd)),
}
}
}
@ -241,3 +242,37 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
}
}
}
#[cfg(target_family = "unix")]
fn roc_run(cmd: &mut Command) -> io::Result<i32> {
use std::os::unix::process::CommandExt;
// This is much faster than spawning a subprocess if we're on a UNIX system!
let err = cmd.exec();
// If exec actually returned, it was definitely an error! (Otherwise,
// this process would have been replaced by the other one, and we'd
// never actually reach this line of code.)
Err(err)
}
#[cfg(not(target_family = "unix"))]
fn roc_run(cmd: &mut Command) -> io::Result<i32> {
// Run the compiled app
let exit_status = cmd
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing when roc run spawns a subprocess for the compiled app");
// `roc run` exits with the same status code as the app it ran.
//
// If you want to know whether there were compilation problems
// via status code, use either `roc build` or `roc check` instead!
match exit_status.code() {
Some(code) => Ok(code),
None => {
todo!("TODO gracefully handle the roc run subprocess terminating with a signal.");
}
}
}

View File

@ -1,17 +1,27 @@
use roc_cli::{
build, build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_DOCS, CMD_EDIT, CMD_REPL, CMD_RUN,
build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_DOCS, CMD_EDIT, CMD_REPL, CMD_RUN,
DIRECTORY_OR_FILES, ROC_FILE,
};
use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
#[cfg(feature = "llvm")]
use roc_cli::build;
use std::ffi::{OsStr, OsString};
#[cfg(not(feature = "llvm"))]
fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> {
panic!("Building without LLVM is not currently supported.");
}
fn main() -> io::Result<()> {
let matches = build_app().get_matches();
let exit_code = match matches.subcommand_name() {
None => {
roc_editor::launch(&[])?;
launch_editor(&[])?;
// rustc couldn't infer the error type here
Result::<i32, io::Error>::Ok(0)
@ -44,14 +54,14 @@ fn main() -> io::Result<()> {
.values_of_os(DIRECTORY_OR_FILES)
{
None => {
roc_editor::launch(&[])?;
launch_editor(&[])?;
}
Some(values) => {
let paths = values
.map(|os_str| Path::new(os_str))
.collect::<Vec<&Path>>();
roc_editor::launch(&paths)?;
launch_editor(&paths)?;
}
}
@ -59,17 +69,37 @@ fn main() -> io::Result<()> {
Ok(0)
}
Some(CMD_DOCS) => {
let values = matches
let maybe_values = matches
.subcommand_matches(CMD_DOCS)
.unwrap()
.values_of_os(DIRECTORY_OR_FILES)
.unwrap();
.values_of_os(DIRECTORY_OR_FILES);
let paths = values
.map(|os_str| Path::new(os_str).to_path_buf())
.collect::<Vec<PathBuf>>();
let mut values: Vec<OsString> = Vec::new();
docs(paths);
match maybe_values {
None => {
let mut os_string_values: Vec<OsString> = Vec::new();
read_all_roc_files(&OsStr::new("./").to_os_string(), &mut os_string_values)?;
for os_string in os_string_values {
values.push(os_string);
}
}
Some(os_values) => {
for os_str in os_values {
values.push(os_str.to_os_string());
}
}
}
let mut roc_files = Vec::new();
// Populate roc_files
for os_str in values {
let metadata = fs::metadata(os_str.clone())?;
roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?;
}
docs(roc_files);
Ok(0)
}
@ -78,3 +108,51 @@ fn main() -> io::Result<()> {
std::process::exit(exit_code);
}
fn read_all_roc_files(
dir: &OsString,
mut roc_file_paths: &mut Vec<OsString>,
) -> Result<(), std::io::Error> {
let entries = fs::read_dir(dir)?;
for entry in entries {
let path = entry?.path();
if path.is_dir() {
read_all_roc_files(&path.into_os_string(), &mut roc_file_paths)?;
} else if path.extension().and_then(OsStr::to_str) == Some("roc") {
let file_path = path.into_os_string();
roc_file_paths.push(file_path);
}
}
Ok(())
}
fn roc_files_recursive<P: AsRef<Path>>(
path: P,
file_type: FileType,
roc_files: &mut Vec<PathBuf>,
) -> io::Result<()> {
if file_type.is_dir() {
for entry_res in fs::read_dir(path)? {
let entry = entry_res?;
roc_files_recursive(entry.path(), entry.file_type()?, roc_files)?;
}
} else {
roc_files.push(path.as_ref().to_path_buf());
}
Ok(())
}
#[cfg(feature = "editor")]
fn launch_editor(filepaths: &[&Path]) -> io::Result<()> {
roc_editor::launch(filepaths)
}
#[cfg(not(feature = "editor"))]
fn launch_editor(_filepaths: &[&Path]) -> io::Result<()> {
panic!("Cannot launch the editor because this build of roc did not include `feature = \"editor\"`!");
}

View File

@ -1,15 +1,12 @@
use const_format::concatcp;
#[cfg(feature = "llvm")]
use gen::{gen_and_eval, ReplOutput};
use roc_gen::llvm::build::OptLevel;
use roc_parse::parser::{EExpr, SyntaxError};
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, PromptInfo};
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline::Editor;
use rustyline_derive::{Completer, Helper, Hinter};
use std::borrow::Cow;
use std::io;
use target_lexicon::Triple;
const BLUE: &str = "\u{001b}[36m";
const PINK: &str = "\u{001b}[35m";
@ -30,7 +27,9 @@ pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n";
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
pub const CONT_PROMPT: &str = concatcp!(BLUE, "", END_COL, " ");
#[cfg(feature = "llvm")]
mod eval;
#[cfg(feature = "llvm")]
mod gen;
#[derive(Completer, Helper, Hinter)]
@ -107,7 +106,16 @@ impl Validator for InputValidator {
}
}
#[cfg(not(feature = "llvm"))]
pub fn main() -> io::Result<()> {
panic!("The REPL currently requires being built with LLVM.");
}
#[cfg(feature = "llvm")]
pub fn main() -> io::Result<()> {
use rustyline::error::ReadlineError;
use rustyline::Editor;
// To debug rustyline:
// <UNCOMMENT> env_logger::init();
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
@ -226,7 +234,11 @@ fn report_parse_error(fail: SyntaxError) {
println!("TODO Gracefully report parse error in repl: {:?}", fail);
}
#[cfg(feature = "llvm")]
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
use roc_mono::ir::OptLevel;
use target_lexicon::Triple;
gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)

View File

@ -2,10 +2,11 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use libloading::Library;
use roc_collections::all::MutMap;
use roc_gen::{run_jit_function, run_jit_function_dynamic_type};
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::ident::{Lowercase, TagName};
use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
use roc_region::all::{Located, Region};
@ -37,7 +38,7 @@ pub unsafe fn jit_to_ast<'a>(
arena: &'a Bump,
lib: Library,
main_fn_name: &str,
layout: &Layout<'a>,
layout: ProcLayout<'a>,
content: &Content,
interns: &Interns,
home: ModuleId,
@ -53,11 +54,14 @@ pub unsafe fn jit_to_ast<'a>(
};
match layout {
Layout::FunctionPointer(&[], result) => {
ProcLayout {
arguments: [],
result,
} => {
// this is a thunk
jit_to_ast_help(&env, lib, main_fn_name, result, content)
jit_to_ast_help(&env, lib, main_fn_name, &result, content)
}
_ => jit_to_ast_help(&env, lib, main_fn_name, layout, content),
_ => Err(ToAstProblem::FunctionLayout),
}
}
@ -79,12 +83,26 @@ fn jit_to_ast_help<'a>(
)
}
Layout::Builtin(Builtin::Usize) => Ok(run_jit_function!(lib, main_fn_name, usize, |num| {
num_to_ast(env, nat_to_ast(env.arena, num), content)
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
})),
Layout::Builtin(Builtin::Int16) => {
Ok(run_jit_function!(lib, main_fn_name, i16, |num| num_to_ast(
env,
number_literal_to_ast(env.arena, num),
content
)))
}
Layout::Builtin(Builtin::Int32) => {
Ok(run_jit_function!(lib, main_fn_name, i32, |num| num_to_ast(
env,
number_literal_to_ast(env.arena, num),
content
)))
}
Layout::Builtin(Builtin::Int64) => {
Ok(run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
env,
i64_to_ast(env.arena, num),
number_literal_to_ast(env.arena, num),
content
)))
}
@ -93,13 +111,20 @@ fn jit_to_ast_help<'a>(
lib,
main_fn_name,
i128,
|num| num_to_ast(env, i128_to_ast(env.arena, num), content)
|num| num_to_ast(env, number_literal_to_ast(env.arena, num), content)
))
}
Layout::Builtin(Builtin::Float32) => {
Ok(run_jit_function!(lib, main_fn_name, f32, |num| num_to_ast(
env,
number_literal_to_ast(env.arena, num),
content
)))
}
Layout::Builtin(Builtin::Float64) => {
Ok(run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast(
env,
f64_to_ast(env.arena, num),
number_literal_to_ast(env.arena, num),
content
)))
}
@ -116,7 +141,7 @@ fn jit_to_ast_help<'a>(
}
}))
}
Layout::Builtin(Builtin::List(_, elem_layout)) => Ok(run_jit_function!(
Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!(
lib,
main_fn_name,
(*const u8, usize),
@ -125,29 +150,33 @@ fn jit_to_ast_help<'a>(
Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", other)
}
Layout::PhantomEmptyStruct => Ok(run_jit_function!(lib, main_fn_name, &u8, |_| {
Expr::Record {
fields: &[],
final_comments: env.arena.alloc([]),
}
})),
Layout::Struct(field_layouts) => {
let ptr_to_ast = |ptr: *const u8| match content {
Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, ptr, field_layouts, fields)
Ok(struct_to_ast(env, ptr, field_layouts, fields))
}
Content::Structure(FlatType::EmptyRecord) => {
struct_to_ast(env, ptr, field_layouts, &MutMap::default())
Ok(struct_to_ast(env, ptr, field_layouts, &MutMap::default()))
}
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1);
let (tag_name, payload_vars) = tags.iter().next().unwrap();
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
Ok(single_tag_union_to_ast(
env,
ptr,
field_layouts,
tag_name.clone(),
payload_vars,
))
}
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => {
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[])
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => Ok(
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[]),
),
Content::Structure(FlatType::Func(_, _, _)) => {
// a function with a struct as the closure environment
Err(ToAstProblem::FunctionLayout)
}
other => {
unreachable!(
@ -162,81 +191,141 @@ fn jit_to_ast_help<'a>(
let result_stack_size = layout.stack_size(env.ptr_bytes);
Ok(run_jit_function_dynamic_type!(
run_jit_function_dynamic_type!(
lib,
main_fn_name,
result_stack_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const u8) }
))
)
}
Layout::Union(UnionLayout::NonRecursive(union_layouts)) => match content {
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(union_layouts.len(), tags.len());
Layout::Union(UnionLayout::NonRecursive(union_layouts)) => {
let union_layout = UnionLayout::NonRecursive(union_layouts);
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
match content {
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(union_layouts.len(), tags.len());
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
let size = layout.stack_size(env.ptr_bytes);
use roc_mono::layout::WrappedVariant::*;
match union_variant {
UnionVariant::Wrapped(variant) => {
match variant {
NonRecursive {
sorted_tag_layouts: tags_and_layouts,
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
let size = layout.stack_size(env.ptr_bytes);
use roc_mono::layout::WrappedVariant::*;
match union_variant {
UnionVariant::Wrapped(variant) => {
match variant {
NonRecursive {
sorted_tag_layouts: tags_and_layouts,
} => {
Ok(run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
let offset = tags_and_layouts
.iter()
.map(|(_, fields)| {
fields
.iter()
.map(|l| l.stack_size(env.ptr_bytes))
.sum()
})
.max()
.unwrap_or(0);
let tag_id = match union_layout.tag_id_builtin() {
Builtin::Int1 => {
*(ptr.add(offset as usize) as *const i8) as i64
}
Builtin::Int8 => {
*(ptr.add(offset as usize) as *const i8) as i64
}
Builtin::Int16 => {
*(ptr.add(offset as usize) as *const i16) as i64
}
_ => unreachable!("invalid tag id layout"),
};
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) =
&tags_and_layouts[tag_id as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr =
&*env.arena.alloc(Located::at_zero(tag_expr));
let variables = &tags[tag_name];
debug_assert_eq!(arg_layouts.len(), variables.len());
// NOTE assumes the data bytes are the first bytes
let it =
variables.iter().copied().zip(arg_layouts.iter());
let output = sequence_of_expr(env, ptr, it);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
))
}
Recursive {
sorted_tag_layouts: tags_and_layouts,
} => {
Ok(run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
let tag_id = *(ptr as *const i64);
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) =
&tags_and_layouts[tag_id as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr =
&*env.arena.alloc(Located::at_zero(tag_expr));
let variables = &tags[tag_name];
// because the arg_layouts include the tag ID, it is one longer
debug_assert_eq!(
arg_layouts.len() - 1,
variables.len()
);
// skip forward to the start of the first element, ignoring the tag id
let ptr = ptr.offset(8);
let it =
variables.iter().copied().zip(&arg_layouts[1..]);
let output = sequence_of_expr(env, ptr, it);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
))
}
_ => todo!(),
}
| Recursive {
sorted_tag_layouts: tags_and_layouts,
} => {
Ok(run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
let tag_id = *(ptr as *const i64);
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) =
&tags_and_layouts[tag_id as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr =
&*env.arena.alloc(Located::at_zero(tag_expr));
let variables = &tags[tag_name];
// because the arg_layouts include the tag ID, it is one longer
debug_assert_eq!(arg_layouts.len() - 1, variables.len());
// skip forward to the start of the first element, ignoring the tag id
let ptr = ptr.offset(8);
let it = variables.iter().copied().zip(&arg_layouts[1..]);
let output = sequence_of_expr(env, ptr, it);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
))
}
_ => todo!(),
}
_ => unreachable!("any other variant would have a different layout"),
}
_ => unreachable!("any other variant would have a different layout"),
}
}
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
todo!("print recursive tag unions in the REPL")
}
Content::Alias(_, _, actual) => {
let content = env.subs.get_without_compacting(*actual).content;
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
todo!("print recursive tag unions in the REPL")
}
Content::Alias(_, _, actual) => {
let content = env.subs.get_without_compacting(*actual).content;
jit_to_ast_help(env, lib, main_fn_name, layout, &content)
jit_to_ast_help(env, lib, main_fn_name, layout, &content)
}
other => unreachable!("Weird content for Union layout: {:?}", other),
}
other => unreachable!("Weird content for Union layout: {:?}", other),
},
}
Layout::Union(UnionLayout::Recursive(_))
| Layout::Union(UnionLayout::NullableWrapped { .. })
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
@ -246,8 +335,6 @@ fn jit_to_ast_help<'a>(
}
Layout::Closure(_, _, _) => Err(ToAstProblem::FunctionLayout),
Layout::FunctionPointer(_, _) => Err(ToAstProblem::FunctionLayout),
Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"),
}
}
@ -272,15 +359,30 @@ fn ptr_to_ast<'a>(
content: &Content,
) -> Expr<'a> {
match layout {
Layout::Builtin(Builtin::Int128) => {
let num = unsafe { *(ptr as *const i128) };
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::Int64) => {
let num = unsafe { *(ptr as *const i64) };
num_to_ast(env, i64_to_ast(env.arena, num), content)
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::Int32) => {
let num = unsafe { *(ptr as *const i32) };
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::Int16) => {
let num = unsafe { *(ptr as *const i16) };
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::Usize) => {
let num = unsafe { *(ptr as *const usize) };
num_to_ast(env, nat_to_ast(env.arena, num), content)
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::Int1) => {
// TODO: bits are not as expected here.
@ -292,13 +394,18 @@ fn ptr_to_ast<'a>(
Layout::Builtin(Builtin::Float64) => {
let num = unsafe { *(ptr as *const f64) };
num_to_ast(env, f64_to_ast(env.arena, num), content)
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::Float32) => {
let num = unsafe { *(ptr as *const f32) };
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::EmptyList) => Expr::List {
items: &[],
final_comments: &[],
},
Layout::Builtin(Builtin::List(_, elem_layout)) => {
Layout::Builtin(Builtin::List(elem_layout)) => {
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
let ptr = unsafe { *(ptr as *const *const u8) };
@ -855,25 +962,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn nat_to_ast(arena: &Bump, num: usize) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn i128_to_ast(arena: &Bump, num: i128) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {
fn number_literal_to_ast<T: std::fmt::Display>(arena: &Bump, num: T) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}

View File

@ -8,9 +8,9 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens};
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_gen::llvm::externs::add_default_roc_externs;
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use roc_parse::parser::SyntaxError;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use std::path::{Path, PathBuf};
@ -68,7 +68,8 @@ pub fn gen_and_eval<'a>(
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
mut procedures,
procedures,
entry_point,
interns,
exposed_to_host,
mut subs,
@ -130,7 +131,9 @@ pub fn gen_and_eval<'a>(
let context = Context::create();
let builder = context.create_builder();
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::llvm::build::module_from_builtins(
&context, "",
));
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
// platform to provide them.
@ -166,12 +169,12 @@ pub fn gen_and_eval<'a>(
let module = arena.alloc(module);
let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
// Compile and add all the Procs before adding main
let env = roc_gen::llvm::build::Env {
let env = roc_gen_llvm::llvm::build::Env {
arena: &arena,
builder: &builder,
dibuilder: &dibuilder,
@ -185,65 +188,11 @@ pub fn gen_and_eval<'a>(
exposed_to_host: MutSet::default(),
};
let mut layout_ids = roc_mono::layout::LayoutIds::default();
let mut headers = Vec::with_capacity(procedures.len());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
let mut scope = roc_gen::llvm::build::Scope::default();
for ((symbol, layout), proc) in procedures.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, arena.alloc(layout), fn_val);
}
headers.push((proc, fn_val));
}
// Build each proc using its header info.
for (proc, fn_val) in headers {
let mut current_scope = scope.clone();
// only have top-level thunks for this proc's module in scope
// this retain is not needed for correctness, but will cause less confusion when debugging
let home = proc.name.module_id();
current_scope.retain_top_level_thunks_for_module(home);
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
// call finalize() before any code generation/verification
env.dibuilder.finalize();
if fn_val.verify(true) {
function_pass.run_on(&fn_val);
} else {
let mode = "NON-OPTIMIZED";
eprintln!(
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
fn_val.get_name().to_str().unwrap(),
mode,
);
fn_val.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
fn_val.get_name().to_str().unwrap(),
mode,
);
}
}
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
&env,
&mut layout_ids,
main_fn_symbol,
main_fn_layout,
opt_level,
procedures,
entry_point,
);
env.dibuilder.finalize();
@ -277,7 +226,7 @@ pub fn gen_and_eval<'a>(
&arena,
lib,
main_fn_name,
&arena.alloc(main_fn_layout).full(),
main_fn_layout,
&content,
&env.interns,
home,

View File

@ -7,14 +7,12 @@ extern crate roc_collections;
extern crate roc_load;
extern crate roc_module;
mod helpers;
#[macro_use]
extern crate maplit;
#[cfg(test)]
mod cli_run {
use crate::helpers::{
use cli_utils::helpers::{
example_file, extract_valgrind_errors, run_cmd, run_roc, run_with_valgrind, ValgrindError,
ValgrindErrorXWhat,
};
@ -304,4 +302,162 @@ mod cli_run {
assert_eq!(all_examples, std::collections::HashMap::default());
}
#[serial(hello_world)]
fn run_hello_world() {
check_output(
&example_file("hello-world", "Hello.roc"),
"hello-world",
&[],
"Hello, World!\n",
true,
);
}
#[test]
#[serial(hello_world)]
fn run_hello_world_optimized() {
check_output(
&example_file("hello-world", "Hello.roc"),
"hello-world",
&[],
"Hello, World!\n",
true,
);
}
#[test]
#[serial(quicksort)]
fn run_quicksort_not_optimized() {
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,
);
}
#[test]
#[serial(quicksort)]
fn run_quicksort_optimized() {
check_output(
&example_file("quicksort", "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]
#[serial(quicksort)]
fn run_quicksort_optimized_valgrind() {
check_output(
&example_file("quicksort", "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]
#[serial(nqueens)]
fn run_nqueens_not_optimized() {
check_output_with_stdin(
&example_file("benchmarks", "NQueens.roc"),
"6",
"nqueens",
&[],
"4\n",
true,
);
}
#[test]
#[serial(cfold)]
fn run_cfold_not_optimized() {
check_output_with_stdin(
&example_file("benchmarks", "CFold.roc"),
"3",
"cfold",
&[],
"11 & 11\n",
true,
);
}
#[test]
#[serial(deriv)]
fn run_deriv_not_optimized() {
check_output_with_stdin(
&example_file("benchmarks", "Deriv.roc"),
"2",
"deriv",
&[],
"1 count: 6\n2 count: 22\n",
true,
);
}
#[test]
#[serial(deriv)]
fn run_rbtree_insert_not_optimized() {
check_output(
&example_file("benchmarks", "RBTreeInsert.roc"),
"rbtree-insert",
&[],
"Node Black 0 {} Empty Empty\n",
true,
);
}
#[test]
#[serial(deriv)]
fn run_rbtree_delete_not_optimized() {
check_output_with_stdin(
&example_file("benchmarks", "RBTreeDel.roc"),
"420",
"rbtree-del",
&[],
"30\n",
true,
);
}
#[test]
#[serial(astar)]
fn run_astar_optimized_1() {
check_output(
&example_file("benchmarks", "TestAStar.roc"),
"test-astar",
&[],
"True\n",
false,
);
}
#[test]
#[serial(base64)]
fn base64() {
check_output(
&example_file("benchmarks", "TestBase64.roc"),
"test-base64",
&[],
"encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
true,
);
}
#[test]
#[serial(closure)]
fn closure() {
check_output(
&example_file("benchmarks", "Closure.roc"),
"closure",
&[],
"",
true,
);
}
}

View File

@ -59,7 +59,7 @@ pub export fn main() i32 {
roc__mainForHost_1_exposed(&callresult);
// stdout the result
stdout.print("{}\n", .{callresult.content.asSlice()}) catch unreachable;
stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable;
callresult.content.deinit();

View File

@ -59,7 +59,7 @@ pub export fn main() i32 {
roc__mainForHost_1_exposed(&callresult);
// stdout the result
stdout.print("{}\n", .{callresult.content.asSlice()}) catch unreachable;
stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable;
callresult.content.deinit();

View File

@ -4,12 +4,10 @@ extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
mod helpers;
#[cfg(test)]
mod repl_eval {
use crate::helpers;
use roc_gen::run_roc::RocCallResult;
use cli_utils::helpers;
use roc_gen_llvm::run_roc::RocCallResult;
#[test]
fn check_discriminant_size() {
@ -18,7 +16,8 @@ mod repl_eval {
let value: i64 = 1234;
assert_eq!(
std::mem::size_of_val(&RocCallResult::Success(value)),
roc_gen::run_roc::ROC_CALL_RESULT_DISCRIMINANT_SIZE + std::mem::size_of_val(&value)
roc_gen_llvm::run_roc::ROC_CALL_RESULT_DISCRIMINANT_SIZE
+ std::mem::size_of_val(&value)
)
}
@ -500,15 +499,11 @@ mod repl_eval {
#[test]
fn identity_lambda() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success("\\x -> x", "<function> : a -> a");
}
#[test]
fn stdlib_function() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success("Num.abs", "<function> : Num a -> Num a");
}

View File

@ -19,7 +19,7 @@ roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_gen = { path = "../gen" }
roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_reporting = { path = "../reporting" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
@ -28,24 +28,7 @@ inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release4", features = [ "llvm10-0" ] }
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.10"
[dev-dependencies]
@ -56,6 +39,10 @@ quickcheck = "0.8"
quickcheck_macros = "0.8"
[features]
default = ["llvm"]
target-arm = []
target-aarch64 = []
target-webassembly = []
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
llvm = ["inkwell", "roc_gen_llvm"]

View File

@ -1,21 +1,21 @@
use crate::target;
use crate::target::arch_str;
use inkwell::module::Module;
use inkwell::targets::{CodeModel, FileType, RelocMode};
#[cfg(feature = "llvm")]
use libloading::{Error, Library};
use roc_gen::llvm::build::OptLevel;
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use std::collections::HashMap;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output};
use target_lexicon::{Architecture, OperatingSystem, Triple};
use tempfile::tempdir;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType {
Executable,
Dylib,
// These numbers correspond to the --lib flag; if it's present
// (e.g. is_present returns `1 as bool`), this will be 1 as well.
Executable = 0,
Dylib = 1,
}
/// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"]
@ -360,6 +360,9 @@ fn link_linux(
};
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
init_arch(target);
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
Ok((
@ -466,8 +469,8 @@ fn link_macos(
"-lc++",
// "-lc++abi",
// "-lunwind", // TODO will eventually need this, see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
"-framework",
"Security", // This "-framework Security" arg is needed for the `rand` crate in examples/cli
// "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli
// "Security",
// Output
"-o",
output_path.to_str().unwrap(), // app
@ -477,12 +480,16 @@ fn link_macos(
))
}
#[cfg(feature = "llvm")]
pub fn module_to_dylib(
module: &Module,
module: &inkwell::module::Module,
target: &Triple,
opt_level: OptLevel,
) -> Result<Library, Error> {
let dir = tempdir().unwrap();
use crate::target::{self, convert_opt_level};
use inkwell::targets::{CodeModel, FileType, RelocMode};
let dir = tempfile::tempdir().unwrap();
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let mut app_o_file = file_path;
@ -492,7 +499,8 @@ pub fn module_to_dylib(
// Emit the .o file using position-indepedent code (PIC) - needed for dylibs
let reloc = RelocMode::PIC;
let model = CodeModel::Default;
let target_machine = target::target_machine(target, opt_level.into(), reloc, model).unwrap();
let target_machine =
target::target_machine(target, convert_opt_level(opt_level), reloc, model).unwrap();
target_machine
.write_to_file(module, FileType::Object, &app_o_file)
@ -529,3 +537,13 @@ fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
}
}
}
#[cfg(feature = "llvm")]
fn init_arch(target: &Triple) {
crate::target::init_arch(target);
}
#[cfg(not(feature = "llvm"))]
fn init_arch(_target: &Triple) {
panic!("Tried to initialize LLVM when crate was not built with `feature = \"llvm\"` enabled");
}

View File

@ -1,14 +1,14 @@
use crate::target;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::targets::{CodeModel, FileType, RelocMode};
pub use roc_gen::llvm::build::FunctionIterator;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
#[cfg(feature = "llvm")]
use roc_gen_llvm::llvm::build::module_from_builtins;
#[cfg(feature = "llvm")]
pub use roc_gen_llvm::llvm::build::FunctionIterator;
#[cfg(feature = "llvm")]
use roc_load::file::MonomorphizedModule;
use roc_mono::layout::LayoutIds;
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
#[cfg(feature = "llvm")]
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use target_lexicon::Triple;
use std::time::Duration;
#[derive(Debug, Clone, Copy, Default)]
pub struct CodeGenTiming {
@ -19,16 +19,24 @@ pub struct CodeGenTiming {
// TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
#[cfg(feature = "llvm")]
#[allow(clippy::cognitive_complexity)]
pub fn gen_from_mono_module(
arena: &Bump,
arena: &bumpalo::Bump,
mut loaded: MonomorphizedModule,
roc_file_path: &Path,
target: Triple,
target: target_lexicon::Triple,
app_o_file: &Path,
opt_level: OptLevel,
emit_debug_info: bool,
) -> CodeGenTiming {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::context::Context;
use inkwell::module::Linkage;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use std::time::SystemTime;
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
@ -88,9 +96,6 @@ pub fn gen_from_mono_module(
// module.strip_debug_info();
// mark our zig-defined builtins as internal
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::module::Linkage;
let app_ll_file = {
let mut temp = PathBuf::from(roc_file_path);
temp.set_extension("ll");
@ -100,30 +105,32 @@ pub fn gen_from_mono_module(
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = context.create_enum_attribute(kind_id, 1);
let enum_attr = context.create_enum_attribute(kind_id, 1);
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
// mark our zig-defined builtins as internal
if name.starts_with("roc_builtins") {
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);
if name.starts_with("roc_builtins.dict")
|| name.starts_with("dict.RocDict")
|| name.starts_with("roc_builtins.list")
|| name.starts_with("list.RocList")
{
function.add_attribute(AttributeLoc::Function, enum_attr);
}
}
let builder = context.create_builder();
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 (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
// 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::llvm::build::Env {
arena: &arena,
builder: &builder,
dibuilder: &dibuilder,
@ -136,55 +143,13 @@ pub fn gen_from_mono_module(
exposed_to_host: loaded.exposed_to_host.keys().copied().collect(),
};
// Populate Procs further and get the low-level Expr from the canonical Expr
let mut headers = Vec::with_capacity(loaded.procedures.len());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
let mut layout_ids = LayoutIds::default();
let mut scope = Scope::default();
for ((symbol, layout), proc) in loaded.procedures {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, arena.alloc(layout), fn_val);
}
headers.push((proc, fn_val));
}
// Build each proc using its header info.
for (proc, fn_val) in headers {
// NOTE: This is here to be uncommented in case verification fails.
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {:?}\n\n", proc);
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
// call finalize() before any code generation/verification
env.dibuilder.finalize();
if fn_val.verify(true) {
fpm.run_on(&fn_val);
} else {
fn_val.print_to_stderr();
// write the ll code to a file, so we can modify it
env.module.print_to_file(&app_ll_file).unwrap();
// env.module.print_to_stderr();
// NOTE: If this fails, uncomment the above println to debug.
panic!(
r"Non-main function {:?} failed LLVM verification. I wrote the full LLVM IR to {:?}",
fn_val.get_name(),
app_ll_file,
);
}
}
roc_gen_llvm::llvm::build::build_procedures(
&env,
opt_level,
loaded.procedures,
loaded.entry_point,
Some(&app_ll_file),
);
env.dibuilder.finalize();
@ -199,8 +164,9 @@ pub fn gen_from_mono_module(
env.module.print_to_file(&app_ll_file).unwrap();
panic!(
"😱 LLVM errors when defining module; I wrote the full LLVM IR to {:?}\n\n {:?}",
app_ll_file, errors,
"😱 LLVM errors when defining module; I wrote the full LLVM IR to {:?}\n\n {}",
app_ll_file,
errors.to_string(),
);
}
@ -229,7 +195,7 @@ pub fn gen_from_mono_module(
// run the debugir https://github.com/vaivaswatha/debugir tool
match Command::new("debugir")
.env_clear()
.args(&[app_ll_file.to_str().unwrap()])
.args(&["-instnamer", app_ll_file.to_str().unwrap()])
.output()
{
Ok(_) => {}
@ -245,7 +211,7 @@ pub fn gen_from_mono_module(
}
// assemble the .ll into a .bc
let _ = Command::new("llvm-as-10")
let _ = Command::new("llvm-as")
.env_clear()
.args(&[
app_ll_dbg_file.to_str().unwrap(),
@ -257,7 +223,7 @@ pub fn gen_from_mono_module(
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
let _ = Command::new("llc-10")
let _ = Command::new("llc-12")
.env_clear()
.args(&[
"-filetype=obj",
@ -273,7 +239,7 @@ pub fn gen_from_mono_module(
let reloc = RelocMode::Default;
let model = CodeModel::Default;
let target_machine =
target::target_machine(&target, opt_level.into(), reloc, model).unwrap();
target::target_machine(&target, convert_opt_level(opt_level), reloc, model).unwrap();
target_machine
.write_to_file(&env.module, FileType::Object, &app_o_file)

View File

@ -1,7 +1,10 @@
use inkwell::targets::{
CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple,
#[cfg(feature = "llvm")]
use inkwell::{
targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple},
OptimizationLevel,
};
use inkwell::OptimizationLevel;
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use target_lexicon::{Architecture, OperatingSystem, Triple};
pub fn target_triple_str(target: &Triple) -> &'static str {
@ -28,36 +31,20 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
}
}
/// NOTE: arch_str is *not* the same as the beginning of the magic target triple
/// string! For example, if it's "x86-64" here, the magic target triple string
/// will begin with "x86_64" (with an underscore) instead.
pub fn arch_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings:
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
#[cfg(feature = "llvm")]
pub fn init_arch(target: &Triple) {
match target.architecture {
Architecture::X86_64 => {
Target::initialize_x86(&InitializationConfig::default());
"x86-64"
}
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => {
Target::initialize_aarch64(&InitializationConfig::default());
"aarch64"
}
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
// NOTE: why not enable arm and wasm by default?
//
// We had some trouble getting them to link properly. This may be resolved in the
// future, or maybe it was just some weird configuration on one machine.
Target::initialize_arm(&InitializationConfig::default());
"arm"
}
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
Target::initialize_webassembly(&InitializationConfig::default());
"wasm32"
}
_ => panic!(
"TODO gracefully handle unsupported target architecture: {:?}",
@ -66,6 +53,26 @@ pub fn arch_str(target: &Triple) -> &'static str {
}
}
/// NOTE: arch_str is *not* the same as the beginning of the magic target triple
/// string! For example, if it's "x86-64" here, the magic target triple string
/// will begin with "x86_64" (with an underscore) instead.
pub fn arch_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings:
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
match target.architecture {
Architecture::X86_64 => "x86-64",
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => "aarch64",
Architecture::Arm(_) if cfg!(feature = "target-arm") => "arm",
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => "wasm32",
_ => panic!(
"TODO gracefully handle unsupported target architecture: {:?}",
target.architecture
),
}
}
#[cfg(feature = "llvm")]
pub fn target_machine(
target: &Triple,
opt: OptimizationLevel,
@ -74,6 +81,8 @@ pub fn target_machine(
) -> Option<TargetMachine> {
let arch = arch_str(target);
init_arch(target);
Target::from_name(arch).unwrap().create_target_machine(
&TargetTriple::create(target_triple_str(target)),
"generic",
@ -83,3 +92,11 @@ pub fn target_machine(
model,
)
}
#[cfg(feature = "llvm")]
pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel {
match level {
OptLevel::Normal => OptimizationLevel::None,
OptLevel::Optimize => OptimizationLevel::Aggressive,
}
}

View File

@ -9,15 +9,15 @@ Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used
Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is:
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Int -> elem` in LLVM
- ..writing `List elem, Int -> Result elem [ OutOfBounds ]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `Int` index exists.
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Nat -> elem` in LLVM
- ..writing `List elem, Nat -> Result elem [ OutOfBounds ]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `Nat` index exists.
### can/src/builtins.rs
Right at the top of this module is a function called `builtin_defs`. All this is doing is mapping the `Symbol` defined in `module/src/symbol.rs` to its implementation. Some of the builtins are quite complex, such as `list_get`. What makes `list_get` is that it returns tags, and in order to return tags it first has to defer to lower-level functions via an if statement.
Lets look at `List.repeat : elem, Int -> List elem`, which is more straight-forward, and points directly to its lower level implementation:
Lets look at `List.repeat : elem, Nat -> List elem`, which is more straight-forward, and points directly to its lower level implementation:
```
fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let elem_var = var_store.fresh();
@ -42,7 +42,7 @@ fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
```
In these builtin definitions you will need to allocate for and list the arguments. For `List.repeat`, the arguments are the `elem_var` and the `len_var`. So in both the `body` and `defn` we list these arguments in a vector, with the `Symobl::ARG_1` adn` Symvol::ARG_2` designating which argument is which.
In these builtin definitions you will need to allocate for and list the arguments. For `List.repeat`, the arguments are the `elem_var` and the `len_var`. So in both the `body` and `defn` we list these arguments in a vector, with the `Symobl::ARG_1` and` Symvol::ARG_2` designating which argument is which.
Since `List.repeat` is implemented entirely as low level functions, its `body` is a `RunLowLevel`, and the `op` is `LowLevel::ListRepeat`. Lets talk about `LowLevel` in the next section.
@ -60,7 +60,7 @@ Its one thing to actually write these functions, its _another_ thing to let the
## Specifying how we pass args to the function
### builtins/mono/src/borrow.rs
After we have all of this, we need to specify if the arguements we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
## Specifying the uniqueness of a function
### builtins/src/unique.rs

View File

@ -1,5 +1,7 @@
zig-cache
src/zig-cache
benchmark/zig-cache
builtins.ll
builtins.bc
builtins.o
dec

View File

@ -3,10 +3,10 @@
## Adding a bitcode builtin
To add a builtin:
1. Add the function to the relevent module. For `Num` builtin use it in `src/num.zig`, for `Str` builtins use `src/str.zig`, and so on. **For anything you add, you must add tests for it!** Not only does to make the builtins more maintainable, it's the the easiest way to test these functions on Zig. To run the test, run: `zig build test`
1. Add the function to the relevant module. For `Num` builtin use it in `src/num.zig`, for `Str` builtins use `src/str.zig`, and so on. **For anything you add, you must add tests for it!** Not only does to make the builtins more maintainable, it's the the easiest way to test these functions on Zig. To run the test, run: `zig build test`
2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }`
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first arguement is the function, the second is the name of it in LLVM.
4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevent area and add your new function.
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first argument is the function, the second is the name of it in LLVM.
4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevant area and add your new function.
5. You can now your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
## How it works
@ -32,4 +32,4 @@ There will be two directories like `roc_builtins-[some random characters]`, look
## Calling bitcode functions
use the `call_bitcode_fn` function defined in `llvm/src/build.rs` to call bitcode funcitons.
use the `call_bitcode_fn` function defined in `llvm/src/build.rs` to call bitcode functions.

View File

@ -0,0 +1,6 @@
#!/bin/bash
set -euxo pipefail
zig build-exe benchmark/dec.zig -O ReleaseFast --main-pkg-path .
./dec

View File

@ -0,0 +1,174 @@
const std = @import("std");
const time = std.time;
const Timer = time.Timer;
const RocStr = @import("../src/str.zig").RocStr;
const RocDec = @import("../src/dec.zig").RocDec;
var timer: Timer = undefined;
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
timer = try Timer.start();
try stdout.print("7 additions took ", .{});
try avg_runs(add7);
try stdout.print("7 subtractions took ", .{});
try avg_runs(sub7);
try stdout.print("7 multiplications took ", .{});
try avg_runs(mul7);
try stdout.print("7 divisions took ", .{});
try avg_runs(div7);
}
fn avg_runs(func: fn() u64) !void {
const stdout = std.io.getStdOut().writer();
var first_run = func();
var lowest = first_run;
var highest = first_run;
var sum = first_run;
// 31 runs
var runs = [_]u64{ first_run, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
var next_run: usize = 1; // we already did first_run
while (next_run < runs.len) {
const run = func();
lowest = std.math.min(lowest, run);
highest = std.math.max(highest, run);
runs[next_run] = run;
next_run += 1;
}
std.sort.sort(u64, &runs, {}, comptime std.sort.asc(u64));
const median = runs[runs.len / 2];
try stdout.print("{}ns (lowest: {}ns, highest: {}ns)\n", .{median, lowest, highest});
}
fn add7() u64 {
var str1 = RocStr.init("1.2", 3);
const dec1 = RocDec.fromStr(str1).?;
var str2 = RocStr.init("3.4", 3);
const dec2 = RocDec.fromStr(str2).?;
timer.reset();
var a = dec1.add(dec2);
a = a.add(dec1);
a = a.add(dec2);
a = a.add(dec1);
a = a.add(dec2);
a = a.add(dec1);
a = a.add(dec2);
a = a.add(dec1);
a = a.add(dec2);
a = a.add(dec1);
a = a.add(dec2);
a = a.add(dec1);
a = a.add(dec2);
a = a.add(dec1);
a = a.add(dec2);
a = a.add(dec1);
a = a.add(dec2);
return timer.read();
}
fn sub7() u64 {
var str1 = RocStr.init("1.2", 3);
const dec1 = RocDec.fromStr(str1).?;
var str2 = RocStr.init("3.4", 3);
const dec2 = RocDec.fromStr(str2).?;
timer.reset();
var a = dec1.sub(dec2);
a = a.sub(dec1);
a = a.sub(dec2);
a = a.sub(dec1);
a = a.sub(dec2);
a = a.sub(dec1);
a = a.sub(dec2);
a = a.sub(dec1);
a = a.sub(dec2);
a = a.sub(dec1);
a = a.sub(dec2);
a = a.sub(dec1);
a = a.sub(dec2);
a = a.sub(dec1);
a = a.sub(dec2);
a = a.sub(dec1);
a = a.sub(dec2);
return timer.read();
}
fn mul7() u64 {
var str1 = RocStr.init("1.2", 3);
const dec1 = RocDec.fromStr(str1).?;
var str2 = RocStr.init("3.4", 3);
const dec2 = RocDec.fromStr(str2).?;
timer.reset();
var a = dec1.mul(dec2);
a = a.mul(dec1);
a = a.mul(dec2);
a = a.mul(dec1);
a = a.mul(dec2);
a = a.mul(dec1);
a = a.mul(dec2);
a = a.mul(dec1);
a = a.mul(dec2);
a = a.mul(dec1);
a = a.mul(dec2);
a = a.mul(dec1);
a = a.mul(dec2);
a = a.mul(dec1);
a = a.mul(dec2);
a = a.mul(dec1);
a = a.mul(dec2);
return timer.read();
}
fn div7() u64 {
var str1 = RocStr.init("1.2", 3);
const dec1 = RocDec.fromStr(str1).?;
var str2 = RocStr.init("3.4", 3);
const dec2 = RocDec.fromStr(str2).?;
timer.reset();
var a = dec1.div(dec2);
a = a.div(dec1);
a = a.div(dec2);
a = a.div(dec1);
a = a.div(dec2);
a = a.div(dec1);
a = a.div(dec2);
a = a.div(dec1);
a = a.div(dec2);
a = a.div(dec1);
a = a.div(dec2);
a = a.div(dec1);
a = a.div(dec2);
a = a.div(dec1);
a = a.div(dec2);
a = a.div(dec1);
a = a.div(dec2);
return timer.read();
}

View File

@ -0,0 +1,5 @@
#!/bin/bash
set -euxo pipefail
zig build-obj src/main.zig -O ReleaseFast -femit-llvm-ir=builtins.ll -femit-bin=builtins.o --strip

View File

@ -1,16 +1,15 @@
const builtin = @import("builtin");
const std = @import("std");
const mem = std.mem;
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
// b.setPreferredReleaseMode(builtin.Mode.Debug);
b.setPreferredReleaseMode(builtin.Mode.ReleaseFast);
// b.setPreferredReleaseMode(builtin.Mode.Debug
b.setPreferredReleaseMode(.ReleaseFast);
const mode = b.standardReleaseOptions();
// Options
const fallback_main_path = "./src/main.zig";
const main_path_desc = b.fmt("Override path to main.zig. Used by \"ir\" and \"test\". Defaults to \"{}\". ", .{fallback_main_path});
const main_path_desc = b.fmt("Override path to main.zig. Used by \"ir\" and \"test\". Defaults to \"{s}\". ", .{fallback_main_path});
const main_path = b.option([]const u8, "main-path", main_path_desc) orelse fallback_main_path;
// Tests

View File

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

View File

@ -1,16 +1,199 @@
const std = @import("std");
const str = @import("str.zig");
const math = std.math;
const RocStr = str.RocStr;
pub const RocDec = struct {
num: i128,
pub const decimal_places: u32 = 18;
pub const decimal_places: u5 = 18;
pub const whole_number_places: u5 = 21;
const max_digits: u6 = 39;
const leading_zeros: [17]u8 = "00000000000000000".*;
pub const min: RocDec = .{ .num = math.minInt(i128) };
pub const max: RocDec = .{ .num = math.maxInt(i128) };
pub const one_point_zero: RocDec = .{ .num = comptime math.pow(i128, 10, RocDec.decimal_places) };
pub const one_point_zero_i128: i128 = math.pow(i128, 10, RocDec.decimal_places);
pub const one_point_zero: RocDec = .{ .num = one_point_zero_i128 };
pub fn fromU64(num: u64) RocDec {
return .{ .num = num * one_point_zero_i128 };
}
pub fn fromStr(roc_str: RocStr) ?RocDec {
if (roc_str.isEmpty()) {
return null;
}
const length = roc_str.len();
const roc_str_slice = roc_str.asSlice();
var is_negative: bool = roc_str_slice[0] == '-';
var initial_index: usize = if (is_negative) 1 else 0;
var point_index: ?usize = null;
var index: usize = initial_index;
while (index < length) {
var byte: u8 = roc_str_slice[index];
if (byte == '.' and point_index == null) {
point_index = index;
index += 1;
continue;
}
if (!isDigit(byte)) {
return null;
}
index += 1;
}
var before_str_length = length;
var after_val_i128: ?i128 = null;
if (point_index) |pi| {
before_str_length = pi;
var after_str_len = (length - 1) - pi;
if (after_str_len > decimal_places) {
std.debug.panic("TODO runtime exception for too many decimal places!", .{});
}
var diff_decimal_places = decimal_places - after_str_len;
var after_str = roc_str_slice[pi + 1 .. length];
var after_u64 = std.fmt.parseUnsigned(u64, after_str, 10) catch null;
after_val_i128 = if (after_u64) |f| @intCast(i128, f) * math.pow(i128, 10, diff_decimal_places) else null;
}
var before_str = roc_str_slice[initial_index..before_str_length];
var before_val_not_adjusted = std.fmt.parseUnsigned(i128, before_str, 10) catch null;
var before_val_i128: ?i128 = null;
if (before_val_not_adjusted) |before| {
var result: i128 = undefined;
var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result);
if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{});
}
before_val_i128 = result;
}
var dec: ?RocDec = null;
if (before_val_i128) |before| {
if (after_val_i128) |after| {
var result: i128 = undefined;
var overflowed = @addWithOverflow(i128, before, after, &result);
if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{});
}
dec = .{ .num = result };
} else {
dec = .{ .num = before };
}
} else if (after_val_i128) |after| {
dec = .{ .num = after };
}
if (dec) |d| {
if (is_negative) {
dec = d.negate();
}
}
return dec;
}
fn isDigit(c: u8) bool {
return (c -% 48) <= 9;
}
pub fn toStr(self: RocDec) ?RocStr {
// Special case
if (self.num == 0) {
return RocStr.init("0.0", 3);
}
// Check if this Dec is negative, and if so convert to positive
// We will handle adding the '-' later
const is_negative = self.num < 0;
const num = if (is_negative) std.math.negate(self.num) catch {
std.debug.panic("TODO runtime exception failing to negate", .{});
} else self.num;
// Format the backing i128 into an array of digits (u8s)
var digit_bytes: [max_digits + 1]u8 = undefined;
var num_digits = std.fmt.formatIntBuf(digit_bytes[0..], num, 10, false, .{});
// Get the slice for before the decimal point
var before_digits_slice: []const u8 = undefined;
var before_digits_offset: usize = 0;
var before_digits_adjust: u6 = 0;
if (num_digits > decimal_places) {
before_digits_offset = num_digits - decimal_places;
before_digits_slice = digit_bytes[0..before_digits_offset];
} else {
before_digits_adjust = @intCast(u6, math.absInt(@intCast(i7, num_digits) - decimal_places) catch {
std.debug.panic("TODO runtime exception for overflow when getting abs", .{});
});
before_digits_slice = "0";
}
// Figure out how many trailing zeros there are
// I tried to use https://ziglang.org/documentation/0.8.0/#ctz and it mostly worked,
// but was giving seemingly incorrect values for certain numbers. So instead we use
// a while loop and figure it out that way.
//
// const trailing_zeros = @ctz(u6, num);
//
var trailing_zeros: u6 = 0;
var index = decimal_places - 1 - before_digits_adjust;
var is_consecutive_zero = true;
while (index != 0) {
var digit = digit_bytes[before_digits_offset + index];
if (digit == '0' and is_consecutive_zero) {
trailing_zeros += 1;
} else {
is_consecutive_zero = false;
}
index -= 1;
}
// Figure out if we need to prepend any zeros to the after decimal point
// For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point
// This will only be needed for numbers less 0.01, otherwise after_digits_slice will handle this
const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0;
const after_zeros_slice: []const u8 = leading_zeros[0..after_zeros_num];
// Get the slice for after the decimal point
var after_digits_slice: []const u8 = undefined;
if ((num_digits - before_digits_offset) == trailing_zeros) {
after_digits_slice = "0";
} else {
after_digits_slice = digit_bytes[before_digits_offset .. num_digits - trailing_zeros];
}
// Get the slice for the sign
const sign_slice: []const u8 = if (is_negative) "-" else leading_zeros[0..0];
// Hardcode adding a `1` for the '.' character
const str_len: usize = sign_slice.len + before_digits_slice.len + 1 + after_zeros_slice.len + after_digits_slice.len;
// Join the slices together
// We do `max_digits + 2` here because we need to account for a possible sign ('-') and the dot ('.').
// Ideally, we'd use str_len here
var str_bytes: [max_digits + 2]u8 = undefined;
_ = std.fmt.bufPrint(str_bytes[0..str_len], "{s}{s}.{s}{s}", .{ sign_slice, before_digits_slice, after_zeros_slice, after_digits_slice }) catch {
std.debug.panic("TODO runtime exception failing to print slices", .{});
};
return RocStr.init(&str_bytes, str_len);
}
pub fn negate(self: RocDec) ?RocDec {
var negated = math.negate(self.num) catch null;
return if (negated) |n| .{ .num = n } else null;
}
pub fn add(self: RocDec, other: RocDec) RocDec {
var answer: i128 = undefined;
@ -23,6 +206,17 @@ pub const RocDec = struct {
}
}
pub fn sub(self: RocDec, other: RocDec) RocDec {
var answer: i128 = undefined;
const overflowed = @subWithOverflow(i128, self.num, other.num, &answer);
if (!overflowed) {
return RocDec{ .num = answer };
} else {
std.debug.panic("TODO runtime exception for overflow!", .{});
}
}
pub fn mul(self: RocDec, other: RocDec) RocDec {
const self_i128 = self.num;
const other_i128 = other.num;
@ -58,6 +252,76 @@ pub const RocDec = struct {
return .{ .num = unsigned_answer };
}
}
pub fn div(self: RocDec, other: RocDec) RocDec {
const numerator_i128 = self.num;
const denominator_i128 = other.num;
// (0 / n) is always 0
if (numerator_i128 == 0) {
return RocDec{ .num = 0 };
}
// (n / 0) is an error
if (denominator_i128 == 0) {
std.debug.panic("TODO runtime exception for divide by 0!", .{});
}
// If they're both negative, or if neither is negative, the final answer
// is positive or zero. If one is negative and the denominator isn't, the
// final answer is negative (or zero, in which case final sign won't matter).
//
// It's important that we do this in terms of negatives, because doing
// it in terms of positives can cause bugs when one is zero.
const is_answer_negative = (numerator_i128 < 0) != (denominator_i128 < 0);
// Break the two i128s into two { hi: u64, lo: u64 } tuples, discarding
// the sign for now.
//
// We'll multiply all 4 combinations of these (hi1 x lo1, hi2 x lo2,
// hi1 x lo2, hi2 x lo1) and add them as appropriate, then apply the
// appropriate sign at the very end.
//
// We do checked_abs because if we had -i128::MAX before, this will overflow.
const numerator_abs_i128 = math.absInt(numerator_i128) catch {
// Currently, if you try to do multiplication on i64::MIN, panic
// unless you're specifically multiplying by 0 or 1.
//
// Maybe we could support more cases in the future
if (denominator_i128 == one_point_zero_i128) {
return self;
} else {
std.debug.panic("TODO runtime exception for overflow when dividing!", .{});
}
};
const numerator_u128 = @intCast(u128, numerator_abs_i128);
const denominator_abs_i128 = math.absInt(denominator_i128) catch {
// Currently, if you try to do multiplication on i64::MIN, panic
// unless you're specifically multiplying by 0 or 1.
//
// Maybe we could support more cases in the future
if (numerator_i128 == one_point_zero_i128) {
return other;
} else {
std.debug.panic("TODO runtime exception for overflow when dividing!", .{});
}
};
const denominator_u128 = @intCast(u128, denominator_abs_i128);
const numerator_u256: U256 = mul_u128(numerator_u128, math.pow(u128, 10, decimal_places));
const answer = div_u256_by_u128(numerator_u256, denominator_u128);
var unsigned_answer: i128 = undefined;
if (answer.hi == 0 and answer.lo <= math.maxInt(i128)) {
unsigned_answer = @intCast(i128, answer.lo);
} else {
std.debug.panic("TODO runtime exception for overflow when dividing!", .{});
}
return RocDec{ .num = if (is_answer_negative) -unsigned_answer else unsigned_answer };
}
};
const U256 = struct {
@ -75,7 +339,7 @@ fn mul_and_decimalize(a: u128, b: u128) i128 {
// floor(2^315/10^18) is 66749594872528440074844428317798503581334516323645399060845050244444366430645
// Add 1.
// This can't overflow because the intial numbers are only 127bit due to removing the sign bit.
// This can't overflow because the initial numbers are only 127bit due to removing the sign bit.
var overflowed = @addWithOverflow(u128, lhs_lo, 1, &lhs_lo);
lhs_hi = blk: {
if (overflowed) {
@ -98,7 +362,7 @@ fn mul_and_decimalize(a: u128, b: u128) i128 {
const lk = mul_u128(lhs_hi, rhs_hi);
const e = ea.hi;
const _a = ea.lo;
// const _a = ea.lo;
const g = gf.hi;
const f = gf.lo;
@ -216,18 +480,476 @@ fn mul_u128(a: u128, b: u128) U256 {
return .{ .hi = hi, .lo = lo };
}
const one_e20: i256 = 100000000000000000000;
// Multiply two 128-bit ints and divide the result by 10^DECIMAL_PLACES
//
// Adapted from https://github.com/nlordell/ethnum-rs
// Copyright (c) 2020 Nicholas Rodrigues Lordello
// Licensed under the Apache License version 2.0
//
// When translating this to Zig, we often have to use math.shr/shl instead of >>/<<
// This is because casting to the right types for Zig can be kind of tricky.
// See https://github.com/ziglang/zig/issues/7605
fn div_u256_by_u128(numer: U256, denom: u128) U256 {
const N_UDWORD_BITS: u8 = 128;
const N_UTWORD_BITS: u9 = 256;
const expectEqual = std.testing.expectEqual;
var q: U256 = undefined;
var r: U256 = undefined;
var sr: u8 = undefined;
test "add" {
const dec: RocDec = .{ .num = 0 };
// special case
if (numer.hi == 0) {
// 0 X
// ---
// 0 X
return .{
.hi = 0,
.lo = numer.lo / denom,
};
}
expectEqual(RocDec{ .num = 0 }, dec.add(.{ .num = 0 }));
// numer.hi != 0
if (denom == 0) {
// K X
// ---
// 0 0
return .{
.hi = 0,
.lo = numer.hi / denom,
};
} else {
// K X
// ---
// 0 K
// NOTE: Modified from `if (d.low() & (d.low() - 1)) == 0`.
if (math.isPowerOfTwo(denom)) {
// if d is a power of 2
if (denom == 1) {
return numer;
}
sr = @ctz(u128, denom);
return .{
.hi = math.shr(u128, numer.hi, sr),
.lo = math.shl(u128, numer.hi, N_UDWORD_BITS - sr) | math.shr(u128, numer.lo, sr),
};
}
// K X
// ---
// 0 K
var denom_leading_zeros = @clz(u128, denom);
var numer_hi_leading_zeros = @clz(u128, numer.hi);
sr = 1 + N_UDWORD_BITS + denom_leading_zeros - numer_hi_leading_zeros;
// 2 <= sr <= N_UTWORD_BITS - 1
// q.all = n.all << (N_UTWORD_BITS - sr);
// r.all = n.all >> sr;
// #[allow(clippy::comparison_chain)]
if (sr == N_UDWORD_BITS) {
q = .{
.hi = numer.lo,
.lo = 0,
};
r = .{
.hi = 0,
.lo = numer.hi,
};
} else if (sr < N_UDWORD_BITS) {
// 2 <= sr <= N_UDWORD_BITS - 1
q = .{
.hi = math.shl(u128, numer.lo, N_UDWORD_BITS - sr),
.lo = 0,
};
r = .{
.hi = math.shr(u128, numer.hi, sr),
.lo = math.shl(u128, numer.hi, N_UDWORD_BITS - sr) | math.shr(u128, numer.lo, sr),
};
} else {
// N_UDWORD_BITS + 1 <= sr <= N_UTWORD_BITS - 1
q = .{
.hi = math.shl(u128, numer.hi, N_UTWORD_BITS - sr) | math.shr(u128, numer.lo, sr - N_UDWORD_BITS),
.lo = math.shl(u128, numer.lo, N_UTWORD_BITS - sr),
};
r = .{
.hi = 0,
.lo = math.shr(u128, numer.hi, sr - N_UDWORD_BITS),
};
}
}
// Not a special case
// q and r are initialized with:
// q.all = n.all << (N_UTWORD_BITS - sr);
// r.all = n.all >> sr;
// 1 <= sr <= N_UTWORD_BITS - 1
var carry: u128 = 0;
while (sr > 0) {
// r:q = ((r:q) << 1) | carry
r.hi = (r.hi << 1) | (r.lo >> (N_UDWORD_BITS - 1));
r.lo = (r.lo << 1) | (q.hi >> (N_UDWORD_BITS - 1));
q.hi = (q.hi << 1) | (q.lo >> (N_UDWORD_BITS - 1));
q.lo = (q.lo << 1) | carry;
// carry = 0;
// if (r.all >= d.all)
// {
// r.all -= d.all;
// carry = 1;
// }
// NOTE: Modified from `(d - r - 1) >> (N_UTWORD_BITS - 1)` to be an
// **arithmetic** shift.
var lo: u128 = undefined;
var lo_overflowed: bool = undefined;
var hi: u128 = undefined;
lo_overflowed = @subWithOverflow(u128, denom, r.lo, &lo);
hi = 0 -% @intCast(u128, @bitCast(u1, lo_overflowed)) -% r.hi;
lo_overflowed = @subWithOverflow(u128, lo, 1, &lo);
hi = hi -% @intCast(u128, @bitCast(u1, lo_overflowed));
// TODO this U256 was originally created by:
//
// ((hi as i128) >> 127).as_u256()
//
// ...however, I can't figure out where that function is defined.
// Maybe it's defined using a macro or something. Anyway, hopefully
// this is what it would do in this scenario.
var s = .{
.hi = 0,
.lo = math.shr(u128, hi, 127),
};
carry = s.lo & 1;
// var (lo, carry) = r.lo.overflowing_sub(denom & s.lo);
lo_overflowed = @subWithOverflow(u128, r.lo, (denom & s.lo), &lo);
hi = r.hi -% @intCast(u128, @bitCast(u1, lo_overflowed));
r = .{ .hi = hi, .lo = lo };
sr -= 1;
}
var hi = (q.hi << 1) | (q.lo >> (127));
var lo = (q.lo << 1) | carry;
return .{ .hi = hi, .lo = lo };
}
test "mul" {
var dec1: RocDec = .{ .num = 0 };
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expectError = testing.expectError;
const expectEqualSlices = testing.expectEqualSlices;
const expect = testing.expect;
expectEqual(RocDec{ .num = 0 }, dec1.mul(.{ .num = 0 }));
test "fromU64" {
var dec = RocDec.fromU64(25);
try expectEqual(RocDec{ .num = 25000000000000000000 }, dec);
}
test "fromStr: empty" {
var roc_str = RocStr.init("", 0);
var dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "fromStr: 0" {
var roc_str = RocStr.init("0", 1);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 0 }, dec.?);
}
test "fromStr: 1" {
var roc_str = RocStr.init("1", 1);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec.one_point_zero, dec.?);
}
test "fromStr: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 123450000000000000000 }, dec.?);
}
test "fromStr: .45" {
var roc_str = RocStr.init(".45", 3);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.?);
}
test "fromStr: 0.45" {
var roc_str = RocStr.init("0.45", 4);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.?);
}
test "fromStr: 123" {
var roc_str = RocStr.init("123", 3);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.?);
}
test "fromStr: -.45" {
var roc_str = RocStr.init("-.45", 4);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.?);
}
test "fromStr: -0.45" {
var roc_str = RocStr.init("-0.45", 5);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.?);
}
test "fromStr: -123" {
var roc_str = RocStr.init("-123", 4);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.?);
}
test "fromStr: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = -123450000000000000000 }, dec.?);
}
test "fromStr: abc" {
var roc_str = RocStr.init("abc", 3);
var dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "fromStr: 123.abc" {
var roc_str = RocStr.init("123.abc", 7);
var dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "fromStr: abc.123" {
var roc_str = RocStr.init("abc.123", 7);
var dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "fromStr: .123.1" {
var roc_str = RocStr.init(".123.1", 6);
var dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "toStr: 123.45" {
var dec: RocDec = .{ .num = 123450000000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "123.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: -123.45" {
var dec: RocDec = .{ .num = -123450000000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-123.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 123.0" {
var dec: RocDec = .{ .num = 123000000000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "123.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: -123.0" {
var dec: RocDec = .{ .num = -123000000000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-123.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 0.45" {
var dec: RocDec = .{ .num = 450000000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "0.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: -0.45" {
var dec: RocDec = .{ .num = -450000000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-0.45"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 0.00045" {
var dec: RocDec = .{ .num = 000450000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "0.00045"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: -0.00045" {
var dec: RocDec = .{ .num = -000450000000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-0.00045"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: -111.123456789" {
var dec: RocDec = .{ .num = -111123456789000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "-111.123456789"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 123.1111111" {
var dec: RocDec = .{ .num = 123111111100000000000 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "123.1111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 123.1111111111111 (big str)" {
var dec: RocDec = .{ .num = 123111111111111000000 };
var res_roc_str = dec.toStr();
errdefer res_roc_str.?.deinit();
defer res_roc_str.?.deinit();
const res_slice: []const u8 = "123.111111111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 123.111111111111444444 (max number of decimal places)" {
var dec: RocDec = .{ .num = 123111111111111444444 };
var res_roc_str = dec.toStr();
errdefer res_roc_str.?.deinit();
defer res_roc_str.?.deinit();
const res_slice: []const u8 = "123.111111111111444444"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
var dec: RocDec = .{ .num = 12345678912345678912111111111111111111 };
var res_roc_str = dec.toStr();
errdefer res_roc_str.?.deinit();
defer res_roc_str.?.deinit();
const res_slice: []const u8 = "12345678912345678912.111111111111111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 0" {
var dec: RocDec = .{ .num = 0 };
var res_roc_str = dec.toStr();
const res_slice: []const u8 = "0.0"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "add: 0" {
var dec: RocDec = .{ .num = 0 };
try expectEqual(RocDec{ .num = 0 }, dec.add(.{ .num = 0 }));
}
test "add: 1" {
var dec: RocDec = .{ .num = 0 };
try expectEqual(RocDec{ .num = 1 }, dec.add(.{ .num = 1 }));
}
test "sub: 0" {
var dec: RocDec = .{ .num = 1 };
try expectEqual(RocDec{ .num = 1 }, dec.sub(.{ .num = 0 }));
}
test "sub: 1" {
var dec: RocDec = .{ .num = 1 };
try expectEqual(RocDec{ .num = 0 }, dec.sub(.{ .num = 1 }));
}
test "mul: by 0" {
var dec: RocDec = .{ .num = 0 };
try expectEqual(RocDec{ .num = 0 }, dec.mul(.{ .num = 0 }));
}
test "mul: by 1" {
var dec: RocDec = RocDec.fromU64(15);
try expectEqual(RocDec.fromU64(15), dec.mul(RocDec.fromU64(1)));
}
test "mul: by 2" {
var dec: RocDec = RocDec.fromU64(15);
try expectEqual(RocDec.fromU64(30), dec.mul(RocDec.fromU64(2)));
}
test "div: 0 / 2" {
var dec: RocDec = RocDec.fromU64(0);
try expectEqual(RocDec.fromU64(0), dec.div(RocDec.fromU64(2)));
}
test "div: 2 / 2" {
var dec: RocDec = RocDec.fromU64(2);
try expectEqual(RocDec.fromU64(1), dec.div(RocDec.fromU64(2)));
}
test "div: 20 / 2" {
var dec: RocDec = RocDec.fromU64(20);
try expectEqual(RocDec.fromU64(10), dec.div(RocDec.fromU64(2)));
}
test "div: 8 / 5" {
var dec: RocDec = RocDec.fromU64(8);
var res: RocDec = RocDec.fromStr(RocStr.init("1.6", 3)).?;
try expectEqual(res, dec.div(RocDec.fromU64(5)));
}
test "div: 10 / 3" {
var numer: RocDec = RocDec.fromU64(10);
var denom: RocDec = RocDec.fromU64(3);
var roc_str = RocStr.init("3.333333333333333333", 20);
errdefer roc_str.deinit();
defer roc_str.deinit();
var res: RocDec = RocDec.fromStr(roc_str).?;
try expectEqual(res, numer.div(denom));
}

View File

@ -20,13 +20,9 @@ const Slot = packed enum(u8) {
PreviouslyFilled,
};
const MaybeIndexTag = enum {
index, not_found
};
const MaybeIndexTag = enum { index, not_found };
const MaybeIndex = union(MaybeIndexTag) {
index: usize, not_found: void
};
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?
@ -572,14 +568,6 @@ pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_wid
const data_bytes = length * key_width;
var ptr = allocateWithRefcount(data_bytes, alignment);
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) {
@ -621,14 +609,6 @@ pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_w
const data_bytes = length * value_width;
var ptr = allocateWithRefcount(data_bytes, alignment);
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) {
@ -648,7 +628,7 @@ pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_w
output.* = RocList{ .bytes = ptr, .length = length };
}
fn doNothing(ptr: Opaque) callconv(.C) void {
fn doNothing(_: Opaque) callconv(.C) void {
return;
}
@ -768,8 +748,6 @@ pub fn dictWalk(
key_width: usize,
value_width: usize,
accum_width: usize,
inc_key: Inc,
inc_value: Inc,
output: Opaque,
) callconv(.C) void {
const alignment_u32 = alignment.toU32();
@ -795,9 +773,7 @@ pub fn dictWalk(
caller(data, key, value, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
std.mem.swap([*]u8, &b1, &b2);
},
else => {},
}

View File

@ -8,10 +8,9 @@ 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]);
const slice = nonnull[0..length];
return wyhash_hash(seed, slice);
} else {
return 42;
}
@ -202,13 +201,13 @@ 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);
try expectEqual(hash(0, ""), 0x0);
try expectEqual(hash(1, "a"), 0xbed235177f41d328);
try expectEqual(hash(2, "abc"), 0xbe348debe59b27c3);
try expectEqual(hash(3, "message digest"), 0x37320f657213a290);
try expectEqual(hash(4, "abcdefghijklmnopqrstuvwxyz"), 0xd0b270e1d8a7019c);
try expectEqual(hash(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x602a1894d3bbfe7f);
try expectEqual(hash(6, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x829e9c148b75970e);
}
test "test vectors streaming" {
@ -216,19 +215,19 @@ test "test vectors streaming" {
for ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") |e| {
wh.update(mem.asBytes(&e));
}
expectEqual(wh.final(), 0x602a1894d3bbfe7f);
try expectEqual(wh.final(), 0x602a1894d3bbfe7f);
const pattern = "1234567890";
const count = 8;
const result = 0x829e9c148b75970e;
expectEqual(Wyhash.hash(6, pattern ** 8), result);
try 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);
try expectEqual(wh.final(), result);
}
test "iterative non-divisible update" {
@ -250,6 +249,6 @@ test "iterative non-divisible update" {
}
const iterative_hash = wy.final();
std.testing.expectEqual(iterative_hash, non_iterative_hash);
try std.testing.expectEqual(iterative_hash, non_iterative_hash);
}
}

View File

@ -42,7 +42,7 @@ pub const BoundClass = enum(u8) {
};
test "Bound Class" {
expectEqual(0, @enumToInt(BoundClass.START));
try expectEqual(0, @enumToInt(BoundClass.START));
}
// https://github.com/JuliaStrings/utf8proc/blob/master/utf8proc.c#L261
@ -112,7 +112,7 @@ fn unsafeCodepointToBoundClass(codepoint: u21) *const BoundClass {
}
test "unsafeCodepointToBoundClass: valid" {
expectEqual(BoundClass.CONTROL, unsafeCodepointToBoundClass(8).*);
try expectEqual(BoundClass.CONTROL, unsafeCodepointToBoundClass(8).*);
}
// https://github.com/JuliaStrings/utf8proc/blob/master/utf8proc.c#L242
@ -125,11 +125,11 @@ fn codepointToBoundClass(codepoint: u21) *const BoundClass {
}
test "codepointToBoundClass: valid" {
expectEqual(BoundClass.CONTROL, codepointToBoundClass(8).*);
try expectEqual(BoundClass.CONTROL, codepointToBoundClass(8).*);
}
test "codepointToBoundClass: invalid" {
expectEqual(BoundClass.OTHER, codepointToBoundClass(codepoint_max + 5).*);
try expectEqual(BoundClass.OTHER, codepointToBoundClass(codepoint_max + 5).*);
}
// https://github.com/JuliaStrings/utf8proc/blob/master/utf8proc.c#L319

View File

@ -12,6 +12,7 @@ const Opaque = ?[*]u8;
const Inc = fn (?[*]u8) callconv(.C) void;
const IncN = fn (?[*]u8, usize) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
const HasTagId = fn (u16, ?[*]u8) callconv(.C) extern struct { matched: bool, data: ?[*]u8 };
pub const RocList = extern struct {
bytes: ?[*]u8,
@ -405,11 +406,14 @@ pub fn listKeepOks(
before_width: usize,
result_width: usize,
after_width: usize,
has_tag_id: HasTagId,
dec_result: Dec,
) callconv(.C) RocList {
const good_constructor: u16 = 1;
return listKeepResult(
list,
RocResult.isOk,
good_constructor,
caller,
data,
inc_n_data,
@ -418,6 +422,7 @@ pub fn listKeepOks(
before_width,
result_width,
after_width,
has_tag_id,
dec_result,
);
}
@ -432,11 +437,14 @@ pub fn listKeepErrs(
before_width: usize,
result_width: usize,
after_width: usize,
has_tag_id: HasTagId,
dec_result: Dec,
) callconv(.C) RocList {
const good_constructor: u16 = 0;
return listKeepResult(
list,
RocResult.isErr,
good_constructor,
caller,
data,
inc_n_data,
@ -445,13 +453,14 @@ pub fn listKeepErrs(
before_width,
result_width,
after_width,
has_tag_id,
dec_result,
);
}
pub fn listKeepResult(
list: RocList,
is_good_constructor: fn (RocResult) bool,
good_constructor: u16,
caller: Caller1,
data: Opaque,
inc_n_data: IncN,
@ -460,6 +469,7 @@ pub fn listKeepResult(
before_width: usize,
result_width: usize,
after_width: usize,
has_tag_id: HasTagId,
dec_result: Dec,
) RocList {
if (list.bytes) |source_ptr| {
@ -479,11 +489,14 @@ pub fn listKeepResult(
const before_element = source_ptr + (i * before_width);
caller(data, 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);
// a record { matched: bool, data: ?[*]u8 }
// for now, that data pointer is just the input `temporary` pointer
// this will change in the future to only return a pointer to the
// payload of the tag
const answer = has_tag_id(good_constructor, temporary);
if (answer.matched) {
const contents = (answer.data orelse unreachable);
@memcpy(target_ptr + (kept * after_width), contents, after_width);
kept += 1;
} else {
dec_result(temporary);
@ -542,9 +555,7 @@ pub fn listWalk(
const element = source_ptr + i * element_width;
caller(data, element, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
std.mem.swap([*]u8, &b1, &b2);
}
}
@ -591,9 +602,7 @@ pub fn listWalkBackwards(
const element = source_ptr + i * element_width;
caller(data, element, b2, b1);
const temp = b1;
b2 = b1;
b1 = temp;
std.mem.swap([*]u8, &b1, &b2);
}
}
@ -610,12 +619,13 @@ pub fn listWalkUntil(
accum: Opaque,
alignment: u32,
element_width: usize,
continue_stop_width: usize,
accum_width: usize,
has_tag_id: HasTagId,
dec: Dec,
output: Opaque,
) callconv(.C) void {
// [ Continue a, Stop a ]
const CONTINUE: usize = 0;
if (accum_width == 0) {
return;
@ -626,9 +636,10 @@ pub fn listWalkUntil(
return;
}
const bytes_ptr: [*]u8 = utils.alloc(TAG_WIDTH + accum_width, alignment);
const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment);
@memcpy(bytes_ptr + TAG_WIDTH, accum orelse unreachable, accum_width);
// NOTE: assumes data bytes are the first bytes in a tag
@memcpy(bytes_ptr, accum orelse unreachable, accum_width);
if (list.bytes) |source_ptr| {
var i: usize = 0;
@ -640,10 +651,12 @@ pub fn listWalkUntil(
inc_n_data(data, 1);
}
caller(data, element, bytes_ptr + TAG_WIDTH, bytes_ptr);
caller(data, element, bytes_ptr, bytes_ptr);
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes_ptr));
if (usizes[0] != 0) {
// [ Continue ..., Stop ]
const tag_id = has_tag_id(0, bytes_ptr);
if (!tag_id.matched) {
// decrement refcount of the remaining items
i += 1;
while (i < size) : (i += 1) {
@ -654,7 +667,7 @@ pub fn listWalkUntil(
}
}
@memcpy(output orelse unreachable, bytes_ptr + TAG_WIDTH, accum_width);
@memcpy(output orelse unreachable, bytes_ptr, accum_width);
utils.dealloc(bytes_ptr, alignment);
}
@ -722,6 +735,28 @@ pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width:
return output;
}
pub fn listSwap(
list: RocList,
alignment: u32,
element_width: usize,
index_1: usize,
index_2: usize,
) callconv(.C) RocList {
const size = list.len();
if (index_1 >= size or index_2 >= size) {
// Either index out of bounds so we just return
return list;
}
const newList = list.makeUnique(alignment, element_width);
if (newList.bytes) |source_ptr| {
swapElements(source_ptr, element_width, index_1, index_2);
}
return newList;
}
pub fn listDrop(
list: RocList,
alignment: u32,
@ -759,43 +794,19 @@ pub fn listDrop(
}
pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList {
const IntWidth = utils.IntWidth;
switch (width) {
IntWidth.U8 => {
return helper1(u8, low, high);
},
IntWidth.U16 => {
return helper1(u16, low, high);
},
IntWidth.U32 => {
return helper1(u32, low, high);
},
IntWidth.U64 => {
return helper1(u64, low, high);
},
IntWidth.U128 => {
return helper1(u128, low, high);
},
IntWidth.I8 => {
return helper1(i8, low, high);
},
IntWidth.I16 => {
return helper1(i16, low, high);
},
IntWidth.I32 => {
return helper1(i32, low, high);
},
IntWidth.I64 => {
return helper1(i64, low, high);
},
IntWidth.I128 => {
return helper1(i128, low, high);
},
IntWidth.Usize => {
return helper1(usize, low, high);
},
}
return switch (width) {
.U8 => helper1(u8, low, high),
.U16 => helper1(u16, low, high),
.U32 => helper1(u32, low, high),
.U64 => helper1(u64, low, high),
.U128 => helper1(u128, low, high),
.I8 => helper1(i8, low, high),
.I16 => helper1(i16, low, high),
.I32 => helper1(i32, low, high),
.I64 => helper1(i64, low, high),
.I128 => helper1(i128, low, high),
.Usize => helper1(usize, low, high),
};
}
fn helper1(comptime T: type, low: Opaque, high: Opaque) RocList {
@ -910,7 +921,7 @@ inline fn swapHelp(width: usize, temporary: [*]u8, ptr1: [*]u8, ptr2: [*]u8) voi
}
fn swap(width_initial: usize, p1: [*]u8, p2: [*]u8) void {
const threshold: comptime usize = 64;
const threshold: usize = 64;
var width = width_initial;
@ -936,11 +947,6 @@ fn swap(width_initial: usize, p1: [*]u8, p2: [*]u8) void {
}
fn swapElements(source_ptr: [*]u8, element_width: usize, index_1: usize, index_2: usize) void {
const threshold: comptime usize = 64;
var buffer_actual: [threshold]u8 = undefined;
var buffer: [*]u8 = buffer_actual[0..];
var element_at_i = source_ptr + (index_1 * element_width);
var element_at_j = source_ptr + (index_2 * element_width);
@ -1015,7 +1021,23 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
return output;
}
// input: RocList,
pub fn listSetInPlace(
bytes: ?[*]u8,
index: usize,
element: Opaque,
element_width: usize,
dec: Dec,
) callconv(.C) ?[*]u8 {
// INVARIANT: bounds checking happens on the roc side
//
// at the time of writing, the function is implemented roughly as
// `if inBounds then LowLevelListGet input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
}
pub fn listSet(
bytes: ?[*]u8,
length: usize,
@ -1034,23 +1056,32 @@ pub fn listSet(
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes));
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
// the element we will replace
var element_at_index = (bytes orelse undefined) + (index * element_width);
// decrement its refcount
dec(element_at_index);
// copy in the new element
@memcpy(element_at_index, element orelse undefined, element_width);
return bytes;
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
} else {
return listSetClone(bytes, length, alignment, index, element, element_width, dec);
return listSetImmutable(bytes, length, alignment, index, element, element_width, dec);
}
}
inline fn listSetClone(
inline fn listSetInPlaceHelp(
bytes: ?[*]u8,
index: usize,
element: Opaque,
element_width: usize,
dec: Dec,
) ?[*]u8 {
// the element we will replace
var element_at_index = (bytes orelse undefined) + (index * element_width);
// decrement its refcount
dec(element_at_index);
// copy in the new element
@memcpy(element_at_index, element orelse undefined, element_width);
return bytes;
}
inline fn listSetImmutable(
old_bytes: ?[*]u8,
length: usize,
alignment: u32,
@ -1059,8 +1090,6 @@ inline fn listSetClone(
element_width: usize,
dec: Dec,
) ?[*]u8 {
@setCold(true);
const data_bytes = length * element_width;
var new_bytes = utils.allocateWithRefcount(data_bytes, alignment);

View File

@ -30,6 +30,8 @@ comptime {
exportListFn(list.listConcat, "concat");
exportListFn(list.listDrop, "drop");
exportListFn(list.listSet, "set");
exportListFn(list.listSetInPlace, "set_in_place");
exportListFn(list.listSwap, "swap");
}
// Dict Module

View File

@ -17,7 +17,7 @@ const InPlace = packed enum(u8) {
const SMALL_STR_MAX_LENGTH = small_string_size - 1;
const small_string_size = 2 * @sizeOf(usize);
const blank_small_string: [16]u8 = init_blank_small_string(small_string_size);
const blank_small_string: [@sizeOf(RocStr)]u8 = init_blank_small_string(small_string_size);
fn init_blank_small_string(comptime n: usize) [n]u8 {
var prime_list: [n]u8 = undefined;
@ -85,12 +85,6 @@ pub const RocStr = extern struct {
}
}
pub fn toSlice(self: RocStr) []u8 {
const str_bytes_ptr: [*]u8 = self.str_bytes orelse unreachable;
const str_bytes: []u8 = str_bytes_ptr[0..self.str_len];
return str_bytes;
}
// This takes ownership of the pointed-to bytes if they won't fit in a
// small string, and returns a (pointer, len) tuple which points to them.
pub fn withCapacity(length: usize) RocStr {
@ -203,8 +197,8 @@ pub const RocStr = extern struct {
return result;
}
// NOTE: returns false for empty string!
pub fn isSmallStr(self: RocStr) bool {
// NOTE: returns False for empty string!
return @bitCast(isize, self.str_len) < 0;
}
@ -223,6 +217,82 @@ pub const RocStr = extern struct {
return self.len() == 0;
}
// If a string happens to be null-terminated already, then we can pass its
// bytes directly to functions (e.g. for opening files) that require
// null-terminated strings. Otherwise, we need to allocate and copy a new
// null-terminated string, which has a much higher performance cost!
fn isNullTerminated(self: RocStr) bool {
const len = self.len();
const longest_small_str = @sizeOf(RocStr) - 1;
// NOTE: We want to compare length here, *NOT* check for is_small_str!
// This is because we explicitly want the empty string to be handled in
// this branch, even though the empty string is not a small string.
//
// (The other branch dereferences the bytes pointer, which is not safe
// to do for the empty string.)
if (len <= longest_small_str) {
// If we're a small string, then usually the next byte after the
// end of the string will be zero. (Small strings set all their
// unused bytes to 0, so that comparison for equality can be fast.)
//
// However, empty strings are *not* null terminated, so if this is
// empty, it should return false.
//
// Also, if we are exactly a maximum-length small string,
// then the next byte is off the end of the struct;
// in that case, we are also not null-terminated!
return len != 0 and len != longest_small_str;
} else {
// This is a big string, and it's not empty, so we can safely
// dereference the pointer.
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes));
const capacity_or_refcount: isize = (ptr - 1)[0];
// If capacity_or_refcount is positive, then it's a capacity value.
//
// If we have excess capacity, then we can safely read the next
// byte after the end of the string. Maybe it happens to be zero!
if (capacity_or_refcount > @intCast(isize, len)) {
return self.str_bytes[len] == 0;
} else {
// This string was refcounted or immortal; we can't safely read
// the next byte, so assume the string is not null-terminated.
return false;
}
}
}
// Returns (@sizeOf(RocStr) - 1) for small strings and the empty string.
// Returns 0 for refcounted stirngs and immortal strings.
// Returns the stored capacity value for all other strings.
pub fn capacity(self: RocStr) usize {
const len = self.len();
const longest_small_str = @sizeOf(RocStr) - 1;
if (len <= longest_small_str) {
// Note that although empty strings technically have the full
// capacity of a small string available, they aren't marked as small
// strings, so if you want to make use of that capacity, you need
// to first change its flag to mark it as a small string!
return longest_small_str;
} else {
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes));
const capacity_or_refcount: isize = (ptr - 1)[0];
if (capacity_or_refcount > 0) {
// If capacity_or_refcount is positive, that means it's a
// capacity value.
return capacity_or_refcount;
} else {
// This is either a refcount or else this big string is stored
// in a readonly section; either way, it has no capacity,
// because we cannot mutate it in-place!
return 0;
}
}
}
pub fn isUnique(self: RocStr) bool {
// the empty list is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
@ -240,15 +310,13 @@ pub const RocStr = extern struct {
}
pub fn asSlice(self: RocStr) []u8 {
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
return self.asU8ptr()[0..self.len()];
}
pub fn asU8ptr(self: RocStr) [*]u8 {
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
return if (self.isSmallStr() or self.isEmpty()) (&@bitCast([16]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
return if (self.isSmallStr() or self.isEmpty()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
}
// Given a pointer to some bytes, write the first (len) bytes of this
@ -273,7 +341,7 @@ pub const RocStr = extern struct {
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(roc_str1.eq(roc_str2));
try expect(roc_str1.eq(roc_str2));
roc_str1.deinit();
roc_str2.deinit();
@ -295,7 +363,7 @@ pub const RocStr = extern struct {
roc_str2.deinit();
}
expect(!roc_str1.eq(roc_str2));
try expect(!roc_str1.eq(roc_str2));
}
test "RocStr.eq: not equal same length" {
@ -314,7 +382,7 @@ pub const RocStr = extern struct {
roc_str2.deinit();
}
expect(!roc_str1.eq(roc_str2));
try expect(!roc_str1.eq(roc_str2));
}
};
@ -449,8 +517,8 @@ test "strSplitInPlace: no delimiter" {
delimiter.deinit();
}
expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0]));
try expectEqual(array.len, expected.len);
try expect(array[0].eq(expected[0]));
}
test "strSplitInPlace: empty end" {
@ -490,10 +558,10 @@ test "strSplitInPlace: empty end" {
delimiter.deinit();
}
expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0]));
expect(array[1].eq(expected[1]));
expect(array[2].eq(expected[2]));
try expectEqual(array.len, expected.len);
try expect(array[0].eq(expected[0]));
try expect(array[1].eq(expected[1]));
try expect(array[2].eq(expected[2]));
}
test "strSplitInPlace: delimiter on sides" {
@ -532,10 +600,10 @@ test "strSplitInPlace: delimiter on sides" {
delimiter.deinit();
}
expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0]));
expect(array[1].eq(expected[1]));
expect(array[2].eq(expected[2]));
try expectEqual(array.len, expected.len);
try expect(array[0].eq(expected[0]));
try expect(array[1].eq(expected[1]));
try expect(array[2].eq(expected[2]));
}
test "strSplitInPlace: three pieces" {
@ -573,10 +641,10 @@ test "strSplitInPlace: three pieces" {
delimiter.deinit();
}
expectEqual(expected_array.len, array.len);
expect(array[0].eq(expected_array[0]));
expect(array[1].eq(expected_array[1]));
expect(array[2].eq(expected_array[2]));
try expectEqual(expected_array.len, array.len);
try expect(array[0].eq(expected_array[0]));
try expect(array[1].eq(expected_array[1]));
try expect(array[2].eq(expected_array[2]));
}
// This is used for `Str.split : Str, Str -> Array Str
@ -639,7 +707,7 @@ test "countSegments: long delimiter" {
}
const segments_count = countSegments(str, delimiter);
expectEqual(segments_count, 1);
try expectEqual(segments_count, 1);
}
test "countSegments: delimiter at start" {
@ -658,7 +726,7 @@ test "countSegments: delimiter at start" {
const segments_count = countSegments(str, delimiter);
expectEqual(segments_count, 2);
try expectEqual(segments_count, 2);
}
test "countSegments: delimiter interspered" {
@ -677,7 +745,7 @@ test "countSegments: delimiter interspered" {
const segments_count = countSegments(str, delimiter);
expectEqual(segments_count, 3);
try expectEqual(segments_count, 3);
}
// Str.countGraphemeClusters
@ -721,7 +789,7 @@ fn rocStrFromLiteral(bytes_arr: *const []u8) RocStr {}
test "countGraphemeClusters: empty string" {
const count = countGraphemeClusters(RocStr.empty());
expectEqual(count, 0);
try expectEqual(count, 0);
}
test "countGraphemeClusters: ascii characters" {
@ -731,7 +799,7 @@ test "countGraphemeClusters: ascii characters" {
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 4);
try expectEqual(count, 4);
}
test "countGraphemeClusters: utf8 characters" {
@ -741,7 +809,7 @@ test "countGraphemeClusters: utf8 characters" {
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 3);
try expectEqual(count, 3);
}
test "countGraphemeClusters: emojis" {
@ -751,7 +819,7 @@ test "countGraphemeClusters: emojis" {
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 3);
try expectEqual(count, 3);
}
test "countGraphemeClusters: emojis and ut8 characters" {
@ -761,7 +829,7 @@ test "countGraphemeClusters: emojis and ut8 characters" {
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 6);
try expectEqual(count, 6);
}
test "countGraphemeClusters: emojis, ut8, and ascii characters" {
@ -771,7 +839,7 @@ test "countGraphemeClusters: emojis, ut8, and ascii characters" {
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 10);
try expectEqual(count, 10);
}
// Str.startsWith
@ -821,27 +889,27 @@ pub fn startsWithCodePoint(string: RocStr, prefix: u32) callconv(.C) bool {
test "startsWithCodePoint: ascii char" {
const whole = RocStr.init("foobar", 6);
const prefix = 'f';
expect(startsWithCodePoint(whole, prefix));
try expect(startsWithCodePoint(whole, prefix));
}
test "startsWithCodePoint: emoji" {
const yes = RocStr.init("💖foobar", 10);
const no = RocStr.init("foobar", 6);
const prefix = '💖';
expect(startsWithCodePoint(yes, prefix));
expect(!startsWithCodePoint(no, prefix));
try expect(startsWithCodePoint(yes, prefix));
try expect(!startsWithCodePoint(no, prefix));
}
test "startsWith: foo starts with fo" {
const foo = RocStr.init("foo", 3);
const fo = RocStr.init("fo", 2);
expect(startsWith(foo, fo));
try expect(startsWith(foo, fo));
}
test "startsWith: 123456789123456789 starts with 123456789123456789" {
const str = RocStr.init("123456789123456789", 18);
defer str.deinit();
expect(startsWith(str, str));
try expect(startsWith(str, str));
}
test "startsWith: 12345678912345678910 starts with 123456789123456789" {
@ -850,7 +918,7 @@ test "startsWith: 12345678912345678910 starts with 123456789123456789" {
const prefix = RocStr.init("123456789123456789", 18);
defer prefix.deinit();
expect(startsWith(str, prefix));
try expect(startsWith(str, prefix));
}
// Str.endsWith
@ -882,13 +950,13 @@ test "endsWith: foo ends with oo" {
defer foo.deinit();
defer oo.deinit();
expect(endsWith(foo, oo));
try expect(endsWith(foo, oo));
}
test "endsWith: 123456789123456789 ends with 123456789123456789" {
const str = RocStr.init("123456789123456789", 18);
defer str.deinit();
expect(endsWith(str, str));
try expect(endsWith(str, str));
}
test "endsWith: 12345678912345678910 ends with 345678912345678910" {
@ -897,7 +965,7 @@ test "endsWith: 12345678912345678910 ends with 345678912345678910" {
defer str.deinit();
defer suffix.deinit();
expect(endsWith(str, suffix));
try expect(endsWith(str, suffix));
}
test "endsWith: hello world ends with world" {
@ -906,17 +974,18 @@ test "endsWith: hello world ends with world" {
defer str.deinit();
defer suffix.deinit();
expect(endsWith(str, suffix));
try expect(endsWith(str, suffix));
}
// Str.concat
pub fn strConcatC(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, strConcat, .{ result_in_place, arg1, arg2 });
pub fn strConcatC(arg1: RocStr, arg2: RocStr) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, strConcat, .{ arg1, arg2 });
}
fn strConcat(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
fn strConcat(arg1: RocStr, arg2: RocStr) RocStr {
if (arg1.isEmpty()) {
// the second argument is borrowed, so we must increment its refcount before returning
const result_in_place = InPlace.Clone;
return RocStr.clone(result_in_place, arg2);
} else if (arg2.isEmpty()) {
// the first argument is owned, so we can return it without cloning
@ -974,11 +1043,11 @@ test "RocStr.concat: small concat small" {
roc_str3.deinit();
}
const result = strConcat(InPlace.Clone, roc_str1, roc_str2);
const result = strConcat(roc_str1, roc_str2);
defer result.deinit();
expect(roc_str3.eq(result));
try expect(roc_str3.eq(result));
}
pub const RocListStr = extern struct {
@ -1057,7 +1126,7 @@ test "RocStr.joinWith: result is big" {
defer result.deinit();
expect(roc_result.eq(result));
try expect(roc_result.eq(result));
}
// Str.toBytes
@ -1191,8 +1260,8 @@ fn validateUtf8BytesX(str: RocList) FromUtf8Result {
return fromUtf8(str);
}
fn expectOk(result: FromUtf8Result) void {
expectEqual(result.is_ok, true);
fn expectOk(result: FromUtf8Result) !void {
try expectEqual(result.is_ok, true);
}
fn sliceHelp(bytes: [*]const u8, length: usize) RocList {
@ -1217,7 +1286,7 @@ test "validateUtf8Bytes: ascii" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
try expectOk(validateUtf8BytesX(list));
}
test "validateUtf8Bytes: unicode œ" {
@ -1225,7 +1294,7 @@ test "validateUtf8Bytes: unicode œ" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
try expectOk(validateUtf8BytesX(list));
}
test "validateUtf8Bytes: unicode ∆" {
@ -1233,7 +1302,7 @@ test "validateUtf8Bytes: unicode ∆" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
try expectOk(validateUtf8BytesX(list));
}
test "validateUtf8Bytes: emoji" {
@ -1241,7 +1310,7 @@ test "validateUtf8Bytes: emoji" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
try expectOk(validateUtf8BytesX(list));
}
test "validateUtf8Bytes: unicode ∆ in middle of array" {
@ -1249,15 +1318,15 @@ test "validateUtf8Bytes: unicode ∆ in middle of array" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectOk(validateUtf8BytesX(list));
try expectOk(validateUtf8BytesX(list));
}
fn expectErr(list: RocList, index: usize, err: Utf8DecodeError, problem: Utf8ByteProblem) void {
fn expectErr(list: RocList, index: usize, err: Utf8DecodeError, problem: Utf8ByteProblem) !void {
const str_ptr = @ptrCast([*]u8, list.bytes);
const str_len = list.length;
expectError(err, numberOfNextCodepointBytes(str_ptr, str_len, index));
expectEqual(toErrUtf8ByteResponse(index, problem), validateUtf8Bytes(str_ptr, str_len));
try expectError(err, numberOfNextCodepointBytes(str_ptr, str_len, index));
try expectEqual(toErrUtf8ByteResponse(index, problem), validateUtf8Bytes(str_ptr, str_len));
}
test "validateUtf8Bytes: invalid start byte" {
@ -1266,7 +1335,7 @@ test "validateUtf8Bytes: invalid start byte" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 2, error.Utf8InvalidStartByte, Utf8ByteProblem.InvalidStartByte);
try expectErr(list, 2, error.Utf8InvalidStartByte, Utf8ByteProblem.InvalidStartByte);
}
test "validateUtf8Bytes: unexpected eof for 2 byte sequence" {
@ -1275,7 +1344,7 @@ test "validateUtf8Bytes: unexpected eof for 2 byte sequence" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
try expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
}
test "validateUtf8Bytes: expected continuation for 2 byte sequence" {
@ -1284,7 +1353,7 @@ test "validateUtf8Bytes: expected continuation for 2 byte sequence" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
try expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
}
test "validateUtf8Bytes: unexpected eof for 3 byte sequence" {
@ -1293,7 +1362,7 @@ test "validateUtf8Bytes: unexpected eof for 3 byte sequence" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
try expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
}
test "validateUtf8Bytes: expected continuation for 3 byte sequence" {
@ -1302,7 +1371,7 @@ test "validateUtf8Bytes: expected continuation for 3 byte sequence" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
try expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
}
test "validateUtf8Bytes: unexpected eof for 4 byte sequence" {
@ -1311,7 +1380,7 @@ test "validateUtf8Bytes: unexpected eof for 4 byte sequence" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
try expectErr(list, 3, error.UnexpectedEof, Utf8ByteProblem.UnexpectedEndOfSequence);
}
test "validateUtf8Bytes: expected continuation for 4 byte sequence" {
@ -1320,7 +1389,7 @@ test "validateUtf8Bytes: expected continuation for 4 byte sequence" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
try expectErr(list, 3, error.Utf8ExpectedContinuation, Utf8ByteProblem.ExpectedContinuation);
}
test "validateUtf8Bytes: overlong" {
@ -1329,7 +1398,7 @@ test "validateUtf8Bytes: overlong" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8OverlongEncoding, Utf8ByteProblem.OverlongEncoding);
try expectErr(list, 3, error.Utf8OverlongEncoding, Utf8ByteProblem.OverlongEncoding);
}
test "validateUtf8Bytes: codepoint out too large" {
@ -1338,7 +1407,7 @@ test "validateUtf8Bytes: codepoint out too large" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8CodepointTooLarge, Utf8ByteProblem.CodepointTooLarge);
try expectErr(list, 3, error.Utf8CodepointTooLarge, Utf8ByteProblem.CodepointTooLarge);
}
test "validateUtf8Bytes: surrogate halves" {
@ -1347,5 +1416,5 @@ test "validateUtf8Bytes: surrogate halves" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
expectErr(list, 3, error.Utf8EncodesSurrogateHalf, Utf8ByteProblem.EncodesSurrogateHalf);
try expectErr(list, 3, error.Utf8EncodesSurrogateHalf, Utf8ByteProblem.EncodesSurrogateHalf);
}

View File

@ -20,18 +20,18 @@ comptime {
}
}
fn testing_roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
fn testing_roc_alloc(size: usize, _: u32) callconv(.C) ?*c_void {
return @ptrCast(?*c_void, std.testing.allocator.alloc(u8, size) catch unreachable);
}
fn testing_roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
fn testing_roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, _: u32) callconv(.C) ?*c_void {
const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr));
const slice = ptr[0..old_size];
return @ptrCast(?*c_void, std.testing.allocator.realloc(slice, new_size) catch unreachable);
}
fn testing_roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void {
const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr));
std.testing.allocator.destroy(ptr);
@ -53,8 +53,8 @@ pub const Inc = fn (?[*]u8) callconv(.C) void;
pub const IncN = fn (?[*]u8, u64) callconv(.C) void;
pub const Dec = fn (?[*]u8) callconv(.C) void;
const REFCOUNT_MAX_ISIZE: comptime isize = 0;
pub const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
const REFCOUNT_MAX_ISIZE: isize = 0;
pub const REFCOUNT_ONE_ISIZE: isize = std.math.minInt(isize);
pub const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE);
pub const IntWidth = enum(u8) {
@ -71,44 +71,6 @@ pub const IntWidth = enum(u8) {
Usize,
};
pub fn intWidth(width: IntWidth) anytype {
switch (width) {
IntWidth.U8 => {
return u8;
},
IntWidth.U16 => {
return u16;
},
IntWidth.U32 => {
return u32;
},
IntWidth.U64 => {
return u64;
},
IntWidth.U128 => {
return u128;
},
IntWidth.I8 => {
return i8;
},
IntWidth.I16 => {
return i16;
},
IntWidth.I32 => {
return i32;
},
IntWidth.I64 => {
return i64;
},
IntWidth.I128 => {
return i128;
},
IntWidth.Usize => {
return usize;
},
}
}
pub fn decref(
bytes_or_null: ?[*]u8,
data_bytes: usize,
@ -148,7 +110,7 @@ pub fn allocateWithRefcount(
data_bytes: usize,
alignment: u32,
) [*]u8 {
comptime const result_in_place = false;
const result_in_place = false;
switch (alignment) {
16 => {

View File

@ -9,6 +9,23 @@ use std::str;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
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!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=BUILTINS_O={}", dest_obj);
// When we build on Netlify, zig is not installed (but also not used,
// since all we're doing is generating docs), so we can skip the steps
// that require having zig installed.
if env::var_os("NO_ZIG_INSTALLED").is_some() {
// We still need to do the other things before this point, because
// setting the env vars is needed for other parts of the build.
return;
}
let big_sur_path = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib";
let use_build_script = Path::new(big_sur_path).exists();
// "." is relative to where "build.rs" is
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
@ -16,35 +33,31 @@ fn main() {
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_ir_path = bitcode_path.join("builtins.ll");
let dest_ir = dest_ir_path.to_str().expect("Invalid dest ir path");
if use_build_script {
println!("Compiling zig object & ir to: {} and {}", src_obj, dest_ir);
run_command_with_no_args(&bitcode_path, "./build.sh");
} else {
println!("Compiling zig object to: {}", src_obj);
run_command(&bitcode_path, "zig", &["build", "object", "-Drelease=true"]);
println!("Compiling ir to: {}", dest_ir);
run_command(&bitcode_path, "zig", &["build", "ir", "-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 = dest_ir_path.to_str().expect("Invalid dest ir path");
println!("Compiling ir to: {}", dest_ir);
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
let dest_bc_path = Path::new(&out_dir).join("builtins.bc");
let dest_bc_path = bitcode_path.join("builtins.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling bitcode to: {}", dest_bc);
run_command(
build_script_dir_path,
"llvm-as-10",
&[dest_ir, "-o", dest_bc],
);
run_command(build_script_dir_path, "llvm-as", &[dest_ir, "-o", dest_bc]);
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=BUILTINS_BC={}", dest_bc);
println!("cargo:rustc-env=BUILTINS_O={}", dest_obj);
get_zig_files(bitcode_path.as_path(), &|path| {
let path: &Path = path;
println!(
@ -79,6 +92,25 @@ where
}
}
fn run_command_with_no_args<P: AsRef<Path>>(path: P, command_str: &str) {
let output_result = Command::new(OsStr::new(&command_str))
.current_dir(path)
.output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
};
panic!("{} failed: {}", command_str, error_str);
}
},
Err(reason) => panic!("{} failed: {}", command_str, reason),
}
}
fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {

View File

@ -55,36 +55,36 @@ and : Bool, Bool -> Bool
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## # In Roc, this is not the case. See the performance notes for #Bool.and for details.
## # In Roc, this is not the case. See the performance notes for [Bool.and] for details.
or : Bool, Bool -> Bool
## Exclusive or
xor : Bool, Bool -> Bool
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isEq : 'val, 'val -> Bool`
## Returns `True` if the two values are *structurally equal*, and `False` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`
##
## Structural equality works as follows:
##
## 1. #Int and #Float values are equal if their numbers are equal.
## 2. Records are equal if all their fields are equal.
## 3. Global tags are equal if they are the same tag, and also their contents (if any) are equal.
## 4. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal.
## 5. Collections (#String, #List, #Map, #Set, and #Bytes) are equal if they are the same length, and also all their corresponding elements are equal.
## 1. Global tags are equal if they are the same tag, and also their contents (if any) are equal.
## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal.
## 3. Records are equal if all their fields are equal.
## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See [Num.isNaN] for more about *NaN*.
##
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isEq : 'val, 'val -> Bool`
isEq : val, val -> Bool
## Calls #eq on the given values, then calls #not on the result.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isNotEq : 'val, 'val -> Bool`
## Calls [isEq] on the given values, then calls [not] on the result.
##
## `a != b` is shorthand for `Bool.isNotEq a b`
##
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
## accept arguments whose types contain functions.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isNotEq : 'val, 'val -> Bool`
isNotEq : val, val -> Bool

View File

@ -1,7 +0,0 @@
interface Defaults
exposes []
imports [
Dict.{ Dict },
Set.{ Set },
Num.{ Num, Int, Float }
]

View File

@ -19,3 +19,15 @@ map :
Dict beforeKey beforeValue,
({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue })
-> Dict afterKey afterValue
# DESIGN NOTES: The reason for panicking when given NaN is that:
# * If we allowed NaN in, Dict.insert would no longer be idempotent.
# * If we allowed NaN but overrode its semantics to make it feel like "NaN == NaN" we'd need isNaN checks in all hashing operations as well as all equality checks (during collision detection), not just insert. This would be much worse for performance than panicking on insert, which only requires one extra conditional on insert.
# * It's obviously invalid; the whole point of NaN is that an error occurred. Giving a runtime error notifies you when this problem happens. Giving it only on insert is the best for performance, because it means you aren't paying for isNaN checks on lookups as well.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: insert : Dict 'key val, 'key, val -> Dict 'key val
## Make sure never to insert a key of *NaN* into a [Dict]! Because *NaN* is
## defined to be unequal to *NaN*, inserting a *NaN* key results in an entry
## that can never be retrieved or removed from the [Dict].
insert : Dict key val, key, val -> Dict key val

View File

@ -62,7 +62,7 @@ interface List2
## the same type. If you want to put a mix of #Int and #Str values into a list, try this:
##
## ```
## mixedList : List [ IntElem Int, StrElem Str ]*
## mixedList : List [ IntElem I64, StrElem Str ]*
## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ]
## ```
##
@ -180,7 +180,7 @@ interface List2
## we can free it immediately because there are no other refcounts. However,
## in the case of `lists`, we have to iterate through the list and decrement
## the refcounts of each of its contained lists - because they, too, have
## refcounts! Importantly, beacuse the first element had its refcount incremented
## refcounts! Importantly, because the first element had its refcount incremented
## because the function returned `first`, that element will actually end up
## *not* getting freed at the end - but all the others will be.
##
@ -232,9 +232,28 @@ reverse : List elem -> List elem
## Sorts a list using a function which specifies how two elements are ordered.
##
##
## When sorting by numeric values, it's more efficient to use [sortAsc] or
## [sortDesc] instead.
sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem
## Sorts a list in ascending order (lowest to highest), using a function which
## specifies a way to represent each element as a number.
##
## This is more efficient than [sort] because it skips
## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead.
##
## To sort in descending order (highest to lowest), use [List.sortDesc] instead.
sortAsc : List elem, (elem -> Num *) -> List elem
## Sorts a list in descending order (highest to lowest), using a function which
## specifies a way to represent each element as a number.
##
## This is more efficient than [sort] because it skips
## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead.
##
## To sort in ascending order (lowest to highest), use [List.sortAsc] instead.
sortDesc : List elem, (elem -> Num *) -> List elem
## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values.
##
@ -248,7 +267,7 @@ map : List before, (before -> after) -> List after
## This works like #List.map, except it also passes the index
## of the element to the conversion function.
mapWithIndex : List before, (before, Int -> after) -> List after
mapWithIndex : List before, (before, Nat -> after) -> List after
## This works like #List.map, except at any time you can return `Err` to
## cancel the entire operation immediately, and return that #Err.
@ -279,7 +298,7 @@ update : List elem, Nat, (elem -> elem) -> List elem
## A more flexible version of #List.update, which returns an "updater" function
## that lets you delay performing the update until later.
updater : List elem, Nat -> { elem, new : elem -> List elem }
updater : List elem, Nat -> { elem, new : (elem -> List elem) }
## If all the elements in the list are #Ok, return a new list containing the
## contents of those #Ok tags. If any elements are #Err, return #Err.
@ -629,7 +648,7 @@ walk : List elem, { start : state, step : (state, elem -> state) } -> state
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`.
walkBackwards : List elem, { start : state, step : (state, elem -> state ]) } -> state
walkBackwards : List elem, { start : state, step : (state, elem -> state) } -> state
## Same as #List.walk, except you can stop walking early.
##

View File

@ -9,13 +9,24 @@ interface Num2
## This is useful for functions that can work on either, for example #Num.add, whose type is:
##
## ```
## add : Num range, Num range -> Num range
## add : Num a, Num a -> Num a
## ```
##
## The number 1.5 technically has the type `Num FloatingPoint`, so when you pass two of them to `Num.add`, the answer you get is `3.0 : Num FloatingPoint`.
## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass
## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`.
#
## Similarly, the number 0x1 (that is, the integer 1 in hexadecimal notation)
## technically has the type `Num (Integer *)`, so when you pass two of them to
## [Num.add], the answer you get is `2 : Num (Integer *)`.
##
## The type #Float is defined to be an alias for `Num FloatingPoint`, so `3.0 : Num FloatingPoint` is the same answer as `3.0 : Float`. # # Similarly, the number 1 technically has the type `Num Integer`, so when you pass two of them to `Num.add`, the answer you get is `2 : Num Integer`. # # The type #Int is defined to be an alias for `Num Integer`, so `2 : Num Integer` is the same answer as `2 : Int`. #
## In this way, the `Num` type makes it possible to have `1 + 1` return `2 : Int` and `1.5 + 1.5` return `3.0 : Float`.
## The type [`Frac a`](#Frac) is defined to be an alias for `Num (Fraction a)`,
## so `3.0 : Num (Fraction *)` is the same value as `3.0 : Frac *`.
## Similarly, the type [`Int a`](#Int) is defined to be an alias for
## `Num (Integer a)`, so `2 : Num (Integer *)` is the same value as
## `2 : Int *`.
##
## In this way, the [Num] type makes it possible to have `1 + 0x1` return
## `2 : Int *` and `1.5 + 1.5` return `3.0 : Frac`.
##
## ## Number Literals
##
@ -29,29 +40,30 @@ interface Num2
## ends up having the type `Nat`.
##
## Sometimes number literals don't become more specific. For example,
## the #Num.toStr function has the type `Num * -> Str`. This means that
## the [Num.toStr] function has the type `Num * -> Str`. This means that
## when calling `Num.toStr (5 + 6)`, the expression `(5 + 6)`
## still has the type `Num *`. When this happens, `Num *` defaults to
## being an #I32 - so this addition expression would overflow
## being an [I64] - so this addition expression would overflow
## if either 5 or 6 were replaced with a number big enough to cause
## addition overflow on an #I32.
## addition overflow on an [I64] value.
##
## If this default of #I32 is not big enough for your purposes,
## you can add an `i64` to the end of the number literal, like so:
## If this default of [I64] is not big enough for your purposes,
## you can add an `i128` to the end of the number literal, like so:
##
## >>> Num.toStr 5_000_000_000i64
## >>> Num.toStr 5_000_000_000i128
##
## This `i64` suffix specifies that you want this number literal to be
## an #I64 instead of a `Num *`. All the other numeric types have
## suffixes just like `i64`; here are some other examples:
## This `i128` suffix specifies that you want this number literal to be
## an [I128] instead of a `Num *`. All the other numeric types have
## suffixes just like `i128`; here are some other examples:
##
## * `215u8` is a `215` value of type #U8
## * `76.4f32` is a `76.4` value of type #F32
## * `12345ulen` is a `12345` value of type #Nat
## * `215u8` is a `215` value of type [U8]
## * `76.4f32` is a `76.4` value of type [F32]
## * `123.45dec` is a `123.45` value of type [Dec]
## * `12345nat` is a `12345` value of type [Nat]
##
## In practice, these are rarely needed. It's most common to write
## number literals without any suffix.
Num range : [ @Num range ]
Num a : [ @Num a ]
## A decimal number.
##
@ -110,6 +122,30 @@ Dec : Frac [ @Decimal128 ]
## been done in a base-2 floating point calculation, which causes noticeable
## precision loss in this case.
##
## The floating-point numbers ([F32] and [F64]) also have three values which
## are not ordinary [finite numbers](https://en.wikipedia.org/wiki/Finite_number).
## They are:
## * ∞ ([infinity](https://en.wikipedia.org/wiki/Infinity))
## * -∞ (negative infinity)
## * *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN))
##
## These values are different from ordinary numbers in that they only occur
## when a floating-point calculation encounters an error. For example:
## * Dividing a positive [F64] by `0.0` returns ∞.
## * Dividing a negative [F64] by `0.0` returns -∞.
## * Dividing a [F64] of `0.0` by `0.0` returns [*NaN*](Num.isNaN).
##
## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
## floating point standard. Because almost all modern processors are built to
## this standard, deviating from these rules has a significant performance
## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## access to hardware-accelerated performance, Roc follows these rules exactly.
##
## There's no literal syntax for these error values, but you can check to see if
## you ended up with one of them by using [isNaN], [isFinite], and [isInfinite].
## Whenever a function in this module could return one of these values, that
## possibility is noted in the function's documentation.
##
## ## Performance Notes
##
## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32]
@ -128,7 +164,7 @@ Dec : Frac [ @Decimal128 ]
## an even bigger performance difference. [F32] and [F64] can do these in a
## single instruction, whereas [Dec] needs entire custom procedures - which use
## loops and conditionals. If you need to do performance-critical trigonometry
## or square roots, either [F32] or [F64] is probably a better choice than the
## or square roots, either [F64] or [F32] is probably a better choice than the
## usual default choice of [Dec], despite the precision problems they bring.
Frac a : Num [ @Fraction a ]
@ -194,6 +230,17 @@ I64 : Int [ @Signed64 ]
U64 : Int [ @Unsigned64 ]
I128 : Int [ @Signed128 ]
U128 : Int [ @Unsigned128 ]
## A [natural number](https://en.wikipedia.org/wiki/Natural_number) represented
## as a 64-bit unsigned integer on 64-bit systems, a 32-bit unsigned integer
## on 32-bit systems, and so on.
##
## This system-specific size makes it useful for certain data structure
## functions like [List.len], because the number of elements many data strucures
## can hold is also system-specific. For example, the maximum number of elements
## a [List] can hold on a 64-bit system fits in a 64-bit unsigned integer, and
## on a 32-bit system it fits in 32-bit unsigned integer. This makes [Nat] a
## good fit for [List.len] regardless of system.
Nat : Int [ @Natural ]
## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values.
@ -272,148 +319,6 @@ Nat : Int [ @Natural ]
## If you need to do math outside these bounds, consider using a larger numeric size.
Int size : Num [ @Int size ]
## A 64-bit floating-point number. All number literals with decimal points are #Float values.
##
## >>> 0.1
##
## >>> 1.0
##
## >>> 0.0
##
## If you like, you can put underscores in your #Float literals.
## They have no effect on the number's value, but can make things easier to read.
##
## >>> 1_000_000.000_000_001
##
## Roc supports two types of floating-point numbers:
##
## - *Decimal* floating-point numbers
## - *Binary* floating-point numbers
##
## Decimal floats are precise for decimal calculations. For example:
##
## >>> 0.1 + 0.2
##
## Operations on binary floats tend to run *much* faster than operations on
## decimal floats, because almost all processors have dedicated instructions
## for binary floats and not for decimal floats.
## However, binary floats are less precise for decimal calculations.
##
## For example, here is the same `0.1 + 0.2` calculation again, this time putting
## `f64` after the numbers to specify that they should be #F64 binary floats
## instead of the default of decimal floats.
##
## >>> 0.1f64 + 0.2f64
##
## If decimal precision is unimportant, binary floats give better performance.
## If decimal precision is important - for example, when representing money -
## decimal floats tend to be worth the performance cost.
##
## Usually, Roc's compiler can infer a more specific type than #Float for
## a particular float value, based on how it is used with other numbers. For example:
##
## >>> coordinates : { x : F32, y : F32 }
## >>> coordinates = { x: 1, y: 2.5 }
## >>>
## >>> coordinates.x + 1
##
## On the last line, the compiler infers that the `1` in `+ 1` is an #F32
## beacuse it's being added to `coordinates.x`, which was defined to be an #F32
## on the first line.
##
## Sometimes the compiler has no information about which specific type to pick.
## For example:
##
## >>> 0.1 + 0.2 == 0.3
##
## When this happens, the compiler defaults to choosing #D64 decimal floats.
## If you want something else, you can write (for example) `0.1f32 + 0.2 == 0.3`
## to compare them as #F32 values instead.
##
## Both decimal and binary #Float values conform to the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754#Interchange_formats)
## specification for floating point numbers. Conforming to this specification
## means Roc's binary floats have nearly universal hardware support, and its
## decimal floats have [some hardware support](http://speleotrove.com/decimal/)
## among the rare processors which support decimal float instructions at all.
##
## This specification covers several float formats. Here are the ones Roc supports:
##
## - #F32 (32-bit binary float)
## - #F64 (64-bit binary float)
## - #D32 (32-bit decimal float)
## - #D64 (64-bit decimal float) # TODO show a table like we do with ints, with the min/max ranges
##
## Like #Int, it's possible for #Float operations to overflow. Like with ints,
## you'll typically get a crash when this happens.
##
## * In a development build, you'll get an assertion failure.
## * In an optimized build, you'll get [`Infinity` or `-Infinity`](https://en.wikipedia.org/wiki/IEEE_754-1985#Positive_and_negative_infinity).
##
## Although some languages treat have first-class representations for
## `-Infinity`, `Infinity`, and the special `NaN` ("not a number")
## floating-point values described in the IEEE-754, Roc does not.
## Instead, Roc treats all of these as errors. If any Float operation
## in a development build encounters one of these values, it will
## result in an assertion failure.
##
## Stll, it's possible that these values may accidentally arise in
## release builds. If this happens, they will behave according to the usual
## IEEE-754 rules: any operation involving `NaN` will output `NaN`,
## any operation involving `Infinity` or `-Infinity` will output either
## `Infinity`, `-Infinity`, or `NaN`, and `NaN` is defined to be not
## equal to itself - meaning `(x == x)` returns `False` if `x` is `NaN`.
##
## These are very error-prone values, so if you see an assertion fail in
## developent because of one of them, take it seriously - and try to fix
## the code so that it can't come up in a release!
##
## ## Loud versus Quiet errors
##
## Besides precision problems, another reason floats are error-prone
## is that they have quiet error handling built in. For example, in
## a 64-bit floating point number, there are certain patterns of those
## 64 bits which do not represent valid floats; instead, they represent
## invalid results of previous operations.
##
## Whenever any arithmetic operation is performed on an invalid float,
## the result is also invalid. This is called *error propagation*, and
## it is notoriously error-prone. In Roc, using equality operations like
## `==` and `!=` on an invalid float causes a crash. (See #Float.verify
## to check the validity of your float.)
##
## Beause invalid floats are so error-prone, Roc discourages using them.
## Instead, by default it treats them the same way as overflow: by
## crashing whenever any #Float function would otherwise return one.
## You can also use functions like #Float.tryAdd to get an `Ok` or an error
## back so you can gracefully recover from invalid values.
##
## Quiet errors can be useful sometimes. For example, you might want to
## do three floating point calculations in a row, and then gracefully handle
## the situation where any one of the three was invalid. In that situation,
## quiet errors can be more efficient than using three `try` functions, because
## it can have one condition at the end instead of three along the way.
##
## Another potential use for quiet errors is for risky performance optimizations.
## When you are absolutely certain there is no chance of overflow or other
## errors, using a *quiet* operation may save an entry in the instruction cache
## by removing a branch that would always have been predicted correctly.
## Always [measure the performance change](https://youtu.be/r-TLSBdHe1A)
## if you do this! The compiler can optimize away those branches behind the scenes,
## so you may find that using the quiet version expliitly
## makes the code riskier to future change, without actually affecting performance.
##
## ## Performance Notes
##
## Currently, loud errors are implemented using an extra conditional. Although
## this conditional will always be correctly branh-predicted unless an error
## occurs, there is a small effect on the instruction cache, which means
## quiet errors are very slightly more efficient.
##
## Long-term, it's possible that the Roc compiler may be able to implement
## loud errors using *signalling errors* in some situations, which could
## eliminate the performance difference between loud and quiet errors in
## the situation where no error occurs.
## Convert
## Return a negative number when given a positive one, and vice versa.
@ -426,16 +331,16 @@ Int size : Num [ @Int size ]
##
## >>> Num.neg 0.0
##
## This is safe to use with any #Float, but it can cause overflow when used with certain #Int values.
## This is safe to use with any #Frac, but it can cause overflow when used with certain #Int values.
##
## For example, calling #Num.neg on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow.
## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than
## the highest value it can represent. (For this reason, calling #Num.abs on the lowest signed value will also cause overflow.)
##
## Additionally, calling #Num.neg on any unsigned integer (such as any #U64 or #U32 value) other than 0 will cause overflow.
## Additionally, calling #Num.neg on any unsigned integer (such as any #U64 or #U32 value) other than zero will cause overflow.
##
## (It will never crash when given a #Float, however, because of how floating point numbers represent positive and negative numbers.)
neg : Num range -> Num range
## (It will never crash when given a #Frac, however, because of how floating point numbers represent positive and negative numbers.)
neg : Num a -> Num a
## Return the absolute value of the number.
##
@ -451,14 +356,14 @@ neg : Num range -> Num range
##
## >>> Num.abs 0.0
##
## This is safe to use with any #Float, but it can cause overflow when used with certain #Int values.
## This is safe to use with any #Frac, but it can cause overflow when used with certain #Int values.
##
## For example, calling #Num.abs on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow.
## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than
## the highest value it can represent. (For this reason, calling #Num.neg on the lowest signed value will also cause overflow.)
##
## Calling this on an unsigned integer (like #U32 or #U64) never does anything.
abs : Num range -> Num range
abs : Num a -> Num a
## Check
@ -485,7 +390,7 @@ isOdd : Num * -> Bool
## Add two numbers of the same type.
##
## (To add an #Int and a #Float, first convert one so that they both have the same type. There are functions in the [`Float`](/Float) module that can convert both #Int to #Float and the other way around.)
## (To add an #Int and a #Frac, first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to #Frac and the other way around.)
##
## `a + b` is shorthand for `Num.add a b`.
##
@ -495,13 +400,24 @@ isOdd : Num * -> Bool
##
## `Num.add` can be convenient in pipelines.
##
## >>> Float.pi
## >>> Frac.pi
## >>> |> Num.add 1.0
add : Num range, Num range -> Num range
##
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
## ∞ or -∞. For all other number types, overflow results in a panic.
add : Num a, Num a -> Num a
## Add two numbers and check for overflow.
##
## This is the same as [Num.add] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
addCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
## Subtract two numbers of the same type.
##
## (To subtract an #Int and a #Float, first convert one so that they both have the same type. There are functions in the [`Float`](/Float) module that can convert both #Int to #Float and the other way around.)
## (To subtract an #Int and a #Frac, first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to #Frac and the other way around.)
##
## `a - b` is shorthand for `Num.sub a b`.
##
@ -511,13 +427,24 @@ add : Num range, Num range -> Num range
##
## `Num.sub` can be convenient in pipelines.
##
## >>> Float.pi
## >>> Frac.pi
## >>> |> Num.sub 2.0
sub : Num range, Num range -> Num range
##
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
## ∞ or -∞. For all other number types, overflow results in a panic.
sub : Num a, Num a -> Num a
## Subtract two numbers and check for overflow.
##
## This is the same as [Num.sub] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
subCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
## Multiply two numbers of the same type.
##
## (To multiply an #Int and a #Float, first convert one so that they both have the same type. There are functions in the [`Float`](/Float) module that can convert both #Int to #Float and the other way around.)
## (To multiply an #Int and a #Frac, first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to #Frac and the other way around.)
##
## `a * b` is shorthand for `Num.mul a b`.
##
@ -527,36 +454,115 @@ sub : Num range, Num range -> Num range
##
## `Num.mul` can be convenient in pipelines.
##
## >>> Float.pi
## >>> Frac.pi
## >>> |> Num.mul 2.0
mul : Num range, Num range -> Num range
##
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
## ∞ or -∞. For all other number types, overflow results in a panic.
mul : Num a, Num a -> Num a
## Multiply two numbers and check for overflow.
##
## This is the same as [Num.mul] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
mulCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
## Convert
## Convert a number to a string, formatted as the traditional base 10 (decimal).
## Convert a number to a [Str].
##
## This is the same as calling `Num.format {}` - so for more details on
## exact formatting, see [Num.format].
##
## >>> Num.toStr 42
##
## Only #Float values will include a decimal point, and they will always include one.
## Only #Frac values will include a decimal point, and they will always include one.
##
## >>> Num.toStr 4.2
##
## >>> Num.toStr 4.0
##
## For other bases see #toHexStr, #toOctalStr, and #toBinaryStr.
## When this function is given a non-[finite](Num.isFinite)
## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`.
##
## To get strings in hexadecimal, octal, or binary format, use [Num.format].
toStr : Num * -> Str
## Convert a number into a [Str], formatted with the given options.
##
## Default options:
## * `base: Decimal`
## * `notation: Standard`
## * `decimalMark: HideForIntegers "."`
## * `decimalDigits: { min: 0, max: All }`
## * `minIntDigits: 1`
## * `wholeSep: { mark: ",", places: 3 }`
##
## ## Options
##
##
## ### decimalMark
##
## * `AlwaysShow` always shows the decimal mark, no matter what.
## * `HideForIntegers` hides the decimal mark if all the numbers after the decimal mark are 0.
##
## The [Str] included in either of these represents the mark itself.
##
## ### `decimalDigits
##
## With 0 decimal digits, the decimal mark will still be rendered if
## `decimalMark` is set to `AlwaysShow`.
##
## If `max` is less than `min`, then first the number will be truncated to `max`
## digits, and then zeroes will be added afterwards until it reaches `min` digits.
##
## >>> Num.format 1.23 { decPlaces: 0, decPointVis: AlwaysShow }
##
## ### minIntDigits
##
## If the integer portion of number is fewer than this many digits, zeroes will
## be added in front of it until there are at least `minWholeDigits` digits.
##
## If this is set to zero, then numbers less than 1 will begin with `"."`
## rather than `"0."`.
##
## ### wholeSep
##
## Examples:
##
## In some countries (e.g. USA and UK), a comma is used to separate thousands:
## >>> Num.format 1_000_000 { base: Decimal, wholeSep: { mark: ",", places: 3 } }
##
## Sometimes when rendering bits, it's nice to group them into groups of 4:
## >>> Num.format 1_000_000 { base: Binary, wholeSep: { mark: " ", places: 4 } }
##
## It's also common to render hexadecimal in groups of 2:
## >>> Num.format 1_000_000 { base: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
format :
Num *,
{
base ? [ Decimal, Hexadecimal, Octal, Binary ],
notation ? [ Standard, Scientific ],
decimalMark ? [ AlwaysShow Str, HideForIntegers ],
decimalDigits ? { min : U16, max : [ All, Trunc U16, Round U16, Floor U16, Ceil U16 ] },
minWholeDigits ? U16,
wholeSep ? { mark : Str, places : U64 }
}
-> Str
## Round off the given float to the nearest integer.
round : Float * -> Int *
ceil : Float * -> Int *
floor : Float * -> Int *
trunc : Float * -> Int *
round : Frac * -> Int *
ceil : Frac * -> Int *
floor : Frac * -> Int *
trunc : Frac * -> Int *
## Convert an #Int to a #Nat. If the given number doesn't fit in #Nat, it will be truncated.
## Since #Nat has a different maximum number depending on the system you're building
## for, this may give a different answer on different systems.
##
## For example, on a 32-bit sytem, #Num.maxNat will return the same answer as
## For example, on a 32-bit system, #Num.maxNat will return the same answer as
## #Num.maxU32. This means that calling `Num.toNat 9_000_000_000` on a 32-bit
## system will return #Num.maxU32 instead of 9 billion, because 9 billion is
## higher than #Num.maxU32 and will not fit in a #Nat on a 32-bit system.
@ -565,13 +571,13 @@ trunc : Float * -> Int *
## the #Nat value of 9_000_000_000. This is because on a 64-bit system, #Nat can
## hold up to #Num.maxU64, and 9_000_000_000 is lower than #Num.maxU64.
##
## To convert a #Float to a #Nat, first call either #Num.round, #Num.ceil, or #Num.floor
## To convert a #Frac to a #Nat, first call either #Num.round, #Num.ceil, or #Num.floor
## on it, then call this on the resulting #Int.
toNat : Int * -> Nat
## Convert an #Int to an #I8. If the given number doesn't fit in #I8, it will be truncated.
##
## To convert a #Float to an #I8, first call either #Num.round, #Num.ceil, or #Num.floor
## To convert a #Frac to an #I8, first call either #Num.round, #Num.ceil, or #Num.floor
## on it, then call this on the resulting #Int.
toI8 : Int * -> I8
toI16 : Int * -> I16
@ -595,13 +601,9 @@ toF32 : Num * -> F32
## there will be a loss of precision.
toF64 : Num * -> F64
## Convert a #Num to a #D32. If the given number can't be precisely represented in a #D32,
## Convert a #Num to a #Dec. If the given number can't be precisely represented in a #Dec,
## there will be a loss of precision.
toD32 : Num * -> D32
## Convert a #Num to a #D64. If the given number can't be precisely represented in a #D64,
## there will be a loss of precision.
toD64 : Num * -> D64
toDec : Num * -> Dec
## Divide two integers and #Num.round the resulut.
##
@ -624,16 +626,16 @@ toD64 : Num * -> D64
## >>> Num.divRound 8 -3
##
## This is the same as the #// operator.
divRound : Int, Int -> Int
divRound : Int a, Int a -> Int a
## Perform flooring modulo on two integers.
##
## Modulo is the same as remainder when working with positive numbers,
## but if either number is negative, then modulo works differently.
##
## Additionally, flooring modulo uses #Float.floor on the result.
## Additionally, flooring modulo uses #Frac.floor on the result.
##
## (Use #Float.mod for non-flooring modulo.)
## (Use #Frac.mod for non-flooring modulo.)
##
## Return `Err DivByZero` if the second integer is zero, because division by zero is undefined in mathematics.
##
@ -646,40 +648,16 @@ divRound : Int, Int -> Int
## >>> -8 %% -3
##
## >>> Int.modFloor -8 -3
#modFloor : Int, Int -> Result DivByZero Int
#modFloor : Int a, Int a -> Result (Int a) [ DivByZero ]*
## Bitwise
xor : Int, Int -> Int
xor : Int a, Int a -> Int a
and : Int, Int -> Int
and : Int a, Int a -> Int a
not : Int -> Int
## Sort ascending - that is, with the lowest first, and the highest last.
##
## List.sort Num.asc [ 3, 6, 0 ]
##
asc : Num a, Num a -> [ Eq, Lt, Gt ]
## Sort descending - that is, with the highest first, and the lowest last.
##
## List.sort Num.desc [ 3, 6, 0 ]
##
desc : Num a, Num a -> [ Eq, Lt, Gt ]
## TODO should we offer hash32 etc even if someday it has to do a hash64 and truncate?
##
## This function can crash under these circumstances:
##
## * It receives a function, or any type that contains a function (for example a record, tag, or #List containing a function)
## * It receives an erroneous #Float (`NaN`, `Infinity`, or `-Infinity` - these values can only originate from hosts)
##
## CAUTION: This function may give different answers in future releases of Roc,
## so be aware that if you rely on the exact answer this gives today, your
## code may break in a future Roc release.
hash64 : a -> U64
not : Int a -> Int a
## Limits
@ -738,88 +716,81 @@ maxU32 : U32
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minU32 : U32
## The highest supported #Float value you can have, which is approximately 1.8 × 10^308.
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxF64 : Float *
maxF64 : F64
## The lowest supported #Float value you can have, which is approximately -1.8 × 10^308.
## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308.
##
## If you go lower than this, your running Roc code will crash - so be careful not to!
minF64 : Float *
minF64 : F64
## The highest integer that can be represented as a #Float without # losing precision.
## It is equal to 2^53, which is approximately 9 × 10^15.
## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308.
##
## Some integers higher than this can be represented, but they may lose precision. For example:
##
## >>> Float.highestInt
##
## >>> Float.highestInt + 100 # Increasing may lose precision
##
## >>> Float.highestInt - 100 # Decreasing is fine - but watch out for lowestLosslessInt!
maxPreciseInt : Float *
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxF32 : F32
## The lowest integer that can be represented as a #Float without losing precision.
## It is equal to -2^53, which is approximately -9 × 10^15.
## The lowest supported #F32 value you can have, which is approximately -1.8 × 10^308.
##
## Some integers lower than this can be represented, but they may lose precision. For example:
## If you go lower than this, your running Roc code will crash - so be careful not to!
minF32 : F32
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308.
##
## >>> Float.lowestIntVal
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxDec : Dec
## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308.
##
## >>> Float.lowestIntVal - 100 # Decreasing may lose precision
##
## >>> Float.lowestIntVal + 100 # Increasing is fine - but watch out for highestInt!
maxPreciseInt : Float *
## If you go lower than this, your running Roc code will crash - so be careful not to!
maxDec : Dec
## Constants
## An approximation of e, specifically 2.718281828459045.
e : Float *
e : Frac *
## An approximation of pi, specifically 3.141592653589793.
pi : Float *
## Constants
## An approximation of e, specifically 2.718281828459045.
e : Float *
## An approximation of pi, specifically 3.141592653589793.
pi : Float *
#ceiling : Float -> Int
#floor : Float -> Int
pi : Frac *
## Trigonometry
#cos : Float -> Float
cos : Frac a -> Frac a
#acos : Float -> Float
acos : Frac a -> Frac a
#sin : Float -> Float
sin : Frac a -> Frac a
#asin : Float -> Float
asin : Frac a -> Frac a
#tan : Float -> Float
tan : Frac a -> Frac a
#atan : Float -> Float
atan : Frac a -> Frac a
## Other Calculations (arithmetic?)
## Divide two #Float numbers.
## Divide one [Frac] by another.
##
## `a / b` is shorthand for `Num.div a b`.
##
## Division by zero is undefined in mathematics. As such, you should make
## sure never to pass zero as the denomaintor to this function!
## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero).
## As such, you should make sure never to pass zero as the denomaintor to this function!
## Calling [div] on a [Dec] denominator of zero will cause a panic.
##
## If zero does get passed as the denominator...
## Calling [div] on [F32] and [F64] values follows these rules:
## * Dividing a positive [F64] or [F32] by zero returns ∞.
## * Dividing a negative [F64] or [F32] by zero returns -∞.
## * Dividing a zero [F64] or [F32] by zero returns [*NaN*](Num.isNaN).
##
## * In a development build, you'll get an assertion failure.
## * In a release build, the function will return `Infinity`, `-Infinity`, or `NaN` depending on the arguments.
## > These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
## > floating point standard. Because almost all modern processors are built to
## > this standard, deviating from these rules has a significant performance
## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## > access to hardware-accelerated performance, Roc follows these rules exactly.
##
## To divide an #Int and a #Float, first convert the #Int to a #Float using one of the functions in this module.
## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using
## one of the functions in this module like [toDec].
##
## >>> 5.0 / 7.0
##
@ -827,45 +798,47 @@ pi : Float *
##
## `Num.div` can be convenient in pipelines.
##
## >>> Float.pi
## >>> Num.pi
## >>> |> Num.div 2.0
#div : Float, Float -> Result Float DivByZero
div = \numerator, denominator ->
when numerator is
0.0 -> 0.0 # TODO return Result!
_ -> denominator
div : Frac a, Frac a -> Frac a
## Perform modulo on two #Float numbers.
## Perform modulo on two [Frac]s.
##
## Modulo is the same as remainder when working with positive numbers,
## but if either number is negative, then modulo works differently.
##
## Return `Err DivByZero` if the second number is zero, because division by zero is undefined in mathematics.
## `a % b` is shorthand for `Num.mod a b`.
##
## `a % b` is shorthand for `Float.mod a b`.
## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero),
## and as such, so is modulo by zero. Because of this, you should make sure never
## to pass zero for the second argument to this function!
##
## Passing [mod] a [Dec] value of zero for its second argument will cause a panic.
## Passing [mod] a [F32] and [F64] value for its second argument will cause it
## to return [*NaN*](Num.isNaN).
##
## >>> 5.0 % 7.0
##
## >>> Float.mod 5 7
## >>> Num.mod 5 7
##
## `Float.mod` can be convenient in pipelines.
## `Num.mod` can be convenient in pipelines.
##
## >>> Float.pi
## >>> |> Float.mod 2.0
mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
## >>> Num.pi
## >>> |> Num.mod 2.0
mod : Frac a, Frac a -> Frac a
## Raises a #Float to the power of another #Float.
## Raises a #Frac to the power of another #Frac.
##
## `
## For an #Int alternative to this function, see #Num.raise.
pow : Float a, Float a -> Float a
pow : Frac a, Frac a -> Frac a
## Raises an integer to the power of another, by multiplying the integer by
## itself the given number of times.
##
## This process is known as [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring).
##
## For a #Float alternative to this function, which supports negative exponents,
## For a #Frac alternative to this function, which supports negative exponents,
## see #Num.exp.
##
## >>> Num.exp 5 0
@ -882,33 +855,187 @@ pow : Float a, Float a -> Float a
## overflow
expBySquaring : Int a, U8 -> Int a
## Return the reciprocal of a #Float - that is, divides `1.0` by the given number.
## Returns an approximation of the absolute value of a [Frac]'s square root.
##
## Crashes if given `0.0`, because division by zero is undefined in mathematics.
## The square root of a negative number is an irrational number, and [Frac] only
## supports rational numbers. As such, you should make sure never to pass this
## function a negative number! Calling [sqrt] on a negative [Dec] will cause a panic.
##
## For a version that does not crash, use #tryRecip
recip : Float a -> Result (Float a) [ DivByZero ]*
## Calling [sqrt] on [F32] and [F64] values follows these rules:
## * Passing a negative [F64] or [F32] returns [*NaN*](Num.isNaN).
## * Passing [*NaN*](Num.isNaN) or -∞ also returns [*NaN*](Num.isNaN).
## * Passing ∞ returns ∞.
##
## > These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
## > floating point standard. Because almost all modern processors are built to
## > this standard, deviating from these rules has a significant performance
## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## > access to hardware-accelerated performance, Roc follows these rules exactly.
##
## >>> Frac.sqrt 4.0
##
## >>> Frac.sqrt 1.5
##
## >>> Frac.sqrt 0.0
##
## >>> Frac.sqrt -4.0f64
##
## >>> Frac.sqrt -4.0dec
sqrt : Frac a -> Frac a
## NOTE: Need to come up a suffix alternative to the "try" prefix.
## This should be like (for example) recipTry so that it's more discoverable
## in documentation and editor autocomplete when you type "recip"
tryRecip : Float a -> Result (Float a) [ DivByZero ]*
## Bit shifts
## Return an approximation of the absolute value of the square root of the #Float.
## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) left.
##
## Return #InvalidSqrt if given a negative number or an invalid #Float. The square root of a negative number is an irrational number, and #Float only supports rational numbers.
## `a << b` is shorthand for `Num.shl a b`.
shl : Int a, Int a -> Int a
## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) left.
##
## >>> Float.sqrt 4.0
## This is called `shlWrap` because any bits shifted
## off the beginning of the number will be wrapped around to
## the end. (In contrast, [shl] replaces discarded bits with zeroes.)
shlWrap : Int a, Int a -> Int a
## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) right.
##
## >>> Float.sqrt 1.5
## `a >> b` is shorthand for `Num.shr a b`.
shr : Int a, Int a -> Int a
## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right.
##
## >>> Float.sqrt 0.0
##
## >>> Float.sqrt -4.0
sqrt : Float a -> [Ok (Float a), InvalidSqrt]*
## This is called `shlWrap` because any bits shifted
## off the end of the number will be wrapped around to
## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.)
shrWrap : Int a, Int a -> Int a
## [Endianness](https://en.wikipedia.org/wiki/Endianness)
Endi : [ Big, Little ]
## The [Endi] argument does not matter for [U8] and [I8], since they have
## only one byte.
toBytes : Num *, Endi -> List U8
## when Num.parseBytes bytes Big is
## Ok { val: f64, rest } -> ...
## Err (ExpectedNum (Float Binary64)) -> ...
parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ ExpectedNum a ]*
## when Num.fromBytes bytes Big is
## Ok f64 -> ...
## Err (ExpectedNum (Float Binary64)) -> ...
fromBytes : List U8, Endi -> Result (Num a) [ ExpectedNum a ]*
## Comparison
## Returns `True` if the first number is less than the second.
##
## `a < b` is shorthand for `Num.isLt a b`.
##
## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
##
## >>> 5
## >>> |> Num.isLt 6
isLt : Num a, Num a -> Bool
## Returns `True` if the first number is less than or equal to the second.
##
## `a <= b` is shorthand for `Num.isLte a b`.
##
## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
isLte : Num a, Num a -> Bool
## Returns `True` if the first number is greater than the second.
##
## `a > b` is shorthand for `Num.isGt a b`.
##
## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
##
## >>> 6
## >>> |> Num.isGt 5
isGt : Num a, Num a -> Bool
## Returns `True` if the first number is greater than or equal to the second.
##
## `a >= b` is shorthand for `Num.isGte a b`.
##
## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
isGte : Num a, Num a -> Bool
## Returns the higher of two numbers.
##
## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
higher : Num a, Num a -> Num a
## Returns the lower of two numbers.
##
## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
lower : Num a, Num a -> Num a
# Branchless implementation that works for all numeric types:
#
# let is_lt = arg1 < arg2;
# let is_eq = arg1 == arg2;
# return (is_lt as i8 - is_eq as i8) + 1;
#
# 1, 1 -> (0 - 1) + 1 == 0 # Eq
# 5, 1 -> (0 - 0) + 1 == 1 # Gt
# 1, 5 -> (1 - 0) + 1 == 2 # Lt
## Returns `Lt` if the first number is less than the second, `Gt` if
## the first is greater than the second, and `Eq` if they're equal.
##
## Although this can be passed to [List.sort], you'll get better performance
## by using [List.sortAsc] or [List.sortDesc] instead.
compare : Num a, Num a -> [ Lt, Eq, Gt ]
## Special Floating-Point Values
## When given a [F64] or [F32] value, returns `False` if that value is
## [*NaN*](Num.isNaN), ∞ or -∞, and `True` otherwise.
##
## Always returns `True` when given a [Dec].
##
## This is the opposite of [isInfinite], except when given [*NaN*](Num.isNaN). Both
## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN).
isFinite : Frac * -> Bool
## When given a [F64] or [F32] value, returns `True` if that value is either
## ∞ or -∞, and `False` otherwise.
##
## Always returns `False` when given a [Dec].
##
## This is the opposite of [isFinite], except when given [*NaN*](Num.isNaN). Both
## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN).
isInfinite : Frac * -> Bool
## When given a [F64] or [F32] value, returns `True` if that value is
## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `False` otherwise.
##
## Always returns `False` when given a [Dec].
##
## >>> Num.isNaN 12.3
##
## >>> Num.isNaN (Num.sqrt -2)
##
## *NaN* is unusual from other numberic values in that:
## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `False` if either argument is *NaN*.
## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `False` if either argument is *NaN*.
##
## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
## floating point standard. Because almost all modern processors are built to
## this standard, deviating from these rules has a significant performance
## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## access to hardware-accelerated performance, Roc follows these rules exactly.
##
## Note that you should never put a *NaN* into a [Set], or use it as the key in
## a [Dict]. The result is entries that can never be removed from those
## collections! See the documentation for [Set.add] and [Dict.insert] for details.
isNaN : Frac * -> Bool

View File

@ -18,6 +18,9 @@ len : Set * -> Nat
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `add : Set 'elem, 'elem -> Set 'elem`
## Make sure never to add a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
add : Set elem, elem -> Set elem
## Drops the given element from the set.

View File

@ -194,7 +194,7 @@ startsWith : Str, Str -> Bool
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithCodePoint '鹏'`
## instead of `Str.startsWithCodePoint "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up mulitple code
## value `40527`.) This will not work for graphemes which take up multiple code
## points, however; `Str.startsWithCodePoint '👩‍👩‍👦‍👦'` would be a compiler error
## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithCodePoint "🕊"` instead.
@ -427,18 +427,68 @@ chomp : Str, Str -> Result Str [ Expected [ ExactStr Str ]* Str ]*
## equal to the given [U32], return whatever comes after that code point.
chompCodePoint : Str, U32 -> Result Str [ Expected [ ExactCodePoint U32 ]* Str ]*
## If the string begins with digits which can represent a valid #U8, return
## that number along with the rest of the string after the digits.
parseU8 : Str -> Result { val : U8, rest : Str } [ Expected [ NumU8 ]* Str ]*
parseI8 : Str -> Result { val : I8, rest : Str } [ Expected [ NumI8 ]* Str ]*
parseU16 : Str -> Result { val : U16, rest : Str } [ Expected [ NumU16 ]* Str ]*
parseI16 : Str -> Result { val : I16, rest : Str } [ Expected [ NumI16 ]* Str ]*
parseU32 : Str -> Result { val : U32, rest : Str } [ Expected [ NumU32 ]* Str ]*
parseI32 : Str -> Result { val : I32, rest : Str } [ Expected [ NumI32 ]* Str ]*
parseU64 : Str -> Result { val : U64, rest : Str } [ Expected [ NumU64 ]* Str ]*
parseI64 : Str -> Result { val : I64, rest : Str } [ Expected [ NumI64 ]* Str ]*
parseU128 : Str -> Result { val : U128, rest : Str } [ Expected [ NumU128 ]* Str ]*
parseI128 : Str -> Result { val : I128, rest : Str } [ Expected [ NumI128 ]* Str ]*
## If the string represents a valid #U8 number, return that number.
##
## For more advanced options, see [parseU8].
toU8 : Str -> Result U8 [ InvalidU8 ]*
toI8 : Str -> Result I8 [ InvalidI8 ]*
toU16 : Str -> Result U16 [ InvalidU16 ]*
toI16 : Str -> Result I16 [ InvalidI16 ]*
toU32 : Str -> Result U32 [ InvalidU32 ]*
toI32 : Str -> Result I32 [ InvalidI32 ]*
toU64 : Str -> Result U64 [ InvalidU64 ]*
toI64 : Str -> Result I64 [ InvalidI64 ]*
toU128 : Str -> Result U128 [ InvalidU128 ]*
toI128 : Str -> Result I128 [ InvalidI128 ]*
toF64 : Str -> Result U128 [ InvalidF64 ]*
toF32 : Str -> Result I128 [ InvalidF32 ]*
toDec : Str -> Result Dec [ InvalidDec ]*
parseF64 : Str -> Result { val : U128, rest : Str } [ Expected [ NumF64 ]* Str ]*
parseF32 : Str -> Result { val : I128, rest : Str } [ Expected [ NumF32 ]* Str ]*
## If the string represents a valid number, return that number.
##
## The exact number type to look for will be inferred from usage. Here's an
## example where the `Err` branch matches `Integer Signed64`, which causes this to
## parse an [I64] because [I64] is defined as `I64 : Num [ Integer [ Signed64 ] ]`.
##
## >>> when Str.toNum "12345" is
## >>> Ok i64 -> "The I64 was: \(i64)"
## >>> Err (ExpectedNum (Integer Signed64)) -> "Not a valid I64!"
##
## If the string is exactly `"NaN"`, `"∞"`, or `"-∞"`, they will be accepted
## only when converting to [F64] or [F32] numbers, and will be translated accordingly.
##
## This never accepts numbers with underscores or commas in them. For more
## advanced options, see [parseNum].
toNum : Str -> Result (Num a) [ ExpectedNum a ]*
## If the string begins with an [Int] or a [finite](Num.isFinite) [Frac], return
## that number along with the rest of the string after it.
##
## The exact number type to look for will be inferred from usage. Here's an
## example where the `Err` branch matches `Float Binary64`, which causes this to
## parse an [F64] because [F64] is defined as `F64 : Num [ Fraction [ Float64 ] ]`.
##
## >>> when Str.parseNum input {} is
## >>> Ok { val: f64, rest } -> "The F64 was: \(f64)"
## >>> Err (ExpectedNum (Fraction Float64)) -> "Not a valid F64!"
##
## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent
## [finite](Num.isFinite) numbers), they will be accepted only when parsing
## [F64] or [F32] numbers, and translated accordingly.
parseNum : Str, NumParseConfig -> Result { val : Num a, rest : Str } [ ExpectedNum a ]*
## Notes:
## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0.
## * For `wholeSep`, `Required` has a payload for how many digits (e.g. "required every 3 digits")
## * For `wholeSep`, `Allowed` allows the separator to appear anywhere.
NumParseConfig :
{
base ? [ Decimal, Hexadecimal, Octal, Binary ],
notation ? [ Standard, Scientific, Any ],
decimalMark ? [ Allowed Str, Required Str, Disallowed ],
decimalDigits ? [ Any, AtLeast U16, Exactly U16 ],
wholeDigits ? [ Any, AtLeast U16, Exactly U16 ],
leadingZeroes ? [ Allowed, Disallowed ],
trailingZeroes ? [ Allowed, Disallowed ],
wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] }
}

View File

@ -1,28 +1,8 @@
use std::fs::File;
use std::io::prelude::Read;
use std::vec::Vec;
const BC_PATH: &str = env!(
"BUILTINS_BC",
"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> {
// In the build script for the builtins module, we compile the builtins bitcode and set
// BUILTINS_BC to the path to the compiled output.
let mut builtins_bitcode = File::open(BC_PATH).expect("Unable to find builtins bitcode source");
let mut buffer = Vec::new();
builtins_bitcode
.read_to_end(&mut buffer)
.expect("Unable to read builtins bitcode");
buffer
}
pub const NUM_ASIN: &str = "roc_builtins.num.asin";
pub const NUM_ACOS: &str = "roc_builtins.num.acos";
pub const NUM_ATAN: &str = "roc_builtins.num.atan";
@ -77,6 +57,7 @@ pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append";
pub const LIST_DROP: &str = "roc_builtins.list.drop";
pub const LIST_SWAP: &str = "roc_builtins.list.swap";
pub const LIST_SINGLE: &str = "roc_builtins.list.single";
pub const LIST_JOIN: &str = "roc_builtins.list.join";
pub const LIST_RANGE: &str = "roc_builtins.list.range";
@ -84,3 +65,4 @@ pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set";
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";

View File

@ -292,7 +292,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// minInt : Int range
add_type!(Symbol::NUM_MIN_INT, int_type(flex(TVAR1)));
// div : Int, Int -> Result Int [ DivByZero ]*
// divInt : Int a, Int a -> Result (Int a) [ DivByZero ]*
let div_by_zero = SolvedType::TagUnion(
vec![(TagName::Global("DivByZero".into()), vec![])],
Box::new(SolvedType::Wildcard),
@ -856,6 +856,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))),
);
// swap : List elem, Nat, Nat -> List elem
add_top_level_function_type!(
Symbol::LIST_SWAP,
vec![list_type(flex(TVAR1)), nat_type(), nat_type()],
Box::new(list_type(flex(TVAR1))),
);
// prepend : List elem, elem -> List elem
add_top_level_function_type!(
Symbol::LIST_PREPEND,

View File

@ -30,7 +30,7 @@ macro_rules! macro_magic {
/// Some builtins cannot be constructed in code gen alone, and need to be defined
/// as separate Roc defs. For example, List.get has this type:
///
/// List.get : List elem, Int -> Result elem [ OutOfBounds ]*
/// List.get : List elem, Nat -> Result elem [ OutOfBounds ]*
///
/// Because this returns an open tag union for its Err type, it's not possible
/// for code gen to return a hardcoded value for OutOfBounds. For example,
@ -85,6 +85,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_MAP2 => list_map2,
LIST_MAP3 => list_map3,
LIST_DROP => list_drop,
LIST_SWAP => list_swap,
LIST_MAP_WITH_INDEX => list_map_with_index,
LIST_KEEP_IF => list_keep_if,
LIST_KEEP_OKS => list_keep_oks,
@ -449,7 +450,7 @@ fn num_add(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumAdd)
}
/// Num.addWrap : Int, Int -> Int
/// Num.addWrap : Int a, Int a -> Int a
fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumAddWrap)
}
@ -548,7 +549,7 @@ fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSub)
}
/// Num.subWrap : Int, Int -> Int
/// Num.subWrap : Int a, Int a -> Int a
fn num_sub_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSubWrap)
}
@ -647,7 +648,7 @@ fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumMul)
}
/// Num.mulWrap : Int, Int -> Int
/// Num.mulWrap : Int a, Int a -> Int a
fn num_mul_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumMulWrap)
}
@ -1151,7 +1152,7 @@ fn num_ceiling(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.powInt : Int, Int -> Int
/// Num.powInt : Int a, Int a -> Int a
fn num_pow_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
@ -1250,17 +1251,17 @@ fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.bitwiseAnd : Int, Int -> Int
/// Num.bitwiseAnd : Int a, Int a -> Int a
fn num_bitwise_and(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumBitwiseAnd)
}
/// Num.bitwiseXor : Int, Int -> Int
/// Num.bitwiseXor : Int a, Int a -> Int a
fn num_bitwise_xor(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumBitwiseXor)
}
/// Num.bitwiseOr: Int, Int -> Int
/// Num.bitwiseOr: Int a, Int a -> Int a
fn num_bitwise_or(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumBitwiseOr)
}
@ -1666,7 +1667,7 @@ fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.repeat : elem, Int -> List elem
/// List.repeat : elem, Nat -> List elem
fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let elem_var = var_store.fresh();
let len_var = var_store.fresh();
@ -1808,7 +1809,7 @@ fn list_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.set : List elem, Int, elem -> List elem
/// List.set : List elem, Nat, elem -> List elem
///
/// List.set :
/// Attr (w | u | v) (List (Attr u a)),
@ -1882,6 +1883,36 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def {
list_ret_var,
)
}
/// List.swap : List elem, Nat, Nat -> List elem
fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index1_var = var_store.fresh();
let index2_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListSwap,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(index1_var, Var(Symbol::ARG_2)),
(index2_var, Var(Symbol::ARG_3)),
],
ret_var: list_var,
};
defn(
symbol,
vec![
(list_var, Symbol::ARG_1),
(index1_var, Symbol::ARG_2),
(index2_var, Symbol::ARG_3),
],
var_store,
body,
list_var,
)
}
/// List.drop : List elem, Nat -> List elem
fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -2267,7 +2298,7 @@ fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
let make_err = tag(
"Err",
vec![tag("OutOfBounds", Vec::new(), var_store)],
vec![tag("KeyNotFound", Vec::new(), var_store)],
var_store,
);
@ -2500,7 +2531,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.rem : Int, Int -> Result Int [ DivByZero ]*
/// Num.rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
@ -2559,7 +2590,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.isMultipleOf : Int, Int -> Bool
/// Num.isMultipleOf : Int a, Int a -> Bool
fn num_is_multiple_of(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::NumIsMultipleOf, var_store)
}
@ -2665,7 +2696,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.div : Int, Int -> Result Int [ DivByZero ]*
/// Num.div : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let num_var = var_store.fresh();

View File

@ -561,14 +561,14 @@ pub fn sort_can_defs(
)));
declarations.push(Declaration::InvalidCycle(entries));
// other groups may depend on the symbols defined here, so
// also push this cycle onto the groups
groups.push(cycle);
} else {
// slightly inefficient, because we know this becomes exactly one DeclareRec already
groups.push(cycle);
}
// if it's an invalid cycle, other groups may depend on the
// symbols defined here, so also push this cycle onto the groups
//
// if it's not an invalid cycle, this is slightly inefficient,
// because we know this becomes exactly one DeclareRec already
groups.push(cycle);
}
// now we have a collection of groups whose dependencies are not cyclic.

View File

@ -60,7 +60,6 @@ pub enum Expr {
Float(Variable, Variable, f64),
Str(InlinableString),
List {
list_var: Variable, // required for uniqueness of the list
elem_var: Variable,
loc_elems: Vec<Located<Expr>>,
},
@ -304,7 +303,6 @@ pub fn canonicalize_expr<'a>(
if loc_elems.is_empty() {
(
List {
list_var: var_store.fresh(),
elem_var: var_store.fresh(),
loc_elems: Vec::new(),
},
@ -331,7 +329,6 @@ pub fn canonicalize_expr<'a>(
(
List {
list_var: var_store.fresh(),
elem_var: var_store.fresh(),
loc_elems: can_elems,
},
@ -603,7 +600,7 @@ pub fn canonicalize_expr<'a>(
// A "when" with no branches is a runtime error, but it will mess things up
// if code gen mistakenly thinks this is a tail call just because its condition
// happend to be one. (The condition gave us our initial output value.)
// happened to be one. (The condition gave us our initial output value.)
if branches.is_empty() {
output.tail_call = None;
}
@ -1234,7 +1231,6 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ ForeignCall { .. } => other,
List {
list_var,
elem_var,
loc_elems,
} => {
@ -1250,7 +1246,6 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
}
List {
list_var,
elem_var,
loc_elems: new_elems,
}

View File

@ -345,7 +345,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
)
}
If(if_thens, final_else_branch) => {
// If does not get desugared into `when` so we can give more targetted error messages during type checking.
// If does not get desugared into `when` so we can give more targeted error messages during type checking.
let desugared_final_else = &*arena.alloc(desugar_expr(arena, &final_else_branch));
let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena);

View File

@ -13,7 +13,7 @@ use roc_types::types::Type::{self, *};
#[inline(always)]
pub fn int_literal(
num_var: Variable,
percision_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
) -> Constraint {
@ -25,7 +25,7 @@ pub fn int_literal(
And(vec![
Eq(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(percision_var)), region),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Int,
region,
),

View File

@ -96,7 +96,7 @@ pub fn constrain_expr(
expected: Expected<Type>,
) -> Constraint {
match expr {
Int(var, percision, _) => int_literal(*var, *percision, expected, region),
Int(var, precision, _) => int_literal(*var, *precision, expected, region),
Num(var, _) => exists(
vec![*var],
Eq(
@ -106,7 +106,7 @@ pub fn constrain_expr(
region,
),
),
Float(var, percision, _) => float_literal(*var, *percision, expected, region),
Float(var, precision, _) => float_literal(*var, *precision, expected, region),
EmptyRecord => constrain_empty_record(region, expected),
Expr::Record { record_var, fields } => {
if fields.is_empty() {
@ -220,7 +220,6 @@ pub fn constrain_expr(
List {
elem_var,
loc_elems,
list_var: _unused,
} => {
if loc_elems.is_empty() {
exists(

View File

@ -146,7 +146,7 @@ pub fn pre_constrain_imports(
// Translate referenced symbols into constraints. We do this on the main
// thread because we need exclusive access to the exposed_types map, in order
// to get the necessary constraint info for any aliases we imported. We also
// resolve builtin types now, so we can use a refernce to stdlib instead of
// resolve builtin types now, so we can use a reference to stdlib instead of
// having to either clone it or recreate it from scratch on the other thread.
for &symbol in references.iter() {
let module_id = symbol.module_id();

View File

@ -116,7 +116,7 @@ mod test_fmt {
}
#[test]
fn force_space_at_begining_of_comment() {
fn force_space_at_beginning_of_comment() {
expr_formats_to(
indoc!(
r#"

View File

@ -1,5 +1,6 @@
[package]
name = "roc_gen_dev"
description = "The development backend for the Roc compiler"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"

View File

@ -5,7 +5,7 @@ It goes from Roc's [mono ir](https://github.com/rtfeldman/roc/blob/trunk/compile
## General Process
The backend is essentially defined as two recursive match statment over the mono ir.
The backend is essentially defined as two recursive match statement over the mono ir.
The first pass is used to do simple linear scan lifetime analysis.
In the future it may be expanded to add a few other quick optimizations.
The second pass is the actual meat of the backend that generates the byte buffer of output binary.
@ -62,7 +62,7 @@ Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob
Adding a new builtin to the dev backend can be pretty simple.
Here is [an example](https://github.com/rtfeldman/roc/pull/893/files) of adding `Num.Sub`.
This is the general procede I follow with some helpful links:
This is the general procedure I follow with some helpful links:
1. Find a feature that is just n+1.
For example, since we already have integers, adding a builtin that functions on them should be n+1.

View File

@ -205,7 +205,7 @@ pub struct Backend64Bit<
float_used_callee_saved_regs: MutSet<FloatReg>,
stack_size: u32,
// The ammount of stack space needed to pass args for function calling.
// The amount of stack space needed to pass args for function calling.
fn_call_stack_size: u32,
}
@ -409,7 +409,7 @@ impl<
Ok(())
}
x => Err(format!(
"recieving return type, {:?}, is not yet implemented",
"receiving return type, {:?}, is not yet implemented",
x
)),
}

View File

@ -236,7 +236,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
Layout::Builtin(Builtin::Float64) => {}
x => {
return Err(format!(
"recieving return type, {:?}, is not yet implemented",
"receiving return type, {:?}, is not yet implemented",
x
));
}
@ -530,7 +530,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(Builtin::Float64) => {}
x => {
return Err(format!(
"recieving return type, {:?}, is not yet implemented",
"receiving return type, {:?}, is not yet implemented",
x
));
}
@ -765,12 +765,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
src1: X86_64GeneralReg,
imm32: i32,
) {
if dst == src1 {
add_reg64_imm32(buf, dst, imm32);
} else {
if dst != src1 {
mov_reg64_reg64(buf, dst, src1);
add_reg64_imm32(buf, dst, imm32);
}
add_reg64_imm32(buf, dst, imm32);
}
#[inline(always)]
fn add_reg64_reg64_reg64(
@ -821,12 +820,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) {
if dst == src1 {
imul_reg64_reg64(buf, dst, src2);
} else {
if dst != src1 {
mov_reg64_reg64(buf, dst, src1);
imul_reg64_reg64(buf, dst, src2);
}
imul_reg64_reg64(buf, dst, src2);
}
#[inline(always)]
@ -926,12 +924,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
src1: X86_64GeneralReg,
imm32: i32,
) {
if dst == src1 {
sub_reg64_imm32(buf, dst, imm32);
} else {
if dst != src1 {
mov_reg64_reg64(buf, dst, src1);
sub_reg64_imm32(buf, dst, imm32);
}
sub_reg64_imm32(buf, dst, imm32);
}
#[inline(always)]
fn sub_reg64_reg64_reg64(
@ -940,12 +937,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) {
if dst == src1 {
sub_reg64_reg64(buf, dst, src2);
} else {
if dst != src1 {
mov_reg64_reg64(buf, dst, src1);
sub_reg64_reg64(buf, dst, src2);
}
sub_reg64_reg64(buf, dst, src2);
}
#[inline(always)]

View File

@ -106,6 +106,7 @@ where
call,
pass,
fail: _,
exception_id: _,
} => {
// for now, treat invoke as a normal call
self.build_expr(symbol, &Expr::Call(call.clone()), layout)?;
@ -144,7 +145,7 @@ where
) -> Result<(), String>;
/// 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 referred to later.
fn build_expr(
&mut self,
sym: &Symbol,
@ -229,7 +230,7 @@ where
}
/// build_run_low_level builds the low level opertation and outputs to 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 referred to later.
fn build_run_low_level(
&mut self,
sym: &Symbol,
@ -443,7 +444,13 @@ where
self.set_last_seen(*sym, stmt);
}
}
Expr::AccessAtIndex { structure, .. } => {
Expr::StructAtIndex { structure, .. } => {
self.set_last_seen(*structure, stmt);
}
Expr::GetTagId { structure, .. } => {
self.set_last_seen(*structure, stmt);
}
Expr::UnionAtIndex { structure, .. } => {
self.set_last_seen(*structure, stmt);
}
Expr::Array { elems, .. } => {
@ -486,6 +493,7 @@ where
call,
pass,
fail: _,
exception_id: _,
} => {
// for now, treat invoke as a normal call
self.set_last_seen(*symbol, stmt);
@ -508,7 +516,7 @@ where
Stmt::Ret(sym) => {
self.set_last_seen(*sym, stmt);
}
Stmt::Rethrow => {}
Stmt::Resume(_exception_id) => {}
Stmt::Refcounting(modify, following) => {
let sym = modify.get_symbol();
@ -517,7 +525,7 @@ where
}
Stmt::Join {
parameters,
continuation,
body: continuation,
remainder,
..
} => {

View File

@ -9,8 +9,7 @@ use object::{
};
use roc_collections::all::MutMap;
use roc_module::symbol;
use roc_mono::ir::Proc;
use roc_mono::layout::Layout;
use roc_mono::ir::{Proc, ProcLayout};
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
// This is used by some code below which is currently commented out.
@ -22,7 +21,7 @@ use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Tripl
pub fn build_module<'a>(
env: &'a Env,
target: &Triple,
procedures: MutMap<(symbol::Symbol, Layout<'a>), Proc<'a>>,
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<Object, String> {
match target {
Triple {
@ -145,7 +144,7 @@ fn generate_wrapper<'a, B: Backend<'a>>(
fn build_object<'a, B: Backend<'a>>(
env: &'a Env,
procedures: MutMap<(symbol::Symbol, Layout<'a>), Proc<'a>>,
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
mut backend: B,
mut output: Object,
) -> Result<Object, String> {
@ -187,7 +186,7 @@ fn build_object<'a, B: Backend<'a>>(
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
for ((sym, layout), proc) in procedures {
let fn_name = layout_ids
.get(sym, &layout)
.get_toplevel(sym, &layout)
.to_symbol_string(sym, &env.interns);
let section_id = output.add_section(

View File

@ -69,8 +69,8 @@ pub fn helper<'a>(
let mut procedures = MutMap::default();
for ((symbol, top_level), proc) in top_procedures {
procedures.insert((symbol, arena.alloc(top_level).full()), proc);
for (key, proc) in top_procedures {
procedures.insert(key, proc);
}
/*
@ -87,17 +87,12 @@ pub fn helper<'a>(
println!("=================================\n");
*/
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.keys().copied().next().unwrap();
let main_fn_layout = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.map(|t| t.1)
.unwrap();
let main_fn_symbol = loaded.entry_point.symbol;
let main_fn_layout = loaded.entry_point.layout;
let mut layout_ids = roc_mono::layout::LayoutIds::default();
let main_fn_name = layout_ids
.get(main_fn_symbol, &main_fn_layout)
.get_toplevel(main_fn_symbol, &main_fn_layout)
.to_symbol_string(main_fn_symbol, &interns);
let mut lines = Vec::new();

View File

@ -0,0 +1,41 @@
[package]
name = "roc_gen_llvm"
description = "The LLVM backend for the Roc compiler"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
morphic_lib = { path = "../../vendor/morphic_lib" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10"
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_load = { path = "../load" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"

View File

@ -2,7 +2,7 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
// we actually want to compare against the literal float bits
#![allow(clippy::clippy::float_cmp)]
#![allow(clippy::float_cmp)]
pub mod llvm;

View File

@ -1,16 +1,16 @@
/// Helpers for interacting with the zig that generates bitcode
use crate::debug_info_init;
use crate::llvm::build::{set_name, Env, C_CALL_CONV, FAST_CALL_CONV};
use crate::llvm::build::{struct_from_fields, Env, C_CALL_CONV, FAST_CALL_CONV, TAG_DATA_INDEX};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::{
decrement_refcount_layout, increment_n_refcount_layout, increment_refcount_layout,
};
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Layout, LayoutIds};
use roc_mono::layout::{Layout, LayoutIds, UnionLayout};
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -66,6 +66,126 @@ const ARGUMENT_SYMBOLS: [Symbol; 8] = [
Symbol::ARG_8,
];
pub fn build_has_tag_id<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
function: FunctionValue<'ctx>,
union_layout: UnionLayout<'a>,
) -> FunctionValue<'ctx> {
let fn_name: &str = &format!("{}_has_tag_id", function.get_name().to_string_lossy());
// currently the code assumes we're dealing with a non-recursive layout
debug_assert!(matches!(union_layout, UnionLayout::NonRecursive(_)));
match env.module.get_function(fn_name) {
Some(function_value) => function_value,
None => build_has_tag_id_help(env, union_layout, &fn_name),
}
}
fn build_has_tag_id_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
union_layout: UnionLayout<'a>,
fn_name: &str,
) -> FunctionValue<'ctx> {
let i8_ptr_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
let argument_types: &[BasicTypeEnum] = &[env.context.i16_type().into(), i8_ptr_type.into()];
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let output_type = crate::llvm::convert::zig_has_tag_id_type(env);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
output_type.into(),
&argument_types,
);
// called from zig, must use C calling convention
function_value.set_call_conventions(C_CALL_CONV);
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 it = function_value.get_param_iter();
let arguments =
bumpalo::collections::Vec::from_iter_in(it.take(argument_types.len()), env.arena);
for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS.iter()) {
argument.set_name(name.ident_string(&env.interns));
}
match arguments.as_slice() {
[tag_id, tag_value_ptr] => {
let tag_type = basic_type_from_layout(env, &Layout::Union(union_layout));
let argument_cast = env
.builder
.build_bitcast(
*tag_value_ptr,
tag_type.ptr_type(AddressSpace::Generic),
"load_opaque",
)
.into_pointer_value();
let tag_value = env.builder.build_load(argument_cast, "get_value");
let actual_tag_id = {
let tag_id_i64 =
crate::llvm::build::get_tag_id(env, function_value, &union_layout, tag_value);
env.builder
.build_int_cast(tag_id_i64, env.context.i16_type(), "to_i16")
};
let answer = env.builder.build_int_compare(
inkwell::IntPredicate::EQ,
tag_id.into_int_value(),
actual_tag_id,
"compare",
);
let tag_data_ptr = {
let data_index = env
.context
.i64_type()
.const_int(TAG_DATA_INDEX as u64, false);
let ptr = unsafe {
env.builder.build_gep(
tag_value_ptr.into_pointer_value(),
&[data_index],
"get_data_ptr",
)
};
env.builder.build_bitcast(ptr, i8_ptr_type, "to_opaque")
};
let field_vals = [(0, answer.into()), (1, tag_data_ptr)];
let output = struct_from_fields(env, output_type, field_vals.iter().copied());
env.builder.build_return(Some(&output));
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
function_value
}
_ => unreachable!(),
}
}
pub fn build_transform_caller<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
function: FunctionValue<'ctx>,
@ -110,6 +230,9 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
&(bumpalo::vec![ in env.arena; BasicTypeEnum::PointerType(arg_type); argument_layouts.len() + 2 ]),
);
// called from zig, must use C calling convention
function_value.set_call_conventions(C_CALL_CONV);
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);
@ -122,13 +245,13 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
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));
closure_ptr.set_name(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));
argument.set_name(name.ident_string(&env.interns));
}
let mut arguments_cast =
@ -148,9 +271,6 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
}
match closure_data_layout {
Layout::FunctionPointer(_, _) => {
// do nothing
}
Layout::Closure(_, lambda_set, _) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
// do nothing
@ -303,6 +423,9 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
),
};
// called from zig, must use C calling convention
function_value.set_call_conventions(C_CALL_CONV);
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);
@ -316,7 +439,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
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));
value_ptr.set_name(Symbol::ARG_1.ident_string(&env.interns));
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
@ -334,7 +457,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
}
Mode::IncN => {
let n = it.next().unwrap().into_int_value();
set_name(n.into(), Symbol::ARG_2.ident_string(&env.interns));
n.set_name(Symbol::ARG_2.ident_string(&env.interns));
increment_n_refcount_layout(env, function_value, layout_ids, n, value, layout);
}
@ -381,6 +504,9 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
&[arg_type.into(), arg_type.into()],
);
// called from zig, must use C calling convention
function_value.set_call_conventions(C_CALL_CONV);
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);
@ -395,8 +521,8 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
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));
value_ptr1.set_name(Symbol::ARG_1.ident_string(&env.interns));
value_ptr2.set_name(Symbol::ARG_2.ident_string(&env.interns));
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
@ -455,6 +581,9 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
&[arg_type.into(), arg_type.into(), arg_type.into()],
);
// called from zig, must use C calling convention
function_value.set_call_conventions(C_CALL_CONV);
// we expose this function to zig; must use c calling convention
function_value.set_call_conventions(C_CALL_CONV);
@ -473,9 +602,9 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let value_ptr1 = it.next().unwrap().into_pointer_value();
let value_ptr2 = it.next().unwrap().into_pointer_value();
set_name(closure_ptr.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value_ptr1.into(), Symbol::ARG_2.ident_string(&env.interns));
set_name(value_ptr2.into(), Symbol::ARG_3.ident_string(&env.interns));
closure_ptr.set_name(Symbol::ARG_1.ident_string(&env.interns));
value_ptr1.set_name(Symbol::ARG_2.ident_string(&env.interns));
value_ptr2.set_name(Symbol::ARG_3.ident_string(&env.interns));
let value_type = basic_type_from_layout(env, layout);
let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
@ -496,7 +625,6 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let default = [value1, value2];
let arguments_cast = match closure_data_layout {
Layout::FunctionPointer(_, _) => &default,
Layout::Closure(_, lambda_set, _) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
&default

View File

@ -3,14 +3,14 @@ use crate::llvm::bitcode::{
build_dec_wrapper, build_eq_wrapper, build_inc_wrapper, call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
complex_bitcast, load_symbol, load_symbol_and_layout, set_name, Env, RocFunctionCall, Scope,
complex_bitcast, load_symbol, load_symbol_and_layout, Env, RocFunctionCall, Scope,
};
use crate::llvm::build_list::{layout_width, pass_as_opaque};
use crate::llvm::convert::{as_const_zero, basic_type_from_layout};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::Mode;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::BasicType;
use inkwell::values::{BasicValueEnum, FunctionValue, StructValue};
use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, StructValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
@ -326,7 +326,7 @@ pub fn dict_get<'a, 'ctx, 'env>(
let done_block = env.context.append_basic_block(parent, "done");
let value_bt = basic_type_from_layout(env, value_layout);
let default = as_const_zero(&value_bt);
let default = value_bt.const_zero();
env.builder
.build_conditional_branch(flag, if_not_null, done_block);
@ -635,7 +635,6 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
#[allow(clippy::too_many_arguments)]
pub fn dict_walk<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
dict: BasicValueEnum<'ctx>,
accum: BasicValueEnum<'ctx>,
@ -660,9 +659,6 @@ pub fn dict_walk<'a, 'ctx, 'env>(
let output_ptr = builder.build_alloca(accum_bt, "output_ptr");
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
call_void_bitcode_fn(
env,
&[
@ -676,8 +672,6 @@ pub fn dict_walk<'a, 'ctx, 'env>(
layout_width(env, key_layout),
layout_width(env, value_layout),
layout_width(env, accum_layout),
inc_key_fn.as_global_value().as_pointer_value().into(),
inc_value_fn.as_global_value().as_pointer_value().into(),
env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"),
],
&bitcode::DICT_WALK,
@ -836,8 +830,8 @@ fn build_hash_wrapper<'a, 'ctx, 'env>(
let seed_arg = it.next().unwrap().into_int_value();
let value_ptr = it.next().unwrap().into_pointer_value();
set_name(seed_arg.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value_ptr.into(), Symbol::ARG_2.ident_string(&env.interns));
seed_arg.set_name(Symbol::ARG_1.ident_string(&env.interns));
value_ptr.set_name(Symbol::ARG_2.ident_string(&env.interns));
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);

View File

@ -1,11 +1,13 @@
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::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
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 inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
};
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
@ -56,11 +58,6 @@ fn build_hash_layout<'a, 'ctx, 'env>(
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)
}
@ -91,11 +88,7 @@ fn build_hash_layout<'a, 'ctx, 'env>(
}
},
Layout::Pointer(_) => {
unreachable!("unused")
}
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
Layout::Closure(_, _, _) => {
unreachable!("the type system will guarantee these are never hashed")
}
}
@ -157,7 +150,7 @@ fn hash_builtin<'a, 'ctx, 'env>(
Builtin::Set(_) => {
todo!("Implement Hash for Set")
}
Builtin::List(_, element_layout) => build_hash_list(
Builtin::List(element_layout) => build_hash_list(
env,
layout_ids,
layout,
@ -241,8 +234,8 @@ fn build_hash_struct_help<'a, 'ctx, 'env>(
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));
seed.set_name(Symbol::ARG_1.ident_string(&env.interns));
value.set_name(Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
@ -382,8 +375,8 @@ fn build_hash_tag_help<'a, 'ctx, 'env>(
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));
seed.set_name(Symbol::ARG_1.ident_string(&env.interns));
value.set_name(Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
@ -408,13 +401,15 @@ fn hash_tag<'a, 'ctx, 'env>(
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");
let tag_id_layout = union_layout.tag_id_layout();
let tag_id_basic_type = basic_type_from_layout(env, &tag_id_layout);
let merge_phi = env.builder.build_phi(seed.get_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 current_tag_id = get_tag_id(env, parent, union_layout, tag);
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
@ -422,7 +417,6 @@ fn hash_tag<'a, 'ctx, 'env>(
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, &struct_layout);
@ -431,6 +425,24 @@ fn hash_tag<'a, 'ctx, 'env>(
let as_struct =
cast_block_of_memory_to_tag(env.builder, tag.into_struct_value(), wrapper_type);
// hash the tag id
let hash_bytes = store_and_use_as_u8_ptr(
env,
tag_id_basic_type
.into_int_type()
.const_int(tag_id as u64, false)
.into(),
&tag_id_layout,
);
let seed = hash_bitcode_fn(
env,
seed,
hash_bytes,
tag_id_layout.stack_size(env.ptr_bytes),
);
// hash the tag data
let answer = build_hash_struct(
env,
layout_ids,
@ -444,7 +456,7 @@ fn hash_tag<'a, 'ctx, 'env>(
env.builder.build_unconditional_branch(merge_block);
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
current_tag_id.get_type().const_int(tag_id as u64, false),
block,
));
}
@ -453,11 +465,10 @@ fn hash_tag<'a, 'ctx, 'env>(
let default = cases.pop().unwrap().1;
env.builder.build_switch(tag_id, default, &cases);
env.builder.build_switch(current_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 current_tag_id = get_tag_id(env, parent, union_layout, tag);
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
@ -465,6 +476,23 @@ fn hash_tag<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
// hash the tag id
let hash_bytes = store_and_use_as_u8_ptr(
env,
tag_id_basic_type
.into_int_type()
.const_int(tag_id as u64, false)
.into(),
&tag_id_layout,
);
let seed = hash_bitcode_fn(
env,
seed,
hash_bytes,
tag_id_layout.stack_size(env.ptr_bytes),
);
// hash the tag data
let answer = hash_ptr_to_struct(
env,
layout_ids,
@ -478,7 +506,7 @@ fn hash_tag<'a, 'ctx, 'env>(
env.builder.build_unconditional_branch(merge_block);
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
current_tag_id.get_type().const_int(tag_id as u64, false),
block,
));
}
@ -487,11 +515,10 @@ fn hash_tag<'a, 'ctx, 'env>(
let default = cases.pop().unwrap().1;
env.builder.build_switch(tag_id, default, &cases);
env.builder.build_switch(current_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");
@ -520,7 +547,10 @@ fn hash_tag<'a, 'ctx, 'env>(
env.builder.build_unconditional_branch(merge_block);
}
}
NullableWrapped { other_tags, .. } => {
NullableWrapped {
other_tags,
nullable_id,
} => {
let tag = tag.into_pointer_value();
let is_null = env.builder.build_is_null(tag, "is_null");
@ -541,31 +571,57 @@ fn hash_tag<'a, 'ctx, 'env>(
}
{
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() {
for (mut tag_id, field_layouts) in other_tags.iter().enumerate() {
if tag_id >= *nullable_id as usize {
tag_id += 1;
}
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);
// hash the tag id
let hash_bytes = store_and_use_as_u8_ptr(
env,
tag_id_basic_type
.into_int_type()
.const_int(tag_id as u64, false)
.into(),
&tag_id_layout,
);
let seed1 = hash_bitcode_fn(
env,
seed,
hash_bytes,
tag_id_layout.stack_size(env.ptr_bytes),
);
// hash tag data
let answer = hash_ptr_to_struct(
env,
layout_ids,
union_layout,
field_layouts,
seed1,
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),
tag_id_basic_type
.into_int_type()
.const_int(tag_id as u64, false),
block,
));
}
env.builder.position_at_end(hash_other_block);
let tag_id = get_tag_id(env, parent, union_layout, tag.into());
let default = cases.pop().unwrap().1;
env.builder.build_switch(tag_id, default, &cases);
@ -662,8 +718,8 @@ fn build_hash_list_help<'a, 'ctx, 'env>(
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));
seed.set_name(Symbol::ARG_1.ident_string(&env.interns));
value.set_name(Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
@ -772,18 +828,27 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
) -> IntValue<'ctx> {
use inkwell::types::BasicType;
let struct_layout = Layout::Struct(field_layouts);
let wrapper_type = basic_type_from_layout(env, &struct_layout);
debug_assert!(wrapper_type.is_struct_type());
let wrapper_type = basic_type_from_layout(env, &Layout::Union(*union_layout));
// cast the opaque pointer to a pointer of the correct shape
let wrapper_ptr = env
.builder
.build_bitcast(tag, wrapper_type, "opaque_to_correct")
.into_pointer_value();
let struct_ptr = env
.builder
.build_struct_gep(wrapper_ptr, TAG_DATA_INDEX, "get_tag_data")
.unwrap();
let struct_layout = Layout::Struct(field_layouts);
let struct_type = basic_type_from_layout(env, &struct_layout);
let struct_ptr = env
.builder
.build_bitcast(
tag,
wrapper_type.ptr_type(inkwell::AddressSpace::Generic),
"opaque_to_correct",
struct_ptr,
struct_type.ptr_type(inkwell::AddressSpace::Generic),
"cast_tag_data",
)
.into_pointer_value();
@ -811,14 +876,13 @@ fn store_and_use_as_u8_ptr<'a, 'ctx, 'env>(
let alloc = env.builder.build_alloca(basic_type, "store");
env.builder.build_store(alloc, value);
let u8_ptr = env
.context
.i8_type()
.ptr_type(inkwell::AddressSpace::Generic);
env.builder
.build_bitcast(
alloc,
env.context
.i8_type()
.ptr_type(inkwell::AddressSpace::Generic),
"as_u8_ptr",
)
.build_bitcast(alloc, u8_ptr, "as_u8_ptr")
.into_pointer_value()
}
@ -837,34 +901,3 @@ fn hash_bitcode_fn<'a, 'ctx, 'env>(
)
.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()
}

View File

@ -1,20 +1,21 @@
#![allow(clippy::too_many_arguments)]
use crate::llvm::bitcode::{
build_dec_wrapper, build_eq_wrapper, build_inc_n_wrapper, build_inc_wrapper,
build_transform_caller, call_bitcode_fn, call_void_bitcode_fn,
build_dec_wrapper, build_eq_wrapper, build_has_tag_id, build_inc_n_wrapper, build_inc_wrapper,
call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, RocFunctionCall,
};
use crate::llvm::convert::{basic_type_from_layout, get_ptr_type};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::increment_refcount_layout;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::types::{BasicTypeEnum, PointerType};
use inkwell::types::{BasicType, BasicTypeEnum, PointerType};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use morphic_lib::UpdateMode;
use roc_builtins::bitcode;
use roc_mono::layout::{Builtin, InPlace, Layout, LayoutIds, MemoryMode};
use roc_mono::layout::{Builtin, Layout, LayoutIds};
fn list_returned_from_zig<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -83,7 +84,6 @@ pub fn pass_as_opaque<'a, 'ctx, 'env>(
/// List.single : a -> List a
pub fn list_single<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_inplace: InPlace,
element: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
@ -124,7 +124,6 @@ pub fn list_repeat<'a, 'ctx, 'env>(
/// List.prepend : List elem, elem -> List elem
pub fn list_prepend<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
original_wrapper: StructValue<'ctx>,
elem: BasicValueEnum<'ctx>,
elem_layout: &Layout<'a>,
@ -135,7 +134,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
// Load the usize length from the wrapper.
let len = list_len(builder, original_wrapper);
let elem_type = basic_type_from_layout(env, elem_layout);
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
let ptr_type = elem_type.ptr_type(AddressSpace::Generic);
let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type);
// The output list length, which is the old list length + 1
@ -146,7 +145,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
);
// Allocate space for the new array that we'll copy into.
let clone_ptr = allocate_list(env, inplace, elem_layout, new_list_len);
let clone_ptr = allocate_list(env, elem_layout, new_list_len);
builder.build_store(clone_ptr, elem);
@ -189,19 +188,18 @@ pub fn list_prepend<'a, 'ctx, 'env>(
/// List.join : List (List elem) -> List elem
pub fn list_join<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_inplace: InPlace,
_parent: FunctionValue<'ctx>,
outer_list: BasicValueEnum<'ctx>,
outer_list_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
match outer_list_layout {
Layout::Builtin(Builtin::EmptyList)
| Layout::Builtin(Builtin::List(_, Layout::Builtin(Builtin::EmptyList))) => {
| Layout::Builtin(Builtin::List(Layout::Builtin(Builtin::EmptyList))) => {
// If the input list is empty, or if it is a list of empty lists
// then simply return an empty list
empty_list(env)
}
Layout::Builtin(Builtin::List(_, Layout::Builtin(Builtin::List(_, element_layout)))) => {
Layout::Builtin(Builtin::List(Layout::Builtin(Builtin::List(element_layout)))) => {
call_bitcode_fn_returns_list(
env,
&[
@ -221,23 +219,16 @@ pub fn list_join<'a, 'ctx, 'env>(
/// List.reverse : List elem -> List elem
pub fn list_reverse<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_output_inplace: InPlace,
list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let (_, element_layout) = match *list_layout {
Layout::Builtin(Builtin::EmptyList) => (
InPlace::InPlace,
let element_layout = match *list_layout {
Layout::Builtin(Builtin::EmptyList) => {
// this pointer will never actually be dereferenced
Layout::Builtin(Builtin::Int64),
),
Layout::Builtin(Builtin::List(memory_mode, elem_layout)) => (
match memory_mode {
MemoryMode::Unique => InPlace::InPlace,
MemoryMode::Refcounted => InPlace::Clone,
},
*elem_layout,
),
Layout::Builtin(Builtin::Int64)
}
Layout::Builtin(Builtin::List(elem_layout)) => *elem_layout,
_ => unreachable!("Invalid layout {:?} in List.reverse", list_layout),
};
@ -264,9 +255,9 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
let builder = env.builder;
match list_layout {
Layout::Builtin(Builtin::List(_, elem_layout)) => {
Layout::Builtin(Builtin::List(elem_layout)) => {
let elem_type = basic_type_from_layout(env, elem_layout);
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
let ptr_type = elem_type.ptr_type(AddressSpace::Generic);
// Load the pointer to the array data
let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
@ -293,7 +284,6 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
/// List.append : List elem, elem -> List elem
pub fn list_append<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_inplace: InPlace,
original_wrapper: StructValue<'ctx>,
element: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
@ -310,7 +300,28 @@ pub fn list_append<'a, 'ctx, 'env>(
)
}
/// List.drop : List elem, Nat -> List elem
/// List.swap : List elem, Nat, Nat -> List elem
pub fn list_swap<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
original_wrapper: StructValue<'ctx>,
index_1: IntValue<'ctx>,
index_2: IntValue<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, original_wrapper.into()),
env.alignment_intvalue(&element_layout),
layout_width(env, &element_layout),
index_1.into(),
index_2.into(),
],
&bitcode::LIST_SWAP,
)
}
/// List.drop : List elem, Nat, Nat -> List elem
pub fn list_drop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -340,6 +351,7 @@ pub fn list_set<'a, 'ctx, 'env>(
index: IntValue<'ctx>,
element: BasicValueEnum<'ctx>,
element_layout: &'a Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
@ -349,19 +361,32 @@ pub fn list_set<'a, 'ctx, 'env>(
env.context.i8_type().ptr_type(AddressSpace::Generic),
);
let new_bytes = call_bitcode_fn(
env,
&[
bytes.into(),
length.into(),
env.alignment_intvalue(&element_layout),
index.into(),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
&bitcode::LIST_SET,
);
let new_bytes = match update_mode {
UpdateMode::InPlace => call_bitcode_fn(
env,
&[
bytes.into(),
index.into(),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_SET_IN_PLACE,
),
UpdateMode::Immutable => call_bitcode_fn(
env,
&[
bytes.into(),
length.into(),
env.alignment_intvalue(&element_layout),
index.into(),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_SET,
),
};
store_list(env, new_bytes.into_pointer_value(), length)
}
@ -400,6 +425,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
function_call_return_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>,
@ -440,6 +466,18 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
);
}
ListWalk::WalkUntil | ListWalk::WalkBackwardsUntil => {
let function = env
.builder
.get_insert_block()
.unwrap()
.get_parent()
.unwrap();
let has_tag_id = match function_call_return_layout {
Layout::Union(union_layout) => build_has_tag_id(env, function, *union_layout),
_ => unreachable!(),
};
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
call_void_bitcode_fn(
env,
@ -452,7 +490,9 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
pass_as_opaque(env, default_ptr),
env.alignment_intvalue(&element_layout),
layout_width(env, element_layout),
layout_width(env, function_call_return_layout),
layout_width(env, default_layout),
has_tag_id.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
pass_as_opaque(env, result_ptr),
],
@ -586,20 +626,26 @@ pub fn list_keep_oks<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
function_layout: &Layout<'a>,
// Layout of the `Result after *`
result_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
before_layout: &Layout<'a>,
after_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
// Layout of the `Result after *`
let result_layout = match function_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
let function = env
.builder
.get_insert_block()
.unwrap()
.get_parent()
.unwrap();
let has_tag_id = match result_layout {
Layout::Union(union_layout) => build_has_tag_id(env, function, *union_layout),
_ => unreachable!(),
};
call_bitcode_fn(
env,
&[
@ -612,6 +658,7 @@ pub fn list_keep_oks<'a, 'ctx, 'env>(
layout_width(env, before_layout),
layout_width(env, result_layout),
layout_width(env, after_layout),
has_tag_id.as_global_value().as_pointer_value().into(),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_KEEP_OKS,
@ -623,20 +670,26 @@ pub fn list_keep_errs<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
function_layout: &Layout<'a>,
// Layout of the `Result * err`
result_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
before_layout: &Layout<'a>,
after_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
// Layout of the `Result after *`
let result_layout = match function_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
let function = env
.builder
.get_insert_block()
.unwrap()
.get_parent()
.unwrap();
let has_tag_id = match result_layout {
Layout::Union(union_layout) => build_has_tag_id(env, function, *union_layout),
_ => unreachable!(),
};
call_bitcode_fn(
env,
&[
@ -649,60 +702,13 @@ pub fn list_keep_errs<'a, 'ctx, 'env>(
layout_width(env, before_layout),
layout_width(env, result_layout),
layout_width(env, after_layout),
has_tag_id.as_global_value().as_pointer_value().into(),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_KEEP_ERRS,
)
}
pub fn list_keep_result<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: FunctionValue<'ctx>,
transform_layout: Layout<'a>,
closure_data: BasicValueEnum<'ctx>,
closure_data_layout: Layout<'a>,
list: BasicValueEnum<'ctx>,
before_layout: &Layout<'a>,
after_layout: &Layout<'a>,
op: &str,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let result_layout = match transform_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let closure_data_ptr = builder.build_alloca(closure_data.get_type(), "closure_data_ptr");
env.builder.build_store(closure_data_ptr, closure_data);
let stepper_caller =
build_transform_caller(env, transform, closure_data_layout, &[*before_layout])
.as_global_value()
.as_pointer_value();
let inc_closure = build_inc_wrapper(env, layout_ids, &transform_layout);
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
call_bitcode_fn(
env,
&[
pass_list_as_i128(env, list),
pass_as_opaque(env, closure_data_ptr),
stepper_caller.into(),
env.alignment_intvalue(&before_layout),
layout_width(env, before_layout),
layout_width(env, after_layout),
layout_width(env, result_layout),
inc_closure.as_global_value().as_pointer_value().into(),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
op,
)
}
/// List.sortWith : List a, (a, a -> Ordering) -> List a
pub fn list_sort_with<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -849,7 +855,6 @@ pub fn list_map3<'a, 'ctx, 'env>(
/// List.concat : List elem, List elem -> List elem
pub fn list_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_inplace: InPlace,
_parent: FunctionValue<'ctx>,
first_list: BasicValueEnum<'ctx>,
second_list: BasicValueEnum<'ctx>,
@ -861,7 +866,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
// then simply return an empty list
empty_list(env)
}
Layout::Builtin(Builtin::List(_, elem_layout)) => call_bitcode_fn_returns_list(
Layout::Builtin(Builtin::List(elem_layout)) => call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, first_list),
@ -1115,7 +1120,6 @@ pub fn load_list_ptr<'ctx>(
pub fn allocate_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
elem_layout: &Layout<'a>,
number_of_elements: IntValue<'ctx>,
) -> PointerValue<'ctx> {
@ -1128,16 +1132,13 @@ pub fn allocate_list<'a, 'ctx, 'env>(
let number_of_data_bytes =
builder.build_int_mul(bytes_per_element, number_of_elements, "data_length");
let rc1 = match inplace {
InPlace::InPlace => number_of_elements,
InPlace::Clone => {
// the refcount of a new list is initially 1
// we assume that the list is indeed used (dead variables are eliminated)
crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes)
}
};
// the refcount of a new list is initially 1
// we assume that the list is indeed used (dead variables are eliminated)
let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes);
allocate_with_refcount_help(env, elem_layout, number_of_data_bytes, rc1)
let basic_type = basic_type_from_layout(env, elem_layout);
let alignment_bytes = elem_layout.alignment_bytes(env.ptr_bytes);
allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes, rc1)
}
pub fn store_list<'a, 'ctx, 'env>(

View File

@ -6,7 +6,7 @@ use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, Str
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, InPlace, Layout};
use roc_mono::layout::{Builtin, Layout};
use super::build::load_symbol;
@ -16,7 +16,6 @@ pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
pub fn str_split<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
inplace: InPlace,
str_symbol: Symbol,
delimiter_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
@ -33,7 +32,7 @@ pub fn str_split<'a, 'ctx, 'env>(
.into_int_value();
// a pointer to the elements
let ret_list_ptr = allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), segment_count);
let ret_list_ptr = allocate_list(env, &Layout::Builtin(Builtin::Str), segment_count);
// get the RocStr type defined by zig
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
@ -109,7 +108,6 @@ pub fn destructure<'ctx>(
/// Str.concat : Str, Str -> Str
pub fn str_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
scope: &Scope<'a, 'ctx>,
str1_symbol: Symbol,
str2_symbol: Symbol,
@ -120,14 +118,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
call_bitcode_fn(
env,
&[
env.context
.i8_type()
.const_int(inplace as u64, false)
.into(),
str1_i128.into(),
str2_i128.into(),
],
&[str1_i128.into(), str2_i128.into()],
&bitcode::STR_CONCAT,
)
}
@ -135,7 +126,6 @@ pub fn str_concat<'a, 'ctx, 'env>(
/// Str.join : List Str, Str -> Str
pub fn str_join_with<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_inplace: InPlace,
scope: &Scope<'a, 'ctx>,
list_symbol: Symbol,
str_symbol: Symbol,

View File

@ -1,10 +1,13 @@
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::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV};
use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal;
use crate::llvm::convert::{basic_type_from_layout, get_ptr_type};
use crate::llvm::convert::basic_type_from_layout;
use bumpalo::collections::Vec;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::types::BasicType;
use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
};
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
@ -99,7 +102,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
Builtin::Float16 => float_cmp(FloatPredicate::OEQ, "eq_f16"),
Builtin::Str => str_equal(env, lhs_val, rhs_val),
Builtin::List(_, elem) => build_list_eq(
Builtin::List(elem) => build_list_eq(
env,
layout_ids,
&Layout::Builtin(*builtin),
@ -159,11 +162,6 @@ fn build_eq<'a, 'ctx, 'env>(
rhs_val,
),
Layout::PhantomEmptyStruct => {
// always equal to itself
env.context.bool_type().const_int(1, false).into()
}
Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be compared directly")
@ -197,11 +195,7 @@ fn build_eq<'a, 'ctx, 'env>(
}
},
Layout::Pointer(_) => {
unreachable!("unused")
}
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
Layout::Closure(_, _, _) => {
unreachable!("the type system will guarantee these are never compared")
}
}
@ -258,7 +252,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
result.into()
}
Builtin::List(_, elem) => {
Builtin::List(elem) => {
let is_equal = build_list_eq(
env,
layout_ids,
@ -338,20 +332,11 @@ fn build_neq<'a, 'ctx, 'env>(
result.into()
}
Layout::PhantomEmptyStruct => {
// always equal to itself
env.context.bool_type().const_int(1, false).into()
}
Layout::RecursivePointer => {
unreachable!("recursion pointers should never be compared directly")
}
Layout::Pointer(_) => {
unreachable!("unused")
}
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
Layout::Closure(_, _, _) => {
unreachable!("the type system will guarantee these are never compared")
}
}
@ -446,8 +431,8 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
let list1 = it.next().unwrap().into_struct_value();
let list2 = it.next().unwrap().into_struct_value();
set_name(list1.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(list2.into(), Symbol::ARG_2.ident_string(&env.interns));
list1.set_name(Symbol::ARG_1.ident_string(&env.interns));
list2.set_name(Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
@ -475,7 +460,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
let builder = env.builder;
let element_type = basic_type_from_layout(env, element_layout);
let ptr_type = get_ptr_type(&element_type, AddressSpace::Generic);
let ptr_type = element_type.ptr_type(AddressSpace::Generic);
let ptr1 = load_list_ptr(env.builder, list1, ptr_type);
let ptr2 = load_list_ptr(env.builder, list2, ptr_type);
@ -654,8 +639,8 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
let struct1 = it.next().unwrap().into_struct_value();
let struct2 = it.next().unwrap().into_struct_value();
set_name(struct1.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(struct2.into(), Symbol::ARG_2.ident_string(&env.interns));
struct1.set_name(Symbol::ARG_1.ident_string(&env.interns));
struct2.set_name(Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
let start = ctx.append_basic_block(parent, "start");
@ -835,8 +820,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let tag1 = it.next().unwrap();
let tag2 = it.next().unwrap();
set_name(tag1, Symbol::ARG_1.ident_string(&env.interns));
set_name(tag2, Symbol::ARG_2.ident_string(&env.interns));
tag1.set_name(Symbol::ARG_1.ident_string(&env.interns));
tag2.set_name(Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
@ -861,9 +846,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
match union_layout {
NonRecursive(tags) => {
// SAFETY we know that non-recursive tags cannot be NULL
let id1 = nonrec_tag_id(env, tag1.into_struct_value());
let id2 = nonrec_tag_id(env, tag2.into_struct_value());
let id1 = get_tag_id(env, parent, union_layout, tag1);
let id2 = get_tag_id(env, parent, union_layout, tag2);
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
@ -912,10 +896,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.build_return(Some(&answer));
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
cases.push((id1.get_type().const_int(tag_id as u64, false), block));
}
env.builder.position_at_end(compare_tag_fields);
@ -941,9 +922,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.position_at_end(compare_tag_ids);
// SAFETY we know that non-recursive tags cannot be NULL
let id1 = unsafe { rec_tag_id_unsafe(env, tag1.into_pointer_value()) };
let id2 = unsafe { rec_tag_id_unsafe(env, tag2.into_pointer_value()) };
let id1 = get_tag_id(env, parent, union_layout, tag1);
let id2 = get_tag_id(env, parent, union_layout, tag2);
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
@ -975,10 +955,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.build_return(Some(&answer));
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
cases.push((id1.get_type().const_int(tag_id as u64, false), block));
}
env.builder.position_at_end(compare_tag_fields);
@ -988,9 +965,6 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.build_switch(id1, default, &cases);
}
NullableUnwrapped { other_fields, .. } => {
// drop the tag id; it is not stored
let other_fields = &other_fields[1..];
let ptr_equal = env.builder.build_int_compare(
IntPredicate::EQ,
env.builder
@ -1096,9 +1070,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.position_at_end(compare_other);
// SAFETY we know at this point that tag1/tag2 are not NULL
let id1 = unsafe { rec_tag_id_unsafe(env, tag1.into_pointer_value()) };
let id2 = unsafe { rec_tag_id_unsafe(env, tag2.into_pointer_value()) };
let id1 = get_tag_id(env, parent, union_layout, tag1);
let id2 = get_tag_id(env, parent, union_layout, tag2);
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
@ -1131,10 +1104,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.build_return(Some(&answer));
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
cases.push((id1.get_type().const_int(tag_id as u64, false), block));
}
env.builder.position_at_end(compare_tag_fields);
@ -1182,8 +1152,6 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
tag1: PointerValue<'ctx>,
tag2: PointerValue<'ctx>,
) -> IntValue<'ctx> {
use inkwell::types::BasicType;
let struct_layout = Layout::Struct(field_layouts);
let wrapper_type = basic_type_from_layout(env, &struct_layout);
@ -1228,32 +1196,3 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
)
.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(AddressSpace::Generic),
"cast_for_tag_id",
)
.into_pointer_value();
env.builder.build_load(ptr, "load_tag_id").into_int_value()
}

View File

@ -1,88 +1,9 @@
use bumpalo::collections::Vec;
use inkwell::context::Context;
use inkwell::types::BasicTypeEnum::{self, *};
use inkwell::types::{ArrayType, BasicType, FunctionType, IntType, PointerType, StructType};
use inkwell::values::BasicValueEnum;
use inkwell::types::{BasicType, BasicTypeEnum, IntType, StructType};
use inkwell::AddressSpace;
use roc_mono::layout::{Builtin, Layout, UnionLayout};
/// TODO could this be added to Inkwell itself as a method on BasicValueEnum?
pub fn get_ptr_type<'ctx>(
bt_enum: &BasicTypeEnum<'ctx>,
address_space: AddressSpace,
) -> PointerType<'ctx> {
match bt_enum {
ArrayType(typ) => typ.ptr_type(address_space),
IntType(typ) => typ.ptr_type(address_space),
FloatType(typ) => typ.ptr_type(address_space),
PointerType(typ) => typ.ptr_type(address_space),
StructType(typ) => typ.ptr_type(address_space),
VectorType(typ) => typ.ptr_type(address_space),
}
}
/// TODO could this be added to Inkwell itself as a method on BasicValueEnum?
pub fn get_fn_type<'ctx>(
bt_enum: &BasicTypeEnum<'ctx>,
arg_types: &[BasicTypeEnum<'ctx>],
) -> FunctionType<'ctx> {
match bt_enum {
ArrayType(typ) => typ.fn_type(arg_types, false),
IntType(typ) => typ.fn_type(arg_types, false),
FloatType(typ) => typ.fn_type(arg_types, false),
PointerType(typ) => typ.fn_type(arg_types, false),
StructType(typ) => typ.fn_type(arg_types, false),
VectorType(typ) => typ.fn_type(arg_types, false),
}
}
/// TODO could this be added to Inkwell itself as a method on BasicValueEnum?
pub fn get_array_type<'ctx>(bt_enum: &BasicTypeEnum<'ctx>, size: u32) -> ArrayType<'ctx> {
match bt_enum {
ArrayType(typ) => typ.array_type(size),
IntType(typ) => typ.array_type(size),
FloatType(typ) => typ.array_type(size),
PointerType(typ) => typ.array_type(size),
StructType(typ) => typ.array_type(size),
VectorType(typ) => typ.array_type(size),
}
}
/// 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<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
args: &[Layout<'_>],
closure_type: Option<BasicTypeEnum<'ctx>>,
ret_layout: &Layout<'_>,
) -> BasicTypeEnum<'ctx> {
let ret_type = basic_type_from_layout(env, &ret_layout);
let mut arg_basic_types = Vec::with_capacity_in(args.len(), env.arena);
for arg_layout in args.iter() {
arg_basic_types.push(basic_type_from_layout(env, arg_layout));
}
if let Some(closure) = closure_type {
arg_basic_types.push(closure);
}
let fn_type = get_fn_type(&ret_type, arg_basic_types.into_bump_slice());
let ptr_type = fn_type.ptr_type(AddressSpace::Generic);
ptr_type.as_basic_type_enum()
}
fn basic_type_from_record<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
fields: &[Layout<'_>],
@ -105,38 +26,49 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
use Layout::*;
match layout {
FunctionPointer(args, ret_layout) => {
basic_type_from_function_layout(env, args, None, ret_layout)
}
Closure(_args, closure_layout, _ret_layout) => {
let closure_data_layout = closure_layout.runtime_representation();
basic_type_from_layout(env, &closure_data_layout)
}
Pointer(layout) => basic_type_from_layout(env, &layout)
.ptr_type(AddressSpace::Generic)
.into(),
PhantomEmptyStruct => env.context.struct_type(&[], false).into(),
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
Union(variant) => {
use UnionLayout::*;
let tag_id_type = basic_type_from_layout(env, &variant.tag_id_layout());
match variant {
Recursive(tags)
| NullableWrapped {
NullableWrapped {
other_tags: tags, ..
} => {
let block = block_of_memory_slices(env.context, tags, env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into()
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
env.context
.struct_type(&[data, tag_id_type], false)
.ptr_type(AddressSpace::Generic)
.into()
}
NullableUnwrapped { other_fields, .. } => {
let block =
block_of_memory_slices(env.context, &[&other_fields[1..]], env.ptr_bytes);
block_of_memory_slices(env.context, &[&other_fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into()
}
NonNullableUnwrapped(fields) => {
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into()
}
NonRecursive(_) => block_of_memory(env.context, layout, env.ptr_bytes),
Recursive(tags) => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
env.context
.struct_type(&[data, tag_id_type], false)
.ptr_type(AddressSpace::Generic)
.into()
}
NonRecursive(tags) => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
env.context.struct_type(&[data, tag_id_type], false).into()
}
}
}
RecursivePointer => {
@ -174,7 +106,7 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
Float16 => context.f16_type().as_basic_type_enum(),
Dict(_, _) | EmptyDict => zig_dict_type(env).into(),
Set(_) | EmptySet => zig_dict_type(env).into(),
List(_, _) | EmptyList => zig_list_type(env).into(),
List(_) | EmptyList => zig_list_type(env).into(),
Str | EmptyStr => zig_str_type(env).into(),
}
}
@ -197,13 +129,43 @@ pub fn block_of_memory_slices<'ctx>(
block_of_memory_help(context, union_size)
}
pub fn union_data_is_struct<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
layouts: &[Layout<'_>],
) -> StructType<'ctx> {
let data_type = basic_type_from_record(env, layouts);
union_data_is_struct_type(env.context, data_type.into_struct_type())
}
pub fn union_data_is_struct_type<'ctx>(
context: &'ctx Context,
struct_type: StructType<'ctx>,
) -> StructType<'ctx> {
let tag_id_type = context.i64_type();
context.struct_type(&[struct_type.into(), tag_id_type.into()], false)
}
pub fn union_data_block_of_memory<'ctx>(
context: &'ctx Context,
tag_id_int_type: IntType<'ctx>,
layouts: &[&[Layout<'_>]],
ptr_bytes: u32,
) -> StructType<'ctx> {
let data_type = block_of_memory_slices(context, layouts, ptr_bytes);
context.struct_type(&[data_type, tag_id_int_type.into()], false)
}
pub fn block_of_memory<'ctx>(
context: &'ctx Context,
layout: &Layout<'_>,
ptr_bytes: u32,
) -> BasicTypeEnum<'ctx> {
// TODO make this dynamic
let union_size = layout.stack_size(ptr_bytes as u32);
let mut union_size = layout.stack_size(ptr_bytes as u32);
if let Layout::Union(UnionLayout::NonRecursive { .. }) = layout {
union_size -= ptr_bytes;
}
block_of_memory_help(context, union_size)
}
@ -266,3 +228,9 @@ pub fn zig_str_type<'a, 'ctx, 'env>(
) -> StructType<'ctx> {
env.module.get_struct_type("str.RocStr").unwrap()
}
pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
) -> StructType<'ctx> {
env.module.get_struct_type("list.HasTagId").unwrap()
}

View File

@ -1,8 +1,9 @@
use crate::llvm::build::{add_func, set_name, C_CALL_CONV};
use crate::llvm::build::{add_func, C_CALL_CONV};
use crate::llvm::convert::ptr_int;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::values::BasicValue;
use inkwell::AddressSpace;
/// Define functions for roc_alloc, roc_realloc, and roc_dealloc
@ -69,8 +70,8 @@ pub fn add_default_roc_externs<'ctx>(
debug_assert!(params.next().is_none());
set_name(ptr_arg, "ptr");
set_name(size_arg, "size");
ptr_arg.set_name("ptr");
size_arg.set_name("size");
if cfg!(debug_assertions) {
crate::llvm::build::verify_fn(fn_val);

View File

@ -1,21 +1,24 @@
use crate::debug_info_init;
use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, set_name, Env, FAST_CALL_CONV,
LLVM_SADD_WITH_OVERFLOW_I64,
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive,
Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64, TAG_DATA_INDEX,
};
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
use crate::llvm::convert::{
basic_type_from_layout, block_of_memory, block_of_memory_slices, ptr_int,
basic_type_from_layout, block_of_memory_slices, ptr_int, union_data_block_of_memory,
};
use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::context::Context;
use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
};
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode, UnionLayout};
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
pub const REFCOUNT_MAX: usize = 0_usize;
@ -92,6 +95,14 @@ impl<'ctx> PointerToRefcount<'ctx> {
Self::from_ptr_to_data(env, data_ptr)
}
pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
let current = self.get_refcount(env);
let one = refcount_1(env.context, env.ptr_bytes);
env.builder
.build_int_compare(IntPredicate::EQ, current, one, "is_one")
}
pub fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
env.builder
.build_load(self.value, "get_refcount")
@ -390,7 +401,7 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
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));
arg_val.set_name(arg_symbol.ident_string(&env.interns));
let parent = fn_val;
@ -469,21 +480,17 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
use Builtin::*;
match builtin {
List(memory_mode, element_layout) => {
if let MemoryMode::Refcounted = memory_mode {
let function = modify_refcount_list(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
);
List(element_layout) => {
let function = modify_refcount_list(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
);
Some(function)
} else {
None
}
Some(function)
}
Set(element_layout) => {
let key_layout = &Layout::Struct(&[]);
@ -646,6 +653,7 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
layout_ids,
mode,
&WhenRecursive::Loop(*variant),
*variant,
tags,
true,
);
@ -654,14 +662,13 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
}
NullableUnwrapped { other_fields, .. } => {
let other_fields = &other_fields[1..];
let function = build_rec_union(
env,
layout_ids,
mode,
&WhenRecursive::Loop(*variant),
&*env.arena.alloc([other_fields]),
*variant,
env.arena.alloc([*other_fields]),
true,
);
@ -674,6 +681,7 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
layout_ids,
mode,
&WhenRecursive::Loop(*variant),
*variant,
&*env.arena.alloc([*fields]),
true,
);
@ -686,6 +694,7 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
layout_ids,
mode,
&WhenRecursive::Loop(*variant),
*variant,
tags,
false,
);
@ -724,11 +733,9 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
Some(function)
}
PhantomEmptyStruct => None,
Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be hashed directly")
unreachable!("recursion pointers cannot be in/decremented directly")
}
WhenRecursive::Loop(union_layout) => {
let layout = Layout::Union(*union_layout);
@ -745,8 +752,6 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
Some(function)
}
},
FunctionPointer(_, _) | Pointer(_) => None,
}
}
@ -827,7 +832,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
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));
arg_val.set_name(arg_symbol.ident_string(&env.interns));
let parent = fn_val;
let original_wrapper = arg_val.into_struct_value();
@ -946,7 +951,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
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));
arg_val.set_name(arg_symbol.ident_string(&env.interns));
let parent = fn_val;
@ -1065,7 +1070,7 @@ fn modify_refcount_dict_help<'a, 'ctx, 'env>(
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));
arg_val.set_name(arg_symbol.ident_string(&env.interns));
let parent = fn_val;
@ -1179,7 +1184,7 @@ pub enum Mode {
}
impl Mode {
fn to_call_mode<'ctx>(&self, function: FunctionValue<'ctx>) -> CallMode<'ctx> {
fn to_call_mode(self, function: FunctionValue<'_>) -> CallMode<'_> {
match self {
Mode::Inc => {
let amount = function.get_nth_param(1).unwrap().into_int_value();
@ -1202,10 +1207,11 @@ fn build_rec_union<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [Layout<'a>]],
union_layout: UnionLayout<'a>,
tags: &'a [&'a [Layout<'a>]],
is_nullable: bool,
) -> FunctionValue<'ctx> {
let layout = Layout::Union(UnionLayout::Recursive(fields));
let layout = Layout::Union(UnionLayout::Recursive(tags));
let (_, fn_name) = function_name_from_mode(
layout_ids,
@ -1222,9 +1228,7 @@ fn build_rec_union<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let basic_type = block_of_memory_slices(env.context, fields, env.ptr_bytes)
.ptr_type(AddressSpace::Generic)
.into();
let basic_type = basic_type_from_layout(env, &Layout::Union(union_layout));
let function_value = build_header(env, basic_type, mode, &fn_name);
build_rec_union_help(
@ -1232,7 +1236,8 @@ fn build_rec_union<'a, 'ctx, 'env>(
layout_ids,
mode,
when_recursive,
fields,
union_layout,
tags,
function_value,
is_nullable,
);
@ -1248,12 +1253,14 @@ fn build_rec_union<'a, 'ctx, 'env>(
function
}
#[allow(clippy::too_many_arguments)]
fn build_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
when_recursive: &WhenRecursive<'a>,
tags: &[&[Layout<'a>]],
union_layout: UnionLayout<'a>,
tags: &'a [&'a [roc_mono::layout::Layout<'a>]],
fn_val: FunctionValue<'ctx>,
is_nullable: bool,
) {
@ -1262,8 +1269,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
let context = &env.context;
let builder = env.builder;
let pick = |a, b| if let Mode::Inc = mode { a } else { b };
// Add a basic block for the entry point
let entry = context.append_basic_block(fn_val, "entry");
@ -1276,15 +1281,101 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns));
arg_val.set_name(arg_symbol.ident_string(&env.interns));
let parent = fn_val;
let layout = Layout::Union(UnionLayout::Recursive(tags));
debug_assert!(arg_val.is_pointer_value());
let value_ptr = arg_val.into_pointer_value();
// to increment/decrement the cons-cell itself
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
let call_mode = mode_to_call_mode(fn_val, mode);
let should_recurse_block = env.context.append_basic_block(parent, "should_recurse");
let ctx = env.context;
if is_nullable {
let is_null = env.builder.build_is_null(value_ptr, "is_null");
let then_block = ctx.append_basic_block(parent, "then");
env.builder
.build_conditional_branch(is_null, then_block, should_recurse_block);
{
env.builder.position_at_end(then_block);
env.builder.build_return(None);
}
} else {
env.builder.build_unconditional_branch(should_recurse_block);
}
env.builder.position_at_end(should_recurse_block);
let layout = Layout::Union(union_layout);
match mode {
Mode::Inc => {
// inc is cheap; we never recurse
refcount_ptr.modify(call_mode, &layout, env);
env.builder.build_return(None);
}
Mode::Dec => {
let do_recurse_block = env.context.append_basic_block(parent, "do_recurse");
let no_recurse_block = env.context.append_basic_block(parent, "no_recurse");
builder.build_conditional_branch(
refcount_ptr.is_1(env),
do_recurse_block,
no_recurse_block,
);
{
env.builder.position_at_end(no_recurse_block);
refcount_ptr.modify(call_mode, &layout, env);
env.builder.build_return(None);
}
{
env.builder.position_at_end(do_recurse_block);
build_rec_union_recursive_decrement(
env,
layout_ids,
when_recursive,
parent,
fn_val,
union_layout,
tags,
value_ptr,
refcount_ptr,
do_recurse_block,
)
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
when_recursive: &WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
decrement_fn: FunctionValue<'ctx>,
union_layout: UnionLayout<'a>,
tags: &[&[Layout<'a>]],
value_ptr: PointerValue<'ctx>,
refcount_ptr: PointerToRefcount<'ctx>,
match_block: BasicBlock<'ctx>,
) {
let mode = Mode::Dec;
let call_mode = mode_to_call_mode(decrement_fn, mode);
let builder = env.builder;
// branches that are not/don't contain anything refcounted
// if there is only one branch, we don't need to switch
let switch_needed: bool = (|| {
@ -1300,31 +1391,12 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
false
})();
// to increment/decrement the cons-cell itself
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
let call_mode = mode_to_call_mode(fn_val, mode);
let ctx = env.context;
let cont_block = ctx.append_basic_block(parent, "cont");
if is_nullable {
let is_null = env.builder.build_is_null(value_ptr, "is_null");
let then_block = ctx.append_basic_block(parent, "then");
env.builder
.build_conditional_branch(is_null, then_block, cont_block);
{
env.builder.position_at_end(then_block);
env.builder.build_return(None);
}
} else {
env.builder.build_unconditional_branch(cont_block);
}
// 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 tag_id_int_type =
basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type();
for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on
if !field_layouts
@ -1334,9 +1406,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
continue;
}
let block = env
.context
.append_basic_block(parent, pick("tag_id_increment", "tag_id_decrement"));
let block = env.context.append_basic_block(parent, "tag_id_decrement");
env.builder.position_at_end(block);
@ -1372,7 +1442,23 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
// therefore we must cast it to our desired type
let union_type = block_of_memory_slices(env.context, tags, env.ptr_bytes);
let union_type = match union_layout {
UnionLayout::NonRecursive(_) => unreachable!(),
UnionLayout::Recursive(_) | UnionLayout::NullableWrapped { .. } => {
union_data_block_of_memory(
env.context,
tag_id_int_type,
tags,
env.ptr_bytes,
)
.into()
}
UnionLayout::NonNullableUnwrapped { .. }
| UnionLayout::NullableUnwrapped { .. } => {
block_of_memory_slices(env.context, tags, env.ptr_bytes)
}
};
let recursive_field_ptr = cast_basic_basic(
env.builder,
ptr_as_i64_ptr,
@ -1386,10 +1472,9 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.unwrap();
let field = env.builder.build_load(
elem_pointer,
pick("increment_struct_field", "decrement_struct_field"),
);
let field = env
.builder
.build_load(elem_pointer, "decrement_struct_field");
deferred_nonrec.push((field, field_layout));
}
@ -1401,14 +1486,14 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
// lists. To achieve it, we must first load all fields that we want to inc/dec (done above)
// and store them on the stack, then modify (and potentially free) the current cell, then
// actually inc/dec the fields.
refcount_ptr.modify(call_mode, &layout, env);
refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env);
for (field, field_layout) in deferred_nonrec {
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode.to_call_mode(fn_val),
mode.to_call_mode(decrement_fn),
when_recursive,
field,
field_layout,
@ -1417,22 +1502,19 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
for ptr in deferred_rec {
// recursively decrement the field
let call = call_help(env, fn_val, mode.to_call_mode(fn_val), ptr);
let call = call_help(env, decrement_fn, mode.to_call_mode(decrement_fn), ptr);
call.set_tail_call(true);
}
// this function returns void
builder.build_return(None);
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
cases.push((tag_id_int_type.const_int(tag_id as u64, false), block));
}
cases.reverse();
env.builder.position_at_end(match_block);
env.builder.position_at_end(cont_block);
cases.reverse();
if cases.len() == 1 && !switch_needed {
// there is only one tag in total; we don't need a switch
@ -1443,11 +1525,9 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
env.builder.build_unconditional_branch(only_branch);
} else {
// read the tag_id
let current_tag_id = rec_union_read_tag(env, value_ptr);
let current_tag_id = get_tag_id(env, parent, &union_layout, value_ptr.into());
let merge_block = env
.context
.append_basic_block(parent, pick("increment_merge", "decrement_merge"));
let merge_block = env.context.append_basic_block(parent, "decrement_merge");
// switch on it
env.builder
@ -1456,30 +1536,13 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
env.builder.position_at_end(merge_block);
// increment/decrement the cons-cell itself
refcount_ptr.modify(call_mode, &layout, env);
refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env);
// this function returns void
builder.build_return(None);
}
}
fn rec_union_read_tag<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value_ptr: PointerValue<'ctx>,
) -> IntValue<'ctx> {
// Assumption: the tag is the first thing stored
// so cast the pointer to the data to a `i64*`
let tag_ptr_type = env.context.i64_type().ptr_type(AddressSpace::Generic);
let tag_ptr = env
.builder
.build_bitcast(value_ptr, tag_ptr_type, "cast_tag_ptr")
.into_pointer_value();
env.builder
.build_load(tag_ptr, "load_tag_id")
.into_int_value()
}
fn function_name_from_mode<'a>(
layout_ids: &mut LayoutIds<'a>,
interns: &Interns,
@ -1524,7 +1587,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let basic_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let basic_type = basic_type_from_layout(env, &layout);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_union_help(
@ -1571,7 +1634,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
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));
arg_val.set_name(arg_symbol.ident_string(&env.interns));
let parent = fn_val;
@ -1580,19 +1643,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let wrapper_struct = arg_val.into_struct_value();
// read the tag_id
let tag_id = {
// the first element of the wrapping struct is an array of i64
let first_array = env
.builder
.build_extract_value(wrapper_struct, 0, "read_tag_id")
.unwrap()
.into_array_value();
env.builder
.build_extract_value(first_array, 0, "read_tag_id_2")
.unwrap()
.into_int_value()
};
let tag_id = get_tag_id_non_recursive(env, wrapper_struct);
let tag_id_u8 = env
.builder
@ -1620,7 +1671,12 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
debug_assert!(wrapper_type.is_struct_type());
let wrapper_struct = cast_block_of_memory_to_tag(env.builder, wrapper_struct, wrapper_type);
let data_bytes = env
.builder
.build_extract_value(wrapper_struct, TAG_DATA_INDEX, "read_tag_id")
.unwrap()
.into_struct_value();
let wrapper_struct = cast_block_of_memory_to_tag(env.builder, data_bytes, wrapper_type);
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
@ -1689,7 +1745,7 @@ pub fn refcount_offset<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layou
let value_bytes = layout.stack_size(env.ptr_bytes) as u64;
match layout {
Layout::Builtin(Builtin::List(_, _)) => env.ptr_bytes as u64,
Layout::Builtin(Builtin::List(_)) => env.ptr_bytes as u64,
Layout::Builtin(Builtin::Str) => env.ptr_bytes as u64,
Layout::RecursivePointer | Layout::Union(_) => env.ptr_bytes as u64,
_ => (env.ptr_bytes as u64).max(value_bytes),

View File

@ -38,7 +38,7 @@ macro_rules! run_jit_function {
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
use inkwell::context::Context;
use roc_gen::run_roc::RocCallResult;
use roc_gen_llvm::run_roc::RocCallResult;
use std::mem::MaybeUninit;
unsafe {
@ -77,7 +77,7 @@ macro_rules! run_jit_function_dynamic_type {
($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{
use inkwell::context::Context;
use roc_gen::run_roc::RocCallResult;
use roc_gen_llvm::run_roc::RocCallResult;
unsafe {
let main: libloading::Symbol<unsafe extern "C" fn(*const u8)> = $lib
@ -86,7 +86,7 @@ macro_rules! run_jit_function_dynamic_type {
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
let size = roc_gen::run_roc::ROC_CALL_RESULT_DISCRIMINANT_SIZE + $bytes;
let size = roc_gen_llvm::run_roc::ROC_CALL_RESULT_DISCRIMINANT_SIZE + $bytes;
let layout = std::alloc::Layout::array::<u8>(size).unwrap();
let result = std::alloc::alloc(layout);
main(result);

View File

@ -19,6 +19,7 @@ roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"

View File

@ -1,10 +1,12 @@
use crate::docs::DocEntry::DetatchedDoc;
use crate::docs::TypeAnnotation::{Apply, BoundVariable, Record, TagUnion};
use crate::docs::DocEntry::DetachedDoc;
use crate::docs::TypeAnnotation::{
Apply, BoundVariable, Function, NoTypeAnn, ObscuredRecord, ObscuredTagUnion, Record, TagUnion,
};
use crate::file::LoadedModule;
use inlinable_string::InlinableString;
use roc_can::scope::Scope;
use roc_collections::all::MutMap;
use roc_module::ident::ModuleName;
use roc_module::symbol::{IdentIds, Interns, ModuleId};
use roc_module::symbol::IdentIds;
use roc_parse::ast;
use roc_parse::ast::CommentOrNewline;
use roc_parse::ast::{AssignedField, Def};
@ -17,7 +19,7 @@ pub struct Documentation {
pub name: String,
pub version: String,
pub docs: String,
pub modules: Vec<(MutMap<ModuleId, ModuleDocumentation>, Interns)>,
pub modules: Vec<LoadedModule>,
}
#[derive(Debug)]
@ -30,14 +32,14 @@ pub struct ModuleDocumentation {
#[derive(Debug, Clone)]
pub enum DocEntry {
DocDef(DocDef),
DetatchedDoc(String),
DetachedDoc(String),
}
#[derive(Debug, Clone)]
pub struct DocDef {
pub name: String,
pub type_vars: Vec<String>,
pub type_annotation: Option<TypeAnnotation>,
pub type_annotation: TypeAnnotation,
pub docs: Option<String>,
}
@ -45,8 +47,14 @@ pub struct DocDef {
pub enum TypeAnnotation {
TagUnion {
tags: Vec<Tag>,
extension: Option<Box<TypeAnnotation>>,
extension: Box<TypeAnnotation>,
},
Function {
args: Vec<TypeAnnotation>,
output: Box<TypeAnnotation>,
},
ObscuredTagUnion,
ObscuredRecord,
BoundVariable(String),
Apply {
name: String,
@ -54,7 +62,10 @@ pub enum TypeAnnotation {
},
Record {
fields: Vec<RecordField>,
extension: Box<TypeAnnotation>,
},
Wildcard,
NoTypeAnn,
}
#[derive(Debug, Clone)]
pub enum RecordField {
@ -97,10 +108,10 @@ pub fn generate_module_docs<'a>(
}
}
fn detatched_docs_from_comments_and_new_lines<'a>(
fn detached_docs_from_comments_and_new_lines<'a>(
comments_or_new_lines: &'a [roc_parse::ast::CommentOrNewline<'a>],
) -> Vec<String> {
let mut detatched_docs: Vec<String> = Vec::new();
let mut detached_docs: Vec<String> = Vec::new();
let mut docs = String::new();
@ -112,13 +123,16 @@ fn detatched_docs_from_comments_and_new_lines<'a>(
}
CommentOrNewline::LineComment(_) | CommentOrNewline::Newline => {
detatched_docs.push(docs.clone());
if !docs.is_empty() {
detached_docs.push(docs.clone());
}
docs = String::new();
}
}
}
detatched_docs
detached_docs
}
fn generate_entry_doc<'a>(
@ -136,8 +150,8 @@ fn generate_entry_doc<'a>(
Def::SpaceBefore(sub_def, comments_or_new_lines) => {
// Comments before a definition are attached to the current defition
for detatched_doc in detatched_docs_from_comments_and_new_lines(comments_or_new_lines) {
acc.push(DetatchedDoc(detatched_doc));
for detached_doc in detached_docs_from_comments_and_new_lines(comments_or_new_lines) {
acc.push(DetachedDoc(detached_doc));
}
generate_entry_doc(ident_ids, acc, Some(comments_or_new_lines), sub_def)
@ -161,7 +175,7 @@ fn generate_entry_doc<'a>(
{
let doc_def = DocDef {
name: identifier.to_string(),
type_annotation: None,
type_annotation: NoTypeAnn,
type_vars: Vec::new(),
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
@ -172,7 +186,11 @@ fn generate_entry_doc<'a>(
_ => (acc, None),
},
Def::AnnotatedBody { ann_pattern, .. } => match ann_pattern.value {
Def::AnnotatedBody {
ann_pattern,
ann_type,
..
} => match ann_pattern.value {
Pattern::Identifier(identifier) => {
// Check if the definition is exposed
if ident_ids
@ -181,7 +199,7 @@ fn generate_entry_doc<'a>(
{
let doc_def = DocDef {
name: identifier.to_string(),
type_annotation: None,
type_annotation: type_to_docs(false, ann_type.value),
type_vars: Vec::new(),
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
@ -204,7 +222,7 @@ fn generate_entry_doc<'a>(
let doc_def = DocDef {
name: name.value.to_string(),
type_annotation: type_to_docs(ann.value),
type_annotation: type_to_docs(false, ann.value),
type_vars,
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
@ -221,7 +239,7 @@ fn generate_entry_doc<'a>(
}
}
fn type_to_docs(type_annotation: ast::TypeAnnotation) -> Option<TypeAnnotation> {
fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> TypeAnnotation {
match type_annotation {
ast::TypeAnnotation::TagUnion {
tags,
@ -233,7 +251,7 @@ fn type_to_docs(type_annotation: ast::TypeAnnotation) -> Option<TypeAnnotation>
let mut any_tags_are_private = false;
for tag in tags {
match tag_to_doc(tag.value) {
match tag_to_doc(in_func_type_ann, tag.value) {
None => {
any_tags_are_private = true;
break;
@ -245,20 +263,24 @@ fn type_to_docs(type_annotation: ast::TypeAnnotation) -> Option<TypeAnnotation>
}
if any_tags_are_private {
None
if in_func_type_ann {
ObscuredTagUnion
} else {
NoTypeAnn
}
} else {
let extension = match ext {
None => None,
Some(ext_type_ann) => type_to_docs(ext_type_ann.value).map(Box::new),
None => NoTypeAnn,
Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value),
};
Some(TagUnion {
TagUnion {
tags: tags_to_render,
extension,
})
extension: Box::new(extension),
}
}
}
ast::TypeAnnotation::BoundVariable(var_name) => Some(BoundVariable(var_name.to_string())),
ast::TypeAnnotation::BoundVariable(var_name) => BoundVariable(var_name.to_string()),
ast::TypeAnnotation::Apply(module_name, type_name, type_ann_parts) => {
let mut name = String::new();
@ -272,16 +294,14 @@ fn type_to_docs(type_annotation: ast::TypeAnnotation) -> Option<TypeAnnotation>
let mut parts: Vec<TypeAnnotation> = Vec::new();
for type_ann_part in type_ann_parts {
if let Some(part) = type_to_docs(type_ann_part.value) {
parts.push(part);
}
parts.push(type_to_docs(in_func_type_ann, type_ann_part.value));
}
Some(Apply { name, parts })
Apply { name, parts }
}
ast::TypeAnnotation::Record {
fields,
ext: _,
ext,
final_comments: _,
} => {
let mut doc_fields = Vec::new();
@ -289,7 +309,7 @@ fn type_to_docs(type_annotation: ast::TypeAnnotation) -> Option<TypeAnnotation>
let mut any_fields_include_private_tags = false;
for field in fields {
match record_field_to_doc(field.value) {
match record_field_to_doc(in_func_type_ann, field.value) {
None => {
any_fields_include_private_tags = true;
break;
@ -300,37 +320,61 @@ fn type_to_docs(type_annotation: ast::TypeAnnotation) -> Option<TypeAnnotation>
}
}
if any_fields_include_private_tags {
None
if in_func_type_ann {
ObscuredRecord
} else {
NoTypeAnn
}
} else {
Some(Record { fields: doc_fields })
let extension = match ext {
None => NoTypeAnn,
Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value),
};
Record {
fields: doc_fields,
extension: Box::new(extension),
}
}
}
ast::TypeAnnotation::SpaceBefore(&sub_type_ann, _) => type_to_docs(sub_type_ann),
ast::TypeAnnotation::SpaceAfter(&sub_type_ann, _) => type_to_docs(sub_type_ann),
_ => {
// TODO "Implement type to docs")
None
ast::TypeAnnotation::SpaceBefore(&sub_type_ann, _) => {
type_to_docs(in_func_type_ann, sub_type_ann)
}
ast::TypeAnnotation::SpaceAfter(&sub_type_ann, _) => {
type_to_docs(in_func_type_ann, sub_type_ann)
}
ast::TypeAnnotation::Function(ast_arg_anns, output_ann) => {
let mut doc_arg_anns = Vec::new();
for ast_arg_ann in ast_arg_anns {
doc_arg_anns.push(type_to_docs(true, ast_arg_ann.value));
}
Function {
args: doc_arg_anns,
output: Box::new(type_to_docs(true, output_ann.value)),
}
}
ast::TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
_ => NoTypeAnn,
}
}
fn record_field_to_doc(field: ast::AssignedField<'_, ast::TypeAnnotation>) -> Option<RecordField> {
fn record_field_to_doc(
in_func_ann: bool,
field: ast::AssignedField<'_, ast::TypeAnnotation>,
) -> Option<RecordField> {
match field {
AssignedField::RequiredValue(name, _, type_ann) => {
type_to_docs(type_ann.value).map(|type_ann_docs| RecordField::RecordField {
name: name.value.to_string(),
type_annotation: type_ann_docs,
})
}
AssignedField::SpaceBefore(&sub_field, _) => record_field_to_doc(sub_field),
AssignedField::SpaceAfter(&sub_field, _) => record_field_to_doc(sub_field),
AssignedField::OptionalValue(name, _, type_ann) => {
type_to_docs(type_ann.value).map(|type_ann_docs| RecordField::OptionalField {
name: name.value.to_string(),
type_annotation: type_ann_docs,
})
}
AssignedField::RequiredValue(name, _, type_ann) => Some(RecordField::RecordField {
name: name.value.to_string(),
type_annotation: type_to_docs(in_func_ann, type_ann.value),
}),
AssignedField::SpaceBefore(&sub_field, _) => record_field_to_doc(in_func_ann, sub_field),
AssignedField::SpaceAfter(&sub_field, _) => record_field_to_doc(in_func_ann, sub_field),
AssignedField::OptionalValue(name, _, type_ann) => Some(RecordField::OptionalField {
name: name.value.to_string(),
type_annotation: type_to_docs(in_func_ann, type_ann.value),
}),
AssignedField::LabelOnly(label) => Some(RecordField::LabelOnly {
name: label.value.to_string(),
}),
@ -340,7 +384,7 @@ fn record_field_to_doc(field: ast::AssignedField<'_, ast::TypeAnnotation>) -> Op
// The Option here represents if it is private. Private tags
// evaluate to `None`.
fn tag_to_doc(tag: ast::Tag) -> Option<Tag> {
fn tag_to_doc(in_func_ann: bool, tag: ast::Tag) -> Option<Tag> {
match tag {
ast::Tag::Global { name, args } => Some(Tag {
name: name.value.to_string(),
@ -348,17 +392,15 @@ fn tag_to_doc(tag: ast::Tag) -> Option<Tag> {
let mut type_vars = Vec::new();
for arg in args {
if let Some(type_var) = type_to_docs(arg.value) {
type_vars.push(type_var);
}
type_vars.push(type_to_docs(in_func_ann, arg.value));
}
type_vars
},
}),
ast::Tag::Private { .. } => None,
ast::Tag::SpaceBefore(&sub_tag, _) => tag_to_doc(sub_tag),
ast::Tag::SpaceAfter(&sub_tag, _) => tag_to_doc(sub_tag),
ast::Tag::SpaceBefore(&sub_tag, _) => tag_to_doc(in_func_ann, sub_tag),
ast::Tag::SpaceAfter(&sub_tag, _) => tag_to_doc(in_func_ann, sub_tag),
ast::Tag::Malformed(_) => None,
}
}

View File

@ -19,8 +19,8 @@ use roc_module::symbol::{
Symbol,
};
use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
TopLevelFunctionLayout,
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, PendingSpecialization, Proc,
ProcLayout, Procs,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
@ -621,6 +621,8 @@ pub struct LoadedModule {
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
pub exposed_to_host: MutMap<Symbol, Variable>,
pub exposed_aliases: MutMap<Symbol, Alias>,
pub exposed_values: Vec<Symbol>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>,
@ -660,6 +662,8 @@ enum HeaderFor<'a> {
config_shorthand: &'a str,
/// the type scheme of the main function (required by the platform)
platform_main_type: TypedIdent<'a>,
/// provided symbol to host (commonly `mainForHost`)
main_for_host: Symbol,
},
Interface,
}
@ -705,7 +709,8 @@ pub struct MonomorphizedModule<'a> {
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
pub procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), Proc<'a>>,
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
pub entry_point: EntryPoint<'a>,
pub exposed_to_host: MutMap<Symbol, Variable>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
@ -760,6 +765,8 @@ enum Msg<'a> {
FinishedAllTypeChecking {
solved_subs: Solved<Subs>,
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_values: Vec<Symbol>,
documentation: MutMap<ModuleId, ModuleDocumentation>,
},
FoundSpecializations {
@ -776,7 +783,7 @@ enum Msg<'a> {
ident_ids: IdentIds,
layout_cache: LayoutCache<'a>,
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations<'a>>,
procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), Proc<'a>>,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
problems: Vec<roc_mono::ir::MonoProblem>,
module_timing: ModuleTiming,
subs: Subs,
@ -804,10 +811,16 @@ enum PlatformPath<'a> {
RootIsPkgConfig,
}
#[derive(Debug)]
struct PlatformData {
module_id: ModuleId,
provides: Symbol,
}
#[derive(Debug)]
struct State<'a> {
pub root_id: ModuleId,
pub platform_id: Option<ModuleId>,
pub platform_data: Option<PlatformData>,
pub goal_phase: Phase,
pub stdlib: &'a StdLib,
pub exposed_types: SubsByModule,
@ -818,7 +831,7 @@ struct State<'a> {
pub module_cache: ModuleCache<'a>,
pub dependencies: Dependencies<'a>,
pub procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), Proc<'a>>,
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
pub exposed_to_host: MutMap<Symbol, Variable>,
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
@ -849,7 +862,7 @@ struct State<'a> {
pub needs_specialization: MutSet<ModuleId>,
pub all_pending_specializations:
MutMap<Symbol, MutMap<TopLevelFunctionLayout<'a>, PendingSpecialization<'a>>>,
MutMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>,
pub specializations_in_flight: u32,
@ -1447,7 +1460,7 @@ where
let mut state = State {
root_id,
platform_id: None,
platform_data: None,
goal_phase,
stdlib,
output_path: None,
@ -1501,6 +1514,8 @@ where
Msg::FinishedAllTypeChecking {
solved_subs,
exposed_vars_by_symbol,
exposed_aliases_by_symbol,
exposed_values,
documentation,
} => {
// We're done! There should be no more messages pending.
@ -1516,6 +1531,8 @@ where
return Ok(LoadResult::TypeChecked(finish(
state,
solved_subs,
exposed_values,
exposed_aliases_by_symbol,
exposed_vars_by_symbol,
documentation,
)));
@ -1674,10 +1691,13 @@ fn update<'a>(
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::Valid(to_platform.clone());
}
PkgConfig { .. } => {
debug_assert_eq!(state.platform_id, None);
PkgConfig { main_for_host, .. } => {
debug_assert!(matches!(state.platform_data, None));
state.platform_id = Some(header.module_id);
state.platform_data = Some(PlatformData {
module_id: header.module_id,
provides: main_for_host,
});
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
@ -1896,9 +1916,9 @@ fn update<'a>(
// if there is a platform, the Package-Config module provides host-exposed,
// otherwise the App module exposes host-exposed
let is_host_exposed = match state.platform_id {
let is_host_exposed = match state.platform_data {
None => module_id == state.root_id,
Some(platform_id) => module_id == platform_id,
Some(ref platform_data) => module_id == platform_data.module_id,
};
if is_host_exposed {
@ -1927,6 +1947,8 @@ fn update<'a>(
.send(Msg::FinishedAllTypeChecking {
solved_subs,
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
exposed_values: solved_module.exposed_symbols,
exposed_aliases_by_symbol: solved_module.aliases,
documentation,
})
.map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -2025,7 +2047,7 @@ fn update<'a>(
}
MadeSpecializations {
module_id,
mut ident_ids,
ident_ids,
subs,
procedures,
external_specializations_requested,
@ -2061,22 +2083,16 @@ fn update<'a>(
println!("{}", result);
}
if false {
let it = state.procedures.iter().map(|x| x.1);
if let Err(e) = roc_mono::alias_analysis::spec_program(it) {
println!("Error in alias analysis: {:?}", e)
}
}
Proc::insert_refcount_operations(arena, &mut state.procedures);
Proc::optimize_refcount_operations(
arena,
module_id,
&mut ident_ids,
&mut state.procedures,
);
// This is not safe with the new non-recursive RC updates that we do for tag unions
//
// Proc::optimize_refcount_operations(
// arena,
// module_id,
// &mut ident_ids,
// &mut state.procedures,
// );
state.constrained_ident_ids.insert(module_id, ident_ids);
@ -2160,6 +2176,7 @@ fn finish_specialization(
module_cache,
output_path,
platform_path,
platform_data,
..
} = state;
@ -2207,6 +2224,34 @@ fn finish_specialization(
let platform_path = path_to_platform.into();
let entry_point = {
let symbol = match platform_data {
None => {
debug_assert_eq!(exposed_to_host.len(), 1);
*exposed_to_host.iter().next().unwrap().0
}
Some(PlatformData { provides, .. }) => provides,
};
match procedures.keys().find(|(s, _)| *s == symbol) {
Some((_, layout)) => EntryPoint {
layout: *layout,
symbol,
},
None => {
// the entry point is not specialized. This can happen if the repl output
// is a function value
EntryPoint {
layout: roc_mono::ir::ProcLayout {
arguments: &[],
result: Layout::Struct(&[]),
},
symbol,
}
}
}
};
Ok(MonomorphizedModule {
can_problems,
mono_problems,
@ -2218,6 +2263,7 @@ fn finish_specialization(
subs,
interns,
procedures,
entry_point,
sources,
header_sources,
timings: state.timings,
@ -2227,6 +2273,8 @@ fn finish_specialization(
fn finish(
state: State,
solved: Solved<Subs>,
exposed_values: Vec<Symbol>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
documentation: MutMap<ModuleId, ModuleDocumentation>,
) -> LoadedModule {
@ -2261,6 +2309,8 @@ fn finish(
can_problems: state.module_cache.can_problems,
type_problems: state.module_cache.type_problems,
declarations_by_id: state.declarations_by_id,
exposed_aliases: exposed_aliases_by_symbol,
exposed_values,
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
header_sources,
sources,
@ -2991,7 +3041,7 @@ fn send_header_two<'a>(
HashMap::with_capacity_and_hasher(scope_size, default_hasher());
let home: ModuleId;
let ident_ids = {
let mut ident_ids = {
// Lock just long enough to perform the minimal operations necessary.
let mut module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).lock();
@ -3114,9 +3164,17 @@ fn send_header_two<'a>(
// to decrement its "pending" count.
let module_name = ModuleNameEnum::PkgConfig;
let main_for_host = {
let ident_str: InlinableString = provides[0].value.as_str().into();
let ident_id = ident_ids.get_or_insert(&ident_str);
Symbol::new(home, ident_id)
};
let extra = HeaderFor::PkgConfig {
config_shorthand: shorthand,
platform_main_type: requires[0].value.clone(),
main_for_host,
};
let mut package_qualified_imported_modules = MutSet::default();
@ -3246,6 +3304,7 @@ fn run_solve<'a>(
let solved_module = SolvedModule {
exposed_vars_by_symbol,
exposed_symbols: exposed_symbols.into_iter().collect::<Vec<_>>(),
solved_types,
problems,
aliases: solved_env.aliases,
@ -3482,7 +3541,6 @@ fn fabricate_effects_module<'a>(
&mut var_store,
annotation,
);
exposed_symbols.insert(symbol);
declarations.push(Declaration::Declare(def));
@ -3821,7 +3879,8 @@ fn make_specializations<'a>(
ident_ids: &mut ident_ids,
ptr_bytes,
update_mode_counter: 0,
call_specialization_counter: 0,
// call_specialization_counter=0 is reserved
call_specialization_counter: 1,
};
// TODO: for now this final specialization pass is sequential,
@ -3884,7 +3943,8 @@ fn build_pending_specializations<'a>(
ident_ids: &mut ident_ids,
ptr_bytes,
update_mode_counter: 0,
call_specialization_counter: 0,
// call_specialization_counter=0 is reserved
call_specialization_counter: 1,
};
// Add modules' decls to Procs
@ -4003,7 +4063,7 @@ fn add_def_to_module<'a>(
procs.insert_exposed(
symbol,
TopLevelFunctionLayout::from_layout(mono_env.arena, layout),
ProcLayout::from_layout(mono_env.arena, layout),
mono_env.arena,
mono_env.subs,
def.annotation,
@ -4034,14 +4094,14 @@ fn add_def_to_module<'a>(
if is_exposed {
let annotation = def.expr_var;
let layout = match layout_cache.from_var(
let top_level = match layout_cache.from_var(
mono_env.arena,
annotation,
mono_env.subs,
) {
Ok(l) => {
// remember, this is a 0-argument thunk
Layout::FunctionPointer(&[], mono_env.arena.alloc(l))
ProcLayout::new(mono_env.arena, &[], l)
}
Err(LayoutProblem::Erroneous) => {
let message = "top level function has erroneous type";
@ -4062,7 +4122,7 @@ fn add_def_to_module<'a>(
procs.insert_exposed(
symbol,
TopLevelFunctionLayout::from_layout(mono_env.arena, layout),
top_level,
mono_env.arena,
mono_env.subs,
def.annotation,

View File

@ -18,7 +18,6 @@ pub enum LowLevel {
ListLen,
ListGetUnsafe,
ListSet,
ListSetInPlace,
ListSingle,
ListRepeat,
ListReverse,
@ -40,6 +39,7 @@ pub enum LowLevel {
ListKeepErrs,
ListSortWith,
ListDrop,
ListSwap,
DictSize,
DictEmpty,
DictInsert,
@ -124,7 +124,6 @@ impl LowLevel {
| ListLen
| ListGetUnsafe
| ListSet
| ListSetInPlace
| ListDrop
| ListSingle
| ListRepeat
@ -135,6 +134,7 @@ impl LowLevel {
| ListPrepend
| ListJoin
| ListRange
| ListSwap
| DictSize
| DictEmpty
| DictInsert

View File

@ -278,10 +278,10 @@ impl ModuleId {
// This is a no-op that should get DCE'd
}
pub fn to_string<'a>(&self, interns: &'a Interns) -> &'a InlinableString {
pub fn to_string(self, interns: &Interns) -> &InlinableString {
interns
.module_ids
.get_name(*self)
.get_name(self)
.unwrap_or_else(|| panic!("Could not find ModuleIds for {:?}", self))
}
}
@ -396,17 +396,15 @@ impl<'a> PackageModuleIds<'a> {
fn insert_debug_name(module_id: ModuleId, module_name: &PQModuleName) {
let mut names = DEBUG_MODULE_ID_NAMES.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
if !names.contains_key(&module_id.0) {
match module_name {
PQModuleName::Unqualified(module) => {
names.insert(module_id.0, module.to_string().into());
}
names
.entry(module_id.0)
.or_insert_with(|| match module_name {
PQModuleName::Unqualified(module) => module.to_string().into(),
PQModuleName::Qualified(package, module) => {
let name = format!("{}.{}", package, module).into();
names.insert(module_id.0, name);
name
}
}
}
});
}
#[cfg(not(debug_assertions))]
@ -464,9 +462,9 @@ impl ModuleIds {
let mut names = DEBUG_MODULE_ID_NAMES.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
// TODO make sure modules are never added more than once!
if !names.contains_key(&module_id.0) {
names.insert(module_id.0, module_name.to_string().into());
}
names
.entry(module_id.0)
.or_insert_with(|| module_name.to_string().into());
}
#[cfg(not(debug_assertions))]
@ -904,6 +902,7 @@ define_builtins! {
14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
15 STR_TO_BYTES: "toBytes"
16 STR_STARTS_WITH_CODE_POINT: "startsWithCodePoint"
17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias
@ -939,6 +938,7 @@ define_builtins! {
30 LIST_RANGE: "range"
31 LIST_SORT_WITH: "sortWith"
32 LIST_DROP: "drop"
33 LIST_SWAP: "swap"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View File

@ -1,8 +1,8 @@
use morphic_lib::TypeContext;
use morphic_lib::{
BlockExpr, BlockId, CalleeSpecVar, EntryPointName, ExprContext, FuncDef, FuncDefBuilder,
FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, TypeId, TypeName, UpdateModeVar,
ValueId,
BlockExpr, BlockId, CalleeSpecVar, ConstDefBuilder, ConstName, EntryPointName, ExprContext,
FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, TypeId,
UpdateModeVar, ValueId,
};
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
@ -13,25 +13,144 @@ use crate::ir::{Call, CallType, Expr, Literal, ModifyRc, Proc, Stmt};
use crate::layout::{Builtin, Layout, ListLayout, UnionLayout};
// just using one module for now
const MOD_LIST: ModName = ModName(b"UserApp");
const MOD_APP: ModName = ModName(b"UserApp");
pub const MOD_APP: ModName = ModName(b"UserApp");
pub fn spec_program<'a, I>(procs: I) -> Result<morphic_lib::Solutions>
pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STATIC.to_ne_bytes());
const ENTRY_POINT_NAME: &[u8] = b"mainForHost";
pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] {
func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), proc.ret_layout)
}
const DEBUG: bool = false;
const SIZE: usize = if DEBUG { 50 } else { 16 };
pub fn func_name_bytes_help<'a, I>(
symbol: Symbol,
argument_layouts: I,
return_layout: Layout<'a>,
) -> [u8; SIZE]
where
I: Iterator<Item = Layout<'a>>,
{
let mut name_bytes = [0u8; SIZE];
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
let layout_hash = {
let mut hasher = DefaultHasher::new();
for layout in argument_layouts {
match layout {
Layout::Closure(_, lambda_set, _) => {
lambda_set.runtime_representation().hash(&mut hasher);
}
_ => {
layout.hash(&mut hasher);
}
}
}
match return_layout {
Layout::Closure(_, lambda_set, _) => {
lambda_set.runtime_representation().hash(&mut hasher);
}
_ => {
return_layout.hash(&mut hasher);
}
}
hasher.finish()
};
let sbytes = symbol.to_ne_bytes();
let lbytes = layout_hash.to_ne_bytes();
let it = sbytes
.iter()
.chain(lbytes.iter())
.zip(name_bytes.iter_mut());
for (source, target) in it {
*target = *source;
}
if DEBUG {
for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() {
name_bytes[25 + i] = c as u8;
}
}
name_bytes
}
fn bytes_as_ascii(bytes: &[u8]) -> String {
use std::fmt::Write;
let mut buf = String::new();
for byte in bytes {
write!(buf, "{:02X}", byte).unwrap();
}
buf
}
pub fn spec_program<'a, I>(
entry_point: crate::ir::EntryPoint<'a>,
procs: I,
) -> Result<morphic_lib::Solutions>
where
I: Iterator<Item = &'a Proc<'a>>,
{
let mut main_function = None;
let main_module = {
let mut m = ModDefBuilder::new();
// a const that models all static strings
let static_str_def = {
let mut cbuilder = ConstDefBuilder::new();
let block = cbuilder.add_block();
let cell = cbuilder.add_new_heap_cell(block)?;
let value_id = cbuilder.add_make_tuple(block, &[cell])?;
let root = BlockExpr(block, value_id);
let str_type_id = str_type(&mut cbuilder)?;
cbuilder.build(str_type_id, root)?
};
m.add_const(STATIC_STR_NAME, static_str_def)?;
// the entry point wrapper
let roc_main_bytes = func_name_bytes_help(
entry_point.symbol,
entry_point.layout.arguments.iter().copied(),
entry_point.layout.result,
);
let roc_main = FuncName(&roc_main_bytes);
let entry_point_function = build_entry_point(entry_point.layout, roc_main)?;
let entry_point_name = FuncName(ENTRY_POINT_NAME);
m.add_func(entry_point_name, entry_point_function)?;
// all other functions
for proc in procs {
let bytes = func_name_bytes(proc);
let func_name = FuncName(&bytes);
if DEBUG {
eprintln!(
"{:?}: {:?} with {:?} args",
proc.name,
bytes_as_ascii(&bytes),
(proc.args, proc.ret_layout),
);
}
let spec = proc_spec(proc)?;
m.add_func(FuncName(&proc.name.to_ne_bytes()), spec)?;
if format!("{:?}", proc.name).contains("mainForHost") {
main_function = Some(proc.name);
}
m.add_func(func_name, spec)?;
}
m.build()?
@ -40,18 +159,42 @@ where
let program = {
let mut p = ProgramBuilder::new();
p.add_mod(MOD_APP, main_module)?;
p.add_entry_point(
EntryPointName(b"mainForHost"),
MOD_APP,
FuncName(&main_function.unwrap().to_ne_bytes()),
)?;
let entry_point_name = FuncName(ENTRY_POINT_NAME);
p.add_entry_point(EntryPointName(ENTRY_POINT_NAME), MOD_APP, entry_point_name)?;
p.build()?
};
if DEBUG {
eprintln!("{}", program.to_source_string());
}
morphic_lib::solve(program)
}
fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Result<FuncDef> {
let mut builder = FuncDefBuilder::new();
let block = builder.add_block();
// to the modelling language, the arguments appear out of thin air
let argument_type = build_tuple_type(&mut builder, layout.arguments)?;
let argument = builder.add_unknown_with(block, &[], argument_type)?;
let name_bytes = [0; 16];
let spec_var = CalleeSpecVar(&name_bytes);
let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?;
// to the modelling language, the result disappears into the void
let unit_type = builder.add_tuple_type(&[])?;
let unit_value = builder.add_unknown_with(block, &[result], unit_type)?;
let root = BlockExpr(block, unit_value);
let spec = builder.build(unit_type, unit_type, root)?;
Ok(spec)
}
fn proc_spec(proc: &Proc) -> Result<FuncDef> {
let mut builder = FuncDefBuilder::new();
let mut env = Env::default();
@ -72,13 +215,16 @@ fn proc_spec(proc: &Proc) -> Result<FuncDef> {
let root = BlockExpr(block, value_id);
let arg_type_id = layout_spec(&mut builder, &Layout::Struct(&argument_layouts))?;
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?;
builder.build(arg_type_id, ret_type_id, root)
let spec = builder.build(arg_type_id, ret_type_id, root)?;
Ok(spec)
}
#[derive(Default)]
struct Env {
symbols: MutMap<Symbol, ValueId>,
join_points: MutMap<crate::ir::JoinPointId, morphic_lib::JoinPointId>,
join_points: MutMap<crate::ir::JoinPointId, morphic_lib::ContinuationId>,
}
fn stmt_spec(
@ -91,11 +237,25 @@ fn stmt_spec(
use Stmt::*;
match stmt {
Let(symbol, expr, layout, continuation) => {
let value_id = expr_spec(builder, env, block, layout, expr)?;
Let(symbol, expr, expr_layout, mut continuation) => {
let value_id = expr_spec(builder, env, block, expr_layout, expr)?;
env.symbols.insert(*symbol, value_id);
let mut queue = vec![symbol];
while let Let(symbol, expr, expr_layout, c) = continuation {
let value_id = expr_spec(builder, env, block, expr_layout, expr)?;
env.symbols.insert(*symbol, value_id);
queue.push(symbol);
continuation = c;
}
let result = stmt_spec(builder, env, block, layout, continuation)?;
env.symbols.remove(symbol);
for symbol in queue {
env.symbols.remove(symbol);
}
Ok(result)
}
@ -105,6 +265,7 @@ fn stmt_spec(
layout: call_layout,
pass,
fail,
exception_id: _,
} => {
// a call that might throw an exception
@ -127,7 +288,7 @@ fn stmt_spec(
cond_layout: _,
branches,
default_branch,
ret_layout,
ret_layout: _lies,
} => {
let mut cases = Vec::with_capacity(branches.len() + 1);
@ -138,7 +299,7 @@ fn stmt_spec(
for branch in it {
let block = builder.add_block();
let value_id = stmt_spec(builder, env, block, ret_layout, branch)?;
let value_id = stmt_spec(builder, env, block, layout, branch)?;
cases.push(BlockExpr(block, value_id));
}
@ -146,12 +307,27 @@ fn stmt_spec(
}
Ret(symbol) => Ok(env.symbols[symbol]),
Refcounting(modify_rc, continuation) => match modify_rc {
ModifyRc::Inc(symbol, _) | ModifyRc::Dec(symbol) | ModifyRc::DecRef(symbol) => {
let result_type = builder.add_tuple_type(&[])?;
ModifyRc::Inc(symbol, _) => {
let argument = env.symbols[symbol];
// this is how RC is modelled; it recursively touches all heap cells
builder.add_unknown_with(block, &[argument], result_type)?;
// a recursive touch is never worse for optimizations than a normal touch
// and a bit more permissive in its type
builder.add_recursive_touch(block, argument)?;
stmt_spec(builder, env, block, layout, continuation)
}
ModifyRc::Dec(symbol) => {
let argument = env.symbols[symbol];
builder.add_recursive_touch(block, argument)?;
stmt_spec(builder, env, block, layout, continuation)
}
ModifyRc::DecRef(symbol) => {
let argument = env.symbols[symbol];
builder.add_recursive_touch(block, argument)?;
stmt_spec(builder, env, block, layout, continuation)
}
@ -159,7 +335,7 @@ fn stmt_spec(
Join {
id,
parameters,
continuation,
body,
remainder,
} => {
let mut type_ids = Vec::new();
@ -173,31 +349,37 @@ fn stmt_spec(
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
let (jpid, jp_argument) =
builder.declare_join_point(block, jp_arg_type_id, ret_type_id)?;
builder.declare_continuation(block, jp_arg_type_id, ret_type_id)?;
// NOTE join point arguments can shadow variables from the outer scope
// the ordering of steps here is important
// add this ID so both body and remainder can reference it
env.join_points.insert(*id, jpid);
// first, with the current variable bindings, process the remainder
let cont_block = builder.add_block();
let cont_value_id = stmt_spec(builder, env, cont_block, layout, remainder)?;
// only then introduce variables bound by the jump point, and process its body
let join_body_sub_block = {
env.join_points.insert(*id, jpid);
let jp_body_block = builder.add_block();
// unpack the argument
for (i, p) in parameters.iter().enumerate() {
let value_id =
builder.add_get_tuple_field(jp_body_block, jp_argument, i as u32)?;
env.symbols.insert(p.symbol, value_id);
}
let jp_body_value_id = stmt_spec(builder, env, jp_body_block, layout, remainder)?;
let jp_body_value_id = stmt_spec(builder, env, jp_body_block, layout, body)?;
BlockExpr(jp_body_block, jp_body_value_id)
};
// NOTE the symbols bound by the join point can shadow the argument symbols of the
// surrounding function, so we don't remove them from the env here
let cont_block = builder.add_block();
let cont_value_id = stmt_spec(builder, env, cont_block, layout, continuation)?;
env.join_points.remove(id);
builder.define_join_point(jpid, join_body_sub_block)?;
builder.define_continuation(jpid, join_body_sub_block)?;
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
}
@ -208,7 +390,7 @@ fn stmt_spec(
let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id)
}
Rethrow | RuntimeError(_) => {
Resume(_) | RuntimeError(_) => {
let type_id = layout_spec(builder, layout)?;
builder.add_terminate(block, type_id)
@ -260,17 +442,17 @@ fn call_spec(
match &call.call_type {
ByName {
name: symbol,
full_layout: _,
ret_layout: _,
arg_layouts: _,
ret_layout,
arg_layouts,
specialization_id,
} => {
let array = specialization_id.to_bytes();
let spec_var = CalleeSpecVar(&array);
let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?;
let slice = &symbol.to_ne_bytes();
let name = FuncName(slice);
let it = arg_layouts.iter().copied();
let bytes = func_name_bytes_help(*symbol, it, *ret_layout);
let name = FuncName(&bytes);
let module = MOD_APP;
builder.add_call(block, spec_var, module, name, arg_value_id)
}
@ -297,7 +479,223 @@ fn call_spec(
*update_mode,
call.arguments,
),
HigherOrderLowLevel { .. } => todo!(),
HigherOrderLowLevel {
specialization_id,
closure_env_layout,
op,
arg_layouts,
ret_layout,
..
} => {
let array = specialization_id.to_bytes();
let spec_var = CalleeSpecVar(&array);
let symbol = {
use roc_module::low_level::LowLevel::*;
match op {
ListMap | ListMapWithIndex => call.arguments[1],
ListMap2 => call.arguments[2],
ListMap3 => call.arguments[3],
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => call.arguments[2],
ListKeepIf | ListKeepOks | ListKeepErrs => call.arguments[1],
ListSortWith => call.arguments[1],
_ => unreachable!(),
}
};
let it = arg_layouts.iter().copied();
let bytes = func_name_bytes_help(symbol, it, *ret_layout);
let name = FuncName(&bytes);
let module = MOD_APP;
{
use roc_module::low_level::LowLevel::*;
match op {
DictWalk => {
let dict = env.symbols[&call.arguments[0]];
let default = env.symbols[&call.arguments[1]];
let closure_env = env.symbols[&call.arguments[3]];
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let key = builder.add_get_tuple_field(block, first, 0)?;
let val = builder.add_get_tuple_field(block, first, 1)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[key, val, default])?
} else {
builder.add_make_tuple(block, &[key, val, default, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListWalk | ListWalkBackwards | ListWalkUntil => {
let list = env.symbols[&call.arguments[0]];
let default = env.symbols[&call.arguments[1]];
let closure_env = env.symbols[&call.arguments[3]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[first, default])?
} else {
builder.add_make_tuple(block, &[first, default, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListMapWithIndex => {
let list = env.symbols[&call.arguments[0]];
let closure_env = env.symbols[&call.arguments[2]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let index = builder.add_make_tuple(block, &[])?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[first, index])?
} else {
builder.add_make_tuple(block, &[first, index, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListMap => {
let list1 = env.symbols[&call.arguments[0]];
let closure_env = env.symbols[&call.arguments[2]];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1])?
} else {
builder.add_make_tuple(block, &[elem1, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListSortWith => {
let list1 = env.symbols[&call.arguments[0]];
let closure_env = env.symbols[&call.arguments[2]];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem1])?
} else {
builder.add_make_tuple(block, &[elem1, elem1, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListMap2 => {
let list1 = env.symbols[&call.arguments[0]];
let list2 = env.symbols[&call.arguments[1]];
let closure_env = env.symbols[&call.arguments[3]];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2])?
} else {
builder.add_make_tuple(block, &[elem1, elem2, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListMap3 => {
let list1 = env.symbols[&call.arguments[0]];
let list2 = env.symbols[&call.arguments[1]];
let list3 = env.symbols[&call.arguments[2]];
let closure_env = env.symbols[&call.arguments[4]];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?;
let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?;
let elem3 = builder.add_bag_get(block, bag3)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2, elem3])?
} else {
builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListKeepIf | ListKeepOks | ListKeepErrs => {
let list = env.symbols[&call.arguments[0]];
let closure_env = env.symbols[&call.arguments[2]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
// let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[first])?
} else {
builder.add_make_tuple(block, &[first, closure_env])?
};
let result = builder.add_call(block, spec_var, module, name, argument)?;
let unit = builder.add_tuple_type(&[])?;
builder.add_unknown_with(block, &[result], unit)?;
}
_ => {
// fake a call to the function argument
// to make sure the function is specialized
// very invalid
let arg_value_id = build_tuple_value(builder, env, block, &[])?;
builder.add_call(block, spec_var, module, name, arg_value_id)?;
}
}
}
// TODO overly pessimstic
// filter_map because one of the arguments is a function name, which
// is not defined in the env
let arguments: Vec<_> = call
.arguments
.iter()
.filter_map(|symbol| env.symbols.get(symbol))
.copied()
.collect();
let result_type = layout_spec(builder, layout)?;
builder.add_unknown_with(block, &arguments, result_type)
}
}
}
@ -341,12 +739,17 @@ fn lowlevel_spec(
builder.add_sub_block(block, sub_block)
}
Eq | NotEq => new_bool(builder, block),
NumLte | NumLt | NumGt | NumGte => new_order(builder, block),
Eq | NotEq => {
// just dream up a unit value
builder.add_make_tuple(block, &[])
}
NumLte | NumLt | NumGt | NumGte => {
// just dream up a unit value
builder.add_make_tuple(block, &[])
}
ListLen => {
let list = env.symbols[&arguments[0]];
builder.add_get_tuple_field(block, list, LIST_LEN_INDEX)
// just dream up a unit value
builder.add_make_tuple(block, &[])
}
ListGetUnsafe => {
// NOTE the ListGet lowlevel op is only evaluated if the index is in-bounds
@ -372,21 +775,18 @@ fn lowlevel_spec(
Ok(list)
}
other => todo!("lowlevel op not implemented: {:?}", other),
_other => {
// TODO overly pessimstic
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
let result_type = layout_spec(builder, layout)?;
builder.add_unknown_with(block, &arguments, result_type)
}
}
}
fn build_variant_types(
builder: &mut FuncDefBuilder,
layout: &Layout,
) -> Option<Result<Vec<TypeId>>> {
match layout {
Layout::Union(union_layout) => Some(build_variant_types_help(builder, union_layout)),
_ => None,
}
}
fn build_variant_types_help(
builder: &mut FuncDefBuilder,
union_layout: &UnionLayout,
) -> Result<Vec<TypeId>> {
@ -400,24 +800,29 @@ fn build_variant_types_help(
result.push(build_tuple_type(builder, tag)?);
}
}
Recursive(_) => todo!(),
NonNullableUnwrapped(_) => todo!(),
Recursive(_) => unreachable!(),
NonNullableUnwrapped(_) => unreachable!(),
NullableWrapped {
nullable_id: _,
other_tags: _,
} => todo!(),
} => unreachable!(),
NullableUnwrapped {
nullable_id: _,
other_fields: _,
} => todo!(),
} => unreachable!(),
}
Ok(result)
}
fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> {
let cell = context.add_heap_cell_type();
context.add_bag_type(cell)
}
fn expr_spec(
builder: &mut FuncDefBuilder,
env: &Env,
env: &mut Env,
block: BlockId,
layout: &Layout,
expr: &Expr,
@ -433,37 +838,48 @@ fn expr_spec(
tag_id,
union_size: _,
arguments,
} => {
let value_id = build_tuple_value(builder, env, block, arguments)?;
let variant_types = build_variant_types(builder, tag_layout).unwrap()?;
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)
}
Struct(fields) => build_tuple_value(builder, env, block, fields),
AccessAtIndex {
index,
field_layouts: _,
structure,
wrapped,
} => {
use crate::ir::Wrapped;
let value_id = env.symbols[structure];
match wrapped {
Wrapped::EmptyRecord => {
// this is a unit value
builder.add_make_tuple(block, &[])
}
Wrapped::SingleElementRecord => {
todo!("do we unwrap single-element records still?")
}
Wrapped::RecordOrSingleTagUnion => {
builder.add_get_tuple_field(block, value_id, *index as u32)
}
Wrapped::MultiTagUnion => {
builder.add_get_tuple_field(block, value_id, *index as u32)
}
} => match tag_layout {
UnionLayout::NonRecursive(_) => {
let value_id = build_tuple_value(builder, env, block, arguments)?;
let variant_types = build_variant_types(builder, tag_layout)?;
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)
}
UnionLayout::Recursive(_)
| UnionLayout::NonNullableUnwrapped(_)
| UnionLayout::NullableWrapped { .. }
| UnionLayout::NullableUnwrapped { .. } => {
let result_type = worst_case_type(builder)?;
let value_id = build_tuple_value(builder, env, block, arguments)?;
builder.add_unknown_with(block, &[value_id], result_type)
}
},
Struct(fields) => build_tuple_value(builder, env, block, fields),
UnionAtIndex {
index,
tag_id,
structure,
union_layout,
} => match union_layout {
UnionLayout::NonRecursive(_) => {
let index = (*index) as u32;
let tag_value_id = env.symbols[structure];
let tuple_value_id =
builder.add_unwrap_union(block, tag_value_id, *tag_id as u32)?;
builder.add_get_tuple_field(block, tuple_value_id, index)
}
_ => {
// for the moment recursive tag unions don't quite work
let value_id = env.symbols[structure];
let result_type = layout_spec(builder, layout)?;
builder.add_unknown_with(block, &[value_id], result_type)
}
},
StructAtIndex {
index, structure, ..
} => {
let value_id = env.symbols[structure];
builder.add_get_tuple_field(block, value_id, *index as u32)
}
Array { elem_layout, elems } => {
let type_id = layout_spec(builder, elem_layout)?;
@ -478,7 +894,9 @@ fn expr_spec(
bag = builder.add_bag_insert(block, bag, value_id)?;
}
Ok(bag)
let cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[cell, bag])
}
EmptyArray => {
@ -504,6 +922,7 @@ fn expr_spec(
builder.add_terminate(block, type_id)
}
GetTagId { .. } => builder.add_make_tuple(block, &[]),
}
}
@ -525,16 +944,25 @@ fn layout_spec(builder: &mut FuncDefBuilder, layout: &Layout) -> Result<TypeId>
match layout {
Builtin(builtin) => builtin_spec(builder, builtin),
PhantomEmptyStruct => todo!(),
Struct(fields) => build_tuple_type(builder, fields),
Union(union_layout) => {
let variant_types = build_variant_types_help(builder, union_layout)?;
builder.add_union_type(&variant_types)
}
RecursivePointer => todo!(),
FunctionPointer(_, _) => todo!(),
Closure(_, _, _) => todo!(),
Pointer(_) => todo!(),
Union(union_layout) => match union_layout {
UnionLayout::NonRecursive(_) => {
let variant_types = build_variant_types(builder, union_layout)?;
builder.add_union_type(&variant_types)
}
UnionLayout::Recursive(_) => worst_case_type(builder),
UnionLayout::NonNullableUnwrapped(_) => worst_case_type(builder),
UnionLayout::NullableWrapped {
nullable_id: _,
other_tags: _,
} => worst_case_type(builder),
UnionLayout::NullableUnwrapped {
nullable_id: _,
other_fields: _,
} => worst_case_type(builder),
},
RecursivePointer => worst_case_type(builder),
Closure(_, lambda_set, _) => layout_spec(builder, &lambda_set.runtime_representation()),
}
}
@ -543,69 +971,80 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeI
match builtin {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]),
Float128 => todo!(),
Float64 => todo!(),
Float32 => todo!(),
Float16 => todo!(),
Str => todo!(),
Dict(_, _) => todo!(),
Set(_) => todo!(),
List(_, _) => {
// TODO should incorporate the element type into the name
Ok(builder.add_named_type(MOD_LIST, TypeName(b"List")))
Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]),
Str | EmptyStr => str_type(builder),
Dict(key_layout, value_layout) => {
let value_type = layout_spec(builder, value_layout)?;
let key_type = layout_spec(builder, key_layout)?;
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
builder.add_tuple_type(&[cell, bag])
}
Set(key_layout) => {
let value_type = builder.add_tuple_type(&[])?;
let key_type = layout_spec(builder, key_layout)?;
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
builder.add_tuple_type(&[cell, bag])
}
List(element_layout) => {
let element_type = layout_spec(builder, element_layout)?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
builder.add_tuple_type(&[cell, bag])
}
EmptyList => {
// TODO make sure that we consistently treat the EmptyList as a list of unit values
let element_type = builder.add_tuple_type(&[])?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
builder.add_tuple_type(&[cell, bag])
}
EmptyDict | EmptySet => {
// TODO make sure that we consistently treat the these as a dict of unit values
let unit = builder.add_tuple_type(&[])?;
let element_type = builder.add_tuple_type(&[unit, unit])?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
builder.add_tuple_type(&[cell, bag])
}
EmptyStr => todo!(),
EmptyList => todo!(),
EmptyDict => todo!(),
EmptySet => todo!(),
}
}
fn str_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
let cell_id = builder.add_heap_cell_type();
builder.add_tuple_type(&[cell_id])
}
// const OK_TAG_ID: u8 = 1u8;
// const ERR_TAG_ID: u8 = 0u8;
const LIST_CELL_INDEX: u32 = 0;
const LIST_BAG_INDEX: u32 = 1;
const LIST_LEN_INDEX: u32 = 2;
const DICT_CELL_INDEX: u32 = LIST_CELL_INDEX;
const DICT_BAG_INDEX: u32 = LIST_BAG_INDEX;
fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) -> Result<ValueId> {
let cell = builder.add_new_heap_cell(block)?;
let bag = builder.add_empty_bag(block, element_type)?;
let length = new_usize(builder, block)?;
builder.add_make_tuple(block, &[cell, bag, length])
}
fn new_usize(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
new_num(builder, block)
builder.add_make_tuple(block, &[cell, bag])
}
fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
let cell = builder.add_new_heap_cell(block)?;
let module = MOD_APP;
// immediately mutate the cell, so any future updates on this value are invalid
// updating a static string would cause a crash at runtime
let _ = builder.add_update(block, UpdateModeVar(&[]), cell)?;
let length = new_usize(builder, block)?;
builder.add_make_tuple(block, &[cell, length])
}
fn new_order(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// always generats EQ
let tag_id = 0;
let unit = builder.add_tuple_type(&[])?;
let unit_value = builder.add_make_tuple(block, &[])?;
builder.add_make_union(block, &[unit, unit, unit], tag_id, unit_value)
}
fn new_bool(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// always generats False
let tag_id = 0;
let unit = builder.add_tuple_type(&[])?;
let unit_value = builder.add_make_tuple(block, &[])?;
builder.add_make_union(block, &[unit, unit], tag_id, unit_value)
builder.add_const_ref(block, module, STATIC_STR_NAME)
}
fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {

View File

@ -1,4 +1,4 @@
use crate::ir::{Expr, JoinPointId, Param, Proc, Stmt, TopLevelFunctionLayout};
use crate::ir::{Expr, JoinPointId, Param, Proc, ProcLayout, Stmt};
use crate::layout::Layout;
use bumpalo::collections::Vec;
use bumpalo::Bump;
@ -18,15 +18,14 @@ fn should_borrow_layout(layout: &Layout) -> bool {
pub fn infer_borrow<'a>(
arena: &'a Bump,
procs: &MutMap<(Symbol, TopLevelFunctionLayout<'a>), Proc<'a>>,
procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> ParamMap<'a> {
let mut param_map = ParamMap {
items: MutMap::default(),
};
for ((s, top_level), proc) in procs {
let key = (*s, arena.alloc(*top_level).full());
param_map.visit_proc(arena, proc, key);
for (key, proc) in procs {
param_map.visit_proc(arena, proc, *key);
}
let mut env = BorrowInfState {
@ -40,7 +39,7 @@ pub fn infer_borrow<'a>(
// This is a fixed-point analysis
//
// all functions initiall own all their paramters
// all functions initiall own all their parameters
// through a series of checks and heuristics, some arguments are set to borrowed
// when that doesn't lead to conflicts the change is kept, otherwise it may be reverted
//
@ -51,8 +50,7 @@ pub fn infer_borrow<'a>(
// mutually recursive functions (or just make all their arguments owned)
for (key, proc) in procs {
let layout = arena.alloc(key.1).full();
env.collect_proc(proc, layout);
env.collect_proc(proc, key.1);
}
if !env.modified {
@ -69,7 +67,7 @@ pub fn infer_borrow<'a>(
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Key<'a> {
Declaration(Symbol, Layout<'a>),
Declaration(Symbol, ProcLayout<'a>),
JoinPoint(JoinPointId),
}
@ -98,7 +96,7 @@ impl<'a> IntoIterator for &'a ParamMap<'a> {
}
impl<'a> ParamMap<'a> {
pub fn get_symbol(&self, symbol: Symbol, layout: Layout<'a>) -> Option<&'a [Param<'a>]> {
pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&'a [Param<'a>]> {
let key = Key::Declaration(symbol, layout);
self.items.get(&key).copied()
@ -153,7 +151,7 @@ impl<'a> ParamMap<'a> {
.into_bump_slice()
}
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>, key: (Symbol, Layout<'a>)) {
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>, key: (Symbol, ProcLayout<'a>)) {
if proc.must_own_arguments {
self.visit_proc_always_owned(arena, proc, key);
return;
@ -171,7 +169,7 @@ impl<'a> ParamMap<'a> {
&mut self,
arena: &'a Bump,
proc: &Proc<'a>,
key: (Symbol, Layout<'a>),
key: (Symbol, ProcLayout<'a>),
) {
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
@ -193,12 +191,16 @@ impl<'a> ParamMap<'a> {
id: j,
parameters: xs,
remainder: v,
continuation: b,
body: b,
} => {
let already_in_there = self
.items
.insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs));
debug_assert!(already_in_there.is_none());
debug_assert!(
already_in_there.is_none(),
"join point {:?} is already defined!",
j
);
stack.push(v);
stack.push(b);
@ -220,7 +222,7 @@ impl<'a> ParamMap<'a> {
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Rethrow | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => {
// these are terminal, do nothing
}
}
@ -348,7 +350,7 @@ impl<'a> BorrowInfState<'a> {
/// let z = e in ...
///
/// and determines whether z and which of the symbols used in e
/// must be taken as owned paramters
/// must be taken as owned parameters
fn collect_call(&mut self, z: Symbol, e: &crate::ir::Call<'a>) {
use crate::ir::CallType::*;
@ -359,12 +361,17 @@ impl<'a> BorrowInfState<'a> {
match call_type {
ByName {
name, full_layout, ..
name,
ret_layout,
arg_layouts,
..
} => {
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout);
// get the borrow signature of the applied function
let ps = self
.param_map
.get_symbol(*name, *full_layout)
.get_symbol(*name, top_level)
.expect("function is defined");
// the return value will be owned
@ -393,15 +400,23 @@ impl<'a> BorrowInfState<'a> {
}
HigherOrderLowLevel {
op, closure_layout, ..
op,
arg_layouts,
ret_layout,
..
} => {
use roc_module::low_level::LowLevel::*;
debug_assert!(op.is_higher_order());
let closure_layout = ProcLayout {
arguments: arg_layouts,
result: *ret_layout,
};
match op {
ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
match self.param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => {
// own the list if the function wants to own the element
if !function_ps[0].borrow {
@ -417,7 +432,7 @@ impl<'a> BorrowInfState<'a> {
}
}
ListMapWithIndex => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
match self.param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => {
// own the list if the function wants to own the element
if !function_ps[1].borrow {
@ -432,7 +447,7 @@ impl<'a> BorrowInfState<'a> {
None => unreachable!(),
}
}
ListMap2 => match self.param_map.get_symbol(arguments[2], *closure_layout) {
ListMap2 => match self.param_map.get_symbol(arguments[2], closure_layout) {
Some(function_ps) => {
// own the lists if the function wants to own the element
if !function_ps[0].borrow {
@ -450,7 +465,7 @@ impl<'a> BorrowInfState<'a> {
}
None => unreachable!(),
},
ListMap3 => match self.param_map.get_symbol(arguments[3], *closure_layout) {
ListMap3 => match self.param_map.get_symbol(arguments[3], closure_layout) {
Some(function_ps) => {
// own the lists if the function wants to own the element
if !function_ps[0].borrow {
@ -471,7 +486,7 @@ impl<'a> BorrowInfState<'a> {
None => unreachable!(),
},
ListSortWith => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
match self.param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => {
// always own the input list
self.own_var(arguments[0]);
@ -485,7 +500,7 @@ impl<'a> BorrowInfState<'a> {
}
}
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => {
match self.param_map.get_symbol(arguments[2], *closure_layout) {
match self.param_map.get_symbol(arguments[2], closure_layout) {
Some(function_ps) => {
// own the data structure if the function wants to own the element
if !function_ps[0].borrow {
@ -554,7 +569,12 @@ impl<'a> BorrowInfState<'a> {
EmptyArray => {
self.own_var(z);
}
AccessAtIndex { structure: x, .. } => {
Call(call) => self.collect_call(z, call),
Literal(_) | RuntimeErrorFunction(_) => {}
StructAtIndex { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) {
self.own_var(z);
@ -566,9 +586,29 @@ impl<'a> BorrowInfState<'a> {
}
}
Call(call) => self.collect_call(z, call),
UnionAtIndex { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) {
self.own_var(z);
}
Literal(_) | RuntimeErrorFunction(_) => {}
// if the extracted value is owned, the structure must be too
if self.is_owned(z) {
self.own_var(*x);
}
}
GetTagId { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) {
self.own_var(z);
}
// if the extracted value is owned, the structure must be too
if self.is_owned(z) {
self.own_var(*x);
}
}
}
}
@ -579,7 +619,8 @@ impl<'a> BorrowInfState<'a> {
call_type:
crate::ir::CallType::ByName {
name: g,
full_layout,
arg_layouts,
ret_layout,
..
},
arguments: ys,
@ -588,10 +629,12 @@ impl<'a> BorrowInfState<'a> {
Stmt::Ret(z),
) = (v, b)
{
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout);
if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g, *full_layout) {
if let Some(ps) = self.param_map.get_symbol(*g, top_level) {
self.own_params_using_args(ys, ps)
}
}
@ -618,7 +661,7 @@ impl<'a> BorrowInfState<'a> {
id: j,
parameters: ys,
remainder: v,
continuation: b,
body: b,
} => {
let old = self.param_set.clone();
self.update_param_set(ys);
@ -641,6 +684,7 @@ impl<'a> BorrowInfState<'a> {
layout: _,
pass,
fail,
exception_id: _,
} => {
self.collect_stmt(pass);
self.collect_stmt(fail);
@ -672,13 +716,13 @@ impl<'a> BorrowInfState<'a> {
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | RuntimeError(_) | Rethrow => {
Ret(_) | RuntimeError(_) | Resume(_) => {
// these are terminal, do nothing
}
}
}
fn collect_proc(&mut self, proc: &Proc<'a>, layout: Layout<'a>) {
fn collect_proc(&mut self, proc: &Proc<'a>, layout: ProcLayout<'a>) {
let old = self.param_set.clone();
let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice();
@ -720,7 +764,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
match op {
ListLen | StrIsEmpty | StrCountGraphemes => arena.alloc_slice_copy(&[borrowed]),
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListSetInPlace => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
@ -748,6 +791,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
// List.append should own its first argument
ListAppend => arena.alloc_slice_copy(&[owned, owned]),
ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]),
ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),

File diff suppressed because it is too large Load Diff

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