Merge branch 'main' into fromutf8

This commit is contained in:
Anton-4 2024-12-23 11:29:59 +01:00 committed by GitHub
commit 63abc4bb01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
402 changed files with 11302 additions and 4184 deletions

View file

@ -1,5 +1,5 @@
on:
# pull_request:
# pull_request:
workflow_dispatch:
# this cancels workflows currently in progress if you start a new one
@ -11,7 +11,7 @@ env:
# use .tar.gz for quick testing
ARCHIVE_FORMAT: .tar.br
# Make a new basic-cli git tag and set it here before starting this workflow
RELEASE_TAG: 0.16.0
RELEASE_TAG: 0.17.0
jobs:
prepare:
@ -34,19 +34,19 @@ jobs:
fi
# get latest nightly releases
- 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
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-TESTING.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-TESTING.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-TESTING.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-TESTING.tar.gz
#- 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
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-TESTING.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-TESTING.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-TESTING.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-TESTING.tar.gz
- name: Save roc_nightly archives
uses: actions/upload-artifact@v4
with:
path: roc_nightly-*
path: roc_nightly-*
build-linux-x86_64-files:
runs-on: [ubuntu-20.04]
@ -71,7 +71,6 @@ jobs:
basic-cli/platform/linux-x64.rh
basic-cli/platform/linux-x64.a
build-linux-arm64-files:
runs-on: [self-hosted, Linux, ARM64]
needs: [prepare]
@ -84,8 +83,8 @@ jobs:
- name: build basic-cli
env:
CARGO_BUILD_TARGET: aarch64-unknown-linux-musl
CC_aarch64_unknown_linux_musl: clang-16
AR_aarch64_unknown_linux_musl: llvm-ar-16
CC_aarch64_unknown_linux_musl: clang-18
AR_aarch64_unknown_linux_musl: llvm-ar-18
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld"
run: ./ci/build_basic_cli.sh linux_arm64
@ -134,7 +133,13 @@ jobs:
basic-cli/platform/macos-arm64.a
create-release-archive:
needs: [build-linux-x86_64-files, build-linux-arm64-files, build-macos-x86_64-files, build-macos-apple-silicon-files]
needs:
[
build-linux-x86_64-files,
build-linux-arm64-files,
build-macos-x86_64-files,
build-macos-apple-silicon-files,
]
name: create release archive
runs-on: [ubuntu-20.04]
steps:
@ -202,7 +207,6 @@ jobs:
needs: [create-release-archive]
runs-on: [ubuntu-20.04]
steps:
- name: Download the previously uploaded files
uses: actions/download-artifact@v4

View file

@ -28,11 +28,10 @@ jobs:
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-TESTING.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-TESTING.tar.gz
- name: Save roc_nightly archives
uses: actions/upload-artifact@v4
with:
path: roc_nightly-*
path: roc_nightly-*
build-linux-x86_64-files:
runs-on: [ubuntu-20.04]
@ -57,7 +56,6 @@ jobs:
basic-webserver/platform/linux-x64.rh
basic-webserver/platform/linux-x64.a
build-linux-arm64-files:
runs-on: [self-hosted, Linux, ARM64]
needs: [fetch-releases]
@ -70,8 +68,8 @@ jobs:
- name: build basic-webserver
env:
CARGO_BUILD_TARGET: aarch64-unknown-linux-musl
CC_aarch64_unknown_linux_musl: clang-16
AR_aarch64_unknown_linux_musl: llvm-ar-16
CC_aarch64_unknown_linux_musl: clang-18
AR_aarch64_unknown_linux_musl: llvm-ar-18
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld"
run: ./ci/build_basic_webserver.sh linux_arm64
@ -120,7 +118,13 @@ jobs:
basic-webserver/platform/macos-arm64.a
create-release-archive:
needs: [build-linux-x86_64-files, build-linux-arm64-files, build-macos-x86_64-files, build-macos-apple-silicon-files]
needs:
[
build-linux-x86_64-files,
build-linux-arm64-files,
build-macos-x86_64-files,
build-macos-apple-silicon-files,
]
name: create release archive
runs-on: [ubuntu-20.04]
steps:

View file

@ -1,33 +1,36 @@
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@v4
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_180_PREFIX
run: echo "LLVM_SYS_180_PREFIX=$(brew --prefix llvm@18)" >> $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
- name: check for zig 13
run: ls /Users/username1/Downloads/zig-macos-x86_64-0.13.0
- run: zig version
- name: Update PATH to use zig 13
run: |
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.13.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_tags::phantom_polymorphic_record && sccache --show-stats
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos 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 x86_64 CI machine
# this issue may be caused by using older versions of XCode

View file

@ -2,7 +2,7 @@ on:
#pull_request:
workflow_dispatch:
schedule:
- cron: '0 9 * * *'
- cron: "0 9 * * *"
name: Nightly Release Linux arm64/aarch64
@ -15,9 +15,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Update PATH to use zig 11
- name: Update PATH to use zig 13
run: |
echo "PATH=/home/username/Downloads/zig-linux-aarch64-0.11.0:$PATH" >> $GITHUB_ENV
echo "PATH=/home/username/Downloads/zig-linux-aarch64-0.13.0:$PATH" >> $GITHUB_ENV
- run: zig version
@ -28,15 +28,15 @@ jobs:
run: cargo build --profile=release-with-lto --locked --bin roc --bin roc_language_server
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
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 }}
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-linux_arm64-$DATE-$SHA" >> $GITHUB_ENV
# this makes the roc binary a lot smaller
@ -49,6 +49,6 @@ jobs:
- name: Upload roc nightly tar. 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
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View file

@ -2,7 +2,7 @@ on:
#pull_request:
workflow_dispatch:
schedule:
- cron: '0 9 * * *'
- cron: "0 9 * * *"
name: Nightly Release Linux x86_64
@ -15,9 +15,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Update PATH to use zig 11
- name: Update PATH to use zig 13
run: |
echo "PATH=/home/big-ci-user/Downloads/zig-linux-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
echo "PATH=/home/big-ci-user/Downloads/zig-linux-x86_64-0.13.0:$PATH" >> $GITHUB_ENV
- run: zig version
@ -29,7 +29,7 @@ jobs:
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. This was also faster in our tests: https://roc.zulipchat.com/#narrow/stream/231635-compiler-development/topic/.2Ecargo.2Fconfig.2Etoml/near/325726299
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
@ -40,14 +40,14 @@ jobs:
- name: Upload wasm repl tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v4
with:
name: roc_repl_wasm.tar.gz
path: roc_repl_wasm.tar.gz
retention-days: 4
name: roc_repl_wasm.tar.gz
path: roc_repl_wasm.tar.gz
retention-days: 4
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-linux_x86_64-$DATE-$SHA" >> $GITHUB_ENV
# this makes the roc binary a lot smaller
@ -60,6 +60,6 @@ jobs:
- name: Upload roc nightly tar. 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
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View file

@ -8,7 +8,7 @@ name: Nightly Release macOS Apple Silicon
env:
RUST_BACKTRACE: 1
LLVM_SYS_160_PREFIX: /opt/homebrew/opt/llvm@16
LLVM_SYS_180_PREFIX: /opt/homebrew/opt/llvm@18
jobs:
test-and-build:
@ -20,9 +20,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Update PATH to use zig 11
- name: Update PATH to use zig 13
run: |
echo "PATH=/Users/m1ci/Downloads/zig-macos-aarch64-0.11.0:$PATH" >> $GITHUB_ENV
echo "PATH=/Users/m1ci/Downloads/zig-macos-aarch64-0.13.0:$PATH" >> $GITHUB_ENV
- run: zig version

View file

@ -1,62 +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_180_PREFIX: /usr/local/opt/llvm@18
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@v4
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
- name: Update PATH to use zig 13
run: |
echo "PATH=/Users/username1/Downloads/zig-macos-x86_64-0.13.0:$PATH" >> $GITHUB_ENV
- run: zig version
- run: zig version
- name: write version to file
run: ./ci/write_version.sh
- 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 x86_64 CI machine
# this issue may be caused by using older versions of XCode
- 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 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: 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 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: 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
- 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
# 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: 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
- 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

View file

@ -11,6 +11,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Update PATH to use zig 13
run: echo "PATH=/Users/m1ci/Downloads/zig-macos-aarch64-0.13.0:$PATH" >> $GITHUB_ENV
- run: zig version
- 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

View file

@ -1,4 +1,5 @@
on:
# pull_request:
workflow_dispatch:
name: Test latest nightly releases for macOS and Linux x86_64
@ -9,22 +10,22 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ macos-13, ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
os: [macos-13, ubuntu-20.04, ubuntu-22.04, ubuntu-24.04]
runs-on: ${{ matrix.os }}
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- uses: goto-bus-stop/setup-zig@v2
with:
version: 0.11.0
version: 0.13.0
- name: Install zlib on macOS-13
- name: Install z3 on macOS-13
if: matrix.os == 'macos-13'
run: brew install zlib
run: brew install z3
- 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
- name: get the latest release archive for macos (x86_64)

View file

@ -17,9 +17,9 @@ jobs:
- 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.
- name: Update PATH to use zig 11
- name: Update PATH to use zig 13
run: |
echo "PATH=/home/big-ci-user/Downloads/zig-linux-x86_64-0.11.0:$PATH" >> $GITHUB_ENV
echo "PATH=/home/big-ci-user/Downloads/zig-linux-x86_64-0.13.0:$PATH" >> $GITHUB_ENV
- run: zig version
@ -62,3 +62,8 @@ jobs:
- name: test website build script
run: bash www/build.sh
- name: run fuzz tests - ok to merge if this one fails # for now!
run: |
cargo +nightly-2024-02-03 install --locked cargo-fuzz
cd crates/compiler/test_syntax/fuzz && cargo +nightly-2024-02-03 fuzz run -j4 fuzz_expr --sanitizer=none -- -dict=dict.txt -max_total_time=60

View file

@ -11,7 +11,7 @@ jobs:
name: windows-release-build
runs-on: windows-2022
env:
LLVM_SYS_160_PREFIX: C:\LLVM-16.0.6-win64
LLVM_SYS_180_PREFIX: C:\LLVM-18.1.8-win64
timeout-minutes: 150
steps:
@ -21,10 +21,10 @@ jobs:
- name: download and install zig
run: |
curl.exe -f --output "C:\zig-windows-x86_64-0.11.0.zip" --url https://ziglang.org/download/0.11.0/zig-windows-x86_64-0.11.0.zip
curl.exe -f --output "C:\zig-windows-x86_64-0.13.0.zip" --url https://ziglang.org/download/0.13.0/zig-windows-x86_64-0.13.0.zip
cd C:\
7z x zig-windows-x86_64-0.11.0.zip
Add-Content $env:GITHUB_PATH "C:\zig-windows-x86_64-0.11.0\"
7z x zig-windows-x86_64-0.13.0.zip
Add-Content $env:GITHUB_PATH "C:\zig-windows-x86_64-0.13.0\"
- name: zig version
run: zig version
@ -32,10 +32,10 @@ jobs:
- name: install rust nightly 1.77.0
run: rustup install nightly-2024-02-03
- name: set up llvm 16
- name: set up llvm 18
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
curl.exe -f -L -O -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://github.com/roc-lang/llvm-package-windows/releases/download/v18.1.8/LLVM-18.1.8-win64.7z
7z x LLVM-18.1.8-win64.7z -oC:\LLVM-18.1.8-win64
- name: cargo build release.
run: cargo build --locked --release

View file

@ -1,62 +1,62 @@
on:
workflow_call:
name: windows - subset of tests
env:
RUST_BACKTRACE: 1
jobs:
windows-test-subset:
name: windows-test-subset
runs-on: windows-2022
env:
LLVM_SYS_160_PREFIX: C:\LLVM-16.0.6-win64
timeout-minutes: 150
steps:
- uses: actions/checkout@v4
- run: Add-Content -Path "$env:GITHUB_ENV" -Value "GITHUB_RUNNER_CPU=$((Get-CimInstance Win32_Processor).Name)"
- uses: Swatinem/rust-cache@v2
with:
shared-key: "rust-cache-windows-${{env.GITHUB_RUNNER_CPU}}"
- name: download and install zig
run: |
curl.exe -f --output "C:\zig-windows-x86_64-0.11.0.zip" --url https://ziglang.org/download/0.11.0/zig-windows-x86_64-0.11.0.zip
cd C:\
7z x zig-windows-x86_64-0.11.0.zip
Add-Content $env:GITHUB_PATH "C:\zig-windows-x86_64-0.11.0\"
- run: zig version
- name: zig tests
run: |
cd crates\compiler\builtins\bitcode\
zig build test
- name: install rust nightly 1.77.0
run: rustup install nightly-2024-02-03
- 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.
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: 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
- 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
on:
workflow_call:
name: windows - subset of tests
env:
RUST_BACKTRACE: 1
jobs:
windows-test-subset:
name: windows-test-subset
runs-on: windows-2022
env:
LLVM_SYS_180_PREFIX: C:\LLVM-18.1.8-win64
timeout-minutes: 150
steps:
- uses: actions/checkout@v4
- run: Add-Content -Path "$env:GITHUB_ENV" -Value "GITHUB_RUNNER_CPU=$((Get-CimInstance Win32_Processor).Name)"
- uses: Swatinem/rust-cache@v2
with:
shared-key: "rust-cache-windows-${{env.GITHUB_RUNNER_CPU}}"
- name: download and install zig
run: |
curl.exe -f --output "C:\zig-windows-x86_64-0.13.0.zip" --url https://ziglang.org/download/0.13.0/zig-windows-x86_64-0.13.0.zip
cd C:\
7z x zig-windows-x86_64-0.13.0.zip
Add-Content $env:GITHUB_PATH "C:\zig-windows-x86_64-0.13.0\"
- run: zig version
- name: zig tests
run: |
cd crates\compiler\builtins\bitcode\
zig build test
- name: install rust nightly 1.77.0
run: rustup install nightly-2024-02-03
- name: set up llvm 18
run: |
curl.exe -f -L -O -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://github.com/roc-lang/llvm-package-windows/releases/download/v18.1.8/LLVM-18.1.8-win64.7z
7z x LLVM-18.1.8-win64.7z -oC:\LLVM-18.1.8-win64
- 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: 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
- 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

6
.gitignore vendored
View file

