mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-11 18:58:24 +03:00
Merge remote-tracking branch 'upstream/main' into rename-roc-language-server
This commit is contained in:
commit
87ff15905e
51
.github/workflows/basic_cli_build_release.yml
vendored
51
.github/workflows/basic_cli_build_release.yml
vendored
@ -6,7 +6,7 @@ on:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
env:
|
||||
# use .tar.gz for quick testing
|
||||
ARCHIVE_FORMAT: .tar.br
|
||||
@ -17,7 +17,7 @@ jobs:
|
||||
prepare:
|
||||
runs-on: [ubuntu-20.04]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: roc-lang/basic-cli
|
||||
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
|
||||
|
||||
- name: Save roc_nightly archives
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: roc_nightly-*
|
||||
|
||||
@ -48,18 +48,18 @@ jobs:
|
||||
runs-on: [ubuntu-20.04]
|
||||
needs: [prepare]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download the previously uploaded roc_nightly archives
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: build basic-cli with surgical linker and also with legacy linker
|
||||
env:
|
||||
CARGO_BUILD_TARGET: x86_64-unknown-linux-musl
|
||||
run: ./ci/build_basic_cli.sh linux_x86_64 "--linker legacy"
|
||||
|
||||
- name: Save .rh, .rm and .o file
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Save .rh, .rm and .o file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-x86_64-files
|
||||
path: |
|
||||
@ -72,10 +72,10 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, ARM64]
|
||||
needs: [prepare]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download the previously uploaded roc_nightly archives
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: build basic-cli
|
||||
env:
|
||||
@ -85,8 +85,8 @@ jobs:
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld"
|
||||
run: ./ci/build_basic_cli.sh linux_arm64
|
||||
|
||||
- name: Save .o file
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Save .o file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-arm64-files
|
||||
path: |
|
||||
@ -96,15 +96,15 @@ jobs:
|
||||
runs-on: [macos-11] # I expect the generated files to work on macOS 12 and up
|
||||
needs: [prepare]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download the previously uploaded roc_nightly archives
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- run: ./ci/build_basic_cli.sh macos_x86_64
|
||||
|
||||
- name: Save .o files
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-x86_64-files
|
||||
path: |
|
||||
@ -115,15 +115,15 @@ jobs:
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
needs: [prepare]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download the previously uploaded roc_nightly archives
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- run: ./ci/build_basic_cli.sh macos_apple_silicon
|
||||
|
||||
- name: Save macos-arm64.o file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-apple-silicon-files
|
||||
path: |
|
||||
@ -134,13 +134,13 @@ jobs:
|
||||
name: create release archive
|
||||
runs-on: [ubuntu-20.04]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: remove all folders except the ci folder
|
||||
run: ls | grep -v ci | xargs rm -rf
|
||||
|
||||
- name: Download the previously uploaded files
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: mv roc nightly and simplify name
|
||||
run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz
|
||||
@ -157,7 +157,7 @@ jobs:
|
||||
- run: git clone https://github.com/roc-lang/basic-cli.git
|
||||
|
||||
- run: cp macos-apple-silicon-files/* ./basic-cli/platform
|
||||
|
||||
|
||||
- run: cp linux-x86_64-files/* ./basic-cli/platform
|
||||
|
||||
- run: cp linux-arm64-files/* ./basic-cli/platform
|
||||
@ -177,14 +177,14 @@ jobs:
|
||||
- run: echo "TAR_FILENAME=$(ls -d basic-cli/platform/* | grep ${{ env.ARCHIVE_FORMAT }})" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload platform archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: basic-cli-platform
|
||||
path: |
|
||||
${{ env.TAR_FILENAME }}
|
||||
|
||||
- name: Upload docs archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-assets-docs
|
||||
path: |
|
||||
@ -196,7 +196,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Download the previously uploaded files
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: mv roc nightly and simplify name
|
||||
run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz
|
||||
@ -232,7 +232,7 @@ jobs:
|
||||
mkdir platform
|
||||
# move all files to platform dir
|
||||
find . -maxdepth 1 -type f -exec mv {} platform/ \;
|
||||
|
||||
|
||||
mkdir temp-basic-cli
|
||||
cd temp-basic-cli
|
||||
git clone https://github.com/roc-lang/basic-cli.git
|
||||
@ -242,9 +242,8 @@ jobs:
|
||||
cp -r ci ../..
|
||||
cp -r LICENSE ../..
|
||||
# LICENSE is necessary for command test
|
||||
|
||||
|
||||
- name: run tests
|
||||
run: |
|
||||
cd basic-cli-platform
|
||||
ROC=./roc_nightly/roc EXAMPLES_DIR=./examples/ ROC_BUILD_FLAGS=--prebuilt-platform ./ci/all_tests.sh
|
||||
|
||||
|
8
.github/workflows/basic_cli_test_arm64.yml
vendored
8
.github/workflows/basic_cli_test_arm64.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, ARM64]
|
||||
steps:
|
||||
- name: clone basic-cli repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: roc-lang/basic-cli
|
||||
ref: main
|
||||
@ -19,7 +19,7 @@ jobs:
|
||||
- name: get latest roc nightly
|
||||
run: |
|
||||
curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz
|
||||
|
||||
|
||||
- name: rename nightly tar
|
||||
run: mv $(ls | grep "roc_nightly.*tar\.gz") roc_nightly.tar.gz
|
||||
|
||||
@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
- run: expect -v
|
||||
|
||||
# Run all tests
|
||||
# Run all tests
|
||||
- run: ROC=./roc_nightly/roc EXAMPLES_DIR=./examples/ ./ci/all_tests.sh
|
||||
|
||||
######
|
||||
@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
- name: Remove roc_nightly folder to keep things simple (we'll download it again later)
|
||||
run: rm -rf roc_nightly
|
||||
|
||||
|
||||
- name: Get the repo of the latest basic-cli release
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/roc-lang/basic-cli
|
||||
|
@ -6,7 +6,7 @@ on:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
env:
|
||||
# use .tar.gz for quick testing
|
||||
ARCHIVE_FORMAT: .tar.br
|
||||
@ -16,7 +16,7 @@ jobs:
|
||||
fetch-releases:
|
||||
runs-on: [ubuntu-20.04]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz
|
||||
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz
|
||||
@ -24,7 +24,7 @@ jobs:
|
||||
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
|
||||
|
||||
- name: Save roc_nightly archives
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: roc_nightly-*
|
||||
|
||||
@ -32,18 +32,18 @@ jobs:
|
||||
runs-on: [ubuntu-20.04]
|
||||
needs: [fetch-releases]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download the previously uploaded roc_nightly archives
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: build basic-webserver with legacy linker
|
||||
env:
|
||||
CARGO_BUILD_TARGET: x86_64-unknown-linux-musl
|
||||
run: ./ci/build_basic_webserver.sh linux_x86_64 "--linker legacy"
|
||||
|
||||
- name: Save .rh, .rm and .o file
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Save .rh, .rm and .o file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-x86_64-files
|
||||
path: |
|
||||
@ -56,10 +56,10 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, ARM64]
|
||||
needs: [fetch-releases]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download the previously uploaded roc_nightly archives
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: build basic-webserver
|
||||
env:
|
||||
@ -69,8 +69,8 @@ jobs:
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld"
|
||||
run: ./ci/build_basic_webserver.sh linux_arm64
|
||||
|
||||
- name: Save .o file
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Save .o file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-arm64-files
|
||||
path: |
|
||||
@ -80,15 +80,15 @@ jobs:
|
||||
runs-on: [macos-11] # I expect the generated files to work on macOS 12 and 13
|
||||
needs: [fetch-releases]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download the previously uploaded roc_nightly archives
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- run: ./ci/build_basic_webserver.sh macos_x86_64
|
||||
|
||||
- name: Save .o files
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-x86_64-files
|
||||
path: |
|
||||
@ -99,15 +99,15 @@ jobs:
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
needs: [fetch-releases]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download the previously uploaded roc_nightly archives
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- run: ./ci/build_basic_webserver.sh macos_apple_silicon
|
||||
|
||||
- name: Save macos-arm64.o file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-apple-silicon-files
|
||||
path: |
|
||||
@ -118,13 +118,13 @@ jobs:
|
||||
name: create release archive
|
||||
runs-on: [ubuntu-20.04]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: remove all folders except the ci folder
|
||||
run: ls | grep -v ci | xargs rm -rf
|
||||
|
||||
- name: Download the previously uploaded files
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: mv roc nightly and simplify name
|
||||
run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz
|
||||
@ -145,7 +145,7 @@ jobs:
|
||||
cd ..
|
||||
|
||||
- run: cp macos-apple-silicon-files/* ./basic-webserver/platform
|
||||
|
||||
|
||||
- run: cp linux-x86_64-files/* ./basic-webserver/platform
|
||||
|
||||
- run: cp linux-arm64-files/* ./basic-webserver/platform
|
||||
@ -157,7 +157,7 @@ jobs:
|
||||
- run: echo "TAR_FILENAME=$(ls -d basic-webserver/platform/* | grep ${{ env.ARCHIVE_FORMAT }})" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload platform archive
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: basic-webserver-platform
|
||||
path: |
|
||||
|
4
.github/workflows/benchmarks.yml
vendored
4
.github/workflows/benchmarks.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: "main"
|
||||
clean: "true"
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
- name: on main; prepare a self-contained benchmark folder
|
||||
run: nix develop -c ./ci/benchmarks/prep_folder.sh main
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
clean: "false" # we want to keep the benchmark folder
|
||||
|
||||
|
6
.github/workflows/ci_manager.yml
vendored
6
.github/workflows/ci_manager.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
run_tests: ${{ steps.filecheck.outputs.run_tests }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check if only css, html or md files changed
|
||||
id: filecheck
|
||||
@ -112,7 +112,3 @@ jobs:
|
||||
fi
|
||||
|
||||
- run: echo "Workflow succeeded :)"
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
|
||||
name: devtools nix files test - linux
|
||||
|
||||
concurrency:
|
||||
@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: [ubuntu-20.04]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Only run all steps if a nix file changed
|
||||
run: |
|
||||
@ -25,7 +25,7 @@ jobs:
|
||||
echo "A nix file was changed. No need to run tests."
|
||||
echo "nix_changed=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
|
||||
|
||||
- uses: cachix/install-nix-action@v23
|
||||
if: env.nix_changed == 'true'
|
||||
@ -53,6 +53,3 @@ jobs:
|
||||
echo "locally deleting devtools/flake.lock and following the"
|
||||
echo "instructions in devtools/README.md. This will create a"
|
||||
echo "new flake.lock you should use to replace the old devtools/flake.lock"
|
||||
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Only run all steps if a nix file changed
|
||||
run: |
|
||||
@ -47,6 +47,3 @@ jobs:
|
||||
echo "locally deleting devtools/flake.lock and following the"
|
||||
echo "instructions in devtools/README.md. This will create a"
|
||||
echo "new flake.lock you should use to replace the old devtools/flake.lock"
|
||||
|
||||
|
||||
|
||||
|
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: [ubuntu-22.04]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Copy example docker file
|
||||
run: cp docker/nightly-ubuntu-latest/docker-compose.example.yml docker/nightly-ubuntu-latest/docker-compose.yml
|
||||
@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: [ubuntu-22.04]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Copy example docker file
|
||||
run: cp docker/nightly-ubuntu-2204/docker-compose.example.yml docker/nightly-ubuntu-2204/docker-compose.yml
|
||||
@ -42,7 +42,7 @@ jobs:
|
||||
runs-on: [ubuntu-22.04]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Copy example docker file
|
||||
run: cp docker/nightly-ubuntu-2004/docker-compose.example.yml docker/nightly-ubuntu-2004/docker-compose.yml
|
||||
@ -58,7 +58,7 @@ jobs:
|
||||
runs-on: [ubuntu-22.04]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Copy example docker file
|
||||
run: cp docker/nightly-debian-latest/docker-compose.example.yml docker/nightly-debian-latest/docker-compose.yml
|
||||
@ -74,7 +74,7 @@ jobs:
|
||||
runs-on: [ubuntu-22.04]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Copy example docker file
|
||||
run: cp docker/nightly-debian-bookworm/docker-compose.example.yml docker/nightly-debian-bookworm/docker-compose.yml
|
||||
@ -90,7 +90,7 @@ jobs:
|
||||
runs-on: [ubuntu-22.04]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Copy example docker file
|
||||
run: cp docker/nightly-debian-buster/docker-compose.example.yml docker/nightly-debian-buster/docker-compose.yml
|
||||
@ -100,4 +100,3 @@ jobs:
|
||||
|
||||
- name: Run hello world test
|
||||
run: docker-compose -f docker/nightly-debian-buster/docker-compose.yml run roc examples/helloWorld.roc
|
||||
|
||||
|
44
.github/workflows/macos_x86_64.yml
vendored
44
.github/workflows/macos_x86_64.yml
vendored
@ -1,33 +1,33 @@
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_call:
|
||||
|
||||
name: Macos x86-64 rust tests
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
test-rust-macos-x86-64:
|
||||
runs-on: [self-hosted, macOS, X64]
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
RUSTC_WRAPPER: /Users/username1/.cargo/bin/sccache
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
test-rust-macos-x86-64:
|
||||
runs-on: [self-hosted, macOS, X64]
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
RUSTC_WRAPPER: /Users/username1/.cargo/bin/sccache
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set LLVM_SYS_160_PREFIX
|
||||
run: echo "LLVM_SYS_160_PREFIX=$(brew --prefix llvm@16)" >> $GITHUB_ENV
|
||||
- name: set LLVM_SYS_160_PREFIX
|
||||
run: echo "LLVM_SYS_160_PREFIX=$(brew --prefix llvm@16)" >> $GITHUB_ENV
|
||||
|
||||
- name: Update PATH to use zig 11
|
||||
run: |
|
||||
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
|
||||
|
||||
- run: zig version
|
||||
- name: Update PATH to use zig 11
|
||||
run: |
|
||||
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
|
||||
|
||||
- name: test_gen llvm tests
|
||||
run: cargo nextest-gen-llvm --release --no-fail-fast --locked -E "package(test_gen) - test(gen_str::str_append_scalar)"
|
||||
- run: zig version
|
||||
|
||||
- name: regular rust tests
|
||||
run: cargo test --locked --release -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_str::str_append_scalar --skip gen_tags::phantom_polymorphic_record && sccache --show-stats
|
||||
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
|
||||
# this issue may be caused by using older versions of XCode
|
||||
- name: test_gen llvm tests
|
||||
run: cargo nextest-gen-llvm --release --no-fail-fast --locked -E "package(test_gen) - test(gen_str::str_append_scalar)"
|
||||
|
||||
- name: regular rust tests
|
||||
run: cargo test --locked --release -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_tags::phantom_polymorphic_record && sccache --show-stats
|
||||
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
|
||||
# this issue may be caused by using older versions of XCode
|
||||
|
2
.github/workflows/markdown_link_check.yml
vendored
2
.github/workflows/markdown_link_check.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
markdown-link-check:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
use-quiet-mode: 'yes'
|
||||
|
14
.github/workflows/nightly_linux_arm64.yml
vendored
14
.github/workflows/nightly_linux_arm64.yml
vendored
@ -5,7 +5,7 @@ on:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
name: Nightly Release Linux arm64/aarch64
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build and package nightly release
|
||||
@ -13,12 +13,12 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Update PATH to use zig 11
|
||||
run: |
|
||||
echo "PATH=/home/username/Downloads/zig-linux-aarch64-0.11.0:$PATH" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- run: zig version
|
||||
|
||||
- name: create version.txt
|
||||
@ -29,10 +29,10 @@ jobs:
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
@ -42,12 +42,12 @@ jobs:
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release-with-lto/roc
|
||||
|
||||
|
||||
- name: Make nightly release tar archive
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
||||
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
|
18
.github/workflows/nightly_linux_x86_64.yml
vendored
18
.github/workflows/nightly_linux_x86_64.yml
vendored
@ -5,20 +5,20 @@ on:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
name: Nightly Release Linux x86_64
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build and package nightly release
|
||||
runs-on: [self-hosted, i7-6700K]
|
||||
timeout-minutes: 90
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Update PATH to use zig 11
|
||||
run: |
|
||||
echo "PATH=/home/big-ci-user/Downloads/zig-linux-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- run: zig version
|
||||
|
||||
- name: create version.txt
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
@ -38,12 +38,12 @@ jobs:
|
||||
run: ./ci/www-repl.sh
|
||||
|
||||
- name: Upload wasm repl tar. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: roc_repl_wasm.tar.gz
|
||||
path: roc_repl_wasm.tar.gz
|
||||
retention-days: 4
|
||||
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
@ -53,12 +53,12 @@ jobs:
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release-with-lto/roc
|
||||
|
||||
|
||||
- name: Make nightly release tar archive
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
||||
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
|
@ -16,8 +16,8 @@ jobs:
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- run: zig version
|
||||
|
||||
- name: llvm version
|
||||
@ -25,18 +25,18 @@ jobs:
|
||||
|
||||
- name: run tests
|
||||
run: cargo test --locked --release
|
||||
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_apple_silicon-$DATE-$SHA" >> $GITHUB_ENV
|
||||
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_apple_silicon-$DATE-$SHA" >> $GITHUB_ENV
|
||||
|
||||
- name: write version to file
|
||||
run: ./ci/write_version.sh
|
||||
@ -61,7 +61,7 @@ jobs:
|
||||
- name: print date
|
||||
run: date
|
||||
- name: Upload artifact Actually uploading to github releases has to be done manually
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
|
103
.github/workflows/nightly_macos_x86_64.yml
vendored
103
.github/workflows/nightly_macos_x86_64.yml
vendored
@ -1,63 +1,62 @@
|
||||
on:
|
||||
# pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 9 * * *' # 9=9am utc+0
|
||||
# pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 9 * * *" # 9=9am utc+0
|
||||
|
||||
name: Nightly Release macOS x86_64
|
||||
|
||||
env:
|
||||
LLVM_SYS_160_PREFIX: /usr/local/opt/llvm@16
|
||||
LLVM_SYS_160_PREFIX: /usr/local/opt/llvm@16
|
||||
|
||||
jobs:
|
||||
test-build-upload:
|
||||
name: build, test, package and upload nightly release
|
||||
runs-on: [self-hosted, macOS, X64]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
test-build-upload:
|
||||
name: build, test, package and upload nightly release
|
||||
runs-on: [self-hosted, macOS, X64]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Update PATH to use zig 11
|
||||
run: |
|
||||
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
|
||||
|
||||
- run: zig version
|
||||
|
||||
- name: write version to file
|
||||
run: ./ci/write_version.sh
|
||||
|
||||
- name: execute rust tests
|
||||
run: cargo test --release --locked -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_str::str_append_scalar --skip gen_tags::phantom_polymorphic_record
|
||||
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
|
||||
# this issue may be caused by using older versions of XCode
|
||||
- name: Update PATH to use zig 11
|
||||
run: |
|
||||
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
|
||||
|
||||
- name: build release
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc --bin roc_language_server
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's.
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV
|
||||
- run: zig version
|
||||
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release-with-lto/roc
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
||||
- name: Upload artifact. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
retention-days: 4
|
||||
- name: write version to file
|
||||
run: ./ci/write_version.sh
|
||||
|
||||
- name: execute rust tests
|
||||
run: cargo test --release --locked -- --skip opaque_wrap_function --skip gen_list::bool_list_literal --skip platform_switching_swift --skip swift_ui --skip gen_tags::phantom_polymorphic_record
|
||||
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
|
||||
# this issue may be caused by using older versions of XCode
|
||||
|
||||
- name: build release
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc --bin roc_language_server
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's.
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV
|
||||
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release-with-lto/roc
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
||||
- name: Upload artifact. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
retention-days: 4
|
||||
|
10
.github/workflows/nightly_old_linux_arm64.yml
vendored
10
.github/workflows/nightly_old_linux_arm64.yml
vendored
@ -5,21 +5,21 @@ on:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
name: Nightly Release Old Linux arm64 using Earthly
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build and package nightly release
|
||||
runs-on: [self-hosted, Linux, ARM64]
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
@ -32,7 +32,7 @@ jobs:
|
||||
run: earthly +build-nightly-release --RELEASE_FOLDER_NAME=${{ env.RELEASE_FOLDER_NAME }} --ZIG_ARCH=aarch64
|
||||
|
||||
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
|
14
.github/workflows/nightly_old_linux_x86_64.yml
vendored
14
.github/workflows/nightly_old_linux_x86_64.yml
vendored
@ -5,37 +5,37 @@ on:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
name: Nightly Release Old Linux x86_64 using Earthly
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build and package nightly release
|
||||
runs-on: [ubuntu-20.04]
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_FOLDER_NAME=roc_nightly-old_linux_x86_64-$DATE-$SHA" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: install earthly
|
||||
run: sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly && /usr/local/bin/earthly bootstrap --with-autocomplete'
|
||||
|
||||
|
||||
- run: earthly --version
|
||||
|
||||
- name: build release with earthly
|
||||
run: earthly +build-nightly-release --RELEASE_FOLDER_NAME=${{ env.RELEASE_FOLDER_NAME }} --RUSTFLAGS="-C target-cpu=x86-64"
|
||||
|
||||
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
|
||||
|
6
.github/workflows/nix_linux_arm64_cargo.yml
vendored
6
.github/workflows/nix_linux_arm64_cargo.yml
vendored
@ -1,6 +1,6 @@
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
|
||||
name: test cargo build on linux arm64 inside nix
|
||||
|
||||
env:
|
||||
@ -12,11 +12,11 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, ARM64]
|
||||
timeout-minutes: 150
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: test release build
|
||||
run: nix develop -c cargo build --release --locked
|
||||
|
||||
# TODO
|
||||
# TODO
|
||||
#- name: build tests without running
|
||||
# run: cargo test --no-run --release
|
||||
|
@ -1,7 +1,7 @@
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
name: test default.nix on linux arm64
|
||||
|
||||
name: test default.nix on linux arm64
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, ARM64]
|
||||
timeout-minutes: 150
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: test building default.nix
|
||||
run: nix-build
|
||||
|
6
.github/workflows/nix_linux_x86_64.yml
vendored
6
.github/workflows/nix_linux_x86_64.yml
vendored
@ -12,10 +12,10 @@ jobs:
|
||||
runs-on: [self-hosted, i5-4690K]
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: test building default.nix
|
||||
run: nix-build
|
||||
run: nix-build
|
||||
|
||||
- name: execute tests with --release
|
||||
run: nix develop -c cargo test --locked --release
|
||||
|
@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# These started to accumulate quickly since #5990, not sure why
|
||||
- name: Clean up old nix shells
|
||||
|
2
.github/workflows/nix_macos_x86_64.yml
vendored
2
.github/workflows/nix_macos_x86_64.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: [macos-12]
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: cachix/install-nix-action@v22
|
||||
|
||||
|
2
.github/workflows/spellcheck.yml
vendored
2
.github/workflows/spellcheck.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- run: cargo install typos-cli --version 1.0.11
|
||||
|
||||
|
@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: get the latest release archive
|
||||
run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
|
||||
@ -45,6 +45,3 @@ jobs:
|
||||
cp target/release/repl_basic_test ../../roc_nightly
|
||||
cd ../../roc_nightly
|
||||
./repl_basic_test
|
||||
|
||||
|
||||
|
||||
|
8
.github/workflows/test_nightly_many_os.yml
vendored
8
.github/workflows/test_nightly_many_os.yml
vendored
@ -13,14 +13,14 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: goto-bus-stop/setup-zig@v2
|
||||
with:
|
||||
version: 0.11.0
|
||||
|
||||
- name: get the latest release archive for linux (x86_64)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
run: |
|
||||
curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz
|
||||
|
||||
|
||||
@ -40,7 +40,3 @@ jobs:
|
||||
rm -rf roc_nightly
|
||||
curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-old_linux_x86_64-latest.tar.gz
|
||||
./ci/basic_nightly_test.sh
|
||||
|
||||
|
||||
|
||||
|
||||
|
6
.github/workflows/ubuntu_x86_64.yml
vendored
6
.github/workflows/ubuntu_x86_64.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
env:
|
||||
RUSTC_WRAPPER: /home/big-ci-user/.cargo/bin/sccache
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for duplicate AUTHORS
|
||||
run: diff <(sort AUTHORS) <(sort AUTHORS | uniq) # The < operator treats a string as a file. diff 'succeeds' if no difference.
|
||||
@ -22,7 +22,7 @@ jobs:
|
||||
- name: Update PATH to use zig 11
|
||||
run: |
|
||||
echo "PATH=/home/big-ci-user/Downloads/zig-linux-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- run: zig version
|
||||
|
||||
- name: zig fmt check, zig tests
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: check that the platform`s produced dylib is loadable
|
||||
run: cd examples/platform-switching/rust-platform && LD_LIBRARY_PATH=. cargo test --release --locked
|
||||
|
||||
|
||||
- name: test the dev backend # these tests require an explicit feature flag
|
||||
run: cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
|
||||
|
||||
|
6
.github/workflows/windows_release_build.yml
vendored
6
.github/workflows/windows_release_build.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
timeout-minutes: 150
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- run: Add-Content -Path "$env:GITHUB_ENV" -Value "GITHUB_RUNNER_CPU=$((Get-CimInstance Win32_Processor).Name)"
|
||||
|
||||
@ -29,8 +29,8 @@ jobs:
|
||||
- name: zig version
|
||||
run: zig version
|
||||
|
||||
- name: install rust nightly 1.71.0
|
||||
run: rustup install nightly-2023-05-28
|
||||
- name: install rust nightly 1.72.0
|
||||
run: rustup install nightly-2023-07-09
|
||||
|
||||
- name: set up llvm 16
|
||||
run: |
|
||||
|
19
.github/workflows/windows_tests.yml
vendored
19
.github/workflows/windows_tests.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
timeout-minutes: 150
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- run: Add-Content -Path "$env:GITHUB_ENV" -Value "GITHUB_RUNNER_CPU=$((Get-CimInstance Win32_Processor).Name)"
|
||||
|
||||
@ -37,29 +37,26 @@ jobs:
|
||||
cd crates\compiler\builtins\bitcode\
|
||||
zig build test
|
||||
|
||||
- name: install rust nightly 1.71.0
|
||||
run: rustup install nightly-2023-05-28
|
||||
- name: install rust nightly 1.72.0
|
||||
run: rustup install nightly-2023-07-09
|
||||
|
||||
- name: set up llvm 16
|
||||
run: |
|
||||
curl.exe -f -L -O -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://github.com/roc-lang/llvm-package-windows/releases/download/v16.0.6/LLVM-16.0.6-win64.7z
|
||||
7z x LLVM-16.0.6-win64.7z -oC:\LLVM-16.0.6-win64
|
||||
|
||||
- name: Build tests --release without running.
|
||||
- name: Build tests --release without running.
|
||||
run: cargo test --locked --release --no-run
|
||||
|
||||
# Why are these tests not build with previous command? => fingerprint error. Use `CARGO_LOG=cargo::core::compiler::fingerprint=info` to investigate
|
||||
- name: Build specific tests without running.
|
||||
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli -p test_gen
|
||||
- name: Build specific tests without running.
|
||||
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli -p test_gen
|
||||
|
||||
- name: Test setjmp/longjmp logic
|
||||
run: cargo test-gen-dev --locked --release nat_alias && cargo test-gen-dev --locked --release a_crash
|
||||
|
||||
- name: Run gen tests
|
||||
run: cargo test-gen-llvm --locked --release gen_str
|
||||
|
||||
run: cargo test-gen-llvm --locked --release gen_str
|
||||
|
||||
- name: Actually run the tests.
|
||||
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_linker -p roc_cli
|
||||
|
||||
|
||||
|
||||
|
37
.gitignore
vendored
37
.gitignore
vendored
@ -1,3 +1,27 @@
|
||||
### Do not modify these first three ignore rules. Needed to ignore files with no extension ###
|
||||
# Ignore all files including binary files that have no extension
|
||||
*
|
||||
# Unignore all files with extensions
|
||||
!*.*
|
||||
# Unignore all directories
|
||||
!*/
|
||||
|
||||
# Specifically keep these files with no extension
|
||||
!Earthfile
|
||||
!AUTHORS
|
||||
!LICENSE*
|
||||
!LEGAL*
|
||||
!Dockerfile
|
||||
# .reuse/dep5 see https://reuse.software/
|
||||
!dep5
|
||||
# NotARocFile is used for formatter test
|
||||
!NotARocFile
|
||||
# also includes keeping one exe benchmark file
|
||||
!dynhost_benchmarks*
|
||||
|
||||
### Add specific file extensions and directories below ###
|
||||
|
||||
# Ignore the following directories and file extensions
|
||||
target
|
||||
generated-docs
|
||||
zig-cache
|
||||
@ -63,7 +87,7 @@ result
|
||||
|
||||
# Only keep Cargo.lock dependencies for the main compiler.
|
||||
# Examples and test only crates should be fine to be unlocked.
|
||||
# This remove unneccessary lock file versioning.
|
||||
# This remove unnecessary lock file versioning.
|
||||
# It also ensures the compiler can always pull in 1 version of things and doesn't get restricted by sub lockfiles.
|
||||
/**/Cargo.lock
|
||||
!/Cargo.lock
|
||||
@ -83,4 +107,13 @@ www/dist
|
||||
# ignore the examples folder in the WIP website, this is copied from roc-lang/examples in when building the site
|
||||
www/content/examples
|
||||
www/examples-main.zip
|
||||
www/examples-main
|
||||
www/examples-main
|
||||
|
||||
examples/static-site-gen/**/*.html
|
||||
|
||||
# glue auto-generated fixture code
|
||||
crates/glue/tests/fixtures/*/Cargo.toml
|
||||
crates/glue/tests/fixtures/*/build.rs
|
||||
crates/glue/tests/fixtures/*/host.c
|
||||
crates/glue/tests/fixtures/*/src/main.rs
|
||||
crates/glue/tests/fixtures/*/test_glue/
|
||||
|
@ -10,7 +10,7 @@ On MacOS and Linux, we highly recommend Using [nix](https://nixos.org/download.h
|
||||
|
||||
### On Linux x86_64 or MacOS aarch64/arm64/x86_64
|
||||
|
||||
#### Install
|
||||
#### Installing Nix
|
||||
|
||||
If you are running ArchLinux or a derivative like Manjaro, you'll need to run `sudo sysctl -w kernel.unprivileged_userns_clone=1` before installing nix.
|
||||
|
||||
|
119
Cargo.lock
generated
119
Cargo.lock
generated
@ -771,6 +771,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dissimilar"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632"
|
||||
|
||||
[[package]]
|
||||
name = "distance"
|
||||
version = "0.4.0"
|
||||
@ -838,6 +844,19 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
@ -864,6 +883,16 @@ dependencies = [
|
||||
"str-buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "expect-test"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3"
|
||||
dependencies = [
|
||||
"dissimilar",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
version = "3.0.13"
|
||||
@ -1151,6 +1180,12 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.27"
|
||||
@ -1327,6 +1362,17 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.3",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
@ -1922,7 +1968,7 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"env_logger 0.8.4",
|
||||
"log",
|
||||
"rand",
|
||||
]
|
||||
@ -2273,6 +2319,7 @@ dependencies = [
|
||||
"roc_collections",
|
||||
"roc_command_utils",
|
||||
"roc_constrain",
|
||||
"roc_debug_flags",
|
||||
"roc_error_macros",
|
||||
"roc_gen_dev",
|
||||
"roc_gen_llvm",
|
||||
@ -2649,6 +2696,10 @@ name = "roc_lang_srv"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"env_logger 0.10.2",
|
||||
"expect-test",
|
||||
"indoc",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
@ -4546,6 +4597,15 @@ dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
@ -4576,6 +4636,21 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@ -4588,6 +4663,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
@ -4600,6 +4681,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
@ -4612,6 +4699,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
@ -4624,6 +4717,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
@ -4636,6 +4735,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@ -4648,6 +4753,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
@ -4660,6 +4771,12 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
VERSION 0.6
|
||||
FROM rust:1.71.1-slim-buster # make sure to update rust-toolchain.toml too so that everything uses the same rust version
|
||||
FROM rust:1.72.1-slim-buster # make sure to update rust-toolchain.toml too so that everything uses the same rust version
|
||||
WORKDIR /earthbuild
|
||||
|
||||
prep-debian:
|
||||
@ -32,7 +32,7 @@ install-zig-llvm:
|
||||
RUN apt -y install libpolly-16-dev # required by llvm-sys crate
|
||||
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
|
||||
RUN apt -y install libssl-dev
|
||||
RUN OPENSSL_NO_VENDOR=1 cargo install wasm-pack
|
||||
RUN wget https://rustwasm.github.io/wasm-pack/installer/init.sh -O init.sh && sh init.sh
|
||||
# sccache
|
||||
RUN cargo install sccache --locked
|
||||
RUN sccache -V
|
||||
|
@ -33,6 +33,7 @@ If you would like your company to become a corporate sponsor of Roc's developmen
|
||||
|
||||
We'd also like to express our gratitude to our generous [individual sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more:
|
||||
|
||||
* [Steven Chen](https://github.com/megakilo)
|
||||
* [Drew Lazzeri](https://github.com/asteroidb612)
|
||||
* [Alex Binaei](https://github.com/mrmizz)
|
||||
* [Jono Mallanyk](https://github.com/jonomallanyk)
|
||||
|
@ -3,7 +3,6 @@ use data_encoding::HEXUPPER;
|
||||
use is_executable::IsExecutable;
|
||||
use regex::Regex;
|
||||
use ring::digest::{Context, Digest, SHA256};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
@ -22,7 +21,7 @@ fn main() {
|
||||
delete_old_bench_results();
|
||||
|
||||
if optional_args.check_executables_changed {
|
||||
println!("Doing a test run to verify benchmarks are working correctly and generate executables.");
|
||||
println!("\nDoing a test run to verify benchmarks are working correctly and generate executables.\n");
|
||||
|
||||
std::env::set_var("BENCH_DRY_RUN", "1");
|
||||
|
||||
@ -79,6 +78,7 @@ fn finish(all_regressed_benches: HashSet<String>, nr_repeat_benchmarks: usize) {
|
||||
// returns all benchmarks that have regressed
|
||||
fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet<String> {
|
||||
delete_old_bench_results();
|
||||
|
||||
do_benchmark("main");
|
||||
let mut all_regressed_benches = do_benchmark("branch");
|
||||
|
||||
@ -91,6 +91,7 @@ fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet<String> {
|
||||
|
||||
for _ in 1..nr_repeat_benchmarks {
|
||||
delete_old_bench_results();
|
||||
|
||||
do_benchmark("main");
|
||||
let regressed_benches = do_benchmark("branch");
|
||||
|
||||
@ -110,17 +111,25 @@ fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet<String> {
|
||||
|
||||
// returns Vec with names of regressed benchmarks
|
||||
fn do_benchmark(branch_name: &'static str) -> HashSet<String> {
|
||||
let mut cmd_child = Command::new(format!(
|
||||
"./bench-folder-{}/target/release/deps/time_bench",
|
||||
branch_name
|
||||
))
|
||||
.args(&["--bench", "--noplot"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.unwrap_or_else(|_| panic!("Failed to benchmark {}.", branch_name));
|
||||
let mut bench_cmd =
|
||||
Command::new(format!(
|
||||
"./bench-folder-{}/target/release/deps/time_bench",
|
||||
branch_name
|
||||
));
|
||||
|
||||
let stdout = cmd_child.stdout.as_mut().unwrap();
|
||||
let bench_cmd_w_args =
|
||||
bench_cmd.args(&["--bench", "--noplot"]);
|
||||
|
||||
let bench_cmd_as_str = format!("{bench_cmd_w_args:?}");
|
||||
|
||||
let mut bench_cmd_child =
|
||||
bench_cmd_w_args
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.unwrap_or_else(|_| panic!("Failed to benchmark {}.", branch_name));
|
||||
|
||||
let stdout = bench_cmd_child.stdout.as_mut().unwrap();
|
||||
let stdout_reader = BufReader::new(stdout);
|
||||
let stdout_lines = stdout_reader.lines();
|
||||
|
||||
@ -147,6 +156,18 @@ fn do_benchmark(branch_name: &'static str) -> HashSet<String> {
|
||||
println!(">>bench {:?}: {:?}", branch_name, line_str);
|
||||
}
|
||||
|
||||
let exit_status = bench_cmd_child.wait().expect("Failed to wait on cmd_child");
|
||||
|
||||
if !exit_status.success() {
|
||||
panic!(
|
||||
"Error: time-bench execution failed with exit code {}.\n\
|
||||
See output above for error info.\n\
|
||||
Command was:\n\t{}",
|
||||
exit_status,
|
||||
bench_cmd_as_str
|
||||
);
|
||||
}
|
||||
|
||||
regressed_benches
|
||||
}
|
||||
|
||||
@ -190,20 +211,21 @@ fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest, io::Error> {
|
||||
}
|
||||
|
||||
fn sha_file(file_path: &Path) -> Result<String, io::Error> {
|
||||
// Debug info is dependent on the dir in which executable was created,
|
||||
// so we need to strip that to be able to compare binaries.
|
||||
let no_debug_info_file_path = file_path.to_str().unwrap().to_string() + ("_no_debug_info");
|
||||
std::fs::copy(file_path, &no_debug_info_file_path)?;
|
||||
// only checking disassembly because of #6386
|
||||
let disassembly_output = Command::new("objdump")
|
||||
.args(["-d", file_path.to_str().unwrap()])
|
||||
.output()
|
||||
.expect("failed to execute objdump");
|
||||
|
||||
let strip_output = Command::new("strip")
|
||||
.args(["--strip-debug", &no_debug_info_file_path])
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
assert!(disassembly_output.status.success());
|
||||
|
||||
assert!(strip_output.status.success());
|
||||
let mut reader = BufReader::new(disassembly_output.stdout.as_slice());
|
||||
|
||||
// the first line contains the path, we want to skip it
|
||||
let mut _discard_lines = String::new();
|
||||
reader.read_line(&mut _discard_lines)?;
|
||||
reader.read_line(&mut _discard_lines)?;
|
||||
|
||||
let no_debug_info_file = File::open(no_debug_info_file_path)?;
|
||||
let reader = BufReader::new(no_debug_info_file);
|
||||
let digest = sha256_digest(reader)?;
|
||||
|
||||
Ok(HEXUPPER.encode(digest.as_ref()))
|
||||
@ -250,6 +272,7 @@ fn check_if_bench_executables_changed() -> bool {
|
||||
let main_bench_hashes = calc_hashes_for_folder(&main_benches_path_str);
|
||||
|
||||
let branch_benches_path_str = [BENCH_FOLDER_BRANCH, bench_folder_str].join("");
|
||||
|
||||
let branch_bench_hashes = calc_hashes_for_folder(&branch_benches_path_str);
|
||||
|
||||
if main_bench_hashes.keys().len() == branch_bench_hashes.keys().len() {
|
||||
|
@ -1,19 +1,12 @@
|
||||
|
||||
# Running the benchmarks
|
||||
|
||||
Install cargo criterion:
|
||||
|
||||
If you're not using nix, install cargo criterion:
|
||||
```sh
|
||||
cargo install cargo-criterion
|
||||
```
|
||||
|
||||
To prevent stack overflow on the `CFold` benchmark:
|
||||
|
||||
```sh
|
||||
ulimit -s unlimited
|
||||
```
|
||||
|
||||
In the `cli` folder execute:
|
||||
In the `crates/cli` folder execute:
|
||||
|
||||
```sh
|
||||
cargo criterion
|
||||
|
@ -68,6 +68,7 @@ pub const FLAG_STDIN: &str = "stdin";
|
||||
pub const FLAG_STDOUT: &str = "stdout";
|
||||
pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb";
|
||||
pub const FLAG_OUTPUT: &str = "output";
|
||||
pub const FLAG_FUZZ: &str = "fuzz";
|
||||
pub const ROC_FILE: &str = "ROC_FILE";
|
||||
pub const ROC_DIR: &str = "ROC_DIR";
|
||||
pub const GLUE_DIR: &str = "GLUE_DIR";
|
||||
@ -139,6 +140,12 @@ pub fn build_app() -> Command {
|
||||
.value_parser(value_parser!(u32))
|
||||
.required(false);
|
||||
|
||||
let flag_fuzz = Arg::new(FLAG_FUZZ)
|
||||
.long(FLAG_FUZZ)
|
||||
.help("Instrument the roc binary for fuzzing with roc-fuzz")
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false);
|
||||
|
||||
let roc_file_to_run = Arg::new(ROC_FILE)
|
||||
.help("The .roc file of an app to run")
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
@ -175,6 +182,7 @@ pub fn build_app() -> Command {
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_linker.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(flag_fuzz.clone())
|
||||
.arg(flag_wasm_stack_size_kb)
|
||||
.arg(
|
||||
Arg::new(FLAG_TARGET)
|
||||
@ -225,6 +233,7 @@ pub fn build_app() -> Command {
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_linker.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(flag_fuzz.clone())
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file for the main module")
|
||||
@ -248,6 +257,7 @@ pub fn build_app() -> Command {
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_linker.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(flag_fuzz.clone())
|
||||
.arg(roc_file_to_run.clone())
|
||||
.arg(args_for_app.clone().last(true))
|
||||
)
|
||||
@ -262,11 +272,12 @@ pub fn build_app() -> Command {
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_linker.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(flag_fuzz.clone())
|
||||
.arg(roc_file_to_run.clone())
|
||||
.arg(args_for_app.clone().last(true))
|
||||
)
|
||||
.subcommand(Command::new(CMD_FORMAT)
|
||||
.about("Format a .roc file using standard Roc formatting")
|
||||
.about("Format a .roc file or the .roc files contained in a directory using standard\nRoc formatting")
|
||||
.arg(
|
||||
Arg::new(DIRECTORY_OR_FILES)
|
||||
.index(1)
|
||||
@ -294,6 +305,7 @@ pub fn build_app() -> Command {
|
||||
.action(ArgAction::SetTrue)
|
||||
.required(false),
|
||||
)
|
||||
.after_help("If DIRECTORY_OR_FILES is omitted, the .roc files in the current working\ndirectory are formatted.")
|
||||
)
|
||||
.subcommand(Command::new(CMD_VERSION)
|
||||
.about(concatcp!("Print the Roc compiler’s version, which is currently ", VERSION)))
|
||||
@ -392,6 +404,7 @@ pub fn build_app() -> Command {
|
||||
.arg(flag_time)
|
||||
.arg(flag_linker)
|
||||
.arg(flag_prebuilt)
|
||||
.arg(flag_fuzz)
|
||||
.arg(roc_file_to_run)
|
||||
.arg(args_for_app.trailing_var_arg(true))
|
||||
}
|
||||
@ -747,6 +760,11 @@ pub fn build(
|
||||
(cross_compile && !targeting_wasm)
|
||||
};
|
||||
|
||||
let fuzz = matches.get_flag(FLAG_FUZZ);
|
||||
if fuzz && !matches!(code_gen_backend, CodeGenBackend::Llvm(_)) {
|
||||
user_error!("Cannot instrument binary for fuzzing while using a dev backend.");
|
||||
}
|
||||
|
||||
let wasm_dev_stack_bytes: Option<u32> = matches
|
||||
.try_get_one::<u32>(FLAG_WASM_STACK_SIZE_KB)
|
||||
.ok()
|
||||
@ -763,6 +781,7 @@ pub fn build(
|
||||
opt_level,
|
||||
emit_debug_info,
|
||||
emit_llvm_ir,
|
||||
fuzz,
|
||||
};
|
||||
|
||||
let load_config = standard_load_config(&triple, build_ordering, threading);
|
||||
|
@ -287,6 +287,7 @@ fn main() -> io::Result<()> {
|
||||
values.push(os_string.to_owned());
|
||||
}
|
||||
}
|
||||
None if from_stdin || to_stdout => {}
|
||||
None => {
|
||||
let mut os_string_values: Vec<OsString> = Vec::new();
|
||||
|
||||
|
@ -323,11 +323,12 @@ mod cli_run {
|
||||
}
|
||||
|
||||
// when you want to run `roc test` to execute `expect`s, perhaps on a library rather than an application.
|
||||
fn test_roc_expect(dir_name: &str, roc_filename: &str) {
|
||||
let path = file_path_from_root(dir_name, roc_filename);
|
||||
let out = run_roc([CMD_TEST, path.to_str().unwrap()], &[], &[]);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
// not currently used
|
||||
// fn test_roc_expect(dir_name: &str, roc_filename: &str) {
|
||||
// let path = file_path_from_root(dir_name, roc_filename);
|
||||
// let out = run_roc([CMD_TEST, path.to_str().unwrap()], &[], &[]);
|
||||
// assert!(out.status.success());
|
||||
// }
|
||||
|
||||
// when you don't need args, stdin or extra_env
|
||||
fn test_roc_app_slim(
|
||||
@ -552,9 +553,11 @@ mod cli_run {
|
||||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
── EXPECT FAILED in ...roc/roc/crates/cli_testing_examples/expects/expects.roc ─
|
||||
|
||||
This expectation failed:
|
||||
|
||||
19│ expect words == []
|
||||
28│ expect words == []
|
||||
^^^^^^^^^^^
|
||||
|
||||
When it failed, these variables had these values:
|
||||
@ -562,12 +565,12 @@ mod cli_run {
|
||||
words : List Str
|
||||
words = ["this", "will", "for", "sure", "be", "a", "large", "string", "so", "when", "we", "split", "it", "it", "will", "use", "seamless", "slices", "which", "affect", "printing"]
|
||||
|
||||
[<ignored for tests>:22] x = 42
|
||||
[<ignored for tests>:23] "Fjoer en ferdjer frieten oan dyn geve lea" = "Fjoer en ferdjer frieten oan dyn geve lea"
|
||||
[<ignored for tests>:24] "this is line 24" = "this is line 24"
|
||||
[<ignored for tests>:13] x = "abc"
|
||||
[<ignored for tests>:13] x = 10
|
||||
[<ignored for tests>:13] x = (A (B C))
|
||||
[<ignored for tests>:31] x = 42
|
||||
[<ignored for tests>:33] "Fjoer en ferdjer frieten oan dyn geve lea" = "Fjoer en ferdjer frieten oan dyn geve lea"
|
||||
[<ignored for tests>:35] "this is line 24" = "this is line 24"
|
||||
[<ignored for tests>:21] x = "abc"
|
||||
[<ignored for tests>:21] x = 10
|
||||
[<ignored for tests>:21] x = (A (B C))
|
||||
Program finished!
|
||||
"#
|
||||
),
|
||||
@ -583,20 +586,46 @@ mod cli_run {
|
||||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
── EXPECT FAILED in ...roc/roc/crates/cli_testing_examples/expects/expects.roc ─
|
||||
|
||||
This expectation failed:
|
||||
|
||||
6│> expect
|
||||
7│> a = 1
|
||||
8│> b = 2
|
||||
9│>
|
||||
10│> a == b
|
||||
9│ expect a == 2
|
||||
^^^^^^
|
||||
|
||||
When it failed, these variables had these values:
|
||||
|
||||
a : Num *
|
||||
a = 1
|
||||
|
||||
b : Num *
|
||||
── EXPECT FAILED in ...roc/roc/crates/cli_testing_examples/expects/expects.roc ─
|
||||
|
||||
This expectation failed:
|
||||
|
||||
10│ expect a == 3
|
||||
^^^^^^
|
||||
|
||||
When it failed, these variables had these values:
|
||||
|
||||
a : Num *
|
||||
a = 1
|
||||
|
||||
── EXPECT FAILED in ...roc/roc/crates/cli_testing_examples/expects/expects.roc ─
|
||||
|
||||
This expectation failed:
|
||||
|
||||
14│> expect
|
||||
15│> a = makeA
|
||||
16│> b = 2i64
|
||||
17│>
|
||||
18│> a == b
|
||||
|
||||
When it failed, these variables had these values:
|
||||
|
||||
a : Int Signed64
|
||||
a = 1
|
||||
|
||||
b : I64
|
||||
b = 2
|
||||
|
||||
|
||||
@ -874,7 +903,7 @@ mod cli_run {
|
||||
This roc file can print it's own source code. The source is:
|
||||
|
||||
app "ingested-file"
|
||||
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" }
|
||||
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
|
||||
imports [
|
||||
pf.Stdout,
|
||||
"ingested-file.roc" as ownCode : Str,
|
||||
@ -882,7 +911,7 @@ mod cli_run {
|
||||
provides [main] to pf
|
||||
|
||||
main =
|
||||
Stdout.line "\nThis roc file can print it's own source code. The source is:\n\n\(ownCode)"
|
||||
Stdout.line "\nThis roc file can print it's own source code. The source is:\n\n$(ownCode)"
|
||||
|
||||
"#
|
||||
),
|
||||
@ -912,9 +941,9 @@ mod cli_run {
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn parse_movies_csv() {
|
||||
test_roc_app_slim(
|
||||
"examples/parser/examples",
|
||||
"examples/parser",
|
||||
"parse-movies-csv.roc",
|
||||
"Parse success!\n",
|
||||
"2 movies were found:\n\nThe movie 'Airplane!' was released in 1980 and stars Robert Hays and Julie Hagerty\nThe movie 'Caddyshack' was released in 1980 and stars Chevy Chase, Rodney Dangerfield, Ted Knight, Michael O'Keefe and Bill Murray\n\nParse success!\n\n",
|
||||
UseValgrind::No,
|
||||
)
|
||||
}
|
||||
@ -924,19 +953,13 @@ mod cli_run {
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn parse_letter_counts() {
|
||||
test_roc_app_slim(
|
||||
"examples/parser/examples",
|
||||
"examples/parser",
|
||||
"letter-counts.roc",
|
||||
"I counted 7 letter A's!\n",
|
||||
UseValgrind::No,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn parse_http() {
|
||||
test_roc_expect("examples/parser/package", "ParserHttp.roc")
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn inspect_logging() {
|
||||
@ -976,13 +999,13 @@ mod cli_run {
|
||||
|
||||
// TODO fix QuicksortApp and then remove this!
|
||||
match roc_filename {
|
||||
"QuicksortApp.roc" => {
|
||||
"quicksortApp.roc" => {
|
||||
eprintln!(
|
||||
"WARNING: skipping testing benchmark {roc_filename} because the test is broken right now!"
|
||||
);
|
||||
return;
|
||||
}
|
||||
"TestAStar.roc" => {
|
||||
"testAStar.roc" => {
|
||||
if cfg!(feature = "wasm32-cli-run") {
|
||||
eprintln!(
|
||||
"WARNING: skipping testing benchmark {roc_filename} because it currently does not work on wasm32 due to dictionaries."
|
||||
@ -1137,20 +1160,20 @@ mod cli_run {
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn nqueens() {
|
||||
test_benchmark("NQueens.roc", &["6"], "4\n", UseValgrind::Yes)
|
||||
test_benchmark("nQueens.roc", &["6"], "4\n", UseValgrind::Yes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn cfold() {
|
||||
test_benchmark("CFold.roc", &["3"], "11 & 11\n", UseValgrind::Yes)
|
||||
test_benchmark("cFold.roc", &["3"], "11 & 11\n", UseValgrind::Yes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn deriv() {
|
||||
test_benchmark(
|
||||
"Deriv.roc",
|
||||
"deriv.roc",
|
||||
&["2"],
|
||||
"1 count: 6\n2 count: 22\n",
|
||||
UseValgrind::Yes,
|
||||
@ -1160,14 +1183,14 @@ mod cli_run {
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn rbtree_ck() {
|
||||
test_benchmark("RBTreeCk.roc", &["100"], "10\n", UseValgrind::Yes)
|
||||
test_benchmark("rBTreeCk.roc", &["100"], "10\n", UseValgrind::Yes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn rbtree_insert() {
|
||||
test_benchmark(
|
||||
"RBTreeInsert.roc",
|
||||
"rBTreeInsert.roc",
|
||||
&[],
|
||||
"Node Black 0 {} Empty Empty\n",
|
||||
UseValgrind::Yes,
|
||||
@ -1179,25 +1202,25 @@ mod cli_run {
|
||||
#[test]
|
||||
fn rbtree_del() {
|
||||
test_benchmark(
|
||||
"RBTreeDel.roc",
|
||||
"rBTreeDel.roc",
|
||||
&["420"],
|
||||
&[],
|
||||
"30\n",
|
||||
true
|
||||
UseValgrind::Yes,
|
||||
)
|
||||
}*/
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn astar() {
|
||||
test_benchmark("TestAStar.roc", &[], "True\n", UseValgrind::No)
|
||||
test_benchmark("testAStar.roc", &[], "True\n", UseValgrind::No)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn base64() {
|
||||
test_benchmark(
|
||||
"TestBase64.roc",
|
||||
"testBase64.roc",
|
||||
&[],
|
||||
"encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
|
||||
UseValgrind::Yes,
|
||||
@ -1207,19 +1230,19 @@ mod cli_run {
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn closure() {
|
||||
test_benchmark("Closure.roc", &[], "", UseValgrind::No)
|
||||
test_benchmark("closure.roc", &[], "", UseValgrind::No)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn issue2279() {
|
||||
test_benchmark("Issue2279.roc", &[], "Hello, world!\n", UseValgrind::Yes)
|
||||
test_benchmark("issue2279.roc", &[], "Hello, world!\n", UseValgrind::Yes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quicksort_app() {
|
||||
test_benchmark(
|
||||
"QuicksortApp.roc",
|
||||
"quicksortApp.roc",
|
||||
&[],
|
||||
"todo put the correct quicksort answer here",
|
||||
UseValgrind::Yes,
|
||||
@ -1333,7 +1356,7 @@ mod cli_run {
|
||||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ─────────────────────────────── tests/known_bad/TypeError.roc ─
|
||||
── TYPE MISMATCH in tests/known_bad/TypeError.roc ──────────────────────────────
|
||||
|
||||
Something is off with the body of the main definition:
|
||||
|
||||
@ -1362,6 +1385,29 @@ mod cli_run {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_type_error_with_long_path() {
|
||||
check_compile_error(
|
||||
&known_bad_file("UnusedImportButWithALongFileNameForTesting.roc"),
|
||||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
── UNUSED IMPORT in ...nown_bad/UnusedImportButWithALongFileNameForTesting.roc ─
|
||||
|
||||
Nothing from Symbol is used in this module.
|
||||
|
||||
3│ imports [Symbol.{ Ident }]
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Since Symbol isn't used, you don't need to import it.
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
0 errors and 1 warning found in <ignored for test> ms."#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exposed_not_defined() {
|
||||
check_compile_error(
|
||||
@ -1369,7 +1415,7 @@ mod cli_run {
|
||||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
── MISSING DEFINITION ────────────────── tests/known_bad/ExposedNotDefined.roc ─
|
||||
── MISSING DEFINITION in tests/known_bad/ExposedNotDefined.roc ─────────────────
|
||||
|
||||
bar is listed as exposed, but it isn't defined in this module.
|
||||
|
||||
@ -1390,7 +1436,7 @@ mod cli_run {
|
||||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
── UNUSED IMPORT ──────────────────────────── tests/known_bad/UnusedImport.roc ─
|
||||
── UNUSED IMPORT in tests/known_bad/UnusedImport.roc ───────────────────────────
|
||||
|
||||
Nothing from Symbol is used in this module.
|
||||
|
||||
@ -1413,7 +1459,7 @@ mod cli_run {
|
||||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
── UNKNOWN GENERATES FUNCTION ─────── tests/known_bad/UnknownGeneratesWith.roc ─
|
||||
── UNKNOWN GENERATES FUNCTION in tests/known_bad/UnknownGeneratesWith.roc ──────
|
||||
|
||||
I don't know how to generate the foobar function.
|
||||
|
||||
|
11
crates/cli/tests/fixtures/.gitignore
vendored
11
crates/cli/tests/fixtures/.gitignore
vendored
@ -1,11 +0,0 @@
|
||||
app
|
||||
*.o
|
||||
*.dSYM
|
||||
dynhost
|
||||
libapp.so
|
||||
metadata
|
||||
preprocessedhost
|
||||
packages-test
|
||||
|
||||
multi-dep-str/multi-dep-str
|
||||
multi-dep-thunk/multi-dep-thunk
|
@ -1 +0,0 @@
|
||||
Main
|
@ -1 +0,0 @@
|
||||
Main
|
2
crates/cli/tests/fixtures/packages/app.roc
vendored
2
crates/cli/tests/fixtures/packages/app.roc
vendored
@ -3,4 +3,4 @@ app "packages-test"
|
||||
imports [json.JsonParser, csv.Csv]
|
||||
provides [main] to pf
|
||||
|
||||
main = "Hello, World! \(JsonParser.example) \(Csv.example)"
|
||||
main = "Hello, World! $(JsonParser.example) $(Csv.example)"
|
||||
|
@ -0,0 +1,7 @@
|
||||
interface UnusedImportButWithALongFileNameForTesting
|
||||
exposes [plainText, emText]
|
||||
imports [Symbol.{ Ident }]
|
||||
|
||||
plainText = \str -> PlainText str
|
||||
|
||||
emText = \str -> EmText str
|
12
crates/cli_testing_examples/.gitignore
vendored
12
crates/cli_testing_examples/.gitignore
vendored
@ -1,12 +0,0 @@
|
||||
*.dSYM
|
||||
libhost.a
|
||||
libapp.so
|
||||
dynhost
|
||||
preprocessedhost
|
||||
metadata
|
||||
|
||||
expects/expects
|
||||
benchmarks/rbtree-ck
|
||||
benchmarks/rbtree-insert
|
||||
benchmarks/test-astar
|
||||
benchmarks/test-base64
|
@ -1,2 +0,0 @@
|
||||
fibonacci
|
||||
quicksort
|
@ -1,13 +0,0 @@
|
||||
CFold
|
||||
Closure
|
||||
Deriv
|
||||
Issue2279
|
||||
NQueens
|
||||
Quicksort
|
||||
QuicksortApp
|
||||
RBTreeCk
|
||||
RBTreeDel
|
||||
RBTreeInsert
|
||||
TestAStar
|
||||
TestBase64
|
||||
*.wasm
|
@ -10,7 +10,7 @@ show = \list ->
|
||||
|> List.map Num.toStr
|
||||
|> Str.joinWith ", "
|
||||
|
||||
"[\(content)]"
|
||||
"[$(content)]"
|
||||
|
||||
sortBy : List a, (a -> Num *) -> List a
|
||||
sortBy = \list, toComparable ->
|
||||
|
@ -15,7 +15,7 @@ closure1 = \_ ->
|
||||
Task.succeed (foo toUnitBorrowed "a long string such that it's malloced")
|
||||
|> Task.map \_ -> {}
|
||||
|
||||
toUnitBorrowed = \x -> Str.countGraphemes x
|
||||
toUnitBorrowed = \x -> Str.countUtf8Bytes x
|
||||
|
||||
foo = \f, x -> f x
|
||||
|
@ -248,4 +248,4 @@ del = \t, k ->
|
||||
rebalanceLeft cx lx ky vy ry
|
||||
|
||||
Delmin (Del ry Bool.false) ky vy ->
|
||||
Del (Node cx lx ky vy ry) Bool.false
|
||||
Del (Node cx lx ky vy ry) Bool.false
|
@ -26,7 +26,7 @@ showRBTree = \tree, showKey, showValue ->
|
||||
sL = nodeInParens left showKey showValue
|
||||
sR = nodeInParens right showKey showValue
|
||||
|
||||
"Node \(sColor) \(sKey) \(sValue) \(sL) \(sR)"
|
||||
"Node $(sColor) $(sKey) $(sValue) $(sL) $(sR)"
|
||||
|
||||
nodeInParens : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
|
||||
nodeInParens = \tree, showKey, showValue ->
|
||||
@ -37,7 +37,7 @@ nodeInParens = \tree, showKey, showValue ->
|
||||
Node _ _ _ _ _ ->
|
||||
inner = showRBTree tree showKey showValue
|
||||
|
||||
"(\(inner))"
|
||||
"($(inner))"
|
||||
|
||||
showColor : NodeColor -> Str
|
||||
showColor = \color ->
|
@ -14,7 +14,7 @@ main =
|
||||
#
|
||||
# _ ->
|
||||
# ns = Num.toStr n
|
||||
# Task.putLine "No test \(ns)"
|
||||
# Task.putLine "No test $(ns)"
|
||||
showBool : Bool -> Str
|
||||
showBool = \b ->
|
||||
if
|
@ -3,14 +3,23 @@ app "expects-test"
|
||||
imports []
|
||||
provides [main] to pf
|
||||
|
||||
expect
|
||||
makeA =
|
||||
a = 1
|
||||
b = 2
|
||||
|
||||
expect a == 2
|
||||
expect a == 3
|
||||
|
||||
a
|
||||
|
||||
expect
|
||||
a = makeA
|
||||
b = 2i64
|
||||
|
||||
a == b
|
||||
|
||||
polyDbg = \x ->
|
||||
dbg x
|
||||
|
||||
x
|
||||
|
||||
main =
|
||||
@ -20,10 +29,12 @@ main =
|
||||
|
||||
x = 42
|
||||
dbg x
|
||||
|
||||
dbg "Fjoer en ferdjer frieten oan dyn geve lea"
|
||||
|
||||
dbg "this is line 24"
|
||||
|
||||
r = {x : polyDbg "abc", y: polyDbg 10u8, z : polyDbg (A (B C))}
|
||||
r = { x: polyDbg "abc", y: polyDbg 10u8, z: polyDbg (A (B C)) }
|
||||
|
||||
when r is
|
||||
_ -> "Program finished!\n"
|
||||
|
File diff suppressed because one or more lines are too long
@ -255,7 +255,12 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|_| panic!("failed to execute cmd `{cmd_name}` in CLI test"));
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Encountered error:\n\t{:?}\nWhile executing cmd:\n\t{:?}",
|
||||
err, cmd_str
|
||||
)
|
||||
});
|
||||
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
@ -269,7 +274,7 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.unwrap_or_else(|_| panic!("failed to execute cmd `{cmd_name}` in CLI test"));
|
||||
.unwrap_or_else(|_| panic!("Failed to execute cmd:\n\t`{:?}`", cmd_str));
|
||||
|
||||
Out {
|
||||
cmd_str,
|
||||
|
@ -1173,6 +1173,16 @@ fn lowlevel_spec<'a>(
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
ListClone => {
|
||||
let list = env.symbols[&arguments[0]];
|
||||
|
||||
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 _unit = builder.add_update(block, update_mode_var, cell)?;
|
||||
|
||||
with_new_heap_cell(builder, block, bag)
|
||||
}
|
||||
ListSwap => {
|
||||
let list = env.symbols[&arguments[0]];
|
||||
|
||||
|
@ -12,6 +12,7 @@ roc_bitcode = { path = "../builtins/bitcode" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_gen_dev = { path = "../gen_dev", default-features = false }
|
||||
roc_gen_llvm = { path = "../gen_llvm" }
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::target::{arch_str, target_zig_str};
|
||||
use libloading::{Error, Library};
|
||||
use roc_command_utils::{cargo, clang, rustup, zig};
|
||||
use roc_debug_flags;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use std::collections::HashMap;
|
||||
@ -537,7 +538,7 @@ pub fn rebuild_host(
|
||||
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
|
||||
// using `+nightly` only works when running cargo through rustup
|
||||
let mut cmd = rustup();
|
||||
cmd.args(["run", "nightly-2023-05-28", "cargo"]);
|
||||
cmd.args(["run", "nightly-2023-07-09", "cargo"]);
|
||||
|
||||
cmd
|
||||
} else {
|
||||
@ -613,7 +614,8 @@ pub fn rebuild_host(
|
||||
|
||||
// Clean up c_host.o
|
||||
if c_host_dest.exists() {
|
||||
std::fs::remove_file(c_host_dest).unwrap();
|
||||
// there can be a race condition on this file cleanup
|
||||
let _ = std::fs::remove_file(c_host_dest);
|
||||
}
|
||||
}
|
||||
} else if rust_host_src.exists() {
|
||||
@ -848,6 +850,17 @@ fn strs_to_path(strs: &[&str]) -> PathBuf {
|
||||
strs.iter().collect()
|
||||
}
|
||||
|
||||
fn extra_link_flags() -> Vec<String> {
|
||||
match env::var("ROC_LINK_FLAGS") {
|
||||
Ok(flags) => {
|
||||
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
|
||||
|
||||
flags
|
||||
}
|
||||
Err(_) => "".to_string(),
|
||||
}.split_whitespace().map(|x| x.to_owned()).collect()
|
||||
}
|
||||
|
||||
fn link_linux(
|
||||
target: &Triple,
|
||||
output_path: PathBuf,
|
||||
@ -1037,6 +1050,7 @@ fn link_linux(
|
||||
.args(&base_args)
|
||||
.args(["-dynamic-linker", ld_linux])
|
||||
.args(input_paths)
|
||||
.args(extra_link_flags())
|
||||
// ld.lld requires this argument, and does not accept --arch
|
||||
// .args(&["-L/usr/lib/x86_64-linux-gnu"])
|
||||
.args([
|
||||
@ -1054,6 +1068,7 @@ fn link_linux(
|
||||
"-o",
|
||||
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
|
||||
]);
|
||||
debug_print_command(&command);
|
||||
|
||||
let output = command.spawn()?;
|
||||
|
||||
@ -1108,7 +1123,8 @@ fn link_macos(
|
||||
"-macos_version_min",
|
||||
&get_macos_version(),
|
||||
])
|
||||
.args(input_paths);
|
||||
.args(input_paths)
|
||||
.args(extra_link_flags());
|
||||
|
||||
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
|
||||
if Path::new(sdk_path).exists() {
|
||||
@ -1116,18 +1132,6 @@ fn link_macos(
|
||||
ld_command.arg(format!("-L{sdk_path}/swift"));
|
||||
};
|
||||
|
||||
let roc_link_flags = match env::var("ROC_LINK_FLAGS") {
|
||||
Ok(flags) => {
|
||||
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
|
||||
|
||||
flags
|
||||
}
|
||||
Err(_) => "".to_string(),
|
||||
};
|
||||
for roc_link_flag in roc_link_flags.split_whitespace() {
|
||||
ld_command.arg(roc_link_flag);
|
||||
}
|
||||
|
||||
ld_command.args([
|
||||
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496392274
|
||||
// for discussion and further references
|
||||
@ -1162,14 +1166,18 @@ fn link_macos(
|
||||
output_path.to_str().unwrap(), // app
|
||||
]);
|
||||
|
||||
debug_print_command(&ld_command);
|
||||
|
||||
let mut ld_child = ld_command.spawn()?;
|
||||
|
||||
match target.architecture {
|
||||
Architecture::Aarch64(_) => {
|
||||
ld_child.wait()?;
|
||||
let codesign_child = Command::new("codesign")
|
||||
.args(["-s", "-", output_path.to_str().unwrap()])
|
||||
.spawn()?;
|
||||
|
||||
let mut codesign_cmd = Command::new("codesign");
|
||||
codesign_cmd.args(["-s", "-", output_path.to_str().unwrap()]);
|
||||
debug_print_command(&codesign_cmd);
|
||||
let codesign_child = codesign_cmd.spawn()?;
|
||||
|
||||
Ok((codesign_child, output_path))
|
||||
}
|
||||
@ -1178,8 +1186,11 @@ fn link_macos(
|
||||
}
|
||||
|
||||
fn get_macos_version() -> String {
|
||||
let cmd_stdout = Command::new("sw_vers")
|
||||
.arg("-productVersion")
|
||||
let mut cmd = Command::new("sw_vers");
|
||||
cmd.arg("-productVersion");
|
||||
debug_print_command(&cmd);
|
||||
|
||||
let cmd_stdout = cmd
|
||||
.output()
|
||||
.expect("Failed to execute command 'sw_vers -productVersion'")
|
||||
.stdout;
|
||||
@ -1382,15 +1393,11 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
|
||||
}
|
||||
|
||||
fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_counter: usize) {
|
||||
let mut command_string = std::ffi::OsString::new();
|
||||
command_string.push(command.get_program());
|
||||
|
||||
for arg in command.get_args() {
|
||||
command_string.push(" ");
|
||||
command_string.push(arg);
|
||||
}
|
||||
|
||||
let cmd_str = command_string.to_str().unwrap();
|
||||
let command_string = stringify_command(&command);
|
||||
let cmd_str = &command_string;
|
||||
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRINT_BUILD_COMMANDS, {
|
||||
print_command_str(cmd_str);
|
||||
});
|
||||
let cmd_output = command.output().unwrap();
|
||||
let max_flaky_fail_count = 10;
|
||||
|
||||
@ -1428,3 +1435,41 @@ fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stringify a command for printing
|
||||
/// e.g. `HOME=~ zig build-exe foo.zig -o foo`
|
||||
fn stringify_command(cmd: &Command) -> String {
|
||||
let mut command_string = std::ffi::OsString::new();
|
||||
|
||||
for (name, opt_val) in cmd.get_envs() {
|
||||
command_string.push(name);
|
||||
command_string.push("=");
|
||||
if let Some(val) = opt_val {
|
||||
command_string.push(val);
|
||||
} else {
|
||||
command_string.push("''");
|
||||
}
|
||||
command_string.push(" ");
|
||||
}
|
||||
|
||||
command_string.push(cmd.get_program());
|
||||
|
||||
for arg in cmd.get_args() {
|
||||
command_string.push(" ");
|
||||
command_string.push(arg);
|
||||
}
|
||||
|
||||
String::from(command_string.to_str().unwrap())
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn print_command_str(s: &str) {
|
||||
println!("\nRoc build command:\n{}\n", s);
|
||||
}
|
||||
|
||||
fn debug_print_command(_cmd: &Command) {
|
||||
// This debug macro is compiled out in release mode, so the argument is unused
|
||||
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRINT_BUILD_COMMANDS, {
|
||||
print_command_str(&stringify_command(_cmd));
|
||||
});
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ pub struct CodeGenOptions {
|
||||
pub opt_level: OptLevel,
|
||||
pub emit_debug_info: bool,
|
||||
pub emit_llvm_ir: bool,
|
||||
pub fuzz: bool,
|
||||
}
|
||||
|
||||
type GenFromMono<'a> = (CodeObject, CodeGenTiming, ExpectMetadata<'a>);
|
||||
@ -103,6 +104,7 @@ pub fn gen_from_mono_module<'a>(
|
||||
let path = roc_file_path;
|
||||
let debug = code_gen_options.emit_debug_info;
|
||||
let emit_llvm_ir = code_gen_options.emit_llvm_ir;
|
||||
let fuzz = code_gen_options.fuzz;
|
||||
let opt = code_gen_options.opt_level;
|
||||
|
||||
match code_gen_options.backend {
|
||||
@ -131,6 +133,7 @@ pub fn gen_from_mono_module<'a>(
|
||||
backend_mode,
|
||||
debug,
|
||||
emit_llvm_ir,
|
||||
fuzz,
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -148,6 +151,7 @@ fn gen_from_mono_module_llvm<'a>(
|
||||
backend_mode: LlvmBackendMode,
|
||||
emit_debug_info: bool,
|
||||
emit_llvm_ir: bool,
|
||||
fuzz: bool,
|
||||
) -> GenFromMono<'a> {
|
||||
use crate::target::{self, convert_opt_level};
|
||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||
@ -284,7 +288,8 @@ fn gen_from_mono_module_llvm<'a>(
|
||||
|
||||
// annotate the LLVM IR output with debug info
|
||||
// so errors are reported with the line number of the LLVM source
|
||||
let memory_buffer = if cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok() {
|
||||
let gen_sanitizers = cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok();
|
||||
let memory_buffer = if fuzz || gen_sanitizers {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let dir = dir.into_path();
|
||||
|
||||
@ -301,33 +306,27 @@ fn gen_from_mono_module_llvm<'a>(
|
||||
let mut passes = vec![];
|
||||
let mut extra_args = vec![];
|
||||
let mut unrecognized = vec![];
|
||||
for sanitizer in std::env::var("ROC_SANITIZERS")
|
||||
.unwrap()
|
||||
.split(',')
|
||||
.map(|x| x.trim())
|
||||
{
|
||||
match sanitizer {
|
||||
"address" => passes.push("asan-module"),
|
||||
"memory" => passes.push("msan-module"),
|
||||
"thread" => passes.push("tsan-module"),
|
||||
"cargo-fuzz" => {
|
||||
passes.push("sancov-module");
|
||||
extra_args.extend_from_slice(&[
|
||||
"-sanitizer-coverage-level=3",
|
||||
"-sanitizer-coverage-prune-blocks=0",
|
||||
"-sanitizer-coverage-inline-8bit-counters",
|
||||
"-sanitizer-coverage-pc-table",
|
||||
]);
|
||||
if fuzz {
|
||||
passes.push("sancov-module");
|
||||
extra_args.extend_from_slice(&[
|
||||
"-sanitizer-coverage-level=4",
|
||||
"-sanitizer-coverage-inline-8bit-counters",
|
||||
"-sanitizer-coverage-pc-table",
|
||||
"-sanitizer-coverage-trace-compares",
|
||||
]);
|
||||
}
|
||||
if gen_sanitizers {
|
||||
for sanitizer in std::env::var("ROC_SANITIZERS")
|
||||
.unwrap()
|
||||
.split(',')
|
||||
.map(|x| x.trim())
|
||||
{
|
||||
match sanitizer {
|
||||
"address" => passes.push("asan-module"),
|
||||
"memory" => passes.push("msan-module"),
|
||||
"thread" => passes.push("tsan-module"),
|
||||
x => unrecognized.push(x.to_owned()),
|
||||
}
|
||||
"afl.rs" => {
|
||||
passes.push("sancov-module");
|
||||
extra_args.extend_from_slice(&[
|
||||
"-sanitizer-coverage-level=3",
|
||||
"-sanitizer-coverage-prune-blocks=0",
|
||||
"-sanitizer-coverage-trace-pc-guard",
|
||||
]);
|
||||
}
|
||||
x => unrecognized.push(x.to_owned()),
|
||||
}
|
||||
}
|
||||
if !unrecognized.is_empty() {
|
||||
@ -802,7 +801,7 @@ fn build_loaded_file<'a>(
|
||||
platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target).unwrap())
|
||||
};
|
||||
|
||||
let mut output_exe_path = match out_path {
|
||||
let output_exe_path = match out_path {
|
||||
Some(path) => {
|
||||
// true iff the path ends with a directory separator,
|
||||
// e.g. '/' on UNIX, '/' or '\\' on Windows
|
||||
@ -830,12 +829,22 @@ fn build_loaded_file<'a>(
|
||||
if ends_with_sep {
|
||||
let filename = app_module_path.file_name().unwrap_or_default();
|
||||
|
||||
with_executable_extension(&path.join(filename), operating_system)
|
||||
with_output_extension(
|
||||
&path.join(filename),
|
||||
operating_system,
|
||||
linking_strategy,
|
||||
link_type,
|
||||
)
|
||||
} else {
|
||||
path.to_path_buf()
|
||||
}
|
||||
}
|
||||
None => with_executable_extension(&app_module_path, operating_system),
|
||||
None => with_output_extension(
|
||||
&app_module_path,
|
||||
operating_system,
|
||||
linking_strategy,
|
||||
link_type,
|
||||
),
|
||||
};
|
||||
|
||||
// We don't need to spawn a rebuild thread when using a prebuilt host.
|
||||
@ -994,7 +1003,6 @@ fn build_loaded_file<'a>(
|
||||
}
|
||||
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
|
||||
// Just copy the object file to the output folder.
|
||||
output_exe_path.set_extension(operating_system.object_file_ext());
|
||||
std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap();
|
||||
}
|
||||
(LinkingStrategy::Legacy, _) => {
|
||||
@ -1282,6 +1290,7 @@ pub fn build_str_test<'a>(
|
||||
opt_level: OptLevel::Normal,
|
||||
emit_debug_info: false,
|
||||
emit_llvm_ir: false,
|
||||
fuzz: false,
|
||||
};
|
||||
|
||||
let emit_timings = false;
|
||||
@ -1324,6 +1333,17 @@ pub fn build_str_test<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
fn with_executable_extension(path: &Path, os: OperatingSystem) -> PathBuf {
|
||||
path.with_extension(os.executable_file_ext().unwrap_or_default())
|
||||
fn with_output_extension(
|
||||
path: &Path,
|
||||
os: OperatingSystem,
|
||||
linking_strategy: LinkingStrategy,
|
||||
link_type: LinkType,
|
||||
) -> PathBuf {
|
||||
match (linking_strategy, link_type) {
|
||||
(LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
|
||||
// Additive linking and no linking both output the object file type.
|
||||
path.with_extension(os.object_file_ext())
|
||||
}
|
||||
_ => path.with_extension(os.executable_file_ext().unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
|
1
crates/compiler/builtins/.gitignore
vendored
1
crates/compiler/builtins/.gitignore
vendored
@ -1 +0,0 @@
|
||||
builtins.ll
|
5
crates/compiler/builtins/bitcode/.gitignore
vendored
5
crates/compiler/builtins/bitcode/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
zig-out
|
||||
zig-cache
|
||||
src/zig-cache
|
||||
benchmark/zig-cache
|
||||
dec
|
@ -24,6 +24,9 @@ pub const RocDec = extern struct {
|
||||
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 const two_point_zero: RocDec = RocDec.add(RocDec.one_point_zero, RocDec.one_point_zero);
|
||||
pub const zero_point_five: RocDec = RocDec.div(RocDec.one_point_zero, RocDec.two_point_zero);
|
||||
|
||||
pub fn fromU64(num: u64) RocDec {
|
||||
return .{ .num = num * one_point_zero_i128 };
|
||||
}
|
||||
@ -340,6 +343,77 @@ pub const RocDec = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn trunc(self: RocDec) RocDec {
|
||||
return RocDec.sub(self, self.fract());
|
||||
}
|
||||
|
||||
fn fract(self: RocDec) RocDec {
|
||||
const sign = std.math.sign(self.num);
|
||||
const digits = @mod(sign * self.num, RocDec.one_point_zero.num);
|
||||
|
||||
return RocDec{ .num = sign * digits };
|
||||
}
|
||||
|
||||
// Returns the nearest integer to self. If a value is half-way between two integers, round away from 0.0.
|
||||
fn round(arg1: RocDec) RocDec {
|
||||
// this rounds towards zero
|
||||
const tmp = arg1.trunc();
|
||||
|
||||
const sign = std.math.sign(arg1.num);
|
||||
const abs_fract = sign * arg1.fract().num;
|
||||
|
||||
if (abs_fract >= RocDec.zero_point_five.num) {
|
||||
return RocDec.add(tmp, RocDec{ .num = sign * RocDec.one_point_zero.num });
|
||||
} else {
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the largest integer less than or equal to itself
|
||||
fn floor(arg1: RocDec) RocDec {
|
||||
const tmp = arg1.trunc();
|
||||
|
||||
if (arg1.num < 0 and arg1.fract().num != 0) {
|
||||
return RocDec.sub(tmp, RocDec.one_point_zero);
|
||||
} else {
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the smallest integer greater than or equal to itself
|
||||
fn ceiling(arg1: RocDec) RocDec {
|
||||
const tmp = arg1.trunc();
|
||||
|
||||
if (arg1.num > 0 and arg1.fract().num != 0) {
|
||||
return RocDec.add(tmp, RocDec.one_point_zero);
|
||||
} else {
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
|
||||
fn powInt(base: RocDec, exponent: i128) RocDec {
|
||||
if (exponent == 0) {
|
||||
return RocDec.one_point_zero;
|
||||
} else if (exponent > 0) {
|
||||
if (@mod(exponent, 2) == 0) {
|
||||
const half_power = RocDec.powInt(base, exponent >> 1); // `>> 1` == `/ 2`
|
||||
return RocDec.mul(half_power, half_power);
|
||||
} else {
|
||||
return RocDec.mul(base, RocDec.powInt(base, exponent - 1));
|
||||
}
|
||||
} else {
|
||||
return RocDec.div(RocDec.one_point_zero, RocDec.powInt(base, -exponent));
|
||||
}
|
||||
}
|
||||
|
||||
fn pow(base: RocDec, exponent: RocDec) RocDec {
|
||||
if (exponent.trunc().num == exponent.num) {
|
||||
return base.powInt(@divTrunc(exponent.num, RocDec.one_point_zero_i128));
|
||||
} else {
|
||||
return fromF64(std.math.pow(f64, base.toF64(), exponent.toF64())).?;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul(self: RocDec, other: RocDec) RocDec {
|
||||
const answer = RocDec.mulWithOverflow(self, other);
|
||||
|
||||
@ -1195,6 +1269,153 @@ test "log: 1" {
|
||||
try expectEqual(RocDec.fromU64(0), RocDec.log(RocDec.fromU64(1)));
|
||||
}
|
||||
|
||||
test "fract: 0" {
|
||||
var roc_str = RocStr.init("0", 1);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = 0 }, dec.fract());
|
||||
}
|
||||
|
||||
test "fract: 1" {
|
||||
var roc_str = RocStr.init("1", 1);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = 0 }, dec.fract());
|
||||
}
|
||||
|
||||
test "fract: 123.45" {
|
||||
var roc_str = RocStr.init("123.45", 6);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.fract());
|
||||
}
|
||||
|
||||
test "fract: -123.45" {
|
||||
var roc_str = RocStr.init("-123.45", 7);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.fract());
|
||||
}
|
||||
|
||||
test "fract: .45" {
|
||||
var roc_str = RocStr.init(".45", 3);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.fract());
|
||||
}
|
||||
|
||||
test "fract: -0.00045" {
|
||||
const dec: RocDec = .{ .num = -450000000000000 };
|
||||
const res = dec.fract();
|
||||
|
||||
try expectEqual(dec.num, res.num);
|
||||
}
|
||||
|
||||
test "trunc: 0" {
|
||||
var roc_str = RocStr.init("0", 1);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = 0 }, dec.trunc());
|
||||
}
|
||||
|
||||
test "trunc: 1" {
|
||||
var roc_str = RocStr.init("1", 1);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec.one_point_zero, dec.trunc());
|
||||
}
|
||||
|
||||
test "trunc: 123.45" {
|
||||
var roc_str = RocStr.init("123.45", 6);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.trunc());
|
||||
}
|
||||
|
||||
test "trunc: -123.45" {
|
||||
var roc_str = RocStr.init("-123.45", 7);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.trunc());
|
||||
}
|
||||
|
||||
test "trunc: .45" {
|
||||
var roc_str = RocStr.init(".45", 3);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = 0 }, dec.trunc());
|
||||
}
|
||||
|
||||
test "trunc: -0.00045" {
|
||||
const dec: RocDec = .{ .num = -450000000000000 };
|
||||
const res = dec.trunc();
|
||||
|
||||
try expectEqual(RocDec{ .num = 0 }, res);
|
||||
}
|
||||
|
||||
test "round: 123.45" {
|
||||
var roc_str = RocStr.init("123.45", 6);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.round());
|
||||
}
|
||||
|
||||
test "round: -123.45" {
|
||||
var roc_str = RocStr.init("-123.45", 7);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.round());
|
||||
}
|
||||
|
||||
test "round: 0.5" {
|
||||
var roc_str = RocStr.init("0.5", 3);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec.one_point_zero, dec.round());
|
||||
}
|
||||
|
||||
test "round: -0.5" {
|
||||
var roc_str = RocStr.init("-0.5", 4);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec{ .num = -1000000000000000000 }, dec.round());
|
||||
}
|
||||
|
||||
test "powInt: 3.1 ^ 0" {
|
||||
var roc_str = RocStr.init("3.1", 3);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(RocDec.one_point_zero, dec.powInt(0));
|
||||
}
|
||||
|
||||
test "powInt: 3.1 ^ 1" {
|
||||
var roc_str = RocStr.init("3.1", 3);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(dec, dec.powInt(1));
|
||||
}
|
||||
|
||||
test "powInt: 2 ^ 2" {
|
||||
var roc_str = RocStr.init("4", 1);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(dec, RocDec.two_point_zero.powInt(2));
|
||||
}
|
||||
|
||||
test "powInt: 0.5 ^ 2" {
|
||||
var roc_str = RocStr.init("0.25", 4);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(dec, RocDec.zero_point_five.powInt(2));
|
||||
}
|
||||
|
||||
test "pow: 0.5 ^ 2.0" {
|
||||
var roc_str = RocStr.init("0.25", 4);
|
||||
var dec = RocDec.fromStr(roc_str).?;
|
||||
|
||||
try expectEqual(dec, RocDec.zero_point_five.pow(RocDec.two_point_zero));
|
||||
}
|
||||
|
||||
// exports
|
||||
|
||||
pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) {
|
||||
@ -1295,6 +1516,10 @@ pub fn logC(arg: RocDec) callconv(.C) i128 {
|
||||
return @call(.always_inline, RocDec.log, .{arg}).num;
|
||||
}
|
||||
|
||||
pub fn powC(arg1: RocDec, arg2: RocDec) callconv(.C) i128 {
|
||||
return @call(.always_inline, RocDec.pow, .{ arg1, arg2 }).num;
|
||||
}
|
||||
|
||||
pub fn sinC(arg: RocDec) callconv(.C) i128 {
|
||||
return @call(.always_inline, RocDec.sin, .{arg}).num;
|
||||
}
|
||||
@ -1342,3 +1567,30 @@ pub fn mulOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
||||
pub fn mulSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
||||
return @call(.always_inline, RocDec.mulSaturated, .{ arg1, arg2 });
|
||||
}
|
||||
|
||||
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: RocDec) callconv(.C) T {
|
||||
return @as(T, @intCast(@divFloor(input.round().num, RocDec.one_point_zero_i128)));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportFloor(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: RocDec) callconv(.C) T {
|
||||
return @as(T, @intCast(@divFloor(input.floor().num, RocDec.one_point_zero_i128)));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportCeiling(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: RocDec) callconv(.C) T {
|
||||
return @as(T, @intCast(@divFloor(input.ceiling().num, RocDec.one_point_zero_i128)));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -962,6 +962,14 @@ pub fn listIsUnique(
|
||||
return list.isEmpty() or list.isUnique();
|
||||
}
|
||||
|
||||
pub fn listClone(
|
||||
list: RocList,
|
||||
alignment: u32,
|
||||
element_width: usize,
|
||||
) callconv(.C) RocList {
|
||||
return list.makeUnique(alignment, element_width);
|
||||
}
|
||||
|
||||
pub fn listCapacity(
|
||||
list: RocList,
|
||||
) callconv(.C) usize {
|
||||
|
@ -36,6 +36,7 @@ comptime {
|
||||
exportDecFn(dec.fromStr, "from_str");
|
||||
exportDecFn(dec.fromU64C, "from_u64");
|
||||
exportDecFn(dec.logC, "log");
|
||||
exportDecFn(dec.powC, "pow");
|
||||
exportDecFn(dec.mulC, "mul_with_overflow");
|
||||
exportDecFn(dec.mulOrPanicC, "mul_or_panic");
|
||||
exportDecFn(dec.mulSaturatedC, "mul_saturated");
|
||||
@ -52,6 +53,10 @@ comptime {
|
||||
|
||||
inline for (INTEGERS) |T| {
|
||||
dec.exportFromInt(T, ROC_BUILTINS ++ ".dec.from_int.");
|
||||
|
||||
dec.exportRound(T, ROC_BUILTINS ++ ".dec.round.");
|
||||
dec.exportFloor(T, ROC_BUILTINS ++ ".dec.floor.");
|
||||
dec.exportCeiling(T, ROC_BUILTINS ++ ".dec.ceiling.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +80,7 @@ comptime {
|
||||
exportListFn(list.listReplaceInPlace, "replace_in_place");
|
||||
exportListFn(list.listSwap, "swap");
|
||||
exportListFn(list.listIsUnique, "is_unique");
|
||||
exportListFn(list.listClone, "clone");
|
||||
exportListFn(list.listCapacity, "capacity");
|
||||
exportListFn(list.listAllocationPtr, "allocation_ptr");
|
||||
exportListFn(list.listReleaseExcessCapacity, "release_excess_capacity");
|
||||
@ -110,19 +116,6 @@ comptime {
|
||||
exportNumFn(num.greaterThanU128, "greater_than.u128");
|
||||
exportNumFn(num.greaterThanOrEqualU128, "greater_than_or_equal.u128");
|
||||
|
||||
exportNumFn(num.compareI128, "compare.i128");
|
||||
exportNumFn(num.compareU128, "compare.u128");
|
||||
|
||||
exportNumFn(num.lessThanI128, "less_than.i128");
|
||||
exportNumFn(num.lessThanOrEqualI128, "less_than_or_equal.i128");
|
||||
exportNumFn(num.greaterThanI128, "greater_than.i128");
|
||||
exportNumFn(num.greaterThanOrEqualI128, "greater_than_or_equal.i128");
|
||||
|
||||
exportNumFn(num.lessThanU128, "less_than.u128");
|
||||
exportNumFn(num.lessThanOrEqualU128, "less_than_or_equal.u128");
|
||||
exportNumFn(num.greaterThanU128, "greater_than.u128");
|
||||
exportNumFn(num.greaterThanOrEqualU128, "greater_than_or_equal.u128");
|
||||
|
||||
inline for (INTEGERS, 0..) |T, i| {
|
||||
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
|
||||
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
|
||||
@ -134,6 +127,9 @@ comptime {
|
||||
num.exportCeiling(f32, T, ROC_BUILTINS ++ "." ++ NUM ++ ".ceiling_f32.");
|
||||
num.exportCeiling(f64, T, ROC_BUILTINS ++ "." ++ NUM ++ ".ceiling_f64.");
|
||||
|
||||
num.exportNumToFloatCast(T, f32, ROC_BUILTINS ++ "." ++ NUM ++ ".num_to_float_cast_f32.");
|
||||
num.exportNumToFloatCast(T, f64, ROC_BUILTINS ++ "." ++ NUM ++ ".num_to_float_cast_f64.");
|
||||
|
||||
num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow.");
|
||||
num.exportAddOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_or_panic.");
|
||||
num.exportAddSaturatedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_saturated.");
|
||||
@ -190,15 +186,12 @@ comptime {
|
||||
const str = @import("str.zig");
|
||||
comptime {
|
||||
exportStrFn(str.init, "init");
|
||||
exportStrFn(str.strToScalarsC, "to_scalars");
|
||||
exportStrFn(str.strSplit, "str_split");
|
||||
exportStrFn(str.countSegments, "count_segments");
|
||||
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
|
||||
exportStrFn(str.countUtf8Bytes, "count_utf8_bytes");
|
||||
exportStrFn(str.isEmpty, "is_empty");
|
||||
exportStrFn(str.getCapacity, "capacity");
|
||||
exportStrFn(str.startsWith, "starts_with");
|
||||
exportStrFn(str.startsWithScalar, "starts_with_scalar");
|
||||
exportStrFn(str.endsWith, "ends_with");
|
||||
exportStrFn(str.strConcatC, "concat");
|
||||
exportStrFn(str.strJoinWithC, "joinWith");
|
||||
@ -207,8 +200,6 @@ comptime {
|
||||
exportStrFn(str.substringUnsafe, "substring_unsafe");
|
||||
exportStrFn(str.getUnsafe, "get_unsafe");
|
||||
exportStrFn(str.reserve, "reserve");
|
||||
exportStrFn(str.getScalarUnsafe, "get_scalar_unsafe");
|
||||
exportStrFn(str.appendScalar, "append_scalar");
|
||||
exportStrFn(str.strToUtf8C, "to_utf8");
|
||||
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
|
||||
exportStrFn(str.repeat, "repeat");
|
||||
@ -217,7 +208,6 @@ comptime {
|
||||
exportStrFn(str.strTrimEnd, "trim_end");
|
||||
exportStrFn(str.strCloneTo, "clone_to");
|
||||
exportStrFn(str.withCapacity, "with_capacity");
|
||||
exportStrFn(str.strGraphemes, "graphemes");
|
||||
exportStrFn(str.strAllocationPtr, "allocation_ptr");
|
||||
exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity");
|
||||
|
||||
@ -264,6 +254,9 @@ comptime {
|
||||
if (builtin.target.cpu.arch == .aarch64) {
|
||||
@export(__roc_force_setjmp, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
|
||||
@export(__roc_force_longjmp, .{ .name = "__roc_force_longjmp", .linkage = .Weak });
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
@export(__roc_force_setjmp_windows, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
|
||||
@export(__roc_force_longjmp_windows, .{ .name = "__roc_force_longjmp", .linkage = .Weak });
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,14 +272,103 @@ pub extern fn _longjmp([*c]c_int, c_int) noreturn;
|
||||
pub extern fn sigsetjmp([*c]c_int, c_int) c_int;
|
||||
pub extern fn siglongjmp([*c]c_int, c_int) noreturn;
|
||||
pub extern fn longjmperror() void;
|
||||
|
||||
// Zig won't expose the externs (and hence link correctly) unless we force them to be used.
|
||||
fn __roc_force_setjmp(it: [*c]c_int) callconv(.C) c_int {
|
||||
return setjmp(it);
|
||||
}
|
||||
|
||||
fn __roc_force_longjmp(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn {
|
||||
longjmp(a0, a1);
|
||||
}
|
||||
|
||||
pub extern fn windows_setjmp([*c]c_int) c_int;
|
||||
pub extern fn windows_longjmp([*c]c_int, c_int) noreturn;
|
||||
|
||||
fn __roc_force_setjmp_windows(it: [*c]c_int) callconv(.C) c_int {
|
||||
return windows_setjmp(it);
|
||||
}
|
||||
|
||||
fn __roc_force_longjmp_windows(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn {
|
||||
windows_longjmp(a0, a1);
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (builtin.os.tag == .windows) {
|
||||
asm (
|
||||
\\.global windows_longjmp;
|
||||
\\windows_longjmp:
|
||||
\\ movq 0x00(%rcx), %rdx
|
||||
\\ movq 0x08(%rcx), %rbx
|
||||
\\ # note 0x10 is not used yet!
|
||||
\\ movq 0x18(%rcx), %rbp
|
||||
\\ movq 0x20(%rcx), %rsi
|
||||
\\ movq 0x28(%rcx), %rdi
|
||||
\\ movq 0x30(%rcx), %r12
|
||||
\\ movq 0x38(%rcx), %r13
|
||||
\\ movq 0x40(%rcx), %r14
|
||||
\\ movq 0x48(%rcx), %r15
|
||||
\\
|
||||
\\ # restore stack pointer
|
||||
\\ movq 0x10(%rcx), %rsp
|
||||
\\
|
||||
\\ # load jmp address
|
||||
\\ movq 0x50(%rcx), %r8
|
||||
\\
|
||||
\\ # set up return value
|
||||
\\ movq %rbx, %rax
|
||||
\\
|
||||
\\ movdqu 0x60(%rcx), %xmm6
|
||||
\\ movdqu 0x70(%rcx), %xmm7
|
||||
\\ movdqu 0x80(%rcx), %xmm8
|
||||
\\ movdqu 0x90(%rcx), %xmm9
|
||||
\\ movdqu 0xa0(%rcx), %xmm10
|
||||
\\ movdqu 0xb0(%rcx), %xmm11
|
||||
\\ movdqu 0xc0(%rcx), %xmm12
|
||||
\\ movdqu 0xd0(%rcx), %xmm13
|
||||
\\ movdqu 0xe0(%rcx), %xmm14
|
||||
\\ movdqu 0xf0(%rcx), %xmm15
|
||||
\\
|
||||
\\ jmp *%r8
|
||||
\\
|
||||
\\.global windows_setjmp;
|
||||
\\windows_setjmp:
|
||||
\\ movq %rdx, 0x00(%rcx)
|
||||
\\ movq %rbx, 0x08(%rcx)
|
||||
\\ # note 0x10 is not used yet!
|
||||
\\ movq %rbp, 0x18(%rcx)
|
||||
\\ movq %rsi, 0x20(%rcx)
|
||||
\\ movq %rdi, 0x28(%rcx)
|
||||
\\ movq %r12, 0x30(%rcx)
|
||||
\\ movq %r13, 0x38(%rcx)
|
||||
\\ movq %r14, 0x40(%rcx)
|
||||
\\ movq %r15, 0x48(%rcx)
|
||||
\\
|
||||
\\ # the stack location right after the windows_setjmp call
|
||||
\\ leaq 0x08(%rsp), %r8
|
||||
\\ movq %r8, 0x10(%rcx)
|
||||
\\
|
||||
\\ movq (%rsp), %r8
|
||||
\\ movq %r8, 0x50(%rcx)
|
||||
\\
|
||||
\\ movdqu %xmm6, 0x60(%rcx)
|
||||
\\ movdqu %xmm7, 0x70(%rcx)
|
||||
\\ movdqu %xmm8, 0x80(%rcx)
|
||||
\\ movdqu %xmm9, 0x90(%rcx)
|
||||
\\ movdqu %xmm10, 0xa0(%rcx)
|
||||
\\ movdqu %xmm11, 0xb0(%rcx)
|
||||
\\ movdqu %xmm12, 0xc0(%rcx)
|
||||
\\ movdqu %xmm13, 0xd0(%rcx)
|
||||
\\ movdqu %xmm14, 0xe0(%rcx)
|
||||
\\ movdqu %xmm15, 0xf0(%rcx)
|
||||
\\
|
||||
\\ xorl %eax, %eax
|
||||
\\ ret
|
||||
\\
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Export helpers - Must be run inside a comptime
|
||||
fn exportBuiltinFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
@export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .Strong });
|
||||
|
@ -86,6 +86,15 @@ pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void {
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportNumToFloatCast(comptime T: type, comptime F: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(x: T) callconv(.C) F {
|
||||
return @floatFromInt(x);
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportPow(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(base: T, exp: T) callconv(.C) T {
|
||||
|
@ -1,6 +1,5 @@
|
||||
const utils = @import("utils.zig");
|
||||
const RocList = @import("list.zig").RocList;
|
||||
const grapheme = @import("helpers/grapheme.zig");
|
||||
const UpdateMode = utils.UpdateMode;
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
@ -552,242 +551,6 @@ pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
|
||||
return string.len();
|
||||
}
|
||||
|
||||
// Str.toScalars
|
||||
pub fn strToScalarsC(str: RocStr) callconv(.C) RocList {
|
||||
return @call(.always_inline, strToScalars, .{str});
|
||||
}
|
||||
|
||||
fn strToScalars(string: RocStr) callconv(.C) RocList {
|
||||
const len = string.len();
|
||||
|
||||
if (len == 0) {
|
||||
return RocList.empty();
|
||||
}
|
||||
|
||||
var capacity = len;
|
||||
|
||||
if (!string.isSmallStr()) {
|
||||
capacity = string.getCapacity();
|
||||
}
|
||||
|
||||
// For purposes of preallocation, assume the number of code points is the same
|
||||
// as the number of bytes. This might be longer than necessary, but definitely
|
||||
// should not require a second allocation.
|
||||
var answer = RocList.allocate(@alignOf(u32), capacity, @sizeOf(u32));
|
||||
|
||||
// `orelse unreachable` is fine here, because we already did an early
|
||||
// return to verify the string was nonempty.
|
||||
var answer_elems = answer.elements(u32) orelse unreachable;
|
||||
var src_index: usize = 0;
|
||||
var answer_index: usize = 0;
|
||||
|
||||
while (src_index < len) {
|
||||
src_index += writeNextScalar(string, src_index, answer_elems, answer_index);
|
||||
answer_index += 1;
|
||||
}
|
||||
|
||||
answer.length = answer_index;
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
// Given a non-empty RocStr, and a src_index byte index into that string,
|
||||
// and a destination [*]u32, and an index into that destination,
|
||||
// Parses the next scalar value out of the string (at the given byte index),
|
||||
// writes it into the destination, and returns the number of bytes parsed.
|
||||
inline fn writeNextScalar(non_empty_string: RocStr, src_index: usize, dest: [*]u32, dest_index: usize) usize {
|
||||
const utf8_byte = non_empty_string.getUnchecked(src_index);
|
||||
|
||||
// How UTF-8 bytes work:
|
||||
// https://docs.teradata.com/r/Teradata-Database-International-Character-Set-Support/June-2017/Client-Character-Set-Options/UTF8-Client-Character-Set-Support/UTF8-Multibyte-Sequences
|
||||
if (utf8_byte <= 127) {
|
||||
// It's an ASCII character. Copy it over directly.
|
||||
dest[dest_index] = @as(u32, @intCast(utf8_byte));
|
||||
|
||||
return 1;
|
||||
} else if (utf8_byte >> 5 == 0b0000_0110) {
|
||||
// Its three high order bits are 110, so this is a two-byte sequence.
|
||||
|
||||
// Example:
|
||||
// utf-8: 1100 1111 1011 0001
|
||||
// code pt: 0000 0011 1111 0001 (decimal: 1009)
|
||||
|
||||
// Discard the first byte's high order bits of 110.
|
||||
var code_pt = @as(u32, @intCast(utf8_byte & 0b0001_1111));
|
||||
|
||||
// Discard the second byte's high order bits of 10.
|
||||
code_pt <<= 6;
|
||||
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
|
||||
|
||||
dest[dest_index] = code_pt;
|
||||
|
||||
return 2;
|
||||
} else if (utf8_byte >> 4 == 0b0000_1110) {
|
||||
// Its four high order bits are 1110, so this is a three-byte sequence.
|
||||
|
||||
// Discard the first byte's high order bits of 1110.
|
||||
var code_pt = @as(u32, @intCast(utf8_byte & 0b0000_1111));
|
||||
|
||||
// Discard the second byte's high order bits of 10.
|
||||
code_pt <<= 6;
|
||||
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
|
||||
|
||||
// Discard the third byte's high order bits of 10 (same as second byte).
|
||||
code_pt <<= 6;
|
||||
code_pt |= non_empty_string.getUnchecked(src_index + 2) & 0b0011_1111;
|
||||
|
||||
dest[dest_index] = code_pt;
|
||||
|
||||
return 3;
|
||||
} else {
|
||||
// This must be a four-byte sequence, so the five high order bits should be 11110.
|
||||
|
||||
// Discard the first byte's high order bits of 11110.
|
||||
var code_pt = @as(u32, @intCast(utf8_byte & 0b0000_0111));
|
||||
|
||||
// Discard the second byte's high order bits of 10.
|
||||
code_pt <<= 6;
|
||||
code_pt |= non_empty_string.getUnchecked(src_index + 1) & 0b0011_1111;
|
||||
|
||||
// Discard the third byte's high order bits of 10 (same as second byte).
|
||||
code_pt <<= 6;
|
||||
code_pt |= non_empty_string.getUnchecked(src_index + 2) & 0b0011_1111;
|
||||
|
||||
// Discard the fourth byte's high order bits of 10 (same as second and third).
|
||||
code_pt <<= 6;
|
||||
code_pt |= non_empty_string.getUnchecked(src_index + 3) & 0b0011_1111;
|
||||
|
||||
dest[dest_index] = code_pt;
|
||||
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
test "strToScalars: empty string" {
|
||||
const str = RocStr.fromSlice("");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected = RocList.empty();
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
test "strToScalars: One ASCII char" {
|
||||
const str = RocStr.fromSlice("R");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected_array = [_]u32{82};
|
||||
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
|
||||
defer expected.decref(@sizeOf(u32));
|
||||
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
test "strToScalars: Multiple ASCII chars" {
|
||||
const str = RocStr.fromSlice("Roc!");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected_array = [_]u32{ 82, 111, 99, 33 };
|
||||
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
|
||||
defer expected.decref(@sizeOf(u32));
|
||||
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
test "strToScalars: One 2-byte UTF-8 character" {
|
||||
const str = RocStr.fromSlice("é");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected_array = [_]u32{233};
|
||||
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
|
||||
defer expected.decref(@sizeOf(u32));
|
||||
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
test "strToScalars: Multiple 2-byte UTF-8 characters" {
|
||||
const str = RocStr.fromSlice("Cäfés");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected_array = [_]u32{ 67, 228, 102, 233, 115 };
|
||||
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
|
||||
defer expected.decref(@sizeOf(u32));
|
||||
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
test "strToScalars: One 3-byte UTF-8 character" {
|
||||
const str = RocStr.fromSlice("鹏");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected_array = [_]u32{40527};
|
||||
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
|
||||
defer expected.decref(@sizeOf(u32));
|
||||
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
test "strToScalars: Multiple 3-byte UTF-8 characters" {
|
||||
const str = RocStr.fromSlice("鹏很有趣");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected_array = [_]u32{ 40527, 24456, 26377, 36259 };
|
||||
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
|
||||
defer expected.decref(@sizeOf(u32));
|
||||
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
test "strToScalars: One 4-byte UTF-8 character" {
|
||||
// from https://design215.com/toolbox/utf8-4byte-characters.php
|
||||
const str = RocStr.fromSlice("𒀀");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected_array = [_]u32{73728};
|
||||
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
|
||||
defer expected.decref(@sizeOf(u32));
|
||||
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
test "strToScalars: Multiple 4-byte UTF-8 characters" {
|
||||
// from https://design215.com/toolbox/utf8-4byte-characters.php
|
||||
const str = RocStr.fromSlice("𒀀𒀁");
|
||||
defer RocStr.decref(str);
|
||||
|
||||
const expected_array = [_]u32{ 73728, 73729 };
|
||||
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
|
||||
defer expected.decref(@sizeOf(u32));
|
||||
|
||||
const actual = strToScalars(str);
|
||||
defer actual.decref(@sizeOf(u32));
|
||||
|
||||
try expect(RocList.eql(actual, expected));
|
||||
}
|
||||
|
||||
// Str.fromInt
|
||||
pub fn exportFromInt(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
@ -1371,125 +1134,6 @@ test "countSegments: overlapping delimiter 2" {
|
||||
try expectEqual(segments_count, 3);
|
||||
}
|
||||
|
||||
// Str.countGraphemeClusters
|
||||
pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
|
||||
if (string.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bytes_len = string.len();
|
||||
const bytes_ptr = string.asU8ptr();
|
||||
|
||||
var bytes = bytes_ptr[0..bytes_len];
|
||||
var iter = (unicode.Utf8View.init(bytes) catch unreachable).iterator();
|
||||
|
||||
var count: usize = 0;
|
||||
var grapheme_break_state: ?grapheme.BoundClass = null;
|
||||
var grapheme_break_state_ptr = &grapheme_break_state;
|
||||
var opt_last_codepoint: ?u21 = null;
|
||||
while (iter.nextCodepoint()) |cur_codepoint| {
|
||||
if (opt_last_codepoint) |last_codepoint| {
|
||||
var did_break = grapheme.isGraphemeBreak(last_codepoint, cur_codepoint, grapheme_break_state_ptr);
|
||||
if (did_break) {
|
||||
count += 1;
|
||||
grapheme_break_state = null;
|
||||
}
|
||||
}
|
||||
opt_last_codepoint = cur_codepoint;
|
||||
}
|
||||
|
||||
// If there are no breaks, but the str is not empty, then there
|
||||
// must be a single grapheme
|
||||
if (bytes_len != 0) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Str.graphemes
|
||||
pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList {
|
||||
var break_state: ?grapheme.BoundClass = null;
|
||||
var opt_last_codepoint: ?u21 = null;
|
||||
var index: usize = 0;
|
||||
var last_codepoint_len: u8 = 0;
|
||||
|
||||
const alloc_ptr = @intFromPtr(roc_str.getAllocationPtr()) >> 1;
|
||||
const init_fn = if (roc_str.isSmallStr())
|
||||
&initFromSmallStr
|
||||
else
|
||||
&initFromBigStr;
|
||||
|
||||
var result = RocList.allocate(@alignOf(RocStr), countGraphemeClusters(roc_str), @sizeOf(RocStr));
|
||||
const graphemes = result.elements(RocStr) orelse return result;
|
||||
var slice = roc_str.asSlice();
|
||||
var iter = (unicode.Utf8View.init(slice) catch unreachable).iterator();
|
||||
|
||||
while (iter.nextCodepoint()) |cur_codepoint| {
|
||||
const cur_codepoint_len = unicode.utf8CodepointSequenceLength(cur_codepoint) catch unreachable;
|
||||
if (opt_last_codepoint) |last_codepoint| {
|
||||
var did_break = grapheme.isGraphemeBreak(last_codepoint, cur_codepoint, &break_state);
|
||||
if (did_break) {
|
||||
graphemes[index] = init_fn(@constCast(slice.ptr), last_codepoint_len, alloc_ptr);
|
||||
slice = slice[last_codepoint_len..];
|
||||
index += 1;
|
||||
break_state = null;
|
||||
last_codepoint_len = 0;
|
||||
}
|
||||
}
|
||||
last_codepoint_len += cur_codepoint_len;
|
||||
opt_last_codepoint = cur_codepoint;
|
||||
}
|
||||
// Append last grapheme
|
||||
graphemes[index] = init_fn(@constCast(slice.ptr), slice.len, alloc_ptr);
|
||||
|
||||
if (!roc_str.isSmallStr()) {
|
||||
// Correct refcount for all of the splits made.
|
||||
roc_str.incref(index + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// these test both countGraphemeClusters() and strGraphemes()
|
||||
fn graphemesTest(input: []const u8, expected: []const []const u8) !void {
|
||||
const rocstr = RocStr.fromSlice(input);
|
||||
defer rocstr.decref();
|
||||
const count = countGraphemeClusters(rocstr);
|
||||
try expectEqual(expected.len, count);
|
||||
|
||||
const graphemes = strGraphemes(rocstr);
|
||||
defer graphemes.decref(@sizeOf(u8));
|
||||
if (input.len == 0) return; // empty string
|
||||
const elems = graphemes.elements(RocStr) orelse unreachable;
|
||||
for (expected, 0..) |g, i| {
|
||||
try std.testing.expectEqualStrings(g, elems[i].asSlice());
|
||||
}
|
||||
}
|
||||
|
||||
test "graphemes: empty string" {
|
||||
try graphemesTest("", &.{});
|
||||
}
|
||||
|
||||
test "graphemes: ascii characters" {
|
||||
try graphemesTest("abcd", &.{ "a", "b", "c", "d" });
|
||||
}
|
||||
|
||||
test "graphemes: utf8 characters" {
|
||||
try graphemesTest("ãxā", &.{ "ã", "x", "ā" });
|
||||
}
|
||||
|
||||
test "graphemes: emojis" {
|
||||
try graphemesTest("🤔🤔🤔", &.{ "🤔", "🤔", "🤔" });
|
||||
}
|
||||
|
||||
test "graphemes: emojis and ut8 characters" {
|
||||
try graphemesTest("🤔å🤔¥🤔ç", &.{ "🤔", "å", "🤔", "¥", "🤔", "ç" });
|
||||
}
|
||||
|
||||
test "graphemes: emojis, ut8, and ascii characters" {
|
||||
try graphemesTest("6🤔å🤔e¥🤔çpp", &.{ "6", "🤔", "å", "🤔", "e", "¥", "🤔", "ç", "p", "p" });
|
||||
}
|
||||
|
||||
pub fn countUtf8Bytes(string: RocStr) callconv(.C) usize {
|
||||
return string.len();
|
||||
}
|
||||
@ -1614,44 +1258,6 @@ pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr {
|
||||
return ret_string;
|
||||
}
|
||||
|
||||
// Str.startsWithScalar
|
||||
pub fn startsWithScalar(string: RocStr, prefix: u32) callconv(.C) bool {
|
||||
const len = string.len();
|
||||
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write this (non-empty) string's first scalar into `first_scalar`
|
||||
var first_scalar: [1]u32 = undefined;
|
||||
|
||||
_ = writeNextScalar(string, 0, &first_scalar, 0);
|
||||
|
||||
// Return whether `first_scalar` equals `prefix`
|
||||
return @as(*u32, @ptrCast(&first_scalar)).* == prefix;
|
||||
}
|
||||
|
||||
test "startsWithScalar: empty string" {
|
||||
const whole = RocStr.empty();
|
||||
const prefix: u32 = 'x';
|
||||
try expect(!startsWithScalar(whole, prefix));
|
||||
}
|
||||
|
||||
test "startsWithScalar: ascii char" {
|
||||
const whole = RocStr.fromSlice("foobar");
|
||||
const prefix: u32 = 'f';
|
||||
try expect(startsWithScalar(whole, prefix));
|
||||
}
|
||||
|
||||
test "startsWithScalar: emoji" {
|
||||
const yes = RocStr.fromSlice("💖foobar");
|
||||
const no = RocStr.fromSlice("foobar");
|
||||
const prefix: u32 = '💖';
|
||||
|
||||
try expect(startsWithScalar(yes, prefix));
|
||||
try expect(!startsWithScalar(no, prefix));
|
||||
}
|
||||
|
||||
test "startsWith: foo starts with fo" {
|
||||
const foo = RocStr.fromSlice("foo");
|
||||
const fo = RocStr.fromSlice("fo");
|
||||
@ -2761,78 +2367,6 @@ test "capacity: big string" {
|
||||
try expect(data.getCapacity() >= data_bytes.len);
|
||||
}
|
||||
|
||||
pub fn appendScalar(string: RocStr, scalar_u32: u32) callconv(.C) RocStr {
|
||||
const scalar = @as(u21, @intCast(scalar_u32));
|
||||
const width = std.unicode.utf8CodepointSequenceLength(scalar) catch unreachable;
|
||||
|
||||
var output = string.reallocate(string.len() + width);
|
||||
var slice = output.asSliceWithCapacityMut();
|
||||
|
||||
_ = std.unicode.utf8Encode(scalar, slice[string.len() .. string.len() + width]) catch unreachable;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
test "appendScalar: small A" {
|
||||
const A: []const u8 = "A";
|
||||
|
||||
const data_bytes = "hello";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const actual = appendScalar(data, A[0]);
|
||||
defer actual.decref();
|
||||
|
||||
const expected_bytes = "helloA";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.decref();
|
||||
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
test "appendScalar: small 😀" {
|
||||
const data_bytes = "hello";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const actual = appendScalar(data, 0x1F600);
|
||||
defer actual.decref();
|
||||
|
||||
const expected_bytes = "hello😀";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.decref();
|
||||
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
test "appendScalar: big A" {
|
||||
const A: []const u8 = "A";
|
||||
|
||||
const data_bytes = "a string so large that it must be heap-allocated";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const actual = appendScalar(data, A[0]);
|
||||
defer actual.decref();
|
||||
|
||||
const expected_bytes = "a string so large that it must be heap-allocatedA";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.decref();
|
||||
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
test "appendScalar: big 😀" {
|
||||
const data_bytes = "a string so large that it must be heap-allocated";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const actual = appendScalar(data, 0x1F600);
|
||||
defer actual.decref();
|
||||
|
||||
const expected_bytes = "a string so large that it must be heap-allocated😀";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.decref();
|
||||
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
pub fn reserve(string: RocStr, spare: usize) callconv(.C) RocStr {
|
||||
const old_length = string.len();
|
||||
if (string.getCapacity() >= old_length + spare) {
|
||||
@ -2849,27 +2383,6 @@ pub fn withCapacity(capacity: usize) callconv(.C) RocStr {
|
||||
str.setLen(0);
|
||||
return str;
|
||||
}
|
||||
|
||||
pub fn getScalarUnsafe(string: RocStr, index: usize) callconv(.C) extern struct { bytesParsed: usize, scalar: u32 } {
|
||||
const slice = string.asSlice();
|
||||
const bytesParsed = @as(usize, @intCast(std.unicode.utf8ByteSequenceLength(slice[index]) catch unreachable));
|
||||
const scalar = std.unicode.utf8Decode(slice[index .. index + bytesParsed]) catch unreachable;
|
||||
|
||||
return .{ .bytesParsed = bytesParsed, .scalar = @as(u32, @intCast(scalar)) };
|
||||
}
|
||||
|
||||
test "getScalarUnsafe" {
|
||||
const data_bytes = "A";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const result = getScalarUnsafe(data, 0);
|
||||
|
||||
const expected = try std.unicode.utf8Decode("A");
|
||||
|
||||
try expectEqual(result.scalar, @as(u32, @intCast(expected)));
|
||||
try expectEqual(result.bytesParsed, 1);
|
||||
}
|
||||
|
||||
pub fn strCloneTo(
|
||||
string: RocStr,
|
||||
ptr: [*]u8,
|
||||
|
@ -729,7 +729,7 @@ incrementDist = \distAndFingerprint ->
|
||||
distAndFingerprint + distInc
|
||||
|
||||
incrementDistN = \distAndFingerprint, n ->
|
||||
distAndFingerprint + (n * distInc)
|
||||
distAndFingerprint + (Num.mulWrap n distInc)
|
||||
|
||||
decrementDist = \distAndFingerprint ->
|
||||
distAndFingerprint - distInc
|
||||
@ -837,7 +837,7 @@ removeBucketHelper = \buckets, bucketIndex ->
|
||||
nextIndex = nextBucketIndex bucketIndex (List.len buckets)
|
||||
nextBucket = listGetUnsafe buckets nextIndex
|
||||
# shift down until either empty or an element with correct spot is found
|
||||
if nextBucket.distAndFingerprint >= distInc * 2 then
|
||||
if nextBucket.distAndFingerprint >= Num.mulWrap distInc 2 then
|
||||
List.set buckets bucketIndex { nextBucket & distAndFingerprint: decrementDist nextBucket.distAndFingerprint }
|
||||
|> removeBucketHelper nextIndex
|
||||
else
|
||||
@ -857,7 +857,7 @@ increaseSize = \@Dict { data, maxBucketCapacity, maxLoadFactor, shifts } ->
|
||||
shifts: newShifts,
|
||||
}
|
||||
else
|
||||
crash "Dict hit limit of \(Num.toStr maxBucketCount) elements. Unable to grow more."
|
||||
crash "Dict hit limit of $(Num.toStr maxBucketCount) elements. Unable to grow more."
|
||||
|
||||
allocBucketsFromShift : U8, F32 -> (List Bucket, U64)
|
||||
allocBucketsFromShift = \shifts, maxLoadFactor ->
|
||||
@ -1417,7 +1417,7 @@ wymix = \a, b ->
|
||||
|
||||
wymum : U64, U64 -> { lower : U64, upper : U64 }
|
||||
wymum = \a, b ->
|
||||
r = Num.toU128 a * Num.toU128 b
|
||||
r = Num.mulWrap (Num.toU128 a) (Num.toU128 b)
|
||||
lower = Num.toU64 r
|
||||
upper = Num.shiftRightZfBy r 64 |> Num.toU64
|
||||
|
||||
|
@ -435,7 +435,8 @@ repeatHelp = \value, count, accum ->
|
||||
## ```
|
||||
reverse : List a -> List a
|
||||
reverse = \list ->
|
||||
reverseHelp list 0 (Num.subSaturated (List.len list) 1)
|
||||
end = List.len list |> Num.subSaturated 1
|
||||
reverseHelp (List.clone list) 0 end
|
||||
|
||||
reverseHelp = \list, left, right ->
|
||||
if left < right then
|
||||
@ -443,6 +444,9 @@ reverseHelp = \list, left, right ->
|
||||
else
|
||||
list
|
||||
|
||||
# Ensures that the list in unique (will re-use if already unique)
|
||||
clone : List a -> List a
|
||||
|
||||
## Join the given lists together into one list.
|
||||
## ```
|
||||
## expect List.join [[1], [2, 3], [], [4, 5]] == [1, 2, 3, 4, 5]
|
||||
|
@ -48,6 +48,7 @@ interface Num
|
||||
isLte,
|
||||
isGt,
|
||||
isGte,
|
||||
isApproxEq,
|
||||
sin,
|
||||
cos,
|
||||
tan,
|
||||
@ -661,6 +662,22 @@ isLte : Num a, Num a -> Bool
|
||||
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
|
||||
isGte : Num a, Num a -> Bool
|
||||
|
||||
## Returns `Bool.true` if the first number and second number are within a specific threshold
|
||||
##
|
||||
## A specific relative and absolute tolerance can be provided to change the threshold
|
||||
##
|
||||
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
|
||||
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
|
||||
isApproxEq : Frac a, Frac a, { rtol ? Frac a, atol ? Frac a } -> Bool
|
||||
isApproxEq = \value, refValue, { rtol ? 0.00001, atol ? 0.00000001 } -> value
|
||||
<= refValue
|
||||
&& value
|
||||
>= refValue
|
||||
|| Num.absDiff value refValue
|
||||
<= atol
|
||||
+ rtol
|
||||
* Num.abs refValue
|
||||
|
||||
## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise.
|
||||
isZero : Num a -> Bool
|
||||
|
||||
@ -985,13 +1002,21 @@ divCeilChecked = \a, b ->
|
||||
## Num.divTrunc 8 -3
|
||||
## ```
|
||||
divTrunc : Int a, Int a -> Int a
|
||||
divTrunc = \a, b ->
|
||||
if Num.isZero b then
|
||||
crash "Integer division by 0!"
|
||||
else
|
||||
Num.divTruncUnchecked a b
|
||||
|
||||
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]
|
||||
divTruncChecked = \a, b ->
|
||||
if Num.isZero b then
|
||||
Err DivByZero
|
||||
else
|
||||
Ok (Num.divTrunc a b)
|
||||
Ok (Num.divTruncUnchecked a b)
|
||||
|
||||
## traps (hardware fault) when given zero as the second argument.
|
||||
divTruncUnchecked : Int a, Int a -> Int a
|
||||
|
||||
## Obtains the remainder (truncating modulo) from the division of two integers.
|
||||
##
|
||||
@ -1006,13 +1031,21 @@ divTruncChecked = \a, b ->
|
||||
## Num.rem -8 -3
|
||||
## ```
|
||||
rem : Int a, Int a -> Int a
|
||||
rem = \a, b ->
|
||||
if Num.isZero b then
|
||||
crash "Integer division by 0!"
|
||||
else
|
||||
Num.remUnchecked a b
|
||||
|
||||
remChecked : Int a, Int a -> Result (Int a) [DivByZero]
|
||||
remChecked = \a, b ->
|
||||
if Num.isZero b then
|
||||
Err DivByZero
|
||||
else
|
||||
Ok (Num.rem a b)
|
||||
Ok (Num.remUnchecked a b)
|
||||
|
||||
## traps (hardware fault) when given zero as the second argument.
|
||||
remUnchecked : Int a, Int a -> Int a
|
||||
|
||||
isMultipleOf : Int a, Int a -> Bool
|
||||
|
||||
|
@ -1,90 +1,331 @@
|
||||
## Roc strings are sequences of text values. This module includes functions for combining strings,
|
||||
## as well as breaking them up into smaller units—most commonly [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
|
||||
## (referred to in this module's documentation as "graphemes" rather than "characters" for clarity;
|
||||
## "characters" can mean very different things in different languages).
|
||||
## Strings represent text. For example, `"Hi!"` is a string.
|
||||
##
|
||||
## This module focuses on graphemes (as opposed to, say, Unicode code points or LATIN-1 bytes)
|
||||
## because graphemes avoid common classes of bugs. Breaking strings up using code points often
|
||||
## leads to bugs around things like emoji, where multiple code points combine to form to a
|
||||
## single rendered glyph. Graphemes avoid these bugs by treating multi-code-point things like
|
||||
## emojis as indivisible units.
|
||||
## This guide starts at a high level and works down to the in-memory representation of strings and their [performance characteristics](#performance). For reasons that will be explained later in this guide, some string operations are in the `Str` module while others (notably [capitalization](#capitalization), [code points](#code-points), [graphemes](#graphemes), and sorting) are in separate packages. There's also a list of recommendations for [when to use code points, graphemes, and UTF-8](#when-to-use).
|
||||
##
|
||||
## Because graphemes can have variable length (there's no upper limit on how many code points one
|
||||
## grapheme can represent), it takes linear time to count the number of graphemes in a string,
|
||||
## and also linear time to find an individual grapheme within a string by its position (or "index")
|
||||
## among the string's other graphemes. The only way to get constant-time access to these is in a way
|
||||
## that can result in bugs if the string contains multi-code-point things like emojis, which is why
|
||||
## this module does not offer those.
|
||||
## ## Syntax
|
||||
##
|
||||
## The most common way to represent strings is using quotation marks:
|
||||
##
|
||||
## ## Working with Unicode strings in Roc
|
||||
##
|
||||
## Unicode can represent text values which span multiple languages, symbols, and emoji.
|
||||
## Here are some valid Roc strings:
|
||||
## ```
|
||||
## "Roc!"
|
||||
## "鹏"
|
||||
## "🕊"
|
||||
## "Hello, World!"
|
||||
## ```
|
||||
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
|
||||
## An extended grapheme cluster represents what a person reading a string might
|
||||
## call a "character" - like "A" or "ö" or "👩👩👦👦".
|
||||
## Because the term "character" means different things in different areas of
|
||||
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
|
||||
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
|
||||
##
|
||||
## You can get the number of graphemes in a string by calling `Str.countGraphemes` on it:
|
||||
## Using this syntax, the whole string must go on one line. You can write multiline strings using triple quotes:
|
||||
##
|
||||
## ```
|
||||
## Str.countGraphemes "Roc!"
|
||||
## Str.countGraphemes "折り紙"
|
||||
## Str.countGraphemes "🕊"
|
||||
## text =
|
||||
## """
|
||||
## In memory, this string will not have any spaces
|
||||
## at its start. That's because the first line
|
||||
## starts at the same indentation level as the
|
||||
## opening quotation mark. Actually, none of these
|
||||
## lines will be indented.
|
||||
##
|
||||
## However, this line will be indented!
|
||||
## """
|
||||
## ```
|
||||
## > The `countGraphemes` function walks through the entire string to get its answer,
|
||||
## > so if you want to check whether a string is empty, you'll get much better performance
|
||||
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
|
||||
##
|
||||
## ### Escape sequences
|
||||
## In triple-quoted strings, both the opening and closing `"""` must be at the same indentation level. Lines in the string begin at that indentation level; the spaces that indent the multiline string itself are not considered content.
|
||||
##
|
||||
## ### Interpolation
|
||||
##
|
||||
## *String interpolation* is syntax for inserting a string into another string.
|
||||
##
|
||||
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
|
||||
## An escape sequence is a convenient way to insert certain strings into other strings.
|
||||
## For example, suppose you write this Roc string:
|
||||
## ```
|
||||
## "I took the one less traveled by,\nAnd that has made all the difference."
|
||||
## name = "Sam"
|
||||
##
|
||||
## "Hi, my name is $(name)!"
|
||||
## ```
|
||||
## The `"\n"` in the middle will insert a line break into this string. There are
|
||||
## other ways of getting a line break in there, but `"\n"` is the most common.
|
||||
##
|
||||
## Another way you could insert a newlines is by writing `\u(0A)` instead of `\n`.
|
||||
## That would result in the same string, because the `\u` escape sequence inserts
|
||||
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
|
||||
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
|
||||
## `\u` escape sequences are always followed by a hexadecimal number inside `(` and `)`
|
||||
## like this.
|
||||
## This will evaluate to the string `"Hi, my name is Sam!"`
|
||||
##
|
||||
## As another example, `"R\u(6F)c"` is the same string as `"Roc"`, because
|
||||
## `"\u(6F)"` corresponds to the Unicode code point for lowercase `o`. If you
|
||||
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
|
||||
## you can write `"R\u(F6)c"` as an alternative way to get the string `"Röc"\.
|
||||
## You can put any expression you like inside the parentheses, as long as it's all on one line:
|
||||
##
|
||||
## Roc strings also support these escape sequences:
|
||||
##
|
||||
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
|
||||
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
|
||||
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
|
||||
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
|
||||
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
|
||||
##
|
||||
## You can also use escape sequences to insert named strings into other strings, like so:
|
||||
## ```
|
||||
## name = "Lee"
|
||||
## city = "Roctown"
|
||||
## greeting = "Hello there, \(name)! Welcome to \(city)."
|
||||
## colors = ["red", "green", "blue"]
|
||||
##
|
||||
## "The colors are $(colors |> Str.joinWith ", ")!"
|
||||
## ```
|
||||
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
|
||||
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
|
||||
## and you can use it as many times as you like inside a string. The name
|
||||
## between the parentheses must refer to a `Str` value that is currently in
|
||||
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
|
||||
##
|
||||
## Interpolation can be used in multiline strings, but the part inside the parentheses must still be on one line.
|
||||
##
|
||||
## ### Escapes
|
||||
##
|
||||
## There are a few special escape sequences in strings:
|
||||
##
|
||||
## * `\n` becomes a [newline](https://en.wikipedia.org/wiki/Newline)
|
||||
## * `\r` becomes a [carriage return](https://en.wikipedia.org/wiki/Carriage_return#Computers)
|
||||
## * `\t` becomes a [tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
|
||||
## * `\"` becomes a normal `"` (this lets you write `"` inside a single-line string)
|
||||
## * `\\` becomes a normal `\` (this lets you write `\` without it being treated as an escape)
|
||||
## * `\$` becomes a normal `$` (this lets you write `$` followed by `(` without it being treated as [interpolation](#interpolation))
|
||||
##
|
||||
## These work in both single-line and multiline strings. We'll also discuss another escape later, for inserting [Unicode code points](#code-points) into a string.
|
||||
##
|
||||
## ### Single quote syntax
|
||||
##
|
||||
## Try putting `'👩'` into `roc repl`. You should see this:
|
||||
##
|
||||
## ```
|
||||
## » '👩'
|
||||
##
|
||||
## 128105 : Int *
|
||||
## ```
|
||||
##
|
||||
## The single-quote `'` syntax lets you represent a Unicode code point (discussed in the next section) in source code, in a way that renders as the actual text it represents rather than as a number literal. This lets you see what it looks like in the source code rather than looking at a number.
|
||||
##
|
||||
## At runtime, the single-quoted value will be treated the same as an ordinary number literal—in other words, `'👩'` is syntax sugar for writing `128105`. You can verify this in `roc repl`:
|
||||
##
|
||||
## ```
|
||||
## » '👩' == 128105
|
||||
##
|
||||
## Bool.true : Bool
|
||||
## ```
|
||||
##
|
||||
## Double quotes (`"`), on the other hand, are not type-compatible with integers—not only because strings can be empty (`""` is valid, but `''` is not) but also because there may be more than one code point involved in any given string!
|
||||
##
|
||||
## There are also some special escape sequences in single-quote strings:
|
||||
##
|
||||
## * `\n` becomes a [newline](https://en.wikipedia.org/wiki/Newline)
|
||||
## * `\r` becomes a [carriage return](https://en.wikipedia.org/wiki/Carriage_return#Computers)
|
||||
## * `\t` becomes a [tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
|
||||
## * `\'` becomes a normal `'` (this lets you write `'` inside a single-quote string)
|
||||
## * `\\` becomes a normal `\` (this lets you write `\` without it being treated as an escape)
|
||||
##
|
||||
## Most often this single-quote syntax is used when writing parsers; most Roc programs never use it at all.
|
||||
##
|
||||
## ## Unicode
|
||||
##
|
||||
## Roc strings represent text using [Unicode](https://unicode.org) This guide will provide only a basic overview of Unicode (the [Unicode glossary](http://www.unicode.org/glossary/) has over 500 entries in it), but it will include the most relevant differences between these concepts:
|
||||
##
|
||||
## * Code points
|
||||
## * Graphemes
|
||||
## * UTF-8
|
||||
##
|
||||
## It will also explain why some operations are included in Roc's builtin [Str](https://www.roc-lang.org/builtins/Str)
|
||||
## module, and why others are in separate packages like [roc-lang/unicode](https://github.com/roc-lang/unicode).
|
||||
##
|
||||
## ### Graphemes
|
||||
##
|
||||
## Let's start with the following string:
|
||||
##
|
||||
## `"👩👩👦👦"`
|
||||
##
|
||||
## Some might call this a "character." After all, in a monospace font, it looks to be about the same width as the letter "A" or the punctuation mark "!"—both of which are commonly called "characters." Unfortunately, the term "character" in programming has changed meanings many times across the years and across programming languages, and today it's become a major source of confusion.
|
||||
##
|
||||
## Unicode uses the less ambiguous term [*grapheme*](https://www.unicode.org/glossary/#grapheme), which it defines as a "user-perceived character" (as opposed to one of the several historical ways the term "character" has been used in programming) or, alternatively, "A minimally distinctive unit of writing in the context of a particular writing system."
|
||||
##
|
||||
## By Unicode's definition, each of the following is an individual grapheme:
|
||||
##
|
||||
## * `a`
|
||||
## * `鹏`
|
||||
## * `👩👩👦👦`
|
||||
##
|
||||
## Note that although *grapheme* is less ambiguous than *character*, its definition is still open to interpretation. To address this, Unicode has formally specified [text segmentation rules](https://www.unicode.org/reports/tr29/) which define grapheme boundaries in precise technical terms. We won't get into those rules here, but since they can change with new Unicode releases, functions for working with graphemes are in the [roc-lang/unicode](https://github.com/roc-lang/unicode) package rather than in the builtin [`Str`](https://www.roc-lang.org/builtins/Str) module. This allows them to be updated without being blocked on a new release of the Roc language.
|
||||
##
|
||||
## ### Code Points
|
||||
##
|
||||
## Every Unicode text value can be broken down into [Unicode code points](http://www.unicode.org/glossary/#code_point), which are integers between `0` and `285_212_438` that describe components of the text. In memory, every Roc string is a sequence of these integers stored in a format called UTF-8, which will be discussed [later](#utf8).
|
||||
##
|
||||
## The string `"👩👩👦👦"` happens to be made up of these code points:
|
||||
##
|
||||
## ```
|
||||
## [128105, 8205, 128105, 8205, 128102, 8205, 128102]
|
||||
## ```
|
||||
##
|
||||
## From this we can see that:
|
||||
##
|
||||
## - One grapheme can be made up of multiple code points. In fact, there is no upper limit on how many code points can go into a single grapheme! (Some programming languages use the term "character" to refer to individual code points; this can be confusing for graphemes like 👩👩👦👦 because it visually looks like "one character" but no single code point can represent it.)
|
||||
## - Sometimes code points repeat within an individual grapheme. Here, 128105 repeats twice, as does 128102, and there's an 8205 in between each of the other code points.
|
||||
##
|
||||
## ### Combining Code Points
|
||||
##
|
||||
## The reason every other code point in 👩👩👦👦 is 8205 is that code point 8205 joins together other code points. This emoji, known as ["Family: Woman, Woman, Boy, Boy"](https://emojipedia.org/family-woman-woman-boy-boy), is made by combining several emoji using [zero-width joiners](https://emojipedia.org/zero-width-joiner)—which are represented by code point 8205 in memory, and which have no visual repesentation on their own.
|
||||
##
|
||||
## Here are those code points again, this time with comments about what they represent:
|
||||
##
|
||||
## ```
|
||||
## [128105] # "👩"
|
||||
## [8205] # (joiner)
|
||||
## [128105] # "👩"
|
||||
## [8205] # (joiner)
|
||||
## [128102] # "👦"
|
||||
## [8205] # (joiner)
|
||||
## [128102] # "👦"
|
||||
## ```
|
||||
##
|
||||
## One way to read this is "woman emoji joined to woman emoji joined to boy emoji joined to boy emoji." Without the joins, it would be:
|
||||
##
|
||||
## ```
|
||||
## "👩👩👦👦"
|
||||
## ```
|
||||
##
|
||||
## With the joins, however, it is instead:
|
||||
##
|
||||
## ```
|
||||
## "👩👩👦👦"
|
||||
## ```
|
||||
##
|
||||
## Even though 👩👩👦👦 is visually smaller when rendered, it takes up almost twice as much memory as 👩👩👦👦 does! That's because it has all the same code points, plus the zero-width joiners in between them.
|
||||
##
|
||||
## ### String equality and normalization
|
||||
##
|
||||
## Besides emoji like 👩👩👦👦, another classic example of multiple code points being combined to render as one grapheme has to do with accent marks. Try putting these two strings into `roc repl`:
|
||||
##
|
||||
## ```
|
||||
## "caf\u(e9)"
|
||||
## "cafe\u(301)"
|
||||
## ```
|
||||
##
|
||||
## The `\u(e9)` syntax is a way of inserting code points into string literals. In this case, it's the same as inserting the hexadecimal number `0xe9` as a code point onto the end of the string `"caf"`. Since Unicode code point `0xe9` happens to be `é`, the string `"caf\u(e9)"` ends up being identical in memory to the string `"café"`.
|
||||
##
|
||||
## We can verify this too:
|
||||
##
|
||||
## ```
|
||||
## » "caf\u(e9)" == "café"
|
||||
##
|
||||
## Bool.true : Bool
|
||||
## ```
|
||||
##
|
||||
## As it turns out, `"cafe\u(301)"` is another way to represent the same word. The Unicode code point 0x301 represents a ["combining acute accent"](https://unicodeplus.com/U+0301)—which essentially means that it will add an accent mark to whatever came before it. In this case, since `"cafe\u(301)"` has an `e` before the `"\u(301)"`, that `e` ends up with an accent mark on it and becomes `é`.
|
||||
##
|
||||
## Although these two strings get rendered identically to one another, they are different in memory because their code points are different! We can also confirm this in `roc repl`:
|
||||
##
|
||||
## ```
|
||||
## » "caf\u(e9)" == "cafe\u(301)"
|
||||
##
|
||||
## Bool.false : Bool
|
||||
## ```
|
||||
##
|
||||
## As you can imagine, this can be a source of bugs. Not only are they considered unequal, they also hash differently, meaning `"caf\u(e9)"` and `"cafe\u(301)"` can both be separate entries in the same [`Set`](https://www.roc-lang.org/builtins/Set).
|
||||
##
|
||||
## One way to prevent problems like these is to perform [Unicode normalization](https://www.unicode.org/reports/tr15/), a process which converts conceptually equivalent strings (like `"caf\u(e9)"` and `"cafe\u(301)"`) into one canonical in-memory representation. This makes equality checks on them pass, among other benefits.
|
||||
##
|
||||
## It would be technically possible for Roc to perform string normalization automatically on every equality check. Unfortunately, although some programs might want to treat `"caf\u(e9)"` and `"cafe\u(301)"` as equivalent, for other programs it might actually be important to be able to tell them apart. If these equality checks always passed, then there would be no way to tell them apart!
|
||||
##
|
||||
## As such, normalization must be performed explicitly when desired. Like graphemes, Unicode normalization rules can change with new releases of Unicode. As such, these functions are in separate packages instead of builtins (normalization is planned to be in [roc-lang/unicode](https://github.com/roc-lang/unicode) in the future, but it has not yet been implemented) so that updates to these functions based on new Unicode releases can happen without waiting on new releases of the Roc language.
|
||||
##
|
||||
## ### Capitalization
|
||||
##
|
||||
## We've already seen two examples of Unicode definitions that can change with new Unicode releases: graphemes and normalization. Another is capitalization; these rules can change with new Unicode releases (most often in the form of additions of new languages, but breaking changes to capitalization rules for existing languages are also possible), and so they are not included in builtin [`Str`](https://www.roc-lang.org/builtins/Str).
|
||||
##
|
||||
## This might seem particularly surprising, since capitalization functions are commonly included in standard libraries. However, it turns out that "capitalizing an arbitrary string" is impossible to do correctly without additional information.
|
||||
##
|
||||
## For example, what is the capitalized version of this string?
|
||||
##
|
||||
## ```
|
||||
## "i"
|
||||
## ```
|
||||
##
|
||||
## * In English, the correct answer is `"I"`.
|
||||
## * In Turkish, the correct answer is `"İ"`.
|
||||
##
|
||||
## Similarly, the correct lowercased version of the string `"I"` is `"i"` in English and `"ı"` in Turkish.
|
||||
##
|
||||
## Turkish is not the only language to use this [dotless i](https://en.wikipedia.org/wiki/Dotless_I), and it's an example of how a function which capitalizes strings cannot give correct answers without the additional information of which language's capitalization rules should be used.
|
||||
##
|
||||
## Many languages defer to the operating system's [localization](https://en.wikipedia.org/wiki/Internationalization_and_localization) settings for this information. In that design, calling a program's capitalization function with an input string of `"i"` might give an answer of `"I"` on one machine and `"İ"` on a different machine, even though it was the same program running on both systems. Naturally, this can cause bugs—but more than that, writing tests to prevent bugs like this usually requires extra complexity compared to writing ordinary tests.
|
||||
##
|
||||
## In general, Roc programs should give the same answers for the same inputs even when run on different machines. There are exceptions to this (e.g. a program running out of system resources on one machine, while being able to make more progress on a machine that has more resources), but operating system's language localization is not among them.
|
||||
##
|
||||
## For these reasons, capitalization functions are not in [`Str`](https://www.roc-lang.org/builtins/Str). There is a planned `roc-lang` package to handle use cases like capitalization and sorting—sorting can also vary by language as well as by things like country—but implementation work has not yet started on this package.
|
||||
##
|
||||
## ### UTF-8
|
||||
##
|
||||
## Earlier, we discussed how Unicode code points can be described as [`U32`](https://www.roc-lang.org/builtins/Num#U32) integers. However, many common code points are very low integers, and can fit into a `U8` instead of needing an entire `U32` to represent them in memory. UTF-8 takes advantage of this, using a variable-width encoding to represent code points in 1-4 bytes, which saves a lot of memory in the typical case—especially compared to [UTF-16](https://en.wikipedia.org/wiki/UTF-16), which always uses at least 2 bytes to represent each code point, or [UTF-32](https://en.wikipedia.org/wiki/UTF-32), which always uses the maximum 4 bytes.
|
||||
##
|
||||
## This guide won't cover all the details of UTF-8, but the basic idea is this:
|
||||
##
|
||||
## - If a code point is 127 or lower, UTF-8 stores it in 1 byte.
|
||||
## - If it's between 128 and 2047, UTF-8 stores it in 2 bytes.
|
||||
## - If it's between 2048 and 65535, UTF-8 stores it in 3 bytes.
|
||||
## - If it's higher than that, UTF-8 stores it in 4 bytes.
|
||||
##
|
||||
## The specific [UTF-8 encoding](https://en.wikipedia.org/wiki/UTF-8#Encoding) of these bytes involves using 1 to 5 bits of each byte for metadata about multi-byte sequences.
|
||||
##
|
||||
## A valuable feature of UTF-8 is that it is backwards-compatible with the [ASCII](https://en.wikipedia.org/wiki/ASCII) encoding that was widely used for many years. ASCII existed before Unicode did, and only used the integers 0 to 127 to represent its equivalent of code points. The Unicode code points 0 to 127 represent the same semantic information as ASCII, (e.g. the number 64 represents the letter "A" in both ASCII and in Unicode), and since UTF-8 represents code points 0 to 127 using one byte, all valid ASCII strings can be successfully parsed as UTF-8 without any need for conversion.
|
||||
##
|
||||
## Since many textual computer encodings—including [CSV](https://en.wikipedia.org/wiki/CSV), [XML](https://en.wikipedia.org/wiki/XML), and [JSON](https://en.wikipedia.org/wiki/JSON)—do not use any code points above 127 for their delimiters, it is often possible to write parsers for these formats using only `Str` functions which present UTF-8 as raw `U8` sequences, such as [`Str.walkUtf8`](https://www.roc-lang.org/builtins/Str#walkUtf8) and [`Str.toUtf8`](https://www.roc-lang.org/builtins/Str#toUtf8). In the typical case where they do not to need to parse out individual Unicode code points, they can get everything they need from `Str` UTF-8 functions without needing to depend on other packages.
|
||||
##
|
||||
## ### When to use code points, graphemes, and UTF-8
|
||||
##
|
||||
## Deciding when to use code points, graphemes, and UTF-8 can be nonobvious to say the least!
|
||||
##
|
||||
## The way Roc organizes the `Str` module and supporting packages is designed to help answer this question. Every situation is different, but the following rules of thumb are typical:
|
||||
##
|
||||
## * Most often, using `Str` values along with helper functions like [`split`](https://www.roc-lang.org/builtins/Str#split), [`joinWith`](https://www.roc-lang.org/builtins/Str#joinWith), and so on, is the best option.
|
||||
## * If you are specifically implementing a parser, working in UTF-8 bytes is usually the best option. So functions like [`walkUtf8`](https://www.roc-lang.org/builtins/Str#walkUtf8), [toUtf8](https://www.roc-lang.org/builtins/Str#toUtf8), and so on. (Note that single-quote literals produce number literals, so ASCII-range literals like `'a'` gives an integer literal that works with a UTF-8 `U8`.)
|
||||
## * If you are implementing a Unicode library like [roc-lang/unicode](https://github.com/roc-lang/unicode), working in terms of code points will be unavoidable. Aside from basic readability considerations like `\u(...)` in string literals, if you have the option to avoid working in terms of code points, it is almost always correct to avoid them.
|
||||
## * If it seems like a good idea to split a string into "characters" (graphemes), you should definitely stop and reconsider whether this is really the best design. Almost always, doing this is some combination of more error-prone or slower (usually both) than doing something else that does not require taking graphemes into consideration.
|
||||
##
|
||||
## For this reason (among others), grapheme functions live in [roc-lang/unicode](https://github.com/roc-lang/unicode) rather than in [`Str`](https://www.roc-lang.org/builtins/Str). They are more niche than they seem, so they should not be reached for all the time!
|
||||
##
|
||||
## ## Performance
|
||||
##
|
||||
## This section deals with how Roc strings are represented in memory, and their performance characteristics.
|
||||
##
|
||||
## A normal heap-allocated roc `Str` is represented on the stack as:
|
||||
## - A "capacity" unsigned integer, which respresents how many bytes are allocated on the heap to hold the string's contents.
|
||||
## - A "length" unsigned integer, which rerepresents how many of the "capacity" bytes are actually in use. (A `Str` can have more bytes allocated on the heap than are actually in use.)
|
||||
## - The memory address of the first byte in the string's actual contents.
|
||||
##
|
||||
## Each of these three fields is the same size: 64 bits on a 64-bit system, and 32 bits on a 32-bit system. The actual contents of the string are stored in one contiguous sequence of bytes, encoded as UTF-8, often on the heap but sometimes elsewhere—more on this later. Empty strings do not have heap allocations, so an empty `Str` on a 64-bit system still takes up 24 bytes on the stack (due to its three 64-bit fields).
|
||||
##
|
||||
## ### Reference counting and opportunistic mutation
|
||||
##
|
||||
## Like lists, dictionaries, and sets, Roc strings are automatically reference-counted and can benefit from opportunistic in-place mutation. The reference count is stored on the heap immediately before the first byte of the string's contents, and it has the same size as a memory address. This means it can count so high that it's impossible to write a Roc program which overflows a reference count, because having that many simultaneous references (each of which is a memory address) would have exhausted the operating system's address space first.
|
||||
##
|
||||
## When the string's reference count is 1, functions like [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) and [`Str.replaceEach`](https://www.roc-lang.org/builtins/Str#replaceEach) mutate the string in-place rather than allocating a new string. This preserves semantic immutability because it is unobservable in terms of the operation's output; if the reference count is 1, it means that memory would have otherwise been deallocated immediately anyway, and it's more efficient to reuse it instead of deallocating it and then immediately making a new allocation.
|
||||
##
|
||||
## The contents of statically-known strings (today that means string literals) are stored in the readonly section of the binary, so they do not need heap allocations or reference counts. They are not eligible for in-place mutation, since mutating the readonly section of the binary would cause an operating system [access violation](https://en.wikipedia.org/wiki/Segmentation_fault).
|
||||
##
|
||||
## ### Small String Optimization
|
||||
##
|
||||
## Roc uses a "small string optimization" when representing certain strings in memory.
|
||||
##
|
||||
## If you have a sufficiently long string, then on a 64-bit system it will be represented on the stack using 24 bytes, and on a 32-bit system it will take 12 bytes—plus however many bytes are in the string itself—on the heap. However, if there is a string shorter than either of these stack sizes (so, a string of up to 23 bytes on a 64-bit system, and up to 11 bytes on a 32-bit system), then that string will be stored entirely on the stack rather than having a separate heap allocation at all.
|
||||
##
|
||||
## This can be much more memory-efficient! However, `List` does not have this optimization (it has some runtime cost, and in the case of `List` it's not anticipated to come up nearly as often), which means when converting a small string to `List U8` it can result in a heap allocation.
|
||||
##
|
||||
## Note that this optimization is based entirely on how many UTF-8 bytes the string takes up in memory. It doesn't matter how many [graphemes](#graphemes), [code points](#code-points) or anything else it has; the only factor that determines whether a particular string is eligible for the small string optimization is the number of UTF-8 bytes it takes up in memory!
|
||||
##
|
||||
## ### Seamless Slices
|
||||
##
|
||||
## Try putting this into `roc repl`:
|
||||
##
|
||||
## ```
|
||||
## » "foo/bar/baz" |> Str.split "/"
|
||||
##
|
||||
## ["foo", "bar", "baz"] : List Str
|
||||
## ```
|
||||
##
|
||||
## All of these strings are small enough that the [small string optimization](#small) will apply, so none of them will be allocated on the heap.
|
||||
##
|
||||
## Now let's suppose they were long enough that this optimization no longer applied:
|
||||
##
|
||||
## ```
|
||||
## » "a much, much, much, much/longer/string compared to the last one!" |> Str.split "/"
|
||||
##
|
||||
## ["a much, much, much, much", "longer", "string compared to the last one!"] : List Str
|
||||
## ```
|
||||
##
|
||||
## Here, the only strings small enough for the small string optimization are `"/"` and `"longer"`. They will be allocated on the stack.
|
||||
##
|
||||
## The first and last strings in the returned list `"a much, much, much, much"` and `"string compared to the last one!"` will not be allocated on the heap either. Instead, they will be *seamless slices*, which means they will share memory with the original input string.
|
||||
##
|
||||
## * `"a much, much, much, much"` will share the first 24 bytes of the original string.
|
||||
## * `"string compared to the last one!"` will share the last 32 bytes of the original string.
|
||||
##
|
||||
## All of these strings are semantically immutable, so sharing these bytes is an implementation detail that should only affect performance. By design, there is no way at either compile time or runtime to tell whether a string is a seamless slice. This allows the optimization's behavior to change in the future without affecting Roc programs' semantic behavior.
|
||||
##
|
||||
## Seamless slices create additional references to the original string, which make it ineligible for opportunistic mutation (along with the slices themselves; slices are never eligible for mutation), and which also make it take longer before the original string can be deallocated. A case where this might be noticeable in terms of performance would be:
|
||||
## 1. A function takes a very large string as an argument and returns a much smaller slice into that string.
|
||||
## 2. The smaller slice is used for a long time in the program, whereas the much larger original string stops being used.
|
||||
## 3. In this situation, it might have been better for total program memory usage (although not necessarily overall performance) if the original large string could have been deallocated sooner, even at the expense of having to copy the smaller string into a new allocation instead of reusing the bytes with a seamless slice.
|
||||
##
|
||||
## If a situation like this comes up, a slice can be turned into a separate string by using [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) to concatenate the slice onto an empty string (or one created with [`Str.withCapacity`](https://www.roc-lang.org/builtins/Str#withCapacity)).
|
||||
##
|
||||
## Currently, the only way to get seamless slices of strings is by calling certain `Str` functions which return them. In general, `Str` functions which accept a string and return a subset of that string tend to do this. [`Str.trim`](https://www.roc-lang.org/builtins/Str#trim) is another example of a function which returns a seamless slice.
|
||||
interface Str
|
||||
exposes [
|
||||
Utf8Problem,
|
||||
@ -94,9 +335,7 @@ interface Str
|
||||
joinWith,
|
||||
split,
|
||||
repeat,
|
||||
countGraphemes,
|
||||
countUtf8Bytes,
|
||||
startsWithScalar,
|
||||
toUtf8,
|
||||
fromUtf8,
|
||||
fromUtf8Range,
|
||||
@ -119,7 +358,6 @@ interface Str
|
||||
toI16,
|
||||
toU8,
|
||||
toI8,
|
||||
toScalars,
|
||||
replaceEach,
|
||||
replaceFirst,
|
||||
replaceLast,
|
||||
@ -129,12 +367,8 @@ interface Str
|
||||
walkUtf8WithIndex,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
appendScalar,
|
||||
walkScalars,
|
||||
walkScalarsUntil,
|
||||
withCapacity,
|
||||
withPrefix,
|
||||
graphemes,
|
||||
contains,
|
||||
]
|
||||
imports [
|
||||
@ -265,8 +499,7 @@ joinWith : List Str, Str -> Str
|
||||
## Split a string around a separator.
|
||||
##
|
||||
## Passing `""` for the separator is not useful;
|
||||
## it returns the original string wrapped in a [List]. To split a string
|
||||
## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
|
||||
## it returns the original string wrapped in a [List].
|
||||
## ```
|
||||
## expect Str.split "1,2,3" "," == ["1","2","3"]
|
||||
## expect Str.split "1,2,3" "" == ["1,2,3"]
|
||||
@ -285,78 +518,6 @@ split : Str, Str -> List Str
|
||||
## ```
|
||||
repeat : Str, Nat -> Str
|
||||
|
||||
## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
|
||||
## in the string.
|
||||
##
|
||||
## Note that the number of extended grapheme clusters can be different from the number
|
||||
## of visual glyphs rendered! Consider the following examples:
|
||||
## ```
|
||||
## expect Str.countGraphemes "Roc" == 3
|
||||
## expect Str.countGraphemes "👩👩👦👦" == 4
|
||||
## expect Str.countGraphemes "🕊" == 1
|
||||
## ```
|
||||
## Note that "👩👩👦👦" takes up 4 graphemes (even though visually it appears as a single
|
||||
## glyph) because under the hood it's represented using an emoji modifier sequence.
|
||||
## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented
|
||||
## using a single Unicode code point.
|
||||
countGraphemes : Str -> Nat
|
||||
|
||||
## Split a string into its constituent graphemes.
|
||||
##
|
||||
## This function breaks a string into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103),
|
||||
## returning them as a list of strings. This is useful for working with text that
|
||||
## contains complex characters, such as emojis.
|
||||
##
|
||||
## Examples:
|
||||
## ```
|
||||
## expect Str.graphemes "Roc" == ["R", "o", "c"]
|
||||
## expect Str.graphemes "नमस्ते" == ["न", "म", "स्", "ते"]
|
||||
## expect Str.graphemes "👩👩👦👦" == ["👩", "👩", "👦", "👦"]
|
||||
## ```
|
||||
##
|
||||
## Note that the "👩👩👦👦" example consists of 4 grapheme clusters, although it visually
|
||||
## appears as a single glyph. This is because it uses an emoji modifier sequence.
|
||||
graphemes : Str -> List Str
|
||||
|
||||
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
|
||||
## equal to the given [U32], returns [Bool.true]. Otherwise returns [Bool.false].
|
||||
##
|
||||
## If the given string is empty, or if the given [U32] is not a valid
|
||||
## code point, returns [Bool.false].
|
||||
## ```
|
||||
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
|
||||
## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
|
||||
## expect !Str.startsWithScalar "" 40527
|
||||
## ```
|
||||
##
|
||||
## ## Performance Details
|
||||
##
|
||||
## This runs slightly faster than [Str.startsWith], so
|
||||
## 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.startsWithScalar '鹏'`
|
||||
## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.)
|
||||
## This will not work for graphemes which take up multiple code points, however;
|
||||
## `Str.startsWithScalar '👩👩👦👦'` 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.startsWithScalar "🕊"` instead.
|
||||
startsWithScalar : Str, U32 -> Bool
|
||||
|
||||
## Returns a [List] of the [Unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value)
|
||||
## in the given string.
|
||||
##
|
||||
## (Roc strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
|
||||
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).)
|
||||
## ```
|
||||
## expect Str.toScalars "Roc" == [82, 111, 99]
|
||||
## expect Str.toScalars "鹏" == [40527]
|
||||
## expect Str.toScalars "சி" == [2970, 3007]
|
||||
## expect Str.toScalars "🐦" == [128038]
|
||||
## expect Str.toScalars "👩👩👦👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
|
||||
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
|
||||
## expect Str.toScalars "" == []
|
||||
## ```
|
||||
toScalars : Str -> List U32
|
||||
|
||||
## Returns a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
|
||||
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
|
||||
## see [Str.split].)
|
||||
@ -683,7 +844,7 @@ replaceFirst : Str, Str, Str -> Str
|
||||
replaceFirst = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
"\(before)\(flower)\(after)"
|
||||
"$(before)$(flower)$(after)"
|
||||
|
||||
Err NotFound -> haystack
|
||||
|
||||
@ -701,7 +862,7 @@ replaceLast : Str, Str, Str -> Str
|
||||
replaceLast = \haystack, needle, flower ->
|
||||
when splitLast haystack needle is
|
||||
Ok { before, after } ->
|
||||
"\(before)\(flower)\(after)"
|
||||
"$(before)$(flower)$(after)"
|
||||
|
||||
Err NotFound -> haystack
|
||||
|
||||
@ -907,80 +1068,6 @@ expect (walkUtf8 "鹏" [] List.append) == [233, 185, 143]
|
||||
## Note: This will also convert seamless slices to regular lists.
|
||||
releaseExcessCapacity : Str -> Str
|
||||
|
||||
## is UB when the scalar is invalid
|
||||
appendScalarUnsafe : Str, U32 -> Str
|
||||
|
||||
## Append a [U32] scalar to the given string. If the given scalar is not a valid
|
||||
## unicode value, it returns [Err InvalidScalar].
|
||||
## ```
|
||||
## expect Str.appendScalar "H" 105 == Ok "Hi"
|
||||
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
|
||||
## ```
|
||||
appendScalar : Str, U32 -> Result Str [InvalidScalar]
|
||||
appendScalar = \string, scalar ->
|
||||
if isValidScalar scalar then
|
||||
Ok (appendScalarUnsafe string scalar)
|
||||
else
|
||||
Err InvalidScalar
|
||||
|
||||
isValidScalar : U32 -> Bool
|
||||
isValidScalar = \scalar ->
|
||||
scalar <= 0xD7FF || (scalar >= 0xE000 && scalar <= 0x10FFFF)
|
||||
|
||||
getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
|
||||
|
||||
## Walks over the unicode [U32] values for the given [Str] and calls a function
|
||||
## to update state for each.
|
||||
## ```
|
||||
## f : List U32, U32 -> List U32
|
||||
## f = \state, scalar -> List.append state scalar
|
||||
## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
|
||||
## ```
|
||||
walkScalars : Str, state, (state, U32 -> state) -> state
|
||||
walkScalars = \string, init, step ->
|
||||
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
|
||||
|
||||
walkScalarsHelp : Str, state, (state, U32 -> state), Nat, Nat -> state
|
||||
walkScalarsHelp = \string, state, step, index, length ->
|
||||
if index < length then
|
||||
{ scalar, bytesParsed } = getScalarUnsafe string index
|
||||
newState = step state scalar
|
||||
|
||||
walkScalarsHelp string newState step (Num.addWrap index bytesParsed) length
|
||||
else
|
||||
state
|
||||
|
||||
## Walks over the unicode [U32] values for the given [Str] and calls a function
|
||||
## to update state for each.
|
||||
## ```
|
||||
## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
|
||||
## f = \state, scalar ->
|
||||
## check = 66
|
||||
## if scalar == check then
|
||||
## Break [check]
|
||||
## else
|
||||
## Continue (List.append state scalar)
|
||||
## expect Str.walkScalarsUntil "ABC" [] f == [66]
|
||||
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
|
||||
## ```
|
||||
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
|
||||
walkScalarsUntil = \string, init, step ->
|
||||
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
|
||||
|
||||
walkScalarsUntilHelp : Str, state, (state, U32 -> [Break state, Continue state]), Nat, Nat -> state
|
||||
walkScalarsUntilHelp = \string, state, step, index, length ->
|
||||
if index < length then
|
||||
{ scalar, bytesParsed } = getScalarUnsafe string index
|
||||
|
||||
when step state scalar is
|
||||
Continue newState ->
|
||||
walkScalarsUntilHelp string newState step (Num.addWrap index bytesParsed) length
|
||||
|
||||
Break newState ->
|
||||
newState
|
||||
else
|
||||
state
|
||||
|
||||
strToNum : Str -> { berrorcode : U8, aresult : Num * }
|
||||
|
||||
strToNumHelp : Str -> Result (Num a) [InvalidNumStr]
|
||||
|
@ -43,7 +43,7 @@ interface TotallyNotJson
|
||||
|
||||
## An opaque type with the `EncoderFormatting` and
|
||||
## `DecoderFormatting` abilities.
|
||||
Json := { fieldNameMapping : FieldNameMapping }
|
||||
Json := {}
|
||||
implements [
|
||||
EncoderFormatting {
|
||||
u8: encodeU8,
|
||||
@ -89,21 +89,11 @@ Json := { fieldNameMapping : FieldNameMapping }
|
||||
]
|
||||
|
||||
## Returns a JSON `Encoder` and `Decoder`
|
||||
json = @Json { fieldNameMapping: Default }
|
||||
json = @Json {}
|
||||
|
||||
## Returns a JSON `Encoder` and `Decoder` with configuration options
|
||||
jsonWithOptions = \{ fieldNameMapping ? Default } ->
|
||||
@Json { fieldNameMapping }
|
||||
|
||||
## Mapping between Roc record fields and JSON object names
|
||||
FieldNameMapping : [
|
||||
Default, # no transformation
|
||||
SnakeCase, # snake_case
|
||||
PascalCase, # PascalCase
|
||||
KebabCase, # kabab-case
|
||||
CamelCase, # camelCase
|
||||
Custom (Str -> Str), # provide a custom formatting
|
||||
]
|
||||
jsonWithOptions = \{} ->
|
||||
@Json {}
|
||||
|
||||
# TODO encode as JSON numbers as base 10 decimal digits
|
||||
# e.g. the REPL `Num.toStr 12e42f64` gives
|
||||
@ -171,14 +161,6 @@ encodeBool = \b ->
|
||||
else
|
||||
List.concat bytes (Str.toUtf8 "false")
|
||||
|
||||
# Test encode boolean
|
||||
expect
|
||||
input = [Bool.true, Bool.false]
|
||||
actual = Encode.toBytes input json
|
||||
expected = Str.toUtf8 "[true,false]"
|
||||
|
||||
actual == expected
|
||||
|
||||
encodeString = \str ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
List.concat bytes (encodeStrBytes str)
|
||||
@ -248,38 +230,10 @@ escapedByteToJson = \b ->
|
||||
0x09 -> [0x5c, 'r'] # U+0009 Tab
|
||||
_ -> [b]
|
||||
|
||||
expect escapedByteToJson '\n' == ['\\', 'n']
|
||||
expect escapedByteToJson '\\' == ['\\', '\\']
|
||||
expect escapedByteToJson '"' == ['\\', '"']
|
||||
|
||||
# Test encode small string
|
||||
expect
|
||||
input = "G'day"
|
||||
actual = Encode.toBytes input json
|
||||
expected = Str.toUtf8 "\"G'day\""
|
||||
|
||||
actual == expected
|
||||
|
||||
# Test encode large string
|
||||
expect
|
||||
input = "the quick brown fox jumps over the lazy dog"
|
||||
actual = Encode.toBytes input json
|
||||
expected = Str.toUtf8 "\"the quick brown fox jumps over the lazy dog\""
|
||||
|
||||
actual == expected
|
||||
|
||||
# Test encode with escapes e.g. "\r" encodes to "\\r"
|
||||
expect
|
||||
input = "the quick brown fox jumps over the lazy doga\r\nbc\\\"xz"
|
||||
actual = Encode.toBytes input json
|
||||
expected = Str.toUtf8 "\"the quick brown fox jumps over the lazy doga\\r\\nbc\\\\\\\"xz\""
|
||||
|
||||
actual == expected
|
||||
|
||||
encodeList = \lst, encodeElem ->
|
||||
Encode.custom \bytes, @Json { fieldNameMapping } ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeList = \{ buffer, elemsLeft }, elem ->
|
||||
bufferWithElem = appendWith buffer (encodeElem elem) (@Json { fieldNameMapping })
|
||||
bufferWithElem = appendWith buffer (encodeElem elem) (@Json {})
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
@ -293,27 +247,16 @@ encodeList = \lst, encodeElem ->
|
||||
|
||||
List.append withList (Num.toU8 ']')
|
||||
|
||||
# Test encode list of floats
|
||||
expect
|
||||
input : List F64
|
||||
input = [-1, 0.00001, 1e12, 2.0e-2, 0.0003, 43]
|
||||
actual = Encode.toBytes input json
|
||||
expected = Str.toUtf8 "[-1,0.00001,1000000000000,0.02,0.0003,43]"
|
||||
|
||||
actual == expected
|
||||
|
||||
encodeRecord = \fields ->
|
||||
Encode.custom \bytes, @Json { fieldNameMapping } ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeRecord = \{ buffer, fieldsLeft }, { key, value } ->
|
||||
|
||||
fieldName = toObjectNameUsingMap key fieldNameMapping
|
||||
|
||||
fieldName = key
|
||||
bufferWithKeyValue =
|
||||
List.append buffer (Num.toU8 '"')
|
||||
|> List.concat (Str.toUtf8 fieldName)
|
||||
|> List.append (Num.toU8 '"')
|
||||
|> List.append (Num.toU8 ':') # Note we need to encode using the json config here
|
||||
|> appendWith value (@Json { fieldNameMapping })
|
||||
|> appendWith value (@Json {})
|
||||
|
||||
bufferWithSuffix =
|
||||
if fieldsLeft > 1 then
|
||||
@ -328,52 +271,11 @@ encodeRecord = \fields ->
|
||||
|
||||
List.append bytesWithRecord (Num.toU8 '}')
|
||||
|
||||
# Test encode for a record with two strings ignoring whitespace
|
||||
expect
|
||||
input = { fruitCount: 2, ownerName: "Farmer Joe" }
|
||||
encoder = jsonWithOptions { fieldNameMapping: PascalCase }
|
||||
actual = Encode.toBytes input encoder
|
||||
expected = Str.toUtf8 "{\"FruitCount\":2,\"OwnerName\":\"Farmer Joe\"}"
|
||||
|
||||
actual == expected
|
||||
|
||||
# Test encode of record with an array of strings and a boolean field
|
||||
expect
|
||||
input = { fruitFlavours: ["Apples", "Bananas", "Pears"], isFresh: Bool.true }
|
||||
encoder = jsonWithOptions { fieldNameMapping: KebabCase }
|
||||
actual = Encode.toBytes input encoder
|
||||
expected = Str.toUtf8 "{\"fruit-flavours\":[\"Apples\",\"Bananas\",\"Pears\"],\"is-fresh\":true}"
|
||||
|
||||
actual == expected
|
||||
|
||||
# Test encode of record with a string and number field
|
||||
expect
|
||||
input = { firstSegment: "ab", secondSegment: 10u8 }
|
||||
encoder = jsonWithOptions { fieldNameMapping: SnakeCase }
|
||||
actual = Encode.toBytes input encoder
|
||||
expected = Str.toUtf8 "{\"first_segment\":\"ab\",\"second_segment\":10}"
|
||||
|
||||
actual == expected
|
||||
|
||||
# Test encode of record of a record
|
||||
expect
|
||||
input = { outer: { inner: "a" }, other: { one: "b", two: 10u8 } }
|
||||
encoder = jsonWithOptions { fieldNameMapping: Custom toYellingCase }
|
||||
actual = Encode.toBytes input encoder
|
||||
expected = Str.toUtf8 "{\"OTHER\":{\"ONE\":\"b\",\"TWO\":10},\"OUTER\":{\"INNER\":\"a\"}}"
|
||||
|
||||
actual == expected
|
||||
|
||||
toYellingCase = \str ->
|
||||
Str.graphemes str
|
||||
|> List.map toUppercase
|
||||
|> Str.joinWith ""
|
||||
|
||||
encodeTuple = \elems ->
|
||||
Encode.custom \bytes, @Json { fieldNameMapping } ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeTuple = \{ buffer, elemsLeft }, elemEncoder ->
|
||||
bufferWithElem =
|
||||
appendWith buffer elemEncoder (@Json { fieldNameMapping })
|
||||
appendWith buffer elemEncoder (@Json {})
|
||||
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
@ -387,20 +289,11 @@ encodeTuple = \elems ->
|
||||
{ buffer: bytesWithRecord } = List.walk elems { buffer: bytesHead, elemsLeft: List.len elems } writeTuple
|
||||
|
||||
List.append bytesWithRecord (Num.toU8 ']')
|
||||
|
||||
# Test encode of tuple
|
||||
expect
|
||||
input = ("The Answer is", 42)
|
||||
actual = Encode.toBytes input json
|
||||
expected = Str.toUtf8 "[\"The Answer is\",42]"
|
||||
|
||||
actual == expected
|
||||
|
||||
encodeTag = \name, payload ->
|
||||
Encode.custom \bytes, @Json { fieldNameMapping } ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
# Idea: encode `A v1 v2` as `{"A": [v1, v2]}`
|
||||
writePayload = \{ buffer, itemsLeft }, encoder ->
|
||||
bufferWithValue = appendWith buffer encoder (@Json { fieldNameMapping })
|
||||
bufferWithValue = appendWith buffer encoder (@Json {})
|
||||
bufferWithSuffix =
|
||||
if itemsLeft > 1 then
|
||||
List.append bufferWithValue (Num.toU8 ',')
|
||||
@ -422,15 +315,6 @@ encodeTag = \name, payload ->
|
||||
List.append bytesWithPayload (Num.toU8 ']')
|
||||
|> List.append (Num.toU8 '}')
|
||||
|
||||
# Test encode of tag
|
||||
expect
|
||||
input = TheAnswer "is" 42
|
||||
encoder = jsonWithOptions { fieldNameMapping: KebabCase }
|
||||
actual = Encode.toBytes input encoder
|
||||
expected = Str.toUtf8 "{\"TheAnswer\":[\"is\",42]}"
|
||||
|
||||
actual == expected
|
||||
|
||||
decodeU8 = Decode.custom \bytes, @Json {} ->
|
||||
{ taken, rest } = takeJsonNumber bytes
|
||||
|
||||
@ -1334,7 +1218,7 @@ expect
|
||||
|
||||
# JSON OBJECTS -----------------------------------------------------------------
|
||||
|
||||
decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Json { fieldNameMapping } ->
|
||||
decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Json {} ->
|
||||
|
||||
# Recursively build up record from object field:value pairs
|
||||
decodeFields = \recordState, bytesBeforeField ->
|
||||
@ -1361,8 +1245,7 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
|
||||
# Decode the json value
|
||||
{ val: updatedRecord, rest: bytesAfterValue } <-
|
||||
(
|
||||
fieldName =
|
||||
fromObjectNameUsingMap objectName fieldNameMapping
|
||||
fieldName = objectName
|
||||
|
||||
# Retrieve value decoder for the current field
|
||||
when stepField recordState fieldName is
|
||||
@ -1375,7 +1258,7 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
|
||||
Keep valueDecoder ->
|
||||
# Decode the value using the decoder from the recordState
|
||||
# Note we need to pass json config options recursively here
|
||||
Decode.decodeWith valueBytes valueDecoder (@Json { fieldNameMapping })
|
||||
Decode.decodeWith valueBytes valueDecoder (@Json {})
|
||||
)
|
||||
|> tryDecode
|
||||
|
||||
@ -1444,327 +1327,3 @@ ObjectState : [
|
||||
AfterClosingBrace Nat,
|
||||
InvalidObject,
|
||||
]
|
||||
|
||||
# Test decode of record with two strings ignoring whitespace
|
||||
expect
|
||||
input = Str.toUtf8 " {\n\"FruitCount\"\t:2\n, \"OwnerName\": \"Farmer Joe\" } "
|
||||
decoder = jsonWithOptions { fieldNameMapping: PascalCase }
|
||||
actual = Decode.fromBytesPartial input decoder
|
||||
expected = Ok { fruitCount: 2, ownerName: "Farmer Joe" }
|
||||
|
||||
actual.result == expected
|
||||
|
||||
# Test decode of record with an array of strings and a boolean field
|
||||
expect
|
||||
input = Str.toUtf8 "{\"fruit-flavours\": [\"Apples\",\"Bananas\",\"Pears\"], \"is-fresh\": true }"
|
||||
decoder = jsonWithOptions { fieldNameMapping: KebabCase }
|
||||
actual = Decode.fromBytesPartial input decoder
|
||||
expected = Ok { fruitFlavours: ["Apples", "Bananas", "Pears"], isFresh: Bool.true }
|
||||
|
||||
actual.result == expected
|
||||
|
||||
# Test decode of record with a string and number field
|
||||
expect
|
||||
input = Str.toUtf8 "{\"first_segment\":\"ab\",\"second_segment\":10}"
|
||||
decoder = jsonWithOptions { fieldNameMapping: SnakeCase }
|
||||
actual = Decode.fromBytesPartial input decoder
|
||||
expected = Ok { firstSegment: "ab", secondSegment: 10u8 }
|
||||
|
||||
actual.result == expected
|
||||
|
||||
# Test decode of record of a record
|
||||
expect
|
||||
input = Str.toUtf8 "{\"OUTER\":{\"INNER\":\"a\"},\"OTHER\":{\"ONE\":\"b\",\"TWO\":10}}"
|
||||
decoder = jsonWithOptions { fieldNameMapping: Custom fromYellingCase }
|
||||
actual = Decode.fromBytesPartial input decoder
|
||||
expected = Ok { outer: { inner: "a" }, other: { one: "b", two: 10u8 } }
|
||||
|
||||
actual.result == expected
|
||||
|
||||
fromYellingCase = \str ->
|
||||
Str.graphemes str
|
||||
|> List.map toLowercase
|
||||
|> Str.joinWith ""
|
||||
|
||||
expect fromYellingCase "YELLING" == "yelling"
|
||||
|
||||
# Complex example from IETF RFC 8259 (2017)
|
||||
complexExampleJson = Str.toUtf8 "{\"Image\":{\"Animated\":false,\"Height\":600,\"Ids\":[116,943,234,38793],\"Thumbnail\":{\"Height\":125,\"Url\":\"http:\\/\\/www.example.com\\/image\\/481989943\",\"Width\":100},\"Title\":\"View from 15th Floor\",\"Width\":800}}"
|
||||
complexExampleRecord = {
|
||||
image: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
title: "View from 15th Floor",
|
||||
thumbnail: {
|
||||
url: "http://www.example.com/image/481989943",
|
||||
height: 125,
|
||||
width: 100,
|
||||
},
|
||||
animated: Bool.false,
|
||||
ids: [116, 943, 234, 38793],
|
||||
},
|
||||
}
|
||||
|
||||
# Test decode of Complex Example
|
||||
expect
|
||||
input = complexExampleJson
|
||||
decoder = jsonWithOptions { fieldNameMapping: PascalCase }
|
||||
actual = Decode.fromBytes input decoder
|
||||
expected = Ok complexExampleRecord
|
||||
|
||||
actual == expected
|
||||
|
||||
# Test encode of Complex Example
|
||||
expect
|
||||
input = complexExampleRecord
|
||||
encoder = jsonWithOptions { fieldNameMapping: PascalCase }
|
||||
actual = Encode.toBytes input encoder
|
||||
expected = complexExampleJson
|
||||
|
||||
actual == expected
|
||||
|
||||
fromObjectNameUsingMap : Str, FieldNameMapping -> Str
|
||||
fromObjectNameUsingMap = \objectName, fieldNameMapping ->
|
||||
when fieldNameMapping is
|
||||
Default -> objectName
|
||||
SnakeCase -> fromSnakeCase objectName
|
||||
PascalCase -> fromPascalCase objectName
|
||||
KebabCase -> fromKebabCase objectName
|
||||
CamelCase -> fromCamelCase objectName
|
||||
Custom transformation -> transformation objectName
|
||||
|
||||
toObjectNameUsingMap : Str, FieldNameMapping -> Str
|
||||
toObjectNameUsingMap = \fieldName, fieldNameMapping ->
|
||||
when fieldNameMapping is
|
||||
Default -> fieldName
|
||||
SnakeCase -> toSnakeCase fieldName
|
||||
PascalCase -> toPascalCase fieldName
|
||||
KebabCase -> toKebabCase fieldName
|
||||
CamelCase -> toCamelCase fieldName
|
||||
Custom transformation -> transformation fieldName
|
||||
|
||||
# Convert a `snake_case` JSON Object name to a Roc Field name
|
||||
fromSnakeCase = \str ->
|
||||
snakeToCamel str
|
||||
|
||||
# Convert a `PascalCase` JSON Object name to a Roc Field name
|
||||
fromPascalCase = \str ->
|
||||
pascalToCamel str
|
||||
|
||||
# Convert a `kabab-case` JSON Object name to a Roc Field name
|
||||
fromKebabCase = \str ->
|
||||
kebabToCamel str
|
||||
|
||||
# Convert a `camelCase` JSON Object name to a Roc Field name
|
||||
fromCamelCase = \str ->
|
||||
# Nothing to change as Roc field names are camelCase by default
|
||||
str
|
||||
|
||||
# Convert a `camelCase` Roc Field name to a `snake_case` JSON Object name
|
||||
toSnakeCase = \str ->
|
||||
camelToSnake str
|
||||
|
||||
# Convert a `camelCase` Roc Field name to a `PascalCase` JSON Object name
|
||||
toPascalCase = \str ->
|
||||
camelToPascal str
|
||||
|
||||
# Convert a `camelCase` Roc Field name to a `kabab-case` JSON Object name
|
||||
toKebabCase = \str ->
|
||||
camelToKebeb str
|
||||
|
||||
# Convert a `camelCase` Roc Field name to a `camelCase` JSON Object name
|
||||
toCamelCase = \str ->
|
||||
# Nothing to change as Roc field names are camelCase by default
|
||||
str
|
||||
|
||||
snakeToCamel : Str -> Str
|
||||
snakeToCamel = \str ->
|
||||
segments = Str.split str "_"
|
||||
when segments is
|
||||
[first, ..] ->
|
||||
segments
|
||||
|> List.dropFirst 1
|
||||
|> List.map uppercaseFirst
|
||||
|> List.prepend first
|
||||
|> Str.joinWith ""
|
||||
|
||||
_ -> str
|
||||
|
||||
expect snakeToCamel "snake_case_string" == "snakeCaseString"
|
||||
|
||||
pascalToCamel : Str -> Str
|
||||
pascalToCamel = \str ->
|
||||
segments = Str.graphemes str
|
||||
when segments is
|
||||
[a, ..] ->
|
||||
first = toLowercase a
|
||||
rest = List.dropFirst segments 1
|
||||
|
||||
Str.joinWith (List.prepend rest first) ""
|
||||
|
||||
_ -> str
|
||||
|
||||
expect pascalToCamel "PascalCaseString" == "pascalCaseString"
|
||||
|
||||
kebabToCamel : Str -> Str
|
||||
kebabToCamel = \str ->
|
||||
segments = Str.split str "-"
|
||||
when segments is
|
||||
[first, ..] ->
|
||||
segments
|
||||
|> List.dropFirst 1
|
||||
|> List.map uppercaseFirst
|
||||
|> List.prepend first
|
||||
|> Str.joinWith ""
|
||||
|
||||
_ -> str
|
||||
|
||||
expect kebabToCamel "kebab-case-string" == "kebabCaseString"
|
||||
|
||||
camelToPascal : Str -> Str
|
||||
camelToPascal = \str ->
|
||||
segments = Str.graphemes str
|
||||
when segments is
|
||||
[a, ..] ->
|
||||
first = toUppercase a
|
||||
rest = List.dropFirst segments 1
|
||||
|
||||
Str.joinWith (List.prepend rest first) ""
|
||||
|
||||
_ -> str
|
||||
|
||||
expect camelToPascal "someCaseString" == "SomeCaseString"
|
||||
|
||||
camelToKebeb : Str -> Str
|
||||
camelToKebeb = \str ->
|
||||
rest = Str.graphemes str
|
||||
taken = List.withCapacity (List.len rest)
|
||||
|
||||
camelToKebabHelp { taken, rest }
|
||||
|> .taken
|
||||
|> Str.joinWith ""
|
||||
|
||||
camelToKebabHelp : { taken : List Str, rest : List Str } -> { taken : List Str, rest : List Str }
|
||||
camelToKebabHelp = \{ taken, rest } ->
|
||||
when rest is
|
||||
[] -> { taken, rest }
|
||||
[a, ..] if isUpperCase a ->
|
||||
camelToKebabHelp {
|
||||
taken: List.concat taken ["-", toLowercase a],
|
||||
rest: List.dropFirst rest 1,
|
||||
}
|
||||
|
||||
[a, ..] ->
|
||||
camelToKebabHelp {
|
||||
taken: List.append taken a,
|
||||
rest: List.dropFirst rest 1,
|
||||
}
|
||||
|
||||
expect camelToKebeb "someCaseString" == "some-case-string"
|
||||
|
||||
camelToSnake : Str -> Str
|
||||
camelToSnake = \str ->
|
||||
rest = Str.graphemes str
|
||||
taken = List.withCapacity (List.len rest)
|
||||
|
||||
camelToSnakeHelp { taken, rest }
|
||||
|> .taken
|
||||
|> Str.joinWith ""
|
||||
|
||||
camelToSnakeHelp : { taken : List Str, rest : List Str } -> { taken : List Str, rest : List Str }
|
||||
camelToSnakeHelp = \{ taken, rest } ->
|
||||
when rest is
|
||||
[] -> { taken, rest }
|
||||
[a, ..] if isUpperCase a ->
|
||||
camelToSnakeHelp {
|
||||
taken: List.concat taken ["_", toLowercase a],
|
||||
rest: List.dropFirst rest 1,
|
||||
}
|
||||
|
||||
[a, ..] ->
|
||||
camelToSnakeHelp {
|
||||
taken: List.append taken a,
|
||||
rest: List.dropFirst rest 1,
|
||||
}
|
||||
|
||||
expect camelToSnake "someCaseString" == "some_case_string"
|
||||
|
||||
uppercaseFirst : Str -> Str
|
||||
uppercaseFirst = \str ->
|
||||
segments = Str.graphemes str
|
||||
when segments is
|
||||
[a, ..] ->
|
||||
first = toUppercase a
|
||||
rest = List.dropFirst segments 1
|
||||
|
||||
Str.joinWith (List.prepend rest first) ""
|
||||
|
||||
_ -> str
|
||||
|
||||
toUppercase : Str -> Str
|
||||
toUppercase = \str ->
|
||||
when str is
|
||||
"a" -> "A"
|
||||
"b" -> "B"
|
||||
"c" -> "C"
|
||||
"d" -> "D"
|
||||
"e" -> "E"
|
||||
"f" -> "F"
|
||||
"g" -> "G"
|
||||
"h" -> "H"
|
||||
"i" -> "I"
|
||||
"j" -> "J"
|
||||
"k" -> "K"
|
||||
"l" -> "L"
|
||||
"m" -> "M"
|
||||
"n" -> "N"
|
||||
"o" -> "O"
|
||||
"p" -> "P"
|
||||
"q" -> "Q"
|
||||
"r" -> "R"
|
||||
"s" -> "S"
|
||||
"t" -> "T"
|
||||
"u" -> "U"
|
||||
"v" -> "V"
|
||||
"w" -> "W"
|
||||
"x" -> "X"
|
||||
"y" -> "Y"
|
||||
"z" -> "Z"
|
||||
_ -> str
|
||||
|
||||
toLowercase : Str -> Str
|
||||
toLowercase = \str ->
|
||||
when str is
|
||||
"A" -> "a"
|
||||
"B" -> "b"
|
||||
"C" -> "c"
|
||||
"D" -> "d"
|
||||
"E" -> "e"
|
||||
"F" -> "f"
|
||||
"G" -> "g"
|
||||
"H" -> "h"
|
||||
"I" -> "i"
|
||||
"J" -> "j"
|
||||
"K" -> "k"
|
||||
"L" -> "l"
|
||||
"M" -> "m"
|
||||
"N" -> "n"
|
||||
"O" -> "o"
|
||||
"P" -> "p"
|
||||
"Q" -> "q"
|
||||
"R" -> "r"
|
||||
"S" -> "s"
|
||||
"T" -> "t"
|
||||
"U" -> "u"
|
||||
"V" -> "v"
|
||||
"W" -> "w"
|
||||
"X" -> "x"
|
||||
"Y" -> "y"
|
||||
"Z" -> "z"
|
||||
_ -> str
|
||||
|
||||
isUpperCase : Str -> Bool
|
||||
isUpperCase = \str ->
|
||||
when str is
|
||||
"A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" -> Bool.true
|
||||
_ -> Bool.false
|
||||
|
@ -130,10 +130,10 @@ impl IntWidth {
|
||||
// according to https://reviews.llvm.org/D28990#655487
|
||||
//
|
||||
// however, rust does not always think that this is true
|
||||
// Our alignmets here are correct, but they will not match rust/zig/llvm until they update to llvm version 18.
|
||||
match target_info.architecture {
|
||||
Architecture::X86_64 => 16,
|
||||
Architecture::Aarch64 | Architecture::Aarch32 | Architecture::Wasm32 => 16,
|
||||
Architecture::X86_32 => 8,
|
||||
Architecture::X86_64 | Architecture::Aarch64 | Architecture::X86_32 => 16,
|
||||
Architecture::Aarch32 | Architecture::Wasm32 => 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -292,6 +292,10 @@ pub const NUM_FLOOR_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.floor_
|
||||
pub const NUM_FLOOR_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.floor_f64");
|
||||
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
|
||||
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
|
||||
pub const INT_TO_FLOAT_CAST_F32: IntrinsicName =
|
||||
int_intrinsic!("roc_builtins.num.num_to_float_cast_f32");
|
||||
pub const INT_TO_FLOAT_CAST_F64: IntrinsicName =
|
||||
int_intrinsic!("roc_builtins.num.num_to_float_cast_f64");
|
||||
|
||||
pub const NUM_ADD_OR_PANIC_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_or_panic");
|
||||
pub const NUM_ADD_SATURATED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_saturated");
|
||||
@ -341,13 +345,10 @@ pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
|
||||
pub const STR_CONCAT: &str = "roc_builtins.str.concat";
|
||||
pub const STR_JOIN_WITH: &str = "roc_builtins.str.joinWith";
|
||||
pub const STR_SPLIT: &str = "roc_builtins.str.str_split";
|
||||
pub const STR_TO_SCALARS: &str = "roc_builtins.str.to_scalars";
|
||||
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
|
||||
pub const STR_COUNT_UTF8_BYTES: &str = "roc_builtins.str.count_utf8_bytes";
|
||||
pub const STR_IS_EMPTY: &str = "roc_builtins.str.is_empty";
|
||||
pub const STR_CAPACITY: &str = "roc_builtins.str.capacity";
|
||||
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
|
||||
pub const STR_STARTS_WITH_SCALAR: &str = "roc_builtins.str.starts_with_scalar";
|
||||
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
||||
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
|
||||
pub const STR_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.from_int");
|
||||
@ -365,11 +366,8 @@ pub const STR_TRIM_START: &str = "roc_builtins.str.trim_start";
|
||||
pub const STR_TRIM_END: &str = "roc_builtins.str.trim_end";
|
||||
pub const STR_GET_UNSAFE: &str = "roc_builtins.str.get_unsafe";
|
||||
pub const STR_RESERVE: &str = "roc_builtins.str.reserve";
|
||||
pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar";
|
||||
pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe";
|
||||
pub const STR_CLONE_TO: &str = "roc_builtins.str.clone_to";
|
||||
pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity";
|
||||
pub const STR_GRAPHEMES: &str = "roc_builtins.str.graphemes";
|
||||
pub const STR_ALLOCATION_PTR: &str = "roc_builtins.str.allocation_ptr";
|
||||
pub const STR_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.str.release_excess_capacity";
|
||||
|
||||
@ -386,6 +384,7 @@ pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
|
||||
pub const LIST_REPLACE: &str = "roc_builtins.list.replace";
|
||||
pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
|
||||
pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique";
|
||||
pub const LIST_CLONE: &str = "roc_builtins.list.clone";
|
||||
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
|
||||
pub const LIST_APPEND_UNSAFE: &str = "roc_builtins.list.append_unsafe";
|
||||
pub const LIST_RESERVE: &str = "roc_builtins.list.reserve";
|
||||
@ -409,6 +408,7 @@ pub const DEC_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.dec.from_in
|
||||
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
|
||||
pub const DEC_FROM_U64: &str = "roc_builtins.dec.from_u64";
|
||||
pub const DEC_LOG: &str = "roc_builtins.dec.log";
|
||||
pub const DEC_POW: &str = "roc_builtins.dec.pow";
|
||||
pub const DEC_MUL_OR_PANIC: &str = "roc_builtins.dec.mul_or_panic";
|
||||
pub const DEC_MUL_SATURATED: &str = "roc_builtins.dec.mul_saturated";
|
||||
pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
|
||||
@ -421,6 +421,9 @@ pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
|
||||
pub const DEC_TAN: &str = "roc_builtins.dec.tan";
|
||||
pub const DEC_TO_I128: &str = "roc_builtins.dec.to_i128";
|
||||
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";
|
||||
pub const DEC_ROUND: IntrinsicName = int_intrinsic!("roc_builtins.dec.round");
|
||||
pub const DEC_FLOOR: IntrinsicName = int_intrinsic!("roc_builtins.dec.floor");
|
||||
pub const DEC_CEILING: IntrinsicName = int_intrinsic!("roc_builtins.dec.ceiling");
|
||||
|
||||
pub const UTILS_DBG_IMPL: &str = "roc_builtins.utils.dbg_impl";
|
||||
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
||||
@ -445,6 +448,9 @@ pub const NOTIFY_PARENT_EXPECT: &str = "roc_builtins.utils.notify_parent_expect"
|
||||
pub const UTILS_LONGJMP: &str = "longjmp";
|
||||
pub const UTILS_SETJMP: &str = "setjmp";
|
||||
|
||||
pub const UTILS_WINDOWS_SETJMP: &str = "windows_setjmp";
|
||||
pub const UTILS_WINDOWS_LONGJMP: &str = "windows_longjmp";
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IntToIntrinsicName {
|
||||
pub options: [IntrinsicName; 10],
|
||||
|
@ -115,10 +115,8 @@ map_symbol_to_lowlevel_and_arity! {
|
||||
StrJoinWith; STR_JOIN_WITH; 2,
|
||||
StrIsEmpty; STR_IS_EMPTY; 1,
|
||||
StrStartsWith; STR_STARTS_WITH; 2,
|
||||
StrStartsWithScalar; STR_STARTS_WITH_SCALAR; 2,
|
||||
StrEndsWith; STR_ENDS_WITH; 2,
|
||||
StrSplit; STR_SPLIT; 2,
|
||||
StrCountGraphemes; STR_COUNT_GRAPHEMES; 1,
|
||||
StrCountUtf8Bytes; STR_COUNT_UTF8_BYTES; 1,
|
||||
StrFromUtf8Range; STR_FROM_UTF8_RANGE_LOWLEVEL; 3,
|
||||
StrToUtf8; STR_TO_UTF8; 1,
|
||||
@ -126,22 +124,18 @@ map_symbol_to_lowlevel_and_arity! {
|
||||
StrTrim; STR_TRIM; 1,
|
||||
StrTrimStart; STR_TRIM_START; 1,
|
||||
StrTrimEnd; STR_TRIM_END; 1,
|
||||
StrToScalars; STR_TO_SCALARS; 1,
|
||||
StrGetUnsafe; STR_GET_UNSAFE; 2,
|
||||
StrSubstringUnsafe; STR_SUBSTRING_UNSAFE; 3,
|
||||
StrReserve; STR_RESERVE; 2,
|
||||
StrAppendScalar; STR_APPEND_SCALAR_UNSAFE; 2,
|
||||
StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2,
|
||||
StrToNum; STR_TO_NUM; 1,
|
||||
StrGetCapacity; STR_CAPACITY; 1,
|
||||
StrWithCapacity; STR_WITH_CAPACITY; 1,
|
||||
StrGraphemes; STR_GRAPHEMES; 1,
|
||||
StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1,
|
||||
|
||||
ListLen; LIST_LEN; 1,
|
||||
ListWithCapacity; LIST_WITH_CAPACITY; 1,
|
||||
ListReserve; LIST_RESERVE; 2,
|
||||
ListIsUnique; LIST_IS_UNIQUE; 1,
|
||||
ListClone; LIST_CLONE; 1,
|
||||
ListAppendUnsafe; LIST_APPEND_UNSAFE; 2,
|
||||
ListPrepend; LIST_PREPEND; 2,
|
||||
ListGetUnsafe; LIST_GET_UNSAFE; 2,
|
||||
@ -178,9 +172,9 @@ map_symbol_to_lowlevel_and_arity! {
|
||||
NumLte; NUM_LTE; 2,
|
||||
NumCompare; NUM_COMPARE; 2,
|
||||
NumDivFrac; NUM_DIV_FRAC; 2,
|
||||
NumDivTruncUnchecked; NUM_DIV_TRUNC; 2,
|
||||
NumDivTruncUnchecked; NUM_DIV_TRUNC_UNCHECKED; 2,
|
||||
NumDivCeilUnchecked; NUM_DIV_CEIL; 2,
|
||||
NumRemUnchecked; NUM_REM; 2,
|
||||
NumRemUnchecked; NUM_REM_UNCHECKED; 2,
|
||||
NumIsMultipleOf; NUM_IS_MULTIPLE_OF; 2,
|
||||
NumAbs; NUM_ABS; 1,
|
||||
NumNeg; NUM_NEG; 1,
|
||||
|
@ -2423,7 +2423,9 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
||||
| ast::StrSegment::Plaintext(_) => true,
|
||||
// Disallow nested interpolation. Alternatively, we could allow it but require
|
||||
// a comment above it apologizing to the next person who has to read the code.
|
||||
ast::StrSegment::Interpolated(_) => false,
|
||||
ast::StrSegment::Interpolated(_) | ast::StrSegment::DeprecatedInterpolated(_) => {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
ast::Expr::Record(fields) => fields.iter().all(|loc_field| match loc_field.value {
|
||||
@ -2550,7 +2552,7 @@ fn flatten_str_lines<'a>(
|
||||
);
|
||||
}
|
||||
},
|
||||
Interpolated(loc_expr) => {
|
||||
Interpolated(loc_expr) | DeprecatedInterpolated(loc_expr) => {
|
||||
if is_valid_interpolation(loc_expr.value) {
|
||||
// Interpolations desugar to Str.concat calls
|
||||
output.references.insert_call(Symbol::STR_CONCAT);
|
||||
@ -2598,10 +2600,38 @@ fn flatten_str_lines<'a>(
|
||||
fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) -> Expr {
|
||||
use StrSegment::*;
|
||||
|
||||
let n = segments.len();
|
||||
let mut iter = segments.into_iter().rev();
|
||||
let mut loc_expr = match iter.next() {
|
||||
Some(Plaintext(string)) => Loc::at(Region::zero(), Expr::Str(string)),
|
||||
Some(Interpolation(loc_expr)) => loc_expr,
|
||||
Some(Interpolation(loc_expr)) => {
|
||||
if n == 1 {
|
||||
// We concat with the empty string to ensure a type error when loc_expr is not a string
|
||||
let empty_string = Loc::at(Region::zero(), Expr::Str("".into()));
|
||||
|
||||
let fn_expr = Loc::at(
|
||||
Region::zero(),
|
||||
Expr::Var(Symbol::STR_CONCAT, var_store.fresh()),
|
||||
);
|
||||
let expr = Expr::Call(
|
||||
Box::new((
|
||||
var_store.fresh(),
|
||||
fn_expr,
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
)),
|
||||
vec![
|
||||
(var_store.fresh(), empty_string),
|
||||
(var_store.fresh(), loc_expr),
|
||||
],
|
||||
CalledVia::StringInterpolation,
|
||||
);
|
||||
|
||||
Loc::at(Region::zero(), expr)
|
||||
} else {
|
||||
loc_expr
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// No segments? Empty string!
|
||||
|
||||
|
@ -1051,14 +1051,13 @@ fn fix_values_captured_in_closure_expr(
|
||||
debug_assert!(!captures.is_empty());
|
||||
captured_symbols.extend(captures);
|
||||
captured_symbols.swap_remove(i);
|
||||
// Jump two, because the next element is now one of the newly-added captures,
|
||||
// which we don't need to check.
|
||||
i += 2;
|
||||
|
||||
added_captures = true;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Always jump one, because the current element either does not have captures or
|
||||
// is now one of the newly-added captures, which we don't need to check.
|
||||
i += 1;
|
||||
}
|
||||
if added_captures {
|
||||
// Re-sort, since we've added new captures.
|
||||
|
@ -594,7 +594,7 @@ pub fn desugar_expr<'a>(
|
||||
|
||||
// line_info is an option so that we can lazily calculate it.
|
||||
// That way it there are no `dbg` statements, we never pay the cast of scanning the source an extra time.
|
||||
if matches!(line_info, None) {
|
||||
if line_info.is_none() {
|
||||
*line_info = Some(LineInfo::new(src));
|
||||
}
|
||||
let line_col = line_info.as_ref().unwrap().convert_pos(region.start());
|
||||
@ -634,6 +634,22 @@ fn desugar_str_segments<'a>(
|
||||
StrSegment::Plaintext(_) | StrSegment::Unicode(_) | StrSegment::EscapedChar(_) => {
|
||||
*segment
|
||||
}
|
||||
StrSegment::DeprecatedInterpolated(loc_expr) => {
|
||||
let loc_desugared = desugar_expr(
|
||||
arena,
|
||||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: *loc_expr.value,
|
||||
}),
|
||||
src,
|
||||
line_info,
|
||||
module_path,
|
||||
);
|
||||
StrSegment::DeprecatedInterpolated(Loc {
|
||||
region: loc_desugared.region,
|
||||
value: arena.alloc(loc_desugared.value),
|
||||
})
|
||||
}
|
||||
StrSegment::Interpolated(loc_expr) => {
|
||||
let loc_desugared = desugar_expr(
|
||||
arena,
|
||||
|
@ -1071,7 +1071,7 @@ fn flatten_str_lines(lines: &[&[StrSegment<'_>]]) -> Pattern {
|
||||
Unicode(loc_digits) => {
|
||||
todo!("parse unicode digits {:?}", loc_digits);
|
||||
}
|
||||
Interpolated(loc_expr) => {
|
||||
Interpolated(loc_expr) | DeprecatedInterpolated(loc_expr) => {
|
||||
return Pattern::UnsupportedPattern(loc_expr.region);
|
||||
}
|
||||
EscapedChar(escaped) => buf.push(escaped.unescape()),
|
||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||
},
|
||||
pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DeclarationInfo<'a> {
|
||||
Value {
|
||||
loc_symbol: Loc<Symbol>,
|
||||
@ -164,7 +164,7 @@ pub fn walk_decls<V: Visitor>(visitor: &mut V, decls: &Declarations) {
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
|
||||
pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
|
||||
use DeclarationInfo::*;
|
||||
|
||||
match decl {
|
||||
|
@ -280,12 +280,12 @@ mod test_can {
|
||||
#[test]
|
||||
fn correct_annotated_body() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int * -> Num.Int *
|
||||
f = \ a -> a
|
||||
|
||||
f
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -296,12 +296,12 @@ mod test_can {
|
||||
#[test]
|
||||
fn correct_annotated_body_with_comments() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int * -> Num.Int * # comment
|
||||
f = \ a -> a
|
||||
|
||||
f
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -312,12 +312,12 @@ mod test_can {
|
||||
#[test]
|
||||
fn name_mismatch_annotated_body() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int * -> Num.Int *
|
||||
g = \ a -> a
|
||||
|
||||
g
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -340,12 +340,12 @@ mod test_can {
|
||||
#[test]
|
||||
fn name_mismatch_annotated_body_with_comment() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int * -> Num.Int * # comment
|
||||
g = \ a -> a
|
||||
|
||||
g
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -368,13 +368,13 @@ mod test_can {
|
||||
#[test]
|
||||
fn separated_annotated_body() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int * -> Num.Int *
|
||||
|
||||
f = \ a -> a
|
||||
|
||||
f 42
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -389,13 +389,13 @@ mod test_can {
|
||||
#[test]
|
||||
fn separated_annotated_body_with_comment() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int * -> Num.Int *
|
||||
# comment
|
||||
f = \ a -> a
|
||||
|
||||
f 42
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -410,13 +410,13 @@ mod test_can {
|
||||
#[test]
|
||||
fn shadowed_annotation() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int * -> Num.Int *
|
||||
|
||||
f : Num.Int * -> Num.Int *
|
||||
|
||||
f
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -432,7 +432,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn correct_nested_unannotated_body() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int *
|
||||
f =
|
||||
g = 42
|
||||
@ -440,7 +440,7 @@ mod test_can {
|
||||
g + 1
|
||||
|
||||
f
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -451,7 +451,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn correct_nested_annotated_body() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int *
|
||||
f =
|
||||
g : Num.Int *
|
||||
@ -460,7 +460,7 @@ mod test_can {
|
||||
g + 1
|
||||
|
||||
f
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -471,7 +471,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn correct_nested_body_annotated_multiple_lines() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int *
|
||||
f =
|
||||
g : Num.Int *
|
||||
@ -482,7 +482,7 @@ mod test_can {
|
||||
g + h + z
|
||||
|
||||
f
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -493,7 +493,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn correct_nested_body_unannotated_multiple_lines() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int *
|
||||
f =
|
||||
g = 42
|
||||
@ -503,7 +503,7 @@ mod test_can {
|
||||
g + h + z
|
||||
|
||||
f
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -513,7 +513,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn correct_double_nested_body() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f : Num.Int *
|
||||
f =
|
||||
g =
|
||||
@ -523,7 +523,7 @@ mod test_can {
|
||||
|
||||
|
||||
f
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -534,13 +534,13 @@ mod test_can {
|
||||
#[test]
|
||||
fn annotation_followed_with_unrelated_affectation() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
F : Str
|
||||
|
||||
x = 1
|
||||
|
||||
x
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -554,7 +554,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn two_annotations_followed_with_unrelated_affectation() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
G : Str
|
||||
|
||||
F : {}
|
||||
@ -562,7 +562,7 @@ mod test_can {
|
||||
x = 1
|
||||
|
||||
x
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -581,11 +581,11 @@ mod test_can {
|
||||
// // it only exists in the closure's arguments.
|
||||
// let arena = Bump::new();
|
||||
// let src = indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// func = \arg -> arg
|
||||
|
||||
// func 2
|
||||
// "#
|
||||
// "
|
||||
// );
|
||||
// let (_actual, output, problems, _var_store, _vars, _constraint) =
|
||||
// can_expr_with(&arena, test_home(), src);
|
||||
@ -608,13 +608,13 @@ mod test_can {
|
||||
// fn call_by_pointer_for_fn_args() {
|
||||
// // This function will get passed in as a pointer.
|
||||
// let src = indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// apply = \f, x -> f x
|
||||
|
||||
// identity = \a -> a
|
||||
|
||||
// apply identity 5
|
||||
// "#
|
||||
// "
|
||||
// );
|
||||
// let arena = Bump::new();
|
||||
// let (_actual, output, problems, _var_store, _vars, _constraint) =
|
||||
@ -637,9 +637,9 @@ mod test_can {
|
||||
#[test]
|
||||
fn incorrect_optional_value() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
{ x ? 42 }
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
@ -782,7 +782,7 @@ mod test_can {
|
||||
|
||||
number = "42"
|
||||
|
||||
succeed {
|
||||
succeed {
|
||||
number: <- parse number,
|
||||
raw: number,
|
||||
}
|
||||
@ -817,7 +817,7 @@ mod test_can {
|
||||
fn multiple_record_builders_error() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
succeed
|
||||
succeed
|
||||
{ a: <- apply "a" }
|
||||
{ b: <- apply "b" }
|
||||
"#
|
||||
@ -911,7 +911,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn recognize_tail_calls() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
g = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
@ -936,7 +936,7 @@ mod test_can {
|
||||
{ x: p, y: h }
|
||||
)
|
||||
)
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
@ -963,7 +963,7 @@ mod test_can {
|
||||
// #[test]
|
||||
// fn reproduce_incorrect_unused_defs() {
|
||||
// let src = indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// g = \x ->
|
||||
// when x is
|
||||
// 0 -> 0
|
||||
@ -983,7 +983,7 @@ mod test_can {
|
||||
// # variables must be (indirectly) referenced in the body for analysis to work
|
||||
// # { x: p, y: h }
|
||||
// g
|
||||
// "#
|
||||
// "
|
||||
// );
|
||||
// let arena = Bump::new();
|
||||
// let CanExprOut {
|
||||
@ -1012,14 +1012,14 @@ mod test_can {
|
||||
#[test]
|
||||
fn when_tail_call() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
g = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> g (x + 1)
|
||||
|
||||
g 0
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
@ -1034,11 +1034,11 @@ mod test_can {
|
||||
#[test]
|
||||
fn immediate_tail_call() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
f = \x -> f x
|
||||
|
||||
f 0
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
@ -1055,13 +1055,13 @@ mod test_can {
|
||||
#[test]
|
||||
fn when_condition_is_no_tail_call() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
q = \x ->
|
||||
when q x is
|
||||
_ -> 0
|
||||
|
||||
q 0
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
@ -1076,7 +1076,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn good_mutual_recursion() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
q = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
@ -1088,7 +1088,7 @@ mod test_can {
|
||||
_ -> q (x - 1)
|
||||
|
||||
q p
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
@ -1107,11 +1107,11 @@ mod test_can {
|
||||
#[test]
|
||||
fn valid_self_recursion() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
boom = \_ -> boom {}
|
||||
|
||||
boom
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
@ -1128,13 +1128,13 @@ mod test_can {
|
||||
#[test]
|
||||
fn invalid_mutual_recursion() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
x = y
|
||||
y = z
|
||||
z = x
|
||||
|
||||
x
|
||||
"#
|
||||
"
|
||||
);
|
||||
let home = test_home();
|
||||
let arena = Bump::new();
|
||||
@ -1176,11 +1176,11 @@ mod test_can {
|
||||
#[test]
|
||||
fn dict() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
x = Dict.empty {}
|
||||
|
||||
Dict.len x
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -1191,7 +1191,7 @@ mod test_can {
|
||||
#[test]
|
||||
fn unused_def_regression() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
Booly : [Yes, No, Maybe]
|
||||
|
||||
y : Booly
|
||||
@ -1205,7 +1205,7 @@ mod test_can {
|
||||
x = [y]
|
||||
|
||||
x
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -1216,14 +1216,14 @@ mod test_can {
|
||||
#[test]
|
||||
fn optional_field_not_unused() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
fallbackZ = 3
|
||||
|
||||
fn = \{ x, y, z ? fallbackZ } ->
|
||||
{ x, y, z }
|
||||
|
||||
fn { x: 0, y: 1 }
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -1234,12 +1234,12 @@ mod test_can {
|
||||
#[test]
|
||||
fn issue_2534() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
r"
|
||||
x = { a: 1 }
|
||||
{
|
||||
x & a: 2
|
||||
}
|
||||
"#
|
||||
"
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
@ -1252,13 +1252,13 @@ mod test_can {
|
||||
// // "local" should be used, because the closure used it.
|
||||
// // However, "unused" should be unused.
|
||||
// let (_, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// local = 5
|
||||
// unused = 6
|
||||
// func = \arg -> arg + local
|
||||
|
||||
// 3 + func 2
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(
|
||||
@ -1283,13 +1283,13 @@ mod test_can {
|
||||
//fn unused_closure() {
|
||||
// // "unused" should be unused because it's in func, which is unused.
|
||||
// let (_, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// local = 5
|
||||
// unused = 6
|
||||
// func = \arg -> arg + unused
|
||||
|
||||
// local
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(
|
||||
@ -1316,9 +1316,9 @@ mod test_can {
|
||||
// #[test]
|
||||
// fn basic_unrecognized_constant() {
|
||||
// let (expr, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// x
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(
|
||||
@ -1342,12 +1342,12 @@ mod test_can {
|
||||
//#[test]
|
||||
//fn complex_unrecognized_constant() {
|
||||
// let (_, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// a = 5
|
||||
// b = 6
|
||||
|
||||
// a + b * z
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(
|
||||
@ -1375,13 +1375,13 @@ mod test_can {
|
||||
// // This should report that both a and b are unused, since the return expr never references them.
|
||||
// // It should not report them as circular, since we haven't solved the halting problem here.
|
||||
// let (_, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// a = \arg -> if arg > 0 then b 7 else 0
|
||||
// b = \arg -> if arg > 0 then a (arg - 1) else 0
|
||||
// c = 5
|
||||
|
||||
// c
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(problems, vec![unused("a"), unused("b")]);
|
||||
@ -1400,7 +1400,7 @@ mod test_can {
|
||||
//#[test]
|
||||
//fn can_fibonacci() {
|
||||
// let (_, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// fibonacci = \num ->
|
||||
// if num < 2 then
|
||||
// num
|
||||
@ -1408,7 +1408,7 @@ mod test_can {
|
||||
// fibonacci (num - 1) + fibonacci (num - 2)
|
||||
|
||||
// fibonacci 9
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(problems, vec![]);
|
||||
@ -1430,7 +1430,7 @@ mod test_can {
|
||||
// // is considered a tail call, even though it only
|
||||
// // calls itself from one branch!
|
||||
// let (_, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// factorial = \num ->
|
||||
// factorialHelp num 0
|
||||
|
||||
@ -1441,7 +1441,7 @@ mod test_can {
|
||||
// factorialHelp (num - 1) (total * num)
|
||||
|
||||
// factorial 9
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(problems, vec![]);
|
||||
@ -1462,12 +1462,12 @@ mod test_can {
|
||||
// // This should report that neither a nor b are unused,
|
||||
// // since if you never call a function but do return it, that's okay!
|
||||
// let (_, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// a = \_ -> 42
|
||||
// b = a
|
||||
|
||||
// b
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(problems, Vec::new());
|
||||
@ -1488,14 +1488,14 @@ mod test_can {
|
||||
//#[test]
|
||||
//fn reorder_assignments() {
|
||||
// let (expr, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// increment = \arg -> arg + 1
|
||||
// z = (increment 2) + y
|
||||
// y = x + 1
|
||||
// x = 9
|
||||
|
||||
// z * 3
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(problems, vec![]);
|
||||
@ -1526,7 +1526,7 @@ mod test_can {
|
||||
//#[test]
|
||||
//fn reorder_closed_over_assignments() {
|
||||
// let (expr, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// z = func1 x
|
||||
// x = 9
|
||||
// y = func2 3
|
||||
@ -1534,7 +1534,7 @@ mod test_can {
|
||||
// func2 = \arg -> arg + x
|
||||
|
||||
// z
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(problems, vec![]);
|
||||
@ -1628,14 +1628,14 @@ mod test_can {
|
||||
//#[test]
|
||||
//fn circular_assignment() {
|
||||
// let (_, _, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// c = d + 3
|
||||
// b = 2 + c
|
||||
// d = a + 7
|
||||
// a = b + 1
|
||||
|
||||
// 2 + d
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(
|
||||
@ -1655,9 +1655,9 @@ mod test_can {
|
||||
// // There was a bug where this reported UnusedArgument("val")
|
||||
// // since it was used only in the returned function only.
|
||||
// let (_, _, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// r"
|
||||
// \val -> \_ -> val
|
||||
// "#
|
||||
// "
|
||||
// ));
|
||||
|
||||
// assert_eq!(problems, vec![]);
|
||||
@ -1822,18 +1822,18 @@ mod test_can {
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn string_with_escaped_interpolation() {
|
||||
// assert_parses_to(
|
||||
// // This should NOT be string interpolation, because of the \\
|
||||
// indoc!(
|
||||
// r#"
|
||||
// "abcd\\(efg)hij"
|
||||
// #[test]
|
||||
// fn string_with_escaped_interpolation() {
|
||||
// assert_parses_to(
|
||||
// // This should NOT be string interpolation, because of the \\
|
||||
// indoc!(
|
||||
// r#"
|
||||
// "abcd\$(efg)hij"
|
||||
// "#
|
||||
// ),
|
||||
// Str(r#"abcd\(efg)hij"#.into()),
|
||||
// );
|
||||
// }
|
||||
// ),
|
||||
// Str(r"abcd$(efg)hij".into()),
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn string_without_escape() {
|
||||
@ -1848,7 +1848,7 @@ mod test_can {
|
||||
|
||||
// #[test]
|
||||
// fn string_with_special_escapes() {
|
||||
// expect_parsed_str(r#"x\x"#, r#""x\\x""#);
|
||||
// expect_parsed_str(r"x\x", r#""x\\x""#);
|
||||
// expect_parsed_str(r#"x"x"#, r#""x\"x""#);
|
||||
// expect_parsed_str("x\tx", r#""x\tx""#);
|
||||
// expect_parsed_str("x\rx", r#""x\rx""#);
|
||||
|
17
crates/compiler/checkmate/www/.gitignore
vendored
17
crates/compiler/checkmate/www/.gitignore
vendored
@ -1,17 +0,0 @@
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
/coverage
|
||||
|
||||
/build
|
||||
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
@ -173,4 +173,7 @@ flags! {
|
||||
|
||||
/// Don't build and use the subs cache (speeds up compilation of load and previous crates)
|
||||
ROC_SKIP_SUBS_CACHE
|
||||
|
||||
/// Print out shell commands used to buid the Roc and host code
|
||||
ROC_PRINT_BUILD_COMMANDS
|
||||
}
|
||||
|
@ -612,9 +612,20 @@ fn format_str_segment(seg: &StrSegment, buf: &mut Buf, indent: u16) {
|
||||
buf.push('\\');
|
||||
buf.push(escaped.to_parsed_char());
|
||||
}
|
||||
Interpolated(loc_expr) => {
|
||||
DeprecatedInterpolated(loc_expr) => {
|
||||
buf.push_str("\\(");
|
||||
// e.g. (name) in "Hi, \(name)!"
|
||||
// e.g. (name) in "Hi, $(name)!"
|
||||
loc_expr.value.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded, // We already printed parens!
|
||||
Newlines::No, // Interpolations can never have newlines
|
||||
indent,
|
||||
);
|
||||
buf.push(')');
|
||||
}
|
||||
Interpolated(loc_expr) => {
|
||||
buf.push_str("$(");
|
||||
// e.g. (name) in "Hi, $(name)!"
|
||||
loc_expr.value.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded, // We already printed parens!
|
||||
|
@ -154,7 +154,9 @@ fn fmt_comment(buf: &mut Buf, comment: &str) {
|
||||
}
|
||||
|
||||
buf.push('#');
|
||||
if !comment.starts_with(' ') {
|
||||
// Add a space between the starting `#` and the rest of the comment,
|
||||
// unless there already is a space or the comment is of the form `#### something`.
|
||||
if !comment.starts_with(' ') && !comment.starts_with('#') {
|
||||
buf.spaces(1);
|
||||
}
|
||||
buf.push_str(comment.trim_end());
|
||||
@ -656,6 +658,9 @@ impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
|
||||
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
|
||||
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
|
||||
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
|
||||
StrSegment::DeprecatedInterpolated(t) => {
|
||||
StrSegment::DeprecatedInterpolated(t.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
#![allow(clippy::redundant_closure_call)]
|
||||
//|> clippy false positive: https://github.com/rust-lang/rust-clippy/issues/1553
|
||||
|
||||
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
|
||||
use crate::{
|
||||
pointer_layouts, single_register_floats, single_register_int_builtins,
|
||||
@ -1242,6 +1245,16 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
fabs_freg_freg(buf, FloatWidth::F64, dst, src);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn abs_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
_relocs: &mut Vec<'_, Relocation>,
|
||||
dst: AArch64FloatReg,
|
||||
src: AArch64FloatReg,
|
||||
) {
|
||||
fabs_freg_freg(buf, FloatWidth::F32, dst, src);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_imm32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -1266,6 +1279,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
) {
|
||||
add_reg64_reg64_reg64(buf, dst, src1, src2);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_freg32_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -1285,6 +1299,25 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
fadd_freg_freg_freg(buf, FloatWidth::F64, dst, src1, src2);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sub_freg32_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: AArch64FloatReg,
|
||||
src1: AArch64FloatReg,
|
||||
src2: AArch64FloatReg,
|
||||
) {
|
||||
fsub_freg_freg_freg(buf, FloatWidth::F32, dst, src1, src2);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn sub_freg64_freg64_freg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: AArch64FloatReg,
|
||||
src1: AArch64FloatReg,
|
||||
src2: AArch64FloatReg,
|
||||
) {
|
||||
fsub_freg_freg_freg(buf, FloatWidth::F64, dst, src1, src2);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String) {
|
||||
let inst = 0b1001_0100_0000_0000_0000_0000_0000_0000u32;
|
||||
@ -1638,6 +1671,11 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
Self::mov_freg64_mem64_offset32(buf, dst, AArch64GeneralReg::FP, offset)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_freg32_base32(buf: &mut Vec<'_, u8>, dst: AArch64FloatReg, offset: i32) {
|
||||
Self::mov_freg32_mem32_offset32(buf, dst, AArch64GeneralReg::FP, offset)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_reg_mem_offset32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -3894,6 +3932,27 @@ fn fadd_freg_freg_freg(
|
||||
buf.extend(inst.bytes());
|
||||
}
|
||||
|
||||
/// `FSUB Sd/Dd, Sn/Dn, Sm/Dm` -> Sub Sn/Dn and Sm/Dm and place the result into Sd/Dd.
|
||||
#[inline(always)]
|
||||
fn fsub_freg_freg_freg(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
ftype: FloatWidth,
|
||||
dst: AArch64FloatReg,
|
||||
src1: AArch64FloatReg,
|
||||
src2: AArch64FloatReg,
|
||||
) {
|
||||
let inst =
|
||||
FloatingPointDataProcessingTwoSource::new(FloatingPointDataProcessingTwoSourceParams {
|
||||
opcode: 0b0011,
|
||||
ptype: ftype,
|
||||
rd: dst,
|
||||
rn: src1,
|
||||
rm: src2,
|
||||
});
|
||||
|
||||
buf.extend(inst.bytes());
|
||||
}
|
||||
|
||||
/// `FCMP Sn/Dn, Sm/Dm` -> Compare Sn/Dn and Sm/Dm, setting condition flags.
|
||||
#[inline(always)]
|
||||
fn fcmp_freg_freg(
|
||||
|
@ -1,3 +1,6 @@
|
||||
#![allow(clippy::redundant_closure_call)]
|
||||
//|> clippy false positive: https://github.com/rust-lang/rust-clippy/issues/1553
|
||||
|
||||
pub fn merge_instructions_without_line_numbers(instructions: capstone::Instructions) -> String {
|
||||
instructions
|
||||
.iter()
|
||||
|
@ -164,8 +164,21 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
||||
dst: FloatReg,
|
||||
src: FloatReg,
|
||||
);
|
||||
fn abs_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
relocs: &mut Vec<'_, Relocation>,
|
||||
dst: FloatReg,
|
||||
src: FloatReg,
|
||||
);
|
||||
|
||||
fn add_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
|
||||
fn add_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src1: GeneralReg,
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn add_freg32_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: FloatReg,
|
||||
@ -178,12 +191,6 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
||||
src1: FloatReg,
|
||||
src2: FloatReg,
|
||||
);
|
||||
fn add_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src1: GeneralReg,
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn and_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -351,6 +358,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
||||
|
||||
// base32 is similar to stack based instructions but they reference the base/frame pointer.
|
||||
fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
|
||||
fn mov_freg32_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
|
||||
|
||||
fn mov_reg_base32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -629,6 +637,19 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
||||
ASM: Assembler<GeneralReg, FloatReg>,
|
||||
CC: CallConv<GeneralReg, FloatReg, ASM>;
|
||||
|
||||
fn sub_freg32_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: FloatReg,
|
||||
src1: FloatReg,
|
||||
src2: FloatReg,
|
||||
);
|
||||
fn sub_freg64_freg64_freg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: FloatReg,
|
||||
src1: FloatReg,
|
||||
src2: FloatReg,
|
||||
);
|
||||
|
||||
fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
|
||||
fn sub_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -1302,22 +1323,25 @@ impl<
|
||||
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
|
||||
ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
|
||||
ASM::abs_freg32_freg32(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
|
||||
}
|
||||
x => todo!("NumAbs: layout, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
|
||||
match self.layout_interner.get_repr(*layout) {
|
||||
LayoutRepr::Builtin(Builtin::Int(quadword_and_smaller!())) => {
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
let src1_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, src1);
|
||||
let src2_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, src2);
|
||||
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Int(int_width)) => self.build_fn_call(
|
||||
dst,
|
||||
bitcode::NUM_ADD_OR_PANIC_INT[int_width].to_string(),
|
||||
&[*src1, *src2],
|
||||
&[*layout, *layout],
|
||||
layout,
|
||||
),
|
||||
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
|
||||
@ -1330,16 +1354,60 @@ impl<
|
||||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::add_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Decimal) => {
|
||||
self.build_fn_call(
|
||||
dst,
|
||||
bitcode::DEC_ADD_OR_PANIC.to_string(),
|
||||
&[*src1, *src2],
|
||||
&[Layout::DEC, Layout::DEC],
|
||||
&Layout::DEC,
|
||||
);
|
||||
|
||||
LayoutRepr::DEC => self.build_fn_call(
|
||||
dst,
|
||||
bitcode::DEC_ADD_OR_PANIC.to_string(),
|
||||
&[*src1, *src2],
|
||||
&[Layout::DEC, Layout::DEC],
|
||||
&Layout::DEC,
|
||||
),
|
||||
|
||||
other => unreachable!("NumAdd for layout {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_add_wrap(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
layout: &InLayout<'a>,
|
||||
) {
|
||||
match self.layout_interner.get_repr(*layout) {
|
||||
LayoutRepr::Builtin(Builtin::Int(quadword_and_smaller!())) => {
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
let src1_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, src1);
|
||||
let src2_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, src2);
|
||||
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
x => todo!("NumAdd: layout, {:?}", x),
|
||||
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
|
||||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
|
||||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::add_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
|
||||
LayoutRepr::DEC => self.build_fn_call(
|
||||
dst,
|
||||
bitcode::DEC_ADD_SATURATED.to_string(),
|
||||
&[*src1, *src2],
|
||||
&[Layout::DEC, Layout::DEC],
|
||||
&Layout::DEC,
|
||||
),
|
||||
|
||||
other => unreachable!("NumAddWrap for layout {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1424,9 +1492,38 @@ impl<
|
||||
}
|
||||
|
||||
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
|
||||
// for the time being, `num_mul` is implemented as wrapping multiplication. In roc, the normal
|
||||
// `mul` should panic on overflow, but we just don't do that yet
|
||||
self.build_num_mul_wrap(dst, src1, src2, layout)
|
||||
match self.layout_interner.get_repr(*layout) {
|
||||
LayoutRepr::Builtin(Builtin::Int(int_width)) => self.build_fn_call(
|
||||
dst,
|
||||
bitcode::NUM_MUL_OR_PANIC_INT[int_width].to_string(),
|
||||
&[*src1, *src2],
|
||||
&[*layout, *layout],
|
||||
layout,
|
||||
),
|
||||
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
|
||||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::mul_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
|
||||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::mul_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
|
||||
LayoutRepr::DEC => self.build_fn_call(
|
||||
dst,
|
||||
bitcode::DEC_MUL_OR_PANIC.to_string(),
|
||||
&[*src1, *src2],
|
||||
&[Layout::DEC, Layout::DEC],
|
||||
&Layout::DEC,
|
||||
),
|
||||
|
||||
other => unreachable!("NumMul for layout {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_mul_wrap(
|
||||
@ -1609,6 +1706,15 @@ impl<
|
||||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::div_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Decimal) => {
|
||||
self.build_fn_call(
|
||||
dst,
|
||||
bitcode::DEC_DIV.to_string(),
|
||||
&[*src1, *src2],
|
||||
&[*layout, *layout],
|
||||
layout,
|
||||
);
|
||||
}
|
||||
x => todo!("NumDiv: layout, {:?}", x),
|
||||
}
|
||||
}
|
||||
@ -1688,9 +1794,38 @@ impl<
|
||||
}
|
||||
|
||||
fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
|
||||
// for the time being, `num_sub` is implemented as wrapping subtraction. In roc, the normal
|
||||
// `sub` should panic on overflow, but we just don't do that yet
|
||||
self.build_num_sub_wrap(dst, src1, src2, layout)
|
||||
match self.layout_interner.get_repr(*layout) {
|
||||
LayoutRepr::Builtin(Builtin::Int(int_width)) => self.build_fn_call(
|
||||
dst,
|
||||
bitcode::NUM_SUB_OR_PANIC_INT[int_width].to_string(),
|
||||
&[*src1, *src2],
|
||||
&[*layout, *layout],
|
||||
layout,
|
||||
),
|
||||
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
|
||||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::sub_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
|
||||
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
|
||||
ASM::sub_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
|
||||
LayoutRepr::DEC => self.build_fn_call(
|
||||
dst,
|
||||
bitcode::DEC_SUB_OR_PANIC.to_string(),
|
||||
&[*src1, *src2],
|
||||
&[Layout::DEC, Layout::DEC],
|
||||
&Layout::DEC,
|
||||
),
|
||||
|
||||
other => unreachable!("NumMul for layout {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_sub_wrap(
|
||||
@ -2020,6 +2155,30 @@ impl<
|
||||
);
|
||||
}
|
||||
|
||||
fn build_int_to_float_cast(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src: &Symbol,
|
||||
int_width: IntWidth,
|
||||
float_width: FloatWidth,
|
||||
) {
|
||||
use roc_builtins::bitcode::{INT_TO_FLOAT_CAST_F32, INT_TO_FLOAT_CAST_F64};
|
||||
|
||||
self.build_fn_call(
|
||||
dst,
|
||||
match float_width {
|
||||
FloatWidth::F32 => INT_TO_FLOAT_CAST_F32[int_width].to_string(),
|
||||
FloatWidth::F64 => INT_TO_FLOAT_CAST_F64[int_width].to_string(),
|
||||
},
|
||||
&[*src],
|
||||
&[Layout::from_int_width(int_width)],
|
||||
match float_width {
|
||||
FloatWidth::F32 => &Layout::F32,
|
||||
FloatWidth::F64 => &Layout::F64,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn build_num_cmp(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
@ -2675,6 +2834,55 @@ impl<
|
||||
self.storage_manager.list_len(&mut self.buf, dst, list);
|
||||
}
|
||||
|
||||
fn build_list_clone(
|
||||
&mut self,
|
||||
dst: Symbol,
|
||||
input_list: Symbol,
|
||||
elem_layout: InLayout<'a>,
|
||||
ret_layout: InLayout<'a>,
|
||||
) {
|
||||
// List alignment argument (u32).
|
||||
self.load_layout_alignment(ret_layout, Symbol::DEV_TMP);
|
||||
|
||||
// Load element_width argument (usize).
|
||||
self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2);
|
||||
|
||||
// Setup the return location.
|
||||
let base_offset =
|
||||
self.storage_manager
|
||||
.claim_stack_area_layout(self.layout_interner, dst, ret_layout);
|
||||
|
||||
let lowlevel_args = [
|
||||
input_list,
|
||||
// alignment
|
||||
Symbol::DEV_TMP,
|
||||
// element_width
|
||||
Symbol::DEV_TMP2,
|
||||
];
|
||||
let lowlevel_arg_layouts = [ret_layout, Layout::U32, Layout::U64];
|
||||
|
||||
self.build_fn_call(
|
||||
&Symbol::DEV_TMP3,
|
||||
bitcode::LIST_CLONE.to_string(),
|
||||
&lowlevel_args,
|
||||
&lowlevel_arg_layouts,
|
||||
&ret_layout,
|
||||
);
|
||||
self.free_symbol(&Symbol::DEV_TMP);
|
||||
self.free_symbol(&Symbol::DEV_TMP2);
|
||||
|
||||
// Copy from list to the output record.
|
||||
self.storage_manager.copy_symbol_to_stack_offset(
|
||||
self.layout_interner,
|
||||
&mut self.buf,
|
||||
base_offset,
|
||||
&Symbol::DEV_TMP3,
|
||||
&ret_layout,
|
||||
);
|
||||
|
||||
self.free_symbol(&Symbol::DEV_TMP3);
|
||||
}
|
||||
|
||||
fn build_list_with_capacity(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
@ -2741,7 +2949,11 @@ impl<
|
||||
self.load_layout_alignment(list_layout, Symbol::DEV_TMP);
|
||||
|
||||
// Load element_width argument (usize).
|
||||
self.load_layout_stack_size(*ret_layout, Symbol::DEV_TMP2);
|
||||
let element_layout = match self.interner().get_repr(*ret_layout) {
|
||||
LayoutRepr::Builtin(Builtin::List(e)) => e,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.load_layout_stack_size(element_layout, Symbol::DEV_TMP2);
|
||||
|
||||
// Load UpdateMode.Immutable argument (0u8)
|
||||
let u8_layout = Layout::U8;
|
||||
@ -3087,7 +3299,7 @@ impl<
|
||||
let elem_layout = arg_layouts[1];
|
||||
|
||||
// List alignment argument (u32).
|
||||
self.load_layout_alignment(*ret_layout, Symbol::DEV_TMP);
|
||||
self.load_layout_alignment(elem_layout, Symbol::DEV_TMP);
|
||||
|
||||
// Have to pass the input element by pointer, so put it on the stack and load it's address.
|
||||
self.storage_manager
|
||||
@ -4663,14 +4875,28 @@ impl<
|
||||
|
||||
// move a zero into the lower 8 bytes
|
||||
ASM::mov_reg64_imm64(buf, tmp_reg, 0x0);
|
||||
ASM::mov_base32_reg64(buf, base_offset, tmp_reg);
|
||||
ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
|
||||
|
||||
ASM::mov_base32_reg64(buf, base_offset + 8, src_reg);
|
||||
ASM::mov_base32_reg64(buf, base_offset, src_reg);
|
||||
|
||||
self.free_symbol(&tmp);
|
||||
|
||||
return;
|
||||
}
|
||||
(U128, I128) | (I128, U128) => {
|
||||
let to_offset = self.storage_manager.claim_stack_area_layout(
|
||||
self.layout_interner,
|
||||
*dst,
|
||||
Layout::from_int_width(target),
|
||||
);
|
||||
|
||||
let (from_offset, size) = self.storage_manager.stack_offset_and_size(src);
|
||||
|
||||
self.storage_manager
|
||||
.copy_to_stack_offset(buf, size, from_offset, to_offset);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
@ -4794,39 +5020,51 @@ impl<
|
||||
}
|
||||
|
||||
fn num_to_f32(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>) {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
match self.layout_interner.get_repr(*arg_layout) {
|
||||
LayoutRepr::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
|
||||
ASM::to_float_freg32_reg64(&mut self.buf, dst_reg, src_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
|
||||
ASM::to_float_freg32_freg64(&mut self.buf, dst_reg, src_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
|
||||
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Int(_)) => {
|
||||
let int_width = arg_layout.to_int_width();
|
||||
self.build_int_to_float_cast(dst, src, int_width, FloatWidth::F32);
|
||||
}
|
||||
arg => todo!("NumToFrac: layout, arg {arg:?}, ret {:?}", Layout::F32),
|
||||
}
|
||||
}
|
||||
|
||||
fn num_to_f64(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>) {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
match self.layout_interner.get_repr(*arg_layout) {
|
||||
LayoutRepr::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::I64)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
|
||||
ASM::to_float_freg64_reg64(&mut self.buf, dst_reg, src_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
|
||||
ASM::to_float_freg64_freg32(&mut self.buf, dst_reg, src_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
|
||||
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
|
||||
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg);
|
||||
}
|
||||
LayoutRepr::Builtin(Builtin::Int(_)) => {
|
||||
let int_width = arg_layout.to_int_width();
|
||||
self.build_int_to_float_cast(dst, src, int_width, FloatWidth::F64);
|
||||
}
|
||||
arg => todo!("NumToFrac: layout, arg {arg:?}, ret {:?}", Layout::F64),
|
||||
}
|
||||
}
|
||||
|
@ -448,17 +448,26 @@ impl<
|
||||
}
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
let reg = self.get_float_reg(buf);
|
||||
ASM::mov_freg64_base32(buf, reg, base_offset);
|
||||
self.float_used_regs.push((reg, *sym));
|
||||
self.symbol_storage_map.insert(*sym, Reg(Float(reg)));
|
||||
self.free_reference(sym);
|
||||
reg
|
||||
}
|
||||
Stack(ReferencedPrimitive { .. }) => {
|
||||
todo!("loading referenced primitives")
|
||||
}) => {
|
||||
if base_offset % 8 == 0 && size == 8 {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
let reg = self.get_float_reg(buf);
|
||||
ASM::mov_freg64_base32(buf, reg, base_offset);
|
||||
self.float_used_regs.push((reg, *sym));
|
||||
self.symbol_storage_map.insert(*sym, Reg(Float(reg)));
|
||||
self.free_reference(sym);
|
||||
reg
|
||||
} else if base_offset % 4 == 0 && size == 4 {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
let reg = self.get_float_reg(buf);
|
||||
ASM::mov_freg32_base32(buf, reg, base_offset);
|
||||
self.float_used_regs.push((reg, *sym));
|
||||
self.symbol_storage_map.insert(*sym, Reg(Float(reg)));
|
||||
self.free_reference(sym);
|
||||
reg
|
||||
} else {
|
||||
todo!("loading referenced primitives")
|
||||
}
|
||||
}
|
||||
Stack(Complex { .. }) => {
|
||||
internal_error!("Cannot load large values into float registers: {}", sym)
|
||||
@ -570,12 +579,15 @@ impl<
|
||||
}
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && *size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
ASM::mov_freg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
Stack(ReferencedPrimitive { .. }) => {
|
||||
todo!("loading referenced primitives")
|
||||
}) => {
|
||||
if base_offset % 8 == 0 && *size == 8 {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
ASM::mov_freg64_base32(buf, reg, *base_offset);
|
||||
} else if base_offset % 4 == 0 && *size == 4 {
|
||||
ASM::mov_freg32_base32(buf, reg, *base_offset);
|
||||
} else {
|
||||
todo!("loading referenced primitives")
|
||||
}
|
||||
}
|
||||
Stack(Complex { .. }) => {
|
||||
internal_error!("Cannot load large values into float registers: {}", sym)
|
||||
|
@ -1,3 +1,6 @@
|
||||
#![allow(clippy::redundant_closure_call)]
|
||||
//|> clippy false positive: https://github.com/rust-lang/rust-clippy/issues/1553
|
||||
|
||||
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
|
||||
use crate::{
|
||||
pointer_layouts, single_register_floats, single_register_int_builtins,
|
||||
@ -523,12 +526,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
||||
use X86_64GeneralReg::*;
|
||||
type ASM = X86_64Assembler;
|
||||
|
||||
// move the first argument to roc_panic (a *RocStr) into r8
|
||||
ASM::add_reg64_reg64_imm32(buf, R8, RSP, 8);
|
||||
// move the first argument to roc_panic (a *const RocStr) into r8
|
||||
ASM::mov_reg64_reg64(buf, R8, RDI);
|
||||
|
||||
// move the crash tag into the second return register. We add 1 to it because the 0 value
|
||||
// is already used for "no crash occurred"
|
||||
ASM::add_reg64_reg64_imm32(buf, RDX, RDI, 1);
|
||||
ASM::add_reg64_reg64_imm32(buf, RDX, RSI, 1);
|
||||
|
||||
// the setlongjmp_buffer
|
||||
ASM::data_pointer(buf, relocs, String::from("setlongjmp_buffer"), RDI);
|
||||
@ -1955,6 +1958,24 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
||||
andpd_freg64_freg64(buf, dst, src);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn abs_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
relocs: &mut Vec<'_, Relocation>,
|
||||
dst: X86_64FloatReg,
|
||||
src: X86_64FloatReg,
|
||||
) {
|
||||
movss_freg32_rip_offset32(buf, dst, 0);
|
||||
|
||||
// TODO: make sure this constant only loads once instead of every call to abs
|
||||
relocs.push(Relocation::LocalData {
|
||||
offset: buf.len() as u64 - 4,
|
||||
data: 0x7fffffffu64.to_le_bytes().to_vec(),
|
||||
});
|
||||
|
||||
andps_freg32_freg32(buf, dst, src);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_imm32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -2004,6 +2025,39 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sub_freg32_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64FloatReg,
|
||||
src1: X86_64FloatReg,
|
||||
src2: X86_64FloatReg,
|
||||
) {
|
||||
if dst == src1 {
|
||||
subss_freg32_freg32(buf, dst, src2);
|
||||
} else if dst == src2 {
|
||||
subss_freg32_freg32(buf, dst, src1);
|
||||
} else {
|
||||
movss_freg32_freg32(buf, dst, src1);
|
||||
subss_freg32_freg32(buf, dst, src2);
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn sub_freg64_freg64_freg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64FloatReg,
|
||||
src1: X86_64FloatReg,
|
||||
src2: X86_64FloatReg,
|
||||
) {
|
||||
if dst == src1 {
|
||||
subsd_freg64_freg64(buf, dst, src2);
|
||||
} else if dst == src2 {
|
||||
subsd_freg64_freg64(buf, dst, src1);
|
||||
} else {
|
||||
movsd_freg64_freg64(buf, dst, src1);
|
||||
subsd_freg64_freg64(buf, dst, src2);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String) {
|
||||
buf.extend([0xE8, 0x00, 0x00, 0x00, 0x00]);
|
||||
@ -2382,6 +2436,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
||||
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_freg32_base32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
|
||||
movss_freg32_base32_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_reg_base32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -3052,124 +3111,78 @@ fn sar_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) {
|
||||
buf.extend([rex, 0xD3, 0xC0 | (7 << 3) | dst_mod]);
|
||||
}
|
||||
|
||||
/// `ADDSD xmm1,xmm2/m64` -> Add the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
fn double_binary_operation(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64FloatReg,
|
||||
src: X86_64FloatReg,
|
||||
float_width: FloatWidth,
|
||||
op_code2: u8,
|
||||
) {
|
||||
let op_code1 = match float_width {
|
||||
FloatWidth::F32 => 0xF3,
|
||||
FloatWidth::F64 => 0xF2,
|
||||
};
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
let src_mod = src as u8 % 8;
|
||||
if dst_high || src_high {
|
||||
buf.extend([
|
||||
0xF2,
|
||||
op_code1,
|
||||
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
|
||||
0x0F,
|
||||
0x58,
|
||||
op_code2,
|
||||
0xC0 | (dst_mod << 3) | (src_mod),
|
||||
])
|
||||
} else {
|
||||
buf.extend([0xF2, 0x0F, 0x58, 0xC0 | (dst_mod << 3) | (src_mod)])
|
||||
buf.extend([op_code1, 0x0F, op_code2, 0xC0 | (dst_mod << 3) | (src_mod)])
|
||||
}
|
||||
}
|
||||
|
||||
/// `ADDSD xmm1,xmm2/m64` -> Add the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
double_binary_operation(buf, dst, src, FloatWidth::F64, 0x58)
|
||||
}
|
||||
|
||||
/// `ADDSS xmm1,xmm2/m64` -> Add the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn addss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
let src_mod = src as u8 % 8;
|
||||
if dst_high || src_high {
|
||||
buf.extend([
|
||||
0xF3,
|
||||
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
|
||||
0x0F,
|
||||
0x58,
|
||||
0xC0 | (dst_mod << 3) | (src_mod),
|
||||
])
|
||||
} else {
|
||||
buf.extend([0xF3, 0x0F, 0x58, 0xC0 | (dst_mod << 3) | (src_mod)])
|
||||
}
|
||||
double_binary_operation(buf, dst, src, FloatWidth::F32, 0x58)
|
||||
}
|
||||
|
||||
/// `SUBSD xmm1,xmm2/m64` -> Sub the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn subsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
double_binary_operation(buf, dst, src, FloatWidth::F64, 0x5C)
|
||||
}
|
||||
|
||||
/// `SUBSS xmm1,xmm2/m64` -> Sub the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn subss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
double_binary_operation(buf, dst, src, FloatWidth::F32, 0x5C)
|
||||
}
|
||||
|
||||
/// `MULSD xmm1,xmm2/m64` -> Multiply the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn mulsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
let src_mod = src as u8 % 8;
|
||||
if dst_high || src_high {
|
||||
buf.extend([
|
||||
0xF2,
|
||||
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
|
||||
0x0F,
|
||||
0x59,
|
||||
0xC0 | (dst_mod << 3) | (src_mod),
|
||||
])
|
||||
} else {
|
||||
buf.extend([0xF2, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
|
||||
}
|
||||
double_binary_operation(buf, dst, src, FloatWidth::F64, 0x59)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mulss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
double_binary_operation(buf, dst, src, FloatWidth::F32, 0x59)
|
||||
}
|
||||
|
||||
/// `DIVSS xmm1,xmm2/m64` -> Divide the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn divss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
let src_mod = src as u8 % 8;
|
||||
if dst_high || src_high {
|
||||
buf.extend([
|
||||
0xF3,
|
||||
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
|
||||
0x0F,
|
||||
0x5E,
|
||||
0xC0 | (dst_mod << 3) | (src_mod),
|
||||
])
|
||||
} else {
|
||||
buf.extend([0xF3, 0x0F, 0x5E, 0xC0 | (dst_mod << 3) | (src_mod)])
|
||||
}
|
||||
double_binary_operation(buf, dst, src, FloatWidth::F32, 0x5E)
|
||||
}
|
||||
|
||||
/// `DIVSD xmm1,xmm2/m64` -> Divide the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn divsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
let src_mod = src as u8 % 8;
|
||||
if dst_high || src_high {
|
||||
buf.extend([
|
||||
0xF2,
|
||||
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
|
||||
0x0F,
|
||||
0x5E,
|
||||
0xC0 | (dst_mod << 3) | (src_mod),
|
||||
])
|
||||
} else {
|
||||
buf.extend([0xF2, 0x0F, 0x5E, 0xC0 | (dst_mod << 3) | (src_mod)])
|
||||
}
|
||||
}
|
||||
|
||||
/// `ADDSS xmm1,xmm2/m64` -> Add the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
|
||||
#[inline(always)]
|
||||
fn mulss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
let src_mod = src as u8 % 8;
|
||||
if dst_high || src_high {
|
||||
buf.extend([
|
||||
0xF3,
|
||||
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
|
||||
0x0F,
|
||||
0x59,
|
||||
0xC0 | (dst_mod << 3) | (src_mod),
|
||||
])
|
||||
} else {
|
||||
buf.extend([0xF3, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
|
||||
}
|
||||
double_binary_operation(buf, dst, src, FloatWidth::F64, 0x5E)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -3192,6 +3205,25 @@ fn andpd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64Fl
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn andps_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
let src_mod = src as u8 % 8;
|
||||
|
||||
if dst_high || src_high {
|
||||
buf.extend([
|
||||
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
|
||||
0x0F,
|
||||
0x54,
|
||||
0xC0 | (dst_mod << 3) | (src_mod),
|
||||
])
|
||||
} else {
|
||||
buf.extend([0x0F, 0x54, 0xC0 | (dst_mod << 3) | (src_mod)])
|
||||
}
|
||||
}
|
||||
|
||||
/// r/m64 AND imm8 (sign-extended).
|
||||
#[inline(always)]
|
||||
fn and_reg64_imm8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, imm: i8) {
|
||||
@ -4496,6 +4528,16 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_andps_freg32_freg32() {
|
||||
disassembler_test!(
|
||||
andps_freg32_freg32,
|
||||
|reg1, reg2| format!("andps {reg1}, {reg2}"),
|
||||
ALL_FLOAT_REGS,
|
||||
ALL_FLOAT_REGS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_and_reg64_reg64() {
|
||||
disassembler_test!(
|
||||
|
@ -358,27 +358,21 @@ trait Backend<'a> {
|
||||
where
|
||||
I: Iterator<Item = InLayout<'b>>,
|
||||
{
|
||||
use std::fmt::Write;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
|
||||
let symbol = name.name();
|
||||
|
||||
let mut buf = String::with_capacity(1024);
|
||||
|
||||
// NOTE: due to randomness, this will not be consistent between runs
|
||||
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
|
||||
for a in arguments {
|
||||
write!(buf, "{:?}", self.interner().dbg_stable(a)).expect("capacity");
|
||||
a.hash(&mut state);
|
||||
}
|
||||
|
||||
// lambda set should not matter; it should already be added as an argument
|
||||
// but the niche of the lambda name may be the only thing differentiating two different
|
||||
// implementations of a function with the same symbol
|
||||
write!(buf, "{:?}", name.niche().dbg_stable(self.interner())).expect("capacity");
|
||||
|
||||
write!(buf, "{:?}", self.interner().dbg_stable(result)).expect("capacity");
|
||||
|
||||
// NOTE: due to randomness, this will not be consistent between runs
|
||||
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
|
||||
buf.hash(&mut state);
|
||||
name.niche().hash(&mut state);
|
||||
result.hash(&mut state);
|
||||
|
||||
let interns = self.interns();
|
||||
let ident_string = symbol.as_str(interns);
|
||||
@ -668,14 +662,22 @@ trait Backend<'a> {
|
||||
&Literal::Int((crash_tag as u128).to_ne_bytes()),
|
||||
);
|
||||
|
||||
// this function gets a RocStr, but the roc_panic defined by a platform expects a `*RocStr`.
|
||||
// we store the value in a global variable and then use a pointer to this global
|
||||
let panic_msg_ptr = self.debug_symbol("panic_msg_ptr");
|
||||
let ignored = self.debug_symbol("ignored");
|
||||
self.build_data_pointer(&panic_msg_ptr, "panic_msg".to_string());
|
||||
self.load_literal_symbols(&[msg]);
|
||||
self.build_ptr_store(ignored, panic_msg_ptr, msg, Layout::STR);
|
||||
|
||||
// Now that the arguments are needed, load them if they are literals.
|
||||
let arguments = &[msg, error_message];
|
||||
let arguments = &[panic_msg_ptr, error_message];
|
||||
self.load_literal_symbols(arguments);
|
||||
self.build_fn_call(
|
||||
&Symbol::DEV_TMP2,
|
||||
String::from("roc_panic"),
|
||||
arguments,
|
||||
&[Layout::STR, Layout::U32],
|
||||
&[Layout::U64, Layout::U32],
|
||||
&Layout::UNIT,
|
||||
);
|
||||
|
||||
@ -1002,7 +1004,7 @@ trait Backend<'a> {
|
||||
arg_layouts[0], *ret_layout,
|
||||
"NumAdd: expected to have the same argument and return layout"
|
||||
);
|
||||
self.build_num_add(sym, &args[0], &args[1], ret_layout)
|
||||
self.build_num_add_wrap(sym, &args[0], &args[1], ret_layout)
|
||||
}
|
||||
LowLevel::NumAddChecked => {
|
||||
self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
|
||||
@ -1072,13 +1074,80 @@ trait Backend<'a> {
|
||||
);
|
||||
self.build_num_neg(sym, &args[0], ret_layout)
|
||||
}
|
||||
LowLevel::NumPowInt => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_POW_INT[IntWidth::I64].to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::NumPowInt => {
|
||||
let repr = self.interner().get_repr(arg_layouts[0]);
|
||||
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
|
||||
unreachable!("invalid layout for NumPowInt")
|
||||
};
|
||||
|
||||
self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_POW_INT[int_width].to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
)
|
||||
}
|
||||
LowLevel::NumPow => {
|
||||
let intrinsic = match self.interner().get_repr(arg_layouts[0]) {
|
||||
LayoutRepr::Builtin(Builtin::Float(float_width)) => {
|
||||
&bitcode::NUM_POW[float_width]
|
||||
}
|
||||
LayoutRepr::DEC => bitcode::DEC_POW,
|
||||
_ => unreachable!("invalid layout for NumPow"),
|
||||
};
|
||||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
|
||||
LowLevel::NumRound => {
|
||||
let repr = self.interner().get_repr(*ret_layout);
|
||||
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
|
||||
unreachable!("invalid return layout for NumRound")
|
||||
};
|
||||
|
||||
let intrinsic = match arg_layouts[0] {
|
||||
Layout::F32 => &bitcode::NUM_ROUND_F32[int_width],
|
||||
Layout::F64 => &bitcode::NUM_ROUND_F64[int_width],
|
||||
Layout::DEC => &bitcode::DEC_ROUND[int_width],
|
||||
_ => unreachable!("invalid layout for NumRound"),
|
||||
};
|
||||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
|
||||
LowLevel::NumFloor => {
|
||||
let repr = self.interner().get_repr(*ret_layout);
|
||||
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
|
||||
unreachable!("invalid return layout for NumFloor")
|
||||
};
|
||||
|
||||
let intrinsic = match arg_layouts[0] {
|
||||
Layout::F32 => &bitcode::NUM_FLOOR_F32[int_width],
|
||||
Layout::F64 => &bitcode::NUM_FLOOR_F64[int_width],
|
||||
Layout::DEC => &bitcode::DEC_FLOOR[int_width],
|
||||
_ => unreachable!("invalid layout for NumFloor"),
|
||||
};
|
||||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
|
||||
LowLevel::NumCeiling => {
|
||||
let repr = self.interner().get_repr(*ret_layout);
|
||||
let LayoutRepr::Builtin(Builtin::Int(int_width)) = repr else {
|
||||
unreachable!("invalid return layout for NumCeiling")
|
||||
};
|
||||
|
||||
let intrinsic = match arg_layouts[0] {
|
||||
Layout::F32 => &bitcode::NUM_CEILING_F32[int_width],
|
||||
Layout::F64 => &bitcode::NUM_CEILING_F64[int_width],
|
||||
Layout::DEC => &bitcode::DEC_CEILING[int_width],
|
||||
_ => unreachable!("invalid layout for NumCeiling"),
|
||||
};
|
||||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
|
||||
LowLevel::NumSub => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
@ -1385,13 +1454,36 @@ trait Backend<'a> {
|
||||
|
||||
self.build_num_sqrt(*sym, args[0], float_width);
|
||||
}
|
||||
LowLevel::NumRound => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::NumSin => {
|
||||
let intrinsic = match arg_layouts[0] {
|
||||
Layout::F64 => &bitcode::NUM_SIN[FloatWidth::F64],
|
||||
Layout::F32 => &bitcode::NUM_SIN[FloatWidth::F32],
|
||||
Layout::DEC => bitcode::DEC_SIN,
|
||||
_ => unreachable!("invalid layout for sin"),
|
||||
};
|
||||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
LowLevel::NumCos => {
|
||||
let intrinsic = match arg_layouts[0] {
|
||||
Layout::F64 => &bitcode::NUM_COS[FloatWidth::F64],
|
||||
Layout::F32 => &bitcode::NUM_COS[FloatWidth::F32],
|
||||
Layout::DEC => bitcode::DEC_COS,
|
||||
_ => unreachable!("invalid layout for cos"),
|
||||
};
|
||||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
LowLevel::NumTan => {
|
||||
let intrinsic = match arg_layouts[0] {
|
||||
Layout::F64 => &bitcode::NUM_TAN[FloatWidth::F64],
|
||||
Layout::F32 => &bitcode::NUM_TAN[FloatWidth::F32],
|
||||
Layout::DEC => bitcode::DEC_TAN,
|
||||
_ => unreachable!("invalid layout for tan"),
|
||||
};
|
||||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
LowLevel::ListLen => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
@ -1409,6 +1501,15 @@ trait Backend<'a> {
|
||||
let elem_layout = list_element_layout!(self.interner(), *ret_layout);
|
||||
self.build_list_with_capacity(sym, args[0], arg_layouts[0], elem_layout, ret_layout)
|
||||
}
|
||||
LowLevel::ListClone => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
args.len(),
|
||||
"ListClone: expected to have exactly one argument"
|
||||
);
|
||||
let elem_layout = list_element_layout!(self.interner(), *ret_layout);
|
||||
self.build_list_clone(*sym, args[0], elem_layout, *ret_layout)
|
||||
}
|
||||
LowLevel::ListReserve => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
@ -1486,20 +1587,6 @@ trait Backend<'a> {
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrStartsWithScalar => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_STARTS_WITH_SCALAR.to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrAppendScalar => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_APPEND_SCALAR.to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrEndsWith => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_ENDS_WITH.to_string(),
|
||||
@ -1507,13 +1594,6 @@ trait Backend<'a> {
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrCountGraphemes => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_COUNT_GRAPEHEME_CLUSTERS.to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrSubstringUnsafe => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_SUBSTRING_UNSAFE.to_string(),
|
||||
@ -1589,13 +1669,6 @@ trait Backend<'a> {
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrToScalars => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_TO_SCALARS.to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrGetUnsafe => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_GET_UNSAFE.to_string(),
|
||||
@ -1603,13 +1676,6 @@ trait Backend<'a> {
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrGetScalarUnsafe => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_GET_SCALAR_UNSAFE.to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::StrToNum => {
|
||||
let number_layout = match self.interner().get_repr(*ret_layout) {
|
||||
LayoutRepr::Struct(field_layouts) => field_layouts[0], // TODO: why is it sometimes a struct?
|
||||
@ -1936,6 +2002,23 @@ trait Backend<'a> {
|
||||
self.build_num_cmp(sym, &args[0], &args[1], &arg_layouts[0]);
|
||||
}
|
||||
|
||||
LowLevel::NumToFloatCast => {
|
||||
let float_width = match *ret_layout {
|
||||
Layout::F64 => FloatWidth::F64,
|
||||
Layout::F32 => FloatWidth::F32,
|
||||
_ => unreachable!("invalid return layout for NumToFloatCast"),
|
||||
};
|
||||
|
||||
match arg_layouts[0].try_to_int_width() {
|
||||
Some(int_width) => {
|
||||
self.build_int_to_float_cast(sym, &args[0], int_width, float_width);
|
||||
}
|
||||
None => {
|
||||
self.build_num_to_frac(sym, &args[0], &arg_layouts[0], ret_layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x => todo!("low level, {:?}", x),
|
||||
}
|
||||
}
|
||||
@ -1962,12 +2045,15 @@ trait Backend<'a> {
|
||||
"NumIsZero: expected to have return layout of type Bool"
|
||||
);
|
||||
|
||||
let literal = match self.interner().get_repr(arg_layouts[0]) {
|
||||
LayoutRepr::Builtin(Builtin::Int(_)) => Literal::Int(0i128.to_ne_bytes()),
|
||||
LayoutRepr::Builtin(Builtin::Float(_)) => Literal::Float(0.0),
|
||||
LayoutRepr::DEC => Literal::Decimal([0; 16]),
|
||||
_ => unreachable!("invalid layout for sin"),
|
||||
};
|
||||
|
||||
self.load_literal_symbols(args);
|
||||
self.load_literal(
|
||||
&Symbol::DEV_TMP,
|
||||
&arg_layouts[0],
|
||||
&Literal::Int(0i128.to_ne_bytes()),
|
||||
);
|
||||
self.load_literal(&Symbol::DEV_TMP, &arg_layouts[0], &literal);
|
||||
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
|
||||
self.free_symbol(&Symbol::DEV_TMP)
|
||||
}
|
||||
@ -2008,18 +2094,6 @@ trait Backend<'a> {
|
||||
self.load_literal(sym, BOOL_LAYOUT, LITERAL);
|
||||
}
|
||||
}
|
||||
Symbol::STR_IS_VALID_SCALAR => {
|
||||
// just call the function
|
||||
let fn_name = self.lambda_name_to_string(
|
||||
func_name,
|
||||
arg_layouts.iter().copied(),
|
||||
None,
|
||||
*ret_layout,
|
||||
);
|
||||
// Now that the arguments are needed, load them if they are literals.
|
||||
self.load_literal_symbols(args);
|
||||
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
|
||||
}
|
||||
_other => {
|
||||
// just call the function
|
||||
let fn_name = self.lambda_name_to_string(
|
||||
@ -2061,6 +2135,14 @@ trait Backend<'a> {
|
||||
target: IntWidth,
|
||||
);
|
||||
|
||||
fn build_int_to_float_cast(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src: &Symbol,
|
||||
int_width: IntWidth,
|
||||
float_width: FloatWidth,
|
||||
);
|
||||
|
||||
/// build_num_abs stores the absolute value of src into dst.
|
||||
fn build_num_abs(&mut self, dst: &Symbol, src: &Symbol, layout: &InLayout<'a>);
|
||||
|
||||
@ -2086,6 +2168,14 @@ trait Backend<'a> {
|
||||
return_layout: &InLayout<'a>,
|
||||
);
|
||||
|
||||
fn build_num_add_wrap(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
layout: &InLayout<'a>,
|
||||
);
|
||||
|
||||
/// build_num_sub_checked stores the sum of src1 and src2 into dst.
|
||||
fn build_num_sub_checked(
|
||||
&mut self,
|
||||
@ -2296,6 +2386,14 @@ trait Backend<'a> {
|
||||
fn build_indirect_inc(&mut self, layout: InLayout<'a>) -> Symbol;
|
||||
fn build_indirect_dec(&mut self, layout: InLayout<'a>) -> Symbol;
|
||||
|
||||
fn build_list_clone(
|
||||
&mut self,
|
||||
dst: Symbol,
|
||||
input_list: Symbol,
|
||||
elem_layout: InLayout<'a>,
|
||||
ret_layout: InLayout<'a>,
|
||||
);
|
||||
|
||||
/// build_list_with_capacity creates and returns a list with the given capacity.
|
||||
fn build_list_with_capacity(
|
||||
&mut self,
|
||||
|
@ -29,6 +29,28 @@ pub fn build_module<'a, 'r>(
|
||||
layout_interner: &'r mut STLayoutInterner<'a>,
|
||||
target: &Triple,
|
||||
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Object<'a> {
|
||||
let module_object = build_module_help(env, interns, layout_interner, target, procedures);
|
||||
|
||||
if std::env::var("ROC_DEV_WRITE_OBJ").is_ok() {
|
||||
let module_out = module_object
|
||||
.write()
|
||||
.expect("failed to build output object");
|
||||
|
||||
let file_path = std::env::temp_dir().join("app.o");
|
||||
println!("gen-test object file written to {}", file_path.display());
|
||||
std::fs::write(&file_path, module_out).expect("failed to write object to file");
|
||||
}
|
||||
|
||||
module_object
|
||||
}
|
||||
|
||||
fn build_module_help<'a, 'r>(
|
||||
env: &'r Env<'a>,
|
||||
interns: &'r mut Interns,
|
||||
layout_interner: &'r mut STLayoutInterner<'a>,
|
||||
target: &Triple,
|
||||
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Object<'a> {
|
||||
match target {
|
||||
Triple {
|
||||
@ -159,6 +181,30 @@ fn define_setlongjmp_buffer(output: &mut Object) -> SymbolId {
|
||||
symbol_id
|
||||
}
|
||||
|
||||
// needed to implement Crash when setjmp/longjmp is used
|
||||
fn define_panic_msg(output: &mut Object) -> SymbolId {
|
||||
let bss_section = output.section_id(StandardSection::Data);
|
||||
|
||||
// 3 words for a RocStr
|
||||
const SIZE: usize = 3 * core::mem::size_of::<u64>();
|
||||
|
||||
let symbol = Symbol {
|
||||
name: b"panic_msg".to_vec(),
|
||||
value: 0,
|
||||
size: SIZE as u64,
|
||||
kind: SymbolKind::Data,
|
||||
scope: SymbolScope::Linkage,
|
||||
weak: false,
|
||||
section: SymbolSection::Section(bss_section),
|
||||
flags: SymbolFlags::None,
|
||||
};
|
||||
|
||||
let symbol_id = output.add_symbol(symbol);
|
||||
output.add_symbol_data(symbol_id, bss_section, &[0x00; SIZE], 8);
|
||||
|
||||
symbol_id
|
||||
}
|
||||
|
||||
fn generate_setjmp<'a, B: Backend<'a>>(backend: &mut B, output: &mut Object) {
|
||||
let text_section = output.section_id(StandardSection::Text);
|
||||
let proc_symbol = Symbol {
|
||||
@ -422,6 +468,7 @@ fn build_object<'a, B: Backend<'a>>(
|
||||
*/
|
||||
|
||||
if backend.env().mode.generate_roc_panic() {
|
||||
define_panic_msg(&mut output);
|
||||
define_setlongjmp_buffer(&mut output);
|
||||
|
||||
generate_roc_panic(&mut backend, &mut output);
|
||||
|
@ -949,6 +949,12 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
|
||||
}
|
||||
|
||||
pub fn new_debug_info(module: &Module<'ctx>) -> (DebugInfoBuilder<'ctx>, DICompileUnit<'ctx>) {
|
||||
let debug_metadata_version = module.get_context().i32_type().const_int(3, false);
|
||||
module.add_basic_value_flag(
|
||||
"Debug Info Version",
|
||||
inkwell::module::FlagBehavior::Warning,
|
||||
debug_metadata_version,
|
||||
);
|
||||
module.create_debug_info_builder(
|
||||
true,
|
||||
/* language */ inkwell::debug_info::DWARFSourceLanguage::C,
|
||||
@ -1065,17 +1071,19 @@ pub fn module_from_builtins<'ctx>(
|
||||
// Anything not depended on by a `roc_builtin.` function could already by DCE'd theoretically.
|
||||
// That said, this workaround is good enough and fixes compilations times.
|
||||
|
||||
// Also, must_keep is the functions we depend on that would normally be provide by libc.
|
||||
// Also, must_keep is the functions we depend on that would normally be provide by libc or compiler-rt.
|
||||
// They are magically linked to by llvm builtins, so we must specify that they can't be DCE'd.
|
||||
let must_keep = [
|
||||
// Windows special required when floats are used
|
||||
"_fltused",
|
||||
// From libc
|
||||
"floorf",
|
||||
"memcpy",
|
||||
"memset",
|
||||
// I have no idea why this function is special.
|
||||
// Without it, some tests hang on M1 mac outside of nix.
|
||||
// From compiler-rt
|
||||
"__divti3",
|
||||
"__modti3",
|
||||
"__muloti4",
|
||||
// fixes `Undefined Symbol in relocation`
|
||||
"__udivti3",
|
||||
// Roc special functions
|
||||
"__roc_force_longjmp",
|
||||
@ -4897,10 +4905,8 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx>(
|
||||
Attribute::get_named_enum_kind_id("byval"),
|
||||
c_abi_type.as_any_type_enum(),
|
||||
);
|
||||
let nonnull = context.create_type_attribute(
|
||||
Attribute::get_named_enum_kind_id("nonnull"),
|
||||
c_abi_type.as_any_type_enum(),
|
||||
);
|
||||
let nonnull = context
|
||||
.create_enum_attribute(Attribute::get_named_enum_kind_id("nonnull"), 0);
|
||||
// C return pointer goes at the beginning of params, and we must skip it if it exists.
|
||||
let returns_pointer = matches!(cc_return, CCReturn::ByPointer);
|
||||
let param_index = i as u32 + returns_pointer as u32;
|
||||
@ -5066,6 +5072,11 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx>(
|
||||
}
|
||||
|
||||
pub fn get_sjlj_buffer<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> {
|
||||
let word_type = match env.target_info.ptr_width() {
|
||||
PtrWidth::Bytes4 => env.context.i32_type(),
|
||||
PtrWidth::Bytes8 => env.context.i64_type(),
|
||||
};
|
||||
|
||||
// The size of jump_buf is target-dependent.
|
||||
// - AArch64 needs 3 machine-sized words
|
||||
// - LLVM says the following about the SJLJ intrinsic:
|
||||
@ -5077,11 +5088,15 @@ pub fn get_sjlj_buffer<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> {
|
||||
// The following three words are available for use in a target-specific manner.
|
||||
//
|
||||
// So, let's create a 5-word buffer.
|
||||
let word_type = match env.target_info.ptr_width() {
|
||||
PtrWidth::Bytes4 => env.context.i32_type(),
|
||||
PtrWidth::Bytes8 => env.context.i64_type(),
|
||||
let size = if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
|
||||
// Due to https://github.com/llvm/llvm-project/issues/72908
|
||||
// on windows, we store the register contents into this buffer directly!
|
||||
30
|
||||
} else {
|
||||
5
|
||||
};
|
||||
let type_ = word_type.array_type(5);
|
||||
|
||||
let type_ = word_type.array_type(size);
|
||||
|
||||
let global = match env.module.get_global("roc_sjlj_buffer") {
|
||||
Some(global) => global,
|
||||
@ -5099,9 +5114,12 @@ pub fn get_sjlj_buffer<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> {
|
||||
|
||||
pub fn build_setjmp_call<'ctx>(env: &Env<'_, 'ctx, '_>) -> BasicValueEnum<'ctx> {
|
||||
let jmp_buf = get_sjlj_buffer(env);
|
||||
if cfg!(target_arch = "aarch64") {
|
||||
if env.target_info.architecture == roc_target::Architecture::Aarch64 {
|
||||
// Due to https://github.com/roc-lang/roc/issues/2965, we use a setjmp we linked in from Zig
|
||||
call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_SETJMP)
|
||||
} else if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
|
||||
// Due to https://github.com/llvm/llvm-project/issues/72908, we use a setjmp defined as asm in Zig
|
||||
call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_WINDOWS_SETJMP)
|
||||
} else {
|
||||
// Anywhere else, use the LLVM intrinsic.
|
||||
// https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp
|
||||
@ -6735,9 +6753,7 @@ pub fn to_cc_return<'a>(
|
||||
) -> CCReturn {
|
||||
let return_size = layout_interner.stack_size(layout);
|
||||
let pass_result_by_pointer = match env.target_info.operating_system {
|
||||
roc_target::OperatingSystem::Windows => {
|
||||
return_size >= 2 * env.target_info.ptr_width() as u32
|
||||
}
|
||||
roc_target::OperatingSystem::Windows => return_size > env.target_info.ptr_width() as u32,
|
||||
roc_target::OperatingSystem::Unix => return_size > 2 * env.target_info.ptr_width() as u32,
|
||||
roc_target::OperatingSystem::Wasi => return_size > 2 * env.target_info.ptr_width() as u32,
|
||||
};
|
||||
|
@ -315,11 +315,18 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
|
||||
|
||||
pub fn build_longjmp_call(env: &Env) {
|
||||
let jmp_buf = get_sjlj_buffer(env);
|
||||
if cfg!(target_arch = "aarch64") {
|
||||
// Call the Zig-linked longjmp: `void longjmp(i32*, i32)`
|
||||
if env.target_info.architecture == roc_target::Architecture::Aarch64 {
|
||||
// Due to https://github.com/roc-lang/roc/issues/2965, we use a setjmp we linked in from Zig
|
||||
let tag = env.context.i32_type().const_int(1, false);
|
||||
let _call =
|
||||
call_void_bitcode_fn(env, &[jmp_buf.into(), tag.into()], bitcode::UTILS_LONGJMP);
|
||||
} else if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
|
||||
let tag = env.context.i32_type().const_int(1, false);
|
||||
let _call = call_void_bitcode_fn(
|
||||
env,
|
||||
&[jmp_buf.into(), tag.into()],
|
||||
bitcode::UTILS_WINDOWS_LONGJMP,
|
||||
);
|
||||
} else {
|
||||
// Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)`
|
||||
let jmp_buf_i8p = env.builder.new_build_pointer_cast(
|
||||
|
@ -34,8 +34,8 @@ use crate::llvm::{
|
||||
BuilderExt, FuncBorrowSpec, RocReturn,
|
||||
},
|
||||
build_list::{
|
||||
list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map,
|
||||
list_map2, list_map3, list_map4, list_prepend, list_release_excess_capacity,
|
||||
layout_width, list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len,
|
||||
list_map, list_map2, list_map3, list_map4, list_prepend, list_release_excess_capacity,
|
||||
list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
|
||||
list_symbol_to_c_abi, list_with_capacity, pass_update_mode,
|
||||
},
|
||||
@ -153,18 +153,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
||||
}
|
||||
}
|
||||
}
|
||||
StrToScalars => {
|
||||
// Str.toScalars : Str -> List U32
|
||||
arguments!(string);
|
||||
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[string],
|
||||
&[],
|
||||
BitcodeReturns::List,
|
||||
bitcode::STR_TO_SCALARS,
|
||||
)
|
||||
}
|
||||
StrStartsWith => {
|
||||
// Str.startsWith : Str, Str -> Bool
|
||||
arguments!(string, prefix);
|
||||
@ -177,18 +165,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
||||
bitcode::STR_STARTS_WITH,
|
||||
)
|
||||
}
|
||||
StrStartsWithScalar => {
|
||||
// Str.startsWithScalar : Str, U32 -> Bool
|
||||
arguments!(string, prefix);
|
||||
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[string],
|
||||
&[prefix],
|
||||
BitcodeReturns::Basic,
|
||||
bitcode::STR_STARTS_WITH_SCALAR,
|
||||
)
|
||||
}
|
||||
StrEndsWith => {
|
||||
// Str.startsWith : Str, Str -> Bool
|
||||
arguments!(string, prefix);
|
||||
@ -545,102 +521,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
||||
);
|
||||
BasicValueEnum::IntValue(is_zero)
|
||||
}
|
||||
StrCountGraphemes => {
|
||||
// Str.countGraphemes : Str -> Nat
|
||||
arguments!(string);
|
||||
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[string],
|
||||
&[],
|
||||
BitcodeReturns::Basic,
|
||||
bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
|
||||
)
|
||||
}
|
||||
StrGetScalarUnsafe => {
|
||||
// Str.getScalarUnsafe : Str, Nat -> { bytesParsed : Nat, scalar : U32 }
|
||||
arguments!(string, index);
|
||||
|
||||
let roc_return_type =
|
||||
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout));
|
||||
|
||||
use roc_target::Architecture::*;
|
||||
use roc_target::OperatingSystem::*;
|
||||
match env.target_info {
|
||||
TargetInfo {
|
||||
operating_system: Windows,
|
||||
..
|
||||
} => {
|
||||
let result = env.builder.new_build_alloca(roc_return_type, "result");
|
||||
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[result.into(), string, index],
|
||||
bitcode::STR_GET_SCALAR_UNSAFE,
|
||||
);
|
||||
|
||||
let cast_result = env.builder.new_build_pointer_cast(
|
||||
result,
|
||||
roc_return_type.ptr_type(AddressSpace::default()),
|
||||
"cast",
|
||||
);
|
||||
|
||||
env.builder
|
||||
.new_build_load(roc_return_type, cast_result, "load_result")
|
||||
}
|
||||
TargetInfo {
|
||||
architecture: Wasm32,
|
||||
..
|
||||
} => {
|
||||
let result = env.builder.new_build_alloca(roc_return_type, "result");
|
||||
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
result.into(),
|
||||
pass_string_to_zig_wasm(env, string).into(),
|
||||
index,
|
||||
],
|
||||
bitcode::STR_GET_SCALAR_UNSAFE,
|
||||
);
|
||||
|
||||
let cast_result = env.builder.new_build_pointer_cast(
|
||||
result,
|
||||
roc_return_type.ptr_type(AddressSpace::default()),
|
||||
"cast",
|
||||
);
|
||||
|
||||
env.builder
|
||||
.new_build_load(roc_return_type, cast_result, "load_result")
|
||||
}
|
||||
TargetInfo {
|
||||
operating_system: Unix,
|
||||
..
|
||||
} => {
|
||||
let result = call_str_bitcode_fn(
|
||||
env,
|
||||
&[string],
|
||||
&[index],
|
||||
BitcodeReturns::Basic,
|
||||
bitcode::STR_GET_SCALAR_UNSAFE,
|
||||
);
|
||||
|
||||
// zig will pad the struct to the alignment boundary, or bitpack it on 32-bit
|
||||
// targets. So we have to cast it to the format that the roc code expects
|
||||
let alloca = env
|
||||
.builder
|
||||
.new_build_alloca(result.get_type(), "to_roc_record");
|
||||
env.builder.new_build_store(alloca, result);
|
||||
|
||||
env.builder
|
||||
.new_build_load(roc_return_type, alloca, "to_roc_record")
|
||||
}
|
||||
TargetInfo {
|
||||
operating_system: Wasi,
|
||||
..
|
||||
} => unimplemented!(),
|
||||
}
|
||||
}
|
||||
StrCountUtf8Bytes => {
|
||||
// Str.countUtf8Bytes : Str -> Nat
|
||||
arguments!(string);
|
||||
@ -653,12 +533,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
||||
bitcode::STR_COUNT_UTF8_BYTES,
|
||||
)
|
||||
}
|
||||
StrGetCapacity => {
|
||||
// Str.capacity : Str -> Nat
|
||||
arguments!(string);
|
||||
|
||||
call_bitcode_fn(env, &[string], bitcode::STR_CAPACITY)
|
||||
}
|
||||
StrSubstringUnsafe => {
|
||||
// Str.substringUnsafe : Str, Nat, Nat -> Str
|
||||
arguments!(string, start, length);
|
||||
@ -695,18 +569,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
||||
bitcode::STR_RELEASE_EXCESS_CAPACITY,
|
||||
)
|
||||
}
|
||||
StrAppendScalar => {
|
||||
// Str.appendScalar : Str, U32 -> Str
|
||||
arguments!(string, capacity);
|
||||
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[string],
|
||||
&[capacity],
|
||||
BitcodeReturns::Str,
|
||||
bitcode::STR_APPEND_SCALAR,
|
||||
)
|
||||
}
|
||||
StrTrim => {
|
||||
// Str.trim : Str -> Str
|
||||
arguments!(string);
|
||||
@ -749,18 +611,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
||||
bitcode::STR_WITH_CAPACITY,
|
||||
)
|
||||
}
|
||||
StrGraphemes => {
|
||||
// Str.graphemes : Str -> List Str
|
||||
arguments!(string);
|
||||
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[string],
|
||||
&[],
|
||||
BitcodeReturns::List,
|
||||
bitcode::STR_GRAPHEMES,
|
||||
)
|
||||
}
|
||||
ListLen => {
|
||||
// List.len : List * -> Nat
|
||||
arguments!(list);
|
||||
@ -962,6 +812,31 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
||||
bitcode::LIST_IS_UNIQUE,
|
||||
)
|
||||
}
|
||||
ListClone => {
|
||||
// List.clone : List a -> List a
|
||||
arguments_with_layouts!((list, list_layout));
|
||||
let element_layout = list_element_layout!(layout_interner, list_layout);
|
||||
|
||||
match update_mode {
|
||||
UpdateMode::Immutable => {
|
||||
//
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[list.into_struct_value()],
|
||||
&[
|
||||
env.alignment_intvalue(layout_interner, element_layout),
|
||||
layout_width(env, layout_interner, element_layout),
|
||||
],
|
||||
BitcodeReturns::List,
|
||||
bitcode::LIST_CLONE,
|
||||
)
|
||||
}
|
||||
UpdateMode::InPlace => {
|
||||
// we statically know the list is unique
|
||||
list
|
||||
}
|
||||
}
|
||||
}
|
||||
NumToStr => {
|
||||
// Num.toStr : Num a -> Str
|
||||
arguments_with_layouts!((num, num_layout));
|
||||
@ -1853,6 +1728,11 @@ fn build_float_binop<'ctx>(
|
||||
|
||||
let bd = env.builder;
|
||||
|
||||
let float_type = match float_width {
|
||||
FloatWidth::F32 => env.context.f32_type(),
|
||||
FloatWidth::F64 => env.context.f64_type(),
|
||||
};
|
||||
|
||||
match op {
|
||||
NumAdd => bd.new_build_float_add(lhs, rhs, "add_float").into(),
|
||||
NumAddChecked => {
|
||||
@ -1865,10 +1745,8 @@ fn build_float_binop<'ctx>(
|
||||
.into_int_value();
|
||||
let is_infinite = bd.new_build_not(is_finite, "negate");
|
||||
|
||||
let struct_type = context.struct_type(
|
||||
&[context.f64_type().into(), context.bool_type().into()],
|
||||
false,
|
||||
);
|
||||
let struct_type =
|
||||
context.struct_type(&[float_type.into(), context.bool_type().into()], false);
|
||||
|
||||
let struct_value = {
|
||||
let v1 = struct_type.const_zero();
|
||||
@ -1894,10 +1772,8 @@ fn build_float_binop<'ctx>(
|
||||
.into_int_value();
|
||||
let is_infinite = bd.new_build_not(is_finite, "negate");
|
||||
|
||||
let struct_type = context.struct_type(
|
||||
&[context.f64_type().into(), context.bool_type().into()],
|
||||
false,
|
||||
);
|
||||
let struct_type =
|
||||
context.struct_type(&[float_type.into(), context.bool_type().into()], false);
|
||||
|
||||
let struct_value = {
|
||||
let v1 = struct_type.const_zero();
|
||||
@ -1924,10 +1800,8 @@ fn build_float_binop<'ctx>(
|
||||
.into_int_value();
|
||||
let is_infinite = bd.new_build_not(is_finite, "negate");
|
||||
|
||||
let struct_type = context.struct_type(
|
||||
&[context.f64_type().into(), context.bool_type().into()],
|
||||
false,
|
||||
);
|
||||
let struct_type =
|
||||
context.struct_type(&[float_type.into(), context.bool_type().into()], false);
|
||||
|
||||
let struct_value = {
|
||||
let v1 = struct_type.const_zero();
|
||||
@ -2179,6 +2053,54 @@ fn dec_unary_op<'ctx>(
|
||||
}
|
||||
}
|
||||
|
||||
fn dec_binary_op<'ctx>(
|
||||
env: &Env<'_, 'ctx, '_>,
|
||||
fn_name: &str,
|
||||
dec1: BasicValueEnum<'ctx>,
|
||||
dec2: BasicValueEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use roc_target::Architecture::*;
|
||||
use roc_target::OperatingSystem::*;
|
||||
|
||||
let dec1 = dec1.into_int_value();
|
||||
let dec2 = dec2.into_int_value();
|
||||
|
||||
match env.target_info {
|
||||
TargetInfo {
|
||||
architecture: X86_64 | X86_32,
|
||||
operating_system: Unix,
|
||||
} => {
|
||||
let (low1, high1) = dec_split_into_words(env, dec1);
|
||||
let (low2, high2) = dec_split_into_words(env, dec2);
|
||||
let lowr_highr = call_bitcode_fn(
|
||||
env,
|
||||
&[low1.into(), high1.into(), low2.into(), high2.into()],
|
||||
fn_name,
|
||||
);
|
||||
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let parent = block.get_parent().expect("to be in a function");
|
||||
|
||||
let ptr =
|
||||
create_entry_block_alloca(env, parent, env.context.i128_type().into(), "to_i128");
|
||||
env.builder.build_store(ptr, lowr_highr).unwrap();
|
||||
|
||||
env.builder
|
||||
.build_load(env.context.i128_type(), ptr, "to_i128")
|
||||
.unwrap()
|
||||
}
|
||||
TargetInfo {
|
||||
architecture: Wasm32,
|
||||
operating_system: Unix,
|
||||
} => call_bitcode_fn(env, &[dec1.into(), dec2.into()], fn_name),
|
||||
_ => call_bitcode_fn(
|
||||
env,
|
||||
&[dec_alloca(env, dec1), dec_alloca(env, dec2)],
|
||||
fn_name,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn dec_binop_with_overflow<'ctx>(
|
||||
env: &Env<'_, 'ctx, '_>,
|
||||
fn_name: &str,
|
||||
@ -2309,11 +2231,13 @@ fn build_dec_unary_op<'a, 'ctx>(
|
||||
_layout_interner: &STLayoutInterner<'a>,
|
||||
_parent: FunctionValue<'ctx>,
|
||||
arg: BasicValueEnum<'ctx>,
|
||||
_return_layout: InLayout<'a>,
|
||||
return_layout: InLayout<'a>,
|
||||
op: LowLevel,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use roc_module::low_level::LowLevel::*;
|
||||
|
||||
let int_width = || return_layout.to_int_width();
|
||||
|
||||
match op {
|
||||
NumAbs => dec_unary_op(env, bitcode::DEC_ABS, arg),
|
||||
NumAcos => dec_unary_op(env, bitcode::DEC_ACOS, arg),
|
||||
@ -2323,6 +2247,10 @@ fn build_dec_unary_op<'a, 'ctx>(
|
||||
NumSin => dec_unary_op(env, bitcode::DEC_SIN, arg),
|
||||
NumTan => dec_unary_op(env, bitcode::DEC_TAN, arg),
|
||||
|
||||
NumRound => dec_unary_op(env, &bitcode::DEC_ROUND[int_width()], arg),
|
||||
NumFloor => dec_unary_op(env, &bitcode::DEC_FLOOR[int_width()], arg),
|
||||
NumCeiling => dec_unary_op(env, &bitcode::DEC_CEILING[int_width()], arg),
|
||||
|
||||
_ => {
|
||||
unreachable!("Unrecognized dec unary operation: {:?}", op);
|
||||
}
|
||||
@ -2391,6 +2319,7 @@ fn build_dec_binop<'a, 'ctx>(
|
||||
&[lhs, rhs],
|
||||
&bitcode::NUM_GREATER_THAN_OR_EQUAL[IntWidth::I128],
|
||||
),
|
||||
NumPow => dec_binary_op(env, bitcode::DEC_POW, lhs, rhs),
|
||||
_ => {
|
||||
unreachable!("Unrecognized dec binary operation: {:?}", op);
|
||||
}
|
||||
@ -2804,42 +2733,39 @@ fn build_float_unary_op<'a, 'ctx>(
|
||||
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
|
||||
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
|
||||
};
|
||||
match float_width {
|
||||
FloatWidth::F32 => {
|
||||
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F32[int_width])
|
||||
}
|
||||
FloatWidth::F64 => {
|
||||
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F64[int_width])
|
||||
}
|
||||
}
|
||||
|
||||
let intrinsic = match float_width {
|
||||
FloatWidth::F32 => &bitcode::NUM_CEILING_F32[int_width],
|
||||
FloatWidth::F64 => &bitcode::NUM_CEILING_F64[int_width],
|
||||
};
|
||||
|
||||
call_bitcode_fn(env, &[arg.into()], intrinsic)
|
||||
}
|
||||
NumFloor => {
|
||||
let int_width = match layout_interner.get_repr(layout) {
|
||||
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
|
||||
_ => internal_error!("Floor return layout is not int: {:?}", layout),
|
||||
};
|
||||
match float_width {
|
||||
FloatWidth::F32 => {
|
||||
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F32[int_width])
|
||||
}
|
||||
FloatWidth::F64 => {
|
||||
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F64[int_width])
|
||||
}
|
||||
}
|
||||
|
||||
let intrinsic = match float_width {
|
||||
FloatWidth::F32 => &bitcode::NUM_FLOOR_F32[int_width],
|
||||
FloatWidth::F64 => &bitcode::NUM_FLOOR_F64[int_width],
|
||||
};
|
||||
|
||||
call_bitcode_fn(env, &[arg.into()], intrinsic)
|
||||
}
|
||||
NumRound => {
|
||||
let int_width = match layout_interner.get_repr(layout) {
|
||||
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
|
||||
_ => internal_error!("Round return layout is not int: {:?}", layout),
|
||||
};
|
||||
match float_width {
|
||||
FloatWidth::F32 => {
|
||||
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F32[int_width])
|
||||
}
|
||||
FloatWidth::F64 => {
|
||||
call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F64[int_width])
|
||||
}
|
||||
}
|
||||
|
||||
let intrinsic = match float_width {
|
||||
FloatWidth::F32 => &bitcode::NUM_ROUND_F32[int_width],
|
||||
FloatWidth::F64 => &bitcode::NUM_ROUND_F64[int_width],
|
||||
};
|
||||
|
||||
call_bitcode_fn(env, &[arg.into()], intrinsic)
|
||||
}
|
||||
NumIsNan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_NAN[float_width]),
|
||||
NumIsInfinite => {
|
||||
|
@ -1295,15 +1295,25 @@ fn build_rec_union_recursive_decrement<'a, 'ctx>(
|
||||
_ => tag_id,
|
||||
};
|
||||
|
||||
let block = env.context.append_basic_block(parent, "tag_id_decrement");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
// if none of the fields are or contain anything refcounted, just move on
|
||||
if fields_need_no_refcounting(layout_interner, field_layouts) {
|
||||
// Still make sure to decrement the refcount of the union as a whole.
|
||||
if let DecOrReuse::Dec = decrement_or_reuse {
|
||||
let union_layout = LayoutRepr::Union(union_layout);
|
||||
refcount_ptr.modify(call_mode, union_layout, env, layout_interner);
|
||||
}
|
||||
|
||||
// this function returns void
|
||||
builder.new_build_return(None);
|
||||
|
||||
cases.push((tag_id_int_type.const_int(tag_id as u64, false), block));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let block = env.context.append_basic_block(parent, "tag_id_decrement");
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
let fields_struct = LayoutRepr::struct_(field_layouts);
|
||||
let wrapper_type = basic_type_from_layout(env, layout_interner, fields_struct);
|
||||
|
||||
@ -1370,12 +1380,9 @@ fn build_rec_union_recursive_decrement<'a, 'ctx>(
|
||||
// and store them on the stack, then modify (and potentially free) the current cell, then
|
||||
// actually inc/dec the fields.
|
||||
|
||||
match decrement_or_reuse {
|
||||
DecOrReuse::Reuse => {}
|
||||
DecOrReuse::Dec => {
|
||||
let union_layout = LayoutRepr::Union(union_layout);
|
||||
refcount_ptr.modify(call_mode, union_layout, env, layout_interner);
|
||||
}
|
||||
if let DecOrReuse::Dec = decrement_or_reuse {
|
||||
let union_layout = LayoutRepr::Union(union_layout);
|
||||
refcount_ptr.modify(call_mode, union_layout, env, layout_interner);
|
||||
}
|
||||
|
||||
for (field, field_layout) in deferred_nonrec {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user