@ -24,7 +24,11 @@
# Ignore the following directories and file extensions
target
generated-docs
# todo remove after upgrade to zig 0.13.0
zig-cache
zig-out
.zig-cache
.direnv
.envrc
@ -123,3 +127,5 @@ crates/glue/tests/fixtures/*/test_glue/
# ignore the zig glue files copied into test platforms
**/*platform/glue/*
crates/cli/tests/test-projects/test-platform-effects-zig/glue
crates/cli/tests/test-projects/test-platform-simple-zig/glue

View file

@ -1 +1 @@
16.0.0
18.0.0

View file

@ -152,7 +152,7 @@ For Ubuntu and Debian:
sudo apt -y install lsb-release software-properties-common gnupg
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 16
./llvm.sh 18
```
If you use this script, you'll need to add `clang` to your `PATH`.
@ -169,7 +169,7 @@ There are also alternative installation options at <http://releases.llvm.org/dow
For Fedora:
```sh
sudo dnf install llvm16 llvm16-devel
sudo dnf install llvm18 llvm18-devel
```
#### LLVM Linux troubleshooting
@ -183,20 +183,20 @@ If you encounter:
```text
error: No suitable version of LLVM was found system-wide or pointed
to by LLVM_SYS_160_PREFIX.
to by LLVM_SYS_180_PREFIX.
```
Add `export LLVM_SYS_160_PREFIX=/usr/lib/llvm-16` to your `~/.bashrc` or equivalent file for your shell.
Add `export LLVM_SYS_180_PREFIX=/usr/lib/llvm-18` to your `~/.bashrc` or equivalent file for your shell.
### LLVM installation on MacOS
For macOS, you can install LLVM 16 using `brew install llvm@16` and then adding
`$(brew --prefix llvm@16)/bin` to your `PATH`. You can confirm this worked by
For macOS, you can install LLVM 18 using `brew install llvm@18` and then adding
`$(brew --prefix llvm@18)/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 16.0.x" at the top.
You may also need to manually specify a prefix env var like so:
```sh
export LLVM_SYS_160_PREFIX=$(brew --prefix llvm@16)
export LLVM_SYS_180_PREFIX=$(brew --prefix llvm@18)
```
#### LLVM MacOS troubleshooting
@ -215,14 +215,14 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include"
**Warning** While `cargo build` works on windows, linking roc programs does not yet, see issue #2608. This also means the repl, and many tests will not work on windows.
The official LLVM pre-built binaries for Windows lack features that roc needs. Instead:
1. Download the custom LLVM 7z archive [here](https://github.com/roc-lang/llvm-package-windows/releases/download/v16.0.6/LLVM-16.0.6-win64.7z).
1. Download the custom LLVM 7z archive [here](https://github.com/roc-lang/llvm-package-windows/releases/download/v18.1.8/LLVM-18.1.8-win64.7z).
1. [Download 7-zip](https://www.7-zip.org/) to be able to extract this archive.
1. Extract the 7z file to where you want to permanently keep the folder. We recommend you pick a path without any spaces in it.
1. In powershell, set the `LLVM_SYS_160_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-environment-variables-with-the-system-control-panel) to make this a permanent environment variable):
1. In powershell, set the `LLVM_SYS_180_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-environment-variables-with-the-system-control-panel) to make this a permanent environment variable):
```text
<# ! Replace YOUR_USERNAME ! #>
$env:LLVM_SYS_160_PREFIX = 'C:\Users\YOUR_USERNAME\Downloads\LLVM-16.0.6-win64'
$env:LLVM_SYS_180_PREFIX = 'C:\Users\YOUR_USERNAME\Downloads\LLVM-18.1.8-win64'
```
Once all that was done, `cargo build` ran successfully for Roc!

View file

@ -68,6 +68,8 @@ programs.gnupg.agent = {
<details>
<summary>Forgot to sign commits?</summary>
:exclamation: Make sure [to set up signing on your device](devtools/signing.md) first, then continue below.
You can view your commits on github, those without the "Verified" badge still need to be signed.
If any of those is a merge commit, follow [these steps](https://stackoverflow.com/a/9958215/4200103) instead of the ones below.

36
Cargo.lock generated
View file

@ -132,6 +132,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "arrayref"
version = "0.3.7"
@ -548,6 +554,13 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "copy_zig_glue"
version = "0.0.1"
dependencies = [
"roc_test_utils_dir",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@ -1300,8 +1313,8 @@ checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
[[package]]
name = "inkwell"
version = "0.2.0"
source = "git+https://github.com/roc-lang/inkwell?branch=inkwell-llvm-16#d1a596391894933aa1f0f91ec87187e47917cf86"
version = "0.4.0"
source = "git+https://github.com/TheDan64/inkwell?rev=89e06af#89e06af9dd70dc5d6bc5ae42a2a03f680a367d37"
dependencies = [
"either",
"inkwell_internals",
@ -1313,8 +1326,8 @@ dependencies = [
[[package]]
name = "inkwell_internals"
version = "0.8.0"
source = "git+https://github.com/roc-lang/inkwell?branch=inkwell-llvm-16#d1a596391894933aa1f0f91ec87187e47917cf86"
version = "0.9.0"
source = "git+https://github.com/TheDan64/inkwell?rev=89e06af#89e06af9dd70dc5d6bc5ae42a2a03f680a367d37"
dependencies = [
"proc-macro2",
"quote",
@ -1463,14 +1476,15 @@ checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "llvm-sys"
version = "160.1.3"
version = "180.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf51981ac0622b10fe4790763e3de1f3d68a0ee4222e03accaaab6731bd508d"
checksum = "778fa5fa02e32728e718f11eec147e6f134137399ab02fd2c13d32476337affa"
dependencies = [
"anyhow",
"cc",
"lazy_static",
"libc",
"regex",
"regex-lite",
"semver",
]
@ -2117,6 +2131,12 @@ dependencies = [
"regex-syntax 0.8.2",
]
[[package]]
name = "regex-lite"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
[[package]]
name = "regex-syntax"
version = "0.6.29"
@ -2395,6 +2415,7 @@ dependencies = [
"clap 4.4.6",
"cli_test_utils",
"const_format",
"copy_zig_glue",
"criterion",
"distance",
"errno",
@ -4416,6 +4437,7 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"cli_test_utils",
"copy_zig_glue",
"indoc",
"roc_build",
"roc_command_utils",

View file

@ -31,6 +31,7 @@ members = [
"crates/wasm_module",
"crates/wasm_interp",
"crates/language_server",
"crates/copy_zig_glue",
"crates/roc_std_heap",
]
@ -59,26 +60,8 @@ object = { version = "0.32.2", default-features = false, features = [
"read",
"write",
] }
# NOTE: roc-lang/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the roc-lang/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up roc-lang/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
# TODO: Switch this back to roc-lang/inkwell once it is updated
inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-16", features = [
"llvm16-0",
inkwell = { git = "https://github.com/TheDan64/inkwell", rev = "89e06af", features = [
"llvm18-0",
] }
arrayvec = "0.7.2"
base64-url = "1.4.13"
@ -183,6 +166,7 @@ wyhash = "0.5.0"
# INTERNAL DEPENDENCIES
cli_test_utils = { path = "crates/cli_test_utils" }
copy_zig_glue = { path = "crates/copy_zig_glue" }
roc_alias_analysis = { path = "crates/compiler/alias_analysis" }
roc_bitcode = { path = "crates/compiler/builtins/bitcode" }
roc_bitcode_bc = { path = "crates/compiler/builtins/bitcode/bc" }

View file

@ -16,20 +16,20 @@ install-zig-llvm:
ARG ZIG_ARCH
FROM +install-other-libs
# zig
RUN wget -c https://ziglang.org/download/0.11.0/zig-linux-$ZIG_ARCH-0.11.0.tar.xz --no-check-certificate
RUN tar -xf zig-linux-$ZIG_ARCH-0.11.0.tar.xz
RUN ln -s /earthbuild/zig-linux-$ZIG_ARCH-0.11.0/zig /bin/zig
RUN wget -c https://ziglang.org/download/0.13.0/zig-linux-$ZIG_ARCH-0.13.0.tar.xz --no-check-certificate
RUN tar -xf zig-linux-$ZIG_ARCH-0.13.0.tar.xz
RUN ln -s /earthbuild/zig-linux-$ZIG_ARCH-0.13.0/zig /bin/zig
# zig builtins wasm tests
RUN apt -y install build-essential
# llvm
RUN apt -y install lsb-release software-properties-common gnupg
RUN wget https://apt.llvm.org/llvm.sh
RUN chmod +x llvm.sh
RUN ./llvm.sh 16
RUN ln -s /usr/bin/clang-16 /usr/bin/clang
RUN ./llvm.sh 18
RUN ln -s /usr/bin/clang-18 /usr/bin/clang
# use lld as linker
RUN ln -s /usr/bin/lld-16 /usr/bin/ld.lld
RUN apt -y install libpolly-16-dev # required by llvm-sys crate
RUN ln -s /usr/bin/lld-18 /usr/bin/ld.lld
RUN apt -y install libpolly-18-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 wget https://rustwasm.github.io/wasm-pack/installer/init.sh -O init.sh && sh init.sh

View file

@ -9,16 +9,20 @@ set -euxo pipefail
RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
BENCH_SUFFIX=$1
# copy the builtin *.zig files into crates/cli/tests/benchmarks/platform
# TODO replace this with a zig package once we have zig 0.13.0
cargo run --bin copy_zig_glue
cargo criterion -V
cd crates/cli && cargo criterion --no-run && cd ../..
mkdir -p bench-folder/crates/cli/tests/benchmarks
mkdir -p bench-folder/crates/compiler/builtins/bitcode/src
mkdir -p bench-folder/crates/cli/tests/benchmarks/
mkdir -p bench-folder/target/release/deps
cp "crates/cli/tests/benchmarks/"*".roc" bench-folder/crates/cli/tests/benchmarks/
mkdir -p bench-folder/target/release/lib
cp crates/cli/tests/benchmarks/*.roc bench-folder/crates/cli/tests/benchmarks/
cp -r crates/cli/tests/benchmarks/platform bench-folder/crates/cli/tests/benchmarks/
cp crates/compiler/builtins/bitcode/src/str.zig bench-folder/crates/compiler/builtins/bitcode/src
cp crates/compiler/builtins/bitcode/src/*.zig bench-folder/target/release/lib/
cp target/release/roc bench-folder/target/release
# copy the most recent time bench to bench-folder
cp target/release/deps/`ls -t target/release/deps/ | grep time_bench | head -n 1` bench-folder/target/release/deps/time_bench
mv bench-folder bench-folder-$BENCH_SUFFIX
mv bench-folder bench-folder-$BENCH_SUFFIX

View file

@ -54,6 +54,6 @@ fi
./jump-start.sh
# build the basic cli platform
roc build.roc
roc build.roc --linker=legacy
cd ..

View file

@ -7,14 +7,21 @@ set -euxo pipefail
strip ./target/release-with-lto/roc
strip ./target/release-with-lto/roc_language_server
mkdir -p $1 $1/examples $1/crates/compiler/builtins/bitcode
mkdir -p $1 $1/examples
mv target/release-with-lto/{roc,roc_language_server,lib} $1
mv LICENSE LEGAL_DETAILS $1
mv examples/{platform-switching,cli} $1/examples
mv examples/platform-switching $1/examples
mv crates/roc_std $1/crates
mv crates/compiler/builtins/bitcode/src $1/crates/compiler/builtins/bitcode
# temporary github.com/roc-lang/roc/pull/7231
rm $1/examples/platform-switching/rocLovesRust.roc
rm -rf $1/examples/platform-switching/rust-platform
# copy zig builtins
if [ ! -d "$1/examples/platform-switching/zig-platform/glue" ]; then
mkdir $1/examples/platform-switching/zig-platform/glue
mv crates/compiler/builtins/bitcode/src/* $1/examples/platform-switching/zig-platform/glue
fi
tar -czvf "$1.tar.gz" $1

View file

@ -17,13 +17,14 @@ fn roc_repl_session() -> Result<PtyReplSession, Error> {
fn main() -> Result<(), Error> {
let mut repl = roc_repl_session()?;
repl.exp_regex(".*roc repl.*")?;
repl.send_line("1+1")?;
thread::sleep(Duration::from_secs(1));
match repl.exp_regex(r".*2\u{1b}\[1;32m : \u{1b}\[0mNum *.*") { // 2 : Num
match repl.exp_regex(r".*2\u{1b}\[1;32m : \u{1b}\[0mNum *.*") {
// 2 : Num
Ok(_) => {
println!("Expected output received.");
return Ok(());
@ -33,4 +34,4 @@ fn main() -> Result<(), Error> {
std::process::exit(1);
}
}
}
}

View file

@ -43,6 +43,7 @@ sanitizers = ["roc_build/sanitizers"]
[dependencies]
copy_zig_glue.workspace = true
roc_build.workspace = true
roc_builtins.workspace = true
roc_can.workspace = true

View file

@ -871,24 +871,26 @@ pub fn build(
// so we don't want to spend time freeing these values
let arena = ManuallyDrop::new(Bump::new());
let opt_level = if let BuildConfig::BuildAndRunIfNoErrors = config {
OptLevel::Development
} else {
opt_level_from_flags(matches)
};
let opt_level = opt_level_from_flags(matches);
// Note: This allows using `--dev` with `--optimize`.
// This means frontend optimizations and dev backend.
let code_gen_backend = if matches.get_flag(FLAG_DEV) {
let should_run_expects = matches!(opt_level, OptLevel::Development | OptLevel::Normal) &&
// TODO: once expect is decoupled from roc launching the executable, remove this part of the conditional.
matches!(
config,
BuildConfig::BuildAndRun | BuildConfig::BuildAndRunIfNoErrors
);
let code_gen_backend = if matches!(opt_level, OptLevel::Development) {
if matches!(target.architecture(), Architecture::Wasm32) {
CodeGenBackend::Wasm
} else {
CodeGenBackend::Assembly(AssemblyBackendMode::Binary)
}
} else {
let backend_mode = match opt_level {
OptLevel::Development => LlvmBackendMode::BinaryDev,
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
let backend_mode = if should_run_expects {
LlvmBackendMode::BinaryWithExpect
} else {
LlvmBackendMode::Binary
};
CodeGenBackend::Llvm(backend_mode)
@ -1026,7 +1028,7 @@ pub fn build(
roc_run(
&arena,
path,
opt_level,
should_run_expects,
target,
args,
bytes,
@ -1069,7 +1071,7 @@ pub fn build(
roc_run(
&arena,
path,
opt_level,
should_run_expects,
target,
args,
bytes,
@ -1088,7 +1090,7 @@ pub fn build(
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
arena: &Bump,
script_path: &Path,
opt_level: OptLevel,
should_run_expects: bool,
target: Target,
args: I,
binary_bytes: &[u8],
@ -1130,7 +1132,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
_ => roc_run_native(
arena,
script_path,
opt_level,
should_run_expects,
args,
binary_bytes,
expect_metadata,
@ -1198,7 +1200,7 @@ fn make_argv_envp<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: &Bump,
script_path: &Path,
opt_level: OptLevel,
should_run_expects: bool,
args: I,
binary_bytes: &[u8],
expect_metadata: ExpectMetadata,
@ -1220,11 +1222,10 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
.chain([std::ptr::null()])
.collect_in(arena);
match opt_level {
OptLevel::Development => roc_dev_native(arena, executable, argv, envp, expect_metadata),
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => unsafe {
roc_run_native_fast(executable, &argv, &envp);
},
if should_run_expects {
roc_dev_native(arena, executable, argv, envp, expect_metadata);
} else {
unsafe { roc_run_native_fast(executable, &argv, &envp) };
}
Ok(1)
@ -1462,7 +1463,7 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: &Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
script_path: &Path,
opt_level: OptLevel,
should_run_expects: bool,
args: I,
binary_bytes: &[u8],
_expect_metadata: ExpectMetadata,
@ -1487,14 +1488,11 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
.chain([std::ptr::null()])
.collect_in(arena);
match opt_level {
OptLevel::Development => {
// roc_run_native_debug(executable, &argv, &envp, expectations, interns)
internal_error!("running `expect`s does not currently work on windows")
}
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
roc_run_native_fast(executable, &argv, &envp);
}
if should_run_expects {
// roc_run_native_debug(executable, &argv, &envp, expectations, interns)
internal_error!("running `expect`s does not currently work on windows");
} else {
roc_run_native_fast(executable, &argv, &envp);
}
}

View file

@ -254,7 +254,7 @@ fn main() -> io::Result<()> {
exit_code = problems.exit_code();
}
Err(LoadingProblem::FormattedReport(report)) => {
Err(LoadingProblem::FormattedReport(report, _)) => {
print!("{report}");
exit_code = 1;
@ -282,10 +282,11 @@ fn main() -> io::Result<()> {
) {
Ok((problems, total_time)) => {
problems.print_error_warning_count(total_time);
println!(".\n");
Ok(problems.exit_code())
}
Err(LoadingProblem::FormattedReport(report)) => {
Err(LoadingProblem::FormattedReport(report, _)) => {
print!("{report}");
Ok(1)

View file

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("glue").str;
const str = @import("glue/str.zig");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -27,7 +27,7 @@ const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
@ -99,23 +99,26 @@ fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}
const Unit = extern struct {};
pub fn main() !u8 {
pub export fn main() u8 {
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
const size = @max(@as(usize, @intCast(roc__mainForHost_1_exposed_size())), 8);
const raw_output = roc_alloc(@as(usize, @intCast(size)), @alignOf(u64)).?;
var output = @as([*]u8, @ptrCast(raw_output));
const raw_output = roc_alloc(@as(usize, @intCast(size)), @alignOf(u64)) orelse {
std.log.err("Memory allocation failed", .{});
return 1;
};
const output = @as([*]u8, @ptrCast(raw_output));
defer {
roc_dealloc(raw_output, @alignOf(u64));
@ -136,7 +139,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
const size = @max(roc__mainForHost_0_result_size(), 8);
const raw_output = allocator.alignedAlloc(u8, @alignOf(u64), @as(usize, @intCast(size))) catch unreachable;
var output = @as([*]u8, @ptrCast(raw_output));
const output = @as([*]u8, @ptrCast(raw_output));
defer {
allocator.free(raw_output);

View file

@ -50,7 +50,7 @@ mod cli_tests {
const TEST_LEGACY_LINKER: bool = false;
#[test]
#[ignore = "Works when run manually, but not in CI"]
#[ignore = "Needs investigation, see also github.com/roc-lang/roc/pull/7231"]
fn platform_switching_rust() {
// pre-build the platform
std::process::Command::new("bash")
@ -76,6 +76,8 @@ mod cli_tests {
#[test]
#[cfg_attr(windows, ignore)]
fn platform_switching_zig() {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root("examples/platform-switching", "rocLovesZig.roc"),
@ -96,6 +98,8 @@ mod cli_tests {
#[test]
fn platform_switching_wasm() {
copy_zig_glue::initialize_zig_test_platforms();
// this is a web assembly example, but we don't test with JS at the moment
// so let's just check it for now
let cli_check = ExecCli::new(
@ -113,6 +117,8 @@ mod cli_tests {
ignore = "Flaky failure: Roc command failed with status ExitStatus(ExitStatus(3221225477))"
)]
fn fibonacci() {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root("crates/cli/tests/test-projects/algorithms", "fibonacci.roc"),
@ -134,6 +140,8 @@ mod cli_tests {
#[test]
#[cfg_attr(windows, ignore)]
fn quicksort() {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root("crates/cli/tests/test-projects/algorithms", "quicksort.roc"),
@ -184,6 +192,8 @@ mod cli_tests {
#[cfg_attr(windows, ignore)]
// tea = The Elm Architecture
fn terminal_ui_tea() {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root("crates/cli/tests/test-projects/tui", "main.roc"),
@ -202,31 +212,34 @@ mod cli_tests {
);
}
// TODO check this out, there's more that's going wrong than a segfault
//#[test]
/*#[cfg_attr(
any(target_os = "windows", target_os = "linux", target_os = "macos"),
ignore = "Segfault, likely broken because of alias analysis: https://github.com/roc-lang/roc/issues/6544"
)]*/
/*
#[test]
#[cfg_attr(windows, ignore)]
fn false_interpreter() {
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root("crates/cli/tests/test-projects/false-interpreter", "main.roc")
)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG);
CMD_BUILD,
file_from_root(
"crates/cli/tests/test-projects/false-interpreter",
"main.roc",
),
)
.arg(BUILD_HOST_FLAG)
.arg(SUPPRESS_BUILD_HOST_WARNING_FLAG);
let sqrt_false_path_buf = file_from_root("crates/cli/tests/test-projects/false-interpreter/examples", "sqrt.false");
let sqrt_false_path_buf = file_from_root(
"crates/cli/tests/test-projects/false-interpreter/examples",
"sqrt.false",
);
let app_args = ["--",
sqrt_false_path_buf
.as_path()
.to_str()
.unwrap()];
let app_args = [sqrt_false_path_buf.as_path().to_str().unwrap()];
cli_build.full_check_build_and_run("1414", TEST_LEGACY_LINKER, ALLOW_VALGRIND, None, Some(&app_args));
}*/
cli_build.full_check_build_and_run(
"1414",
TEST_LEGACY_LINKER,
ALLOW_VALGRIND,
None,
Some(&app_args),
);
}
#[test]
#[cfg_attr(windows, ignore)]
@ -266,6 +279,8 @@ mod cli_tests {
#[test]
#[cfg_attr(windows, ignore)]
fn multiple_exposed() {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root(
@ -461,6 +476,8 @@ mod cli_tests {
/// Build the platform host once for all tests in this module
fn build_platform_host() {
BUILD_PLATFORM_HOST.call_once(|| {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root(
@ -800,6 +817,8 @@ mod cli_tests {
/// Build the platform host once for all tests in this module
fn build_platform_host() {
BUILD_PLATFORM_HOST.call_once(|| {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root(
@ -1031,6 +1050,8 @@ mod cli_tests {
/// Build the platform host once for all tests in this module
fn build_platform_host() {
BUILD_PLATFORM_HOST.call_once(|| {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root("crates/cli/tests/benchmarks/platform", "app.roc"),
@ -1284,6 +1305,7 @@ mod cli_tests {
}
#[test]
#[ignore = "flaky currently due to 7022"]
fn known_type_error() {
let cli_check = ExecCli::new(
CMD_CHECK,
@ -1318,6 +1340,8 @@ mod cli_tests {
#[cfg_attr(windows, ignore)]
/// this tests that a platform can correctly import a package
fn platform_requires_pkg() {
copy_zig_glue::initialize_zig_test_platforms();
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root(

View file

@ -1,6 +1,7 @@
---
source: crates/cli/tests/cli_tests.rs
expression: cli_check_out.normalize_stdout_and_stderr()
snapshot_kind: text
---
── MISSING DEFINITION in tests/test-projects/known_bad/ExposedNotDefined.roc ───
@ -12,4 +13,5 @@ from exposes.
────────────────────────────────────────────────────────────────────────────────
1 error and 0 warning found in <ignored for test> ms
1 error and 0 warnings found in <ignored for test> ms.

View file

@ -1,6 +1,7 @@
---
source: crates/cli/tests/cli_tests.rs
expression: cli_check_out.normalize_stdout_and_stderr()
snapshot_kind: text
---
── TYPE MISMATCH in tests/test-projects/known_bad/TypeError.roc ────────────────
@ -25,4 +26,5 @@ this out.
────────────────────────────────────────────────────────────────────────────────
1 error and 0 warning found in <ignored for test> ms
1 error and 0 warnings found in <ignored for test> ms.

View file

@ -1,6 +1,7 @@
---
source: crates/cli/tests/cli_tests.rs
expression: cli_check_out.normalize_stdout_and_stderr()
snapshot_kind: text
---
── UNUSED IMPORT in ...nown_bad/UnusedImportButWithALongFileNameForTesting.roc ─
@ -14,4 +15,5 @@ Since Symbol isn't used, you don't need to import it.
────────────────────────────────────────────────────────────────────────────────
0 error and 1 warning found in <ignored for test> ms
0 errors and 1 warning found in <ignored for test> ms.

View file

@ -1,6 +1,7 @@
---
source: crates/cli/tests/cli_tests.rs
expression: cli_check_out.normalize_stdout_and_stderr()
snapshot_kind: text
---
── UNUSED IMPORT in tests/test-projects/known_bad/UnusedImport.roc ─────────────
@ -14,4 +15,4 @@ Since Symbol isn't used, you don't need to import it.
────────────────────────────────────────────────────────────────────────────────
0 error and 1 warning found in <ignored for test> ms
0 errors and 1 warning found in <ignored for test> ms.

View file

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("glue").str;
const str = @import("glue/str.zig");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -25,7 +25,7 @@ const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
@ -97,13 +97,13 @@ fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}

View file

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("glue").str;
const str = @import("glue/str.zig");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -22,7 +22,7 @@ const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
@ -94,13 +94,13 @@ fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}
@ -125,14 +125,14 @@ pub export fn main() u8 {
numbers[i] = @mod(@as(i64, @intCast(i)), 12);
}
var roc_list = RocList{ .elements = numbers, .length = NUM_NUMS, .capacity = NUM_NUMS };
const roc_list = RocList{ .elements = numbers, .length = NUM_NUMS, .capacity = NUM_NUMS };
// actually call roc to populate the callresult
const callresult: RocList = roc__mainForHost_1_exposed(roc_list);
// stdout the result
const length = @min(20, callresult.length);
var result = callresult.elements[0..length];
const result = callresult.elements[0..length];
for (result, 0..) |x, i| {
if (i == 0) {

View file

@ -1,4 +1,4 @@
module [Context, Data, with, getChar, Option, pushStack, popStack, toStr, inWhileScope]
module [Context, Data, with!, getChar!, Option, pushStack, popStack, toStr, inWhileScope]
import pf.File
import Variable exposing [Variable]
@ -20,13 +20,13 @@ pushStack = \ctx, data ->
# I think an open tag union should just work here.
# Instead at a call sites, I need to match on the error and then return the same error.
# Otherwise it hits unreachable code in ir.rs
popStack : Context -> Result [T Context Data] [EmptyStack]
popStack : Context -> Result (Context, Data) [EmptyStack]
popStack = \ctx ->
when List.last ctx.stack is
Ok val ->
poppedCtx = { ctx & stack: List.dropAt ctx.stack (List.len ctx.stack - 1) }
Ok (T poppedCtx val)
Ok (poppedCtx, val)
Err ListWasEmpty ->
Err EmptyStack
@ -58,30 +58,30 @@ toStr = \{ scopes, stack, state, vars } ->
"\n============\nDepth: $(depth)\nState: $(stateStr)\nStack: [$(stackStr)]\nVars: [$(varsStr)]\n============\n"
with : Str, (Context -> Task {} a) -> Task {} a
with = \path, callback ->
File.withOpen path \handle ->
with! : Str, (Context => a) => a
with! = \path, callback! ->
File.withOpen! path \handle ->
# I cant define scope here and put it in the list in callback. It breaks alias anaysis.
# Instead I have to inline this.
# root_scope = { data: Some handle, index: 0, buf: [], whileInfo: None }
callback { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount }
callback! { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount }
# I am pretty sure there is a syntax to destructure and keep a reference to the whole, but Im not sure what it is.
getChar : Context -> Task [T U8 Context] [EndOfData, NoScope]
getChar = \ctx ->
getChar! : Context => Result (U8, Context) [EndOfData, NoScope]
getChar! = \ctx ->
when List.last ctx.scopes is
Ok scope ->
(T val newScope) = getCharScope! scope
Task.ok (T val { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope })
(val, newScope) = getCharScope!? scope
Ok (val, { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope })
Err ListWasEmpty ->
Task.err NoScope
Err NoScope
getCharScope : Scope -> Task [T U8 Scope] [EndOfData, NoScope]
getCharScope = \scope ->
getCharScope! : Scope => Result (U8, Scope) [EndOfData, NoScope]
getCharScope! = \scope ->
when List.get scope.buf scope.index is
Ok val ->
Task.ok (T val { scope & index: scope.index + 1 })
Ok (val, { scope & index: scope.index + 1 })
Err OutOfBounds ->
when scope.data is
@ -90,13 +90,13 @@ getCharScope = \scope ->
when List.first bytes is
Ok val ->
# This starts at 1 because the first character is already being returned.
Task.ok (T val { scope & buf: bytes, index: 1 })
Ok (val, { scope & buf: bytes, index: 1 })
Err ListWasEmpty ->
Task.err EndOfData
Err EndOfData
None ->
Task.err EndOfData
Err EndOfData
inWhileScope : Context -> Bool
inWhileScope = \ctx ->

View file

@ -31,4 +31,3 @@ fromUtf8 = \char ->
toIndex : Variable -> U64
toIndex = \@Variable char ->
Num.intCast (char - 0x61) # "a"
# List.first (Str.toUtf8 "a")

View file

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

View file

@ -18,6 +18,6 @@ path = "src/main.rs"
[dependencies]
libc = "0.2"
roc_std = { path = "../../../../roc_std/" }
roc_std = { path = "../../../../../roc_std/" }
[workspace]

View file

@ -1,34 +1,30 @@
module [line, withOpen, chunk, Handle]
module [line!, withOpen!, chunk!, Handle]
import pf.PlatformTasks
import pf.Host
Handle := U64
line : Handle -> Task Str *
line = \@Handle handle ->
PlatformTasks.getFileLine handle
|> Task.mapErr \_ -> crash "unreachable File.line"
line! : Handle => Str
line! = \@Handle handle ->
Host.getFileLine! handle
chunk : Handle -> Task (List U8) *
chunk = \@Handle handle ->
PlatformTasks.getFileBytes handle
|> Task.mapErr \_ -> crash "unreachable File.chunk"
chunk! : Handle => List U8
chunk! = \@Handle handle ->
Host.getFileBytes! handle
open : Str -> Task Handle *
open = \path ->
PlatformTasks.openFile path
|> Task.mapErr \_ -> crash "unreachable File.open"
|> Task.map @Handle
open! : Str => Handle
open! = \path ->
Host.openFile! path
|> @Handle
close : Handle -> Task.Task {} *
close = \@Handle handle ->
PlatformTasks.closeFile handle
|> Task.mapErr \_ -> crash "unreachable File.close"
close! : Handle => {}
close! = \@Handle handle ->
Host.closeFile! handle
withOpen : Str, (Handle -> Task {} a) -> Task {} a
withOpen = \path, callback ->
withOpen! : Str, (Handle => a) => a
withOpen! = \path, callback! ->
handle = open! path
result = callback handle |> Task.result!
result = callback! handle
close! handle
Task.fromResult result
result

View file

@ -0,0 +1,19 @@
hosted Host
exposes [openFile!, closeFile!, getFileLine!, getFileBytes!, putLine!, putRaw!, getLine!, getChar!]
imports []
openFile! : Str => U64
closeFile! : U64 => {}
getFileLine! : U64 => Str
getFileBytes! : U64 => List U8
putLine! : Str => {}
putRaw! : Str => {}
getLine! : {} => Str
getChar! : {} => U8

View file

@ -1,21 +0,0 @@
hosted PlatformTasks
exposes [openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getLine, getChar]
imports []
openFile : Str -> Task U64 {}
closeFile : U64 -> Task {} {}
withFileOpen : Str, (U64 -> Task ok err) -> Task {} {}
getFileLine : U64 -> Task Str {}
getFileBytes : U64 -> Task (List U8) {}
putLine : Str -> Task {} {}
putRaw : Str -> Task {} {}
getLine : Task Str {}
getChar : Task U8 {}

View file

@ -1,16 +1,11 @@
module [
line,
char,
]
module [line!, char!]
import pf.PlatformTasks
import pf.Host
line : {} -> Task Str *
line = \{} ->
PlatformTasks.getLine
|> Task.mapErr \_ -> crash "unreachable Stdin.line"
line! : {} => Str
line! = \{} ->
Host.getLine! {}
char : {} -> Task U8 *
char = \{} ->
PlatformTasks.getChar
|> Task.mapErr \_ -> crash "unreachable Stdin.char"
char! : {} => U8
char! = \{} ->
Host.getChar! {}

View file

@ -1,13 +1,11 @@
module [line, raw]
module [line!, raw!]
import pf.PlatformTasks
import pf.Host
line : Str -> Task {} *
line = \text ->
PlatformTasks.putLine text
|> Task.mapErr \_ -> crash "unreachable Stdout.line"
line! : Str => {}
line! = \text ->
Host.putLine! text
raw : Str -> Task {} *
raw = \text ->
PlatformTasks.putRaw text
|> Task.mapErr \_ -> crash "unreachable Stdout.raw"
raw! : Str => {}
raw! = \text ->
Host.putRaw! text

View file

@ -1,5 +1,3 @@
#include <stdio.h>
extern int rust_main();
int main() {

View file

@ -1,9 +1,9 @@
platform "false-interpreter"
requires {} { main : Str -> Task {} [] }
requires {} { main! : Str => {} }
exposes []
packages {}
imports []
provides [mainForHost]
provides [mainForHost!]
mainForHost : Str -> Task {} []
mainForHost = \file -> main file
mainForHost! : Str => {}
mainForHost! = \file -> main! file

View file

@ -1,9 +1,8 @@
#![allow(non_snake_case)]
use core::ffi::c_void;
use core::mem::MaybeUninit;
use libc;
use roc_std::{RocList, RocResult, RocStr};
use roc_std::{RocList, RocStr};
use std::collections::HashMap;
use std::env;
use std::fs::File;
@ -21,19 +20,7 @@ fn file_handles() -> &'static Mutex<HashMap<u64, BufReader<File>>> {
extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(output: *mut u8, args: &RocStr);
#[link_name = "roc__mainForHost_1_exposed_size"]
fn roc_main_size() -> i64;
#[link_name = "roc__mainForHost_0_caller"]
fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8);
#[link_name = "roc__mainForHost_0_size"]
fn size_Fx() -> i64;
#[link_name = "roc__mainForHost_0_result_size"]
fn size_Fx_result() -> i64;
fn roc_main(void: *const c_void, args: *mut RocStr);
}
#[no_mangle]
@ -114,88 +101,57 @@ pub extern "C" fn rust_main() -> i32 {
let arg = env::args()
.nth(1)
.expect("Please pass a .false file as a command-line argument to the false interpreter!");
let arg = RocStr::from(arg.as_str());
let mut arg = RocStr::from(arg.as_str());
let size = unsafe { roc_main_size() } as usize;
unsafe { roc_main(std::ptr::null(), &mut arg) };
std::mem::forget(arg);
unsafe {
let buffer = roc_alloc(size, 1) as *mut u8;
roc_main(buffer, &arg);
// arg has been passed to roc now, and it assumes ownership.
// so we must not touch its refcount now
std::mem::forget(arg);
let result = call_the_closure(buffer);
roc_dealloc(buffer as _, 1);
result
};
// This really shouldn't need to be freed, but valgrid is picky about possibly lost.
*file_handles().lock().unwrap() = HashMap::default();
// Exit code
0
}
unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
let size = size_Fx_result() as usize;
let buffer = roc_alloc(size, 1) as *mut u8;
call_Fx(
// This flags pointer will never get dereferenced
MaybeUninit::uninit().as_ptr(),
closure_data_ptr as *const u8,
buffer as *mut u8,
);
roc_dealloc(buffer as _, 1);
0
}
#[no_mangle]
pub extern "C" fn roc_fx_getLine() -> RocResult<RocStr, ()> {
pub extern "C" fn roc_fx_getLine() -> RocStr {
let stdin = std::io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocResult::ok(RocStr::from(line1.as_str()))
RocStr::from(line1.as_str())
}
#[no_mangle]
pub extern "C" fn roc_fx_getChar() -> RocResult<u8, ()> {
pub extern "C" fn roc_fx_getChar() -> u8 {
let mut buffer = [0];
if let Err(ioerr) = std::io::stdin().lock().read_exact(&mut buffer[..]) {
if ioerr.kind() == std::io::ErrorKind::UnexpectedEof {
RocResult::ok(u8::MAX)
u8::MAX
} else {
panic!("Got an unexpected error while reading char from stdin");
}
} else {
RocResult::ok(buffer[0])
buffer[0]
}
}
#[no_mangle]
pub extern "C" fn roc_fx_putLine(line: &RocStr) -> RocResult<(), ()> {
pub extern "C" fn roc_fx_putLine(line: &RocStr) {
let string = line.as_str();
println!("{}", string);
let _ = std::io::stdout().lock().flush();
RocResult::ok(())
}
#[no_mangle]
pub extern "C" fn roc_fx_putRaw(line: &RocStr) -> RocResult<(), ()> {
pub extern "C" fn roc_fx_putRaw(line: &RocStr) {
let string = line.as_str();
print!("{}", string);
let _ = std::io::stdout().lock().flush();
RocResult::ok(())
}
#[no_mangle]
pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocResult<RocStr, ()> {
pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocStr {
let mut br_map = file_handles().lock().unwrap();
let br = br_map.get_mut(&br_id).unwrap();
let mut line1 = String::default();
@ -203,11 +159,11 @@ pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocResult<RocStr, ()> {
br.read_line(&mut line1)
.expect("Failed to read line from file");
RocResult::ok(RocStr::from(line1.as_str()))
RocStr::from(line1.as_str())
}
#[no_mangle]
pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocResult<RocList<u8>, ()> {
pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocList<u8> {
let mut br_map = file_handles().lock().unwrap();
let br = br_map.get_mut(&br_id).unwrap();
let mut buffer = [0; 0x10 /* This is intentionally small to ensure correct implementation */];
@ -216,18 +172,16 @@ pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocResult<RocList<u8>, ()>
.read(&mut buffer[..])
.expect("Failed to read bytes from file");
RocResult::ok(RocList::from_slice(&buffer[..count]))
RocList::from_slice(&buffer[..count])
}
#[no_mangle]
pub extern "C" fn roc_fx_closeFile(br_id: u64) -> RocResult<(), ()> {
pub extern "C" fn roc_fx_closeFile(br_id: u64) {
file_handles().lock().unwrap().remove(&br_id);
RocResult::ok(())
}
#[no_mangle]
pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult<u64, ()> {
pub extern "C" fn roc_fx_openFile(name: &RocStr) -> u64 {
let string = name.as_str();
match File::open(string) {
Ok(f) => {
@ -236,7 +190,7 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult<u64, ()> {
file_handles().lock().unwrap().insert(br_id, br);
RocResult::ok(br_id)
br_id
}
Err(_) => {
panic!(
@ -246,17 +200,3 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult<u64, ()> {
}
}
}
#[no_mangle]
pub extern "C" fn roc_fx_withFileOpen(_name: &RocStr, _buffer: *const u8) -> RocResult<(), ()> {
// TODO: figure out accepting a closure in an fx and passing data to it.
// let f = File::open(name.as_str()).expect("Unable to open file");
// let mut br = BufReader::new(f);
// unsafe {
// let closure_data_ptr = buffer.offset(8);
// call_the_closure(closure_data_ptr);
// }
RocResult::ok(())
}

View file

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("glue").str;
const str = @import("glue/str.zig");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -24,7 +24,7 @@ const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
@ -96,13 +96,13 @@ fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}

View file

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("glue").str;
const str = @import("glue/str.zig");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -17,7 +17,7 @@ const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
@ -87,14 +87,14 @@ fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}
@ -105,7 +105,7 @@ extern fn roc__mainForHost_1_exposed_generic(*RocStr) void;
const Unit = extern struct {};
pub fn main() u8 {
pub export fn main() u8 {
const stdout = std.io.getStdOut().writer();
// actually call roc to populate the callresult

View file

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("glue").str;
const str = @import("glue/str.zig");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -28,7 +28,7 @@ const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
@ -95,13 +95,13 @@ fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}
@ -113,7 +113,7 @@ pub export fn main() u8 {
// NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes
const size = @max(8, @as(usize, @intCast(roc__mainForHost_1_exposed_size())));
const raw_output = allocator.alignedAlloc(u8, @alignOf(u64), @as(usize, @intCast(size))) catch unreachable;
var output = @as([*]u8, @ptrCast(raw_output));
const output = @as([*]u8, @ptrCast(raw_output));
defer {
allocator.free(raw_output);

View file

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("glue").str;
const str = @import("glue/str.zig");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -79,13 +79,13 @@ fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}

View file

@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const str = @import("glue").str;
const str = @import("glue/str.zig");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -25,7 +25,7 @@ extern fn roc__mainForHost_0_result_size() i64;
fn allocate_model(allocator: *Allocator) MutModel {
const size = roc__mainForHost_0_result_size();
const raw_output = allocator.alignedAlloc(u8, @alignOf(u64), @as(usize, @intCast(size))) catch unreachable;
var output = @as([*]u8, @ptrCast(raw_output));
const output = @as([*]u8, @ptrCast(raw_output));
return output;
}
@ -86,7 +86,7 @@ const DEBUG: bool = false;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
if (DEBUG) {
var ptr = malloc(size);
const ptr = malloc(size);
const stdout = std.io.getStdOut().writer();
stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
return ptr;
@ -158,13 +158,13 @@ fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_
comptime {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}

View file

@ -358,7 +358,16 @@ where
match opt_level {
OptLevel::Development | OptLevel::Normal => morphic_lib::solve_trivial(program),
OptLevel::Optimize | OptLevel::Size => morphic_lib::solve(program),
// TODO(#7367): Change this back to `morphic_lib::solve`.
// For now, using solve_trivial to avoid bug with loops.
// Note: when disabling this, there was not much of a change in performance.
// Notably, NQueens was about 5% slower. False interpreter was 0-5% faster (depending on input).
// cFold and derive saw minor gains ~1.5%. rBTreeCk saw a big gain of ~4%.
// This feels wrong, morphic should not really be able to slow down code.
// Likely, noise or the bug and wrong inplace mutation lead to these perf changes.
// When re-enabling this, we should analysis the perf and inplace mutations of a few apps.
// It might be the case that our current benchmarks just aren't affected by morphic much.
OptLevel::Optimize | OptLevel::Size => morphic_lib::solve_trivial(program),
}
}
@ -1026,10 +1035,10 @@ fn lowlevel_spec<'a>(
let _unit1 = builder.add_touch(block, cell)?;
let _unit2 = builder.add_update(block, update_mode_var, cell)?;
builder.add_bag_insert(block, bag, to_insert)?;
let new_bag = builder.add_bag_insert(block, bag, to_insert)?;
let old_value = builder.add_bag_get(block, bag)?;
let new_list = with_new_heap_cell(builder, block, bag)?;
let old_value = builder.add_bag_get(block, new_bag)?;
let new_list = with_new_heap_cell(builder, block, new_bag)?;
// depending on the types, the list or value will come first in the struct
let fields = match interner.get_repr(layout) {

View file

@ -3,5 +3,6 @@
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod link;
pub mod llvm_passes;
pub mod program;
pub mod target;

View file

@ -80,21 +80,6 @@ pub fn get_relative_path(sub_path: &Path) -> Option<PathBuf> {
None
}
fn find_zig_glue_path() -> PathBuf {
// First try using the repo path relative to the executable location.
let path = get_relative_path(Path::new("crates/compiler/builtins/bitcode/src/glue.zig"));
if let Some(path) = path {
return path;
}
// Fallback on a lib path relative to the executable location.
let path = get_relative_path(Path::new("lib/glue.zig"));
if let Some(path) = path {
return path;
}
internal_error!("cannot find `glue.zig`. Check the source code in find_zig_glue_path() to show all the paths I tried.")
}
fn find_wasi_libc_path() -> PathBuf {
// This path is available when built and run from source
// Environment variable defined in wasi-libc-sys/build.rs
@ -148,10 +133,6 @@ pub fn build_zig_host_native(
zig_cmd.args([
zig_host_src,
&format!("-femit-bin={emit_bin}"),
"--mod",
&format!("glue::{}", find_zig_glue_path().to_str().unwrap()),
"--deps",
"glue",
// include libc
"-lc",
// cross-compile?
@ -220,10 +201,6 @@ pub fn build_zig_host_native(
zig_cmd.args(&[
zig_host_src,
&format!("-femit-bin={}", emit_bin),
"--mod",
&format!("glue::{}", find_zig_glue_path().to_str().unwrap()),
"--deps",
"glue",
// include the zig runtime
// "-fcompiler-rt", compiler-rt causes segfaults on windows; investigate why
// include libc
@ -267,15 +244,8 @@ pub fn build_zig_host_wasm32(
"build-obj",
zig_host_src,
emit_bin,
"--mod",
&format!("glue::{}", find_zig_glue_path().to_str().unwrap()),
"--deps",
"glue",
// include the zig runtime
// "-fcompiler-rt",
// include libc
"--library",
"c",
"-target",
"wasm32-wasi",
// "-femit-llvm-ir=/home/folkertdev/roc/roc/crates/cli/tests/benchmarks/platform/host.ll",
@ -1206,10 +1176,6 @@ fn link_wasm32(
&format!("-femit-bin={}", output_path.to_str().unwrap()),
"-target",
"wasm32-wasi-musl",
"--mod",
&format!("glue::{}", find_zig_glue_path().to_str().unwrap()),
"--deps",
"glue",
"-fstrip",
"-O",
"ReleaseSmall",
@ -1237,10 +1203,6 @@ fn link_windows(
&format!("-femit-bin={}", output_path.to_str().unwrap()),
"-target",
"native",
"--mod",
&format!("glue::{}", find_zig_glue_path().to_str().unwrap()),
"--deps",
"glue",
"-O",
"Debug",
"-dynamic",
@ -1389,7 +1351,7 @@ fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_count
if !cmd_output.status.success() {
match std::str::from_utf8(&cmd_output.stderr) {
Ok(stderr) => {
// flaky error seen on macos 12 apple silicon, related to https://github.com/ziglang/zig/issues/9711
// flaky error seen on macos 12 apple silicon, related to https://github.com/ziglang/zig/issues/20501
if stderr.contains("unable to save cached ZIR code") {
if flaky_fail_counter < max_flaky_fail_count {
run_build_command(command, file_to_build, flaky_fail_counter + 1)

View file

@ -0,0 +1,83 @@
use roc_error_macros::internal_error;
use roc_mono::ir::OptLevel;
use roc_target::Target;
use std::path::Path;
// generated using "opt --passes="default<Oz>" --print-pipeline-passes example.ll"
//
// we also include a "globaldce" pass at the beginning of each pipeline here, which is required
// to prevent bugs with the surgical linker, and also improves the build time.
pub fn get_llvm_passes_str(opt_level: OptLevel) -> &'static str {
match opt_level {
OptLevel::Development | OptLevel::Normal => {
"globaldce,always-inline,coro-cond(coro-early,cgscc(coro-split),coro-cleanup,globaldce),function(annotation-remarks),verify"
},
OptLevel::Size => {
"globaldce,annotation2metadata,forceattrs,inferattrs,coro-early,function<eager-inv>(lower-expect,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;no-switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>,sroa<modify-cfg>,early-cse<>),openmp-opt,ipsccp,called-value-propagation,globalopt,function<eager-inv>(mem2reg,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>),always-inline,require<globals-aa>,function(invalidate<aa>),require<profile-summary>,cgscc(devirt<4>(inline,function-attrs<skip-non-recursive-function-attrs>,function<eager-inv;no-rerun>(sroa<modify-cfg>,early-cse<memssa>,speculative-execution<only-if-divergent-target>,jump-threading,correlated-propagation,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,aggressive-instcombine,tailcallelim,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>,reassociate,constraint-elimination,loop-mssa(loop-instsimplify,loop-simplifycfg,licm<no-allowspeculation>,loop-rotate<no-header-duplication;no-prepare-for-lto>,licm<allowspeculation>,simple-loop-unswitch<no-nontrivial;trivial>),simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,loop(loop-idiom,indvars,loop-deletion,loop-unroll-full),sroa<modify-cfg>,vector-combine,mldst-motion<no-split-footer-bb>,gvn<>,sccp,bdce,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,jump-threading,correlated-propagation,adce,memcpyopt,dse,move-auto-init,loop-mssa(licm<allowspeculation>),coro-elide,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;hoist-common-insts;sink-common-insts;speculate-blocks;simplify-cond-branch>,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>),function-attrs,function(require<should-not-run-function-passes>),coro-split)),deadargelim,coro-cleanup,globalopt,globaldce,elim-avail-extern,rpo-function-attrs,recompute-globalsaa,function<eager-inv>(float2int,lower-constant-intrinsics,loop(loop-rotate<no-header-duplication;no-prepare-for-lto>,loop-deletion),loop-distribute,inject-tli-mappings,loop-vectorize<no-interleave-forced-only;vectorize-forced-only;>,infer-alignment,loop-load-elim,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,simplifycfg<bonus-inst-threshold=1;forward-switch-cond;switch-range-to-icmp;switch-to-lookup;no-keep-loops;hoist-common-insts;sink-common-insts;speculate-blocks;simplify-cond-branch>,vector-combine,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,loop-unroll<O2>,transform-warning,sroa<preserve-cfg>,infer-alignment,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,loop-mssa(licm<allowspeculation>),alignment-from-assumptions,loop-sink,instsimplify,div-rem-pairs,tailcallelim,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>),globaldce,constmerge,cg-profile,rel-lookup-table-converter,function(annotation-remarks),verify"
},
OptLevel::Optimize => {
"globaldce,annotation2metadata,forceattrs,inferattrs,coro-early,function<eager-inv>(lower-expect,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;no-switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>,sroa<modify-cfg>,early-cse<>,callsite-splitting),openmp-opt,ipsccp,called-value-propagation,globalopt,function<eager-inv>(mem2reg,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>),always-inline,require<globals-aa>,function(invalidate<aa>),require<profile-summary>,cgscc(devirt<4>(inline,function-attrs<skip-non-recursive-function-attrs>,argpromotion,openmp-opt-cgscc,function<eager-inv;no-rerun>(sroa<modify-cfg>,early-cse<memssa>,speculative-execution<only-if-divergent-target>,jump-threading,correlated-propagation,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,aggressive-instcombine,libcalls-shrinkwrap,tailcallelim,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>,reassociate,constraint-elimination,loop-mssa(loop-instsimplify,loop-simplifycfg,licm<no-allowspeculation>,loop-rotate<header-duplication;no-prepare-for-lto>,licm<allowspeculation>,simple-loop-unswitch<nontrivial;trivial>),simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,loop(loop-idiom,indvars,loop-deletion,loop-unroll-full),sroa<modify-cfg>,vector-combine,mldst-motion<no-split-footer-bb>,gvn<>,sccp,bdce,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,jump-threading,correlated-propagation,adce,memcpyopt,dse,move-auto-init,loop-mssa(licm<allowspeculation>),coro-elide,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;hoist-common-insts;sink-common-insts;speculate-blocks;simplify-cond-branch>,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>),function-attrs,function(require<should-not-run-function-passes>),coro-split)),deadargelim,coro-cleanup,globalopt,globaldce,elim-avail-extern,rpo-function-attrs,recompute-globalsaa,function<eager-inv>(float2int,lower-constant-intrinsics,chr,loop(loop-rotate<header-duplication;no-prepare-for-lto>,loop-deletion),loop-distribute,inject-tli-mappings,loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>,infer-alignment,loop-load-elim,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,simplifycfg<bonus-inst-threshold=1;forward-switch-cond;switch-range-to-icmp;switch-to-lookup;no-keep-loops;hoist-common-insts;sink-common-insts;speculate-blocks;simplify-cond-branch>,slp-vectorizer,vector-combine,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,loop-unroll<O3>,transform-warning,sroa<preserve-cfg>,infer-alignment,instcombine<max-iterations=1;no-use-loop-info;no-verify-fixpoint>,loop-mssa(licm<allowspeculation>),alignment-from-assumptions,loop-sink,instsimplify,div-rem-pairs,tailcallelim,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>),globaldce,constmerge,cg-profile,rel-lookup-table-converter,function(annotation-remarks),verify"
},
}
}
pub fn optimize_llvm_ir(
env: &roc_gen_llvm::llvm::build::Env,
target: Target,
opt_level: OptLevel,
emit_debug_info: bool,
ll_file_path: &Path,
) {
env.dibuilder.finalize();
if !emit_debug_info {
env.module.strip_debug_info();
}
// TODO: double check how much time is spent verifying here and below.
// For real compilation, we may not want to pay the cost.
// Verify the module before optimizing
if let Err(errors) = env.module.verify() {
// write the ll code to a file, so we can modify it
env.module.print_to_file(ll_file_path).unwrap();
internal_error!(
"😱 LLVM errors when defining module; I wrote the full LLVM IR to {:?}\n\n {}",
ll_file_path,
errors.to_string(),
);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
let inkwell_opt_level = crate::target::convert_opt_level(opt_level);
let inkwell_llvm_passes = get_llvm_passes_str(opt_level);
let inkwell_target_machine =
crate::target::target_machine(target, inkwell_opt_level, inkwell::targets::RelocMode::PIC)
.unwrap_or_else(|| internal_error!("invalid target machine"));
env.module
.run_passes(
inkwell_llvm_passes,
&inkwell_target_machine,
inkwell::passes::PassBuilderOptions::create(),
)
.unwrap_or_else(|e| internal_error!("invalid llvm optimization passes: {:?}", e));
// Verify the module after optimizing
if let Err(errors) = env.module.verify() {
// write the ll code to a file, so we can modify it
env.module.print_to_file(ll_file_path).unwrap();
internal_error!(
"😱 LLVM errors when optimizing module; I wrote the full LLVM IR to {:?}\n\n {}",
ll_file_path,
errors.to_string(),
);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
}

View file

@ -204,7 +204,6 @@ fn gen_from_mono_module_llvm<'a>(
let builder = context.create_builder();
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
// Compile and add all the Procs before adding main
let env = roc_gen_llvm::llvm::build::Env {
@ -264,31 +263,7 @@ fn gen_from_mono_module_llvm<'a>(
let generate_final_ir = all_code_gen_start.elapsed();
let code_gen_object_start = Instant::now();
env.dibuilder.finalize();
if !emit_debug_info {
module.strip_debug_info();
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
mpm.run_on(module);
// Verify the module
if let Err(errors) = env.module.verify() {
// write the ll code to a file, so we can modify it
env.module.print_to_file(&app_ll_file).unwrap();
internal_error!(
"😱 LLVM errors when defining module; I wrote the full LLVM IR to {:?}\n\n {}",
app_ll_file,
errors.to_string(),
);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
crate::llvm_passes::optimize_llvm_ir(&env, target, opt_level, emit_debug_info, &app_ll_file);
let gen_sanitizers = cfg!(feature = "sanitizers") && std::env::var("ROC_SANITIZERS").is_ok();
let memory_buffer = if fuzz || gen_sanitizers {
@ -702,7 +677,7 @@ pub fn handle_error_module(
pub fn handle_loading_problem(problem: LoadingProblem) -> std::io::Result<i32> {
match problem {
LoadingProblem::FormattedReport(report) => {
LoadingProblem::FormattedReport(report, _) => {
print!("{report}");
Ok(1)
}

View file

@ -60,7 +60,7 @@ pub fn arch_str(target: Target) -> &'static str {
roc_target::Architecture::X86_32 if cfg!(feature = "target-x86") => "x86",
roc_target::Architecture::Aarch64 if cfg!(feature = "target-aarch64") => "aarch64",
roc_target::Architecture::Aarch32 if cfg!(feature = "target-arm") => "arm",
roc_target::Architecture::Wasm32 if cfg!(feature = "target-webassembly") => "wasm32",
roc_target::Architecture::Wasm32 if cfg!(feature = "target-wasm32") => "wasm32",
_ => internal_error!(
"TODO gracefully handle unsupported target architecture: {:?}",
target.architecture()

View file

@ -2,8 +2,8 @@ const std = @import("std");
const time = std.time;
const Timer = time.Timer;
const RocStr = @import("../src/str.zig").RocStr;
const RocDec = @import("../src/dec.zig").RocDec;
const RocStr = @import("./bitcode/src/str.zig").RocStr;
const RocDec = @import("./bitcode/src/dec.zig").RocDec;
fn roc_alloc(_: usize, _: u32) callconv(.C) ?*anyopaque {
@panic("Not needed for dec benchmark");
@ -16,14 +16,22 @@ fn roc_dbg(_: *anyopaque, _: *anyopaque, _: *anyopaque) callconv(.C) void {
}
comptime {
@export(roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
@export(roc_dbg, .{ .name = "roc_dbg", .linkage = .Strong });
@export(roc_alloc, .{ .name = "roc_alloc", .linkage = .strong });
@export(roc_panic, .{ .name = "roc_panic", .linkage = .strong });
@export(roc_dbg, .{ .name = "roc_dbg", .linkage = .strong });
}
var timer: Timer = undefined;
pub fn main() !void {
pub export fn main() u8 {
run_tests() catch |err| {
std.debug.print("Error: {}\n", .{err});
return 1;
};
return 0;
}
fn run_tests() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Warning: Timer seems to step in units of 41ns\n\n", .{});
timer = try Timer.start();
@ -35,11 +43,15 @@ pub fn main() !void {
// This number are very close to 1 to avoid over and underflow.
const f1 = 1.00123;
const dec1 = RocDec.fromF64(f1).?;
const dec1 = RocDec.fromF64(f1) orelse {
@panic("Failed to create RocDec from f64");
};
// `asin` and `acos` have a limited range, so they will use this value.
const f2 = 0.00130000847;
const dec2 = RocDec.fromF64(f2).?;
const dec2 = RocDec.fromF64(f2) orelse {
@panic("Failed to create RocDec from f64");
};
try stdout.print("Dec:\n", .{});
try stdout.print("{} additions took ", .{add_sub_n});
@ -126,11 +138,11 @@ fn avg_runs(comptime T: type, comptime n: usize, comptime op: fn (T, T) T, v: T)
var i: usize = 0;
while (i < warmups + repeats) : (i += 1) {
// Never inline run to ensure it doesn't optimize for the value of `v`.
runs[i] = callWrapper(u64, .never_inline, run, .{ T, n, op, v });
runs[i] = @call(.never_inline, run, .{ T, n, op, v });
}
var real_runs = runs[warmups..runs.len];
std.sort.sort(u64, real_runs, {}, comptime std.sort.asc(u64));
const real_runs = runs[warmups..runs.len];
std.sort.insertion(u64, real_runs, {}, comptime std.sort.asc(u64));
const median = real_runs[real_runs.len / 2];
const highest = real_runs[real_runs.len - 1];
@ -146,19 +158,19 @@ fn run(comptime T: type, comptime n: usize, comptime op: fn (T, T) T, v: T) u64
// Split into outer and inner loop to avoid breaking comptime.
const max_inline = 100;
comptime var outer = n / max_inline;
comptime var inner = std.math.min(n, max_inline);
const outer = n / max_inline;
const inner = @min(n, max_inline);
var i: usize = 0;
while (i < outer) : (i += 1) {
comptime var j = 0;
inline while (j < inner) : (j += 1) {
a = callWrapper(T, .always_inline, op, .{ a, v });
a = @call(.always_inline, op, .{ a, v });
}
}
const rem = n % max_inline;
comptime var j = 0;
const j = 0;
inline while (j < rem) : (j += 1) {
a = callWrapper(T, .always_inline, op, .{ a, v });
a = @call(.always_inline, op, .{ a, v });
}
// Clobber `a` to avoid removal as dead code.
@ -170,11 +182,6 @@ fn run(comptime T: type, comptime n: usize, comptime op: fn (T, T) T, v: T) u64
return timer.read();
}
// This is needed to work around a bug with using `@call` in loops.
inline fn callWrapper(comptime T: type, call_modifier: anytype, comptime func: anytype, params: anytype) T {
return @call(.{ .modifier = call_modifier }, func, params);
}
fn addF64(x: f64, y: f64) f64 {
return x + y;
}

View file

@ -3,5 +3,5 @@
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
zig build-exe benchmark/dec.zig -O ReleaseFast --main-pkg-path .
./dec
zig build-exe benchmark-dec.zig -O ReleaseFast
./benchmark-dec

View file

@ -19,7 +19,7 @@ fn main() {
// dunce can be removed once ziglang/zig#5109 is fixed
let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap().join("..");
// workaround for github.com/ziglang/zig/issues/9711
// workaround for github.com/ziglang/zig/issues/20501
#[cfg(target_os = "macos")]
let zig_cache_dir = tempdir().expect("Failed to create temp directory for zig cache");
#[cfg(target_os = "macos")]
@ -69,9 +69,9 @@ fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling 64-bit bitcode to: {dest_bc_64bit}");
// workaround for github.com/ziglang/zig/issues/9711
// workaround for github.com/ziglang/zig/issues/20501
#[cfg(target_os = "macos")]
let _ = fs::remove_dir_all("./zig-cache");
let _ = fs::remove_dir_all("./.zig-cache");
let mut zig_cmd = zig();
@ -126,7 +126,7 @@ fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
let entry = entry?;
let path_buf = entry.path();
if path_buf.is_dir() {
if !path_buf.ends_with("zig-cache") {
if !path_buf.ends_with(".zig-cache") {
get_zig_files(&path_buf, cb).unwrap();
}
} else {

View file

@ -19,7 +19,7 @@ fn main() {
// dunce can be removed once ziglang/zig#5109 is fixed
let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap();
// workaround for github.com/ziglang/zig/issues/9711
// workaround for github.com/ziglang/zig/issues/20501
#[cfg(target_os = "macos")]
let zig_cache_dir = tempdir().expect("Failed to create temp directory for zig cache");
#[cfg(target_os = "macos")]
@ -70,6 +70,10 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
println!("Compiling zig object `{zig_object}` to: {src_obj}");
// workaround for github.com/ziglang/zig/issues/20501
#[cfg(target_os = "macos")]
let _ = fs::remove_dir_all("./.zig-cache");
let mut zig_cmd = zig();
zig_cmd
@ -119,7 +123,7 @@ fn copy_zig_builtins_to_target_dir(bitcode_path: &Path) {
});
}
// recursively copy all the .zig files from this directory, but do *not* recurse into zig-cache/
// recursively copy all the .zig files from this directory, but do *not* recurse into .zig-cache/
fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
// Make sure the destination directory exists before we try to copy anything into it.
std::fs::create_dir_all(target_dir).unwrap_or_else(|err| {
@ -146,8 +150,8 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
err
);
});
} else if src_path.is_dir() && src_filename != "zig-cache" {
// Recursively copy all directories except zig-cache
} else if src_path.is_dir() && src_filename != ".zig-cache" {
// Recursively copy all directories except .zig-cache
cp_unless_zig_cache(&src_path, &target_dir.join(src_filename))?;
}
}
@ -170,22 +174,21 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
Err(_) => format!("Failed to run \"{command_str}\""),
};
// Flaky test errors that only occur sometimes on MacOS ci server.
if error_str.contains("FileNotFound")
|| error_str.contains("unable to save cached ZIR code")
|| error_str.contains("LLVM failed to emit asm")
{
if flaky_fail_counter == 10 {
internal_error!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
} else {
run_command(command, flaky_fail_counter + 1)
}
} else if error_str
if error_str
.contains("lld-link: error: failed to write the output file: Permission denied")
{
internal_error!("{} failed with:\n\n {}\n\nWorkaround:\n\n Re-run the cargo command that triggered this build.\n\n", command_str, error_str);
} else {
internal_error!("{} failed with:\n\n {}\n", command_str, error_str);
// We have bunch of flaky failures here on macos, particularly since upgrading to zig 13 github.com/roc-lang/roc/pull/6921
if cfg!(target_os = "macos") {
if flaky_fail_counter == 10 {
internal_error!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
} else {
run_command(command, flaky_fail_counter + 1)
}
} else {
internal_error!("{} failed with:\n\n {}\n", command_str, error_str);
}
}
}
},
@ -199,7 +202,7 @@ fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
let entry = entry?;
let path_buf = entry.path();
if path_buf.is_dir() {
if !path_buf.ends_with("zig-cache") {
if !path_buf.ends_with(".zig-cache") {
get_zig_files(&path_buf, cb).unwrap();
}
} else {

View file

@ -11,9 +11,7 @@ pub fn build(b: *Build) void {
const mode = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
// Options
const fallback_main_path = "./src/main.zig";
const main_path_desc = b.fmt("Override path to main.zig. Used by \"ir\" and \"test\". Defaults to \"{s}\". ", .{fallback_main_path});
const main_path = .{ .path = b.option([]const u8, "main-path", main_path_desc) orelse fallback_main_path };
const main_path = b.path("src/main.zig");
// Tests
const main_tests = b.addTest(.{ .root_source_file = main_path, .link_libc = true });
@ -21,17 +19,36 @@ pub fn build(b: *Build) void {
test_step.dependOn(&b.addRunArtifact(main_tests).step);
// Targets
const host_target = b.standardTargetOptions(.{
.default_target = CrossTarget{
.cpu_model = .baseline,
.os_tag = builtin.os.tag,
},
const host_target = b.resolveTargetQuery(.{
.cpu_model = .baseline,
.os_tag = builtin.os.tag,
});
const linux32_target = b.resolveTargetQuery(.{
.cpu_arch = std.Target.Cpu.Arch.x86,
.os_tag = std.Target.Os.Tag.linux,
.abi = std.Target.Abi.none,
});
const linux_x64_target = b.resolveTargetQuery(.{
.cpu_arch = std.Target.Cpu.Arch.x86_64,
.os_tag = std.Target.Os.Tag.linux,
.abi = std.Target.Abi.none,
});
const linux_aarch64_target = b.resolveTargetQuery(.{
.cpu_arch = std.Target.Cpu.Arch.aarch64,
.os_tag = std.Target.Os.Tag.linux,
.abi = std.Target.Abi.none,
});
const windows64_target = b.resolveTargetQuery(.{
.cpu_arch = std.Target.Cpu.Arch.x86_64,
.os_tag = std.Target.Os.Tag.windows,
.abi = std.Target.Abi.none,
});
const wasm32_target = b.resolveTargetQuery(.{
// 32-bit wasm
.cpu_arch = std.Target.Cpu.Arch.wasm32,
.os_tag = std.Target.Os.Tag.freestanding,
.abi = std.Target.Abi.none,
});
const linux32_target = makeLinux32Target();
const linux_x64_target = makeLinuxX64Target();
const linux_aarch64_target = makeLinuxAarch64Target();
const windows64_target = makeWindows64Target();
const wasm32_target = makeWasm32Target();
// LLVM IR
generateLlvmIrFile(b, mode, host_target, main_path, "ir", "builtins-host");
@ -51,15 +68,16 @@ pub fn build(b: *Build) void {
fn generateLlvmIrFile(
b: *Build,
mode: std.builtin.Mode,
target: CrossTarget,
target: std.Build.ResolvedTarget,
main_path: LazyPath,
step_name: []const u8,
object_name: []const u8,
) void {
const obj = b.addObject(.{ .name = object_name, .root_source_file = main_path, .optimize = mode, .target = target, .use_llvm = true });
obj.strip = true;
obj.disable_stack_probing = true;
if (target.cpu_arch != .wasm32)
const obj = b.addObject(.{ .strip = true, .pic = true, .name = object_name, .root_source_file = main_path, .optimize = mode, .target = target, .use_llvm = true });
obj.root_module.stack_check = false;
if (target.result.cpu.arch != std.Target.Cpu.Arch.wasm32)
obj.bundle_compiler_rt = true;
// Generating the bin seems required to get zig to generate the llvm ir.
@ -83,23 +101,24 @@ fn generateLlvmIrFile(
fn generateObjectFile(
b: *Build,
mode: std.builtin.Mode,
target: CrossTarget,
target: std.Build.ResolvedTarget,
main_path: LazyPath,
step_name: []const u8,
object_name: []const u8,
) void {
const obj = b.addObject(.{ .name = object_name, .root_source_file = main_path, .optimize = mode, .target = target, .use_llvm = true });
obj.strip = true;
const is_wasm = target.result.cpu.arch == .wasm32 or target.result.cpu.arch == .wasm64;
const obj = b.addObject(.{ .strip = true, .pic = !is_wasm, .name = object_name, .root_source_file = main_path, .optimize = mode, .target = target, .use_llvm = true });
obj.link_function_sections = true;
obj.force_pic = true;
obj.disable_stack_probing = true;
if (target.cpu_arch != .wasm32)
obj.root_module.stack_check = false;
if (!is_wasm)
obj.bundle_compiler_rt = true;
const obj_file = obj.getEmittedBin();
const suffix =
if (target.os_tag == .windows)
if (target.result.os.tag == std.Target.Os.Tag.windows)
"obj"
else
"o";
@ -110,54 +129,3 @@ fn generateObjectFile(
obj_step.dependOn(&install.step);
b.getInstallStep().dependOn(obj_step);
}
fn makeLinux32Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.x86;
target.os_tag = std.Target.Os.Tag.linux;
target.abi = std.Target.Abi.none;
return target;
}
fn makeLinuxAarch64Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.aarch64;
target.os_tag = std.Target.Os.Tag.linux;
target.abi = std.Target.Abi.none;
return target;
}
fn makeLinuxX64Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.x86_64;
target.os_tag = std.Target.Os.Tag.linux;
target.abi = std.Target.Abi.none;
return target;
}
fn makeWindows64Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.x86_64;
target.os_tag = std.Target.Os.Tag.windows;
target.abi = std.Target.Abi.none;
return target;
}
fn makeWasm32Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
// 32-bit wasm
target.cpu_arch = std.Target.Cpu.Arch.wasm32;
target.os_tag = std.Target.Os.Tag.freestanding;
target.abi = std.Target.Abi.none;
return target;
}

View file

@ -32,7 +32,7 @@ pub const RocDec = extern struct {
}
pub fn fromF64(num: f64) ?RocDec {
var result: f64 = num * comptime @as(f64, @floatFromInt(one_point_zero_i128));
const result: f64 = num * comptime @as(f64, @floatFromInt(one_point_zero_i128));
if (result > comptime @as(f64, @floatFromInt(math.maxInt(i128)))) {
return null;
@ -42,7 +42,7 @@ pub const RocDec = extern struct {
return null;
}
var ret: RocDec = .{ .num = @as(i128, @intFromFloat(result)) };
const ret: RocDec = .{ .num = @as(i128, @intFromFloat(result)) };
return ret;
}
@ -61,13 +61,13 @@ pub const RocDec = extern struct {
const roc_str_slice = roc_str.asSlice();
var is_negative: bool = roc_str_slice[0] == '-';
var initial_index: usize = if (is_negative) 1 else 0;
const is_negative: bool = roc_str_slice[0] == '-';
const initial_index: usize = if (is_negative) 1 else 0;
var point_index: ?usize = null;
var index: usize = initial_index;
while (index < length) {
var byte: u8 = roc_str_slice[index];
const byte: u8 = roc_str_slice[index];
if (byte == '.' and point_index == null) {
point_index = index;
index += 1;
@ -85,20 +85,20 @@ pub const RocDec = extern struct {
if (point_index) |pi| {
before_str_length = pi;
var after_str_len = (length - 1) - pi;
const after_str_len = (length - 1) - pi;
if (after_str_len > decimal_places) {
// TODO: runtime exception for too many decimal places!
return null;
}
var diff_decimal_places = decimal_places - after_str_len;
const diff_decimal_places = decimal_places - after_str_len;
var after_str = roc_str_slice[pi + 1 .. length];
var after_u64 = std.fmt.parseUnsigned(u64, after_str, 10) catch null;
const after_str = roc_str_slice[pi + 1 .. length];
const after_u64 = std.fmt.parseUnsigned(u64, after_str, 10) catch null;
after_val_i128 = if (after_u64) |f| @as(i128, @intCast(f)) * math.pow(i128, 10, diff_decimal_places) else null;
}
var before_str = roc_str_slice[initial_index..before_str_length];
var before_val_not_adjusted = std.fmt.parseUnsigned(i128, before_str, 10) catch null;
const before_str = roc_str_slice[initial_index..before_str_length];
const before_val_not_adjusted = std.fmt.parseUnsigned(i128, before_str, 10) catch null;
var before_val_i128: ?i128 = null;
if (before_val_not_adjusted) |before| {
@ -115,7 +115,7 @@ pub const RocDec = extern struct {
const dec: RocDec = blk: {
if (before_val_i128) |before| {
if (after_val_i128) |after| {
var answer = @addWithOverflow(before, after);
const answer = @addWithOverflow(before, after);
const result = answer[0];
const overflowed = answer[1];
if (overflowed == 1) {
@ -239,13 +239,16 @@ pub const RocDec = extern struct {
}
pub fn negate(self: RocDec) ?RocDec {
var negated = math.negate(self.num) catch null;
const negated = math.negate(self.num) catch null;
return if (negated) |n| .{ .num = n } else null;
}
pub fn abs(self: RocDec) !RocDec {
const absolute = try math.absInt(self.num);
return RocDec{ .num = absolute };
const absolute = @abs(self.num);
if (absolute <= @as(u128, @intCast(std.math.maxInt(i128)))) {
return RocDec{ .num = @intCast(absolute) };
}
return error.OutOfRange;
}
pub fn addWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
@ -314,7 +317,8 @@ pub const RocDec = extern struct {
const is_answer_negative = (self_i128 < 0) != (other_i128 < 0);
const self_u128 = @as(u128, @intCast(math.absInt(self_i128) catch {
const self_u128 = @abs(self_i128);
if (self_u128 > @as(u128, @intCast(std.math.maxInt(i128)))) {
if (other_i128 == 0) {
return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
} else if (other_i128 == RocDec.one_point_zero.num) {
@ -324,9 +328,10 @@ pub const RocDec = extern struct {
} else {
return .{ .value = RocDec.max, .has_overflowed = true };
}
}));
}
const other_u128 = @as(u128, @intCast(math.absInt(other_i128) catch {
const other_u128 = @abs(other_i128);
if (other_u128 > @as(u128, @intCast(std.math.maxInt(i128)))) {
if (self_i128 == 0) {
return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
} else if (self_i128 == RocDec.one_point_zero.num) {
@ -336,14 +341,13 @@ pub const RocDec = extern struct {
} else {
return .{ .value = RocDec.max, .has_overflowed = true };
}
}));
const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128);
}
const unsigned_answer = mul_and_decimalize(self_u128, other_u128);
if (is_answer_negative) {
return .{ .value = RocDec{ .num = -unsigned_answer }, .has_overflowed = false };
return .{ .value = RocDec{ .num = -unsigned_answer.value }, .has_overflowed = unsigned_answer.has_overflowed };
} else {
return .{ .value = RocDec{ .num = unsigned_answer }, .has_overflowed = false };
return .{ .value = RocDec{ .num = unsigned_answer.value }, .has_overflowed = unsigned_answer.has_overflowed };
}
}
@ -430,7 +434,15 @@ pub const RocDec = extern struct {
pub fn mulSaturated(self: RocDec, other: RocDec) RocDec {
const answer = RocDec.mulWithOverflow(self, other);
return answer.value;
if (answer.has_overflowed) {
if (answer.value.num < 0) {
return RocDec.max;
} else {
return RocDec.min;
}
} else {
return answer.value;
}
}
pub fn div(self: RocDec, other: RocDec) RocDec {
@ -464,7 +476,8 @@ pub const RocDec = extern struct {
//
// We do checked_abs because if we had -i128::MAX before, this will overflow.
const numerator_abs_i128 = math.absInt(numerator_i128) catch {
const numerator_u128 = @abs(numerator_i128);
if (numerator_u128 > @as(u128, @intCast(std.math.maxInt(i128)))) {
// Currently, if you try to do multiplication on i64::MIN, panic
// unless you're specifically multiplying by 0 or 1.
//
@ -474,10 +487,10 @@ pub const RocDec = extern struct {
} else {
roc_panic("Decimal division overflow in numerator!", 0);
}
};
const numerator_u128 = @as(u128, @intCast(numerator_abs_i128));
}
const denominator_abs_i128 = math.absInt(denominator_i128) catch {
const denominator_u128 = @abs(denominator_i128);
if (denominator_u128 > @as(u128, @intCast(std.math.maxInt(i128)))) {
// Currently, if you try to do multiplication on i64::MIN, panic
// unless you're specifically multiplying by 0 or 1.
//
@ -487,8 +500,7 @@ pub const RocDec = extern struct {
} else {
roc_panic("Decimal division overflow in denominator!", 0);
}
};
const denominator_u128 = @as(u128, @intCast(denominator_abs_i128));
}
const numerator_u256: U256 = mul_u128(numerator_u128, math.pow(u128, 10, decimal_places));
const answer = div_u256_by_u128(numerator_u256, denominator_u128);
@ -516,7 +528,7 @@ pub const RocDec = extern struct {
// This is dec/(b0+1), but as a multiplication.
// So dec * (1/(b0+1)). This is way faster.
const dec = self.num;
const tmp = @as(i128, @intCast(num_.mul_u128(math.absCast(dec), 249757942369376157886101012127821356963).hi >> (190 - 128)));
const tmp = @as(i128, @intCast(num_.mul_u128(@abs(dec), 249757942369376157886101012127821356963).hi >> (190 - 128)));
const q0 = if (dec < 0) -tmp else tmp;
const upper = q0 * b0;
@ -592,7 +604,7 @@ inline fn count_trailing_zeros_base10(input: i128) u6 {
return count;
}
fn mul_and_decimalize(a: u128, b: u128) i128 {
fn mul_and_decimalize(a: u128, b: u128) WithOverflow(i128) {
const answer_u256 = mul_u128(a, b);
var lhs_hi = answer_u256.hi;
@ -710,14 +722,15 @@ fn mul_and_decimalize(a: u128, b: u128) i128 {
overflowed = overflowed | answer[1];
const d = answer[0];
if (overflowed == 1) {
roc_panic("Decimal multiplication overflow!", 0);
}
// Final 512bit value is d, c, b, a
// need to left shift 321 times
// 315 - 256 is 59. So left shift d, c 59 times.
return @as(i128, @intCast(c >> 59 | (d << (128 - 59))));
// need to right shift 315 times
// 315 - 256 is 59. So shift c right 59 times.
// d takes up the higher space above c, so shift it left by (128 - 59 = 69).
// Since d is being shift left 69 times, all of those 69 bits (+1 for the sign bit)
// must be zero. Otherwise, we have an overflow.
const d_high_bits = d >> 58;
return .{ .value = @as(i128, @intCast(c >> 59 | (d << (128 - 59)))), .has_overflowed = overflowed | d_high_bits != 0 };
}
// Multiply two 128-bit ints and divide the result by 10^DECIMAL_PLACES
@ -779,8 +792,8 @@ fn div_u256_by_u128(numer: U256, denom: u128) U256 {
// K X
// ---
// 0 K
var denom_leading_zeros = @clz(denom);
var numer_hi_leading_zeros = @clz(numer.hi);
const denom_leading_zeros = @clz(denom);
const numer_hi_leading_zeros = @clz(numer.hi);
sr = 1 + N_UDWORD_BITS + denom_leading_zeros - numer_hi_leading_zeros;
// 2 <= sr <= N_UTWORD_BITS - 1
// q.all = n.all << (N_UTWORD_BITS - sr);
@ -857,7 +870,7 @@ fn div_u256_by_u128(numer: U256, denom: u128) U256 {
//
// As an implementation of `as_u256`, we wrap a negative value around to the maximum value of U256.
var s_u128 = math.shr(u128, hi, 127);
const s_u128 = math.shr(u128, hi, 127);
var s_hi: u128 = undefined;
var s_lo: u128 = undefined;
if (s_u128 == 1) {
@ -867,7 +880,7 @@ fn div_u256_by_u128(numer: U256, denom: u128) U256 {
s_hi = 0;
s_lo = 0;
}
var s = .{
const s = .{
.hi = s_hi,
.lo = s_lo,
};
@ -885,8 +898,8 @@ fn div_u256_by_u128(numer: U256, denom: u128) U256 {
sr -= 1;
}
var hi = (q.hi << 1) | (q.lo >> (127));
var lo = (q.lo << 1) | carry;
const hi = (q.hi << 1) | (q.lo >> (127));
const lo = (q.lo << 1) | carry;
return .{ .hi = hi, .lo = lo };
}
@ -898,122 +911,122 @@ const expectEqualSlices = testing.expectEqualSlices;
const expect = testing.expect;
test "fromU64" {
var dec = RocDec.fromU64(25);
const dec = RocDec.fromU64(25);
try expectEqual(RocDec{ .num = 25000000000000000000 }, dec);
}
test "fromF64" {
var dec = RocDec.fromF64(25.5);
const dec = RocDec.fromF64(25.5);
try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?);
}
test "fromF64 overflow" {
var dec = RocDec.fromF64(1e308);
const dec = RocDec.fromF64(1e308);
try expectEqual(dec, null);
}
test "fromStr: empty" {
var roc_str = RocStr.init("", 0);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("", 0);
const dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "fromStr: 0" {
var roc_str = RocStr.init("0", 1);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("0", 1);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 0 }, dec.?);
}
test "fromStr: 1" {
var roc_str = RocStr.init("1", 1);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("1", 1);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec.one_point_zero, dec.?);
}
test "fromStr: 123.45" {
var roc_str = RocStr.init("123.45", 6);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("123.45", 6);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 123450000000000000000 }, dec.?);
}
test "fromStr: .45" {
var roc_str = RocStr.init(".45", 3);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init(".45", 3);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.?);
}
test "fromStr: 0.45" {
var roc_str = RocStr.init("0.45", 4);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("0.45", 4);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.?);
}
test "fromStr: 123" {
var roc_str = RocStr.init("123", 3);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("123", 3);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = 123000000000000000000 }, dec.?);
}
test "fromStr: -.45" {
var roc_str = RocStr.init("-.45", 4);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("-.45", 4);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.?);
}
test "fromStr: -0.45" {
var roc_str = RocStr.init("-0.45", 5);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("-0.45", 5);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = -450000000000000000 }, dec.?);
}
test "fromStr: -123" {
var roc_str = RocStr.init("-123", 4);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("-123", 4);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = -123000000000000000000 }, dec.?);
}
test "fromStr: -123.45" {
var roc_str = RocStr.init("-123.45", 7);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("-123.45", 7);
const dec = RocDec.fromStr(roc_str);
try expectEqual(RocDec{ .num = -123450000000000000000 }, dec.?);
}
test "fromStr: abc" {
var roc_str = RocStr.init("abc", 3);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("abc", 3);
const dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "fromStr: 123.abc" {
var roc_str = RocStr.init("123.abc", 7);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("123.abc", 7);
const dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "fromStr: abc.123" {
var roc_str = RocStr.init("abc.123", 7);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init("abc.123", 7);
const dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
test "fromStr: .123.1" {
var roc_str = RocStr.init(".123.1", 6);
var dec = RocDec.fromStr(roc_str);
const roc_str = RocStr.init(".123.1", 6);
const dec = RocDec.fromStr(roc_str);
try expectEqual(dec, null);
}
@ -1226,46 +1239,46 @@ test "div: 20 / 2" {
test "div: 8 / 5" {
var dec: RocDec = RocDec.fromU64(8);
var res: RocDec = RocDec.fromStr(RocStr.init("1.6", 3)).?;
const res: RocDec = RocDec.fromStr(RocStr.init("1.6", 3)).?;
try expectEqual(res, dec.div(RocDec.fromU64(5)));
}
test "div: 10 / 3" {
var numer: RocDec = RocDec.fromU64(10);
var denom: RocDec = RocDec.fromU64(3);
const denom: RocDec = RocDec.fromU64(3);
var roc_str = RocStr.init("3.333333333333333333", 20);
errdefer roc_str.decref();
defer roc_str.decref();
var res: RocDec = RocDec.fromStr(roc_str).?;
const res: RocDec = RocDec.fromStr(roc_str).?;
try expectEqual(res, numer.div(denom));
}
test "div: 341 / 341" {
var number1: RocDec = RocDec.fromU64(341);
var number2: RocDec = RocDec.fromU64(341);
const number2: RocDec = RocDec.fromU64(341);
try expectEqual(RocDec.fromU64(1), number1.div(number2));
}
test "div: 342 / 343" {
var number1: RocDec = RocDec.fromU64(342);
var number2: RocDec = RocDec.fromU64(343);
var roc_str = RocStr.init("0.997084548104956268", 20);
const number2: RocDec = RocDec.fromU64(343);
const roc_str = RocStr.init("0.997084548104956268", 20);
try expectEqual(RocDec.fromStr(roc_str), number1.div(number2));
}
test "div: 680 / 340" {
var number1: RocDec = RocDec.fromU64(680);
var number2: RocDec = RocDec.fromU64(340);
const number2: RocDec = RocDec.fromU64(340);
try expectEqual(RocDec.fromU64(2), number1.div(number2));
}
test "div: 500 / 1000" {
var number1: RocDec = RocDec.fromU64(500);
var number2: RocDec = RocDec.fromU64(1000);
var roc_str = RocStr.init("0.5", 3);
const number1: RocDec = RocDec.fromU64(500);
const number2: RocDec = RocDec.fromU64(1000);
const roc_str = RocStr.init("0.5", 3);
try expectEqual(RocDec.fromStr(roc_str), number1.div(number2));
}
@ -1274,35 +1287,35 @@ test "log: 1" {
}
test "fract: 0" {
var roc_str = RocStr.init("0", 1);
const 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);
const 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);
const 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);
const 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);
const roc_str = RocStr.init(".45", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 450000000000000000 }, dec.fract());
@ -1316,35 +1329,35 @@ test "fract: -0.00045" {
}
test "trunc: 0" {
var roc_str = RocStr.init("0", 1);
const 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);
const 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);
const 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);
const 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);
const roc_str = RocStr.init(".45", 3);
var dec = RocDec.fromStr(roc_str).?;
try expectEqual(RocDec{ .num = 0 }, dec.trunc());
@ -1358,64 +1371,64 @@ test "trunc: -0.00045" {
}
test "round: 123.45" {
var roc_str = RocStr.init("123.45", 6);
const 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);
const 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);
const 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);
const 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);
const 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);
const 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).?;
const roc_str = RocStr.init("4", 1);
const 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).?;
const roc_str = RocStr.init("0.25", 4);
const 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).?;
const roc_str = RocStr.init("0.25", 4);
const dec = RocDec.fromStr(roc_str).?;
try expectEqual(dec, RocDec.zero_point_five.pow(RocDec.two_point_zero));
}
@ -1456,7 +1469,7 @@ pub fn toF64(arg: RocDec) callconv(.C) f64 {
}
pub fn exportFromInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T) callconv(.C) i128 {
const this = @as(i128, @intCast(self));
@ -1468,7 +1481,7 @@ pub fn exportFromInt(comptime T: type, comptime name: []const u8) void {
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn fromU64C(arg: u64) callconv(.C) i128 {
@ -1577,28 +1590,28 @@ pub fn mulSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
}
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const 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 });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportFloor(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const 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 });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportCeiling(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const 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 });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}

View file

@ -1,7 +1,7 @@
const std = @import("std");
const builtin = @import("builtin");
const Atomic = std.atomic.Atomic;
const Atomic = std.atomic.Value;
const O_RDWR: c_int = 2;
const O_CREAT: c_int = 64;
@ -83,11 +83,10 @@ pub fn notifyParent(shared_buffer: [*]u8, tag: u32) callconv(.C) void {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
const usize_ptr = @as([*]u32, @ptrCast(@alignCast(shared_buffer)));
const atomic_ptr = @as(*Atomic(u32), @ptrCast(&usize_ptr[5]));
atomic_ptr.storeUnchecked(tag);
atomic_ptr.store(tag, .unordered);
// wait till the parent is done before proceeding
const Ordering = std.atomic.Ordering;
while (atomic_ptr.load(Ordering.Acquire) != 0) {
while (atomic_ptr.load(.acquire) != 0) {
std.atomic.spinLoopHint();
}
}

View file

@ -1,7 +0,0 @@
// This is a glue package that just re-exports other libs useful for zig hosts.
// Long term, a slimmed down version of these libraries without all of the roc builtins should be create via `roc glue`.
// We also should make RocList use comptime types in order to make it nice to use in zig.
pub const dec = @import("dec.zig");
pub const list = @import("list.zig");
pub const str = @import("str.zig");

View file

@ -931,7 +931,7 @@ inline fn listReplaceInPlaceHelp(
copy: CopyFn,
) RocList {
// the element we will replace
var element_at_index = (list.bytes orelse unreachable) + (index * element_width);
const element_at_index = (list.bytes orelse unreachable) + (index * element_width);
// copy out the old element
copy((out_element orelse unreachable), element_at_index);

View file

@ -16,7 +16,7 @@ const dec = @import("dec.zig");
var FLTUSED: i32 = 0;
comptime {
if (builtin.os.tag == .windows) {
@export(FLTUSED, .{ .name = "_fltused", .linkage = .Weak });
@export(FLTUSED, .{ .name = "_fltused", .linkage = .weak });
}
}
@ -52,7 +52,7 @@ comptime {
exportDecFn(dec.fromI128, "from_i128");
exportDecFn(dec.toStr, "to_str");
inline for (INTEGERS) |T| {
for (INTEGERS) |T| {
dec.exportFromInt(T, ROC_BUILTINS ++ ".dec.from_int.");
dec.exportRound(T, ROC_BUILTINS ++ ".dec.round.");
@ -115,7 +115,7 @@ comptime {
exportNumFn(num.f32FromParts, "f32_from_parts");
exportNumFn(num.f64FromParts, "f64_from_parts");
inline for (INTEGERS, 0..) |T, i| {
for (INTEGERS, 0..) |T, i| {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
@ -132,10 +132,12 @@ comptime {
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.");
num.exportAddWrappedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_wrapped.");
num.exportSubWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_with_overflow.");
num.exportSubOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_or_panic.");
num.exportSubSaturatedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_saturated.");
num.exportSubWrappedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_wrapped.");
num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow.");
num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic.");
@ -149,15 +151,15 @@ comptime {
num.exportCountOneBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_one_bits.");
}
inline for (INTEGERS) |FROM| {
inline for (INTEGERS) |TO| {
for (INTEGERS) |FROM| {
for (INTEGERS) |TO| {
// We're exporting more than we need here, but that's okay.
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
}
}
inline for (FLOATS) |T| {
for (FLOATS) |T| {
num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin.");
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
@ -210,12 +212,12 @@ comptime {
exportStrFn(str.strAllocationPtr, "allocation_ptr");
exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity");
inline for (INTEGERS) |T| {
for (INTEGERS) |T| {
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");
num.exportParseInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".to_int.");
}
inline for (FLOATS) |T| {
for (FLOATS) |T| {
str.exportFromFloat(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_float.");
num.exportParseFloat(T, ROC_BUILTINS ++ "." ++ STR ++ ".to_float.");
}
@ -236,8 +238,8 @@ comptime {
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");
exportUtilsFn(utils.dictPseudoSeed, "dict_pseudo_seed");
@export(panic_utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
@export(dbg_utils.dbg_impl, .{ .name = "roc_builtins.utils." ++ "dbg_impl", .linkage = .Weak });
@export(panic_utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .weak });
@export(dbg_utils.dbg_impl, .{ .name = "roc_builtins.utils." ++ "dbg_impl", .linkage = .weak });
if (builtin.target.cpu.arch != .wasm32) {
exportUtilsFn(expect.expectFailedStartSharedBuffer, "expect_failed_start_shared_buffer");
@ -245,17 +247,17 @@ comptime {
exportUtilsFn(expect.notifyParentExpect, "notify_parent_expect");
// sets the buffer used for expect failures
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak });
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .weak });
exportUtilsFn(expect.readSharedBufferEnv, "read_env_shared_buffer");
}
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 });
@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 });
@export(__roc_force_setjmp_windows, .{ .name = "__roc_force_setjmp", .linkage = .weak });
@export(__roc_force_longjmp_windows, .{ .name = "__roc_force_longjmp", .linkage = .weak });
}
}
@ -370,7 +372,7 @@ comptime {
// 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 });
@export(func, .{ .name = "roc_builtins." ++ func_name, .linkage = .strong });
}
fn exportNumFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "num." ++ func_name);

View file

@ -67,7 +67,7 @@ pub fn mul_u128(a: u128, b: u128) U256 {
}
pub fn exportParseInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(buf: RocStr) callconv(.C) NumParseResult(T) {
// a radix of 0 will make zig determine the radix from the frefix:
// * A prefix of "0b" implies radix=2,
@ -82,11 +82,11 @@ pub fn exportParseInt(comptime T: type, comptime name: []const u8) void {
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(buf: RocStr) callconv(.C) NumParseResult(T) {
if (std.fmt.parseFloat(T, buf.asSlice())) |success| {
return .{ .errorcode = 0, .value = success };
@ -95,20 +95,20 @@ pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void {
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@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 {
const f = struct {
fn func(x: T) callconv(.C) F {
return @floatFromInt(x);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportPow(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(base: T, exp: T) callconv(.C) T {
switch (@typeInfo(T)) {
// std.math.pow can handle ints via powi, but it turns any errors to unreachable
@ -127,153 +127,153 @@ pub fn exportPow(comptime T: type, comptime name: []const u8) void {
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportIsNan(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) bool {
return std.math.isNan(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportIsInfinite(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) bool {
return std.math.isInf(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportIsFinite(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) bool {
return std.math.isFinite(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportAsin(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return std.math.asin(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportAcos(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return std.math.acos(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return std.math.atan(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportSin(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return math.sin(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportCos(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return math.cos(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportTan(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return math.tan(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportLog(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return @log(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportFAbs(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return @fabs(input);
return @abs(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportSqrt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: T) callconv(.C) T {
return math.sqrt(input);
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportRound(comptime F: type, comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: F) callconv(.C) T {
return @as(T, @intFromFloat((math.round(input))));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportFloor(comptime F: type, comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: F) callconv(.C) T {
return @as(T, @intFromFloat((math.floor(input))));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportCeiling(comptime F: type, comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: F) callconv(.C) T {
return @as(T, @intFromFloat((math.ceil(input))));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(a: T, b: T) callconv(.C) T {
return math.divCeil(T, a, b) catch {
roc_panic("Integer division by 0!", 0);
};
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn ToIntCheckedResult(comptime T: type) type {
@ -286,7 +286,7 @@ pub fn ToIntCheckedResult(comptime T: type) type {
}
pub fn exportToIntCheckingMax(comptime From: type, comptime To: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
if (input > std.math.maxInt(To)) {
return .{ .out_of_bounds = true, .value = 0 };
@ -294,11 +294,11 @@ pub fn exportToIntCheckingMax(comptime From: type, comptime To: type, comptime n
return .{ .out_of_bounds = false, .value = @as(To, @intCast(input)) };
}
}.func;
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(From), .linkage = .strong });
}
pub fn exportToIntCheckingMaxAndMin(comptime From: type, comptime To: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
if (input > std.math.maxInt(To) or input < std.math.minInt(To)) {
return .{ .out_of_bounds = true, .value = 0 };
@ -306,7 +306,7 @@ pub fn exportToIntCheckingMaxAndMin(comptime From: type, comptime To: type, comp
return .{ .out_of_bounds = false, .value = @as(To, @intCast(input)) };
}
}.func;
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(From), .linkage = .strong });
}
fn isMultipleOf(comptime T: type, lhs: T, rhs: T) bool {
@ -325,12 +325,12 @@ fn isMultipleOf(comptime T: type, lhs: T, rhs: T) bool {
}
pub fn exportIsMultipleOf(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) bool {
return @call(.always_inline, isMultipleOf, .{ T, self, other });
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) {
@ -348,16 +348,16 @@ fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) {
}
pub fn exportAddWithOverflow(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) WithOverflow(T) {
return @call(.always_inline, addWithOverflow, .{ T, self, other });
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportAddSaturatedInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
const result = addWithOverflow(T, self, other);
if (result.has_overflowed) {
@ -372,11 +372,20 @@ pub fn exportAddSaturatedInt(comptime T: type, comptime name: []const u8) void {
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportAddWrappedInt(comptime T: type, comptime name: []const u8) void {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
return self +% other;
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportAddOrPanic(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
const result = addWithOverflow(T, self, other);
if (result.has_overflowed) {
@ -386,7 +395,7 @@ pub fn exportAddOrPanic(comptime T: type, comptime name: []const u8) void {
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
fn subWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) {
@ -404,16 +413,16 @@ fn subWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) {
}
pub fn exportSubWithOverflow(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) WithOverflow(T) {
return @call(.always_inline, subWithOverflow, .{ T, self, other });
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportSubSaturatedInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
const result = subWithOverflow(T, self, other);
if (result.has_overflowed) {
@ -429,11 +438,20 @@ pub fn exportSubSaturatedInt(comptime T: type, comptime name: []const u8) void {
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportSubWrappedInt(comptime T: type, comptime name: []const u8) void {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
return self -% other;
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportSubOrPanic(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
const result = subWithOverflow(T, self, other);
if (result.has_overflowed) {
@ -443,7 +461,7 @@ pub fn exportSubOrPanic(comptime T: type, comptime name: []const u8) void {
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
fn mulWithOverflow(comptime T: type, comptime W: type, self: T, other: T) WithOverflow(T) {
@ -454,7 +472,8 @@ fn mulWithOverflow(comptime T: type, comptime W: type, self: T, other: T) WithOv
const max = std.math.maxInt(i128);
const min = std.math.minInt(i128);
const self_u128 = @as(u128, @intCast(math.absInt(self) catch {
const self_u128 = @abs(self);
if (self_u128 > @as(u128, @intCast(std.math.maxInt(i128)))) {
if (other == 0) {
return .{ .value = 0, .has_overflowed = false };
} else if (other == 1) {
@ -464,9 +483,10 @@ fn mulWithOverflow(comptime T: type, comptime W: type, self: T, other: T) WithOv
} else {
return .{ .value = max, .has_overflowed = true };
}
}));
}
const other_u128 = @as(u128, @intCast(math.absInt(other) catch {
const other_u128 = @abs(other);
if (other_u128 > @as(u128, @intCast(std.math.maxInt(i128)))) {
if (self == 0) {
return .{ .value = 0, .has_overflowed = false };
} else if (self == 1) {
@ -476,7 +496,7 @@ fn mulWithOverflow(comptime T: type, comptime W: type, self: T, other: T) WithOv
} else {
return .{ .value = max, .has_overflowed = true };
}
}));
}
const answer256: U256 = mul_u128(self_u128, other_u128);
@ -521,31 +541,31 @@ fn mulWithOverflow(comptime T: type, comptime W: type, self: T, other: T) WithOv
}
pub fn exportMulWithOverflow(comptime T: type, comptime W: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) WithOverflow(T) {
return @call(.always_inline, mulWithOverflow, .{ T, W, self, other });
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportMulSaturatedInt(comptime T: type, comptime W: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
const result = @call(.always_inline, mulWithOverflow, .{ T, W, self, other });
return result.value;
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportMulWrappedInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
return self *% other;
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn shiftRightZeroFillI128(self: i128, other: u8) callconv(.C) i128 {
@ -617,7 +637,7 @@ pub fn greaterThanOrEqualU128(self: u128, other: u128) callconv(.C) bool {
}
pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T, other: T) callconv(.C) T {
const result = @call(.always_inline, mulWithOverflow, .{ T, W, self, other });
if (result.has_overflowed) {
@ -627,34 +647,34 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportCountLeadingZeroBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T) callconv(.C) u8 {
return @as(u8, @clz(self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportCountTrailingZeroBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T) callconv(.C) u8 {
return @as(u8, @ctz(self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(self: T) callconv(.C) u8 {
return @as(u8, @popCount(self));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
pub fn f32ToParts(self: f32) callconv(.C) F32Parts {

View file

@ -61,7 +61,7 @@ pub fn fluxsort(
} else {
if (utils.alloc(len * @sizeOf(usize), @alignOf(usize))) |alloc_ptr| {
// Build list of pointers to sort.
var arr_ptr = @as([*]Opaque, @ptrCast(@alignCast(alloc_ptr)));
const arr_ptr = @as([*]Opaque, @ptrCast(@alignCast(alloc_ptr)));
defer utils.dealloc(alloc_ptr, @alignOf(usize));
for (0..len) |i| {
arr_ptr[i] = array + i * element_width;
@ -552,7 +552,7 @@ fn flux_default_partition(
) usize {
var arr_ptr = array;
var swap_ptr = swap;
var pivot_ptr = pivot;
const pivot_ptr = pivot;
var x_ptr = x;
// len guaranteed compares
@ -621,7 +621,7 @@ fn flux_reverse_partition(
) void {
var arr_ptr = array;
var swap_ptr = swap;
var pivot_ptr = pivot;
const pivot_ptr = pivot;
var x_ptr = x;
// len guaranteed compares
@ -661,9 +661,9 @@ test "flux_default_partition" {
var pivot: i64 = 0;
var arr: [32]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [32]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
arr = [32]i64{
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
@ -748,9 +748,9 @@ test "flux_reverse_partition" {
var pivot: i64 = 0;
var arr: [32]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [32]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
arr = [32]i64{
1, 3, 5, 7, 9, 11, 13, 15, 17, 17, 17, 17, 17, 17, 17, 17,
@ -809,7 +809,7 @@ fn median_of_cube_root(
// Using a pointer to div as an int is to get a random offset from 0 to div.
var arr_ptr = x_ptr + (@intFromPtr(&div) / 16 % div) * element_width;
var swap_ptr = if (x_ptr == array) swap else array;
const swap_ptr = if (x_ptr == array) swap else array;
for (0..cbrt) |cnt| {
copy(swap_ptr + cnt * element_width, arr_ptr);
@ -953,7 +953,7 @@ fn binary_median(
ptr_b += len * element_width;
}
}
var from = if (compare(cmp, cmp_data, ptr_a, ptr_b, indirect) == GT) ptr_a else ptr_b;
const from = if (compare(cmp, cmp_data, ptr_a, ptr_b, indirect) == GT) ptr_a else ptr_b;
copy(out, from);
}
@ -963,10 +963,10 @@ test "median_of_cube_root" {
var generic = false;
var swap: [32]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
{
var arr: [32]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [32]i64{
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
@ -1001,7 +1001,7 @@ test "median_of_nine" {
{
var arr: [9]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [9]i64{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
median_of_nine(arr_ptr, 10, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, @ptrCast(&out), false);
@ -1030,7 +1030,7 @@ test "trim_four" {
var test_count: i64 = 0;
var arr: [4]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [4]i64{ 1, 2, 3, 4 };
trim_four(arr_ptr, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -1054,7 +1054,7 @@ test "binary_median" {
{
var arr: [10]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [10]i64{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
binary_median(arr_ptr, arr_ptr + 5 * @sizeOf(i64), 5, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, @ptrCast(&out), false);
@ -1068,7 +1068,7 @@ test "binary_median" {
}
{
var arr: [16]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [16]i64{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
binary_median(arr_ptr, arr_ptr + 8 * @sizeOf(i64), 8, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, @ptrCast(&out), false);
@ -1141,7 +1141,7 @@ pub fn quadsort(
} else {
if (utils.alloc(len * @sizeOf(usize), @alignOf(usize))) |alloc_ptr| {
// Build list of pointers to sort.
var arr_ptr = @as([*]Opaque, @ptrCast(@alignCast(alloc_ptr)));
const arr_ptr = @as([*]Opaque, @ptrCast(@alignCast(alloc_ptr)));
defer utils.dealloc(alloc_ptr, @alignOf(usize));
for (0..len) |i| {
arr_ptr[i] = array + i * element_width;
@ -1184,7 +1184,7 @@ fn quadsort_direct(
inc_n_data: IncN,
comptime indirect: bool,
) void {
var arr_ptr = array;
const arr_ptr = array;
if (len < 32) {
// TODO: This is a solid amount of stack space. Is that ok?
// That said, it only ever allocates once (not recursive).
@ -1194,7 +1194,7 @@ fn quadsort_direct(
const swap = @as([*]u8, @ptrCast(&swap_buffer[0]));
tail_swap(arr_ptr, len, swap, cmp, cmp_data, element_width, copy, data_is_owned, inc_n_data, indirect);
} else if (quad_swap(arr_ptr, len, cmp, cmp_data, element_width, copy, data_is_owned, inc_n_data, indirect) != .sorted) {
var swap_len = len;
const swap_len = len;
// This is optional, for about 5% perf hit, lower memory usage on large arrays.
// if (len > 4194304) {
@ -1253,7 +1253,7 @@ fn rotate_merge(
inc_n_data: IncN,
comptime indirect: bool,
) void {
var end_ptr = array + len * element_width;
const end_ptr = array + len * element_width;
if (len <= block_len * 2 and len -% block_len <= swap_len) {
partial_backwards_merge(array, len, swap, swap_len, block_len, cmp, cmp_data, element_width, copy, data_is_owned, inc_n_data, indirect);
@ -1301,10 +1301,10 @@ fn rotate_merge_block(
return;
}
var right_block = left_block / 2;
const right_block = left_block / 2;
left_block -= right_block;
var left = monobound_binary_first(array + (left_block + right_block) * element_width, right, array + left_block * element_width, cmp, cmp_data, element_width, data_is_owned, inc_n_data, indirect);
const left = monobound_binary_first(array + (left_block + right_block) * element_width, right, array + left_block * element_width, cmp, cmp_data, element_width, data_is_owned, inc_n_data, indirect);
right -= left;
if (left != 0) {
@ -1473,7 +1473,7 @@ fn trinity_rotation(
var bridge = left_len - right_len;
if (bridge <= swap_len and bridge > 3) {
var c_ptr = a_ptr + right_len * element_width;
var d_ptr = c_ptr + left_len * element_width;
const d_ptr = c_ptr + left_len * element_width;
@memcpy(swap[0..(bridge * element_width)], c_ptr[0..(bridge * element_width)]);
@ -1543,9 +1543,9 @@ test "rotate_merge" {
var test_count: i64 = 0;
var arr: [10]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [10]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
arr = [10]i64{ 7, 8, 5, 6, 3, 4, 1, 2, 9, 10 };
rotate_merge(arr_ptr, 10, swap_ptr, 10, 2, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -1573,9 +1573,9 @@ test "monobound_binary_first" {
var test_count: i64 = 0;
var arr = [25]i64{ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49 };
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var value: i64 = undefined;
var value_ptr = @as([*]u8, @ptrCast(&value));
const value_ptr = @as([*]u8, @ptrCast(&value));
value = 7;
var res = monobound_binary_first(arr_ptr, 25, value_ptr, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), true, &test_inc_n_data, false);
@ -1606,9 +1606,9 @@ test "monobound_binary_first" {
test "trinity_rotation" {
{
var arr: [10]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [10]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
// Even.
arr = [10]i64{ 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 };
@ -1637,9 +1637,9 @@ test "trinity_rotation" {
}
{
var arr: [16]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [5]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
// left larger, bridge in swap.
arr = [16]i64{ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6 };
@ -1781,8 +1781,8 @@ fn partial_backwards_merge(
inc_n_data(cmp_data, 2);
}
const lte = compare(cmp, cmp_data, left_tail, right_tail, indirect) != GT;
var x = if (lte) element_width else 0;
var not_x = if (!lte) element_width else 0;
const x = if (lte) element_width else 0;
const not_x = if (!lte) element_width else 0;
dest_tail -= element_width;
copy(dest_tail + x, right_tail);
right_tail -= element_width;
@ -1816,8 +1816,8 @@ fn partial_backwards_merge(
}
// Couldn't move two elements, do a cross swap and continue.
const lte = compare(cmp, cmp_data, left_tail, right_tail, indirect) != GT;
var x = if (lte) element_width else 0;
var not_x = if (!lte) element_width else 0;
const x = if (lte) element_width else 0;
const not_x = if (!lte) element_width else 0;
dest_tail -= element_width;
copy(dest_tail + x, right_tail);
right_tail -= element_width;
@ -1983,8 +1983,8 @@ fn partial_forward_merge(
}
// Couldn't move two elements, do a cross swap and continue.
const lte = compare(cmp, cmp_data, left_head, right_head, indirect) != GT;
var x = if (lte) element_width else 0;
var not_x = if (!lte) element_width else 0;
const x = if (lte) element_width else 0;
const not_x = if (!lte) element_width else 0;
copy(dest_head + x, right_head);
right_head += element_width;
copy(dest_head + not_x, left_head);
@ -2096,9 +2096,9 @@ test "tail_merge" {
const expected = [10]i64{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var arr: [10]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [10]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
arr = [10]i64{ 7, 8, 5, 6, 3, 4, 1, 2, 9, 10 };
tail_merge(arr_ptr, 10, swap_ptr, 10, 2, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -2122,9 +2122,9 @@ test "partial_backwards_merge" {
const expected = [10]i64{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var arr: [10]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [10]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
arr = [10]i64{ 3, 4, 5, 6, 7, 8, 1, 2, 9, 10 };
partial_backwards_merge(arr_ptr, 10, swap_ptr, 10, 6, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -2154,9 +2154,9 @@ test "partial_backwards_merge" {
}
var arr: [64]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [64]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
// chunks
for (0..16) |i| {
@ -2202,9 +2202,9 @@ test "partial_forward_merge" {
const expected = [10]i64{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var arr: [10]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [10]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
arr = [10]i64{ 3, 4, 5, 6, 7, 8, 1, 2, 9, 10 };
partial_forward_merge(arr_ptr, 10, swap_ptr, 10, 6, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -2461,9 +2461,9 @@ test "quad_merge" {
const expected = [10]i64{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var arr: [10]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [10]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
var size: usize = undefined;
arr = [10]i64{ 7, 8, 5, 6, 3, 4, 1, 2, 9, 10 };
@ -2503,9 +2503,9 @@ test "quad_merge_block" {
const expected = [8]i64{ 1, 2, 3, 4, 5, 6, 7, 8 };
var arr: [8]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap: [8]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
// case 0 - totally unsorted
arr = [8]i64{ 7, 8, 5, 6, 3, 4, 1, 2 };
@ -2547,8 +2547,8 @@ test "cross_merge" {
var src: [64]i64 = undefined;
var dest: [64]i64 = undefined;
var src_ptr = @as([*]u8, @ptrCast(&src[0]));
var dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
const src_ptr = @as([*]u8, @ptrCast(&src[0]));
const dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
// Opitimal case, ordered but swapped
for (0..32) |i| {
@ -2941,7 +2941,7 @@ fn quad_reversal(
test "quad_swap" {
var test_count: i64 = 0;
var arr: [75]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [75]i64{
// multiple ordered chunks
@ -2997,8 +2997,8 @@ test "quad_swap" {
test "quad_swap_merge" {
var arr: [8]i64 = undefined;
var swap: [8]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
arr = [8]i64{ 5, 6, 7, 8, 1, 2, 3, 4 };
swap = [8]i64{ 0, 0, 0, 0, 0, 0, 0, 0 };
@ -3019,15 +3019,15 @@ test "quad_swap_merge" {
test "quad_reversal" {
{
var arr = [8]i64{ 8, 7, 6, 5, 4, 3, 2, 1 };
var start_ptr = @as([*]u8, @ptrCast(&arr[0]));
var end_ptr = @as([*]u8, @ptrCast(&arr[7]));
const start_ptr = @as([*]u8, @ptrCast(&arr[0]));
const end_ptr = @as([*]u8, @ptrCast(&arr[7]));
quad_reversal(start_ptr, end_ptr, @sizeOf(i64), &test_i64_copy);
try testing.expectEqual(arr, [8]i64{ 1, 2, 3, 4, 5, 6, 7, 8 });
}
{
var arr = [9]i64{ 9, 8, 7, 6, 5, 4, 3, 2, 1 };
var start_ptr = @as([*]u8, @ptrCast(&arr[0]));
var end_ptr = @as([*]u8, @ptrCast(&arr[8]));
const start_ptr = @as([*]u8, @ptrCast(&arr[0]));
const end_ptr = @as([*]u8, @ptrCast(&arr[8]));
quad_reversal(start_ptr, end_ptr, @sizeOf(i64), &test_i64_copy);
try testing.expectEqual(arr, [9]i64{ 1, 2, 3, 4, 5, 6, 7, 8, 9 });
}
@ -3130,7 +3130,7 @@ fn parity_merge(
test "tail_swap" {
var test_count: i64 = 0;
var swap: [31]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
var arr: [31]i64 = undefined;
var expected: [31]i64 = undefined;
@ -3138,7 +3138,7 @@ test "tail_swap" {
arr[i] = @intCast(i + 1);
expected[i] = @intCast(i + 1);
}
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
for (0..10) |seed| {
var rng = std.rand.DefaultPrng.init(seed);
@ -3154,10 +3154,10 @@ test "parity_merge" {
var test_count: i64 = 0;
{
var dest: [8]i64 = undefined;
var dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
const dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
var arr: [8]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [8]i64{ 1, 3, 5, 7, 2, 4, 6, 8 };
dest = [8]i64{ 0, 0, 0, 0, 0, 0, 0, 0 };
@ -3173,10 +3173,10 @@ test "parity_merge" {
}
{
var dest: [9]i64 = undefined;
var dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
const dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
var arr: [9]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [9]i64{ 1, 3, 5, 8, 2, 4, 6, 7, 9 };
dest = [9]i64{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
@ -3395,8 +3395,8 @@ fn parity_swap_six(
}
{
const gt = compare(cmp, cmp_data, arr_ptr, arr_ptr + element_width, indirect) == GT;
var x = if (gt) element_width else 0;
var not_x = if (!gt) element_width else 0;
const x = if (gt) element_width else 0;
const not_x = if (!gt) element_width else 0;
copy(swap, arr_ptr + x);
copy(swap + element_width, arr_ptr + not_x);
copy(swap + 2 * element_width, arr_ptr + 2 * element_width);
@ -3404,8 +3404,8 @@ fn parity_swap_six(
}
{
const gt = compare(cmp, cmp_data, arr_ptr, arr_ptr + element_width, indirect) == GT;
var x = if (gt) element_width else 0;
var not_x = if (!gt) element_width else 0;
const x = if (gt) element_width else 0;
const not_x = if (!gt) element_width else 0;
copy(swap + 4 * element_width, arr_ptr + x);
copy(swap + 5 * element_width, arr_ptr + not_x);
copy(swap + 3 * element_width, arr_ptr - element_width);
@ -3472,8 +3472,8 @@ fn parity_swap_seven(
{
const gt = compare(cmp, cmp_data, arr_ptr, arr_ptr + element_width, indirect) == GT;
var x = if (gt) element_width else 0;
var not_x = if (!gt) element_width else 0;
const x = if (gt) element_width else 0;
const not_x = if (!gt) element_width else 0;
copy(swap, arr_ptr + x);
copy(swap + element_width, arr_ptr + not_x);
copy(swap + 2 * element_width, arr_ptr + 2 * element_width);
@ -3481,16 +3481,16 @@ fn parity_swap_seven(
}
{
const gt = compare(cmp, cmp_data, arr_ptr, arr_ptr + element_width, indirect) == GT;
var x = if (gt) element_width else 0;
var not_x = if (!gt) element_width else 0;
const x = if (gt) element_width else 0;
const not_x = if (!gt) element_width else 0;
copy(swap + 3 * element_width, arr_ptr + x);
copy(swap + 4 * element_width, arr_ptr + not_x);
arr_ptr += 2 * element_width;
}
{
const gt = compare(cmp, cmp_data, arr_ptr, arr_ptr + element_width, indirect) == GT;
var x = if (gt) element_width else 0;
var not_x = if (!gt) element_width else 0;
const x = if (gt) element_width else 0;
const not_x = if (!gt) element_width else 0;
copy(swap + 5 * element_width, arr_ptr + x);
copy(swap + 6 * element_width, arr_ptr + not_x);
}
@ -3518,11 +3518,11 @@ fn parity_swap_seven(
test "tiny_sort" {
var test_count: i64 = 0;
var swap: [7]i64 = undefined;
var swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
const swap_ptr = @as([*]u8, @ptrCast(&swap[0]));
{
var arr: [7]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [7]i64{ 3, 1, 2, 5, 4, 7, 6 };
tiny_sort(arr_ptr, 7, swap_ptr, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -3536,7 +3536,7 @@ test "tiny_sort" {
}
{
var arr: [6]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [6]i64{ 3, 1, 2, 6, 4, 5 };
tiny_sort(arr_ptr, 6, swap_ptr, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -3550,7 +3550,7 @@ test "tiny_sort" {
}
{
var arr: [5]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [5]i64{ 2, 1, 4, 3, 5 };
tiny_sort(arr_ptr, 5, swap_ptr, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -3564,7 +3564,7 @@ test "tiny_sort" {
}
{
var arr: [4]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
arr = [4]i64{ 4, 2, 1, 3 };
tiny_sort(arr_ptr, 4, swap_ptr, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
@ -3578,14 +3578,14 @@ test "tiny_sort" {
}
{
var arr = [3]i64{ 2, 3, 1 };
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
tiny_sort(arr_ptr, 3, swap_ptr, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
try testing.expectEqual(test_count, 0);
try testing.expectEqual(arr, [3]i64{ 1, 2, 3 });
}
{
var arr = [2]i64{ 2, 1 };
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
tiny_sort(arr_ptr, 2, swap_ptr, &test_i64_compare_refcounted, @ptrCast(&test_count), @sizeOf(i64), &test_i64_copy, true, &test_inc_n_data, false);
try testing.expectEqual(test_count, 0);
try testing.expectEqual(arr, [2]i64{ 1, 2 });
@ -3734,7 +3734,7 @@ inline fn swap_branchless_return_gt(
) u8 {
// While not guaranteed branchless, tested in godbolt for x86_64, aarch32, aarch64, riscv64, and wasm32.
const gt = compare(cmp, cmp_data, ptr, ptr + element_width, indirect) == GT;
var x = if (gt) element_width else 0;
const x = if (gt) element_width else 0;
const from = if (gt) ptr else ptr + element_width;
copy(tmp, from);
copy(ptr, ptr + x);
@ -3783,8 +3783,8 @@ inline fn compare_inc(
test "parity_merge_four" {
var arr: [8]i64 = undefined;
var dest: [8]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
arr = [8]i64{ 1, 2, 3, 4, 5, 6, 7, 8 };
dest = [8]i64{ 0, 0, 0, 0, 0, 0, 0, 0 };
@ -3805,8 +3805,8 @@ test "parity_merge_four" {
test "parity_merge_two" {
var arr: [4]i64 = undefined;
var dest: [4]i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const dest_ptr = @as([*]u8, @ptrCast(&dest[0]));
arr = [4]i64{ 1, 2, 3, 4 };
dest = [4]i64{ 0, 0, 0, 0 };
@ -3873,8 +3873,8 @@ test "tail_branchless_merge" {
test "swap" {
var arr: [2]i64 = undefined;
var tmp: i64 = undefined;
var arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
var tmp_ptr = @as([*]u8, @ptrCast(&tmp));
const arr_ptr = @as([*]u8, @ptrCast(&arr[0]));
const tmp_ptr = @as([*]u8, @ptrCast(&tmp));
arr = [2]i64{ 10, 20 };
swap_branchless(arr_ptr, tmp_ptr, &test_i64_compare, null, @sizeOf(i64), &test_i64_copy, false);

View file

@ -212,7 +212,7 @@ pub const RocStr = extern struct {
// just return the bytes
return str;
} else {
var new_str = RocStr.allocateBig(str.length, str.length);
const new_str = RocStr.allocateBig(str.length, str.length);
var old_bytes: [*]u8 = @as([*]u8, @ptrCast(str.bytes));
var new_bytes: [*]u8 = @as([*]u8, @ptrCast(new_str.bytes));
@ -274,7 +274,7 @@ pub const RocStr = extern struct {
const source_ptr = self.asU8ptr();
const dest_ptr = result.asU8ptrMut();
std.mem.copy(u8, dest_ptr[0..old_length], source_ptr[0..old_length]);
@memcpy(dest_ptr[0..old_length], source_ptr[0..old_length]);
@memset(dest_ptr[old_length..new_length], 0);
self.decref();
@ -290,7 +290,7 @@ pub const RocStr = extern struct {
const source_ptr = self.asU8ptr();
std.mem.copy(u8, dest_ptr[0..old_length], source_ptr[0..old_length]);
@memcpy(dest_ptr[0..old_length], source_ptr[0..old_length]);
@memset(dest_ptr[old_length..new_length], 0);
self.decref();
@ -554,13 +554,13 @@ pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
// Str.fromInt
pub fn exportFromInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(int: T) callconv(.C) RocStr {
return @call(.always_inline, strFromIntHelp, .{ T, int });
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
fn strFromIntHelp(comptime T: type, int: T) RocStr {
@ -568,9 +568,9 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
const size = comptime blk: {
// the string representation of the minimum i128 value uses at most 40 characters
var buf: [40]u8 = undefined;
var resultMin = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
var resultMax = std.fmt.bufPrint(&buf, "{}", .{std.math.maxInt(T)}) catch unreachable;
var result = if (resultMin.len > resultMax.len) resultMin.len else resultMax.len;
const resultMin = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
const resultMax = std.fmt.bufPrint(&buf, "{}", .{std.math.maxInt(T)}) catch unreachable;
const result = if (resultMin.len > resultMax.len) resultMin.len else resultMax.len;
break :blk result;
};
@ -582,13 +582,13 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
// Str.fromFloat
pub fn exportFromFloat(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
const f = struct {
fn func(float: T) callconv(.C) RocStr {
return @call(.always_inline, strFromFloatHelp, .{ T, float });
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
@export(f, .{ .name = name ++ @typeName(T), .linkage = .strong });
}
fn strFromFloatHelp(comptime T: type, float: T) RocStr {
@ -662,7 +662,7 @@ test "strSplitHelp: empty delimiter" {
strSplitOnHelp(array_ptr, str, delimiter);
var expected = [1]RocStr{
const expected = [1]RocStr{
str,
};
@ -696,7 +696,7 @@ test "strSplitHelp: no delimiter" {
strSplitOnHelp(array_ptr, str, delimiter);
var expected = [1]RocStr{
const expected = [1]RocStr{
str,
};
@ -735,7 +735,7 @@ test "strSplitHelp: empty start" {
const one = RocStr.init("a", 1);
var expected = [2]RocStr{
const expected = [2]RocStr{
RocStr.empty(), one,
};
@ -777,7 +777,7 @@ test "strSplitHelp: empty end" {
const one = RocStr.init("1", 1);
const two = RocStr.init("2", 1);
var expected = [3]RocStr{
const expected = [3]RocStr{
one, two, RocStr.empty(),
};
@ -813,7 +813,7 @@ test "strSplitHelp: string equals delimiter" {
strSplitOnHelp(array_ptr, str_delimiter, str_delimiter);
var expected = [2]RocStr{ RocStr.empty(), RocStr.empty() };
const expected = [2]RocStr{ RocStr.empty(), RocStr.empty() };
defer {
for (array) |rocStr| {
@ -851,7 +851,7 @@ test "strSplitHelp: delimiter on sides" {
const ghi_arr = "ghi";
const ghi = RocStr.init(ghi_arr, ghi_arr.len);
var expected = [3]RocStr{
const expected = [3]RocStr{
RocStr.empty(), ghi, RocStr.empty(),
};
@ -892,7 +892,7 @@ test "strSplitHelp: three pieces" {
const b = RocStr.init("b", 1);
const c = RocStr.init("c", 1);
var expected_array = [array_len]RocStr{
const expected_array = [array_len]RocStr{
a, b, c,
};
@ -928,7 +928,7 @@ test "strSplitHelp: overlapping delimiter 1" {
strSplitOnHelp(array_ptr, str, delimiter);
var expected = [2]RocStr{
const expected = [2]RocStr{
RocStr.empty(),
RocStr.init("a", 1),
};
@ -953,7 +953,7 @@ test "strSplitHelp: overlapping delimiter 2" {
strSplitOnHelp(array_ptr, str, delimiter);
var expected = [3]RocStr{
const expected = [3]RocStr{
RocStr.empty(),
RocStr.empty(),
RocStr.empty(),
@ -1364,7 +1364,7 @@ fn strJoinWith(list: RocListStr, separator: RocStr) RocStr {
total_size += separator.len() * (len - 1);
var result = RocStr.allocate(total_size);
var result_ptr = result.asU8ptrMut();
const result_ptr = result.asU8ptrMut();
var offset: usize = 0;
for (slice[0 .. len - 1]) |substr| {
@ -1943,7 +1943,7 @@ pub fn strTrimEnd(input_string: RocStr) callconv(.C) RocStr {
fn countLeadingWhitespaceBytes(string: RocStr) usize {
var byte_count: usize = 0;
var bytes = string.asU8ptr()[0..string.len()];
const bytes = string.asU8ptr()[0..string.len()];
var iter = unicode.Utf8View.initUnchecked(bytes).iterator();
while (iter.nextCodepoint()) |codepoint| {
if (isWhitespace(codepoint)) {
@ -1959,7 +1959,7 @@ fn countLeadingWhitespaceBytes(string: RocStr) usize {
fn countTrailingWhitespaceBytes(string: RocStr) usize {
var byte_count: usize = 0;
var bytes = string.asU8ptr()[0..string.len()];
const bytes = string.asU8ptr()[0..string.len()];
var iter = ReverseUtf8View.initUnchecked(bytes).iterator();
while (iter.nextCodepoint()) |codepoint| {
if (isWhitespace(codepoint)) {

View file

@ -1,6 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const Monotonic = std.builtin.AtomicOrder.Monotonic;
const DEBUG_INCDEC = false;
const DEBUG_TESTING_ALLOC = false;
@ -56,20 +55,20 @@ fn testing_roc_dbg(loc: *anyopaque, message: *anyopaque, src: *anyopaque) callco
comptime {
// During tests, use the testing allocators to satisfy these functions.
if (builtin.is_test) {
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
@export(testing_roc_dbg, .{ .name = "roc_dbg", .linkage = .Strong });
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .strong });
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .strong });
@export(testing_roc_dbg, .{ .name = "roc_dbg", .linkage = .strong });
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
@export(testing_roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
@export(testing_roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
@export(testing_roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
@export(testing_roc_getppid, .{ .name = "roc_getppid", .linkage = .strong });
@export(testing_roc_mmap, .{ .name = "roc_mmap", .linkage = .strong });
@export(testing_roc_shm_open, .{ .name = "roc_shm_open", .linkage = .strong });
}
if (builtin.os.tag == .windows) {
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
@export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .strong });
}
}
}
@ -198,10 +197,10 @@ const Refcount = enum {
atomic,
};
const RC_TYPE = Refcount.normal;
const RC_TYPE: Refcount = .normal;
pub fn increfRcPtrC(ptr_to_refcount: *isize, amount: isize) callconv(.C) void {
if (RC_TYPE == Refcount.none) return;
if (RC_TYPE == .none) return;
if (DEBUG_INCDEC and builtin.target.cpu.arch != .wasm32) {
std.debug.print("| increment {*}: ", .{ptr_to_refcount});
@ -212,7 +211,7 @@ pub fn increfRcPtrC(ptr_to_refcount: *isize, amount: isize) callconv(.C) void {
// Note: we assume that a refcount will never overflow.
// As such, we do not need to cap incrementing.
switch (RC_TYPE) {
Refcount.normal => {
.normal => {
if (DEBUG_INCDEC and builtin.target.cpu.arch != .wasm32) {
const old = @as(usize, @bitCast(ptr_to_refcount.*));
const new = old + @as(usize, @intCast(amount));
@ -225,10 +224,10 @@ pub fn increfRcPtrC(ptr_to_refcount: *isize, amount: isize) callconv(.C) void {
ptr_to_refcount.* += amount;
},
Refcount.atomic => {
_ = @atomicRmw(isize, ptr_to_refcount, std.builtin.AtomicRmwOp.Add, amount, Monotonic);
.atomic => {
_ = @atomicRmw(isize, ptr_to_refcount, .Add, amount, .monotonic);
},
Refcount.none => unreachable,
.none => unreachable,
}
}
}
@ -242,7 +241,7 @@ pub fn decrefRcPtrC(
// (NOT the start of the data, or the start of the allocation)
// this is of course unsafe, but we trust what we get from the llvm side
var bytes = @as([*]isize, @ptrCast(bytes_or_null));
const bytes = @as([*]isize, @ptrCast(bytes_or_null));
return @call(.always_inline, decref_ptr_to_refcount, .{ bytes, alignment, elements_refcounted });
}
@ -263,7 +262,7 @@ pub fn decrefDataPtrC(
alignment: u32,
elements_refcounted: bool,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
const bytes = bytes_or_null orelse return;
const data_ptr = @intFromPtr(bytes);
const tag_mask: usize = if (@sizeOf(usize) == 8) 0b111 else 0b11;
@ -279,7 +278,7 @@ pub fn increfDataPtrC(
bytes_or_null: ?[*]u8,
inc_amount: isize,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
const bytes = bytes_or_null orelse return;
const ptr = @intFromPtr(bytes);
const tag_mask: usize = if (@sizeOf(usize) == 8) 0b111 else 0b11;
@ -295,7 +294,7 @@ pub fn freeDataPtrC(
alignment: u32,
elements_refcounted: bool,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
const bytes = bytes_or_null orelse return;
const ptr = @intFromPtr(bytes);
const tag_mask: usize = if (@sizeOf(usize) == 8) 0b111 else 0b11;
@ -312,7 +311,7 @@ pub fn freeRcPtrC(
alignment: u32,
elements_refcounted: bool,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
const bytes = bytes_or_null orelse return;
return free_ptr_to_refcount(bytes, alignment, elements_refcounted);
}
@ -326,7 +325,7 @@ pub fn decref(
return;
}
var bytes = bytes_or_null orelse return;
const bytes = bytes_or_null orelse return;
const isizes: [*]isize = @as([*]isize, @ptrCast(@alignCast(bytes)));
@ -338,7 +337,7 @@ inline fn free_ptr_to_refcount(
alignment: u32,
elements_refcounted: bool,
) void {
if (RC_TYPE == Refcount.none) return;
if (RC_TYPE == .none) return;
const ptr_width = @sizeOf(usize);
const required_space: usize = if (elements_refcounted) (2 * ptr_width) else ptr_width;
const extra_bytes = @max(required_space, alignment);
@ -357,7 +356,7 @@ inline fn decref_ptr_to_refcount(
element_alignment: u32,
elements_refcounted: bool,
) void {
if (RC_TYPE == Refcount.none) return;
if (RC_TYPE == .none) return;
if (DEBUG_INCDEC and builtin.target.cpu.arch != .wasm32) {
std.debug.print("| decrement {*}: ", .{refcount_ptr});
@ -371,7 +370,7 @@ inline fn decref_ptr_to_refcount(
const refcount: isize = refcount_ptr[0];
if (refcount != REFCOUNT_MAX_ISIZE) {
switch (RC_TYPE) {
Refcount.normal => {
.normal => {
const old = @as(usize, @bitCast(refcount));
refcount_ptr[0] = refcount -% 1;
const new = @as(usize, @bitCast(refcount -% 1));
@ -387,13 +386,13 @@ inline fn decref_ptr_to_refcount(
free_ptr_to_refcount(refcount_ptr, alignment, elements_refcounted);
}
},
Refcount.atomic => {
var last = @atomicRmw(isize, &refcount_ptr[0], std.builtin.AtomicRmwOp.Sub, 1, Monotonic);
.atomic => {
const last = @atomicRmw(isize, &refcount_ptr[0], .Sub, 1, .monotonic);
if (last == REFCOUNT_ONE_ISIZE) {
free_ptr_to_refcount(refcount_ptr, alignment, elements_refcounted);
}
},
Refcount.none => unreachable,
.none => unreachable,
}
}
}
@ -401,7 +400,7 @@ inline fn decref_ptr_to_refcount(
pub fn isUnique(
bytes_or_null: ?[*]u8,
) callconv(.C) bool {
var bytes = bytes_or_null orelse return true;
const bytes = bytes_or_null orelse return true;
const ptr = @intFromPtr(bytes);
const tag_mask: usize = if (@sizeOf(usize) == 8) 0b111 else 0b11;
@ -487,7 +486,7 @@ pub fn allocateWithRefcount(
const extra_bytes = @max(required_space, element_alignment);
const length = extra_bytes + data_bytes;
var new_bytes: [*]u8 = alloc(length, alignment) orelse unreachable;
const new_bytes: [*]u8 = alloc(length, alignment) orelse unreachable;
if (DEBUG_ALLOC and builtin.target.cpu.arch != .wasm32) {
std.debug.print("+ allocated {*} ({} bytes with alignment {})\n", .{ new_bytes, data_bytes, alignment });
@ -495,7 +494,7 @@ pub fn allocateWithRefcount(
const data_ptr = new_bytes + extra_bytes;
const refcount_ptr = @as([*]usize, @ptrCast(@as([*]align(ptr_width) u8, @alignCast(data_ptr)) - ptr_width));
refcount_ptr[0] = if (RC_TYPE == Refcount.none) REFCOUNT_MAX_ISIZE else REFCOUNT_ONE;
refcount_ptr[0] = if (RC_TYPE == .none) REFCOUNT_MAX_ISIZE else REFCOUNT_ONE;
return data_ptr;
}
@ -546,14 +545,14 @@ pub const UpdateMode = enum(u8) {
test "increfC, refcounted data" {
var mock_rc: isize = REFCOUNT_ONE_ISIZE + 17;
var ptr_to_refcount: *isize = &mock_rc;
const ptr_to_refcount: *isize = &mock_rc;
increfRcPtrC(ptr_to_refcount, 2);
try std.testing.expectEqual(mock_rc, REFCOUNT_ONE_ISIZE + 19);
}
test "increfC, static data" {
var mock_rc: isize = REFCOUNT_MAX_ISIZE;
var ptr_to_refcount: *isize = &mock_rc;
const ptr_to_refcount: *isize = &mock_rc;
increfRcPtrC(ptr_to_refcount, 2);
try std.testing.expectEqual(mock_rc, REFCOUNT_MAX_ISIZE);
}

View file

@ -286,12 +286,14 @@ pub const INT_TO_FLOAT_CAST_F64: IntrinsicName =
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");
pub const NUM_ADD_WRAP_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_wrapped");
pub const NUM_ADD_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_with_overflow");
pub const NUM_ADD_CHECKED_FLOAT: IntrinsicName =
float_intrinsic!("roc_builtins.num.add_with_overflow");
pub const NUM_SUB_OR_PANIC_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.sub_or_panic");
pub const NUM_SUB_SATURATED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.sub_saturated");
pub const NUM_SUB_WRAP_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.sub_wrapped");
pub const NUM_SUB_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.sub_with_overflow");
pub const NUM_SUB_CHECKED_FLOAT: IntrinsicName =
float_intrinsic!("roc_builtins.num.sub_with_overflow");

View file

@ -887,7 +887,9 @@ fn can_annotation_help(
"tuples should never be implicitly inferred open"
);
debug_assert!(!elems.is_empty()); // We don't allow empty tuples
if elems.is_empty() {
env.problem(roc_problem::can::Problem::EmptyTupleType(region));
}
let elem_types = can_assigned_tuple_elems(
env,

View file

@ -9,7 +9,7 @@ use crate::scope::{PendingAbilitiesInScope, Scope};
use roc_exhaustive::ListArity;
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::ast::{self, ExtractSpaces, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
@ -476,7 +476,14 @@ pub fn canonicalize_pattern<'a>(
Pattern::OpaqueNotInScope(Loc::at(tag.region, name.into()))
}
},
_ => unreachable!("Other patterns cannot be applied"),
_ => {
env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern(
MalformedPatternProblem::CantApplyPattern,
tag.region,
)));
Pattern::UnsupportedPattern(region)
}
}
}
@ -817,7 +824,7 @@ pub fn canonicalize_record_destructs<'a>(
let mut opt_erroneous = None;
for loc_pattern in patterns.iter() {
match loc_pattern.value {
match loc_pattern.value.extract_spaces().item {
Identifier { ident: label } => {
match scope.introduce(label.into(), region) {
Ok(symbol) => {

View file

@ -1,11 +1,15 @@
use crate::{
collection::{fmt_collection, Braces},
expr::merge_spaces_conservative,
pattern::pattern_lift_spaces_after,
expr::{format_spaces, merge_spaces_conservative},
node::{Node, NodeSequenceBuilder, Nodify, Sp},
pattern::{pattern_lift_spaces_after, snakify_camel_ident},
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
Buf,
};
use bumpalo::{collections::Vec, Bump};
use bumpalo::{
collections::{String, Vec},
Bump,
};
use roc_parse::ast::{
AbilityImpls, AssignedField, Collection, CommentOrNewline, Expr, ExtractSpaces, FunctionArrow,
ImplementsAbilities, ImplementsAbility, ImplementsClause, Spaceable, Spaces, SpacesAfter,
@ -45,6 +49,7 @@ pub enum Parens {
InOperator,
InAsPattern,
InApplyLastArg,
InClosurePattern,
}
/// In an AST node, do we show newlines around it
@ -331,7 +336,11 @@ fn fmt_ty_ann(
}
TypeAnnotation::BoundVariable(v) => {
buf.indent(indent);
buf.push_str(v)
if *v == "implements" {
buf.push_str("(implements)");
} else {
buf.push_str(v);
}
}
TypeAnnotation::Wildcard => {
buf.indent(indent);
@ -343,7 +352,7 @@ fn fmt_ty_ann(
}
TypeAnnotation::TagUnion { tags, ext } => {
fmt_collection(buf, indent, Braces::Square, *tags, newlines);
fmt_tag_collection(buf, indent, *tags, newlines);
fmt_ext(ext, buf, indent);
}
@ -353,22 +362,39 @@ fn fmt_ty_ann(
}
TypeAnnotation::Record { fields, ext } => {
fmt_collection(buf, indent, Braces::Curly, *fields, newlines);
fmt_ty_field_collection(buf, indent, *fields, newlines);
fmt_ext(ext, buf, indent);
}
TypeAnnotation::As(lhs, _spaces, TypeHeader { name, vars }) => {
// TODO use _spaces?
TypeAnnotation::As(lhs, spaces, TypeHeader { name, vars }) => {
let write_parens = parens == Parens::InAsPattern || parens == Parens::InApply;
buf.indent(indent);
if write_parens {
buf.push('(')
}
let lhs_indent = buf.cur_line_indent();
lhs.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
.format_with_options(buf, Parens::InAsPattern, Newlines::No, indent);
buf.spaces(1);
format_spaces(buf, spaces, newlines, indent);
buf.indent(lhs_indent + INDENT);
buf.push_str("as");
buf.spaces(1);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
var.value
.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
var.value.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
lhs_indent + INDENT,
);
}
if write_parens {
buf.push(')')
}
}
@ -384,6 +410,7 @@ fn fmt_ty_ann(
buf.spaces(1);
}
for (i, has) in implements_clauses.iter().enumerate() {
buf.indent(indent);
buf.push_str(if i == 0 {
roc_parse::keyword::WHERE
} else {
@ -403,6 +430,114 @@ fn fmt_ty_ann(
}
}
fn fmt_ty_field_collection(
buf: &mut Buf<'_>,
indent: u16,
fields: Collection<'_, Loc<AssignedField<'_, TypeAnnotation<'_>>>>,
newlines: Newlines,
) {
let arena = buf.text.bump();
let mut new_items: Vec<'_, NodeSpaces<'_, Node<'_>>> =
Vec::with_capacity_in(fields.len(), arena);
let mut last_after: &[CommentOrNewline<'_>] = &[];
for item in fields.items.iter() {
let lifted = item.value.to_node(arena, Parens::NotNeeded);
let before = merge_spaces_conservative(arena, last_after, lifted.before);
last_after = lifted.after;
new_items.push(NodeSpaces {
before,
item: lifted.item,
after: &[],
});
}
let final_comments = merge_spaces_conservative(arena, last_after, fields.final_comments());
let new_items =
Collection::with_items_and_comments(arena, new_items.into_bump_slice(), final_comments);
fmt_collection(buf, indent, Braces::Curly, new_items, newlines);
}
fn fmt_tag_collection<'a>(
buf: &mut Buf<'_>,
indent: u16,
tags: Collection<'a, Loc<Tag<'a>>>,
newlines: Newlines,
) {
let arena = buf.text.bump();
let mut new_items: Vec<'_, NodeSpaces<'_, Node<'_>>> = Vec::with_capacity_in(tags.len(), arena);
let mut last_after: &[CommentOrNewline<'_>] = &[];
for item in tags.items.iter() {
let lifted = item.value.to_node(arena, Parens::NotNeeded);
let before = merge_spaces_conservative(arena, last_after, lifted.before);
last_after = lifted.after;
new_items.push(NodeSpaces {
before,
item: lifted.item,
after: &[],
});
}
let final_comments = merge_spaces_conservative(arena, last_after, tags.final_comments());
let new_items =
Collection::with_items_and_comments(arena, new_items.into_bump_slice(), final_comments);
fmt_collection(buf, indent, Braces::Square, new_items, newlines);
}
impl<'a> Nodify<'a> for Tag<'a> {
fn to_node<'b>(&'a self, arena: &'b Bump, parens: Parens) -> Spaces<'b, Node<'b>>
where
'a: 'b,
{
match self {
Tag::Apply { name, args } => {
if args.is_empty() {
Spaces {
before: &[],
item: Node::Literal(name.value),
after: &[],
}
} else {
let first = Node::Literal(name.value);
let mut new_args: Vec<'b, (Sp<'b>, Node<'b>)> =
Vec::with_capacity_in(args.len(), arena);
let mut last_after: &[CommentOrNewline<'_>] = &[];
for arg in args.iter() {
let lifted = arg.value.to_node(arena, Parens::InApply);
let before = merge_spaces_conservative(arena, last_after, lifted.before);
last_after = lifted.after;
new_args.push((Sp::with_space(before), lifted.item));
}
Spaces {
before: &[],
item: Node::Sequence(arena.alloc(first), new_args.into_bump_slice()),
after: last_after,
}
}
}
Tag::SpaceBefore(inner, sp) => {
let mut inner = inner.to_node(arena, parens);
inner.before = merge_spaces_conservative(arena, sp, inner.before);
inner
}
Tag::SpaceAfter(inner, sp) => {
let mut inner = inner.to_node(arena, parens);
inner.after = merge_spaces_conservative(arena, inner.after, sp);
inner
}
}
}
}
fn lower<'a, 'b: 'a>(
arena: &'b Bump,
lifted: Spaces<'b, TypeAnnotation<'b>>,
@ -427,7 +562,6 @@ fn lower<'a, 'b: 'a>(
fn fmt_ty_collection(
buf: &mut Buf<'_>,
indent: u16,
braces: Braces,
items: Collection<'_, Loc<TypeAnnotation<'_>>>,
@ -440,16 +574,12 @@ fn fmt_ty_collection(
let mut last_after: &[CommentOrNewline<'_>] = &[];
for (i, item) in items.items.iter().enumerate() {
let func_ty_risky = i > 0;
let lifted = ann_lift_to_node(
if func_ty_risky {
Parens::InCollection
} else {
Parens::NotNeeded
},
arena,
&item.value,
);
let parens = if i > 0 {
Parens::InCollection
} else {
Parens::NotNeeded
};
let lifted = item.value.to_node(arena, parens);
let before = merge_spaces_conservative(arena, last_after, lifted.before);
last_after = lifted.after;
new_items.push(NodeSpaces {
@ -552,6 +682,70 @@ impl<'a> Formattable for AssignedField<'a, Expr<'a>> {
}
}
impl<'a> Nodify<'a> for AssignedField<'a, TypeAnnotation<'a>> {
fn to_node<'b>(&'a self, arena: &'b Bump, parens: Parens) -> Spaces<'b, Node<'b>>
where
'a: 'b,
{
match self {
AssignedField::RequiredValue(name, sp, value) => {
assigned_field_value_to_node(name.value, arena, sp, &value.value, ":")
}
AssignedField::IgnoredValue(name, sp, value) => {
let mut n = String::with_capacity_in(name.value.len() + 1, arena);
n.push('_');
n.push_str(name.value);
assigned_field_value_to_node(n.into_bump_str(), arena, sp, &value.value, ":")
}
AssignedField::OptionalValue(name, sp, value) => {
assigned_field_value_to_node(name.value, arena, sp, &value.value, "?")
}
AssignedField::LabelOnly(name) => Spaces {
before: &[],
item: Node::Literal(name.value),
after: &[],
},
AssignedField::SpaceBefore(inner, sp) => {
let mut inner = inner.to_node(arena, parens);
inner.before = merge_spaces_conservative(arena, sp, inner.before);
inner
}
AssignedField::SpaceAfter(inner, sp) => {
let mut inner = inner.to_node(arena, parens);
inner.after = merge_spaces_conservative(arena, inner.after, sp);
inner
}
}
}
}
fn assigned_field_value_to_node<'a, 'b>(
name: &'b str,
arena: &'b Bump,
sp: &'a [CommentOrNewline<'a>],
value: &'a TypeAnnotation<'a>,
sep: &'static str,
) -> Spaces<'b, Node<'b>>
where
'a: 'b,
{
let first = Node::Literal(name);
let mut b = NodeSequenceBuilder::new(arena, first, 2);
b.push(Sp::with_space(sp), Node::Literal(sep));
let value_lifted = value.to_node(arena, Parens::NotNeeded);
b.push(Sp::with_space(value_lifted.before), value_lifted.item);
Spaces {
before: &[],
item: b.build(),
after: value_lifted.after,
}
}
fn is_multiline_assigned_field_help<T: Formattable>(afield: &AssignedField<'_, T>) -> bool {
use self::AssignedField::*;
@ -567,7 +761,6 @@ fn is_multiline_assigned_field_help<T: Formattable>(afield: &AssignedField<'_, T
fn format_assigned_field_help<T>(
zelf: &AssignedField<T>,
buf: &mut Buf,
indent: u16,
separator_spaces: usize,
is_multiline: bool,
@ -583,7 +776,11 @@ fn format_assigned_field_help<T>(
}
buf.indent(indent);
buf.push_str(name.value);
if buf.flags().snakify {
snakify_camel_ident(buf, name.value);
} else {
buf.push_str(name.value);
}
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
@ -601,7 +798,11 @@ fn format_assigned_field_help<T>(
}
buf.indent(indent);
buf.push_str(name.value);
if buf.flags().snakify {
snakify_camel_ident(buf, name.value);
} else {
buf.push_str(name.value);
}
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
@ -620,7 +821,11 @@ fn format_assigned_field_help<T>(
buf.indent(indent);
buf.push('_');
buf.push_str(name.value);
if buf.flags().snakify {
snakify_camel_ident(buf, name.value);
} else {
buf.push_str(name.value);
}
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
@ -638,7 +843,11 @@ fn format_assigned_field_help<T>(
}
buf.indent(indent);
buf.push_str(name.value);
if buf.flags().snakify {
snakify_camel_ident(buf, name.value);
} else {
buf.push_str(name.value);
}
}
AssignedField::SpaceBefore(sub_field, spaces) => {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
@ -805,8 +1014,8 @@ impl<'a> Formattable for ImplementsAbilities<'a> {
ImplementsAbilities::Implements(has_abilities) => {
if newlines == Newlines::Yes {
buf.newline();
buf.indent(indent);
}
buf.indent(indent);
buf.push_str(roc_parse::keyword::IMPLEMENTS);
buf.spaces(1);
fmt_collection(buf, indent, Braces::Square, *has_abilities, Newlines::No);
@ -1068,159 +1277,141 @@ pub fn type_head_lift_spaces_after<'a, 'b: 'a>(
}
}
type Sp<'a> = &'a [CommentOrNewline<'a>];
#[derive(Copy, Clone, Debug)]
enum Node<'a> {
DelimitedSequence(Braces, &'a [(Sp<'a>, Node<'a>)], Sp<'a>),
TypeAnnotation(TypeAnnotation<'a>),
}
impl<'a> Formattable for Node<'a> {
fn is_multiline(&self) -> bool {
impl<'a> Nodify<'a> for TypeAnnotation<'a> {
fn to_node<'b>(&'a self, arena: &'b Bump, parens: Parens) -> Spaces<'b, Node<'b>>
where
'a: 'b,
{
match self {
Node::DelimitedSequence(_braces, lefts, right) => {
if !right.is_empty() {
return true;
TypeAnnotation::Apply(module, func, args) => {
if args.is_empty() {
return Spaces {
item: Node::TypeAnnotation(*self),
before: &[],
after: &[],
};
}
for (sp, l) in *lefts {
if l.is_multiline() || !sp.is_empty() {
return true;
let mut new_args = Vec::with_capacity_in(args.len(), arena);
if !args.is_empty() {
for arg in args.iter().take(args.len() - 1) {
let lifted = ann_lift_spaces(arena, &arg.value);
new_args.push(Loc::at(arg.region, lower(arena, lifted)));
}
}
false
}
Node::TypeAnnotation(type_annotation) => type_annotation.is_multiline(),
}
}
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
match self {
Node::DelimitedSequence(braces, lefts, right) => {
buf.indent(indent);
buf.push(braces.start());
for (sp, l) in *lefts {
if !sp.is_empty() {
fmt_spaces(buf, sp.iter(), indent);
let after = if let Some(last) = args.last() {
let lifted = ann_lift_spaces(arena, &last.value);
if lifted.before.is_empty() {
new_args.push(Loc::at(last.region, lifted.item));
} else {
new_args.push(Loc::at(
last.region,
TypeAnnotation::SpaceBefore(arena.alloc(lifted.item), lifted.before),
));
}
l.format_with_options(buf, parens, newlines, indent);
}
if !right.is_empty() {
fmt_spaces(buf, right.iter(), indent);
}
buf.indent(indent);
buf.push(braces.end());
}
Node::TypeAnnotation(type_annotation) => {
type_annotation.format_with_options(buf, parens, newlines, indent);
}
}
}
}
fn ann_lift_to_node<'a, 'b: 'a>(
parens: Parens,
arena: &'a Bump,
ann: &TypeAnnotation<'b>,
) -> Spaces<'a, Node<'a>> {
match ann {
TypeAnnotation::Apply(module, func, args) => {
if args.is_empty() {
return Spaces {
item: Node::TypeAnnotation(*ann),
before: &[],
after: &[],
};
}
let mut new_args = Vec::with_capacity_in(args.len(), arena);
if !args.is_empty() {
for arg in args.iter().take(args.len() - 1) {
let lifted = ann_lift_spaces(arena, &arg.value);
new_args.push(Loc::at(arg.region, lower(arena, lifted)));
}
}
let after = if let Some(last) = args.last() {
let lifted = ann_lift_spaces(arena, &last.value);
if lifted.before.is_empty() {
new_args.push(Loc::at(last.region, lifted.item));
lifted.after
} else {
new_args.push(Loc::at(
last.region,
TypeAnnotation::SpaceBefore(arena.alloc(lifted.item), lifted.before),
));
}
lifted.after
} else {
&[]
};
&[]
};
Spaces {
before: &[],
item: Node::TypeAnnotation(TypeAnnotation::Apply(
let item = Node::TypeAnnotation(TypeAnnotation::Apply(
module,
func,
new_args.into_bump_slice(),
)),
after,
}
}
TypeAnnotation::SpaceBefore(expr, spaces) => {
let mut inner = ann_lift_to_node(parens, arena, expr);
inner.before = merge_spaces_conservative(arena, spaces, inner.before);
inner
}
TypeAnnotation::SpaceAfter(expr, spaces) => {
let mut inner = ann_lift_to_node(parens, arena, expr);
inner.after = merge_spaces_conservative(arena, inner.after, spaces);
inner
}
TypeAnnotation::Function(args, purity, res) => {
let new_args = arena.alloc_slice_copy(args);
let before = if let Some(first) = new_args.first_mut() {
let lifted = ann_lift_spaces_before(arena, &first.value);
first.value = lifted.item;
lifted.before
} else {
&[]
};
let new_res = ann_lift_spaces_after(arena, &res.value);
let new_ann = TypeAnnotation::Function(
new_args,
*purity,
arena.alloc(Loc::at_zero(new_res.item)),
);
let inner = Spaces {
before,
item: Node::TypeAnnotation(new_ann),
after: new_res.after,
};
if parens == Parens::InCollection {
let node = Node::DelimitedSequence(
Braces::Round,
arena.alloc_slice_copy(&[(inner.before, inner.item)]),
inner.after,
);
));
Spaces {
before: &[],
item: node,
after: &[],
if parens == Parens::InApply {
parens_around_node(
arena,
Spaces {
before: &[],
item,
after,
},
)
} else {
Spaces {
before: &[],
item,
after,
}
}
} else {
}
TypeAnnotation::SpaceBefore(expr, spaces) => {
let mut inner = expr.to_node(arena, parens);
inner.before = merge_spaces_conservative(arena, spaces, inner.before);
inner
}
TypeAnnotation::SpaceAfter(expr, spaces) => {
let mut inner = expr.to_node(arena, parens);
inner.after = merge_spaces_conservative(arena, inner.after, spaces);
inner
}
TypeAnnotation::Function(args, purity, res) => {
let new_args = arena.alloc_slice_copy(args);
let before = if let Some(first) = new_args.first_mut() {
let lifted = ann_lift_spaces_before(arena, &first.value);
first.value = lifted.item;
lifted.before
} else {
&[]
};
let new_res = ann_lift_spaces_after(arena, &res.value);
let new_ann = TypeAnnotation::Function(
new_args,
*purity,
arena.alloc(Loc::at_zero(new_res.item)),
);
let inner = Spaces {
before,
item: Node::TypeAnnotation(new_ann),
after: new_res.after,
};
if parens == Parens::InCollection || parens == Parens::InApply {
parens_around_node(arena, inner)
} else {
inner
}
}
TypeAnnotation::As(_left, _sp, _right) => {
let lifted = ann_lift_spaces(arena, self);
let item = Spaces {
before: lifted.before,
item: Node::TypeAnnotation(lifted.item),
after: lifted.after,
};
if parens == Parens::InApply || parens == Parens::InAsPattern {
parens_around_node(arena, item)
} else {
item
}
}
_ => {
let lifted = ann_lift_spaces(arena, self);
Spaces {
before: lifted.before,
item: Node::TypeAnnotation(lifted.item),
after: lifted.after,
}
}
}
_ => Spaces {
before: &[],
item: Node::TypeAnnotation(*ann),
after: &[],
},
}
}
fn parens_around_node<'a, 'b: 'a>(
arena: &'a Bump,
item: Spaces<'b, Node<'b>>,
) -> Spaces<'a, Node<'a>> {
Spaces {
before: &[],
item: Node::DelimitedSequence(
Braces::Round,
arena.alloc_slice_copy(&[(item.before.into(), item.item)]),
Sp::empty(),
),
// We move the comments/newlines to the outer scope, since they tend to migrate there when re-parsed
after: item.after,
}
}

View file

@ -7,8 +7,8 @@ use crate::expr::{
expr_lift_and_lower, expr_lift_spaces, expr_lift_spaces_after, expr_lift_spaces_before,
fmt_str_literal, is_str_multiline, sub_expr_requests_parens,
};
use crate::pattern::{fmt_pattern, pattern_lift_spaces};
use crate::pattern::{pattern_lift_spaces_before, starts_with_inline_comment};
use crate::pattern::pattern_fmt_apply;
use crate::pattern::pattern_lift_spaces_before;
use crate::spaces::{
fmt_comments_only, fmt_default_newline, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT,
};
@ -16,10 +16,10 @@ use crate::Buf;
use bumpalo::Bump;
use roc_error_macros::internal_error;
use roc_parse::ast::{
AbilityMember, CommentOrNewline, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword,
ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation, IngestedFileImport,
ModuleImport, ModuleImportParams, Pattern, Spaceable, Spaces, SpacesAfter, SpacesBefore,
StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
ModuleImportParams, Pattern, Spaceable, Spaces, SpacesAfter, SpacesBefore, StrLiteral,
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use roc_parse::expr::merge_spaces;
use roc_parse::header::Keyword;
@ -37,7 +37,6 @@ impl<'a> Formattable for Defs<'a> {
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
let mut prev_spaces = true;
@ -495,12 +494,22 @@ impl<'a> Formattable for TypeDef<'a> {
);
} else {
for member in members.iter() {
member.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent + INDENT,
);
let Spaces {
before,
item,
after,
} = member.name.value.extract_spaces();
fmt_spaces(buf, before.iter(), indent + INDENT);
buf.ensure_ends_with_newline();
buf.indent(indent + INDENT);
buf.push_str(item);
fmt_spaces(buf, after.iter(), indent + INDENT);
buf.spaces(1);
buf.push(':');
buf.spaces(1);
member.typ.value.format(buf, indent + 2 * INDENT);
}
}
}
@ -518,69 +527,16 @@ impl<'a> Formattable for TypeHeader<'a> {
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.indent(indent);
buf.push_str(self.name.value);
let vars_indent = if self.vars.iter().any(|v| v.is_multiline()) {
indent + INDENT
} else {
indent
};
let mut last_after: &[CommentOrNewline<'_>] = &[];
let mut last_multiline = false;
for var in self.vars.iter() {
let var = pattern_lift_spaces(buf.text.bump(), &var.value);
let before = if !last_after.is_empty() {
merge_spaces(buf.text.bump(), last_after, var.before)
} else {
var.before
};
if !before.is_empty() {
if !var.item.is_multiline() {
fmt_comments_only(buf, before.iter(), NewlineAt::Bottom, vars_indent)
} else {
fmt_spaces(buf, before.iter(), vars_indent);
}
}
buf.ensure_ends_with_whitespace();
last_after = var.after;
last_multiline = var.item.is_multiline();
let need_parens = matches!(var.item, Pattern::Apply(..));
if need_parens {
buf.push_str("(");
}
fmt_pattern(buf, &var.item, vars_indent, Parens::NotNeeded);
buf.indent(vars_indent);
if need_parens {
buf.push_str(")");
}
}
if !last_after.is_empty() {
if starts_with_inline_comment(last_after.iter()) {
buf.spaces(1);
}
if !last_multiline {
fmt_comments_only(buf, last_after.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, last_after.iter(), indent);
}
}
pattern_fmt_apply(
buf,
Pattern::Tag(self.name.value),
self.vars,
Parens::NotNeeded,
indent,
self.vars.iter().any(|v| v.is_multiline()),
);
}
}
@ -697,6 +653,7 @@ impl<'a> Formattable for IngestedFileImport<'a> {
fmt_str_literal(buf, path.value, indent);
name.keyword.format(buf, indent);
buf.indent(indent);
buf.push_str(name.item.value);
annotation.format(buf, indent);
@ -803,6 +760,7 @@ impl<'a> Formattable for IngestedFileAnnotation<'a> {
} = self;
fmt_default_spaces(buf, before_colon, indent);
buf.indent(indent);
buf.push_str(":");
buf.spaces(1);
annotation.format(buf, indent);
@ -901,7 +859,6 @@ fn fmt_general_def<L: Formattable>(
lhs: L,
lhs_parens: Parens,
buf: &mut Buf,
indent: u16,
sep: &str,
rhs: &TypeAnnotation,
@ -1032,6 +989,9 @@ pub fn fmt_body<'a>(
&& pattern_extracted.before.iter().all(|s| s.is_newline())
&& pattern_extracted.after.iter().all(|s| s.is_newline())
&& !matches!(body.extract_spaces().item, Expr::Defs(..))
&& !matches!(body.extract_spaces().item, Expr::Return(..))
&& !matches!(body.extract_spaces().item, Expr::Backpassing(..))
&& !starts_with_expect_ident(body)
} else {
false
};
@ -1049,6 +1009,7 @@ pub fn fmt_body<'a>(
} else {
buf.spaces(1);
}
let indent = buf.cur_line_indent();
buf.push_str("=");
let body = expr_lift_and_lower(Parens::NotNeeded, buf.text.bump(), body);
@ -1142,6 +1103,19 @@ pub fn fmt_body<'a>(
}
}
fn starts_with_expect_ident(expr: &Expr<'_>) -> bool {
// We need to be persnickety about not formatting `{}=expect foo` into `expect foo`,
// because `expect` is treated as a keyword at the statement level but not at the expr level.
// If we removed the `{}=` in this case, that would change the meaning
match expr {
Expr::Apply(inner, _, _) => starts_with_expect_ident(&inner.value),
Expr::Var { module_name, ident } => {
module_name.is_empty() && (*ident == "expect" || *ident == "expect!")
}
_ => false,
}
}
pub fn starts_with_block_string_literal(expr: &Expr<'_>) -> bool {
match expr {
Expr::Str(s) => is_str_multiline(s),

View file

@ -2,7 +2,8 @@ use crate::annotation::{except_last, is_collection_multiline, Formattable, Newli
use crate::collection::{fmt_collection, Braces};
use crate::def::{fmt_defs, valdef_lift_spaces_before};
use crate::pattern::{
fmt_pattern, pattern_lift_spaces, pattern_lift_spaces_before, starts_with_inline_comment,
fmt_pattern, pattern_lift_spaces, pattern_lift_spaces_before, snakify_camel_ident,
starts_with_inline_comment,
};
use crate::spaces::{
count_leading_newlines, fmt_comments_only, fmt_spaces, fmt_spaces_no_blank_lines,
@ -72,8 +73,11 @@ fn format_expr_only(
buf.push_str(module_name);
buf.push('.');
}
buf.push_str(ident);
if buf.flags().snakify {
snakify_camel_ident(buf, ident);
} else {
buf.push_str(ident);
}
}
Expr::Underscore(name) => {
buf.indent(indent);
@ -133,13 +137,19 @@ fn format_expr_only(
buf.push_str(string);
}
Expr::Record(fields) => {
fmt_record_like(buf, None, *fields, indent, assigned_field_to_spaces);
fmt_record_like(
buf,
None,
prepare_expr_field_collection(buf.text.bump(), *fields),
indent,
assigned_field_to_spaces,
);
}
Expr::RecordUpdate { update, fields } => {
fmt_record_like(
buf,
Some(RecordPrefix::Update(update)),
*fields,
prepare_expr_field_collection(buf.text.bump(), *fields),
indent,
assigned_field_to_spaces,
);
@ -148,7 +158,7 @@ fn format_expr_only(
fmt_record_like(
buf,
Some(RecordPrefix::Mapper(mapper)),
*fields,
prepare_expr_field_collection(buf.text.bump(), *fields),
indent,
assigned_field_to_spaces,
);
@ -297,19 +307,33 @@ fn format_expr_only(
buf.indent(indent);
buf.push('.');
match key {
Accessor::RecordField(key) => buf.push_str(key),
Accessor::RecordField(key) => {
if buf.flags().snakify {
snakify_camel_ident(buf, key);
} else {
buf.push_str(key);
}
}
Accessor::TupleIndex(key) => buf.push_str(key),
}
}
Expr::RecordUpdater(key) => {
buf.indent(indent);
buf.push('&');
buf.push_str(key);
if buf.flags().snakify {
snakify_camel_ident(buf, key);
} else {
buf.push_str(key);
}
}
Expr::RecordAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');
buf.push_str(key);
if buf.flags().snakify {
snakify_camel_ident(buf, key);
} else {
buf.push_str(key);
}
}
Expr::TupleAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
@ -325,7 +349,11 @@ fn format_expr_only(
}
Expr::MalformedIdent(str, _) => {
buf.indent(indent);
buf.push_str(str)
if buf.flags().snakify {
snakify_camel_ident(buf, str);
} else {
buf.push_str(str);
}
}
Expr::MalformedSuffixed(loc_expr) => {
buf.indent(indent);
@ -338,6 +366,110 @@ fn format_expr_only(
}
}
fn prepare_expr_field_collection<'a>(
arena: &'a Bump,
items: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
) -> Collection<'a, Loc<AssignedField<'a, Expr<'a>>>> {
let mut new_items: Vec<'_, Loc<AssignedField<'a, Expr<'a>>>> =
Vec::with_capacity_in(items.len(), arena);
let mut last_after: &[CommentOrNewline<'_>] = &[];
for (i, item) in items.items.iter().enumerate() {
let mut lifted = assigned_field_lift_spaces(arena, item.value);
if i == items.items.len() - 1 {
last_after = lifted.after;
lifted.after = &[];
}
new_items.push(Loc::at(item.region, lower_assigned_field(arena, lifted)));
}
let final_comments = merge_spaces_conservative(arena, last_after, items.final_comments());
Collection::with_items_and_comments(arena, new_items.into_bump_slice(), final_comments)
}
fn lower_assigned_field<'a>(
arena: &'a Bump,
lifted: Spaces<'a, AssignedField<'a, Expr<'a>>>,
) -> AssignedField<'a, Expr<'a>> {
if lifted.before.is_empty() && lifted.after.is_empty() {
return lifted.item;
}
if lifted.before.is_empty() {
return AssignedField::SpaceAfter(arena.alloc(lifted.item), lifted.after);
}
if lifted.after.is_empty() {
return AssignedField::SpaceBefore(arena.alloc(lifted.item), lifted.before);
}
AssignedField::SpaceBefore(
arena.alloc(AssignedField::SpaceAfter(
arena.alloc(lifted.item),
lifted.after,
)),
lifted.before,
)
}
fn assigned_field_lift_spaces<'a, 'b: 'a>(
arena: &'a Bump,
value: AssignedField<'b, Expr<'b>>,
) -> Spaces<'a, AssignedField<'a, Expr<'a>>> {
match value {
AssignedField::RequiredValue(name, sp, value) => {
let new_value = expr_lift_spaces_after(Parens::NotNeeded, arena, &value.value);
Spaces {
before: &[],
item: AssignedField::RequiredValue(
name,
sp,
arena.alloc(Loc::at(value.region, new_value.item)),
),
after: new_value.after,
}
}
AssignedField::OptionalValue(name, sp, value) => {
let new_value = expr_lift_spaces_after(Parens::NotNeeded, arena, &value.value);
Spaces {
before: &[],
item: AssignedField::OptionalValue(
name,
sp,
arena.alloc(Loc::at(value.region, new_value.item)),
),
after: new_value.after,
}
}
AssignedField::IgnoredValue(name, sp, value) => {
let new_value = expr_lift_spaces_after(Parens::NotNeeded, arena, &value.value);
Spaces {
before: &[],
item: AssignedField::IgnoredValue(
name,
sp,
arena.alloc(Loc::at(value.region, new_value.item)),
),
after: new_value.after,
}
}
AssignedField::LabelOnly(name) => Spaces {
before: &[],
item: AssignedField::LabelOnly(name),
after: &[],
},
AssignedField::SpaceBefore(inner, sp) => {
let mut inner = assigned_field_lift_spaces(arena, *inner);
inner.before = merge_spaces_conservative(arena, sp, inner.before);
inner
}
AssignedField::SpaceAfter(inner, sp) => {
let mut inner = assigned_field_lift_spaces(arena, *inner);
inner.after = merge_spaces_conservative(arena, inner.after, sp);
inner
}
}
}
pub fn expr_is_multiline(me: &Expr<'_>, comments_only: bool) -> bool {
match me {
// Return whether these spaces contain any Newlines
@ -389,16 +521,7 @@ pub fn expr_is_multiline(me: &Expr<'_>, comments_only: bool) -> bool {
.any(|loc_arg| expr_is_multiline(&loc_arg.value, comments_only))
}
Expr::DbgStmt {
first: condition,
extra_args,
..
} => {
expr_is_multiline(&condition.value, comments_only)
|| extra_args
.iter()
.any(|loc_arg| expr_is_multiline(&loc_arg.value, comments_only))
}
Expr::DbgStmt { .. } => true,
Expr::LowLevelDbg(_, _, _) => {
unreachable!("LowLevelDbg should only exist after desugaring, not during formatting")
}
@ -513,6 +636,9 @@ fn requires_space_after_unary(item: &Expr<'_>) -> bool {
is_negative,
} => *is_negative,
Expr::RecordUpdater(..) => true,
Expr::RecordAccess(inner, _field) | Expr::TupleAccess(inner, _field) => {
requires_space_after_unary(inner)
}
Expr::Apply(inner, _, _) => requires_space_after_unary(&inner.value),
Expr::TrySuffix { target: _, expr } => requires_space_after_unary(expr),
Expr::SpaceAfter(inner, _) | Expr::SpaceBefore(inner, _) => {
@ -611,20 +737,16 @@ fn fmt_apply(
}
last_after = arg.after;
if should_reflow_outdentable {
buf.spaces(1);
// Ignore any comments+newlines before/after.
// We checked above that there's only a single newline before the last arg,
// which we're intentionally ignoring.
format_expr_only(&arg.item, buf, Parens::InApply, Newlines::Yes, arg_indent);
} else if needs_indent {
if needs_indent {
buf.ensure_ends_with_newline();
format_expr_only(&arg.item, buf, Parens::InApply, Newlines::Yes, arg_indent);
} else {
buf.spaces(1);
}
if matches!(arg.item, Expr::Var { module_name, ident } if module_name.is_empty() && ident == "implements")
{
fmt_parens(&arg.item, buf, arg_indent);
} else {
format_expr_only(&arg.item, buf, Parens::InApply, Newlines::Yes, arg_indent);
}
}
@ -645,9 +767,10 @@ fn is_outdentable_collection(expr: &Expr<'_>) -> bool {
fn fmt_parens(sub_expr: &Expr<'_>, buf: &mut Buf<'_>, indent: u16) {
let should_add_newlines = match sub_expr {
Expr::Closure(..) | Expr::SpaceBefore(..) | Expr::SpaceAfter(Expr::Closure(..), ..) => {
false
}
Expr::Closure(..)
| Expr::SpaceBefore(..)
| Expr::SpaceAfter(Expr::Closure(..), ..)
| Expr::DbgStmt { .. } => false,
_ => sub_expr.is_multiline(),
};
@ -669,7 +792,7 @@ fn fmt_parens(sub_expr: &Expr<'_>, buf: &mut Buf<'_>, indent: u16) {
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, next_indent);
if !matches!(sub_expr, Expr::SpaceAfter(..)) && should_add_newlines {
buf.newline();
buf.ensure_ends_with_newline();
}
buf.indent(indent);
buf.push(')');
@ -739,6 +862,7 @@ fn starts_with_newline(expr: &Expr) -> bool {
SpaceBefore(_, comment_or_newline) => {
matches!(comment_or_newline.first(), Some(CommentOrNewline::Newline))
}
DbgStmt { .. } => true,
_ => false,
}
}
@ -793,6 +917,7 @@ fn format_str_segment(seg: &StrSegment, buf: &mut Buf) {
Newlines::No, // Interpolations can never have newlines
min_indent,
);
buf.indent(min_indent);
buf.push(')');
}
}
@ -1009,7 +1134,7 @@ pub fn expr_lift_spaces<'a, 'b: 'a>(
after: body_lifted.after,
}
}
Expr::If { .. } | Expr::When(_, _) | Expr::Return(_, _) => {
Expr::If { .. } | Expr::When(_, _) => {
if parens == Parens::InApply || parens == Parens::InApplyLastArg {
Spaces {
before: &[],
@ -1024,6 +1149,34 @@ pub fn expr_lift_spaces<'a, 'b: 'a>(
}
}
}
Expr::Return(val, opt_after) => {
if parens == Parens::InApply || parens == Parens::InApplyLastArg {
Spaces {
before: &[],
item: Expr::ParensAround(arena.alloc(*expr)),
after: &[],
}
} else if let Some(after) = opt_after {
let after_lifted = expr_lift_spaces_after(Parens::NotNeeded, arena, &after.value);
Spaces {
before: &[],
item: Expr::Return(
val,
Some(arena.alloc(Loc::at(after.region, after_lifted.item))),
),
after: after_lifted.after,
}
} else {
let val_lifted = expr_lift_spaces_after(Parens::NotNeeded, arena, &val.value);
Spaces {
before: &[],
item: Expr::Return(arena.alloc(Loc::at(val.region, val_lifted.item)), None),
after: val_lifted.after,
}
}
}
Expr::Backpassing(pats, call, continuation) => {
let pats = arena.alloc_slice_copy(pats);
let before = if let Some(first) = pats.first_mut() {
@ -1252,8 +1405,12 @@ fn fmt_binops<'a>(
expr_lift_spaces(Parens::InOperator, buf.text.bump(), &loc_left_side.value);
format_spaces(buf, lifted_left_side.before, Newlines::Yes, indent);
buf.indent(indent);
let line_indent = buf.cur_line_indent();
let need_parens = matches!(lifted_left_side.item, Expr::BinOps(..))
|| starts_with_unary_minus(lifted_left_side.item);
|| starts_with_unary_minus(lifted_left_side.item)
|| (ends_with_closure(&lifted_left_side.item) && line_indent < indent);
if need_parens {
fmt_parens(&lifted_left_side.item, buf, indent);
@ -1298,6 +1455,17 @@ fn fmt_binops<'a>(
format_spaces(buf, lifted_right_side.after, Newlines::Yes, indent);
}
fn ends_with_closure(item: &Expr<'_>) -> bool {
match item {
Expr::Closure(..) => true,
Expr::Apply(expr, args, _) => args
.last()
.map(|a| ends_with_closure(&a.value))
.unwrap_or_else(|| ends_with_closure(&expr.value)),
_ => false,
}
}
fn starts_with_unary_minus(item: Expr<'_>) -> bool {
match item {
Expr::UnaryOp(
@ -1380,7 +1548,6 @@ fn fmt_when<'a>(
buf: &mut Buf,
loc_condition: &'a Loc<Expr<'a>>,
branches: &[&'a WhenBranch<'a>],
indent: u16,
) {
buf.ensure_ends_with_newline();
@ -1464,45 +1631,37 @@ fn fmt_when<'a>(
}
if let Some(guard_expr) = &branch.guard {
buf.indent(indent + INDENT);
buf.push_str(" if");
buf.spaces(1);
guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
buf.indent(indent + INDENT);
let line_indent = buf.cur_line_indent();
buf.push_str(" ->");
match expr.value {
Expr::SpaceBefore(nested, spaces) => {
fmt_spaces_no_blank_lines(buf, spaces.iter(), indent + (INDENT * 2));
let inner_indent = line_indent + INDENT;
if is_multiline_expr {
buf.ensure_ends_with_newline();
} else {
buf.spaces(1);
}
let expr = expr_lift_spaces(Parens::NotNeeded, buf.text.bump(), &expr.value);
fmt_spaces_no_blank_lines(buf, expr.before.iter(), inner_indent);
if is_multiline_expr {
buf.ensure_ends_with_newline();
} else {
buf.spaces(1);
}
nested.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent + 2 * INDENT,
);
}
_ => {
if is_multiline_expr {
buf.ensure_ends_with_newline();
} else {
buf.spaces(1);
}
// expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, inner_indent);
format_expr_only(
&expr.item,
buf,
Parens::NotNeeded,
Newlines::Yes,
inner_indent,
);
expr.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent + 2 * INDENT,
);
}
if !expr.after.is_empty() {
format_spaces(buf, expr.after, Newlines::Yes, inner_indent);
}
prev_branch_was_multiline = is_multiline_expr || is_multiline_patterns;
@ -1515,9 +1674,9 @@ fn fmt_dbg_stmt<'a>(
extra_args: &'a [&'a Loc<Expr<'a>>],
continuation: &'a Loc<Expr<'a>>,
parens: Parens,
indent: u16,
) {
buf.ensure_ends_with_newline();
let mut args = Vec::with_capacity_in(extra_args.len() + 1, buf.text.bump());
args.push(condition);
args.extend_from_slice(extra_args);
@ -1557,26 +1716,35 @@ fn fmt_return<'a>(
after_return: &Option<&'a Loc<Expr<'a>>>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str(keyword::RETURN);
if matches!(return_value.value.extract_spaces().item, Expr::Defs(..)) {
buf.ensure_ends_with_newline();
} else {
buf.spaces(1);
}
let return_indent = if return_value.is_multiline() {
indent + INDENT
} else {
indent
};
return_value.format_with_options(buf, parens, Newlines::No, return_indent);
let value = expr_lift_spaces(parens, buf.text.bump(), &return_value.value);
if !value.before.is_empty() {
format_spaces(buf, value.before, newlines, return_indent);
}
if matches!(value.item, Expr::Defs(..) | Expr::Backpassing(..)) {
buf.ensure_ends_with_newline();
} else {
buf.spaces(1);
}
format_expr_only(&value.item, buf, parens, newlines, return_indent);
if !value.after.is_empty() {
format_spaces(buf, value.after, newlines, indent);
}
if let Some(after_return) = after_return {
let lifted = expr_lift_spaces(Parens::NotNeeded, buf.text.bump(), &after_return.value);
@ -1706,7 +1874,7 @@ fn fmt_closure<'a>(
}
arg.item
.format_with_options(buf, Parens::InAsPattern, Newlines::No, indent);
.format_with_options(buf, Parens::InClosurePattern, Newlines::No, indent);
if !arg.after.is_empty() {
if starts_with_inline_comment(arg.after.iter()) {
@ -1723,31 +1891,19 @@ fn fmt_closure<'a>(
buf.spaces(1);
}
let arrow_line_indent = buf.cur_line_indent();
buf.push_str("->");
buf.spaces(1);
let is_multiline = loc_ret.value.is_multiline();
// If the body is multiline, go down a line and indent.
let body_indent = if is_multiline {
indent + INDENT
arrow_line_indent + INDENT
} else {
indent
};
// the body of the Closure can be on the same line, or
// on a new line. If it's on the same line, insert a space.
match &loc_ret.value {
SpaceBefore(_, _) => {
// the body starts with (first comment and then) a newline
// do nothing
}
_ => {
// add a space after the `->`
buf.spaces(1);
}
};
if is_multiline {
match &loc_ret.value {
SpaceBefore(sub_expr, spaces) => {
@ -1760,7 +1916,6 @@ fn fmt_closure<'a>(
};
if should_outdent {
buf.spaces(1);
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} else {
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
@ -1783,11 +1938,8 @@ fn fmt_backpassing<'a>(
loc_patterns: &'a [Loc<Pattern<'a>>],
loc_body: &'a Loc<Expr<'a>>,
loc_ret: &'a Loc<Expr<'a>>,
outer_indent: u16,
) {
use self::Expr::*;
let arguments_are_multiline = loc_patterns
.iter()
.any(|loc_pattern| loc_pattern.is_multiline());
@ -1808,7 +1960,7 @@ fn fmt_backpassing<'a>(
Parens::NotNeeded
};
let pat = loc_pattern.value.extract_spaces();
let pat = pattern_lift_spaces(buf.text.bump(), &loc_pattern.value);
if !first {
buf.indent(arg_indent);
@ -1854,22 +2006,39 @@ fn fmt_backpassing<'a>(
arg_indent
};
// the body of the Backpass can be on the same line, or
// on a new line. If it's on the same line, insert a space.
buf.spaces(1);
let body_lifted = expr_lift_spaces(Parens::NotNeeded, buf.text.bump(), &loc_body.value);
let ret_lifted = expr_lift_spaces(Parens::NotNeeded, buf.text.bump(), &loc_ret.value);
match &loc_body.value {
SpaceBefore(_, _) => {
// the body starts with (first comment and then) a newline
// do nothing
}
_ => {
// add a space after the `<-`
buf.spaces(1);
}
};
if !body_lifted.before.is_empty() {
format_spaces(buf, body_lifted.before, Newlines::Yes, body_indent);
}
loc_body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, outer_indent);
format_expr_only(
&body_lifted.item,
buf,
Parens::NotNeeded,
Newlines::Yes,
body_indent,
);
let between = merge_spaces(buf.text.bump(), body_lifted.after, ret_lifted.before);
if !between.is_empty() {
format_spaces(buf, between, Newlines::Yes, outer_indent);
}
format_expr_only(
&ret_lifted.item,
buf,
Parens::NotNeeded,
Newlines::Yes,
outer_indent,
);
if !ret_lifted.after.is_empty() {
format_spaces(buf, ret_lifted.after, Newlines::Yes, outer_indent);
}
}
fn pattern_needs_parens_when_backpassing(pat: &Pattern) -> bool {
@ -1894,7 +2063,7 @@ fn fmt_record_like<'a, 'b: 'a, Field, ToSpacesAround>(
indent: u16,
to_space_around: ToSpacesAround,
) where
Field: Formattable,
Field: Formattable + std::fmt::Debug,
ToSpacesAround: Fn(&'a Bump, &'b Field) -> Spaces<'a, Field>,
{
let loc_fields = fields.items;
@ -1966,6 +2135,7 @@ fn fmt_record_like<'a, 'b: 'a, Field, ToSpacesAround>(
Newlines::No,
field_indent,
);
buf.indent(field_indent);
buf.push_str(",");
last_after = field_lifted.after;
}

View file

@ -3,6 +3,7 @@ use std::cmp::max;
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::expr::fmt_str_literal;
use crate::pattern::snakify_camel_ident;
use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT};
use crate::Buf;
use roc_parse::ast::{Collection, CommentOrNewline, Header, Spaced, Spaces, SpacesBefore};
@ -410,7 +411,17 @@ impl<'a> Formattable for ExposedName<'a> {
indent: u16,
) {
buf.indent(indent);
buf.push_str(self.as_str());
if buf.flags().snakify
&& self
.as_str()
.chars()
.next()
.is_some_and(|c| c.is_ascii_lowercase())
{
snakify_camel_ident(buf, self.as_str());
} else {
buf.push_str(self.as_str());
}
}
}

View file

@ -7,6 +7,7 @@ pub mod collection;
pub mod def;
pub mod expr;
pub mod header;
pub mod node;
pub mod pattern;
pub mod spaces;
@ -69,6 +70,7 @@ impl<'a> Buf<'a> {
self.beginning_of_line = false;
}
#[track_caller]
pub fn cur_line_indent(&self) -> u16 {
debug_assert!(!self.beginning_of_line, "cur_line_indent before indent");
self.line_indent
@ -93,11 +95,7 @@ impl<'a> Buf<'a> {
#[track_caller]
pub fn push_str_allow_spaces(&mut self, s: &str) {
debug_assert!(
!self.beginning_of_line,
"push_str: `{s}` with text:\n{}",
self.text
);
debug_assert!(!self.beginning_of_line, "push_str: `{s}`");
self.flush_spaces();
@ -106,11 +104,7 @@ impl<'a> Buf<'a> {
#[track_caller]
pub fn push_str(&mut self, s: &str) {
debug_assert!(
!self.beginning_of_line,
"push_str: `{s}` with text:\n{}",
self.text
);
debug_assert!(!self.beginning_of_line, "push_str: `{s}`");
debug_assert!(!s.contains('\n'));
debug_assert!(!s.ends_with(' '));

View file

@ -0,0 +1,147 @@
use bumpalo::{collections::Vec, Bump};
use roc_parse::ast::{CommentOrNewline, Spaces, TypeAnnotation};
use crate::{
annotation::{Formattable, Newlines, Parens},
collection::Braces,
spaces::fmt_spaces,
Buf,
};
#[derive(Copy, Clone, Debug)]
pub struct Sp<'a> {
default_space: bool, // if true and comments is empty, use a space (' ')
comments: &'a [CommentOrNewline<'a>],
}
impl<'a> Sp<'a> {
pub fn empty() -> Sp<'a> {
Sp {
default_space: false,
comments: &[],
}
}
pub fn space() -> Sp<'a> {
Sp {
default_space: true,
comments: &[],
}
}
pub fn with_space(sp: &'a [CommentOrNewline<'a>]) -> Self {
Sp {
default_space: true,
comments: sp,
}
}
}
impl<'a> From<&'a [CommentOrNewline<'a>]> for Sp<'a> {
fn from(comments: &'a [CommentOrNewline<'a>]) -> Self {
Sp {
default_space: false,
comments,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum Node<'a> {
Literal(&'a str),
Sequence(&'a Node<'a>, &'a [(Sp<'a>, Node<'a>)]),
DelimitedSequence(Braces, &'a [(Sp<'a>, Node<'a>)], Sp<'a>),
TypeAnnotation(TypeAnnotation<'a>),
}
pub trait Nodify<'a> {
fn to_node<'b>(&'a self, arena: &'b Bump, parens: Parens) -> Spaces<'b, Node<'b>>
where
'a: 'b;
}
fn fmt_sp(buf: &mut Buf, sp: Sp<'_>, indent: u16) {
if !sp.comments.is_empty() {
fmt_spaces(buf, sp.comments.iter(), indent);
} else if sp.default_space {
buf.spaces(1);
}
}
impl<'a> Formattable for Node<'a> {
fn is_multiline(&self) -> bool {
match self {
Node::DelimitedSequence(_braces, lefts, right) => {
right.comments.is_empty()
&& lefts
.iter()
.any(|(sp, l)| l.is_multiline() || !sp.comments.is_empty())
}
Node::Sequence(first, rest) => {
first.is_multiline()
|| rest
.iter()
.any(|(sp, l)| l.is_multiline() || !sp.comments.is_empty())
}
Node::TypeAnnotation(type_annotation) => type_annotation.is_multiline(),
Node::Literal(_) => false,
}
}
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
match self {
Node::DelimitedSequence(braces, lefts, right) => {
buf.indent(indent);
buf.push(braces.start());
for (sp, l) in *lefts {
fmt_sp(buf, *sp, indent);
l.format_with_options(buf, parens, newlines, indent);
}
fmt_sp(buf, *right, indent);
buf.indent(indent);
buf.push(braces.end());
}
Node::Sequence(first, rest) => {
first.format_with_options(buf, parens, newlines, indent);
for (sp, l) in *rest {
fmt_sp(buf, *sp, indent);
l.format_with_options(buf, parens, newlines, indent);
}
}
Node::TypeAnnotation(type_annotation) => {
type_annotation.format_with_options(buf, parens, newlines, indent);
}
Node::Literal(text) => {
buf.indent(indent);
buf.push_str(text);
}
}
}
}
pub struct NodeSequenceBuilder<'a> {
first: Node<'a>,
rest: Vec<'a, (Sp<'a>, Node<'a>)>,
}
impl<'a> NodeSequenceBuilder<'a> {
pub fn new(arena: &'a Bump, first: Node<'a>, capacity: usize) -> Self {
Self {
first,
rest: Vec::with_capacity_in(capacity, arena),
}
}
pub fn push(&mut self, sp: Sp<'a>, literal: Node<'a>) {
self.rest.push((sp, literal));
}
pub fn build(self) -> Node<'a> {
Node::Sequence(
self.rest.bump().alloc(self.first),
self.rest.into_bump_slice(),
)
}
}

View file

@ -102,7 +102,6 @@ fn fmt_pattern_inner(
pat: &Pattern<'_>,
buf: &mut Buf,
parens: Parens,
indent: u16,
outer_is_multiline: bool,
force_newline_at_start: bool,
@ -127,7 +126,7 @@ fn fmt_pattern_inner(
let is_multiline = me.item.is_multiline();
fmt_pattern_only(me, buf, indent, parens, is_multiline);
fmt_pattern_only(&me.item, buf, parens, indent, is_multiline);
if !me.after.is_empty() {
if starts_with_inline_comment(me.after.iter()) {
@ -147,75 +146,30 @@ fn fmt_pattern_inner(
}
fn fmt_pattern_only(
me: Spaces<'_, Pattern<'_>>,
me: &Pattern<'_>,
buf: &mut Buf<'_>,
indent: u16,
parens: Parens,
indent: u16,
is_multiline: bool,
) {
match me.item {
Pattern::Identifier { ident: string } => {
match me {
Pattern::Identifier { ident } => {
buf.indent(indent);
snakify_camel_ident(buf, string);
snakify_camel_ident(buf, ident);
}
Pattern::Tag(name) | Pattern::OpaqueRef(name) => {
buf.indent(indent);
buf.push_str(name);
}
Pattern::Apply(loc_pattern, loc_arg_patterns) => {
buf.indent(indent);
// Sometimes, an Apply pattern needs parens around it.
// In particular when an Apply's argument is itself an Apply (> 0) arguments
let parens = !loc_arg_patterns.is_empty() && (parens == Parens::InApply);
let indent_more = if is_multiline {
indent + INDENT
} else {
indent
};
if parens {
buf.push('(');
}
let pat = pattern_lift_spaces(buf.text.bump(), &loc_pattern.value);
if !pat.before.is_empty() {
if !is_multiline {
fmt_comments_only(buf, pat.before.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, pat.before.iter(), indent);
}
}
fmt_pattern_inner(&pat.item, buf, Parens::InApply, indent, is_multiline, false);
if !pat.after.is_empty() {
if !is_multiline {
fmt_comments_only(buf, pat.after.iter(), NewlineAt::Bottom, indent_more)
} else {
fmt_spaces(buf, pat.after.iter(), indent_more);
}
}
let mut add_newlines = false;
for loc_arg in loc_arg_patterns.iter() {
buf.spaces(1);
let was_multiline = fmt_pattern_inner(
&loc_arg.value,
buf,
Parens::InApply,
indent_more,
is_multiline,
add_newlines,
);
add_newlines |= was_multiline;
}
if parens {
buf.push(')');
}
pattern_fmt_apply(
buf,
loc_pattern.value,
loc_arg_patterns,
parens,
indent,
is_multiline,
);
}
Pattern::RecordDestructure(loc_patterns) => {
buf.indent(indent);
@ -223,6 +177,7 @@ fn fmt_pattern_only(
if !loc_patterns.is_empty() {
buf.spaces(1);
let mut last_was_multiline = false;
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
let item = pattern_lift_spaces(buf.text.bump(), &loc_pattern.value);
@ -235,6 +190,10 @@ fn fmt_pattern_only(
}
}
if last_was_multiline {
buf.ensure_ends_with_newline();
}
fmt_pattern_inner(
&item.item,
buf,
@ -245,6 +204,7 @@ fn fmt_pattern_only(
);
let is_multiline = item.item.is_multiline();
last_was_multiline = is_multiline;
if it.peek().is_some() {
buf.push_str(",");
@ -297,7 +257,14 @@ fn fmt_pattern_only(
Pattern::NumLiteral(string) => {
buf.indent(indent);
let needs_parens = parens == Parens::InClosurePattern;
if needs_parens {
buf.push('(');
}
buf.push_str(string);
if needs_parens {
buf.push(')');
}
}
Pattern::NonBase10Literal {
base,
@ -305,7 +272,11 @@ fn fmt_pattern_only(
is_negative,
} => {
buf.indent(indent);
if is_negative {
let needs_parens = parens == Parens::InClosurePattern;
if needs_parens {
buf.push('(');
}
if *is_negative {
buf.push('-');
}
@ -317,15 +288,42 @@ fn fmt_pattern_only(
}
buf.push_str(string);
if needs_parens {
buf.push(')');
}
}
Pattern::FloatLiteral(string) => {
buf.indent(indent);
let needs_parens = parens == Parens::InClosurePattern;
if needs_parens {
buf.push('(');
}
buf.push_str(string);
if needs_parens {
buf.push(')');
}
}
Pattern::StrLiteral(literal) => {
let needs_parens = parens == Parens::InClosurePattern;
if needs_parens {
buf.push('(');
}
fmt_str_literal(buf, *literal, indent);
if needs_parens {
buf.push(')');
}
}
Pattern::StrLiteral(literal) => fmt_str_literal(buf, literal, indent),
Pattern::SingleQuote(string) => {
buf.indent(indent);
let needs_parens = parens == Parens::InClosurePattern;
if needs_parens {
buf.push('(');
}
format_sq_literal(buf, string);
if needs_parens {
buf.push(')');
}
}
Pattern::Underscore(name) => {
buf.indent(indent);
@ -336,15 +334,17 @@ fn fmt_pattern_only(
buf.indent(indent);
buf.push_str("(");
let mut add_newlines = false;
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
fmt_pattern_inner(
add_newlines |= fmt_pattern_inner(
&loc_pattern.value,
buf,
Parens::NotNeeded,
indent,
is_multiline,
false,
add_newlines,
);
if it.peek().is_some() {
@ -361,15 +361,17 @@ fn fmt_pattern_only(
buf.indent(indent);
buf.push_str("[");
let mut add_newlines = false;
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
fmt_pattern_inner(
add_newlines |= fmt_pattern_inner(
&loc_pattern.value,
buf,
Parens::NotNeeded,
indent,
is_multiline,
false,
add_newlines,
);
if it.peek().is_some() {
@ -395,14 +397,16 @@ fn fmt_pattern_only(
}
Pattern::As(pattern, pattern_as) => {
let needs_parens = parens == Parens::InAsPattern;
let needs_parens = parens == Parens::InAsPattern
|| parens == Parens::InApply
|| parens == Parens::InClosurePattern;
if needs_parens {
buf.indent(indent);
buf.push('(');
}
fmt_pattern(buf, &pattern.value, indent, parens);
fmt_pattern(buf, &pattern.value, indent, Parens::InAsPattern);
pattern_as.format(buf, indent + INDENT);
@ -433,6 +437,115 @@ fn fmt_pattern_only(
}
}
pub fn pattern_fmt_apply(
buf: &mut Buf<'_>,
func: Pattern<'_>,
args: &[Loc<Pattern<'_>>],
parens: Parens,
indent: u16,
is_multiline: bool,
) {
buf.indent(indent);
// Sometimes, an Apply pattern needs parens around it.
// In particular when an Apply's argument is itself an Apply (> 0) arguments
let parens = !args.is_empty() && parens == Parens::InApply;
let indent_more = if is_multiline {
indent + INDENT
} else {
indent
};
if parens {
buf.push('(');
}
let func = pattern_lift_spaces(buf.text.bump(), &func);
if !func.before.is_empty() {
if !is_multiline {
fmt_comments_only(buf, func.before.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, func.before.iter(), indent);
}
}
fmt_pattern_only(&func.item, buf, Parens::InApply, indent, is_multiline);
let mut last_after = func.after;
let mut add_newlines = is_multiline;
for loc_arg in args.iter() {
buf.spaces(1);
let parens = Parens::InApply;
let arg = pattern_lift_spaces(buf.text.bump(), &loc_arg.value);
let mut was_multiline = arg.item.is_multiline();
let mut before = merge_spaces(buf.text.bump(), last_after, arg.before);
if !before.is_empty() {
if starts_with_block_str(&arg.item) {
// Ick!
// The block string will keep "generating" newlines when formatted (it wants to start on its own line),
// so we strip one out here.
//
// Note that this doesn't affect Expr's because those have explicit parens, and we can control
// whether spaces cross that boundary.
let chop_off = before
.iter()
.rev()
.take_while(|&&s| matches!(s, CommentOrNewline::Newline))
.count();
before = &before[..before.len() - chop_off];
}
handle_multiline_str_spaces(&arg.item, &mut before);
if !is_multiline {
was_multiline |= before.iter().any(|s| s.is_comment());
fmt_comments_only(buf, before.iter(), NewlineAt::Bottom, indent_more)
} else {
was_multiline |= true;
fmt_spaces(buf, before.iter(), indent_more);
}
}
if add_newlines {
buf.ensure_ends_with_newline();
}
if matches!(
arg.item,
Pattern::Identifier {
ident: "implements"
}
) {
buf.indent(indent_more);
buf.push_str("(implements)");
} else {
fmt_pattern_only(&arg.item, buf, parens, indent_more, arg.item.is_multiline());
}
last_after = arg.after;
add_newlines |= was_multiline;
}
if !last_after.is_empty() {
if !is_multiline {
fmt_comments_only(buf, last_after.iter(), NewlineAt::Bottom, indent_more)
} else {
fmt_spaces(buf, last_after.iter(), indent_more);
}
}
if parens {
buf.push(')');
}
}
pub fn starts_with_inline_comment<'a, I: IntoIterator<Item = &'a CommentOrNewline<'a>>>(
spaces: I,
) -> bool {
@ -449,6 +562,7 @@ pub fn pattern_lift_spaces<'a, 'b: 'a>(
match pat {
Pattern::Apply(func, args) => {
let func_lifted = pattern_lift_spaces(arena, &func.value);
let args = arena.alloc_slice_copy(args);
let (before, func, after) = if let Some(last) = args.last_mut() {
let last_lifted = pattern_lift_spaces(arena, &last.value);
@ -504,6 +618,9 @@ pub fn pattern_lift_spaces<'a, 'b: 'a>(
Pattern::SpaceBefore(expr, spaces) => {
let mut inner = pattern_lift_spaces(arena, expr);
inner.before = merge_spaces(arena, spaces, inner.before);
handle_multiline_str_spaces(expr, &mut inner.before);
inner
}
Pattern::SpaceAfter(expr, spaces) => {
@ -519,6 +636,34 @@ pub fn pattern_lift_spaces<'a, 'b: 'a>(
}
}
fn handle_multiline_str_spaces<'a>(pat: &Pattern<'_>, before: &mut &'a [CommentOrNewline<'a>]) {
if starts_with_block_str(pat) {
// Ick!
// The block string will keep "generating" newlines when formatted (it wants to start on its own line),
// so we strip one out here.
//
// Note that this doesn't affect Expr's because those have explicit parens, and we can control
// whether spaces cross that boundary.
let chop_off = before
.iter()
.rev()
.take_while(|&&s| matches!(s, CommentOrNewline::Newline))
.count();
*before = &before[..before.len() - chop_off];
}
}
fn starts_with_block_str(item: &Pattern<'_>) -> bool {
match item {
Pattern::As(inner, _) | Pattern::Apply(inner, _) => starts_with_block_str(&inner.value),
Pattern::SpaceBefore(inner, _) | Pattern::SpaceAfter(inner, _) => {
starts_with_block_str(inner)
}
Pattern::StrLiteral(str_literal) => is_str_multiline(str_literal),
_ => false,
}
}
pub fn pattern_lift_spaces_before<'a, 'b: 'a>(
arena: &'a Bump,
pat: &Pattern<'b>,
@ -542,7 +687,7 @@ pub fn pattern_lift_spaces_after<'a, 'b: 'a>(
}
/// Convert camelCase identifier to snake case
fn snakify_camel_ident(buf: &mut Buf, string: &str) {
pub fn snakify_camel_ident(buf: &mut Buf, string: &str) {
let chars: Vec<char> = string.chars().collect();
if !buf.flags().snakify || (string.contains('_') && !string.ends_with('_')) {
buf.push_str(string);

View file

@ -1388,8 +1388,12 @@ impl<
src2: &Symbol,
layout: &InLayout<'a>,
) {
match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Builtin::Int(quadword_and_smaller!())) => {
let int_width = match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
other => internal_error!("NumAddWrap is not defined for {other:?}"),
};
match int_width {
quadword_and_smaller!() => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -1399,29 +1403,10 @@ impl<
.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::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);
IntWidth::I128 | IntWidth::U128 => {
let intrinsic = bitcode::NUM_ADD_WRAP_INT[int_width].to_string();
self.build_fn_call(dst, intrinsic, &[*src1, *src2], &[*layout, *layout], layout);
}
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:?}"),
}
}
@ -1563,12 +1548,12 @@ impl<
src2: &Symbol,
layout: &InLayout<'a>,
) {
use Builtin::Int;
match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Int(
IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8,
)) => {
let int_width = match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
other => internal_error!("NumMulWrap is not defined for {other:?}"),
};
match int_width {
IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8 => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -1578,9 +1563,7 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
LayoutRepr::Builtin(Int(
IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8,
)) => {
IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8 => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -1597,34 +1580,10 @@ impl<
src2_reg,
);
}
LayoutRepr::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128)) => {
let int_width = match *layout {
Layout::I128 => IntWidth::I128,
Layout::U128 => IntWidth::U128,
_ => unreachable!(),
};
self.build_fn_call(
dst,
bitcode::NUM_MUL_WRAP_INT[int_width].to_string(),
&[*src1, *src2],
&[*layout, *layout],
layout,
);
IntWidth::I128 | IntWidth::U128 => {
let intrinsic = bitcode::NUM_MUL_WRAP_INT[int_width].to_string();
self.build_fn_call(dst, intrinsic, &[*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);
}
x => todo!("NumMulWrap: layout, {:?}", x),
}
}
@ -1874,8 +1833,12 @@ impl<
src2: &Symbol,
layout: &InLayout<'a>,
) {
match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Builtin::Int(quadword_and_smaller!())) => {
let int_width = match self.layout_interner.get_repr(*layout) {
LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width,
other => internal_error!("NumSubWrap is not defined for {other:?}"),
};
match int_width {
quadword_and_smaller!() => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -1885,7 +1848,10 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
x => todo!("NumSubWrap: layout, {:?}", x),
IntWidth::I128 | IntWidth::U128 => {
let intrinsic = bitcode::NUM_SUB_WRAP_INT[int_width].to_string();
self.build_fn_call(dst, intrinsic, &[*src1, *src2], &[*layout, *layout], layout);
}
}
}

View file

@ -44,7 +44,7 @@ pub fn call_bitcode_fn<'ctx>(
if ret.get_type() == vec_type.into() {
return env
.builder
.build_bitcast(ret, env.context.i128_type(), "return_i128")
.build_bit_cast(ret, env.context.i128_type(), "return_i128")
.unwrap();
}
} else if env.target == roc_target::Target::MacArm64 {
@ -265,7 +265,7 @@ fn build_transform_caller_help<'a, 'ctx>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let arg_type = env.context.ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -302,9 +302,7 @@ fn build_transform_caller_help<'a, 'ctx>(
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
let basic_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*layout))
.ptr_type(AddressSpace::default());
let basic_type = env.context.ptr_type(AddressSpace::default());
let cast_ptr = env.builder.new_build_pointer_cast(
argument_ptr.into_pointer_value(),
@ -333,9 +331,7 @@ fn build_transform_caller_help<'a, 'ctx>(
// the function doesn't expect a closure argument, nothing to add
}
(true, layout) => {
let closure_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
.ptr_type(AddressSpace::default());
let closure_type = env.context.ptr_type(AddressSpace::default());
let closure_cast = env.builder.new_build_pointer_cast(
closure_ptr,
@ -442,7 +438,7 @@ fn build_rc_wrapper<'a, 'ctx>(
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let arg_type = env.context.ptr_type(AddressSpace::default());
let function_value = match rc_operation {
Mode::Inc | Mode::Dec => crate::llvm::refcounting::build_header_help(
@ -479,7 +475,7 @@ fn build_rc_wrapper<'a, 'ctx>(
let value_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout));
let value_ptr_type = value_type.ptr_type(AddressSpace::default());
let value_ptr_type = env.context.ptr_type(AddressSpace::default());
let value_ptr = env.builder.new_build_pointer_cast(
generic_value_ptr,
value_ptr_type,
@ -541,7 +537,7 @@ pub fn build_eq_wrapper<'a, 'ctx>(
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let arg_type = env.context.ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -570,9 +566,7 @@ pub fn build_eq_wrapper<'a, 'ctx>(
value_ptr1.set_name(Symbol::ARG_1.as_str(&env.interns));
value_ptr2.set_name(Symbol::ARG_2.as_str(&env.interns));
let value_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
.ptr_type(AddressSpace::default());
let value_type = env.context.ptr_type(AddressSpace::default());
let value_cast1 =
env.builder
@ -639,7 +633,7 @@ pub fn build_compare_wrapper<'a, 'ctx>(
let function_value = match env.module.get_function(fn_name) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let arg_type = env.context.ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -673,9 +667,7 @@ pub fn build_compare_wrapper<'a, 'ctx>(
value_ptr1.set_name(Symbol::ARG_2.as_str(&env.interns));
value_ptr2.set_name(Symbol::ARG_3.as_str(&env.interns));
let value_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout));
let value_ptr_type = value_type.ptr_type(AddressSpace::default());
let value_ptr_type = env.context.ptr_type(AddressSpace::default());
let value_cast1 =
env.builder
@ -718,7 +710,7 @@ pub fn build_compare_wrapper<'a, 'ctx>(
layout_interner,
layout_interner.get_repr(closure_data_repr),
);
let closure_ptr_type = closure_type.ptr_type(AddressSpace::default());
let closure_ptr_type = env.context.ptr_type(AddressSpace::default());
let closure_cast = env.builder.new_build_pointer_cast(
closure_ptr,
@ -781,7 +773,7 @@ pub fn build_copy_wrapper<'a, 'ctx>(
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let arg_type = env.context.ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -811,8 +803,7 @@ pub fn build_copy_wrapper<'a, 'ctx>(
src_ptr.set_name(Symbol::ARG_2.as_str(&env.interns));
let repr = layout_interner.get_repr(layout);
let value_type = basic_type_from_layout(env, layout_interner, repr)
.ptr_type(AddressSpace::default());
let value_type = env.context.ptr_type(AddressSpace::default());
let dst_cast = env
.builder
@ -986,7 +977,7 @@ fn ptr_len_cap<'ctx>(
let ptr = env.builder.new_build_int_to_ptr(
lower_word,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"list_ptr",
);

View file

@ -23,7 +23,7 @@ use inkwell::debug_info::{
};
use inkwell::memory_buffer::MemoryBuffer;
use inkwell::module::{Linkage, Module};
use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::passes::PassManager;
use inkwell::types::{
AnyType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FloatMathType, FunctionType,
IntMathType, IntType, PointerMathType, StructType,
@ -33,8 +33,8 @@ use inkwell::values::{
FunctionValue, InstructionOpcode, InstructionValue, IntMathValue, IntValue, PhiValue,
PointerMathValue, PointerValue, StructValue,
};
use inkwell::FloatPredicate;
use inkwell::{AddressSpace, IntPredicate};
use inkwell::{FloatPredicate, OptimizationLevel};
use morphic_lib::{
CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions, UpdateMode, UpdateModeVar,
};
@ -370,7 +370,7 @@ impl<'ctx> BuilderExt<'ctx> for Builder<'ctx> {
T: BasicType<'ctx>,
V: BasicValue<'ctx>,
{
self.build_bitcast(val, ty, name).unwrap()
self.build_bit_cast(val, ty, name).unwrap()
}
fn new_build_pointer_cast<T: PointerMathValue<'ctx>>(
@ -685,7 +685,7 @@ macro_rules! debug_info_init {
pub enum LlvmBackendMode {
/// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host
Binary,
BinaryDev,
BinaryWithExpect,
/// Creates a test wrapper around the main roc function to catch and report panics.
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
BinaryGlue,
@ -698,7 +698,7 @@ impl LlvmBackendMode {
pub(crate) fn has_host(self) -> bool {
match self {
LlvmBackendMode::Binary => true,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::BinaryWithExpect => true,
LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => true,
@ -710,7 +710,7 @@ impl LlvmBackendMode {
fn returns_roc_result(self) -> bool {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => false,
LlvmBackendMode::BinaryWithExpect => false,
LlvmBackendMode::BinaryGlue => true,
LlvmBackendMode::GenTest => true,
LlvmBackendMode::WasmGenTest => true,
@ -721,7 +721,7 @@ impl LlvmBackendMode {
pub(crate) fn runs_expects(self) -> bool {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => true,
LlvmBackendMode::BinaryWithExpect => true,
LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => false,
@ -1065,6 +1065,7 @@ pub fn module_from_builtins<'ctx>(
"__modti3",
"__muloti4",
"__udivti3",
"__umodti3",
// Roc special functions
"__roc_force_longjmp",
"__roc_force_setjmp",
@ -1083,7 +1084,7 @@ pub fn module_from_builtins<'ctx>(
// Note, running DCE here is faster then waiting until full app DCE.
let mpm = PassManager::create(());
mpm.add_global_dce_pass();
mpm.run_on(&module);
// Now that the unused compiler-rt functions have been removed,
@ -1101,80 +1102,6 @@ pub fn module_from_builtins<'ctx>(
module
}
pub fn construct_optimization_passes<'a>(
module: &'a Module,
opt_level: OptLevel,
) -> (PassManager<Module<'a>>, PassManager<FunctionValue<'a>>) {
let mpm = PassManager::create(());
let fpm = PassManager::create(module);
// remove unused global values (e.g. those defined by zig, but unused in user code)
mpm.add_global_dce_pass();
mpm.add_always_inliner_pass();
// tail-call elimination is always on
fpm.add_instruction_combining_pass();
fpm.add_tail_call_elimination_pass();
let pmb = PassManagerBuilder::create();
match opt_level {
OptLevel::Development | OptLevel::Normal => {
pmb.set_optimization_level(OptimizationLevel::None);
}
OptLevel::Size => {
pmb.set_optimization_level(OptimizationLevel::Default);
// 2 is equivalent to `-Oz`.
pmb.set_size_level(2);
// TODO: For some usecase, like embedded, it is useful to expose this and tune it.
// This really depends on if inlining causes enough simplifications to reduce code size.
pmb.set_inliner_with_threshold(50);
}
OptLevel::Optimize => {
pmb.set_optimization_level(OptimizationLevel::Aggressive);
// this threshold seems to do what we want
pmb.set_inliner_with_threshold(750);
}
}
// Add extra optimization passes for Optimize.
if matches!(opt_level, OptLevel::Optimize) {
// TODO: figure out which of these actually help.
// Note, llvm probably already runs all of these as part of Aggressive.
// function passes
fpm.add_cfg_simplification_pass();
mpm.add_cfg_simplification_pass();
fpm.add_jump_threading_pass();
mpm.add_jump_threading_pass();
fpm.add_memcpy_optimize_pass(); // this one is very important
fpm.add_licm_pass();
// turn invoke into call
// TODO: is this pass needed. It theoretically prunes unused exception handling info.
// This seems unrelated to the comment above. It also seems to be missing in llvm-16.
// mpm.add_prune_eh_pass();
// remove unused global values (often the `_wrapper` can be removed)
mpm.add_global_dce_pass();
mpm.add_function_inlining_pass();
}
pmb.populate_module_pass_manager(&mpm);
pmb.populate_function_pass_manager(&fpm);
fpm.initialize();
// For now, we have just one of each
(mpm, fpm)
}
fn promote_to_main_function<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
@ -1247,8 +1174,8 @@ fn promote_to_wasm_test_wrapper<'a, 'ctx>(
let roc_main_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol);
let output_type = match roc_main_fn.get_type().get_return_type() {
Some(return_type) => {
let output_type = return_type.ptr_type(AddressSpace::default());
Some(..) => {
let output_type = env.context.ptr_type(AddressSpace::default());
output_type.into()
}
None => {
@ -1441,7 +1368,7 @@ fn small_str_ptr_width_8<'ctx>(env: &Env<'_, 'ctx, '_>, str_literal: &str) -> Po
let cap = env.ptr_int().const_int(word3, false);
let address_space = AddressSpace::default();
let ptr_type = env.context.i8_type().ptr_type(address_space);
let ptr_type = env.context.ptr_type(address_space);
let ptr = env.builder.new_build_int_to_ptr(ptr, ptr_type, "to_u8_ptr");
const_str_alloca_ptr(env, ptr, len, cap)
@ -1465,7 +1392,7 @@ fn small_str_ptr_width_4<'ctx>(env: &Env<'_, 'ctx, '_>, str_literal: &str) -> St
let cap = env.ptr_int().const_int(word3 as u64, false);
let address_space = AddressSpace::default();
let ptr_type = env.context.i8_type().ptr_type(address_space);
let ptr_type = env.context.ptr_type(address_space);
let ptr = env.builder.new_build_int_to_ptr(ptr, ptr_type, "to_u8_ptr");
struct_from_fields(
@ -1604,7 +1531,7 @@ fn struct_pointer_from_fields<'a, 'ctx, 'env, I>(
.builder
.new_build_bitcast(
input_pointer,
struct_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"struct_ptr",
)
.into_pointer_value();
@ -1941,7 +1868,7 @@ pub(crate) fn build_exp_expr<'a, 'ctx>(
let data_ptr = env.builder.new_build_pointer_cast(
opaque_data_ptr,
struct_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_data_pointer",
);
@ -2295,7 +2222,7 @@ fn build_tag_field_value<'a, 'ctx>(
env.builder
.new_build_pointer_cast(
value.into_pointer_value(),
env.context.i64_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_recursive_pointer",
)
.into()
@ -2468,7 +2395,7 @@ fn build_tag<'a, 'ctx>(
RocUnion::untagged_from_slices(layout_interner, env.context, &[other_fields]);
if tag_id == *nullable_id as u16 {
let output_type = roc_union.struct_type().ptr_type(AddressSpace::default());
let output_type = env.context.ptr_type(AddressSpace::default());
return output_type.const_null().into();
}
@ -2518,7 +2445,7 @@ fn tag_pointer_set_tag_id<'ctx>(
let cast_pointer = env.builder.new_build_pointer_cast(
pointer,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_to_i8_ptr",
);
@ -2577,7 +2504,7 @@ pub fn tag_pointer_clear_tag_id<'ctx>(
let cast_pointer = env.builder.new_build_pointer_cast(
pointer,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_to_i8_ptr",
);
@ -2791,7 +2718,7 @@ fn union_field_ptr_at_index_help<'a, 'ctx>(
let data_ptr = env.builder.new_build_pointer_cast(
value,
struct_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_lookup_at_index_ptr",
);
@ -2842,17 +2769,11 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx>(
RocUnion::untagged_from_slices(layout_interner, env.context, fields)
};
reserve_union_with_refcount_help(
env,
roc_union.struct_type(),
roc_union.tag_width(),
roc_union.tag_alignment(),
)
reserve_union_with_refcount_help(env, roc_union.tag_width(), roc_union.tag_alignment())
}
fn reserve_union_with_refcount_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
basic_type: impl BasicType<'ctx>,
fn reserve_union_with_refcount_help<'ctx>(
env: &Env<'_, 'ctx, '_>,
stack_size: u32,
alignment_bytes: u32,
) -> PointerValue<'ctx> {
@ -2862,18 +2783,11 @@ fn reserve_union_with_refcount_help<'a, 'ctx, 'env>(
// elem_refcounted does not apply to unions, only lists.
let elem_refcounted = env.context.bool_type().const_zero().into();
allocate_with_refcount_help(
env,
basic_type,
alignment_bytes,
value_bytes_intvalue,
elem_refcounted,
)
allocate_with_refcount_help(env, alignment_bytes, value_bytes_intvalue, elem_refcounted)
}
pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value_type: impl BasicType<'ctx>,
pub fn allocate_with_refcount_help<'ctx>(
env: &Env<'_, 'ctx, '_>,
alignment_bytes: u32,
number_of_data_bytes: IntValue<'ctx>,
elem_refcounted: BasicValueEnum<'ctx>,
@ -2889,7 +2803,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
)
.into_pointer_value();
let ptr_type = value_type.ptr_type(AddressSpace::default());
let ptr_type = env.context.ptr_type(AddressSpace::default());
env.builder
.new_build_pointer_cast(ptr, ptr_type, "alloc_cast_to_desired")
@ -3094,9 +3008,7 @@ pub fn store_roc_value_opaque<'a, 'ctx>(
opaque_destination: PointerValue<'ctx>,
value: BasicValueEnum<'ctx>,
) {
let target_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
.ptr_type(AddressSpace::default());
let target_type = env.context.ptr_type(AddressSpace::default());
let destination = env.builder.new_build_pointer_cast(
opaque_destination,
target_type,
@ -3559,12 +3471,10 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
variable: _,
remainder,
} => {
if env.mode.runs_expects() {
let location = build_string_literal(env, source_location);
let source = build_string_literal(env, source);
let message = scope.load_symbol(symbol);
env.call_dbg(env, location, source, message);
}
let location = build_string_literal(env, source_location);
let source = build_string_literal(env, source);
let message = scope.load_symbol(symbol);
env.call_dbg(env, location, source, message);
build_exp_stmt(
env,
@ -3620,7 +3530,7 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
variables,
);
if let LlvmBackendMode::BinaryDev = env.mode {
if let LlvmBackendMode::BinaryWithExpect = env.mode {
crate::llvm::expect::notify_parent_expect(env, &shared_memory);
}
@ -3912,7 +3822,7 @@ fn complex_bitcast_from_bigger_than_to<'ctx>(
// then read it back as a different type
let to_type_pointer = builder.new_build_pointer_cast(
argument_pointer,
to_type.ptr_type(inkwell::AddressSpace::default()),
env.context.ptr_type(inkwell::AddressSpace::default()),
name,
);
@ -3934,9 +3844,7 @@ fn complex_bitcast_to_bigger_than_from<'ctx>(
// then cast the pointer to our desired type
let from_type_pointer = builder.new_build_pointer_cast(
storage,
from_value
.get_type()
.ptr_type(inkwell::AddressSpace::default()),
env.context.ptr_type(inkwell::AddressSpace::default()),
name,
);
@ -4303,8 +4211,8 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
let output_type = roc_function.get_type().get_param_types().pop().unwrap();
argument_types.insert(0, output_type);
}
Some(return_type) => {
let output_type = return_type.ptr_type(AddressSpace::default());
Some(..) => {
let output_type = env.context.ptr_type(AddressSpace::default());
argument_types.insert(0, output_type.into());
}
}
@ -4356,7 +4264,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
// bitcast the ptr
let fastcc_ptr = env.builder.new_build_pointer_cast(
arg.into_pointer_value(),
fastcc_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"bitcast_arg",
);
@ -4497,7 +4405,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx>(
let return_type = wrapper_return_type;
let c_function_spec = {
let output_type = return_type.ptr_type(AddressSpace::default());
let output_type = env.context.ptr_type(AddressSpace::default());
argument_types.push(output_type.into());
FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types)
};
@ -4673,10 +4581,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx>(
let c_abi_roc_str_type = env.context.struct_type(
&[
env.context
.i8_type()
.ptr_type(AddressSpace::default())
.into(),
env.context.ptr_type(AddressSpace::default()).into(),
env.ptr_int().into(),
env.ptr_int().into(),
],
@ -4817,7 +4722,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx>(
// bitcast the ptr
let fastcc_ptr = env.builder.new_build_pointer_cast(
arg.into_pointer_value(),
fastcc_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"bitcast_arg",
);
@ -4903,7 +4808,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx>(
)
}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {}
LlvmBackendMode::Binary
| LlvmBackendMode::BinaryWithExpect
| LlvmBackendMode::BinaryGlue => {}
}
// a generic version that writes the result into a passed *u8 pointer
@ -4956,13 +4863,13 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx>(
roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {
basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(return_layout),
)
}
LlvmBackendMode::Binary
| LlvmBackendMode::BinaryWithExpect
| LlvmBackendMode::BinaryGlue => basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(return_layout),
),
};
let size: BasicValueEnum = return_type.size_of().unwrap().into();
@ -5007,7 +4914,7 @@ pub fn get_sjlj_buffer<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> {
env.builder.new_build_pointer_cast(
global.as_pointer_value(),
env.context.i32_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_sjlj_buffer",
)
}
@ -5024,15 +4931,11 @@ pub fn build_setjmp_call<'ctx>(env: &Env<'_, 'ctx, '_>) -> BasicValueEnum<'ctx>
// Anywhere else, use the LLVM intrinsic.
// https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp
let buf_type = env
.context
.i8_type()
.ptr_type(AddressSpace::default())
.array_type(5);
let buf_type = env.context.ptr_type(AddressSpace::default()).array_type(5);
let jmp_buf_i8p_arr = env.builder.new_build_pointer_cast(
jmp_buf,
buf_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"jmp_buf [5 x i8*]",
);
@ -5073,7 +4976,7 @@ pub fn build_setjmp_call<'ctx>(env: &Env<'_, 'ctx, '_>) -> BasicValueEnum<'ctx>
.builder
.new_build_pointer_cast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"jmp_buf i8*",
)
.into();
@ -5245,7 +5148,7 @@ fn roc_call_result_type<'ctx>(
env.context.struct_type(
&[
env.context.i64_type().into(),
zig_str_type(env).ptr_type(AddressSpace::default()).into(),
env.context.ptr_type(AddressSpace::default()).into(),
return_type,
],
false,
@ -5698,8 +5601,6 @@ fn build_procedures_help<'a>(
&mut layout_ids,
);
let (_, function_pass) = construct_optimization_passes(env.module, opt_level);
for (proc, fn_vals) in headers {
for (func_spec_solutions, fn_val) in fn_vals {
let mut current_scope = scope.clone();
@ -5722,9 +5623,7 @@ fn build_procedures_help<'a>(
// call finalize() before any code generation/verification
env.dibuilder.finalize();
if fn_val.verify(true) {
function_pass.run_on(&fn_val);
} else {
if !fn_val.verify(true) {
let mode = "NON-OPTIMIZED";
eprintln!(
@ -5759,7 +5658,7 @@ fn build_procedures_help<'a>(
use LlvmBackendMode::*;
match env.mode {
GenTest | WasmGenTest | CliTest => { /* no host, or exposing types is not supported */ }
Binary | BinaryDev | BinaryGlue => {
Binary | BinaryWithExpect | BinaryGlue => {
for (proc_name, alias_name, hels) in host_exposed_lambda_sets.iter() {
let ident_string = proc_name.name().as_unsuffixed_str(&env.interns);
let fn_name: String = format!("{}_{}", ident_string, hels.id.0);
@ -5977,23 +5876,11 @@ fn build_closure_caller<'a, 'ctx>(
) {
let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena);
for layout in arguments {
let arg_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*layout));
let arg_ptr_type = arg_type.ptr_type(AddressSpace::default());
argument_types.push(arg_ptr_type.into());
for _ in arguments {
argument_types.push(env.context.ptr_type(AddressSpace::default()).into());
}
let closure_argument_type = {
let basic_type = basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(lambda_set.runtime_representation()),
);
basic_type.ptr_type(AddressSpace::default())
};
let closure_argument_type = env.context.ptr_type(AddressSpace::default());
argument_types.push(closure_argument_type.into());
let context = &env.context;
@ -6002,7 +5889,7 @@ fn build_closure_caller<'a, 'ctx>(
let result_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(result));
let output_type = { result_type.ptr_type(AddressSpace::default()) };
let output_type = { env.context.ptr_type(AddressSpace::default()) };
argument_types.push(output_type.into());
// STEP 1: build function header
@ -6251,7 +6138,7 @@ fn roc_call_erased_with_args<'a, 'ctx>(
) -> BasicValueEnum<'ctx> {
let function_type =
fn_ptr::function_type(env, layout_interner, argument_layouts, result_layout);
let function_ptr_type = function_type.ptr_type(AddressSpace::default());
let function_ptr_type = env.context.ptr_type(AddressSpace::default());
let function_pointer = fn_ptr::cast_to_function_ptr_type(env, pointer, function_ptr_type);
@ -6473,7 +6360,7 @@ fn to_cc_type<'a, 'ctx>(
let stack_type = basic_type_from_layout(env, layout_interner, layout_repr);
if layout_repr.is_passed_by_reference(layout_interner) {
stack_type.ptr_type(AddressSpace::default()).into()
env.context.ptr_type(AddressSpace::default()).into()
} else {
stack_type
}
@ -6493,18 +6380,7 @@ fn to_cc_type_builtin<'a, 'ctx>(
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => {
basic_type_from_builtin(env, builtin)
}
Builtin::Str | Builtin::List(_) => {
let address_space = AddressSpace::default();
let field_types: [BasicTypeEnum; 3] = [
env.context.i8_type().ptr_type(address_space).into(),
env.ptr_int().into(),
env.ptr_int().into(),
];
let struct_type = env.context.struct_type(&field_types, false);
struct_type.ptr_type(address_space).into()
}
Builtin::Str | Builtin::List(_) => env.context.ptr_type(AddressSpace::default()).into(),
}
}
@ -6584,7 +6460,7 @@ impl<'ctx> FunctionSpec<'ctx> {
let (typ, opt_sret_parameter) = match cc_return {
CCReturn::ByPointer => {
// turn the output type into a pointer type. Make it the first argument to the function
let output_type = return_type.unwrap().ptr_type(AddressSpace::default());
let output_type = env.context.ptr_type(AddressSpace::default());
let mut arguments: Vec<'_, BasicTypeEnum> =
bumpalo::vec![in env.arena; output_type.into()];
@ -6628,7 +6504,7 @@ impl<'ctx> FunctionSpec<'ctx> {
return_type.fn_type(&function_arguments(env, &argument_types), false)
}
RocReturn::ByPointer => {
argument_types.push(return_type.ptr_type(AddressSpace::default()).into());
argument_types.push(env.context.ptr_type(AddressSpace::default()).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
@ -6888,7 +6764,7 @@ fn define_global_str_literal_ptr<'ctx>(
let ptr = env.builder.new_build_pointer_cast(
global.as_pointer_value(),
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_opaque",
);

View file

@ -2,7 +2,7 @@ use crate::llvm::bitcode::build_dec_wrapper;
use crate::llvm::build::{allocate_with_refcount_help, cast_basic_basic, Env, RocFunctionCall};
use crate::llvm::convert::basic_type_from_layout;
use inkwell::builder::Builder;
use inkwell::types::{BasicType, PointerType};
use inkwell::types::PointerType;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use morphic_lib::UpdateMode;
@ -73,7 +73,7 @@ fn pass_element_as_opaque<'a, 'ctx>(
env.builder
.new_build_pointer_cast(
element_ptr,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"pass_element_as_opaque",
)
.into()
@ -110,7 +110,7 @@ pub(crate) fn pass_as_opaque<'ctx>(
env.builder
.new_build_pointer_cast(
ptr,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"pass_as_opaque",
)
.into()
@ -155,7 +155,7 @@ pub(crate) fn list_get_unsafe<'a, 'ctx>(
);
// listGetUnsafe takes a U64, but we need to convert that to usize for index calculation.
let elem_index = builder.new_build_int_cast(elem_index, env.ptr_int(), "u64_to_usize");
let ptr_type = elem_type.ptr_type(AddressSpace::default());
let ptr_type = env.context.ptr_type(AddressSpace::default());
// Load the pointer to the array data
let array_data_ptr = load_list_ptr(env, wrapper_struct, ptr_type);
@ -728,18 +728,9 @@ pub(crate) fn allocate_list<'a, 'ctx>(
let bytes_per_element = len_type.const_int(elem_bytes, false);
let number_of_data_bytes =
builder.new_build_int_mul(bytes_per_element, number_of_elements, "data_length");
let basic_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(elem_layout));
let alignment_bytes = layout_interner.alignment_bytes(elem_layout);
let elem_refcounted = layout_refcounted(env, layout_interner, elem_layout);
allocate_with_refcount_help(
env,
basic_type,
alignment_bytes,
number_of_data_bytes,
elem_refcounted,
)
allocate_with_refcount_help(env, alignment_bytes, number_of_data_bytes, elem_refcounted)
}
pub(crate) fn store_list<'ctx>(

View file

@ -3,7 +3,6 @@ use crate::llvm::build_list::{list_len_usize, load_list_ptr};
use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout;
use bumpalo::collections::Vec;
use inkwell::types::BasicType;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use roc_builtins::bitcode;
@ -528,7 +527,7 @@ fn build_list_eq_help<'a, 'ctx>(
let builder = env.builder;
let element_type = basic_type_from_layout(env, layout_interner, element_layout);
let ptr_type = element_type.ptr_type(AddressSpace::default());
let ptr_type = env.context.ptr_type(AddressSpace::default());
let ptr1 = load_list_ptr(env, list1, ptr_type);
let ptr2 = load_list_ptr(env, list2, ptr_type);
@ -1286,13 +1285,13 @@ fn eq_ptr_to_struct<'a, 'ctx>(
// cast the opaque pointer to a pointer of the correct shape
let struct1_ptr = env.builder.new_build_pointer_cast(
tag1,
wrapper_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"opaque_to_correct",
);
let struct2_ptr = env.builder.new_build_pointer_cast(
tag2,
wrapper_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"opaque_to_correct",
);

View file

@ -1,7 +1,7 @@
use crate::llvm::build::{BuilderExt, Env, FunctionSpec, RocReturn};
use crate::llvm::build::{BuilderExt, Env};
use crate::llvm::erased;
use crate::llvm::memcpy::build_memcpy;
use bumpalo::collections::{CollectIn, Vec as AVec};
use bumpalo::collections::Vec as AVec;
use inkwell::context::Context;
use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
use inkwell::values::PointerValue;
@ -32,37 +32,18 @@ pub fn basic_type_from_layout<'a, 'ctx>(
layout_interner.get_repr(lambda_set.runtime_representation()),
),
Ptr(inner_layout) => {
let inner_type = basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(inner_layout),
);
inner_type.ptr_type(AddressSpace::default()).into()
}
Ptr(..) => env.context.ptr_type(AddressSpace::default()).into(),
Union(union_layout) => basic_type_from_union_layout(env, layout_interner, &union_layout),
RecursivePointer(_) => env
.context
.i64_type()
.ptr_type(AddressSpace::default())
.as_basic_type_enum(),
FunctionPointer(self::FunctionPointer { args, ret }) => {
let args = args.iter().map(|arg| {
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*arg))
});
let ret_repr = layout_interner.get_repr(ret);
let ret = basic_type_from_layout(env, layout_interner, ret_repr);
let roc_return = RocReturn::from_layout(layout_interner, ret_repr);
let fn_spec = FunctionSpec::fastcc(env, roc_return, ret, args.collect_in(env.arena));
fn_spec.typ.ptr_type(AddressSpace::default()).into()
FunctionPointer(self::FunctionPointer { .. }) => {
env.context.ptr_type(AddressSpace::default()).into()
}
Erased(_) => erased::basic_type(env).into(),
Builtin(builtin) => basic_type_from_builtin(env, &builtin),
@ -136,7 +117,7 @@ fn basic_type_from_union_layout<'a, 'ctx>(
Recursive(_)
| NonNullableUnwrapped(_)
| NullableWrapped { .. }
| NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::default()).into(),
| NullableUnwrapped { .. } => env.context.ptr_type(AddressSpace::default()).into(),
}
}
@ -188,7 +169,7 @@ pub fn argument_type_from_layout<'a, 'ctx>(
let base = basic_type_from_layout(env, layout_interner, layout);
if layout.is_passed_by_reference(layout_interner) {
base.ptr_type(AddressSpace::default()).into()
env.context.ptr_type(AddressSpace::default()).into()
} else {
base
}
@ -208,7 +189,7 @@ fn argument_type_from_struct_layout<'a, 'ctx>(
let stack_type = basic_type_from_layout(env, layout_interner, struct_layout);
if struct_layout.is_passed_by_reference(layout_interner) {
stack_type.ptr_type(AddressSpace::default()).into()
env.context.ptr_type(AddressSpace::default()).into()
} else {
stack_type
}
@ -223,7 +204,7 @@ pub fn argument_type_from_union_layout<'a, 'ctx>(
let heap_type = basic_type_from_union_layout(env, layout_interner, union_layout);
if let UnionLayout::NonRecursive(_) = union_layout {
heap_type.ptr_type(AddressSpace::default()).into()
env.context.ptr_type(AddressSpace::default()).into()
} else {
heap_type
}
@ -415,7 +396,7 @@ impl<'ctx> RocUnion<'ctx> {
RocStruct::ByValue(value) => {
let cast_pointer = env.builder.new_build_pointer_cast(
data_buffer,
value.get_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_data_ptr",
);
env.builder.new_build_store(cast_pointer, value);
@ -482,7 +463,7 @@ pub fn zig_dec_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> StructType<'ctx> {
}
pub fn zig_has_tag_id_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> StructType<'ctx> {
let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::default());
let u8_ptr_t = env.context.ptr_type(AddressSpace::default());
env.context
.struct_type(&[env.context.bool_type().into(), u8_ptr_t.into()], false)

View file

@ -8,16 +8,11 @@ use roc_mono::ir::ErasedField;
use super::build::{BuilderExt, Env};
pub fn opaque_ptr_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerType<'ctx> {
env.context.i8_type().ptr_type(AddressSpace::default())
env.context.ptr_type(AddressSpace::default())
}
fn refcounter_type<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerType<'ctx> {
let return_void = env.context.void_type();
let arg_ty = opaque_ptr_type(env);
return_void
.fn_type(&[arg_ty.into()], false)
.ptr_type(AddressSpace::default())
env.context.ptr_type(AddressSpace::default())
}
/// Erased is laid out like
@ -52,7 +47,7 @@ fn bitcast_to_opaque_ptr<'ctx>(
env.builder
.new_build_bitcast(
value,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_opaque_ptr",
)
.into_pointer_value()

View file

@ -29,7 +29,7 @@ pub(crate) struct SharedMemoryPointer<'ctx>(PointerValue<'ctx>);
impl<'ctx> SharedMemoryPointer<'ctx> {
pub(crate) fn get<'a, 'env>(env: &Env<'a, 'ctx, 'env>) -> Self {
let start_function = if let LlvmBackendMode::BinaryDev = env.mode {
let start_function = if let LlvmBackendMode::BinaryWithExpect = env.mode {
bitcode::UTILS_EXPECT_FAILED_START_SHARED_FILE
} else {
bitcode::UTILS_EXPECT_FAILED_START_SHARED_BUFFER
@ -100,7 +100,7 @@ fn read_state<'ctx>(
env: &Env<'_, 'ctx, '_>,
ptr: PointerValue<'ctx>,
) -> (IntValue<'ctx>, IntValue<'ctx>) {
let ptr_type = env.ptr_int().ptr_type(AddressSpace::default());
let ptr_type = env.context.ptr_type(AddressSpace::default());
let ptr = env.builder.new_build_pointer_cast(ptr, ptr_type, "");
let one = env.ptr_int().const_int(1, false);
@ -120,7 +120,7 @@ fn write_state<'ctx>(
count: IntValue<'ctx>,
offset: IntValue<'ctx>,
) {
let ptr_type = env.ptr_int().ptr_type(AddressSpace::default());
let ptr_type = env.context.ptr_type(AddressSpace::default());
let ptr = env.builder.new_build_pointer_cast(ptr, ptr_type, "");
let one = env.ptr_int().const_int(1, false);
@ -249,7 +249,7 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx>(
)
};
let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::default());
let u32_ptr = env.context.ptr_type(AddressSpace::default());
let ptr = env
.builder
.new_build_pointer_cast(ptr, u32_ptr, "cast_ptr_type");
@ -321,7 +321,7 @@ fn build_clone<'a, 'ctx>(
)
};
let ptr_type = value.get_type().ptr_type(AddressSpace::default());
let ptr_type = env.context.ptr_type(AddressSpace::default());
let ptr = env
.builder
.new_build_pointer_cast(ptr, ptr_type, "cast_ptr_type");
@ -446,10 +446,7 @@ fn build_clone_tag<'a, 'ctx>(
let function_type = env.ptr_int().fn_type(
&[
env.context
.i8_type()
.ptr_type(AddressSpace::default())
.into(),
env.context.ptr_type(AddressSpace::default()).into(),
env.ptr_int().into(),
env.ptr_int().into(),
BasicMetadataTypeEnum::from(value.get_type()),
@ -523,7 +520,7 @@ fn load_tag_data<'a, 'ctx>(
let data_ptr = env.builder.new_build_pointer_cast(
raw_data_ptr,
tag_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"data_ptr",
);
@ -545,7 +542,7 @@ fn clone_tag_payload_and_id<'a, 'ctx>(
let payload_ptr = env.builder.new_build_pointer_cast(
opaque_payload_ptr,
payload_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_payload_ptr",
);
@ -948,7 +945,7 @@ fn build_copy<'ctx>(
)
};
let ptr_type = value.get_type().ptr_type(AddressSpace::default());
let ptr_type = env.context.ptr_type(AddressSpace::default());
let ptr = env
.builder
.new_build_pointer_cast(ptr, ptr_type, "cast_ptr_type");
@ -1016,18 +1013,16 @@ fn build_clone_builtin<'a, 'ctx>(
let dest = pointer_at_offset(bd, env.context.i8_type(), ptr, elements_start_offset);
let src = bd.new_build_pointer_cast(
elements,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_bytes_pointer",
);
bd.build_memcpy(dest, 1, src, 1, elements_width).unwrap();
bd.new_build_int_add(elements_start_offset, elements_width, "new_offset")
} else {
let element_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(elem));
let elements = bd.new_build_pointer_cast(
elements,
element_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"elements",
);

View file

@ -19,7 +19,7 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
let builder = env.builder;
let usize_type = env.ptr_int();
let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::default());
let i8_ptr_type = env.context.ptr_type(AddressSpace::default());
match env.mode {
super::build::LlvmBackendMode::CliTest => {
@ -331,7 +331,7 @@ pub fn build_longjmp_call(env: &Env) {
// Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)`
let jmp_buf_i8p = env.builder.new_build_pointer_cast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"jmp_buf i8*",
);
let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p.into()]);

View file

@ -75,8 +75,7 @@ pub(crate) fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
//
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
let i1_type = ctx.bool_type();
let i8_type = ctx.i8_type();
let i8_ptr_type = i8_type.ptr_type(AddressSpace::default());
let i8_ptr_type = ctx.ptr_type(AddressSpace::default());
let i32_type = ctx.i32_type();
let void_type = ctx.void_type();
@ -139,7 +138,7 @@ pub static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
pub static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
pub static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0";
pub static LLVM_STACK_SAVE: &str = "llvm.stacksave";
pub static LLVM_STACK_SAVE: &str = "llvm.stacksave.p0";
pub static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp";
pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp";

View file

@ -1,7 +1,7 @@
use inkwell::{
attributes::{Attribute, AttributeLoc},
module::Linkage,
types::{BasicType, IntType},
types::IntType,
values::{
BasicValue, BasicValueEnum, FloatValue, FunctionValue, InstructionOpcode, IntValue,
StructValue,
@ -237,12 +237,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
intrinsic,
);
let roc_return_type = basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(layout),
)
.ptr_type(AddressSpace::default());
let roc_return_type = env.context.ptr_type(AddressSpace::default());
let roc_return_alloca = env.builder.new_build_pointer_cast(
zig_return_alloca,
@ -331,12 +326,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
intrinsic,
);
let roc_return_type = basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(layout),
)
.ptr_type(AddressSpace::default());
let roc_return_type = env.context.ptr_type(AddressSpace::default());
let roc_return_alloca = env.builder.new_build_pointer_cast(
zig_return_alloca,
@ -1385,7 +1375,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
let ptr = env.builder.new_build_pointer_cast(
data_ptr.into_pointer_value(),
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_to_i8_ptr",
);
@ -2000,7 +1990,7 @@ fn dec_alloca<'ctx>(env: &Env<'_, 'ctx, '_>, value: IntValue<'ctx>) -> BasicValu
let ptr = env.builder.new_build_pointer_cast(
alloca,
value.get_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_to_i128_ptr",
);
@ -2021,7 +2011,7 @@ fn dec_alloca<'ctx>(env: &Env<'_, 'ctx, '_>, value: IntValue<'ctx>) -> BasicValu
instruction.set_alignment(16).unwrap();
let ptr = env.builder.new_build_pointer_cast(
alloca,
value.get_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_to_i128_ptr",
);
env.builder.new_build_store(ptr, value);
@ -2510,12 +2500,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
intrinsic,
);
let roc_return_type = basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(return_layout),
)
.ptr_type(AddressSpace::default());
let roc_return_type = env.context.ptr_type(AddressSpace::default());
let roc_return_alloca = env.builder.new_build_pointer_cast(
zig_return_alloca,

View file

@ -11,7 +11,7 @@ use crate::llvm::struct_::RocStruct;
use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, InstructionValue, IntValue, PointerValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_builtins::bitcode;
@ -37,12 +37,9 @@ impl<'ctx> PointerToRefcount<'ctx> {
/// not the data, and only is the start of the allocated buffer if the
/// alignment works out that way.
pub unsafe fn from_ptr<'a, 'env>(env: &Env<'a, 'ctx, 'env>, ptr: PointerValue<'ctx>) -> Self {
// must make sure it's a pointer to usize
let refcount_type = env.ptr_int();
let value = env.builder.new_build_pointer_cast(
ptr,
refcount_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_refcount_ptr",
);
@ -56,7 +53,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let builder = env.builder;
// pointer to usize
let refcount_type = env.ptr_int();
let refcount_ptr_type = refcount_type.ptr_type(AddressSpace::default());
let refcount_ptr_type = env.context.ptr_type(AddressSpace::default());
let ptr_as_usize_ptr =
builder.new_build_pointer_cast(data_ptr, refcount_ptr_type, "as_usize_ptr");
@ -145,7 +142,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
None => {
// inc and dec return void
let fn_type = context.void_type().fn_type(
&[env.ptr_int().ptr_type(AddressSpace::default()).into()],
&[env.context.ptr_type(AddressSpace::default()).into()],
false,
);
@ -231,7 +228,7 @@ fn incref_pointer<'ctx>(
env.builder
.new_build_pointer_cast(
pointer,
env.ptr_int().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_isize_ptr",
)
.into(),
@ -255,7 +252,7 @@ fn free_pointer<'ctx>(
env.builder
.new_build_pointer_cast(
pointer,
env.ptr_int().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_isize_ptr",
)
.into(),
@ -280,7 +277,7 @@ fn decref_pointer<'ctx>(
env.builder
.new_build_pointer_cast(
pointer,
env.ptr_int().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_isize_ptr",
)
.into(),
@ -306,7 +303,7 @@ pub fn decref_pointer_check_null<'ctx>(
env.builder
.new_build_pointer_cast(
pointer,
env.context.i8_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"to_i8_ptr",
)
.into(),
@ -1320,7 +1317,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx>(
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = env.builder.new_build_pointer_cast(
value_ptr,
wrapper_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"opaque_to_correct_recursive_decrement",
);
@ -1340,7 +1337,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx>(
);
let ptr_as_i64_ptr = env.builder.new_build_load(
env.context.i64_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
elem_pointer,
"load_recursive_pointer",
);
@ -1792,7 +1789,7 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx>(
let cast_tag_data_pointer = env.builder.new_build_pointer_cast(
opaque_tag_data_ptr,
data_struct_type.ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
"cast_to_concrete_tag",
);
@ -1810,7 +1807,7 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx>(
// This is the actual pointer to the recursive data.
let field_value = env.builder.new_build_load(
env.context.i64_type().ptr_type(AddressSpace::default()),
env.context.ptr_type(AddressSpace::default()),
field_ptr,
"load_recursive_pointer",
);

View file

@ -940,8 +940,7 @@ impl<'a> LowLevelCall<'a> {
NumAddWrap => match self.ret_layout_raw {
LayoutRepr::Builtin(Builtin::Int(width)) => match width {
IntWidth::I128 | IntWidth::U128 => {
// TODO: don't panic
self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_OR_PANIC_INT[width])
self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_WRAP_INT[width])
}
IntWidth::I64 | IntWidth::U64 => {
self.load_args(backend);
@ -1031,8 +1030,7 @@ impl<'a> LowLevelCall<'a> {
NumSubWrap => match self.ret_layout_raw {
LayoutRepr::Builtin(Builtin::Int(width)) => match width {
IntWidth::I128 | IntWidth::U128 => {
// TODO: don't panic
self.load_args_and_call_zig(backend, &bitcode::NUM_SUB_OR_PANIC_INT[width])
self.load_args_and_call_zig(backend, &bitcode::NUM_SUB_WRAP_INT[width])
}
IntWidth::I64 | IntWidth::U64 => {
self.load_args(backend);

View file

@ -88,7 +88,7 @@ fn write_types_for_module_real(module_id: ModuleId, filename: &str, output_path:
let mut module = match res_module {
Ok(v) => v,
Err(LoadingProblem::FormattedReport(report)) => {
Err(LoadingProblem::FormattedReport(report, _)) => {
internal_error!("{}", report);
}
Err(other) => {

View file

@ -187,7 +187,7 @@ mod test_reporting {
let mut buf = String::new();
match infer_expr_help_new(subdir, arena, src) {
Err(LoadingProblem::FormattedReport(fail)) => fail,
Err(LoadingProblem::FormattedReport(fail, _)) => fail,
Ok((module_src, type_problems, can_problems, home, interns)) => {
let lines = LineInfo::new(&module_src);
let src_lines: Vec<&str> = module_src.split('\n').collect();

View file

@ -998,13 +998,52 @@ pub enum LoadingProblem<'a> {
TriedToImportAppModule,
/// a formatted report
FormattedReport(String),
FormattedReport(String, Option<Region>),
ImportCycle(PathBuf, Vec<ModuleId>),
IncorrectModuleName(FileError<'a, IncorrectModuleName<'a>>),
CouldNotFindCacheDir,
ChannelProblem(ChannelProblem),
}
impl<'a> LoadingProblem<'a> {
pub fn get_region(&self) -> Option<Region> {
match self {
LoadingProblem::ParsingFailed(err) => err.problem.problem.get_region(),
LoadingProblem::MultiplePlatformPackages {
filename: _,
module_id: _,
source: _,
region,
} => Some(*region),
LoadingProblem::NoPlatformPackage {
filename: _,
module_id: _,
source: _,
region,
} => Some(*region),
LoadingProblem::UnrecognizedPackageShorthand {
filename: _,
module_id: _,
source: _,
region,
shorthand: _,
available: _,
} => Some(*region),
LoadingProblem::FileProblem {
filename: _,
error: _,
} => None,
LoadingProblem::UnexpectedHeader(_) => None,
LoadingProblem::ErrJoiningWorkerThreads => None,
LoadingProblem::TriedToImportAppModule => None,
LoadingProblem::FormattedReport(_, region) => *region,
LoadingProblem::ImportCycle(_, _) => None,
LoadingProblem::IncorrectModuleName(_) => None,
LoadingProblem::CouldNotFindCacheDir => None,
LoadingProblem::ChannelProblem(_) => None,
}
}
}
#[derive(Debug)]
pub enum AvailableShorthands<'a> {
@ -1179,12 +1218,12 @@ impl<'a> LoadStart<'a> {
})
.into_inner()
.into_module_ids();
let region = problem.get_region();
let report = report_loading_problem(problem, module_ids, render, palette);
// TODO try to gracefully recover and continue
// instead of changing the control flow to exit.
return Err(LoadingProblem::FormattedReport(report));
return Err(LoadingProblem::FormattedReport(report, region));
}
};
@ -1672,10 +1711,11 @@ fn state_thread_step<'a>(
}
Msg::FailedToReadFile { filename, error } => {
let buf = to_file_problem_report_string(filename, error, true);
Err(LoadingProblem::FormattedReport(buf))
Err(LoadingProblem::FormattedReport(buf, None))
}
Msg::FailedToParse(problem) => {
let region = problem.problem.problem.get_region();
let module_ids = (*state.arc_modules).lock().clone().into_module_ids();
let buf = to_parse_problem_report(
problem,
@ -1684,14 +1724,14 @@ fn state_thread_step<'a>(
state.render,
state.palette,
);
Err(LoadingProblem::FormattedReport(buf))
Err(LoadingProblem::FormattedReport(buf, region))
}
Msg::IncorrectModuleName(FileError {
problem: SourceError { problem, bytes },
filename,
}) => {
let module_ids = (*state.arc_modules).lock().clone().into_module_ids();
let buf = to_incorrect_module_name_report(
let (buf, region) = to_incorrect_module_name_report(
module_ids,
state.constrained_ident_ids,
problem,
@ -1699,7 +1739,7 @@ fn state_thread_step<'a>(
bytes,
state.render,
);
Err(LoadingProblem::FormattedReport(buf))
Err(LoadingProblem::FormattedReport(buf, Some(region)))
}
msg => {
// This is where most of the main thread's work gets done.
@ -1716,6 +1756,7 @@ fn state_thread_step<'a>(
match res_state {
Ok(new_state) => Ok(ControlFlow::Continue(new_state)),
Err(LoadingProblem::ParsingFailed(problem)) => {
let region = problem.problem.problem.get_region();
let module_ids = Arc::try_unwrap(arc_modules)
.unwrap_or_else(|_| {
panic!(
@ -1734,7 +1775,7 @@ fn state_thread_step<'a>(
render,
palette,
);
Err(LoadingProblem::FormattedReport(buf))
Err(LoadingProblem::FormattedReport(buf, region))
}
Err(LoadingProblem::ImportCycle(filename, cycle)) => {
let module_ids = arc_modules.lock().clone().into_module_ids();
@ -1747,7 +1788,7 @@ fn state_thread_step<'a>(
filename,
render,
);
return Err(LoadingProblem::FormattedReport(buf));
return Err(LoadingProblem::FormattedReport(buf, None));
}
Err(LoadingProblem::IncorrectModuleName(FileError {
problem: SourceError { problem, bytes },
@ -1756,7 +1797,7 @@ fn state_thread_step<'a>(
let module_ids = arc_modules.lock().clone().into_module_ids();
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let buf = to_incorrect_module_name_report(
let (buf, region) = to_incorrect_module_name_report(
module_ids,
root_exposed_ident_ids,
problem,
@ -1764,7 +1805,7 @@ fn state_thread_step<'a>(
bytes,
render,
);
return Err(LoadingProblem::FormattedReport(buf));
return Err(LoadingProblem::FormattedReport(buf, Some(region)));
}
Err(LoadingProblem::UnrecognizedPackageShorthand {
filename,
@ -1788,7 +1829,7 @@ fn state_thread_step<'a>(
available,
render,
);
return Err(LoadingProblem::FormattedReport(buf));
return Err(LoadingProblem::FormattedReport(buf, Some(region)));
}
Err(e) => Err(e),
}
@ -1836,8 +1877,9 @@ pub fn report_loading_problem(
bytes,
render,
)
.0
}
LoadingProblem::FormattedReport(report) => report,
LoadingProblem::FormattedReport(report, _region) => report,
LoadingProblem::FileProblem { filename, error } => {
to_file_problem_report_string(filename, error, true)
}
@ -2085,7 +2127,7 @@ fn load_multi_threaded<'a>(
"command can sometimes give a more helpful error report than other commands.\n\n"
)
.to_string(),
))
None))
})
}
}
@ -3046,7 +3088,7 @@ fn register_package_shorthands<'a>(
Problem::InvalidUrl(url_err),
module_path.to_path_buf(),
);
return Err(LoadingProblem::FormattedReport(buf));
return Err(LoadingProblem::FormattedReport(buf, None));
}
}
}
@ -3164,7 +3206,7 @@ fn finish_specialization<'a>(
Valid(To::NewPackage(p_or_p)) => PathBuf::from(p_or_p.as_str()),
other => {
let buf = report_cannot_run(state.root_id, state.root_path, other);
return Err(LoadingProblem::FormattedReport(buf));
return Err(LoadingProblem::FormattedReport(buf, None));
}
};
@ -3686,9 +3728,9 @@ enum ShorthandPath {
root_module: PathBuf,
},
RelativeToSrc {
/// e.g. "/home/rtfeldman/my-roc-code/examples/cli/cli-platform/"
/// e.g. "/home/username/roc/examples/platform-switching/zig-platform/"
root_module_dir: PathBuf,
/// e.g. "/home/rtfeldman/my-roc-code/examples/cli/cli-platform/main.roc"
/// e.g. "/home/username/roc/examples/platform-switching/zig-platform/main.roc"
root_module: PathBuf,
},
}
@ -4129,7 +4171,9 @@ fn load_packages<'a>(
Err(problem) => {
let buf = to_https_problem_report_string(src, problem, filename);
load_messages.push(Msg::FailedToLoad(LoadingProblem::FormattedReport(buf)));
load_messages.push(Msg::FailedToLoad(LoadingProblem::FormattedReport(
buf, None,
)));
return;
}
}
@ -6447,7 +6491,7 @@ fn to_incorrect_module_name_report<'a>(
filename: PathBuf,
src: &'a [u8],
render: RenderTarget,
) -> String {
) -> (String, Region) {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
@ -6488,7 +6532,7 @@ fn to_incorrect_module_name_report<'a>(
let mut buf = String::new();
let palette = DEFAULT_PALETTE;
report.render(render, &mut buf, &alloc, &palette);
buf
(buf, found.region)
}
fn to_no_platform_package_report(

View file

@ -141,7 +141,7 @@ fn multiple_modules(subdir: &str, files: Vec<(&str, &str)>) -> Result<LoadedModu
match multiple_modules_help(subdir, arena, files) {
Err(io_error) => panic!("IO trouble: {io_error:?}"),
Ok(Err(LoadingProblem::FormattedReport(buf))) => Err(buf),
Ok(Err(LoadingProblem::FormattedReport(buf, _))) => Err(buf),
Ok(Err(loading_problem)) => Err(format!("{loading_problem:?}")),
Ok(Ok(mut loaded_module)) => {
let home = loaded_module.module_id;
@ -247,7 +247,7 @@ fn load_fixture(
);
let mut loaded_module = match loaded {
Ok(x) => x,
Err(roc_load_internal::file::LoadingProblem::FormattedReport(report)) => {
Err(roc_load_internal::file::LoadingProblem::FormattedReport(report, _)) => {
println!("{report}");
panic!("{}", report);
}

View file

@ -873,7 +873,7 @@ fn specialize_union<'a, 'i>(
// We know the tag, we can specialize the decrement for the tag.
UnionFieldLayouts::Found { field_layouts, tag } => {
match environment.union_children.get(symbol) {
match environment.union_children.get(&(*symbol, tag)) {
None => keep_original_decrement!(),
Some(children) => {
// TODO perhaps this allocation can be avoided.
@ -884,13 +884,11 @@ fn specialize_union<'a, 'i>(
let mut index_symbols = MutMap::default();
for (index, _layout) in field_layouts.iter().enumerate() {
for (child, t, _i) in children_clone
for (child, _i) in children_clone
.iter()
.rev()
.filter(|(_child, _t, i)| *i == index as u64)
.filter(|(_child, i)| *i == index as u64)
{
debug_assert_eq!(tag, *t);
let removed = incremented_children.pop(child);
index_symbols.entry(index).or_insert((*child, removed));
@ -1392,7 +1390,7 @@ struct DropSpecializationEnvironment<'a> {
struct_children: MutMap<Parent, Vec<'a, (Child, Index)>>,
// Keeps track of which parent symbol is indexed by which child symbol for unions
union_children: MutMap<Parent, Vec<'a, (Child, Tag, Index)>>,
union_children: MutMap<(Parent, Tag), Vec<'a, (Child, Index)>>,
// Keeps track of which parent symbol is indexed by which child symbol for boxes
box_children: MutMap<Parent, Vec<'a, Child>>,
@ -1469,9 +1467,9 @@ impl<'a> DropSpecializationEnvironment<'a> {
fn add_union_child(&mut self, parent: Parent, child: Child, tag: u16, index: Index) {
self.union_children
.entry(parent)
.entry((parent, tag))
.or_insert_with(|| Vec::new_in(self.arena))
.push((child, tag, index));
.push((child, index));
}
fn add_list_child(&mut self, parent: Parent, child: Child, index: u64) {
@ -1494,9 +1492,12 @@ impl<'a> DropSpecializationEnvironment<'a> {
res.extend(children.iter().rev().map(|(child, _)| child));
}
if let Some(children) = self.union_children.get(parent) {
res.extend(children.iter().rev().map(|(child, _, _)| child));
}
let children = self
.union_children
.iter()
.filter(|(k, _v)| k.0 == *parent)
.flat_map(|(_k, v)| v.iter().rev());
res.extend(children.map(|(child, _)| child));
if let Some(children) = self.box_children.get(parent) {
res.extend(children.iter().rev());

View file

@ -5,7 +5,7 @@ use crate::header::{
self, AppHeader, HostedHeader, ModuleHeader, ModuleName, PackageHeader, PlatformHeader,
};
use crate::ident::Accessor;
use crate::parser::ESingleQuote;
use crate::parser::{ESingleQuote, EString};
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
use roc_collections::soa::{index_push_new, slice_extend_new};
@ -360,9 +360,9 @@ pub enum SingleQuoteLiteral<'a> {
}
impl<'a> SingleQuoteLiteral<'a> {
pub fn to_str_in(&self, arena: &'a Bump) -> &'a str {
pub fn to_str_in(&self, arena: &'a Bump) -> Result<&'a str, EString<'a>> {
match self {
SingleQuoteLiteral::PlainLine(s) => s,
SingleQuoteLiteral::PlainLine(s) => Ok(s),
SingleQuoteLiteral::Line(segments) => {
let mut s = String::new_in(arena);
for segment in *segments {
@ -370,15 +370,19 @@ impl<'a> SingleQuoteLiteral<'a> {
SingleQuoteSegment::Plaintext(s2) => s.push_str(s2),
SingleQuoteSegment::Unicode(loc) => {
let s2 = loc.value;
let c = u32::from_str_radix(s2, 16).expect("Invalid unicode escape");
s.push(char::from_u32(c).expect("Invalid unicode codepoint"));
let c = u32::from_str_radix(s2, 16)
.map_err(|_| EString::UnicodeEscapeTooLarge(loc.region))?;
s.push(
char::from_u32(c)
.ok_or(EString::InvalidUnicodeCodepoint(loc.region))?,
);
}
SingleQuoteSegment::EscapedChar(c) => {
s.push(c.unescape());
}
}
}
s.into_bump_str()
Ok(s.into_bump_str())
}
}
}
@ -896,7 +900,7 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
}
}
fn push_pending_from_expr(&mut self, expr: &'b Expr<'a>) {
pub fn push_pending_from_expr(&mut self, expr: &'b Expr<'a>) {
let mut expr_stack = vec![expr];
use Expr::*;

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